private void PollEditorState(BackendUnityModel backendUnityModel, FrontendBackendHost frontendBackendHost, Lifetime modelLifetime, IThreading threading, ILogger logger) { if (!backendUnityModel.IsBound) { myEditorState = UnityEditorState.Disconnected; UpdateFrontendEditorState(frontendBackendHost, logger); return; } var task = backendUnityModel.GetUnityEditorState.Start(Unit.Instance); task?.Result.AdviseOnce(modelLifetime, result => { logger.Trace($"Got poll result from Unity editor: {result.Result}"); myEditorState = result.Result; UpdateFrontendEditorState(frontendBackendHost, logger); }); Task.Delay(TimeSpan.FromSeconds(2), modelLifetime).ContinueWith(_ => { if (task != null && !task.AsTask().IsCompleted) { logger.Trace( "There were no response from Unity in two seconds. Setting state to Disconnected."); myEditorState = UnityEditorState.Disconnected; UpdateFrontendEditorState(frontendBackendHost, logger); } }, threading.Tasks.GuardedMainThreadScheduler); }
private void AdviseUnityToFrontendModel(Lifetime lifetime, BackendUnityModel backendUnityModel) { // ********************************************************************************************************* // // WARNING // // Be very careful with stateful properties! // // When the backend/Unity protocol is closed, the existing properties maintain their current values. This // doesn't affect BackendUnityModel because we clear the model when the connection is lost. However, it does // affect any properties that have had values flowed in from BackedUnityModel - these values are not reset. // // When the backend/Unity protocol is (re)created and advertised, we *should* have initial values from the // Unity end (the model is advertised asynchronously to being created, and the dispatcher *should* have // processed messages). However, we cannot guarantee this - during testing, it usually works as expected, // but occasionally wouldn't be fully initialised. These means we need to be careful when assuming that // initial values are available in the properties. Advise and RdExtensions.FlowIntoRdSafe will correctly set // the target value if the source value exists. Avoid BeUtilExtensions.FlowIntoRd, as that will throw an // exception if the source value does not yet exist. // Note that creating and advertising the model, as well as all callbacks, happen on the main thread. // // We must ensure that the Unity end (re)initialises properties when the protocol is created, or we could // have stale or empty properties here and in the frontend. // // ********************************************************************************************************* var frontendBackendModel = myFrontendBackendHost.Model.NotNull("frontendBackendModel != null"); AdviseApplicationData(lifetime, backendUnityModel, frontendBackendModel); AdviseApplicationSettings(lifetime, backendUnityModel, frontendBackendModel); AdviseProjectSettings(lifetime, backendUnityModel, frontendBackendModel); AdvisePlayControls(lifetime, backendUnityModel, frontendBackendModel); AdviseConsoleEvents(lifetime, backendUnityModel, frontendBackendModel); AdviseOpenFile(backendUnityModel, frontendBackendModel); }
private static void SetConnectionPollHandler(BackendUnityModel backendUnityModel) { // Set up result for polling. Called before the Unity editor tries to use the protocol to open a // file. It ensures that the protocol is connected and active. // TODO: Is there a simpler check that the model is still connected? backendUnityModel.IsBackendConnected.Set(_ => true); }
private static void GetInitTime(BackendUnityModel model) { model.ConsoleLogging.LastInitTime.SetValue(ourInitTime); #if !UNITY_4_7 && !UNITY_5_5 && !UNITY_5_6 var enterPlayTime = long.Parse(SessionState.GetString("Rider_EnterPlayMode_DateTime", "0")); model.ConsoleLogging.LastPlayTime.SetValue(enterPlayTime); #endif }
private static void AdviseRunMethod(BackendUnityModel model) { model.RunMethodInUnity.Set((lifetime, data) => { var task = new RdTask <RunMethodResult>(); MainThreadDispatcher.Instance.Queue(() => { if (!lifetime.IsAlive) { task.SetCancelled(); return; } try { ourLogger.Verbose($"Attempt to execute {data.MethodName}"); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); var assembly = assemblies .FirstOrDefault(a => a.GetName().Name.Equals(data.AssemblyName)); if (assembly == null) { throw new Exception($"Could not find {data.AssemblyName} assembly in current AppDomain"); } var type = assembly.GetType(data.TypeName); if (type == null) { throw new Exception($"Could not find {data.TypeName} in assembly {data.AssemblyName}."); } var method = type.GetMethod(data.MethodName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); if (method == null) { throw new Exception($"Could not find {data.MethodName} in type {data.TypeName}"); } try { method.Invoke(null, null); } catch (Exception e) { Debug.LogException(e); } task.Set(new RunMethodResult(true, string.Empty, string.Empty)); } catch (Exception e) { ourLogger.Log(LoggingLevel.WARN, $"Execute {data.MethodName} failed.", e); task.Set(new RunMethodResult(false, e.Message, e.StackTrace)); } }); return(task); }); }
private void AdvisePackages(BackendUnityModel backendUnityModel, Lifetime modelLifetime, PackageManager packageManager) { backendUnityModel.UnityApplicationData.Advise(modelLifetime, _ => { // When the backend gets new application data, refresh packages, so we can be up to date with // builtin packages. Note that we don't refresh when we lose the model. This means we're // potentially viewing stale builtin packages, but that's ok. It's better than clearing all packages packageManager.RefreshPackages(); }); }
private static void SetRiderProcessId(BackendUnityModel backendUnityModel) { if (PlatformUtil.RuntimePlatform == PlatformUtil.Platform.Windows) { // RiderProcessId is only used on Windows (for AllowSetForegroundWindow) var frontendProcess = Process.GetCurrentProcess().GetParent(); if (frontendProcess != null) { backendUnityModel.RiderProcessId.SetValue(frontendProcess.Id); } } }
private static void GetBuildLocation(BackendUnityModel model) { var path = EditorUserBuildSettings.GetBuildLocation(EditorUserBuildSettings.selectedStandaloneTarget); if (PluginSettings.SystemInfoRiderPlugin.operatingSystemFamily == OperatingSystemFamilyRider.MacOSX) { path = Path.Combine(Path.Combine(Path.Combine(path, "Contents"), "MacOS"), PlayerSettings.productName); } if (!string.IsNullOrEmpty(path) && File.Exists(path)) { model.UnityProjectSettings.BuildLocation.Value = path; } }
private static void AdviseShowPreferences(BackendUnityModel model, Lifetime connectionLifetime, ILog log) { model.ShowPreferences.Advise(connectionLifetime, result => { if (result != null) { MainThreadDispatcher.Instance.Queue(() => { try { var tab = UnityUtils.UnityVersion >= new Version(2018, 2) ? "_General" : "Rider"; var type = typeof(SceneView).Assembly.GetType("UnityEditor.SettingsService"); if (type != null) { // 2018+ var method = type.GetMethod("OpenUserPreferences", BindingFlags.Static | BindingFlags.Public); if (method == null) { log.Error("'OpenUserPreferences' was not found"); } else { method.Invoke(null, new object[] { $"Preferences/{tab}" }); } } else { // 5.5, 2017 ... type = typeof(SceneView).Assembly.GetType("UnityEditor.PreferencesWindow"); var method = type?.GetMethod("ShowPreferencesWindow", BindingFlags.Static | BindingFlags.NonPublic); if (method == null) { log.Error("'ShowPreferencesWindow' was not found"); } else { method.Invoke(null, null); } } } catch (Exception ex) { log.Error("Show preferences " + ex); } }); } }); }
private static void AdviseRefresh(BackendUnityModel model) { model.Refresh.Set((l, force) => { var refreshTask = new RdTask <Unit>(); void SendResult() { if (!EditorApplication.isCompiling) { // ReSharper disable once DelegateSubtraction EditorApplication.update -= SendResult; ourLogger.Verbose("Refresh: SyncSolution Completed"); refreshTask.Set(Unit.Instance); } } ourLogger.Verbose("Refresh: SyncSolution Enqueue"); MainThreadDispatcher.Instance.Queue(() => { if (!EditorApplication.isPlaying && EditorPrefsWrapper.AutoRefresh || force != RefreshType.Normal) { try { if (force == RefreshType.ForceRequestScriptReload) { ourLogger.Verbose("Refresh: RequestScriptReload"); UnityEditorInternal.InternalEditorUtility.RequestScriptReload(); } ourLogger.Verbose("Refresh: SyncSolution Started"); UnityUtils.SyncSolution(); } catch (Exception e) { ourLogger.Error("Refresh failed with exception", e); } finally { EditorApplication.update += SendResult; } } else { refreshTask.Set(Unit.Instance); ourLogger.Verbose("AutoRefresh is disabled via Unity settings."); } }); return(refreshTask); }); }
private void TrackActivity(BackendUnityModel backendUnityModel, Lifetime modelLifetime) { backendUnityModel.UnityApplicationData.AdviseOnce(modelLifetime, data => { // ApplicationVersion may look like `2017.2.1f1-CustomPostfix` var unityVersion = UnityVersion.VersionToString(UnityVersion.Parse(data.ApplicationVersion)); if (data.ApplicationVersion.StartsWith(unityVersion) && unityVersion != data.ApplicationVersion) { myUsageStatistics.TrackActivity("UnityVersion", unityVersion + "-custom"); // impersonate, but still track that it is custom build } else { myUsageStatistics.TrackActivity("UnityVersion", unityVersion); } }); backendUnityModel.UnityProjectSettings.ScriptingRuntime.AdviseOnce(modelLifetime, runtime => { myUsageStatistics.TrackActivity("ScriptingRuntime", runtime.ToString()); }); }
internal static bool CheckConnectedToBackendSync(BackendUnityModel model) { if (model == null) { return(false); } var connected = false; try { // HostConnected also means that in Rider and in Unity the same solution is opened connected = model.IsBackendConnected.Sync(Unit.Instance, new RpcTimeouts(TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(200))); } catch (Exception) { ourLogger.Verbose("Rider Protocol not connected."); } return(connected); }
private static void AdviseEditorState(BackendUnityModel modelValue) { modelValue.GetUnityEditorState.Set(rdVoid => { if (EditorApplication.isPaused) { return(UnityEditorState.Pause); } if (EditorApplication.isPlaying) { return(UnityEditorState.Play); } if (EditorApplication.isCompiling || EditorApplication.isUpdating) { return(UnityEditorState.Refresh); } return(UnityEditorState.Idle); }); }
private void StartPollingUnityEditorState(BackendUnityModel backendUnityModel, Lifetime modelLifetime, FrontendBackendHost frontendBackendHost, IThreading threading, IIsApplicationActiveState isApplicationActiveState, ILogger logger) { modelLifetime.StartAsync(threading.Tasks.GuardedMainThreadScheduler, async() => { // TODO: This would be much easier with a property // Would have to reset the property when the connection drops while (modelLifetime.IsAlive) { if (isApplicationActiveState.IsApplicationActive.Value || frontendBackendHost.Model?.RiderFrontendTests.HasTrueValue() == true) { PollEditorState(backendUnityModel, frontendBackendHost, modelLifetime, threading, logger); } await Task.Delay(1000, modelLifetime); } }); }
private static void AdviseExitUnity(BackendUnityModel model) { model.ExitUnity.Set((_, rdVoid) => { var task = new RdTask <bool>(); MainThreadDispatcher.Instance.Queue(() => { try { ourLogger.Verbose("ExitUnity: Started"); EditorApplication.Exit(0); ourLogger.Verbose("ExitUnity: Completed"); task.Set(true); } catch (Exception e) { ourLogger.Log(LoggingLevel.WARN, "EditorApplication.Exit failed.", e); task.Set(false); } }); return(task); }); }
private static void AdviseGenerateUISchema(BackendUnityModel model) { model.GenerateUIElementsSchema.Set(_ => UIElementsSupport.GenerateSchema()); }
private static void InitialiseModel(BackendUnityModel backendUnityModel) { SetConnectionPollHandler(backendUnityModel); SetRiderProcessId(backendUnityModel); }
public UnityModelAndLifetime(BackendUnityModel model, Lifetime lifetime) { Model = model; Lifetime = lifetime; }
private static void AdviseUnityActions(BackendUnityModel model, Lifetime connectionLifetime) { var syncPlayState = new Action(() => { MainThreadDispatcher.Instance.Queue(() => { var isPlaying = EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying; if (!model.PlayControls.Play.HasValue() || model.PlayControls.Play.HasValue() && model.PlayControls.Play.Value != isPlaying) { ourLogger.Verbose("Reporting play mode change to model: {0}", isPlaying); model.PlayControls.Play.SetValue(isPlaying); } var isPaused = EditorApplication.isPaused; if (!model.PlayControls.Pause.HasValue() || model.PlayControls.Pause.HasValue() && model.PlayControls.Pause.Value != isPaused) { ourLogger.Verbose("Reporting pause mode change to model: {0}", isPaused); model.PlayControls.Pause.SetValue(isPaused); } }); }); syncPlayState(); model.PlayControls.Play.Advise(connectionLifetime, play => { MainThreadDispatcher.Instance.Queue(() => { var current = EditorApplication.isPlayingOrWillChangePlaymode && EditorApplication.isPlaying; if (current != play) { ourLogger.Verbose("Request to change play mode from model: {0}", play); EditorApplication.isPlaying = play; } }); }); model.PlayControls.Pause.Advise(connectionLifetime, pause => { MainThreadDispatcher.Instance.Queue(() => { ourLogger.Verbose("Request to change pause mode from model: {0}", pause); EditorApplication.isPaused = pause; }); }); model.PlayControls.Step.Advise(connectionLifetime, x => { MainThreadDispatcher.Instance.Queue(EditorApplication.Step); }); var onPlaymodeStateChanged = new EditorApplication.CallbackFunction(() => syncPlayState()); // left for compatibility with Unity <= 5.5 #pragma warning disable 618 connectionLifetime.AddBracket(() => { EditorApplication.playmodeStateChanged += onPlaymodeStateChanged; }, () => { EditorApplication.playmodeStateChanged -= onPlaymodeStateChanged; }); #pragma warning restore 618 // new api - not present in Unity 5.5 // private static Action<PauseState> IsPauseStateChanged(UnityModel model) // { // return state => model?.Pause.SetValue(state == PauseState.Paused); // } }
private void CreateProtocol(FileSystemPath protocolInstancePath) { var protocolInstance = GetProtocolInstanceData(protocolInstancePath); if (protocolInstance == null) { return; } myLogger.Info($"EditorPlugin protocol port {protocolInstance.Port} for Solution: {protocolInstance.SolutionName}."); if (protocolInstance.ProtocolGuid != ProtocolCompatibility.ProtocolGuid) { OnOutOfSync(myLifetime); myLogger.Info("Avoid attempt to create protocol, incompatible."); return; } try { var thisSessionLifetime = mySessionLifetimes.Next(); myLogger.Info("Create protocol..."); myLogger.Info("Creating SocketWire with port = {0}", protocolInstance.Port); var wire = new SocketWire.Client(thisSessionLifetime, myDispatcher, protocolInstance.Port, "UnityClient") { BackwardsCompatibleWireFormat = true }; var protocol = new Rd.Impl.Protocol("UnityEditorPlugin", new Serializers(thisSessionLifetime, null, null), new Identities(IdKind.Client), myDispatcher, wire, thisSessionLifetime) { ThrowErrorOnOutOfSyncModels = false }; protocol.OutOfSyncModels.AdviseOnce(thisSessionLifetime, _ => OnOutOfSync(thisSessionLifetime)); wire.Connected.WhenTrue(thisSessionLifetime, connectionLifetime => { myLogger.Info("WireConnected."); var backendUnityModel = new BackendUnityModel(connectionLifetime, protocol); SafeExecuteOrQueueEx("setModel", () => myBackendUnityHost.BackendUnityModel.SetValue(backendUnityModel)); connectionLifetime.OnTermination(() => { SafeExecuteOrQueueEx("clearModel", () => { myLogger.Info("Wire disconnected."); // Clear model myBackendUnityHost.BackendUnityModel.SetValue(null); }); }); }); } catch (Exception ex) { myLogger.Error(ex); } }
public ModelWithLifetime(BackendUnityModel model, Lifetime lifetime) { Model = model; Lifetime = lifetime; }
private static int CreateProtocolForSolution(Lifetime lifetime, string solutionName, Action onDisconnected) { try { var dispatcher = MainThreadDispatcher.Instance; var currentWireAndProtocolLifetimeDef = lifetime.CreateNested(); var currentWireAndProtocolLifetime = currentWireAndProtocolLifetimeDef.Lifetime; var riderProtocolController = new RiderProtocolController(dispatcher, currentWireAndProtocolLifetime); #if !NET35 var serializers = new Serializers(lifetime, null, null); #else var serializers = new Serializers(); #endif var identities = new Identities(IdKind.Server); MainThreadDispatcher.AssertThread(); var protocol = new Protocol("UnityEditorPlugin" + solutionName, serializers, identities, MainThreadDispatcher.Instance, riderProtocolController.Wire, currentWireAndProtocolLifetime); riderProtocolController.Wire.Connected.WhenTrue(currentWireAndProtocolLifetime, connectionLifetime => { ourLogger.Log(LoggingLevel.VERBOSE, "Create UnityModel and advise for new sessions..."); var model = new BackendUnityModel(connectionLifetime, protocol); AdviseUnityActions(model, connectionLifetime); AdviseEditorState(model); OnModelInitialization(new UnityModelAndLifetime(model, connectionLifetime)); AdviseRefresh(model); var paths = GetLogPaths(); model.UnityApplicationData.SetValue(new UnityApplicationData( EditorApplication.applicationPath, EditorApplication.applicationContentsPath, UnityUtils.UnityApplicationVersion, paths[0], paths[1], Process.GetCurrentProcess().Id)); model.UnityApplicationSettings.ScriptCompilationDuringPlay.Set(UnityUtils.SafeScriptCompilationDuringPlay); model.UnityProjectSettings.ScriptingRuntime.SetValue(UnityUtils.ScriptingRuntime); AdviseShowPreferences(model, connectionLifetime, ourLogger); AdviseGenerateUISchema(model); AdviseExitUnity(model); GetBuildLocation(model); AdviseRunMethod(model); GetInitTime(model); ourLogger.Verbose("UnityModel initialized."); var pair = new ModelWithLifetime(model, connectionLifetime); connectionLifetime.OnTermination(() => { UnityModels.Remove(pair); }); UnityModels.Add(pair); connectionLifetime.OnTermination(() => { ourLogger.Verbose($"Connection lifetime is not alive for {solutionName}, destroying protocol"); onDisconnected(); }); }); return(riderProtocolController.Wire.Port); } catch (Exception ex) { ourLogger.Error("Init Rider Plugin " + ex); return(-1); } }
private void AdviseModel(BackendUnityModel backendUnityModel, in Lifetime modelLifetime)
// Subscribe to changes from the protocol private void AdviseModel(BackendUnityModel backendUnityModel, Lifetime modelLifetime, PackageManager packageManager) { AdvisePackages(backendUnityModel, modelLifetime, packageManager); TrackActivity(backendUnityModel, modelLifetime); }