File size: 8,324 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | 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);
}
}
}
}
|