You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
8.3 KiB
326 lines
8.3 KiB
2 years ago
|
/*---------------------------------------------------------------------------------------------
|
||
|
* 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 UnityEditor.PackageManager;
|
||
|
using UnityEditor.PackageManager.Requests;
|
||
|
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 DateTime 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();
|
||
|
|
||
|
private static ListRequest _listRequest;
|
||
|
|
||
|
static VisualStudioIntegration()
|
||
|
{
|
||
|
if (!VisualStudioEditor.IsEnabled)
|
||
|
return;
|
||
|
|
||
|
if (!SessionSettings.PackageVersionChecked)
|
||
|
_listRequest = UnityEditor.PackageManager.Client.List();
|
||
|
|
||
|
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 (!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 HandleListRequestCompletion()
|
||
|
{
|
||
|
const string packageName = "com.unity.ide.visualstudio";
|
||
|
|
||
|
if (_listRequest.Status == StatusCode.Success)
|
||
|
{
|
||
|
var package = _listRequest.Result.FirstOrDefault(p => p.name == packageName);
|
||
|
|
||
|
if (package != null
|
||
|
&& Version.TryParse(package.version, out var packageVersion)
|
||
|
&& Version.TryParse(package.versions.latest, out var latestVersion)
|
||
|
&& packageVersion < latestVersion)
|
||
|
{
|
||
|
Debug.LogWarning($"Visual Studio Editor Package version {package.versions.latest} is available, we strongly encourage you to update from the Unity Package Manager for a better Visual Studio integration");
|
||
|
}
|
||
|
|
||
|
SessionSettings.PackageVersionChecked = true;
|
||
|
}
|
||
|
|
||
|
_listRequest = null;
|
||
|
}
|
||
|
|
||
|
private static void OnUpdate()
|
||
|
{
|
||
|
if (_listRequest != null && _listRequest.IsCompleted)
|
||
|
{
|
||
|
HandleListRequestCompletion();
|
||
|
}
|
||
|
|
||
|
lock (_incomingLock)
|
||
|
{
|
||
|
while (_incoming.Count > 0)
|
||
|
{
|
||
|
ProcessIncoming(_incoming.Dequeue());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lock (_clientsLock)
|
||
|
{
|
||
|
foreach (var client in _clients.Values.ToArray())
|
||
|
{
|
||
|
if (DateTime.Now.Subtract(client.LastMessage) > TimeSpan.FromMilliseconds(4000))
|
||
|
_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 = DateTime.Now
|
||
|
};
|
||
|
|
||
|
_clients.Add(endPoint, client);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
client.LastMessage = DateTime.Now;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|