123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using Microsoft.Unity.VisualStudio.Editor.Messaging;
- using Microsoft.Unity.VisualStudio.Editor.Testing;
- using UnityEditor;
- using UnityEngine;
- using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType;
-
- namespace Microsoft.Unity.VisualStudio.Editor
- {
- [InitializeOnLoad]
- internal class VisualStudioIntegration
- {
- class Client
- {
- public IPEndPoint EndPoint { get; set; }
- public double LastMessage { get; set; }
- }
-
- private static Messager _messager;
-
- private static readonly Queue<Message> _incoming = new Queue<Message>();
- private static readonly Dictionary<IPEndPoint, Client> _clients = new Dictionary<IPEndPoint, Client>();
- private static readonly object _incomingLock = new object();
- private static readonly object _clientsLock = new object();
-
- static VisualStudioIntegration()
- {
- if (!VisualStudioEditor.IsEnabled)
- return;
-
- RunOnceOnUpdate(() =>
- {
- // Despite using ReuseAddress|!ExclusiveAddressUse, we can fail here:
- // - if another application is using this port with exclusive access
- // - or if the firewall is not properly configured
- var messagingPort = MessagingPort();
-
- try
- {
- _messager = Messager.BindTo(messagingPort);
- _messager.ReceiveMessage += ReceiveMessage;
- }
- catch (SocketException)
- {
- // We'll have a chance to try to rebind on next domain reload
- Debug.LogWarning($"Unable to use UDP port {messagingPort} for VS/Unity messaging. You should check if another process is already bound to this port or if your firewall settings are compatible.");
- }
-
- RunOnShutdown(Shutdown);
- });
-
- EditorApplication.update += OnUpdate;
-
- CheckLegacyAssemblies();
- }
-
- private static void CheckLegacyAssemblies()
- {
- var checkList = new HashSet<string>(new[] { KnownAssemblies.UnityVS, KnownAssemblies.Messaging, KnownAssemblies.Bridge });
-
- try
- {
- var assemblies = AppDomain
- .CurrentDomain
- .GetAssemblies()
- .Where(a => checkList.Contains(a.GetName().Name));
-
- foreach (var assembly in assemblies)
- {
- // for now we only want to warn against local assemblies, do not check externals.
- var relativePath = FileUtility.MakeRelativeToProjectPath(assembly.Location);
- if (relativePath == null)
- continue;
-
- Debug.LogWarning($"Project contains legacy assembly that could interfere with the Visual Studio Package. You should delete {relativePath}");
- }
- }
- catch (Exception)
- {
- // abandon legacy check
- }
- }
-
- private static void RunOnceOnUpdate(Action action)
- {
- var callback = null as EditorApplication.CallbackFunction;
-
- callback = () =>
- {
- EditorApplication.update -= callback;
- action();
- };
-
- EditorApplication.update += callback;
- }
-
- private static void RunOnShutdown(Action action)
- {
- // Mono on OSX has all kinds of quirks on AppDomain shutdown
- #if UNITY_EDITOR_WIN
- AppDomain.CurrentDomain.DomainUnload += (_, __) => action();
- #endif
- }
-
- private static int DebuggingPort()
- {
- return 56000 + (System.Diagnostics.Process.GetCurrentProcess().Id % 1000);
- }
-
- private static int MessagingPort()
- {
- return DebuggingPort() + 2;
- }
-
- private static void ReceiveMessage(object sender, MessageEventArgs args)
- {
- OnMessage(args.Message);
- }
-
- private static void OnUpdate()
- {
- lock (_incomingLock)
- {
- while (_incoming.Count > 0)
- {
- ProcessIncoming(_incoming.Dequeue());
- }
- }
-
- lock (_clientsLock)
- {
- foreach (var client in _clients.Values.ToArray())
- {
- // EditorApplication.timeSinceStartup: The time since the editor was started, in seconds, not reset when starting play mode.
- if (EditorApplication.timeSinceStartup - client.LastMessage > 4)
- _clients.Remove(client.EndPoint);
- }
- }
- }
-
- private static void AddMessage(Message message)
- {
- lock (_incomingLock)
- {
- _incoming.Enqueue(message);
- }
- }
-
- private static void ProcessIncoming(Message message)
- {
- lock (_clientsLock)
- {
- CheckClient(message);
- }
-
- switch (message.Type)
- {
- case MessageType.Ping:
- Answer(message, MessageType.Pong);
- break;
- case MessageType.Play:
- Shutdown();
- EditorApplication.isPlaying = true;
- break;
- case MessageType.Stop:
- EditorApplication.isPlaying = false;
- break;
- case MessageType.Pause:
- EditorApplication.isPaused = true;
- break;
- case MessageType.Unpause:
- EditorApplication.isPaused = false;
- break;
- case MessageType.Build:
- // Not used anymore
- break;
- case MessageType.Refresh:
- Refresh();
- break;
- case MessageType.Version:
- Answer(message, MessageType.Version, PackageVersion());
- break;
- case MessageType.UpdatePackage:
- // Not used anymore
- break;
- case MessageType.ProjectPath:
- Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, "..")));
- break;
- case MessageType.ExecuteTests:
- TestRunnerApiListener.ExecuteTests(message.Value);
- break;
- case MessageType.RetrieveTestList:
- TestRunnerApiListener.RetrieveTestList(message.Value);
- break;
- case MessageType.ShowUsage:
- UsageUtility.ShowUsage(message.Value);
- break;
- }
- }
-
- private static void CheckClient(Message message)
- {
- var endPoint = message.Origin;
-
- if (!_clients.TryGetValue(endPoint, out var client))
- {
- client = new Client
- {
- EndPoint = endPoint,
- LastMessage = EditorApplication.timeSinceStartup
- };
-
- _clients.Add(endPoint, client);
- }
- else
- {
- client.LastMessage = EditorApplication.timeSinceStartup;
- }
- }
-
- internal static string PackageVersion()
- {
- var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly);
- return package.version;
- }
-
- private static void Refresh()
- {
- // If the user disabled auto-refresh in Unity, do not try to force refresh the Asset database
- if (!EditorPrefs.GetBool("kAutoRefresh", true))
- return;
-
- if (UnityInstallation.IsInSafeMode)
- return;
-
- RunOnceOnUpdate(AssetDatabase.Refresh);
- }
-
- private static void OnMessage(Message message)
- {
- AddMessage(message);
- }
-
- private static void Answer(Client client, MessageType answerType, string answerValue)
- {
- Answer(client.EndPoint, answerType, answerValue);
- }
-
- private static void Answer(Message message, MessageType answerType, string answerValue = "")
- {
- var targetEndPoint = message.Origin;
-
- Answer(
- targetEndPoint,
- answerType,
- answerValue);
- }
-
- private static void Answer(IPEndPoint targetEndPoint, MessageType answerType, string answerValue)
- {
- _messager?.SendMessage(targetEndPoint, answerType, answerValue);
- }
-
- private static void Shutdown()
- {
- if (_messager == null)
- return;
-
- _messager.ReceiveMessage -= ReceiveMessage;
- _messager.Dispose();
- _messager = null;
- }
-
- internal static void BroadcastMessage(MessageType type, string value)
- {
- lock (_clientsLock)
- {
- foreach (var client in _clients.Values.ToArray())
- {
- Answer(client, type, value);
- }
- }
- }
- }
- }
|