A game about forced loneliness, made by TACStudios
at master 7.6 kB view raw
1/*--------------------------------------------------------------------------------------------- 2 * Copyright (c) Microsoft Corporation. All rights reserved. 3 * Licensed under the MIT License. See License.txt in the project root for license information. 4 *--------------------------------------------------------------------------------------------*/ 5using System; 6using System.Collections.Generic; 7using System.IO; 8using System.Linq; 9using System.Net; 10using System.Net.Sockets; 11using Microsoft.Unity.VisualStudio.Editor.Messaging; 12using Microsoft.Unity.VisualStudio.Editor.Testing; 13using UnityEditor; 14using UnityEngine; 15using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType; 16 17namespace Microsoft.Unity.VisualStudio.Editor 18{ 19 [InitializeOnLoad] 20 internal class VisualStudioIntegration 21 { 22 class Client 23 { 24 public IPEndPoint EndPoint { get; set; } 25 public double LastMessage { get; set; } 26 } 27 28 private static Messager _messager; 29 30 private static readonly Queue<Message> _incoming = new Queue<Message>(); 31 private static readonly Dictionary<IPEndPoint, Client> _clients = new Dictionary<IPEndPoint, Client>(); 32 private static readonly object _incomingLock = new object(); 33 private static readonly object _clientsLock = new object(); 34 35 static VisualStudioIntegration() 36 { 37 if (!VisualStudioEditor.IsEnabled) 38 return; 39 40 RunOnceOnUpdate(() => 41 { 42 // Despite using ReuseAddress|!ExclusiveAddressUse, we can fail here: 43 // - if another application is using this port with exclusive access 44 // - or if the firewall is not properly configured 45 var messagingPort = MessagingPort(); 46 47 try 48 { 49 _messager = Messager.BindTo(messagingPort); 50 _messager.ReceiveMessage += ReceiveMessage; 51 } 52 catch (SocketException) 53 { 54 // We'll have a chance to try to rebind on next domain reload 55 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."); 56 } 57 58 RunOnShutdown(Shutdown); 59 }); 60 61 EditorApplication.update += OnUpdate; 62 63 CheckLegacyAssemblies(); 64 } 65 66 private static void CheckLegacyAssemblies() 67 { 68 var checkList = new HashSet<string>(new[] { KnownAssemblies.UnityVS, KnownAssemblies.Messaging, KnownAssemblies.Bridge }); 69 70 try 71 { 72 var assemblies = AppDomain 73 .CurrentDomain 74 .GetAssemblies() 75 .Where(a => checkList.Contains(a.GetName().Name)); 76 77 foreach (var assembly in assemblies) 78 { 79 // for now we only want to warn against local assemblies, do not check externals. 80 var relativePath = FileUtility.MakeRelativeToProjectPath(assembly.Location); 81 if (relativePath == null) 82 continue; 83 84 Debug.LogWarning($"Project contains legacy assembly that could interfere with the Visual Studio Package. You should delete {relativePath}"); 85 } 86 } 87 catch (Exception) 88 { 89 // abandon legacy check 90 } 91 } 92 93 private static void RunOnceOnUpdate(Action action) 94 { 95 var callback = null as EditorApplication.CallbackFunction; 96 97 callback = () => 98 { 99 EditorApplication.update -= callback; 100 action(); 101 }; 102 103 EditorApplication.update += callback; 104 } 105 106 private static void RunOnShutdown(Action action) 107 { 108 // Mono on OSX has all kinds of quirks on AppDomain shutdown 109#if UNITY_EDITOR_WIN 110 AppDomain.CurrentDomain.DomainUnload += (_, __) => action(); 111#endif 112 } 113 114 private static int DebuggingPort() 115 { 116 return 56000 + (System.Diagnostics.Process.GetCurrentProcess().Id % 1000); 117 } 118 119 private static int MessagingPort() 120 { 121 return DebuggingPort() + 2; 122 } 123 124 private static void ReceiveMessage(object sender, MessageEventArgs args) 125 { 126 OnMessage(args.Message); 127 } 128 129 private static void OnUpdate() 130 { 131 lock (_incomingLock) 132 { 133 while (_incoming.Count > 0) 134 { 135 ProcessIncoming(_incoming.Dequeue()); 136 } 137 } 138 139 lock (_clientsLock) 140 { 141 foreach (var client in _clients.Values.ToArray()) 142 { 143 // EditorApplication.timeSinceStartup: The time since the editor was started, in seconds, not reset when starting play mode. 144 if (EditorApplication.timeSinceStartup - client.LastMessage > 4) 145 _clients.Remove(client.EndPoint); 146 } 147 } 148 } 149 150 private static void AddMessage(Message message) 151 { 152 lock (_incomingLock) 153 { 154 _incoming.Enqueue(message); 155 } 156 } 157 158 private static void ProcessIncoming(Message message) 159 { 160 lock (_clientsLock) 161 { 162 CheckClient(message); 163 } 164 165 switch (message.Type) 166 { 167 case MessageType.Ping: 168 Answer(message, MessageType.Pong); 169 break; 170 case MessageType.Play: 171 Shutdown(); 172 EditorApplication.isPlaying = true; 173 break; 174 case MessageType.Stop: 175 EditorApplication.isPlaying = false; 176 break; 177 case MessageType.Pause: 178 EditorApplication.isPaused = true; 179 break; 180 case MessageType.Unpause: 181 EditorApplication.isPaused = false; 182 break; 183 case MessageType.Build: 184 // Not used anymore 185 break; 186 case MessageType.Refresh: 187 Refresh(); 188 break; 189 case MessageType.Version: 190 Answer(message, MessageType.Version, PackageVersion()); 191 break; 192 case MessageType.UpdatePackage: 193 // Not used anymore 194 break; 195 case MessageType.ProjectPath: 196 Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, ".."))); 197 break; 198 case MessageType.ExecuteTests: 199 TestRunnerApiListener.ExecuteTests(message.Value); 200 break; 201 case MessageType.RetrieveTestList: 202 TestRunnerApiListener.RetrieveTestList(message.Value); 203 break; 204 case MessageType.ShowUsage: 205 UsageUtility.ShowUsage(message.Value); 206 break; 207 } 208 } 209 210 private static void CheckClient(Message message) 211 { 212 var endPoint = message.Origin; 213 214 if (!_clients.TryGetValue(endPoint, out var client)) 215 { 216 client = new Client 217 { 218 EndPoint = endPoint, 219 LastMessage = EditorApplication.timeSinceStartup 220 }; 221 222 _clients.Add(endPoint, client); 223 } 224 else 225 { 226 client.LastMessage = EditorApplication.timeSinceStartup; 227 } 228 } 229 230 internal static string PackageVersion() 231 { 232 var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly); 233 return package.version; 234 } 235 236 private static void Refresh() 237 { 238 // If the user disabled auto-refresh in Unity, do not try to force refresh the Asset database 239 if (!EditorPrefs.GetBool("kAutoRefresh", true)) 240 return; 241 242 if (UnityInstallation.IsInSafeMode) 243 return; 244 245 RunOnceOnUpdate(AssetDatabase.Refresh); 246 } 247 248 private static void OnMessage(Message message) 249 { 250 AddMessage(message); 251 } 252 253 private static void Answer(Client client, MessageType answerType, string answerValue) 254 { 255 Answer(client.EndPoint, answerType, answerValue); 256 } 257 258 private static void Answer(Message message, MessageType answerType, string answerValue = "") 259 { 260 var targetEndPoint = message.Origin; 261 262 Answer( 263 targetEndPoint, 264 answerType, 265 answerValue); 266 } 267 268 private static void Answer(IPEndPoint targetEndPoint, MessageType answerType, string answerValue) 269 { 270 _messager?.SendMessage(targetEndPoint, answerType, answerValue); 271 } 272 273 private static void Shutdown() 274 { 275 if (_messager == null) 276 return; 277 278 _messager.ReceiveMessage -= ReceiveMessage; 279 _messager.Dispose(); 280 _messager = null; 281 } 282 283 internal static void BroadcastMessage(MessageType type, string value) 284 { 285 lock (_clientsLock) 286 { 287 foreach (var client in _clients.Values.ToArray()) 288 { 289 Answer(client, type, value); 290 } 291 } 292 } 293 } 294}