A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3using System.Text;
4using Unity.Collections.LowLevel.Unsafe;
5using UnityEngine.InputSystem.Layouts;
6using UnityEngine.InputSystem.LowLevel;
7using UnityEngine.InputSystem.Utilities;
8
9////TODO: show remote device IDs in the debugger
10
11////TODO: remote timestamps need to be translated to local timestamps; doesn't make sense for remote events getting
12//// processed on the local timeline as is when the originating timeline may be quite different
13
14////TODO: support actions
15
16////TODO: support input users
17
18////TODO: text input events
19
20////TODO: support remoting of device commands
21
22////TODO: Reuse memory allocated for messages instead of allocating separately for each message.
23
24////REVIEW: it seems that the various XXXMsg struct should be public; ATM doesn't seem like working with the message interface is practical
25
26////REVIEW: the namespacing mechanism for layouts which changes base layouts means that layouts can't be played
27//// around with on the editor side but will only be changed once they're updated in the player
28
29namespace UnityEngine.InputSystem
30{
31 /// <summary>
32 /// Makes the activity and data of an InputManager observable in message form.
33 /// </summary>
34 /// <remarks>
35 /// Can act as both the sender and receiver of these message so the flow is fully bidirectional,
36 /// i.e. the InputManager on either end can mirror its layouts, devices, and events over
37 /// to the other end. This permits streaming input not just from the player to the editor but
38 /// also feeding input from the editor back into the player.
39 ///
40 /// Remoting sits entirely on top of the input system as an optional piece of functionality.
41 /// In development players and the editor, we enable it automatically but in non-development
42 /// players it has to be explicitly requested by the user.
43 ///
44 /// To see devices and input from players in the editor, open the Input Debugger through
45 /// "Windows >> Input Debugger".
46 /// </remarks>
47 /// <seealso cref="InputSystem.remoting"/>
48 public sealed class InputRemoting : IObservable<InputRemoting.Message>, IObserver<InputRemoting.Message>
49 {
50 /// <summary>
51 /// Enumeration of possible types of messages exchanged between two InputRemoting instances.
52 /// </summary>
53 public enum MessageType
54 {
55 Connect,
56 Disconnect,
57 NewLayout,
58 NewDevice,
59 NewEvents,
60 RemoveDevice,
61 RemoveLayout, // Not used ATM.
62 ChangeUsages,
63 StartSending,
64 StopSending,
65 }
66
67 /// <summary>
68 /// A message exchanged between two InputRemoting instances.
69 /// </summary>
70 public struct Message
71 {
72 /// <summary>
73 /// For messages coming in, numeric ID of the sender of the message. For messages
74 /// going out, numeric ID of the targeted receiver of the message.
75 /// </summary>
76 public int participantId;
77 public MessageType type;
78 public byte[] data;
79 }
80
81 public bool sending
82 {
83 get => (m_Flags & Flags.Sending) == Flags.Sending;
84 private set
85 {
86 if (value)
87 m_Flags |= Flags.Sending;
88 else
89 m_Flags &= ~Flags.Sending;
90 }
91 }
92
93 internal InputRemoting(InputManager manager, bool startSendingOnConnect = false)
94 {
95 if (manager == null)
96 throw new ArgumentNullException(nameof(manager));
97
98 m_LocalManager = manager;
99
100 if (startSendingOnConnect)
101 m_Flags |= Flags.StartSendingOnConnect;
102
103 //when listening for newly added layouts, must filter out ones we've added from remote
104 }
105
106 /// <summary>
107 /// Start sending messages for data and activity in the local input system
108 /// to observers.
109 /// </summary>
110 /// <seealso cref="sending"/>
111 /// <seealso cref="StopSending"/>
112 public void StartSending()
113 {
114 if (sending)
115 return;
116
117 ////TODO: send events in bulk rather than one-by-one
118 m_LocalManager.onEvent += SendEvent;
119 m_LocalManager.onDeviceChange += SendDeviceChange;
120 m_LocalManager.onLayoutChange += SendLayoutChange;
121
122 sending = true;
123
124 SendInitialMessages();
125 }
126
127 public void StopSending()
128 {
129 if (!sending)
130 return;
131
132 m_LocalManager.onEvent -= SendEvent;
133 m_LocalManager.onDeviceChange -= SendDeviceChange;
134 m_LocalManager.onLayoutChange -= SendLayoutChange;
135
136 sending = false;
137 }
138
139 void IObserver<Message>.OnNext(Message msg)
140 {
141 switch (msg.type)
142 {
143 case MessageType.Connect:
144 ConnectMsg.Process(this);
145 break;
146 case MessageType.Disconnect:
147 DisconnectMsg.Process(this, msg);
148 break;
149 case MessageType.NewLayout:
150 NewLayoutMsg.Process(this, msg);
151 break;
152 case MessageType.NewDevice:
153 NewDeviceMsg.Process(this, msg);
154 break;
155 case MessageType.NewEvents:
156 NewEventsMsg.Process(this, msg);
157 break;
158 case MessageType.ChangeUsages:
159 ChangeUsageMsg.Process(this, msg);
160 break;
161 case MessageType.RemoveDevice:
162 RemoveDeviceMsg.Process(this, msg);
163 break;
164 case MessageType.StartSending:
165 StartSendingMsg.Process(this);
166 break;
167 case MessageType.StopSending:
168 StopSendingMsg.Process(this);
169 break;
170 }
171 }
172
173 void IObserver<Message>.OnError(Exception error)
174 {
175 }
176
177 void IObserver<Message>.OnCompleted()
178 {
179 }
180
181 public IDisposable Subscribe(IObserver<Message> observer)
182 {
183 if (observer == null)
184 throw new ArgumentNullException(nameof(observer));
185
186 var subscriber = new Subscriber {owner = this, observer = observer};
187 ArrayHelpers.Append(ref m_Subscribers, subscriber);
188
189 return subscriber;
190 }
191
192 private void SendInitialMessages()
193 {
194 SendAllGeneratedLayouts();
195 SendAllDevices();
196 }
197
198 private void SendAllGeneratedLayouts()
199 {
200 foreach (var entry in m_LocalManager.m_Layouts.layoutBuilders)
201 SendLayout(entry.Key);
202 }
203
204 private void SendLayout(string layoutName)
205 {
206 if (m_Subscribers == null)
207 return;
208
209 var message = NewLayoutMsg.Create(this, layoutName);
210 if (message != null)
211 Send(message.Value);
212 }
213
214 private void SendAllDevices()
215 {
216 var devices = m_LocalManager.devices;
217 foreach (var device in devices)
218 SendDevice(device);
219 }
220
221 private void SendDevice(InputDevice device)
222 {
223 if (m_Subscribers == null)
224 return;
225
226 // Don't mirror remote devices to other remotes.
227 if (device.remote)
228 return;
229
230 var newDeviceMessage = NewDeviceMsg.Create(device);
231 Send(newDeviceMessage);
232
233 // Send current state. We do this here in this case as the device
234 // may have been added some time ago and thus have already received events.
235 var stateEventMessage = NewEventsMsg.CreateStateEvent(device);
236 Send(stateEventMessage);
237 }
238
239 private unsafe void SendEvent(InputEventPtr eventPtr, InputDevice device)
240 {
241 if (m_Subscribers == null)
242 return;
243
244 ////REVIEW: we probably want to have better control over this and allow producing local events
245 //// against remote devices which *are* indeed sent across the wire
246 // Don't send events that came in from remote devices.
247 if (device != null && device.remote)
248 return;
249
250 var message = NewEventsMsg.Create(eventPtr.data, 1);
251 Send(message);
252 }
253
254 private void SendDeviceChange(InputDevice device, InputDeviceChange change)
255 {
256 if (m_Subscribers == null)
257 return;
258
259 // Don't mirror remote devices to other remotes.
260 if (device.remote)
261 return;
262
263 Message msg;
264 switch (change)
265 {
266 case InputDeviceChange.Added:
267 msg = NewDeviceMsg.Create(device);
268 break;
269 case InputDeviceChange.Removed:
270 msg = RemoveDeviceMsg.Create(device);
271 break;
272 case InputDeviceChange.UsageChanged:
273 msg = ChangeUsageMsg.Create(device);
274 break;
275 ////FIXME: This creates a double reset event in case the reset itself happens from a reset event that we are also remoting at the same time.
276 case InputDeviceChange.SoftReset:
277 msg = NewEventsMsg.CreateResetEvent(device, false);
278 break;
279 case InputDeviceChange.HardReset:
280 msg = NewEventsMsg.CreateResetEvent(device, true);
281 break;
282 default:
283 return;
284 }
285
286 Send(msg);
287 }
288
289 private void SendLayoutChange(string layout, InputControlLayoutChange change)
290 {
291 if (m_Subscribers == null)
292 return;
293
294 // Ignore changes made to layouts that aren't generated. We don't send those over
295 // the wire.
296 if (!m_LocalManager.m_Layouts.IsGeneratedLayout(new InternedString(layout)))
297 return;
298
299 // We're only interested in new generated layouts popping up or existing ones
300 // getting replaced.
301 if (change != InputControlLayoutChange.Added && change != InputControlLayoutChange.Replaced)
302 return;
303
304 var message = NewLayoutMsg.Create(this, layout);
305 if (message != null)
306 Send(message.Value);
307 }
308
309 private void Send(Message msg)
310 {
311 foreach (var subscriber in m_Subscribers)
312 subscriber.observer.OnNext(msg);
313 }
314
315 private int FindOrCreateSenderRecord(int senderId)
316 {
317 // Try to find existing.
318 if (m_Senders != null)
319 {
320 var senderCount = m_Senders.Length;
321 for (var i = 0; i < senderCount; ++i)
322 if (m_Senders[i].senderId == senderId)
323 return i;
324 }
325
326 // Create new.
327 var sender = new RemoteSender
328 {
329 senderId = senderId,
330 };
331 return ArrayHelpers.Append(ref m_Senders, sender);
332 }
333
334 private static InternedString BuildLayoutNamespace(int senderId)
335 {
336 return new InternedString($"Remote::{senderId}");
337 }
338
339 private int FindLocalDeviceId(int remoteDeviceId, int senderIndex)
340 {
341 var localDevices = m_Senders[senderIndex].devices;
342 if (localDevices != null)
343 {
344 var numLocalDevices = localDevices.Length;
345
346 for (var i = 0; i < numLocalDevices; ++i)
347 {
348 if (localDevices[i].remoteId == remoteDeviceId)
349 return localDevices[i].localId;
350 }
351 }
352
353 return InputDevice.InvalidDeviceId;
354 }
355
356 private InputDevice TryGetDeviceByRemoteId(int remoteDeviceId, int senderIndex)
357 {
358 var localId = FindLocalDeviceId(remoteDeviceId, senderIndex);
359 return m_LocalManager.TryGetDeviceById(localId);
360 }
361
362 internal InputManager manager => m_LocalManager;
363
364 private Flags m_Flags;
365 private InputManager m_LocalManager; // Input system we mirror input from and to.
366 private Subscriber[] m_Subscribers; // Receivers we send input to.
367 private RemoteSender[] m_Senders; // Senders we receive input from.
368
369 [Flags]
370 private enum Flags
371 {
372 Sending = 1 << 0,
373 StartSendingOnConnect = 1 << 1
374 }
375
376 // Data we keep about a unique sender that we receive input data
377 // from. We keep track of the layouts and devices we added to
378 // the local system.
379 [Serializable]
380 internal struct RemoteSender
381 {
382 public int senderId;
383 public InternedString[] layouts; // Each item is the unqualified name of the layout (without namespace)
384 public RemoteInputDevice[] devices;
385 }
386
387 [Serializable]
388 internal struct RemoteInputDevice
389 {
390 public int remoteId; // Device ID used by sender.
391 public int localId; // Device ID used by us in local system.
392
393 public InputDeviceDescription description;
394 }
395
396 internal class Subscriber : IDisposable
397 {
398 public InputRemoting owner;
399 public IObserver<Message> observer;
400 public void Dispose()
401 {
402 ArrayHelpers.Erase(ref owner.m_Subscribers, this);
403 }
404 }
405
406 private static class ConnectMsg
407 {
408 public static void Process(InputRemoting receiver)
409 {
410 if (receiver.sending)
411 receiver.SendInitialMessages();
412 else if ((receiver.m_Flags & Flags.StartSendingOnConnect) == Flags.StartSendingOnConnect)
413 receiver.StartSending();
414 }
415 }
416
417 private static class StartSendingMsg
418 {
419 public static void Process(InputRemoting receiver)
420 {
421 receiver.StartSending();
422 }
423 }
424
425 private static class StopSendingMsg
426 {
427 public static void Process(InputRemoting receiver)
428 {
429 receiver.StopSending();
430 }
431 }
432
433 public void RemoveRemoteDevices(int participantId)
434 {
435 var senderIndex = FindOrCreateSenderRecord(participantId);
436
437 // Remove devices added by remote.
438 var devices = m_Senders[senderIndex].devices;
439 if (devices != null)
440 {
441 foreach (var remoteDevice in devices)
442 {
443 var device = m_LocalManager.TryGetDeviceById(remoteDevice.localId);
444 if (device != null)
445 m_LocalManager.RemoveDevice(device);
446 }
447 }
448
449 ArrayHelpers.EraseAt(ref m_Senders, senderIndex);
450 }
451
452 private static class DisconnectMsg
453 {
454 public static void Process(InputRemoting receiver, Message msg)
455 {
456 Debug.Log("DisconnectMsg.Process");
457
458 receiver.RemoveRemoteDevices(msg.participantId);
459 receiver.StopSending();
460 }
461 }
462
463 // Tell remote input system that there's a new layout.
464 private static class NewLayoutMsg
465 {
466 [Serializable]
467 public struct Data
468 {
469 public string name;
470 public string layoutJson;
471 public bool isOverride;
472 }
473
474 public static Message? Create(InputRemoting sender, string layoutName)
475 {
476 // Try to load the layout. Ignore the layout if it couldn't
477 // be loaded.
478 InputControlLayout layout;
479 try
480 {
481 layout = sender.m_LocalManager.TryLoadControlLayout(new InternedString(layoutName));
482 if (layout == null)
483 {
484 Debug.Log(string.Format(
485 "Could not find layout '{0}' meant to be sent through remote connection; this should not happen",
486 layoutName));
487 return null;
488 }
489 }
490 catch (Exception exception)
491 {
492 Debug.Log($"Could not load layout '{layoutName}'; not sending to remote listeners (exception: {exception})");
493 return null;
494 }
495
496 var data = new Data
497 {
498 name = layoutName,
499 layoutJson = layout.ToJson(),
500 isOverride = layout.isOverride
501 };
502
503 return new Message
504 {
505 type = MessageType.NewLayout,
506 data = SerializeData(data)
507 };
508 }
509
510 public static void Process(InputRemoting receiver, Message msg)
511 {
512 var data = DeserializeData<Data>(msg.data);
513 var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
514
515 var internedLayoutName = new InternedString(data.name);
516 receiver.m_LocalManager.RegisterControlLayout(data.layoutJson, data.name, data.isOverride);
517 ArrayHelpers.Append(ref receiver.m_Senders[senderIndex].layouts, internedLayoutName);
518 }
519 }
520
521 // Tell remote input system that there's a new device.
522 private static class NewDeviceMsg
523 {
524 [Serializable]
525 public struct Data
526 {
527 public string name;
528 public string layout;
529 public int deviceId;
530 public string[] usages;
531 public InputDeviceDescription description;
532 }
533
534 public static Message Create(InputDevice device)
535 {
536 Debug.Assert(!device.remote, "Device being sent to remotes should be a local device, not a remote one");
537
538 var data = new Data
539 {
540 name = device.name,
541 layout = device.layout,
542 deviceId = device.deviceId,
543 description = device.description,
544 usages = device.usages.Select(x => x.ToString()).ToArray()
545 };
546
547 return new Message
548 {
549 type = MessageType.NewDevice,
550 data = SerializeData(data)
551 };
552 }
553
554 public static void Process(InputRemoting receiver, Message msg)
555 {
556 var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
557 var data = DeserializeData<Data>(msg.data);
558
559 // Make sure we haven't already seen the device.
560 var devices = receiver.m_Senders[senderIndex].devices;
561 if (devices != null)
562 {
563 foreach (var entry in devices)
564 if (entry.remoteId == data.deviceId)
565 {
566 Debug.LogError(string.Format(
567 "Already received device with id {0} (layout '{1}', description '{3}) from remote {2}",
568 data.deviceId,
569 data.layout, msg.participantId, data.description));
570 return;
571 }
572 }
573
574 // Create device.
575 InputDevice device;
576 try
577 {
578 ////REVIEW: this gives remote devices names the same way that local devices receive them; should we make remote status visible in the name?
579 var internedLayoutName = new InternedString(data.layout);
580 device = receiver.m_LocalManager.AddDevice(internedLayoutName, data.name);
581 device.m_ParticipantId = msg.participantId;
582 }
583 catch (Exception exception)
584 {
585 Debug.LogError(
586 $"Could not create remote device '{data.description}' with layout '{data.layout}' locally (exception: {exception})");
587 return;
588 }
589 ////FIXME: Setting this here like so means none of this is visible during onDeviceChange
590 device.m_Description = data.description;
591 device.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
592 foreach (var usage in data.usages)
593 receiver.m_LocalManager.AddDeviceUsage(device, new InternedString(usage));
594
595 // Remember it.
596 var record = new RemoteInputDevice
597 {
598 remoteId = data.deviceId,
599 localId = device.deviceId,
600 description = data.description,
601 };
602 ArrayHelpers.Append(ref receiver.m_Senders[senderIndex].devices, record);
603 }
604 }
605
606 // Tell remote system there's new input events.
607 private static class NewEventsMsg
608 {
609 public static unsafe Message CreateResetEvent(InputDevice device, bool isHardReset)
610 {
611 var resetEvent = DeviceResetEvent.Create(device.deviceId, isHardReset);
612 return Create((InputEvent*)UnsafeUtility.AddressOf(ref resetEvent), 1);
613 }
614
615 public static unsafe Message CreateStateEvent(InputDevice device)
616 {
617 using (StateEvent.From(device, out var eventPtr))
618 return Create(eventPtr.data, 1);
619 }
620
621 public static unsafe Message Create(InputEvent* events, int eventCount)
622 {
623 // Find total size of event buffer we need.
624 var totalSize = 0u;
625 var eventPtr = new InputEventPtr(events);
626 for (var i = 0; i < eventCount; ++i, eventPtr = eventPtr.Next())
627 totalSize = totalSize.AlignToMultipleOf(4) + eventPtr.sizeInBytes;
628
629 // Copy event data to buffer. Would be nice if we didn't have to do that
630 // but unfortunately we need a byte[] and can't just pass the 'events' IntPtr
631 // directly.
632 var data = new byte[totalSize];
633 fixed(byte* dataPtr = data)
634 {
635 UnsafeUtility.MemCpy(dataPtr, events, totalSize);
636 }
637
638 // Done.
639 return new Message
640 {
641 type = MessageType.NewEvents,
642 data = data
643 };
644 }
645
646 public static unsafe void Process(InputRemoting receiver, Message msg)
647 {
648 var manager = receiver.m_LocalManager;
649
650 fixed(byte* dataPtr = msg.data)
651 {
652 var dataEndPtr = new IntPtr(dataPtr + msg.data.Length);
653 var eventCount = 0;
654 var eventPtr = new InputEventPtr((InputEvent*)dataPtr);
655 var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
656 // Don't use IntPtr.ToInt64() function, on 32 bit systems, the pointer is first converted to Int32 and then casted to Int64
657 // Thus for big pointer value, you might get a negative value even though the pointer value will be less than Int64.MaxValue
658 while ((void*)eventPtr.data < dataEndPtr.ToPointer())
659 {
660 // Patch up device ID to refer to local device and send event.
661 var remoteDeviceId = eventPtr.deviceId;
662 var localDeviceId = receiver.FindLocalDeviceId(remoteDeviceId, senderIndex);
663 eventPtr.deviceId = localDeviceId;
664
665 if (localDeviceId != InputDevice.InvalidDeviceId)
666 {
667 ////TODO: add API to send events in bulk rather than one by one
668 manager.QueueEvent(eventPtr);
669 }
670
671 ++eventCount;
672 eventPtr = eventPtr.Next();
673 }
674 }
675 }
676 }
677
678 private static class ChangeUsageMsg
679 {
680 [Serializable]
681 public struct Data
682 {
683 public int deviceId;
684 public string[] usages;
685 }
686
687 public static Message Create(InputDevice device)
688 {
689 var data = new Data
690 {
691 deviceId = device.deviceId,
692 usages = device.usages.Select(x => x.ToString()).ToArray()
693 };
694
695 return new Message
696 {
697 type = MessageType.ChangeUsages,
698 data = SerializeData(data)
699 };
700 }
701
702 public static void Process(InputRemoting receiver, Message msg)
703 {
704 var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
705 var data = DeserializeData<Data>(msg.data);
706
707 var device = receiver.TryGetDeviceByRemoteId(data.deviceId, senderIndex);
708 if (device != null)
709 {
710 foreach (var deviceUsage in device.usages)
711 {
712 if (!data.usages.Contains(deviceUsage))
713 receiver.m_LocalManager.RemoveDeviceUsage(device, new InternedString(deviceUsage));
714 }
715
716 foreach (var dataUsage in data.usages)
717 {
718 var internedDataUsage = new InternedString(dataUsage);
719 if (!device.usages.Contains(internedDataUsage))
720 receiver.m_LocalManager.AddDeviceUsage(device, new InternedString(dataUsage));
721 }
722 }
723 }
724 }
725
726 private static class RemoveDeviceMsg
727 {
728 public static Message Create(InputDevice device)
729 {
730 return new Message
731 {
732 type = MessageType.RemoveDevice,
733 data = BitConverter.GetBytes(device.deviceId)
734 };
735 }
736
737 public static void Process(InputRemoting receiver, Message msg)
738 {
739 var senderIndex = receiver.FindOrCreateSenderRecord(msg.participantId);
740 var remoteDeviceId = BitConverter.ToInt32(msg.data, 0);
741
742 var device = receiver.TryGetDeviceByRemoteId(remoteDeviceId, senderIndex);
743 if (device != null)
744 receiver.m_LocalManager.RemoveDevice(device);
745 }
746 }
747
748 private static byte[] SerializeData<TData>(TData data)
749 {
750 var json = JsonUtility.ToJson(data);
751 return Encoding.UTF8.GetBytes(json);
752 }
753
754 private static TData DeserializeData<TData>(byte[] data)
755 {
756 var json = Encoding.UTF8.GetString(data);
757 return JsonUtility.FromJson<TData>(json);
758 }
759
760#if UNITY_EDITOR || DEVELOPMENT_BUILD
761 // State we want to take across domain reloads. We can only take some of the
762 // state across. Subscriptions will be lost and have to be manually restored.
763 [Serializable]
764 internal struct SerializedState
765 {
766 public int senderId;
767 public RemoteSender[] senders;
768
769 // We can't take these across domain reloads but we want to take them across
770 // InputSystem.Save/Restore.
771 [NonSerialized] public Subscriber[] subscribers;
772 }
773
774 internal SerializedState SaveState()
775 {
776 return new SerializedState
777 {
778 senders = m_Senders,
779 subscribers = m_Subscribers
780 };
781 }
782
783 internal void RestoreState(SerializedState state, InputManager manager)
784 {
785 m_LocalManager = manager;
786 m_Senders = state.senders;
787 }
788
789#endif
790 }
791}