This commit is contained in:
Rene Damm rene@unity3d.com 2017-11-22 13:37:26 -08:00
Родитель bbe1252fba
Коммит c9b310ff95
26 изменённых файлов: 501 добавлений и 127 удалений

Просмотреть файл

@ -17,7 +17,7 @@ namespace ISX.HID
// The HID device descriptor as received from the device driver.
public HIDDeviceDescriptor hidDescriptor => new HIDDeviceDescriptor();////TODO: parse on demand from description.capabilities
internal static string OnDeviceDiscovered(InputDeviceDescription description, string matchedTemplate)
internal static string OnFindTemplateForDevice(InputDeviceDescription description, string matchedTemplate)
{
// If the system found a matching template, there's nothing for us to do.
if (!string.IsNullOrEmpty(matchedTemplate))
@ -40,10 +40,11 @@ namespace ISX.HID
HIDDeviceDescriptor hidDeviceDescriptor;
try
{
hidDeviceDescriptor = JsonUtility.FromJson<HIDDeviceDescriptor>(description.capabilities);
hidDeviceDescriptor = HIDDeviceDescriptor.FromJson(description.capabilities);
}
catch (Exception ex)
catch (Exception exception)
{
Debug.Log($"Could not parse HID descriptor (exception: {exception}");
return null;
}
@ -66,6 +67,7 @@ namespace ISX.HID
var baseTemplate = "HID";
if (hidDeviceDescriptor.usagePage == UsagePage.GenericDesktop)
{
////TODO: there's some work to be done to make the HID *actually* compatible with these devices
if (hidDeviceDescriptor.usage == (int)GenericDesktop.Joystick)
baseTemplate = "Joystick";
else if (hidDeviceDescriptor.usage == (int)GenericDesktop.Gamepad)
@ -98,7 +100,8 @@ namespace ISX.HID
{
var builder = new InputTemplate.Builder
{
type = typeof(HID)
type = typeof(HID),
stateFormat = new FourCC('H', 'I', 'D')
};
////TODO: for joysticks, set up stick from X and Y
@ -113,9 +116,11 @@ namespace ISX.HID
var control =
builder.AddControl(element.DetermineName())
.WithTemplate(template)
.WithOffset(offset)
.WithOffset(offset) ////FIXME: offset is in bits; handle bit addressing correctly
.WithFormat(element.DetermineFormat());
////TODO: configure axis parameters from min/max limits
element.SetUsage(control);
}
@ -183,6 +188,8 @@ namespace ISX.HID
if (reportType != HIDReportType.Input)
return null;
////TODO: deal with arrays
switch (usagePage)
{
case UsagePage.Button:
@ -234,6 +241,16 @@ namespace ISX.HID
public int usage;
public UsagePage usagePage;
public HIDElementDescriptor[] elements;
public string ToJson()
{
return JsonUtility.ToJson(this);
}
public static HIDDeviceDescriptor FromJson(string json)
{
return JsonUtility.FromJson<HIDDeviceDescriptor>(json);
}
}
public enum UsagePage

Просмотреть файл

@ -2,7 +2,7 @@ using UnityEngine;
namespace ISX.HID
{
[InputPlugin]
[InputModule]
public static class HIDSupport
{
public static string description =>
@ -22,7 +22,7 @@ namespace ISX.HID
public static void Initialize()
{
InputSystem.RegisterTemplate<HID>();
InputSystem.onDeviceDiscovered += HID.OnDeviceDiscovered;
InputSystem.onFindTemplateForDevice += HID.OnFindTemplateForDevice;
}
}
}

Просмотреть файл

@ -1,5 +1,4 @@
using NUnit.Framework;
using UnityEngine;
namespace ISX.HID
{
@ -9,7 +8,6 @@ namespace ISX.HID
{
base.Setup();
////TODO: the system should do that automatically for us
HIDSupport.Initialize();
}
@ -38,7 +36,7 @@ namespace ISX.HID
{
interfaceName = HID.kHIDInterface,
product = "MyHIDThing",
capabilities = JsonUtility.ToJson(hidDescriptor)
capabilities = hidDescriptor.ToJson()
});
Assert.That(InputSystem.devices, Has.Count.EqualTo(1));

Просмотреть файл

@ -1,11 +1,15 @@
#if DEVELOPMENT_BUILD || UNITY_EDITOR
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using ISX;
using ISX.Remote;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking.PlayerConnection;
using UnityEngine.TestTools;
using UnityEngineInternal.Input;
@ -261,7 +265,7 @@ public class FunctionalTests : InputTestsBase
[Category("Template")]
public void Templates_CanOverrideTemplateMatchesForDiscoveredDevices()
{
InputSystem.onDeviceDiscovered +=
InputSystem.onFindTemplateForDevice +=
(description, templateMatch) => "Keyboard";
var device = InputSystem.AddDevice(new InputDeviceDescription {deviceClass = "Gamepad"});
@ -3510,9 +3514,9 @@ public class FunctionalTests : InputTestsBase
modifiers: "tap(duration=0.1),slowTap(duration=0.5)");
action.Enable();
var started = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var cancelled = new System.Collections.Generic.List<InputAction.CallbackContext>();
var started = new List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
var cancelled = new List<InputAction.CallbackContext>();
action.started += ctx => started.Add(ctx);
action.performed += ctx => performed.Add(ctx);
@ -3569,7 +3573,7 @@ public class FunctionalTests : InputTestsBase
action.AddBinding("/gamepad/leftTrigger").CombinedWith("/gamepad/buttonSouth");
action.Enable();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
action.performed += ctx => performed.Add(ctx);
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftTrigger = 1.0f});
@ -3596,7 +3600,7 @@ public class FunctionalTests : InputTestsBase
action.AddBinding("/gamepad/leftTrigger").CombinedWith("/gamepad/buttonSouth");
action.Enable();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
action.performed += ctx => performed.Add(ctx);
InputSystem.QueueStateEvent(gamepad,
@ -3616,7 +3620,7 @@ public class FunctionalTests : InputTestsBase
action.AddBinding("/gamepad/leftTrigger").CombinedWith("/gamepad/buttonSouth");
action.Enable();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
action.performed += ctx => performed.Add(ctx);
InputSystem.QueueStateEvent(gamepad,
@ -3643,7 +3647,7 @@ public class FunctionalTests : InputTestsBase
action.AddBinding("/gamepad/leftTrigger").CombinedWith("/gamepad/buttonSouth", modifiers: "tap,slowTap");
action.Enable();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
action.performed += ctx => performed.Add(ctx);
InputSystem.QueueStateEvent(gamepad,
@ -3665,9 +3669,9 @@ public class FunctionalTests : InputTestsBase
var action = new InputAction(binding: "/gamepad/leftStick", modifiers: "continuous");
action.Enable();
var started = new System.Collections.Generic.List<InputAction.CallbackContext>();
var performed = new System.Collections.Generic.List<InputAction.CallbackContext>();
var cancelled = new System.Collections.Generic.List<InputAction.CallbackContext>();
var started = new List<InputAction.CallbackContext>();
var performed = new List<InputAction.CallbackContext>();
var cancelled = new List<InputAction.CallbackContext>();
action.started += ctx => performed.Add(ctx);
action.cancelled += ctx => performed.Add(ctx);
@ -4256,7 +4260,7 @@ public class FunctionalTests : InputTestsBase
var secondInputSystem = new InputManager();
secondInputSystem.InitializeData();
var local = InputSystem.remote;
var local = InputSystem.remoting;
var remote = new InputRemoting(secondInputSystem, senderId: 1);
// We wire the two directly into each other effectively making function calls
@ -4297,7 +4301,7 @@ public class FunctionalTests : InputTestsBase
var secondInputSystem = new InputManager();
secondInputSystem.InitializeData();
var local = InputSystem.remote;
var local = InputSystem.remoting;
var remote = new InputRemoting(secondInputSystem, senderId: 1);
local.Subscribe(remote);
@ -4332,6 +4336,142 @@ public class FunctionalTests : InputTestsBase
Assert.Fail();
}
// If we have more than two players connected, for example, and we add a template from player A
// to the system, we don't want to send the template to player B in turn. I.e. all data mirrored
// from remotes should stay local.
[Test]
[Category("Remote")]
public void TODO_Remote_WithMultipleRemotesConnected_DoesNotDuplicateDataFromOneRemoteToOtherRemotes()
{
Assert.Fail();
}
// PlayerConnection isn't connected in the editor and EditorConnection isn't connected
// in players so we can't really test actual transport in just the application itself.
// This will act as an IEditorPlayerConnection that immediately makes the FakePlayerConnection
// on the other end receive messages.
class FakePlayerConnection : IEditorPlayerConnection
{
public int playerId;
// The fake connection acting as the socket on the opposite end of us.
public FakePlayerConnection otherEnd;
public void Register(Guid messageId, UnityAction<MessageEventArgs> callback)
{
MessageEvent msgEvent;
if (!m_MessageListeners.TryGetValue(messageId, out msgEvent))
{
msgEvent = new MessageEvent();
m_MessageListeners[messageId] = msgEvent;
}
msgEvent.AddListener(callback);
}
public void Unregister(Guid messageId, UnityAction<MessageEventArgs> callback)
{
m_MessageListeners[messageId].RemoveListener(callback);
}
public void DisconnectAll()
{
m_MessageListeners.Clear();
m_ConnectionListeners.RemoveAllListeners();
m_DisconnectionListeners.RemoveAllListeners();
}
public void RegisterConnection(UnityAction<int> callback)
{
m_ConnectionListeners.AddListener(callback);
}
public void RegisterDisconnection(UnityAction<int> callback)
{
m_DisconnectionListeners.AddListener(callback);
}
public void Receive(Guid messageId, byte[] data)
{
MessageEvent msgEvent;
if (m_MessageListeners.TryGetValue(messageId, out msgEvent))
msgEvent.Invoke(new MessageEventArgs {playerId = playerId, data = data});
}
public void Send(Guid messageId, byte[] data)
{
otherEnd.Receive(messageId, data);
}
private Dictionary<Guid, MessageEvent> m_MessageListeners = new Dictionary<Guid, MessageEvent>();
private ConnectEvent m_ConnectionListeners = new ConnectEvent();
private ConnectEvent m_DisconnectionListeners = new ConnectEvent();
private class MessageEvent : UnityEvent<MessageEventArgs>
{
}
private class ConnectEvent : UnityEvent<int>
{
}
}
public class RemoteTestObserver : IObserver<InputRemoting.Message>
{
public List<InputRemoting.Message> messages = new List<InputRemoting.Message>();
public void OnNext(InputRemoting.Message msg)
{
messages.Add(msg);
}
public void OnError(Exception error)
{
}
public void OnCompleted()
{
}
}
[Test]
[Category("Remote")]
public void Remote_CanConnectInputSystemsOverEditorPlayerConnection()
{
var connectionToEditor = ScriptableObject.CreateInstance<RemoteInputPlayerConnection>();
var connectionToPlayer = ScriptableObject.CreateInstance<RemoteInputPlayerConnection>();
connectionToEditor.name = "ConnectionToEditor";
connectionToPlayer.name = "ConnectionToPlayer";
var fakeEditorConnection = new FakePlayerConnection {playerId = 0};
var fakePlayerConnection = new FakePlayerConnection {playerId = 1};
fakeEditorConnection.otherEnd = fakePlayerConnection;
fakePlayerConnection.otherEnd = fakeEditorConnection;
var observer = new RemoteTestObserver();
// In the Unity API, "PlayerConnection" is the connection to the editor
// and "EditorConnection" is the connection to the player. Seems counter-intuitive.
connectionToEditor.Connect(fakePlayerConnection);
connectionToPlayer.Connect(fakeEditorConnection);
// Connect the local remote on the player side.
InputSystem.remoting.Subscribe(connectionToEditor);
InputSystem.remoting.StartSending();
connectionToPlayer.Subscribe(observer);
var device = InputSystem.AddDevice("Gamepad");
InputSystem.QueueStateEvent(device, new GamepadState());
InputSystem.Update();
InputSystem.RemoveDevice(device);
////TODO: make sure that we also get the connection sequence right and send our initial templates and devices
Assert.That(observer.messages, Has.Count.EqualTo(3));
Assert.That(observer.messages[0].type, Is.EqualTo(InputRemoting.MessageType.NewDevice));
Assert.That(observer.messages[1].type, Is.EqualTo(InputRemoting.MessageType.NewEvents));
Assert.That(observer.messages[2].type, Is.EqualTo(InputRemoting.MessageType.RemoveDevice));
}
#if UNITY_EDITOR
[Test]
[Category("Editor")]
@ -4680,3 +4820,4 @@ public class FunctionalTests : InputTestsBase
Assert.Fail();
}
}
#endif // DEVELOPMENT_BUILD || UNITY_EDITOR

Просмотреть файл

@ -2,38 +2,76 @@ namespace ISX
{
public static class CommonUsages
{
// Primary 2D motion control.
// Example: left stick on gamepad.
public static InternedString Primary2DMotion = new InternedString("Primary2DMotion");
// Secondary 2D motion control.
// Example: right stick on gamepad.
public static InternedString Secondary2DMotion = new InternedString("Secondary2DMotion");
public static InternedString PrimaryAction = new InternedString("PrimaryAction");
public static InternedString SecondaryAction = new InternedString("SecondaryAction");
public static InternedString PrimaryTrigger = new InternedString("PrimaryTrigger");
public static InternedString SecondaryTrigger = new InternedString("SecondaryTrigger");
public static InternedString Back = new InternedString("Back");
public static InternedString Forward = new InternedString("Forward");
public static InternedString Menu = new InternedString("Menu");
public static InternedString Submit = new InternedString("Submit");////REVIEW: Call 'accept'?
public static InternedString Cancel = new InternedString("Cancel");
public static InternedString Previous = new InternedString("Previous");
public static InternedString Next = new InternedString("Next");
public static InternedString Modifier = new InternedString("Modifier"); // Stuff like CTRL
public static InternedString ScrollHorizontal = new InternedString("ScrollHorizontal");
public static InternedString Pressure = new InternedString("Pressure");
public static InternedString Position = new InternedString("Position");
public static InternedString Orientation = new InternedString("Orientation");
public static InternedString Hatswitch = new InternedString("Hatswitch");
// Button to navigate to previous location.
// Example: Escape on keyboard, B button on gamepad.
public static InternedString Back = new InternedString("Back");
// Button to navigate to next location.
public static InternedString Forward = new InternedString("Forward");
// Button to bring up menu.
public static InternedString Menu = new InternedString("Menu");
// Button to confirm the current choice.
public static InternedString Accept = new InternedString("Accept");
////REVIEW: isn't this the same as "Back"?
// Button to not accept the current choice.
public static InternedString Cancel = new InternedString("Cancel");
// Horizontal motion axis.
// Example: X axis on mouse.
public static InternedString Horizontal = new InternedString("Horizontal");
// Vertical motion axis.
// Example: Y axis on mouse.
public static InternedString Vertical = new InternedString("Vertical");
// Rotation around single, fixed axis.
// Example: twist on joystick or twist of pen (few pens support that).
public static InternedString Twist = new InternedString("Twist");
// Pressure level axis.
// Example: pen pressure.
public static InternedString Pressure = new InternedString("Pressure");
// Axis to scroll horizontally.
public static InternedString ScrollHorizontal = new InternedString("ScrollHorizontal");
// Axis to scroll vertically.
public static InternedString ScrollVertical = new InternedString("ScrollVertical");
public static InternedString Point = new InternedString("Point");
public static InternedString LowFreqMotor = new InternedString("LowFreqMotor");
public static InternedString HighFreqMotor = new InternedString("HighFreqMotor");
// Device in left hand.
// Example: left hand XR controller.
public static InternedString LeftHand = new InternedString("LeftHand");
// Device in right hand.
// Example: right hand XR controller.
public static InternedString RightHand = new InternedString("RightHand");
// Axis representing charge of battery (1=full, 0=empty).
public static InternedString BatteryStrength = new InternedString("BatteryStrength");
}
}

Просмотреть файл

@ -100,6 +100,7 @@ namespace ISX
public virtual void SetConfiguration(ReadOnlyArray<Configuration> configuration)
{
//For native API, instead of sending actual string names for keys, CRC the keys and use ints for communication
throw new NotImplementedException();
}

Просмотреть файл

@ -0,0 +1,12 @@
namespace ISX
{
// If no module manager has been set up, this one is taken as a fallback.
// Scans for all input modules present in the code and initializes any
// that is compatible with the current runtime platform.
public class DefaultInputModuleManager : IInputModuleManager
{
public void Initialize()
{
}
}
}

Просмотреть файл

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7fd5d06ebb86414baed5cafa3aa761c6
timeCreated: 1511228343

Просмотреть файл

@ -15,6 +15,7 @@ namespace ISX
public InputDeviceDescription description => m_Description;
////REVIEW: on Xbox, a device can have multiple player IDs assigned to it
////TODO: this needs to become part of the device's configuration
// Systems that support multiple concurrent player inputs on the same system, the available
// player inputs are usually numbered. For example, on a console the gamepads slots on the system

Просмотреть файл

@ -6,8 +6,13 @@ namespace ISX
// input device-related assets in the project go into the build. The
// imported asset corresponds to only the assets that get included
// in the build for the current build target.
public class InputDeviceDatabaseAsset : ScriptableObject
public class InputDeviceDatabaseAsset : ScriptableObject, IInputModuleManager
{
public const string kExtension = "inputdevices";
public void Initialize()
{
throw new System.NotImplementedException();
}
}
}

Просмотреть файл

@ -105,7 +105,7 @@ namespace ISX
[InputControl(name = "AnyKey", template = "AnyKey", sizeInBits = kSizeInBits)]
[InputControl(name = "Escape", template = "Key", usages = new[] {"Back", "Cancel"}, bit = (int)Key.Escape)]
[InputControl(name = "Space", template = "Key", bit = (int)Key.Space)]
[InputControl(name = "Enter", template = "Key", usage = "Submit", bit = (int)Key.Enter)]
[InputControl(name = "Enter", template = "Key", usage = "Accept", bit = (int)Key.Enter)]
[InputControl(name = "Tab", template = "Key", bit = (int)Key.Tab)]
[InputControl(name = "Backtick", template = "Key", bit = (int)Key.Backtick)]
[InputControl(name = "Semicolon", template = "Key", bit = (int)Key.Semicolon)]

Просмотреть файл

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c5390a5185844de1abdba122ccdc5510
timeCreated: 1511135240

Просмотреть файл

@ -5,8 +5,6 @@ using System.Text;
using UnityEngine;
using UnityEngineInternal.Input;
////TODO: survive domain reloads
////TODO: reuse memory allocated for messages instead of allocating separately for each message
namespace ISX.Remote
@ -66,6 +64,8 @@ namespace ISX.Remote
m_LocalManager.onDeviceChange += SendDeviceChange;
m_IsSending = true;
SendAllCurrentDataToAllSubscribers();
}
public void StopSending()
@ -74,6 +74,7 @@ namespace ISX.Remote
return;
m_LocalManager.onEvent -= SendEvent;
m_LocalManager.onDeviceChange -= SendDeviceChange;
m_IsSending = false;
}
@ -82,6 +83,10 @@ namespace ISX.Remote
{
switch (msg.type)
{
case MessageType.Connect:
break;
case MessageType.Disconnect:
break;
case MessageType.NewTemplate:
NewTemplateMsg.Process(this, msg);
break;
@ -116,12 +121,31 @@ namespace ISX.Remote
var subscriber = new Subscriber {owner = this, observer = observer};
ArrayHelpers.Append(ref m_Subscribers, subscriber);
SendAllTemplatesTo(subscriber);
SendAllDevicesTo(subscriber);
if (m_IsSending)
SendAllCurrentDataToSubscriber(subscriber);
////REVIEW: Send connect?
return subscriber;
}
// Let all subscribers know about all devices and templates that m_LocalManager has.
private void SendAllCurrentDataToAllSubscribers()
{
if (m_Subscribers == null)
return;
foreach (var subscriber in m_Subscribers)
SendAllCurrentDataToSubscriber(subscriber);
}
// Let the given subscriber know about all devices and templates that m_LocalManager has.
private void SendAllCurrentDataToSubscriber(Subscriber subscriber)
{
SendAllTemplatesTo(subscriber);
SendAllDevicesTo(subscriber);
}
private void SendAllTemplatesTo(Subscriber subscriber)
{
var allTemplates = new List<string>();
@ -186,6 +210,10 @@ namespace ISX.Remote
if (m_Subscribers == null)
return;
// Don't mirror remoted devices to other remotes.
if (device.remote)
return;
Message msg;
switch (change)
{
@ -340,6 +368,8 @@ namespace ISX.Remote
public static Message Create(InputRemoting sender, InputDevice device)
{
Debug.Assert(!device.remote, "Device being sent to remotes should be a local devices, not a remote one");
var data = new Data
{
name = device.name,

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using UnityEngine;
using UnityEngine.Networking.PlayerConnection;
namespace ISX.Remote
@ -7,8 +8,12 @@ namespace ISX.Remote
// 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 : IObserver<InputRemoting.Message>, IObservable<InputRemoting.Message>
internal class RemoteInputPlayerConnection : ScriptableObject, IObserver<InputRemoting.Message>, IObservable<InputRemoting.Message>
{
public static readonly Guid kNewDeviceMsg = new Guid("fcd9651ded40425995dfa6aeb78f1f1c");
public static readonly Guid kNewTemplateMsg = new Guid("fccfec2b7369466d88502a9dd38505f4");
@ -25,7 +30,11 @@ namespace ISX.Remote
throw new InvalidOperationException("Already connected");
}
// If there's already connections on the given IEditorPlayerConnection,
// calling RegisterConnection() will invoke the given callback for every
// already existing connection.
connection.RegisterConnection(OnConnected);
connection.RegisterDisconnection(OnDisconnected);
connection.Register(kNewDeviceMsg, OnNewDevice);
@ -33,34 +42,83 @@ namespace ISX.Remote
connection.Register(kNewEventsMsg, OnNewEvents);
connection.Register(kRemoveDeviceMsg, OnRemoveDevice);
connection.Register(kChangeUsagesMsg, OnChangeUsages);
m_Connection = connection;
}
public IDisposable Subscribe(IObserver<InputRemoting.Message> 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, sender = id });
}
return subscriber;
}
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 OnNewTemplate(MessageEventArgs args)
{
SendToSubscribers(InputRemoting.MessageType.NewTemplate, 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 SendToSubscribers(InputRemoting.MessageType type, MessageEventArgs args)
{
if (m_Subscribers == null)
return;
var msg = new InputRemoting.Message
{
sender = 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)
@ -96,21 +154,9 @@ namespace ISX.Remote
{
}
public IDisposable Subscribe(IObserver<InputRemoting.Message> observer)
{
var subscriber = new Subscriber {owner = this, observer = observer};
ArrayHelpers.Append(ref m_Subscribers, subscriber);
return subscriber;
}
[NonSerialized] private IEditorPlayerConnection m_Connection;
[NonSerialized] private Subscriber[] m_Subscribers;
[Serializable]
private class Player
{
public int playerId;
}
[SerializeField] private int[] m_ConnectedIds;
private class Subscriber : IDisposable
{
@ -119,6 +165,7 @@ namespace ISX.Remote
public void Dispose()
{
ArrayHelpers.Erase(ref owner.m_Subscribers, this);
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace ISX
{
public interface IInputModuleManager
{
void Initialize();
}
}

Просмотреть файл

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 79a3ad6ec60e48feabae232b11acb573
timeCreated: 1511228252

Просмотреть файл

@ -15,7 +15,7 @@ using UnityEngineInternal.Input;
namespace ISX
{
using DeviceChangeListener = Action<InputDevice, InputDeviceChange>;
using DeviceDiscoveredListener = Func<InputDeviceDescription, string, string>;
using DeviceFindTemplateListener = Func<InputDeviceDescription, string, string>;
using EventListener = Action<InputEventPtr>;
using UpdateListener = Action<InputUpdateType>;
@ -49,11 +49,10 @@ namespace ISX
remove { m_DeviceChangeListeners.Remove(value); }
}
////TODO: rename to onDeviceFindTemplate
public event DeviceDiscoveredListener onDeviceDiscovered
public event DeviceFindTemplateListener onFindTemplateForDevice
{
add { m_DeviceDiscoveredListeners.Append(value); }
remove { m_DeviceDiscoveredListeners.Remove(value); }
add { m_DeviceFindTemplateListeners.Append(value); }
remove { m_DeviceFindTemplateListeners.Remove(value); }
}
////TODO: add InputEventBuffer struct that uses NativeArray underneath
@ -573,9 +572,9 @@ namespace ISX
var template = TryFindMatchingTemplate(description);
// Give listeners a shot to select/create a template.
for (var i = 0; i < m_DeviceDiscoveredListeners.Count; ++i)
for (var i = 0; i < m_DeviceFindTemplateListeners.Count; ++i)
{
var newTemplate = m_DeviceDiscoveredListeners[i](description, template);
var newTemplate = m_DeviceFindTemplateListeners[i](description, template);
if (!string.IsNullOrEmpty(newTemplate))
{
template = newTemplate;
@ -923,7 +922,7 @@ namespace ISX
// Restoration of UnityActions is unreliable and it's too easy to end up with double
// registrations what will lead to all kinds of misbehavior.
[NonSerialized] private InlinedArray<DeviceChangeListener> m_DeviceChangeListeners;
[NonSerialized] private InlinedArray<DeviceDiscoveredListener> m_DeviceDiscoveredListeners;
[NonSerialized] private InlinedArray<DeviceFindTemplateListener> m_DeviceFindTemplateListeners;
[NonSerialized] private InlinedArray<EventListener> m_EventListeners;
[NonSerialized] private InlinedArray<UpdateListener> m_UpdateListeners;
[NonSerialized] private bool m_NativeBeforeUpdateHooked;
@ -1685,8 +1684,12 @@ namespace ISX
#endif
}
// Domain reload survival logic.
#if UNITY_EDITOR
// Domain reload survival logic. Also used for pushing and popping input system
// state for testing.
// Stuff everything that we want to survive a domain reload into
// a m_SerializedState.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
[Serializable]
internal struct DeviceState
{
@ -1777,7 +1780,7 @@ namespace ISX
// across domain reloads. So we put them in here but don't serialize them (and
// can't either except if we make them UnityEvents).
[NonSerialized] public InlinedArray<DeviceChangeListener> deviceChangeListeners;
[NonSerialized] public InlinedArray<DeviceDiscoveredListener> deviceDiscoveredListeners;
[NonSerialized] public InlinedArray<DeviceFindTemplateListener> deviceDiscoveredListeners;
[NonSerialized] public InlinedArray<EventListener> eventListeners;
}
@ -1855,7 +1858,7 @@ namespace ISX
buffers = m_StateBuffers,
configuration = InputConfiguration.Save(),
deviceChangeListeners = m_DeviceChangeListeners.Clone(),
deviceDiscoveredListeners = m_DeviceDiscoveredListeners.Clone(),
deviceDiscoveredListeners = m_DeviceFindTemplateListeners.Clone(),
eventListeners = m_EventListeners.Clone(),
updateMask = m_UpdateMask
};
@ -1875,7 +1878,7 @@ namespace ISX
m_Devices = null;
m_TemplateSetupVersion = state.templateSetupVersion + 1;
m_DeviceChangeListeners = state.deviceChangeListeners;
m_DeviceDiscoveredListeners = state.deviceDiscoveredListeners;
m_DeviceFindTemplateListeners = state.deviceDiscoveredListeners;
m_EventListeners = state.eventListeners;
m_UpdateMask = state.updateMask;
@ -2011,6 +2014,8 @@ namespace ISX
}
m_Devices = devices;
////TODO: retry to make sense of available devices that we couldn't make sense of before; maybe we have a template now
// At the moment, there's no support for taking state across domain reloads
// as we don't have support ATM for taking state across format changes.
m_StateBuffers.FreeAll();
@ -2020,8 +2025,8 @@ namespace ISX
[SerializeField] private SerializedState m_SerializedState;
// Stuff everything that we want to survive a domain reload into
// a m_SerializedState.
#endif // UNITY_EDITOR || DEVELOPMENT_BUILD
#if UNITY_EDITOR
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
m_SerializedState = SaveState();

Просмотреть файл

@ -6,7 +6,7 @@ using System;
namespace ISX
{
public class InputPluginAttribute : Attribute
public class InputModuleAttribute : Attribute
{
}
}

Просмотреть файл

@ -8,6 +8,8 @@ using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using ISX.Editor;
#else
using UnityEngine.Networking.PlayerConnection;
#endif
// I'd like to call the DLLs UnityEngine.Input and UnityEngine.Input.Tests
@ -26,7 +28,9 @@ using ISX.Editor;
namespace ISX
{
// The primary API for the input system.
/// <summary>
/// This is the central API for the input system.
/// </summary>
// Takes care of the singletons we need and presents a sanitized API.
#if UNITY_EDITOR
[InitializeOnLoad]
@ -145,10 +149,10 @@ namespace ISX
remove { s_Manager.onDeviceChange -= value; }
}
public static event Func<InputDeviceDescription, string, string> onDeviceDiscovered
public static event Func<InputDeviceDescription, string, string> onFindTemplateForDevice
{
add { s_Manager.onDeviceDiscovered += value; }
remove { s_Manager.onDeviceDiscovered -= value; }
add { s_Manager.onFindTemplateForDevice += value; }
remove { s_Manager.onFindTemplateForDevice -= value; }
}
public static InputDevice AddDevice(string template, string name = null)
@ -454,7 +458,7 @@ namespace ISX
#region Remoting
public static InputRemoting remote
public static InputRemoting remoting
{
get
{
@ -475,13 +479,10 @@ namespace ISX
internal static InputManager s_Manager;
internal static InputRemoting s_Remote;
// The rest here is internal stuff to manage singletons, survive domain reloads,
// and to support the reset ability for tests.
#if UNITY_EDITOR
private static bool s_Initialized;
private static InputSystemObject s_SystemObject;
static InputSystem()
{
// Unity's InitializeOnLoad force-executes static class constructors without
@ -494,6 +495,20 @@ namespace ISX
if (s_Initialized)
return;
#if UNITY_EDITOR
InitializeInEditor();
#else
InitializeInPlayer();
#endif
s_Initialized = true;
}
#if UNITY_EDITOR
private static InputSystemObject s_SystemObject;
private static void InitializeInEditor()
{
var existingSystemObjects = Resources.FindObjectsOfTypeAll<InputSystemObject>();
if (existingSystemObjects != null && existingSystemObjects.Length > 0)
{
@ -509,17 +524,6 @@ namespace ISX
}
EditorApplication.playModeStateChanged += OnPlayModeChange;
s_Initialized = true;
}
internal static void Reset()
{
if (s_SystemObject != null)
UnityEngine.Object.DestroyImmediate(s_SystemObject);
s_SystemObject = ScriptableObject.CreateInstance<InputSystemObject>();
s_Manager = s_SystemObject.manager;
s_Remote = s_SystemObject.remote;
}
// We don't want play mode modifications to templates and controls to seep
@ -542,6 +546,58 @@ namespace ISX
}
}
#else
#if DEVELOPMENT_BUILD
private static RemoteInputPlayerConnection s_RemoteEditorConnection;
#endif
[RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void InitializeInPlayer()
{
if (s_Initialized)
return;
// No domain reloads in the player so we don't need to look for existing
// instances.
s_Manager = new InputManager();
s_Manager.Initialize();
////TODO: put this behind a switch so that it is off by default
// Automatically enable remoting in development players.
#if DEVELOPMENT_BUILD
s_Remote = new InputRemoting(s_Manager);
s_RemoteEditorConnection = ScriptableObject.CreateInstance<RemoteInputPlayerConnection>();
s_RemoteEditorConnection.Connect(PlayerConnection.instance);
s_Remote.Subscribe(s_RemoteEditorConnection);
s_RemoteEditorConnection.Subscribe(s_Remote);
s_Remote.StartSending();
#endif
s_Initialized = true;
}
#endif // UNITY_EDITOR
// For testing, we want the ability to push/pop system state even in the player.
// However, we don't want it in release players.
#if DEVELOPMENT_BUILD || UNITY_EDITOR
internal static void Reset()
{
#if UNITY_EDITOR
if (s_SystemObject != null)
UnityEngine.Object.DestroyImmediate(s_SystemObject);
s_SystemObject = ScriptableObject.CreateInstance<InputSystemObject>();
s_Manager = s_SystemObject.manager;
s_Remote = s_SystemObject.remote;
#else
if (s_Manager != null)
s_Manager.Destroy();
////TODO: reset remote
s_Initialized = false;
InitializeInPlayer();
#endif
}
private static List<InputManager.SerializedState> s_SerializedStateStack;
////REVIEW: what should we do with the remote here?
@ -564,29 +620,6 @@ namespace ISX
}
}
#else
#if DEVELOPMENT_BUILD
private static RemoteInputNetworkTransportToEditor s_RemoteEditorConnection;
#endif
[RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void InitializeInPlayer()
{
// No domain reloads in the player so we don't need to look for existing
// instances.
s_Manager = new InputManager();
////TODO: put this behind a switch so that it is off by default
// Automatically enable remoting in development players.
#if DEVELOPMENT_BUILD
s_Remote = new InputRemoting(s_Manager);
s_RemoteEditorConnection = new RemoteInputNetworkTransportToEditor();
s_Remote.Subscribe(s_RemoteEditorConnection);
s_RemoteEditorConnection.Subscribe(s_Remote);
s_Remote.StartSending();
#endif
}
#endif
}
}

Просмотреть файл

@ -14,7 +14,7 @@ namespace ISX
{
[SerializeField] public InputManager manager;
[NonSerialized] public InputRemoting remote;
[SerializeField] public RemoteInputPlayerConnection playerConnections;
[SerializeField] public RemoteInputPlayerConnection playerConnection;
[SerializeField] private InputRemoting.SerializedState m_RemotingState;
@ -26,7 +26,6 @@ namespace ISX
manager.Initialize();
// In the editor, we always set up for remoting.
playerConnections = new RemoteInputPlayerConnection();
SetUpRemoting();
}
@ -40,8 +39,12 @@ namespace ISX
{
remote = new InputRemoting(manager, m_RemotingState.senderId);
remote.RestoreState(m_RemotingState, manager);
remote.Subscribe(playerConnections); // Feed messages from players into editor.
playerConnections.Subscribe(remote); // Feed messages from editor into players.
if (playerConnection == null)
playerConnection = CreateInstance<RemoteInputPlayerConnection>();
remote.Subscribe(playerConnection); // Feed messages from players into editor.
playerConnection.Subscribe(remote); // Feed messages from editor into players.
// We don't enable sending on the editor's remote by default.
// By default, the editor acts as a receiver only.

Просмотреть файл

@ -1,3 +1,4 @@
#if DEVELOPMENT_BUILD || UNITY_EDITOR
using NUnit.Framework;
namespace ISX
@ -21,9 +22,11 @@ namespace ISX
// none of the native devices.
InputSystem.Reset();
#if UNITY_EDITOR
// Make sure we're not affected by the user giving focus away from the
// game view.
InputConfiguration.LockInputToGame = true;
#endif
if (InputSystem.devices.Count > 0)
Assert.Fail("Input system should not have devices after reset");
@ -42,3 +45,4 @@ namespace ISX
}
}
}
#endif // DEVELOPMENT_BUILD || UNITY_EDITOR

Просмотреть файл

@ -3,20 +3,21 @@
--- !u!129 &1
PlayerSettings:
m_ObjectHideFlags: 0
serializedVersion: 14
serializedVersion: 15
productGUID: 97965645ae37e47c485fb153bcb9de86
AndroidProfiler: 0
AndroidFilterTouchesWhenObscured: 0
AndroidEnableSustainedPerformanceMode: 0
defaultScreenOrientation: 4
targetDevice: 2
useOnDemandResources: 0
accelerometerFrequency: 60
companyName: DefaultCompany
productName: InputSystemX
productName: UnityTestFramework
defaultCursor: {fileID: 0}
cursorHotspot: {x: 0, y: 0}
m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
m_ShowUnitySplashScreen: 1
m_ShowUnitySplashScreen: 0
m_ShowUnitySplashLogo: 1
m_SplashScreenOverlayOpacity: 1
m_SplashScreenAnimation: 1
@ -53,7 +54,7 @@ PlayerSettings:
androidShowActivityIndicatorOnLoading: -1
tizenShowActivityIndicatorOnLoading: -1
iosAppInBackgroundBehavior: 0
displayResolutionDialog: 1
displayResolutionDialog: 0
iosAllowHTTPDownload: 1
allowedAutorotateToPortrait: 1
allowedAutorotateToPortraitUpsideDown: 1
@ -72,11 +73,13 @@ PlayerSettings:
muteOtherAudioSources: 0
Prepare IOS For Recording: 0
Force IOS Speakers When Recording: 0
deferSystemGesturesMode: 0
hideHomeButton: 0
submitAnalytics: 1
usePlayerLog: 1
bakeCollisionMeshes: 0
forceSingleInstance: 0
resizableWindow: 0
resizableWindow: 1
useMacAppStoreValidation: 0
macAppStoreCategory: public.app-category.games
gpuSkinning: 0
@ -89,7 +92,7 @@ PlayerSettings:
visibleInBackground: 1
allowFullscreenSwitch: 1
graphicsJobMode: 0
fullscreenMode: 1
fullscreenMode: 3
xboxSpeechDB: 0
xboxEnableHeadOrientation: 0
xboxEnableGuest: 0
@ -99,6 +102,8 @@ PlayerSettings:
n3dsEnableSharedListOpt: 1
n3dsEnableVSync: 0
xboxOneResolution: 0
xboxOneSResolution: 0
xboxOneXResolution: 3
xboxOneMonoLoggingLevel: 0
xboxOneLoggingLevel: 1
xboxOneDisableEsram: 0
@ -154,7 +159,7 @@ PlayerSettings:
AndroidMinSdkVersion: 16
AndroidTargetSdkVersion: 0
AndroidPreferredInstallLocation: 1
aotOptions:
aotOptions: nimt-trampolines=1024
stripEngineCode: 1
iPhoneStrippingLevel: 0
iPhoneScriptCallOptimization: 0
@ -184,15 +189,22 @@ PlayerSettings:
iPhone47inSplashScreen: {fileID: 0}
iPhone55inPortraitSplashScreen: {fileID: 0}
iPhone55inLandscapeSplashScreen: {fileID: 0}
iPhone58inPortraitSplashScreen: {fileID: 0}
iPhone58inLandscapeSplashScreen: {fileID: 0}
iPadPortraitSplashScreen: {fileID: 0}
iPadHighResPortraitSplashScreen: {fileID: 0}
iPadLandscapeSplashScreen: {fileID: 0}
iPadHighResLandscapeSplashScreen: {fileID: 0}
appleTVSplashScreen: {fileID: 0}
appleTVSplashScreen2x: {fileID: 0}
tvOSSmallIconLayers: []
tvOSSmallIconLayers2x: []
tvOSLargeIconLayers: []
tvOSLargeIconLayers2x: []
tvOSTopShelfImageLayers: []
tvOSTopShelfImageLayers2x: []
tvOSTopShelfImageWideLayers: []
tvOSTopShelfImageWideLayers2x: []
iOSLaunchScreenType: 0
iOSLaunchScreenPortrait: {fileID: 0}
iOSLaunchScreenLandscape: {fileID: 0}
@ -223,8 +235,10 @@ PlayerSettings:
iOSManualSigningProvisioningProfileID:
tvOSManualSigningProvisioningProfileID:
appleEnableAutomaticSigning: 0
iOSRequireARKit: 0
appleEnableProMotion: 0
clonedFromGUID: 00000000000000000000000000000000
AndroidTargetDevice: 0
AndroidTargetArchitectures: 5
AndroidSplashScreenScale: 0
androidSplashScreen: {fileID: 0}
AndroidKeystoreName:
@ -305,6 +319,9 @@ PlayerSettings:
switchTitleNames_9:
switchTitleNames_10:
switchTitleNames_11:
switchTitleNames_12:
switchTitleNames_13:
switchTitleNames_14:
switchPublisherNames_0:
switchPublisherNames_1:
switchPublisherNames_2:
@ -317,6 +334,9 @@ PlayerSettings:
switchPublisherNames_9:
switchPublisherNames_10:
switchPublisherNames_11:
switchPublisherNames_12:
switchPublisherNames_13:
switchPublisherNames_14:
switchIcons_0: {fileID: 0}
switchIcons_1: {fileID: 0}
switchIcons_2: {fileID: 0}
@ -329,6 +349,9 @@ PlayerSettings:
switchIcons_9: {fileID: 0}
switchIcons_10: {fileID: 0}
switchIcons_11: {fileID: 0}
switchIcons_12: {fileID: 0}
switchIcons_13: {fileID: 0}
switchIcons_14: {fileID: 0}
switchSmallIcons_0: {fileID: 0}
switchSmallIcons_1: {fileID: 0}
switchSmallIcons_2: {fileID: 0}
@ -341,6 +364,9 @@ PlayerSettings:
switchSmallIcons_9: {fileID: 0}
switchSmallIcons_10: {fileID: 0}
switchSmallIcons_11: {fileID: 0}
switchSmallIcons_12: {fileID: 0}
switchSmallIcons_13: {fileID: 0}
switchSmallIcons_14: {fileID: 0}
switchManualHTML:
switchAccessibleURLs:
switchLegalInformation:
@ -382,6 +408,8 @@ PlayerSettings:
switchLocalCommunicationIds_7:
switchParentalControl: 0
switchAllowsScreenshot: 1
switchAllowsVideoCapturing: 1
switchAllowsRuntimeAddOnContentInstall: 0
switchDataLossConfirmation: 0
switchSupportedNpadStyles: 3
switchSocketConfigEnabled: 0
@ -439,6 +467,7 @@ PlayerSettings:
ps4pnFriends: 1
ps4pnGameCustomData: 1
playerPrefsSupport: 0
enableApplicationExit: 0
restrictedAudioUsageRights: 0
ps4UseResolutionFallback: 0
ps4ReprojectionSupport: 0
@ -523,8 +552,8 @@ PlayerSettings:
webGLTemplate: APPLICATION:Default
webGLAnalyzeBuildSize: 0
webGLUseEmbeddedResources: 0
webGLUseWasm: 0
webGLCompressionFormat: 1
webGLLinkerTarget: 0
scriptingDefineSymbols: {}
platformArchitecture: {}
scriptingBackend: {}