A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using UnityEditor.Networking.PlayerConnection; 4using UnityEditor.TestTools.TestRunner; 5using UnityEditor.TestTools.TestRunner.Api; 6using UnityEngine; 7using UnityEngine.Networking.PlayerConnection; 8using UnityEngine.TestRunner.TestLaunchers; 9 10namespace UnityEditor.TestRunner.TestLaunchers 11{ 12 [Serializable] 13 internal class RemoteTestRunController : ScriptableSingleton<RemoteTestRunController> 14 { 15 internal const int k_HeartbeatTimeout = 60 * 10; 16 17 [SerializeField] 18 internal bool isRunning; 19 20 [SerializeField] 21 private bool m_RegisteredConnectionCallbacks; 22 23 [SerializeField] 24 private int m_HearbeatTimeOut; 25 26 private enum MessageType 27 { 28 TestStarted, 29 TestFinished, 30 RunStarted, 31 RunFinished 32 } 33 [Serializable] 34 private struct Message 35 { 36 public MessageEventArgs MessageArgs; 37 public MessageType Type; 38 39 public Message(MessageEventArgs messageArgs, MessageType type) 40 { 41 MessageArgs = messageArgs; 42 Type = type; 43 } 44 } 45 46 [SerializeField] 47 private List<Message> m_IncomingMessages = new List<Message>(); 48 49 [SerializeField] 50 private bool m_RegisteredMessageCallback; 51 52 private TestTools.TestRunner.DelayedCallback m_TimeoutCallback; 53 54 public void Init(BuildTarget buildTarget, int heartbeatTimeout) 55 { 56 isRunning = true; 57 m_HearbeatTimeOut = heartbeatTimeout; 58 EditorConnection.instance.Initialize(); 59 if (!m_RegisteredConnectionCallbacks) 60 { 61 EditorConnection.instance.Initialize(); 62 DelegateEditorConnectionEvents(); 63 } 64 } 65 66 private void DelegateEditorConnectionEvents() 67 { 68 m_RegisteredConnectionCallbacks = true; 69 //This is needed because RemoteTestResultReceiver is not a ScriptableObject 70 EditorConnection.instance.Register(PlayerConnectionMessageIds.playerAliveHeartbeat, PlayerAliveHeartbeat); 71 72 // When a message comes in, we should not immediately process it but instead enqueue it for processing later 73 // in the frame. The problem this solves is that Unity only reserves about 1ms worth of time every frame to 74 // process message from the player connection. When some tests run in a player, it can take the editor 75 // minutes to react to all messages we receive because we only do 1ms of processing, then render all of the 76 // editor etc. -- Instead, we use that 1ms time-window to enqueue messages and then react to them later 77 // during the frame. This reduces the waiting time from minutes to seconds. 78 EditorConnection.instance.Register(PlayerConnectionMessageIds.testStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestStarted))); 79 EditorConnection.instance.Register(PlayerConnectionMessageIds.testFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.TestFinished))); 80 EditorConnection.instance.Register(PlayerConnectionMessageIds.runStartedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunStarted))); 81 EditorConnection.instance.Register(PlayerConnectionMessageIds.runFinishedMessageId, args => EnqueueMessage(new Message(args, MessageType.RunFinished))); 82 } 83 84 private void FlushMessageQueue() 85 { 86 EditorApplication.update -= FlushMessageQueue; 87 m_RegisteredMessageCallback = false; 88 foreach (var msg in m_IncomingMessages) 89 { 90 switch (msg.Type) 91 { 92 case MessageType.TestFinished: 93 { 94 CallbacksDelegator.instance.TestFinishedRemotely(msg.MessageArgs.data); 95 break; 96 } 97 case MessageType.TestStarted: 98 { 99 CallbacksDelegator.instance.TestStartedRemotely(msg.MessageArgs.data); 100 break; 101 } 102 case MessageType.RunStarted: 103 { 104 RunStarted(msg.MessageArgs); 105 break; 106 } 107 case MessageType.RunFinished: 108 { 109 RunFinished(msg.MessageArgs); 110 break; 111 } 112 } 113 } 114 m_IncomingMessages.Clear(); 115 } 116 117 private void EnqueueMessage(Message message) 118 { 119 m_TimeoutCallback?.Reset(); 120 if (!m_RegisteredMessageCallback) 121 { 122 EditorApplication.update += FlushMessageQueue; 123 m_RegisteredMessageCallback = true; 124 } 125 m_IncomingMessages.Add(message); 126 } 127 128 private void RunStarted(MessageEventArgs messageEventArgs) 129 { 130 m_TimeoutCallback?.Reset(); 131 CallbacksDelegator.instance.RunStartedRemotely(messageEventArgs.data); 132 } 133 134 private void RunFinished(MessageEventArgs messageEventArgs) 135 { 136 m_TimeoutCallback?.Clear(); 137 EditorConnection.instance.Send(PlayerConnectionMessageIds.quitPlayerMessageId, null, messageEventArgs.playerId); 138 EditorConnection.instance.DisconnectAll(); 139 140 CallbacksDelegator.instance.RunFinishedRemotely(messageEventArgs.data); 141 isRunning = false; 142 } 143 144 private void PlayerAliveHeartbeat(MessageEventArgs messageEventArgs) 145 { 146 m_TimeoutCallback?.Reset(); 147 } 148 149 private void TimeoutCallback() 150 { 151 CallbacksDelegator.instance.RunFailed($"Test execution timed out. No activity received from the player in {m_HearbeatTimeOut} seconds."); 152 } 153 154 public void PostSuccessfulBuildAction() 155 { 156 m_TimeoutCallback = new TestTools.TestRunner.DelayedCallback(TimeoutCallback, m_HearbeatTimeOut); 157 } 158 } 159}