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);
        }
示例#2
0
        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);
        }
示例#3
0
 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);
 }
示例#4
0
        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
        }
示例#5
0
        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();
     });
 }
示例#7
0
 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);
         }
     }
 }
示例#8
0
        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;
            }
        }
示例#9
0
        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);
                        }
                    });
                }
            });
        }
示例#10
0
        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());
     });
 }
示例#12
0
        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);
        }
示例#13
0
        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);
                }
            });
        }
示例#15
0
 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);
     });
 }
示例#16
0
 private static void AdviseGenerateUISchema(BackendUnityModel model)
 {
     model.GenerateUIElementsSchema.Set(_ => UIElementsSupport.GenerateSchema());
 }
示例#17
0
 private static void InitialiseModel(BackendUnityModel backendUnityModel)
 {
     SetConnectionPollHandler(backendUnityModel);
     SetRiderProcessId(backendUnityModel);
 }
示例#18
0
 public UnityModelAndLifetime(BackendUnityModel model, Lifetime lifetime)
 {
     Model    = model;
     Lifetime = lifetime;
 }
示例#19
0
        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);
            //    }
        }
示例#20
0
        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;
 }
示例#22
0
        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);
            }
        }
示例#23
0
 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);
 }