A game about forced loneliness, made by TACStudios
at master 8.3 kB view raw
1using System; 2using UnityEngine.InputSystem.Utilities; 3using UnityEngine.Networking.PlayerConnection; 4#if UNITY_EDITOR 5using UnityEditor; 6#endif 7 8namespace UnityEngine.InputSystem 9{ 10 // Transports input remoting messages from and to players. Can be used to 11 // make input on either side fully available on the other side. I.e. player 12 // input can be fully debugged in the editor and editor input can conversely 13 // be fed into the player. 14 // 15 // NOTE: The Unity EditorConnection/PlayerConnection mechanism requires this to 16 // be a ScriptableObject as it will register every listeners as a persistent 17 // one. 18 [Serializable] 19 internal class RemoteInputPlayerConnection : 20#if UNITY_EDITOR 21 // In the editor, we need to make sure that we get the same instance after domain reloads. 22 // Otherwise, callbacks we have registered before the reload will no longer be valid, because 23 // the object instance they point to will not deserialize to a valid object. So we use a 24 // ScriptableSingleton instance, which fullfills these requirements. In the player, we need to 25 // use a simple ScriptableObject, as ScriptableSingleton is an editor-only class. 26 ScriptableSingleton<RemoteInputPlayerConnection>, 27#else 28 ScriptableObject, 29#endif 30 IObserver<InputRemoting.Message>, IObservable<InputRemoting.Message> 31 { 32 public static readonly Guid kNewDeviceMsg = new Guid("fcd9651ded40425995dfa6aeb78f1f1c"); 33 public static readonly Guid kNewLayoutMsg = new Guid("fccfec2b7369466d88502a9dd38505f4"); 34 public static readonly Guid kNewEventsMsg = new Guid("53546641df1347bc8aa315278a603586"); 35 public static readonly Guid kRemoveDeviceMsg = new Guid("e5e299b2d9e44255b8990bb71af8922d"); 36 public static readonly Guid kChangeUsagesMsg = new Guid("b9fe706dfc854d7ca109a5e38d7db730"); 37 public static readonly Guid kStartSendingMsg = new Guid("0d58e99045904672b3ef34b8797d23cb"); 38 public static readonly Guid kStopSendingMsg = new Guid("548716b2534a45369ab0c9323fc8b4a8"); 39 40 public void Bind(IEditorPlayerConnection connection, bool isConnected) 41 { 42 if (m_Connection != null) 43 { 44 if (m_Connection == connection) 45 return; 46 throw new InvalidOperationException("Already bound to an IEditorPlayerConnection"); 47 } 48 49 // If there's already connections on the given IEditorPlayerConnection, 50 // calling RegisterConnection() will invoke the given callback for every 51 // already existing connection. However, it seems to do so only in the 52 // editor which is why we do the 'isConnected' dance below. 53 connection.RegisterConnection(OnConnected); 54 55 connection.RegisterDisconnection(OnDisconnected); 56 57 connection.Register(kNewDeviceMsg, OnNewDevice); 58 connection.Register(kNewLayoutMsg, OnNewLayout); 59 connection.Register(kNewEventsMsg, OnNewEvents); 60 connection.Register(kRemoveDeviceMsg, OnRemoveDevice); 61 connection.Register(kChangeUsagesMsg, OnChangeUsages); 62 63 connection.Register(kStartSendingMsg, OnStartSending); 64 connection.Register(kStopSendingMsg, OnStopSending); 65 66 m_Connection = connection; 67 68 if (isConnected) 69 OnConnected(0); 70 } 71 72 public IDisposable Subscribe(IObserver<InputRemoting.Message> observer) 73 { 74 if (observer == null) 75 throw new System.ArgumentNullException(nameof(observer)); 76 77 var subscriber = new Subscriber {owner = this, observer = observer}; 78 ArrayHelpers.Append(ref m_Subscribers, subscriber); 79 80 if (m_ConnectedIds != null) 81 { 82 foreach (var id in m_ConnectedIds) 83 observer.OnNext(new InputRemoting.Message { type = InputRemoting.MessageType.Connect, participantId = id }); 84 } 85 86 return subscriber; 87 } 88 89 ////REVIEW: given that the PlayerConnection will connect to the editor regardless, we end up 90 //// on this path whether input remoting is enabled or not 91 private void OnConnected(int id) 92 { 93 if (m_ConnectedIds != null && ArrayHelpers.Contains(m_ConnectedIds, id)) 94 return; 95 96 ArrayHelpers.Append(ref m_ConnectedIds, id); 97 98 SendToSubscribers(InputRemoting.MessageType.Connect, new MessageEventArgs {playerId = id}); 99 } 100 101 private void OnDisconnected(int id) 102 { 103 if (m_ConnectedIds == null || !ArrayHelpers.Contains(m_ConnectedIds, id)) 104 return; 105 106 ArrayHelpers.Erase(ref m_ConnectedIds, id); 107 108 SendToSubscribers(InputRemoting.MessageType.Disconnect, new MessageEventArgs {playerId = id}); 109 } 110 111 private void OnNewDevice(MessageEventArgs args) 112 { 113 SendToSubscribers(InputRemoting.MessageType.NewDevice, args); 114 } 115 116 private void OnNewLayout(MessageEventArgs args) 117 { 118 SendToSubscribers(InputRemoting.MessageType.NewLayout, args); 119 } 120 121 private void OnNewEvents(MessageEventArgs args) 122 { 123 SendToSubscribers(InputRemoting.MessageType.NewEvents, args); 124 } 125 126 private void OnRemoveDevice(MessageEventArgs args) 127 { 128 SendToSubscribers(InputRemoting.MessageType.RemoveDevice, args); 129 } 130 131 private void OnChangeUsages(MessageEventArgs args) 132 { 133 SendToSubscribers(InputRemoting.MessageType.ChangeUsages, args); 134 } 135 136 private void OnStartSending(MessageEventArgs args) 137 { 138 SendToSubscribers(InputRemoting.MessageType.StartSending, args); 139 } 140 141 private void OnStopSending(MessageEventArgs args) 142 { 143 SendToSubscribers(InputRemoting.MessageType.StopSending, args); 144 } 145 146 private void SendToSubscribers(InputRemoting.MessageType type, MessageEventArgs args) 147 { 148 if (m_Subscribers == null) 149 return; 150 151 var msg = new InputRemoting.Message 152 { 153 participantId = args.playerId, 154 type = type, 155 data = args.data 156 }; 157 158 for (var i = 0; i < m_Subscribers.Length; ++i) 159 m_Subscribers[i].observer.OnNext(msg); 160 } 161 162 void IObserver<InputRemoting.Message>.OnNext(InputRemoting.Message msg) 163 { 164 if (m_Connection == null) 165 return; 166 167 ////TODO: this should really be sending to a specific player in the editor (can't 168 //// do that through the IEditorPlayerConnection interface though) 169 170 switch (msg.type) 171 { 172 case InputRemoting.MessageType.NewDevice: 173 m_Connection.Send(kNewDeviceMsg, msg.data); 174 break; 175 case InputRemoting.MessageType.NewLayout: 176 m_Connection.Send(kNewLayoutMsg, msg.data); 177 break; 178 case InputRemoting.MessageType.NewEvents: 179 m_Connection.Send(kNewEventsMsg, msg.data); 180 break; 181 case InputRemoting.MessageType.ChangeUsages: 182 m_Connection.Send(kChangeUsagesMsg, msg.data); 183 break; 184 case InputRemoting.MessageType.RemoveDevice: 185 m_Connection.Send(kRemoveDeviceMsg, msg.data); 186 break; 187 } 188 } 189 190 void IObserver<InputRemoting.Message>.OnError(Exception error) 191 { 192 } 193 194 void IObserver<InputRemoting.Message>.OnCompleted() 195 { 196 } 197 198 [SerializeField] private IEditorPlayerConnection m_Connection; 199 [NonSerialized] private Subscriber[] m_Subscribers; 200 [SerializeField] private int[] m_ConnectedIds; 201 202 private class Subscriber : IDisposable 203 { 204 public RemoteInputPlayerConnection owner; 205 public IObserver<InputRemoting.Message> observer; 206 207 public void Dispose() 208 { 209 ArrayHelpers.Erase(ref owner.m_Subscribers, this); 210 } 211 } 212 } 213}