123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- using System;
- using UnityEngine.InputSystem.Utilities;
- using UnityEngine.Networking.PlayerConnection;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
-
- namespace UnityEngine.InputSystem
- {
- // Transports input remoting messages from and to players. Can be used to
- // make input on either side fully available on the other side. I.e. player
- // input can be fully debugged in the editor and editor input can conversely
- // be fed into the player.
- //
- // NOTE: The Unity EditorConnection/PlayerConnection mechanism requires this to
- // be a ScriptableObject as it will register every listeners as a persistent
- // one.
- [Serializable]
- internal class RemoteInputPlayerConnection :
- #if UNITY_EDITOR
- // In the editor, we need to make sure that we get the same instance after domain reloads.
- // Otherwise, callbacks we have registered before the reload will no longer be valid, because
- // the object instance they point to will not deserialize to a valid object. So we use a
- // ScriptableSingleton instance, which fullfills these requirements. In the player, we need to
- // use a simple ScriptableObject, as ScriptableSingleton is an editor-only class.
- ScriptableSingleton<RemoteInputPlayerConnection>,
- #else
- ScriptableObject,
- #endif
- IObserver<InputRemoting.Message>, IObservable<InputRemoting.Message>
- {
- public static readonly Guid kNewDeviceMsg = new Guid("fcd9651ded40425995dfa6aeb78f1f1c");
- public static readonly Guid kNewLayoutMsg = new Guid("fccfec2b7369466d88502a9dd38505f4");
- public static readonly Guid kNewEventsMsg = new Guid("53546641df1347bc8aa315278a603586");
- public static readonly Guid kRemoveDeviceMsg = new Guid("e5e299b2d9e44255b8990bb71af8922d");
- public static readonly Guid kChangeUsagesMsg = new Guid("b9fe706dfc854d7ca109a5e38d7db730");
- public static readonly Guid kStartSendingMsg = new Guid("0d58e99045904672b3ef34b8797d23cb");
- public static readonly Guid kStopSendingMsg = new Guid("548716b2534a45369ab0c9323fc8b4a8");
-
- public void Bind(IEditorPlayerConnection connection, bool isConnected)
- {
- if (m_Connection != null)
- {
- if (m_Connection == connection)
- return;
- throw new InvalidOperationException("Already bound to an IEditorPlayerConnection");
- }
-
- // If there's already connections on the given IEditorPlayerConnection,
- // calling RegisterConnection() will invoke the given callback for every
- // already existing connection. However, it seems to do so only in the
- // editor which is why we do the 'isConnected' dance below.
- connection.RegisterConnection(OnConnected);
-
- connection.RegisterDisconnection(OnDisconnected);
-
- connection.Register(kNewDeviceMsg, OnNewDevice);
- connection.Register(kNewLayoutMsg, OnNewLayout);
- connection.Register(kNewEventsMsg, OnNewEvents);
- connection.Register(kRemoveDeviceMsg, OnRemoveDevice);
- connection.Register(kChangeUsagesMsg, OnChangeUsages);
-
- connection.Register(kStartSendingMsg, OnStartSending);
- connection.Register(kStopSendingMsg, OnStopSending);
-
- m_Connection = connection;
-
- if (isConnected)
- OnConnected(0);
- }
-
- public IDisposable Subscribe(IObserver<InputRemoting.Message> observer)
- {
- if (observer == null)
- throw new System.ArgumentNullException(nameof(observer));
-
- var subscriber = new Subscriber {owner = this, observer = observer};
- ArrayHelpers.Append(ref m_Subscribers, subscriber);
-
- if (m_ConnectedIds != null)
- {
- foreach (var id in m_ConnectedIds)
- observer.OnNext(new InputRemoting.Message { type = InputRemoting.MessageType.Connect, participantId = id });
- }
-
- return subscriber;
- }
-
- ////REVIEW: given that the PlayerConnection will connect to the editor regardless, we end up
- //// on this path whether input remoting is enabled or not
- private void OnConnected(int id)
- {
- if (m_ConnectedIds != null && ArrayHelpers.Contains(m_ConnectedIds, id))
- return;
-
- ArrayHelpers.Append(ref m_ConnectedIds, id);
-
- SendToSubscribers(InputRemoting.MessageType.Connect, new MessageEventArgs {playerId = id});
- }
-
- private void OnDisconnected(int id)
- {
- if (m_ConnectedIds == null || !ArrayHelpers.Contains(m_ConnectedIds, id))
- return;
-
- ArrayHelpers.Erase(ref m_ConnectedIds, id);
-
- SendToSubscribers(InputRemoting.MessageType.Disconnect, new MessageEventArgs {playerId = id});
- }
-
- private void OnNewDevice(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.NewDevice, args);
- }
-
- private void OnNewLayout(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.NewLayout, args);
- }
-
- private void OnNewEvents(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.NewEvents, args);
- }
-
- private void OnRemoveDevice(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.RemoveDevice, args);
- }
-
- private void OnChangeUsages(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.ChangeUsages, args);
- }
-
- private void OnStartSending(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.StartSending, args);
- }
-
- private void OnStopSending(MessageEventArgs args)
- {
- SendToSubscribers(InputRemoting.MessageType.StopSending, args);
- }
-
- private void SendToSubscribers(InputRemoting.MessageType type, MessageEventArgs args)
- {
- if (m_Subscribers == null)
- return;
-
- var msg = new InputRemoting.Message
- {
- participantId = args.playerId,
- type = type,
- data = args.data
- };
-
- for (var i = 0; i < m_Subscribers.Length; ++i)
- m_Subscribers[i].observer.OnNext(msg);
- }
-
- void IObserver<InputRemoting.Message>.OnNext(InputRemoting.Message msg)
- {
- if (m_Connection == null)
- return;
-
- ////TODO: this should really be sending to a specific player in the editor (can't
- //// do that through the IEditorPlayerConnection interface though)
-
- switch (msg.type)
- {
- case InputRemoting.MessageType.NewDevice:
- m_Connection.Send(kNewDeviceMsg, msg.data);
- break;
- case InputRemoting.MessageType.NewLayout:
- m_Connection.Send(kNewLayoutMsg, msg.data);
- break;
- case InputRemoting.MessageType.NewEvents:
- m_Connection.Send(kNewEventsMsg, msg.data);
- break;
- case InputRemoting.MessageType.ChangeUsages:
- m_Connection.Send(kChangeUsagesMsg, msg.data);
- break;
- case InputRemoting.MessageType.RemoveDevice:
- m_Connection.Send(kRemoveDeviceMsg, msg.data);
- break;
- }
- }
-
- void IObserver<InputRemoting.Message>.OnError(Exception error)
- {
- }
-
- void IObserver<InputRemoting.Message>.OnCompleted()
- {
- }
-
- [SerializeField] private IEditorPlayerConnection m_Connection;
- [NonSerialized] private Subscriber[] m_Subscribers;
- [SerializeField] private int[] m_ConnectedIds;
-
- private class Subscriber : IDisposable
- {
- public RemoteInputPlayerConnection owner;
- public IObserver<InputRemoting.Message> observer;
-
- public void Dispose()
- {
- ArrayHelpers.Erase(ref owner.m_Subscribers, this);
- }
- }
- }
- }
|