NEW: Events now have unique IDs.
This commit is contained in:
Родитель
24d0a83c61
Коммит
94bc8cd87f
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using ISX;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngineInternal.Input;
|
||||
|
||||
////TODO: make work in player (ATM we rely on the domain reload logic; probably want to include that in debug players, too)
|
||||
|
||||
|
@ -20,11 +20,16 @@ public class FunctionalTests
|
|||
////REVIEW: We probably need to prevent device discoveries and events
|
||||
//// that could happen on the native side from mucking with our
|
||||
//// test. We can't completely disconnect from native, though,
|
||||
//// as we need the event queue.
|
||||
//// as we need the event queue. Could have a mode where we
|
||||
//// don't send event discoveries and don't flush the background
|
||||
//// queue.
|
||||
|
||||
// Put system in a blank state where it has all the templates but has
|
||||
// none of the native devices.
|
||||
InputSystem.Reset();
|
||||
|
||||
if (InputSystem.devices.Count > 0)
|
||||
Assert.Fail("Input system should not have devices after reset");
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
@ -771,11 +776,6 @@ public class FunctionalTests
|
|||
Assert.That(device.leftStick.right.stateBlock.sizeInBits, Is.EqualTo(2 * 8));
|
||||
}
|
||||
|
||||
struct CustomGamepadState
|
||||
{
|
||||
public short rightTrigger;
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("State")]
|
||||
public void State_CanStoreAxisAsShort()
|
||||
|
@ -1422,23 +1422,6 @@ public class FunctionalTests
|
|||
Assert.That(receivedCalls, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_DeviceIdIsUnaffectedByHandledFlag()
|
||||
{
|
||||
// We internally use a bit in m_DeviceId to mark events as handled.
|
||||
// Make sure we don't leak that fiddlery through the API.
|
||||
var inputEvent = new InputEvent {deviceId = 5};
|
||||
|
||||
inputEvent.handled = true;
|
||||
|
||||
Assert.That(inputEvent.deviceId, Is.EqualTo(5));
|
||||
|
||||
inputEvent.deviceId = 6;
|
||||
|
||||
Assert.That(inputEvent.handled, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_AreHandledInOrderOfIncreasingTime()
|
||||
|
@ -1481,10 +1464,94 @@ public class FunctionalTests
|
|||
Assert.That(receivedThirdTime, Is.EqualTo(kThirdTime).Within(0.00001));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_CanQueueAndReceiveEventsAgainstNonExistingDevices()
|
||||
{
|
||||
// Device IDs are looked up only *after* the system shows the event to us.
|
||||
|
||||
var receivedCalls = 0;
|
||||
var receivedDeviceId = InputDevice.kInvalidDeviceId;
|
||||
InputSystem.onEvent +=
|
||||
eventPtr =>
|
||||
{
|
||||
++receivedCalls;
|
||||
receivedDeviceId = eventPtr.deviceId;
|
||||
};
|
||||
|
||||
var inputEvent = ConnectEvent.Create(4, 1.0);
|
||||
InputSystem.QueueEvent(ref inputEvent);
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
Assert.That(receivedCalls, Is.EqualTo(1));
|
||||
Assert.That(receivedDeviceId, Is.EqualTo(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_HandledFlagIsResetWhenEventIsQueued()
|
||||
{
|
||||
var receivedCalls = 0;
|
||||
var wasHandled = true;
|
||||
|
||||
InputSystem.onEvent +=
|
||||
eventPtr =>
|
||||
{
|
||||
++receivedCalls;
|
||||
wasHandled = eventPtr.handled;
|
||||
};
|
||||
|
||||
var inputEvent = ConnectEvent.Create(4, 1.0);
|
||||
|
||||
// This should go back to false when we inputEvent goes on the queue.
|
||||
inputEvent.baseEvent.handled = true;
|
||||
|
||||
InputSystem.QueueEvent(ref inputEvent);
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
Assert.That(receivedCalls, Is.EqualTo(1));
|
||||
Assert.That(wasHandled, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_AlreadyHandledEventsAreIgnoredWhenProcessingEvents()
|
||||
{
|
||||
// Need a device with before render enabled so we can produce
|
||||
// the effect of having already handled events in the event queue.
|
||||
// If we use an invalid device, before render updates will simply
|
||||
// ignore the event.
|
||||
const string json = @"
|
||||
{
|
||||
""name"" : ""CustomGamepad"",
|
||||
""extend"" : ""Gamepad"",
|
||||
""beforeRender"" : ""Update""
|
||||
}
|
||||
";
|
||||
|
||||
InputSystem.RegisterTemplate(json);
|
||||
var device = InputSystem.AddDevice("CustomGamepad");
|
||||
|
||||
InputSystem.onEvent +=
|
||||
inputEvent =>
|
||||
{
|
||||
inputEvent.handled = true;
|
||||
};
|
||||
|
||||
var event1 = ConnectEvent.Create(device.id, 1.0);
|
||||
var event2 = ConnectEvent.Create(device.id, 2.0);
|
||||
|
||||
InputSystem.QueueEvent(ref event1);
|
||||
|
||||
// Before render update won't clear queue so after the update
|
||||
// event1 is still in there.
|
||||
InputSystem.Update(InputUpdateType.BeforeRender);
|
||||
|
||||
// Add new unhandled event.
|
||||
InputSystem.QueueEvent(ref event2);
|
||||
|
||||
var receivedCalls = 0;
|
||||
var receivedTime = 0.0;
|
||||
|
||||
|
@ -1494,20 +1561,11 @@ public class FunctionalTests
|
|||
++receivedCalls;
|
||||
receivedTime = inputEvent.time;
|
||||
};
|
||||
|
||||
var device = InputSystem.AddDevice("Gamepad");
|
||||
|
||||
var unhandledEvent = ConnectEvent.Create(device.id, 1.0);
|
||||
var handledEvent = ConnectEvent.Create(device.id, 2.0);
|
||||
handledEvent.baseEvent.handled = true;
|
||||
|
||||
InputSystem.QueueEvent(ref unhandledEvent);
|
||||
InputSystem.QueueEvent(ref handledEvent);
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
// On the second update, we should have seen only event2.
|
||||
Assert.That(receivedCalls, Is.EqualTo(1));
|
||||
Assert.That(receivedTime, Is.EqualTo(1.0).Within(0.00001));
|
||||
Assert.That(receivedTime, Is.EqualTo(2.0).Within(0.00001));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1576,6 +1634,102 @@ public class FunctionalTests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_WhenTraceIsFull_WillStartOverwritingOldEvents()
|
||||
{
|
||||
var device = InputSystem.AddDevice("Gamepad");
|
||||
var trace = new InputEventTrace(StateEvent.GetEventSizeWithPayload<GamepadState>() * 2) {deviceId = device.id};
|
||||
trace.Enable();
|
||||
|
||||
try
|
||||
{
|
||||
var firstState = new GamepadState {rightTrigger = 0.35f};
|
||||
var secondState = new GamepadState {leftTrigger = 0.75f};
|
||||
var thirdState = new GamepadState {leftTrigger = 0.95f};
|
||||
|
||||
InputSystem.QueueStateEvent(device, firstState, 0.5);
|
||||
InputSystem.QueueStateEvent(device, secondState, 1.5);
|
||||
InputSystem.QueueStateEvent(device, thirdState, 2.5);
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
trace.Disable();
|
||||
|
||||
var events = trace.ToList();
|
||||
|
||||
Assert.That(events, Has.Count.EqualTo(2));
|
||||
Assert.That(events, Has.Exactly(1).With.Property("time").EqualTo(1.5).Within(0.000001));
|
||||
Assert.That(events, Has.Exactly(1).With.Property("time").EqualTo(2.5).Within(0.000001));
|
||||
}
|
||||
finally
|
||||
{
|
||||
trace.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_GetUniqueIds()
|
||||
{
|
||||
var device = InputSystem.AddDevice("Gamepad");
|
||||
|
||||
InputSystem.QueueStateEvent(device, new GamepadState());
|
||||
InputSystem.QueueStateEvent(device, new GamepadState());
|
||||
|
||||
var receivedCalls = 0;
|
||||
var firstId = InputEvent.kInvalidId;
|
||||
var secondId = InputEvent.kInvalidId;
|
||||
|
||||
InputSystem.onEvent +=
|
||||
eventPtr =>
|
||||
{
|
||||
++receivedCalls;
|
||||
if (receivedCalls == 1)
|
||||
firstId = eventPtr.id;
|
||||
else if (receivedCalls == 2)
|
||||
secondId = eventPtr.id;
|
||||
};
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
Assert.That(firstId, Is.Not.EqualTo(secondId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Events")]
|
||||
public void Events_DoNotLeakIntoNextUpdate()
|
||||
{
|
||||
var device = InputSystem.AddDevice("Gamepad");
|
||||
|
||||
InputSystem.QueueStateEvent(device, new GamepadState(), 1.0);
|
||||
InputSystem.QueueStateEvent(device, new GamepadState(), 2.0);
|
||||
|
||||
var receivedUpdateCalls = 0;
|
||||
var receivedEventCount = 0;
|
||||
|
||||
NativeUpdateCallback onUpdate =
|
||||
(updateType, eventCount, eventData) =>
|
||||
{
|
||||
++receivedUpdateCalls;
|
||||
receivedEventCount += eventCount;
|
||||
};
|
||||
NativeInputSystem.onUpdate += onUpdate;
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
Assert.That(receivedUpdateCalls, Is.EqualTo(1));
|
||||
Assert.That(receivedEventCount, Is.EqualTo(2));
|
||||
|
||||
receivedEventCount = 0;
|
||||
receivedUpdateCalls = 0;
|
||||
|
||||
InputSystem.Update();
|
||||
|
||||
Assert.That(receivedEventCount, Is.Zero);
|
||||
Assert.That(receivedUpdateCalls, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Actions")]
|
||||
public void Actions_CanAddActionThatTargetsSingleControl()
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
using System.Reflection;
|
||||
using ISX;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
public class PerformanceTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
InputSystem.Save();
|
||||
InputSystem.Reset();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
InputSystem.Restore();
|
||||
}
|
||||
|
||||
// Performing a full state update on 10 devices should take less than 0.01 ms.
|
||||
[Test]
|
||||
[Category("Performance")]
|
||||
public void CanUpdate10GamepadsInLessThanPointZeroOneMilliseconds()
|
||||
{
|
||||
const int kNumGamepads = 10;
|
||||
|
||||
var gamepads = new Gamepad[kNumGamepads];
|
||||
for (var i = 0; i < kNumGamepads; ++i)
|
||||
gamepads[i] = (Gamepad)InputSystem.AddDevice("Gamepad");
|
||||
|
||||
var startTime = Time.realtimeSinceStartup;
|
||||
|
||||
// Generate a full state update for each gamepad.
|
||||
for (var i = 0; i < kNumGamepads; ++i)
|
||||
InputSystem.QueueStateEvent(gamepads[i], new GamepadState());
|
||||
|
||||
// Now run the update.
|
||||
InputSystem.Update();
|
||||
|
||||
var endTime = Time.realtimeSinceStartup;
|
||||
var totalTime = endTime - startTime;
|
||||
|
||||
Assert.That(totalTime, Is.LessThan(0.01 / 1000.0));
|
||||
Debug.Log($"{MethodBase.GetCurrentMethod().Name}: {totalTime*1000}ms");
|
||||
}
|
||||
|
||||
////TODO: same test but with several actions listening on each gamepad
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fbdee94cc663465f813d2081d675282d
|
||||
timeCreated: 1507734516
|
|
@ -3,6 +3,7 @@ using NUnit.Framework;
|
|||
public class StressTests
|
||||
{
|
||||
[Test]
|
||||
[Category("Stress")]
|
||||
public void TODO_Create512GamepadsAndSend1024Events()
|
||||
{
|
||||
Assert.Fail();
|
||||
|
|
|
@ -62,6 +62,7 @@ namespace ISX
|
|||
}
|
||||
}
|
||||
|
||||
////REVIEW: this would technically allow a GetValue<TValue>() method
|
||||
public InputControl lastSource => m_LastSource;
|
||||
|
||||
public bool enabled => m_Enabled;
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace ISX
|
|||
return value >= point;
|
||||
}
|
||||
|
||||
////REVIEW: this may have to go into value itself; otherwise actions will trigger on the slightest value change
|
||||
public bool isPressed => IsValueConsideredPressed(value);
|
||||
public bool wasPressedThisFrame => IsValueConsideredPressed(value) && !IsValueConsideredPressed(previous);
|
||||
public bool wasReleasedThisFrame => !IsValueConsideredPressed(value) && IsValueConsideredPressed(previous);
|
||||
|
|
|
@ -21,6 +21,11 @@ namespace ISX
|
|||
public const string Pressure = "Pressure";
|
||||
public const string Position = "Position";
|
||||
public const string Orientation = "Orientation";
|
||||
|
||||
// Rotation around single, fixed axis.
|
||||
// Example: twist on joystick or twist of pen (few pens support that).
|
||||
public const string Twist = "Twist";
|
||||
|
||||
public const string Point = "Point";
|
||||
|
||||
public const string LowFreqMotor = "LowFreqMotor";
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
namespace ISX
|
||||
{
|
||||
public class Joystick
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e14a578f61014fccab81ed0f1bc21948
|
||||
timeCreated: 1507753901
|
|
@ -0,0 +1,6 @@
|
|||
namespace ISX
|
||||
{
|
||||
public class InputActionDebuggerWindow
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7e9a52c3262d41cd9114cb475831dc7c
|
||||
timeCreated: 1507756149
|
|
@ -0,0 +1,6 @@
|
|||
namespace ISX
|
||||
{
|
||||
public class InputActionInspector
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 780f6a6e947c4acea6b872829d01b0e4
|
||||
timeCreated: 1507756025
|
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||
namespace ISX
|
||||
{
|
||||
// Input device got re-connected after a disconnect.
|
||||
[StructLayout(LayoutKind.Explicit, Size = 20)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
|
||||
public struct ConnectEvent : IInputEventTypeInfo
|
||||
{
|
||||
public const int Type = 0x44434F4E;
|
||||
|
@ -19,7 +19,7 @@ namespace ISX
|
|||
public static ConnectEvent Create(int deviceId, double time)
|
||||
{
|
||||
var inputEvent = new ConnectEvent();
|
||||
inputEvent.baseEvent = new InputEvent(Type, 20, deviceId, time);
|
||||
inputEvent.baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize, deviceId, time);
|
||||
return inputEvent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||
namespace ISX
|
||||
{
|
||||
// Input device got disconnected.
|
||||
[StructLayout(LayoutKind.Explicit, Size = 20)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
|
||||
public struct DisconnectEvent : IInputEventTypeInfo
|
||||
{
|
||||
public const int Type = 0x44444953;
|
||||
|
@ -19,7 +19,7 @@ namespace ISX
|
|||
public static DisconnectEvent Create(int deviceId, double time)
|
||||
{
|
||||
var inputEvent = new DisconnectEvent();
|
||||
inputEvent.baseEvent = new InputEvent(Type, 20, deviceId, time);
|
||||
inputEvent.baseEvent = new InputEvent(Type, InputEvent.kBaseEventSize, deviceId, time);
|
||||
return inputEvent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,31 +4,31 @@ namespace ISX
|
|||
{
|
||||
// A chunk of memory signaling a data transfer in the input system.
|
||||
// This has to be layout compatible with native events.
|
||||
[StructLayout(LayoutKind.Explicit, Size = InputEvent.kBaseEventSize)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = kBaseEventSize)]
|
||||
public struct InputEvent : IInputEventTypeInfo
|
||||
{
|
||||
private const uint kHandledMask = 0x80000000;
|
||||
private const uint kDeviceIdMask = 0x7FFFFFFF;
|
||||
private const uint kIdMask = 0x7FFFFFFF;
|
||||
|
||||
public const int kBaseEventSize = 20;
|
||||
public const int kInvalidId = 0;
|
||||
|
||||
[FieldOffset(0)]
|
||||
private FourCC m_Type;
|
||||
[FieldOffset(4)]
|
||||
private int m_SizeInBytes;
|
||||
[FieldOffset(8)]
|
||||
private uint m_DeviceId;
|
||||
[FieldOffset(12)]
|
||||
private double m_Time;
|
||||
[FieldOffset(0)] private FourCC m_Type;
|
||||
[FieldOffset(4)] private ushort m_SizeInBytes;
|
||||
[FieldOffset(6)] private ushort m_DeviceId;
|
||||
[FieldOffset(8)] private uint m_EventId;
|
||||
////REVIEW: does this really need to be a double? float would save us a 4 bytes
|
||||
[FieldOffset(12)] private double m_Time;
|
||||
|
||||
public FourCC type => m_Type;
|
||||
public int sizeInBytes => m_SizeInBytes;
|
||||
|
||||
public int eventId => (int)(m_EventId & kIdMask);
|
||||
|
||||
public int deviceId
|
||||
{
|
||||
// Need to mask out handled bit.
|
||||
get { return (int)(m_DeviceId & kDeviceIdMask); }
|
||||
set { m_DeviceId = (m_DeviceId & kHandledMask) | (uint)value; }
|
||||
get { return m_DeviceId; }
|
||||
set { m_DeviceId = (ushort)value; }
|
||||
}
|
||||
|
||||
public double time
|
||||
|
@ -40,9 +40,10 @@ namespace ISX
|
|||
public InputEvent(FourCC type, int sizeInBytes, int deviceId, double time)
|
||||
{
|
||||
m_Type = type;
|
||||
m_SizeInBytes = sizeInBytes;
|
||||
m_DeviceId = (uint)deviceId;
|
||||
m_SizeInBytes = (ushort)sizeInBytes;
|
||||
m_DeviceId = (ushort)deviceId;
|
||||
m_Time = time;
|
||||
m_EventId = kInvalidId;
|
||||
}
|
||||
|
||||
public FourCC GetTypeStatic()
|
||||
|
@ -50,18 +51,20 @@ namespace ISX
|
|||
return new FourCC(); // No valid type code; InputEvent is considered abstract.
|
||||
}
|
||||
|
||||
// We internally use bits inside m_DeviceId as flags. Device IDs are
|
||||
// linearly counted up by the native input system starting at 1 so we
|
||||
// have plenty room in m_DeviceId.
|
||||
// We internally use bits inside m_EventId as flags. IDs are linearly counted up by the
|
||||
// native input system starting at 1 so we have plenty room.
|
||||
// NOTE: The native system assigns IDs when events are queued so if our handled flag
|
||||
// will implicitly get overwritten. Having events go back to unhandled state
|
||||
// when they go on the queue makes sense in itself, though, so this is fine.
|
||||
public bool handled
|
||||
{
|
||||
get { return (m_DeviceId & kHandledMask) == kHandledMask; }
|
||||
set { m_DeviceId |= kHandledMask; }
|
||||
get { return (m_EventId & kHandledMask) == kHandledMask; }
|
||||
set { m_EventId |= kHandledMask; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"type = {type}, device = {deviceId}, size = {sizeInBytes}, time = {time}";
|
||||
return $"id={eventId} type={type} device={deviceId} size={sizeInBytes} time={time}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,16 @@ namespace ISX
|
|||
}
|
||||
}
|
||||
|
||||
public int id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!valid)
|
||||
return 0;
|
||||
return m_EventPtr->eventId;
|
||||
}
|
||||
}
|
||||
|
||||
public FourCC type
|
||||
{
|
||||
get
|
||||
|
|
|
@ -93,12 +93,6 @@ namespace ISX
|
|||
var nextEvent = current.data + current.sizeInBytes;
|
||||
var endOfBuffer = m_EventBuffer + m_EventBufferSize;
|
||||
|
||||
Debug.Assert(nextEvent.ToInt64() < endOfBuffer.ToInt64());
|
||||
|
||||
// If we've run into our tail, there's no more events.
|
||||
if (nextEvent.ToInt64() >= m_EventBufferTail.ToInt64())
|
||||
return false;
|
||||
|
||||
// If we've reached blank space at the end of the buffer, wrap
|
||||
// around to the beginning. In this scenario there must be an event
|
||||
// at the beginning of the buffer; tail won't position itself at
|
||||
|
@ -107,8 +101,10 @@ namespace ISX
|
|||
((InputEvent*)nextEvent)->sizeInBytes == 0)
|
||||
{
|
||||
nextEvent = m_EventBuffer;
|
||||
return true;
|
||||
}
|
||||
// If we've run into our tail, there's no more events.
|
||||
else if (nextEvent.ToInt64() >= m_EventBufferTail.ToInt64())
|
||||
return false;
|
||||
|
||||
// We're good. There's still space between us and our tail.
|
||||
current = new InputEventPtr((InputEvent*)nextEvent);
|
||||
|
@ -215,16 +211,28 @@ namespace ISX
|
|||
newTail = m_EventBuffer + eventSize;
|
||||
|
||||
// Recheck whether we're overtaking head.
|
||||
newTailOvertakesHead &= newTail.ToInt64() > m_EventBufferHead.ToInt64();
|
||||
newTailOvertakesHead = newTail.ToInt64() > m_EventBufferHead.ToInt64();
|
||||
}
|
||||
|
||||
// If the new tail runs into head, bump head as many times as we need to
|
||||
// make room for the event. Head may itself wrap around here.
|
||||
if (newTailOvertakesHead)
|
||||
{
|
||||
var ptr = new InputEventPtr((InputEvent*)m_EventBufferHead);
|
||||
while (GetNextEvent(ref ptr))
|
||||
m_EventBufferHead = ptr.data;
|
||||
var newHead = (byte*)m_EventBufferHead;
|
||||
var endOfBufferMinusOneEvent =
|
||||
(byte*)m_EventBuffer + m_EventBufferSize - InputEvent.kBaseEventSize;
|
||||
|
||||
while (newHead < (byte*)newTail)
|
||||
{
|
||||
newHead += ((InputEvent*)newHead)->sizeInBytes;
|
||||
if (newHead > endOfBufferMinusOneEvent)
|
||||
{
|
||||
newHead = (byte*)m_EventBuffer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_EventBufferHead = new IntPtr(newHead);
|
||||
}
|
||||
|
||||
buffer = m_EventBufferTail;
|
||||
|
|
|
@ -5,19 +5,17 @@ using UnityEngine;
|
|||
namespace ISX
|
||||
{
|
||||
// Full state update for an input device.
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 25)]
|
||||
// Variable-size event.
|
||||
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = InputEvent.kBaseEventSize + 5)]
|
||||
public unsafe struct StateEvent : IInputEventTypeInfo
|
||||
{
|
||||
public const int Type = 0x53544154;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public InputEvent baseEvent;
|
||||
[FieldOffset(20)]
|
||||
public FourCC stateFormat;
|
||||
[FieldOffset(24)]
|
||||
public fixed byte stateData[1]; // Variable-sized.
|
||||
[FieldOffset(0)] public InputEvent baseEvent;
|
||||
[FieldOffset(InputEvent.kBaseEventSize)] public FourCC stateFormat;
|
||||
[FieldOffset(InputEvent.kBaseEventSize + 4)] public fixed byte stateData[1]; // Variable-sized.
|
||||
|
||||
public int stateSizeInBytes => baseEvent.sizeInBytes - 24;
|
||||
public int stateSizeInBytes => baseEvent.sizeInBytes - (InputEvent.kBaseEventSize + 4);
|
||||
|
||||
public IntPtr state
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngineInternal.Input;
|
||||
|
||||
namespace ISX
|
||||
|
@ -579,8 +580,8 @@ namespace ISX
|
|||
if (ReferenceEquals(InputProcessor.s_Processors, m_Processors))
|
||||
InputProcessor.s_Processors = null;
|
||||
|
||||
NativeInputSystem.onUpdate -= OnNativeUpdate;
|
||||
NativeInputSystem.onDeviceDiscovered -= OnNativeDeviceDiscovered;
|
||||
NativeInputSystem.onUpdate = null;
|
||||
NativeInputSystem.onDeviceDiscovered = null;
|
||||
}
|
||||
|
||||
// Revive after domain reload.
|
||||
|
@ -591,8 +592,8 @@ namespace ISX
|
|||
InputTemplate.s_BaseTemplateTable = m_BaseTemplateTable;
|
||||
InputProcessor.s_Processors = m_Processors;
|
||||
|
||||
NativeInputSystem.onUpdate += OnNativeUpdate;
|
||||
NativeInputSystem.onDeviceDiscovered += OnNativeDeviceDiscovered;
|
||||
NativeInputSystem.onUpdate = OnNativeUpdate;
|
||||
NativeInputSystem.onDeviceDiscovered = OnNativeDeviceDiscovered;
|
||||
}
|
||||
|
||||
// Bundles a template name and a device description.
|
||||
|
@ -797,6 +798,13 @@ namespace ISX
|
|||
// to the global state buffers so the user won't ever know that updates happen in the background.
|
||||
private unsafe void OnNativeUpdate(NativeInputUpdateType updateType, int eventCount, IntPtr eventData)
|
||||
{
|
||||
#if ENABLE_PROFILER
|
||||
Profiler.BeginSample("InputUpdate");
|
||||
try
|
||||
{
|
||||
#endif
|
||||
|
||||
////REVIEW: this will become obsolete when we actually turn off unneeded updates in native
|
||||
// We *always* have to process events into the current state even if the given update isn't enabled.
|
||||
// This is because the current state is for all updates and reflects the most up-to-date device states.
|
||||
|
||||
|
@ -805,10 +813,13 @@ namespace ISX
|
|||
if (eventCount <= 0)
|
||||
return;
|
||||
|
||||
var isBeforeRenderUpdate = false;
|
||||
if (updateType == NativeInputUpdateType.Dynamic)
|
||||
++m_CurrentDynamicUpdateCount;
|
||||
else if (updateType == NativeInputUpdateType.Fixed)
|
||||
++m_CurrentFixedUpdateCount;
|
||||
else if (updateType == NativeInputUpdateType.BeforeRender)
|
||||
isBeforeRenderUpdate = true;
|
||||
|
||||
// Before render updates work in a special way. For them, we only want specific devices (and
|
||||
// sometimes even just specific controls on those devices) to be updated. What native will do is
|
||||
|
@ -820,37 +831,91 @@ namespace ISX
|
|||
// be used to, for example, *only* update tracking on a device that also contains buttons -- which
|
||||
// should not get updated in berfore render).
|
||||
|
||||
var firstEventPtr = (InputEvent*)eventData;
|
||||
var remainingEventCount = eventCount;
|
||||
|
||||
// Handle events.
|
||||
var firstEvent = (InputEvent*)eventData;
|
||||
for (var i = 0; i < eventCount; ++i)
|
||||
while (remainingEventCount > 0)
|
||||
{
|
||||
// Bump firstEvent up to the next unhandled event.
|
||||
while (eventCount > 0 && firstEvent->handled)
|
||||
InputDevice device = null;
|
||||
InputEvent* currentEventPtr;
|
||||
double currentEventTime;
|
||||
|
||||
////REVIEW: The whole event sorting/filtering logic here is too convoluted. Unfortunately,
|
||||
//// neither the sorting by time nor the filtering of before-render events is
|
||||
//// super trivial. Hope there is a much simpler and also more performant way to do
|
||||
//// this. I'm pretty sure that the cost of the sorting especially can easily
|
||||
//// overshadow the cost of event handling itself.
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
Profiler.BeginSample("SortInputEvents");
|
||||
try
|
||||
{
|
||||
firstEvent = (InputEvent*)((byte*)firstEvent + firstEvent->sizeInBytes);
|
||||
--eventCount;
|
||||
#endif
|
||||
|
||||
// Bump firstEvent up to the next unhandled event (in before-render updates
|
||||
// the event needs to be *both* unhandled *and* for a device with before
|
||||
// render updates enabled).
|
||||
while (remainingEventCount > 0)
|
||||
{
|
||||
if (isBeforeRenderUpdate)
|
||||
{
|
||||
if (!firstEventPtr->handled)
|
||||
{
|
||||
device = TryGetDeviceById(firstEventPtr->deviceId);
|
||||
if (device != null && device.updateBeforeRender)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!firstEventPtr->handled)
|
||||
break;
|
||||
|
||||
firstEventPtr = (InputEvent*)((byte*)firstEventPtr + firstEventPtr->sizeInBytes);
|
||||
--remainingEventCount;
|
||||
}
|
||||
if (eventCount == 0)
|
||||
if (remainingEventCount == 0)
|
||||
break;
|
||||
|
||||
////REVIEW: can we do this faster?
|
||||
// Find next oldest unhandled event.
|
||||
var currentEventPtr = firstEvent;
|
||||
currentEventPtr = firstEventPtr;
|
||||
var oldestEventPtr = currentEventPtr;
|
||||
var oldestEventTime = oldestEventPtr->time;
|
||||
for (var n = 1; n < eventCount; ++n)
|
||||
for (var n = 1; n < remainingEventCount; ++n)
|
||||
{
|
||||
var nextEventPtr = (InputEvent*)((byte*)currentEventPtr + currentEventPtr->sizeInBytes);
|
||||
|
||||
if (!nextEventPtr->handled && nextEventPtr->time < oldestEventTime)
|
||||
if (isBeforeRenderUpdate)
|
||||
{
|
||||
if (!nextEventPtr->handled && nextEventPtr->time < oldestEventTime)
|
||||
{
|
||||
var nextEventDevice = TryGetDeviceById(nextEventPtr->deviceId);
|
||||
if (nextEventDevice != null && nextEventDevice.updateBeforeRender)
|
||||
{
|
||||
oldestEventPtr = nextEventPtr;
|
||||
oldestEventTime = nextEventPtr->time;
|
||||
device = nextEventDevice;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!nextEventPtr->handled && nextEventPtr->time < oldestEventTime)
|
||||
{
|
||||
oldestEventPtr = nextEventPtr;
|
||||
oldestEventTime = oldestEventPtr->time;
|
||||
oldestEventTime = nextEventPtr->time;
|
||||
}
|
||||
|
||||
currentEventPtr = nextEventPtr;
|
||||
}
|
||||
currentEventPtr = oldestEventPtr;
|
||||
var currentEventTime = oldestEventTime;
|
||||
currentEventTime = oldestEventTime;
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
}
|
||||
finally
|
||||
{
|
||||
Profiler.EndSample();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Give listeners a shot at the event.
|
||||
if (m_EventReceivedEvent != null)
|
||||
|
@ -860,26 +925,22 @@ namespace ISX
|
|||
continue;
|
||||
}
|
||||
|
||||
// Grab device for event.
|
||||
var device = TryGetDeviceById(currentEventPtr->deviceId);
|
||||
// Grab device for event. In before-render updates, we already had to
|
||||
// check the device.
|
||||
if (!isBeforeRenderUpdate)
|
||||
device = TryGetDeviceById(currentEventPtr->deviceId);
|
||||
if (device == null)
|
||||
{
|
||||
// No device found matching event. Consider it handled.
|
||||
currentEventPtr->handled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process.
|
||||
var currentEventType = currentEventPtr->type;
|
||||
switch (currentEventType)
|
||||
{
|
||||
case StateEvent.Type:
|
||||
|
||||
// In before render updates, only devices that explicitly enable
|
||||
// before render updates will see their state updated. Events shown
|
||||
// to us in before render updates will re-surface again on the next
|
||||
// normal update so we can safely skip those events.
|
||||
if (updateType == NativeInputUpdateType.BeforeRender && !device.updateBeforeRender)
|
||||
////FIXME: have to make sure next iteration doesn't come up with the same
|
||||
//// event again; current code produces an infinite loop
|
||||
continue;
|
||||
|
||||
var stateEventPtr = (StateEvent*)currentEventPtr;
|
||||
var stateType = stateEventPtr->stateFormat;
|
||||
var stateBlock = device.m_StateBlock;
|
||||
|
@ -977,13 +1038,30 @@ namespace ISX
|
|||
}
|
||||
|
||||
// Mark as processed by setting time to negative.
|
||||
oldestEventPtr->handled = true;
|
||||
currentEventPtr->handled = true;
|
||||
|
||||
// If the event we handled was the first one at our current buffer position and
|
||||
// have still more events to go, bump our buffer position by one.
|
||||
if (currentEventPtr == firstEventPtr && remainingEventCount >= 1)
|
||||
{
|
||||
firstEventPtr = (InputEvent*)((byte*)currentEventPtr + currentEventPtr->sizeInBytes);
|
||||
--remainingEventCount;
|
||||
}
|
||||
|
||||
// Device received event so make it current.
|
||||
device.MakeCurrent();
|
||||
}
|
||||
|
||||
////TODO: fire event that allows code to update state *from* state we just updated
|
||||
|
||||
#if ENABLE_PROFILER
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
Profiler.EndSample();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// If anyone is listening for state changes on the given device, run state change detections
|
||||
|
|
|
@ -35,6 +35,9 @@ GraphicsSettings:
|
|||
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_PreloadedShaders: []
|
||||
m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
|
||||
type: 0}
|
||||
|
|
|
@ -89,7 +89,7 @@ PlayerSettings:
|
|||
visibleInBackground: 1
|
||||
allowFullscreenSwitch: 1
|
||||
graphicsJobMode: 0
|
||||
fullscreenMode: -1
|
||||
fullscreenMode: 1
|
||||
xboxSpeechDB: 0
|
||||
xboxEnableHeadOrientation: 0
|
||||
xboxEnableGuest: 0
|
||||
|
|
Загрузка…
Ссылка в новой задаче