A game about forced loneliness, made by TACStudios
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}