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}