| |
| |
| |
| |
| 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(() => |
| { |
| |
| |
| |
| var messagingPort = MessagingPort(); |
|
|
| try |
| { |
| _messager = Messager.BindTo(messagingPort); |
| _messager.ReceiveMessage += ReceiveMessage; |
| } |
| catch (SocketException) |
| { |
| |
| 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) |
| { |
| |
| 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) |
| { |
| |
| } |
| } |
|
|
| 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) |
| { |
| |
| if (!VisualStudioEditor.IsWindows) |
| return; |
|
|
| AppDomain.CurrentDomain.DomainUnload += (_, __) => action(); |
| } |
|
|
| 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()) |
| { |
| |
| 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: |
| |
| break; |
| case MessageType.Refresh: |
| Refresh(); |
| break; |
| case MessageType.Version: |
| Answer(message, MessageType.Version, PackageVersion()); |
| break; |
| case MessageType.UpdatePackage: |
| |
| 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 (!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); |
| } |
| } |
| } |
| } |
| } |
|
|