Skip to content

Commit e207a70

Browse files
authored
CHANGE: Add event processing limits (#1325)
1 parent 4453d38 commit e207a70

File tree

6 files changed

+106
-8
lines changed

6 files changed

+106
-8
lines changed

Assets/TestScript.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Tests/InputSystem/CoreTests_Events.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using UnityEngine.TestTools.Constraints;
2121
using UnityEngine.TestTools.Utils;
2222
using Is = UnityEngine.TestTools.Constraints.Is;
23+
using Random = UnityEngine.Random;
2324
using TouchPhase = UnityEngine.InputSystem.TouchPhase;
2425

2526
#pragma warning disable CS0649
@@ -1991,7 +1992,6 @@ public IEnumerator Events_CanTestInputDistributedOverFrames()
19911992

19921993
[Test]
19931994
[Category("Events")]
1994-
[Ignore("Not implemented yet. Fix as part of https://jira.unity3d.com/browse/ISX-557.")]
19951995
public void Events_MaximumEventLoadPerUpdateIsLimited()
19961996
{
19971997
// Default setting is 5MB.
@@ -2009,7 +2009,7 @@ public void Events_MaximumEventLoadPerUpdateIsLimited()
20092009
InputSystem.onEvent += (eventPtr, device) => ++ eventCount;
20102010

20112011
LogAssert.Expect(LogType.Error, "Exceeded budget for maximum input event throughput per InputSystem.Update(). Discarding remaining events. "
2012-
+ "Increase InputSystem.settings.maxEventBytesPerUpdate or set it to 0 to raise the limit.");
2012+
+ "Increase InputSystem.settings.maxEventBytesPerUpdate or set it to 0 to remove the limit.");
20132013

20142014
InputSystem.Update();
20152015

@@ -2041,6 +2041,10 @@ public void Events_CanQueueEventsFromWithinEventProcessing()
20412041
var keyboard = InputSystem.AddDevice<Keyboard>();
20422042

20432043
var numMouseEventsQueued = InputTestRuntime.kDefaultEventBufferSize / StateEvent.GetEventSizeWithPayload<MouseState>() + 1;
2044+
2045+
// allow all these events
2046+
InputSystem.settings.maxQueuedEventsPerUpdate = numMouseEventsQueued;
2047+
20442048
var numMouseEventsReceived = 0;
20452049
InputSystem.onEvent +=
20462050
(eventPtr, device) =>
@@ -2068,5 +2072,36 @@ public void Events_CanQueueEventsFromWithinEventProcessing()
20682072
Assert.That(numMouseEventsReceived, Is.EqualTo(numMouseEventsQueued));
20692073
}
20702074

2075+
[Test]
2076+
[Category("Events")]
2077+
public void Events_MaximumQueuedEventsDuringEventProcessingIsLimited()
2078+
{
2079+
// Default setting is 1000.
2080+
Assert.That(InputSystem.settings.maxQueuedEventsPerUpdate, Is.EqualTo(1000));
2081+
2082+
InputSystem.settings.maxQueuedEventsPerUpdate = 20;
2083+
2084+
var mouse = InputSystem.AddDevice<Mouse>();
2085+
2086+
var callbackCount = 0;
2087+
var action = new InputAction(type: InputActionType.Value, binding: "<mouse>/position");
2088+
action.performed +=
2089+
_ =>
2090+
{
2091+
if (callbackCount > InputSystem.settings.maxQueuedEventsPerUpdate)
2092+
Assert.Fail("Maximum queued event count exceeded");
2093+
2094+
callbackCount++;
2095+
Set(mouse.position, Random.insideUnitCircle * 100, queueEventOnly: true);
2096+
};
2097+
action.Enable();
2098+
2099+
Set(mouse.position, Random.insideUnitCircle * 100);
2100+
2101+
LogAssert.Expect(LogType.Error, $"Maximum number of queued events exceeded. Set the '{nameof(InputSettings.maxQueuedEventsPerUpdate)}' setting to a higher value if you " +
2102+
$"need to queue more events than this. Current limit is '{InputSystem.settings.maxQueuedEventsPerUpdate}'.");
2103+
Assert.That(callbackCount - 1, Is.EqualTo(InputSystem.settings.maxQueuedEventsPerUpdate));
2104+
}
2105+
20712106
////TODO: test thread-safe QueueEvent
20722107
}

Packages/com.unity.inputsystem/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ however, it has to be formatted properly to pass verification tests.
108108
* Puts an upper limit on the number of event bytes processed in a single update.
109109
* If exceeded, any additional event data will get thrown away and an error will be issued.
110110
* Set to 5MB by default.
111+
- Added a new API-only setting called `InputSystem.settings.maxQueuedEventsPerUpdate`.
112+
* This limits the number of events that can be queued during event processing using the `InputSystem.QueueEvent` method. This guards against infinite loops in the case where an action callback queues an event that causes the same action callback to be called again.
111113
- Added `InputSystemUIInputModule.AssignDefaultActions` to assign default actions when creating ui module in runtime.
112114
- Added `UNITY_INCLUDE_TESTS` define constraints to our test assemblies, which is 2019.2+ equivalent to `"optionalUnityReferences": ["TestAssemblies"]`.
113115

Packages/com.unity.inputsystem/InputSystem/Events/InputEventStream.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal unsafe struct InputEventStream
3030
(byte*)NativeArrayUnsafeUtility
3131
.GetUnsafeBufferPointerWithoutChecks(m_NativeBuffer.data));
3232

33-
public InputEventStream(ref InputEventBuffer eventBuffer)
33+
public InputEventStream(ref InputEventBuffer eventBuffer, int maxAppendedEvents)
3434
{
3535
m_CurrentNativeEventWritePtr = m_CurrentNativeEventReadPtr =
3636
(InputEvent*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(eventBuffer.data);
@@ -42,6 +42,7 @@ public InputEventStream(ref InputEventBuffer eventBuffer)
4242
m_CurrentAppendEventReadPtr = m_CurrentAppendEventWritePtr = default;
4343
m_AppendBuffer = default;
4444
m_RemainingAppendEventCount = 0;
45+
m_MaxAppendedEvents = maxAppendedEvents;
4546

4647
m_IsOpen = true;
4748
}
@@ -71,6 +72,14 @@ public void Close(ref InputEventBuffer eventBuffer)
7172

7273
public void Write(InputEvent* eventPtr)
7374
{
75+
if (m_AppendBuffer.eventCount >= m_MaxAppendedEvents)
76+
{
77+
Debug.LogError($"Maximum number of queued events exceeded. Set the '{nameof(InputSettings.maxQueuedEventsPerUpdate)}' " +
78+
$"setting to a higher value if you need to queue more events than this. " +
79+
$"Current limit is '{m_MaxAppendedEvents}'.");
80+
return;
81+
}
82+
7483
var wasAlreadyCreated = m_AppendBuffer.data.IsCreated;
7584
var oldBufferPtr = (byte*)m_AppendBuffer.bufferPtr.data;
7685

@@ -117,6 +126,7 @@ public void Write(InputEvent* eventPtr)
117126
private InputEvent* m_CurrentNativeEventReadPtr;
118127
private InputEvent* m_CurrentNativeEventWritePtr;
119128
private int m_RemainingNativeEventCount;
129+
private readonly int m_MaxAppendedEvents;
120130

121131
// During Update, new events that are queued will be added to the append buffer
122132
private InputEventBuffer m_AppendBuffer;

Packages/com.unity.inputsystem/InputSystem/InputManager.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2631,11 +2631,20 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
26312631
var processingStartTime = Stopwatch.GetTimestamp();
26322632
var totalEventLag = 0.0;
26332633

2634-
m_InputEventStream = new InputEventStream(ref eventBuffer);
2634+
m_InputEventStream = new InputEventStream(ref eventBuffer, m_Settings.maxQueuedEventsPerUpdate);
2635+
var totalEventBytesProcessed = 0U;
26352636

26362637
// Handle events.
26372638
while (m_InputEventStream.remainingEventCount > 0)
26382639
{
2640+
if (m_Settings.maxEventBytesPerUpdate > 0 &&
2641+
totalEventBytesProcessed >= m_Settings.maxEventBytesPerUpdate)
2642+
{
2643+
Debug.LogError("Exceeded budget for maximum input event throughput per InputSystem.Update(). Discarding remaining events. "
2644+
+ "Increase InputSystem.settings.maxEventBytesPerUpdate or set it to 0 to remove the limit.");
2645+
break;
2646+
}
2647+
26392648
InputDevice device = null;
26402649
var currentEventReadPtr = m_InputEventStream.currentEventPtr;
26412650

@@ -2786,6 +2795,8 @@ private unsafe void OnUpdate(InputUpdateType updateType, ref InputEventBuffer ev
27862795
haveChangedStateOtherThanNoise = UpdateState(device, eventPtr, updateType);
27872796
}
27882797

2798+
totalEventBytesProcessed += eventPtr.sizeInBytes;
2799+
27892800
// Update timestamp on device.
27902801
// NOTE: We do this here and not in UpdateState() so that InputState.Change() will *NOT* change timestamps.
27912802
// Only events should.

Packages/com.unity.inputsystem/InputSystem/InputSettings.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,9 +417,8 @@ public float multiTapDelayTime
417417
/// </summary>
418418
/// <remarks>
419419
/// This setting establishes a bound on the amount of input event data processed in a single
420-
/// update and thus limits throughput allowed for input. This both prevents long stalls from
421-
/// leading to long delays in input processing and prevents deadlocks when events themselves
422-
/// lead to additional events being queued.
420+
/// update and thus limits throughput allowed for input. This prevents long stalls from
421+
/// leading to long delays in input processing.
423422
///
424423
/// When the limit is exceeded, all events remaining in the buffer are thrown away (the
425424
/// <see cref="InputEventBuffer"/> is reset) and an error is logged. After that, the current
@@ -443,6 +442,35 @@ public int maxEventBytesPerUpdate
443442
}
444443
}
445444

445+
/// <summary>
446+
/// Upper limit on the number of <see cref="InputEvent"/>s that can be queued within one
447+
/// <see cref="InputSystem.Update"/>.
448+
/// <remarks>
449+
/// This settings establishes an upper limit on the number of events that can be queued
450+
/// using <see cref="InputSystem.QueueEvent"/> during a single update. This prevents infinite
451+
/// loops where an action callback queues an event that causes the action callback to
452+
/// be called again which queues an event...
453+
///
454+
/// Note that this limit only applies while the input system is updating. There is no limit
455+
/// on the number of events that can be queued outside of this time, but those will be queued
456+
/// into the next frame where the <see cref="maxEventBytesPerUpdate"/> setting will apply.
457+
///
458+
/// The default value is 1000.
459+
/// </remarks>
460+
/// </summary>
461+
public int maxQueuedEventsPerUpdate
462+
{
463+
get => m_MaxQueuedEventsPerUpdate;
464+
set
465+
{
466+
if (m_MaxQueuedEventsPerUpdate == value)
467+
return;
468+
469+
m_MaxQueuedEventsPerUpdate = value;
470+
OnChange();
471+
}
472+
}
473+
446474
/// <summary>
447475
/// List of device layouts used by the project.
448476
/// </summary>
@@ -496,7 +524,8 @@ public ReadOnlyArray<string> supportedDevices
496524
[Tooltip("Determine when Unity processes events. By default, accumulated input events are flushed out before each fixed update and "
497525
+ "before each dynamic update. This setting can be used to restrict event processing to only where the application needs it.")]
498526
[SerializeField] private UpdateMode m_UpdateMode = UpdateMode.ProcessEventsInDynamicUpdate;
499-
[SerializeField] private int m_MaxEventBytesPerUpdate = 5 * 1014 * 1024;
527+
[SerializeField] private int m_MaxEventBytesPerUpdate = 5 * 1024 * 1024;
528+
[SerializeField] private int m_MaxQueuedEventsPerUpdate = 1000;
500529

501530
[SerializeField] private bool m_CompensateForScreenOrientation = true;
502531
[SerializeField] private bool m_FilterNoiseOnCurrent = false;

0 commit comments

Comments
 (0)