Rasagar/Library/PackageCache/com.unity.inputsystem/InputSystem/Devices/Remote/RemoteInputPlayerConnection.cs
2024-08-26 23:07:20 +03:00

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);
}
}
}
}