Skip to content

Commit a7a2da5

Browse files
committed
Misc refactoring to clean up static fields and other small improvements (ISX-1840)
- Consolidate Touchscreen's cached settings into a separate struct - Rework NativeInputRuntime initialization to (fully) employ Singleton pattern - Refactor Actions_CanHandleModification TestCase generator to work without Domain Reloads - Fix Device static fields not getting reset during SimulateDomainReload()
1 parent 454fc79 commit a7a2da5

File tree

9 files changed

+137
-54
lines changed

9 files changed

+137
-54
lines changed

Assets/Tests/InputSystem/CoreTests_Actions.cs

+49-37
Original file line numberDiff line numberDiff line change
@@ -5263,56 +5263,68 @@ public class ModificationCases : IEnumerable
52635263
[Preserve]
52645264
public ModificationCases() {}
52655265

5266+
private static readonly Modification[] ModificationAppliesToSingleActionMap =
5267+
{
5268+
Modification.AddBinding,
5269+
Modification.RemoveBinding,
5270+
Modification.ModifyBinding,
5271+
Modification.ApplyBindingOverride,
5272+
Modification.AddAction,
5273+
Modification.RemoveAction,
5274+
Modification.ChangeBindingMask,
5275+
Modification.AddDevice,
5276+
Modification.RemoveDevice,
5277+
Modification.AddDeviceGlobally,
5278+
Modification.RemoveDeviceGlobally,
5279+
// Excludes: AddMap, RemoveMap
5280+
};
5281+
5282+
private static readonly Modification[] ModificationAppliesToSingletonAction =
5283+
{
5284+
Modification.AddBinding,
5285+
Modification.RemoveBinding,
5286+
Modification.ModifyBinding,
5287+
Modification.ApplyBindingOverride,
5288+
Modification.AddDeviceGlobally,
5289+
Modification.RemoveDeviceGlobally,
5290+
};
5291+
52665292
public IEnumerator GetEnumerator()
52675293
{
5268-
bool ModificationAppliesToSingletonAction(Modification modification)
5294+
// NOTE: This executes *outside* of our test fixture during test discovery.
5295+
5296+
// We cannot directly create the InputAction objects within GetEnumerator() because the underlying
5297+
// asset object might be invalid by the time the tests are actually run.
5298+
//
5299+
// That is, NUnit TestCases are generated once when the Assembly is loaded and will persist until it's unloaded,
5300+
// meaning they'll never be recreated without a Domain Reload. However, since InputActionAsset is a ScriptableObject,
5301+
// it could be deleted or otherwise invalidated between test case creation and actual test execution.
5302+
//
5303+
// So, instead we'll create a delegate to create the Actions object as the parameter for each test case, allowing
5304+
// the test case to create an Actions object itself when it actually runs.
52695305
{
5270-
switch (modification)
5306+
var actionsFromAsset = new Func<IInputActionCollection2>(() => new DefaultInputActions().asset);
5307+
foreach (var value in Enum.GetValues(typeof(Modification)))
52715308
{
5272-
case Modification.AddBinding:
5273-
case Modification.RemoveBinding:
5274-
case Modification.ModifyBinding:
5275-
case Modification.ApplyBindingOverride:
5276-
case Modification.AddDeviceGlobally:
5277-
case Modification.RemoveDeviceGlobally:
5278-
return true;
5309+
yield return new TestCaseData(value, actionsFromAsset);
52795310
}
5280-
return false;
52815311
}
52825312

5283-
bool ModificationAppliesToSingleActionMap(Modification modification)
52845313
{
5285-
switch (modification)
5314+
var actionMap = new Func<IInputActionCollection2>(CreateMap);
5315+
foreach (var value in Enum.GetValues(typeof(Modification)))
52865316
{
5287-
case Modification.AddMap:
5288-
case Modification.RemoveMap:
5289-
return false;
5317+
if (ModificationAppliesToSingleActionMap.Contains((Modification)value))
5318+
yield return new TestCaseData(value, actionMap);
52905319
}
5291-
return true;
52925320
}
52935321

5294-
// NOTE: This executes *outside* of our test fixture during test discovery.
5295-
5296-
// Creates a matrix of all permutations of Modifications combined with assets, maps, and singleton actions.
5297-
foreach (var func in new Func<IInputActionCollection2>[] { () => new DefaultInputActions().asset, CreateMap, CreateSingletonAction })
52985322
{
5323+
var singletonMap = new Func<IInputActionCollection2>(CreateSingletonAction);
52995324
foreach (var value in Enum.GetValues(typeof(Modification)))
53005325
{
5301-
var actions = func();
5302-
if (actions is InputActionMap map)
5303-
{
5304-
if (map.m_SingletonAction != null)
5305-
{
5306-
if (!ModificationAppliesToSingletonAction((Modification)value))
5307-
continue;
5308-
}
5309-
else if (!ModificationAppliesToSingleActionMap((Modification)value))
5310-
{
5311-
continue;
5312-
}
5313-
}
5314-
5315-
yield return new TestCaseData(value, actions);
5326+
if (ModificationAppliesToSingletonAction.Contains((Modification)value))
5327+
yield return new TestCaseData(value, singletonMap);
53165328
}
53175329
}
53185330
}
@@ -5343,14 +5355,14 @@ private InputActionMap CreateSingletonAction()
53435355
[Test]
53445356
[Category("Actions")]
53455357
[TestCaseSource(typeof(ModificationCases))]
5346-
public void Actions_CanHandleModification(Modification modification, IInputActionCollection2 actions)
5358+
public void Actions_CanHandleModification(Modification modification, Func<IInputActionCollection2> getActions)
53475359
{
53485360
#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
53495361
// Exclude project-wide actions from this test
53505362
InputSystem.actions?.Disable();
53515363
InputActionState.DestroyAllActionMapStates(); // Required for `onActionChange` to report correct number of changes
53525364
#endif
5353-
5365+
var actions = getActions();
53545366
var gamepad = InputSystem.AddDevice<Gamepad>();
53555367

53565368
if (modification == Modification.AddDevice || modification == Modification.RemoveDevice)

Assets/Tests/InputSystem/CoreTests_Editor.cs

+2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ public void Editor_CanSaveAndRestoreState()
165165
Assert.That(unsupportedDevices[0].interfaceName, Is.EqualTo("Test"));
166166
}
167167

168+
#if !ENABLE_CORECLR
168169
// onFindLayoutForDevice allows dynamically injecting new layouts into the system that
169170
// are custom-tailored at runtime for the discovered device. Make sure that our domain
170171
// reload can restore these.
@@ -345,6 +346,7 @@ public void Editor_DomainReload_CanRemoveDevicesDuringDomainReload()
345346
Assert.That(InputSystem.devices, Has.Count.EqualTo(1));
346347
Assert.That(InputSystem.devices[0], Is.AssignableTo<Keyboard>());
347348
}
349+
#endif // !ENABLE_CORECLR
348350

349351
[Test]
350352
[Category("Editor")]

Packages/com.unity.inputsystem/InputSystem/Controls/InputControlLayout.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public delegate string InputDeviceFindControlLayoutDelegate(ref InputDeviceDescr
109109
/// </remarks>
110110
public class InputControlLayout
111111
{
112-
private static InternedString s_DefaultVariant = new InternedString("Default");
112+
private static readonly InternedString s_DefaultVariant = new InternedString("Default");
113113
public static InternedString DefaultVariant => s_DefaultVariant;
114114

115115
public const string VariantSeparator = ";";

Packages/com.unity.inputsystem/InputSystem/Devices/Touchscreen.cs

+23-7
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@ protected TouchControl[] touchControlArray
534534
/// <value>Current touch screen.</value>
535535
public new static Touchscreen current { get; internal set; }
536536

537+
/// <summary>
538+
/// The current global settings for Touchscreen devices.
539+
/// </summary>
540+
/// <remarks>
541+
/// These are cached values taken from <see cref="InputSettings"/>.
542+
/// </remarks>
543+
internal static TouchscreenSettings settings { get; set; }
544+
537545
/// <inheritdoc />
538546
public override void MakeCurrent()
539547
{
@@ -624,14 +632,14 @@ protected override void FinishSetup()
624632
// that to do so we would have to add another record to keep track of timestamps for each touch. And
625633
// since we know the maximum time that a tap can take, we have a reasonable estimate for when a prior
626634
// tap must have ended.
627-
if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + s_TapTime + s_TapDelayTime)
635+
if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + settings.tapTime + settings.tapDelayTime)
628636
InputState.Change(touches[i].tapCount, (byte)0);
629637
}
630638

631639
var primaryTouchState = (TouchState*)((byte*)statePtr + stateBlock.byteOffset);
632640
if (primaryTouchState->delta != default)
633641
InputState.Change(primaryTouch.delta, Vector2.zero);
634-
if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + s_TapTime + s_TapDelayTime)
642+
if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + settings.tapTime + settings.tapDelayTime)
635643
InputState.Change(primaryTouch.tapCount, (byte)0);
636644

637645
Profiler.EndSample();
@@ -720,11 +728,11 @@ protected override void FinishSetup()
720728

721729
// Detect taps.
722730
var isTap = newTouchState.isNoneEndedOrCanceled &&
723-
(eventPtr.time - newTouchState.startTime) <= s_TapTime &&
731+
(eventPtr.time - newTouchState.startTime) <= settings.tapTime &&
724732
////REVIEW: this only takes the final delta to start position into account, not the delta over the lifetime of the
725733
//// touch; is this robust enough or do we need to make sure that we never move more than the tap radius
726734
//// over the entire lifetime of the touch?
727-
(newTouchState.position - newTouchState.startPosition).sqrMagnitude <= s_TapRadiusSquared;
735+
(newTouchState.position - newTouchState.startPosition).sqrMagnitude <= settings.tapRadiusSquared;
728736
if (isTap)
729737
newTouchState.tapCount = (byte)(currentTouchState[i].tapCount + 1);
730738
else
@@ -1044,8 +1052,16 @@ private static void TriggerTap(TouchControl control, ref TouchState state, Input
10441052
state.isTapRelease = false;
10451053
}
10461054

1047-
internal static float s_TapTime;
1048-
internal static float s_TapDelayTime;
1049-
internal static float s_TapRadiusSquared;
1055+
private static TouchscreenSettings s_Settings;
1056+
}
1057+
1058+
/// <summary>
1059+
/// Cached settings retrieved from <see cref="InputSettings"/>.
1060+
/// </summary>
1061+
internal struct TouchscreenSettings
1062+
{
1063+
public float tapTime;
1064+
public float tapDelayTime;
1065+
public float tapRadiusSquared;
10501066
}
10511067
}

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

+33-5
Original file line numberDiff line numberDiff line change
@@ -2520,7 +2520,7 @@ private void InstallBeforeUpdateHookIfNecessary()
25202520

25212521
private void RestoreDevicesAfterDomainReloadIfNecessary()
25222522
{
2523-
#if UNITY_EDITOR
2523+
#if UNITY_EDITOR && !ENABLE_CORECLR
25242524
if (m_SavedDeviceStates != null)
25252525
RestoreDevicesAfterDomainReload();
25262526
#endif
@@ -2714,10 +2714,14 @@ internal void ApplySettings()
27142714
}
27152715
}
27162716

2717-
// Cache some values.
2718-
Touchscreen.s_TapTime = settings.defaultTapTime;
2719-
Touchscreen.s_TapDelayTime = settings.multiTapDelayTime;
2720-
Touchscreen.s_TapRadiusSquared = settings.tapRadius * settings.tapRadius;
2717+
// Cache Touch specific settings to Touchscreen
2718+
Touchscreen.settings = new TouchscreenSettings
2719+
{
2720+
tapTime = settings.defaultTapTime,
2721+
tapDelayTime = settings.multiTapDelayTime,
2722+
tapRadiusSquared = settings.tapRadius * settings.tapRadius
2723+
};
2724+
27212725
// Extra clamp here as we can't tell what we're getting from serialized data.
27222726
ButtonControl.s_GlobalDefaultButtonPressPoint = Mathf.Clamp(settings.defaultButtonPressPoint, ButtonControl.kMinButtonPressPoint, float.MaxValue);
27232727
ButtonControl.s_GlobalDefaultButtonReleaseThreshold = settings.buttonReleaseThreshold;
@@ -4009,6 +4013,7 @@ internal void RestoreStateWithoutDevices(SerializedState state)
40094013
internal DeviceState[] m_SavedDeviceStates;
40104014
internal AvailableDevice[] m_SavedAvailableDevices;
40114015

4016+
#if !ENABLE_CORECLR
40124017
/// <summary>
40134018
/// Recreate devices based on the devices we had before a domain reload.
40144019
/// </summary>
@@ -4092,6 +4097,29 @@ internal void RestoreDevicesAfterDomainReload()
40924097
Profiler.EndSample();
40934098
}
40944099

4100+
/// <summary>
4101+
/// Notifies all devices of removal to better cleanup data when using SimulateDomainReload test hook
4102+
/// </summary>
4103+
/// <remarks>
4104+
/// Devices maintain their own list of Devices within static fields, updated via NotifyAdded and NotifyRemoved overrides.
4105+
/// These fields are reset during a real DR, but not so when we "simulate" causing them to report incorrect values when
4106+
/// queried via direct APIs, e.g. Gamepad.all. So, to mitigate this we'll call NotifyRemove during this scenario.
4107+
/// </remarks>
4108+
internal void TestHook_RemoveDevicesForSimulatedDomainReload()
4109+
{
4110+
if (m_Devices == null)
4111+
return;
4112+
4113+
foreach (var device in m_Devices)
4114+
{
4115+
if (device == null)
4116+
break;
4117+
4118+
device.NotifyRemoved();
4119+
}
4120+
}
4121+
#endif // !ENABLE_CORECLR
4122+
40954123
// We have two general types of devices we need to care about when recreating devices
40964124
// after domain reloads:
40974125
//

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

+5
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,24 @@ internal static void TestHook_InitializeForPlayModeTests(bool enableRemoting, II
3434
#endif
3535
}
3636

37+
#if !ENABLE_CORECLR
3738
internal static void TestHook_SimulateDomainReload(IInputRuntime runtime)
3839
{
3940
// This quite invasive goes into InputSystem internals. Unfortunately, we
4041
// have no proper way of simulating domain reloads ATM. So we directly call various
4142
// internal methods here in a sequence similar to what we'd get during a domain reload.
4243
// Since we're faking it, pass 'true' for calledFromCtor param.
4344

45+
// Need to notify devices of removal so their static fields are cleaned up
46+
InputSystem.s_Manager.TestHook_RemoveDevicesForSimulatedDomainReload();
47+
4448
InputSystem.s_DomainStateManager.OnBeforeSerialize();
4549
InputSystem.s_DomainStateManager = null;
4650
InputSystem.s_Manager = null; // Do NOT Dispose()! The native memory cannot be freed as it's reference by saved state
4751
InputSystem.s_PluginsInitialized = false;
4852
InputSystem.InitializeInEditor(true, runtime);
4953
}
54+
#endif
5055
#endif // UNITY_EDITOR
5156

5257
/// <summary>

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,26 @@ namespace UnityEngine.InputSystem.LowLevel
2020
/// </summary>
2121
internal class NativeInputRuntime : IInputRuntime
2222
{
23-
public static readonly NativeInputRuntime instance = new NativeInputRuntime();
23+
private static NativeInputRuntime s_Instance;
24+
25+
// Private ctor exists to enforce Singleton pattern
26+
private NativeInputRuntime() { }
27+
28+
/// <summary>
29+
/// Employ the Singleton pattern for this class and initialize a new instance on first use.
30+
/// </summary>
31+
/// <remarks>
32+
/// This property is typically used to initialize InputManager and isn't used afterwards, i.e. there's
33+
/// no perf impact to the null check.
34+
/// </remarks>
35+
public static NativeInputRuntime instance
36+
{
37+
get
38+
{
39+
s_Instance ??= new NativeInputRuntime();
40+
return s_Instance;
41+
}
42+
}
2443

2544
public int AllocateDeviceId()
2645
{

Packages/com.unity.inputsystem/InputSystem/Plugins/EnhancedTouch/TouchSimulation.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ private unsafe void UpdateTouch(int touchIndex, int pointerIndex, TouchPhase pha
325325

326326
if (phase == TouchPhase.Ended)
327327
{
328-
touch.isTap = time - oldTouchState->startTime <= Touchscreen.s_TapTime &&
329-
(position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.s_TapRadiusSquared;
328+
touch.isTap = time - oldTouchState->startTime <= Touchscreen.settings.tapTime &&
329+
(position - oldTouchState->startPosition).sqrMagnitude <= Touchscreen.settings.tapRadiusSquared;
330330
if (touch.isTap)
331331
++touch.tapCount;
332332
}

Packages/com.unity.inputsystem/InputSystem/Plugins/UI/InputSystemUIInputModule.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,8 @@ public InputActionReference trackedDevicePosition
13781378

13791379
public void AssignDefaultActions()
13801380
{
1381-
if (defaultActions == null)
1381+
// Without Domain Reloads, the InputActionAsset could be "null" even if defaultActions is valid
1382+
if (defaultActions == null || defaultActions.asset == null)
13821383
{
13831384
defaultActions = new DefaultInputActions();
13841385
}

0 commit comments

Comments
 (0)