214 lines
8.1 KiB
C#
214 lines
8.1 KiB
C#
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|