Skip to content

Commit ab56f25

Browse files
ekcohjamesmcgill
andauthored
NEW: Extended action code generator to provide support for adding and removing multiple callback instances. (#1593)
* NEW: Extended action code generator to provide support for adding and removing multiple callback instances via generated code. * Aligned code generator with auto-formatter * Adoptions to action code generator to align with formatter * Updated generated files not previously covered. * Temporarily ignored new tests to bisect * temp modification * Reenabled functional test * Reenabled ignored test and modified path * Updated changelog to match version * remove generate c# flag on test asset Co-authored-by: James McGill <[email protected]>
1 parent 3b0e8ba commit ab56f25

File tree

9 files changed

+521
-81
lines changed

9 files changed

+521
-81
lines changed

Assets/Samples/InGameHints/InGameHintsActions.cs

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ public int FindBinding(InputBinding bindingMask, out InputAction action)
330330

331331
// Gameplay
332332
private readonly InputActionMap m_Gameplay;
333-
private IGameplayActions m_GameplayActionsCallbackInterface;
333+
private List<IGameplayActions> m_GameplayActionsCallbackInterfaces = new List<IGameplayActions>();
334334
private readonly InputAction m_Gameplay_Move;
335335
private readonly InputAction m_Gameplay_Look;
336336
private readonly InputAction m_Gameplay_PickUp;
@@ -350,45 +350,58 @@ public struct GameplayActions
350350
public void Disable() { Get().Disable(); }
351351
public bool enabled => Get().enabled;
352352
public static implicit operator InputActionMap(GameplayActions set) { return set.Get(); }
353+
public void AddCallbacks(IGameplayActions instance)
354+
{
355+
if (instance == null || m_Wrapper.m_GameplayActionsCallbackInterfaces.Contains(instance)) return;
356+
m_Wrapper.m_GameplayActionsCallbackInterfaces.Add(instance);
357+
@Move.started += instance.OnMove;
358+
@Move.performed += instance.OnMove;
359+
@Move.canceled += instance.OnMove;
360+
@Look.started += instance.OnLook;
361+
@Look.performed += instance.OnLook;
362+
@Look.canceled += instance.OnLook;
363+
@PickUp.started += instance.OnPickUp;
364+
@PickUp.performed += instance.OnPickUp;
365+
@PickUp.canceled += instance.OnPickUp;
366+
@Drop.started += instance.OnDrop;
367+
@Drop.performed += instance.OnDrop;
368+
@Drop.canceled += instance.OnDrop;
369+
@Throw.started += instance.OnThrow;
370+
@Throw.performed += instance.OnThrow;
371+
@Throw.canceled += instance.OnThrow;
372+
}
373+
374+
private void UnregisterCallbacks(IGameplayActions instance)
375+
{
376+
@Move.started -= instance.OnMove;
377+
@Move.performed -= instance.OnMove;
378+
@Move.canceled -= instance.OnMove;
379+
@Look.started -= instance.OnLook;
380+
@Look.performed -= instance.OnLook;
381+
@Look.canceled -= instance.OnLook;
382+
@PickUp.started -= instance.OnPickUp;
383+
@PickUp.performed -= instance.OnPickUp;
384+
@PickUp.canceled -= instance.OnPickUp;
385+
@Drop.started -= instance.OnDrop;
386+
@Drop.performed -= instance.OnDrop;
387+
@Drop.canceled -= instance.OnDrop;
388+
@Throw.started -= instance.OnThrow;
389+
@Throw.performed -= instance.OnThrow;
390+
@Throw.canceled -= instance.OnThrow;
391+
}
392+
393+
public void RemoveCallbacks(IGameplayActions instance)
394+
{
395+
if (m_Wrapper.m_GameplayActionsCallbackInterfaces.Remove(instance))
396+
UnregisterCallbacks(instance);
397+
}
398+
353399
public void SetCallbacks(IGameplayActions instance)
354400
{
355-
if (m_Wrapper.m_GameplayActionsCallbackInterface != null)
356-
{
357-
@Move.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
358-
@Move.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
359-
@Move.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
360-
@Look.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
361-
@Look.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
362-
@Look.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
363-
@PickUp.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnPickUp;
364-
@PickUp.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnPickUp;
365-
@PickUp.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnPickUp;
366-
@Drop.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnDrop;
367-
@Drop.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnDrop;
368-
@Drop.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnDrop;
369-
@Throw.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnThrow;
370-
@Throw.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnThrow;
371-
@Throw.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnThrow;
372-
}
373-
m_Wrapper.m_GameplayActionsCallbackInterface = instance;
374-
if (instance != null)
375-
{
376-
@Move.started += instance.OnMove;
377-
@Move.performed += instance.OnMove;
378-
@Move.canceled += instance.OnMove;
379-
@Look.started += instance.OnLook;
380-
@Look.performed += instance.OnLook;
381-
@Look.canceled += instance.OnLook;
382-
@PickUp.started += instance.OnPickUp;
383-
@PickUp.performed += instance.OnPickUp;
384-
@PickUp.canceled += instance.OnPickUp;
385-
@Drop.started += instance.OnDrop;
386-
@Drop.performed += instance.OnDrop;
387-
@Drop.canceled += instance.OnDrop;
388-
@Throw.started += instance.OnThrow;
389-
@Throw.performed += instance.OnThrow;
390-
@Throw.canceled += instance.OnThrow;
391-
}
401+
foreach (var item in m_Wrapper.m_GameplayActionsCallbackInterfaces)
402+
UnregisterCallbacks(item);
403+
m_Wrapper.m_GameplayActionsCallbackInterfaces.Clear();
404+
AddCallbacks(instance);
392405
}
393406
}
394407
public GameplayActions @Gameplay => new GameplayActions(this);

Assets/Samples/SimpleDemo/SimpleControls.cs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ public int FindBinding(InputBinding bindingMask, out InputAction action)
225225

226226
// gameplay
227227
private readonly InputActionMap m_gameplay;
228-
private IGameplayActions m_GameplayActionsCallbackInterface;
228+
private List<IGameplayActions> m_GameplayActionsCallbackInterfaces = new List<IGameplayActions>();
229229
private readonly InputAction m_gameplay_fire;
230230
private readonly InputAction m_gameplay_move;
231231
private readonly InputAction m_gameplay_look;
@@ -241,33 +241,46 @@ public struct GameplayActions
241241
public void Disable() { Get().Disable(); }
242242
public bool enabled => Get().enabled;
243243
public static implicit operator InputActionMap(GameplayActions set) { return set.Get(); }
244+
public void AddCallbacks(IGameplayActions instance)
245+
{
246+
if (instance == null || m_Wrapper.m_GameplayActionsCallbackInterfaces.Contains(instance)) return;
247+
m_Wrapper.m_GameplayActionsCallbackInterfaces.Add(instance);
248+
@fire.started += instance.OnFire;
249+
@fire.performed += instance.OnFire;
250+
@fire.canceled += instance.OnFire;
251+
@move.started += instance.OnMove;
252+
@move.performed += instance.OnMove;
253+
@move.canceled += instance.OnMove;
254+
@look.started += instance.OnLook;
255+
@look.performed += instance.OnLook;
256+
@look.canceled += instance.OnLook;
257+
}
258+
259+
private void UnregisterCallbacks(IGameplayActions instance)
260+
{
261+
@fire.started -= instance.OnFire;
262+
@fire.performed -= instance.OnFire;
263+
@fire.canceled -= instance.OnFire;
264+
@move.started -= instance.OnMove;
265+
@move.performed -= instance.OnMove;
266+
@move.canceled -= instance.OnMove;
267+
@look.started -= instance.OnLook;
268+
@look.performed -= instance.OnLook;
269+
@look.canceled -= instance.OnLook;
270+
}
271+
272+
public void RemoveCallbacks(IGameplayActions instance)
273+
{
274+
if (m_Wrapper.m_GameplayActionsCallbackInterfaces.Remove(instance))
275+
UnregisterCallbacks(instance);
276+
}
277+
244278
public void SetCallbacks(IGameplayActions instance)
245279
{
246-
if (m_Wrapper.m_GameplayActionsCallbackInterface != null)
247-
{
248-
@fire.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnFire;
249-
@fire.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnFire;
250-
@fire.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnFire;
251-
@move.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
252-
@move.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
253-
@move.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnMove;
254-
@look.started -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
255-
@look.performed -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
256-
@look.canceled -= m_Wrapper.m_GameplayActionsCallbackInterface.OnLook;
257-
}
258-
m_Wrapper.m_GameplayActionsCallbackInterface = instance;
259-
if (instance != null)
260-
{
261-
@fire.started += instance.OnFire;
262-
@fire.performed += instance.OnFire;
263-
@fire.canceled += instance.OnFire;
264-
@move.started += instance.OnMove;
265-
@move.performed += instance.OnMove;
266-
@move.canceled += instance.OnMove;
267-
@look.started += instance.OnLook;
268-
@look.performed += instance.OnLook;
269-
@look.canceled += instance.OnLook;
270-
}
280+
foreach (var item in m_Wrapper.m_GameplayActionsCallbackInterfaces)
281+
UnregisterCallbacks(item);
282+
m_Wrapper.m_GameplayActionsCallbackInterfaces.Clear();
283+
AddCallbacks(instance);
271284
}
272285
}
273286
public GameplayActions @gameplay => new GameplayActions(this);

Assets/Tests/InputSystem/CoreTests_Editor.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,6 +2943,117 @@ private void AssertAssetIsUnmodifiedAfterExitingPlayMode(Action<InputActionAsset
29432943
Assert.That(actualAsset.ToJson(), Is.EqualTo(originalJson), message);
29442944
}
29452945

2946+
[Test]
2947+
public void InputActionCodeGenerator_ShouldGenerateValidCSharpCode()
2948+
{
2949+
// Note that this only tests pre-generated code contents with respect to the code generator.
2950+
// The intent of this test is to capture changes to the generated source that would not be detected since code currently isn't automatically regenerated.
2951+
// Hence, one need to regenerate the source file below if code generator is updated to produce different output. This is not ideal and could be improved if dynamic compilation is used.
2952+
2953+
var directory = "Assets/Tests/InputSystem";
2954+
var csFilePath = $"{directory}/InputActionCodeGeneratorActions.cs";
2955+
var assetPath = $"{directory}/InputActionCodeGeneratorActions.inputactions";
2956+
var csFileContents = File.ReadAllText(csFilePath);
2957+
var asset = AssetDatabase.LoadAssetAtPath<InputActionAsset>(assetPath);
2958+
2959+
var generatedCode = InputActionCodeGenerator.GenerateWrapperCode(asset);
2960+
2961+
Assert.That(generatedCode, Is.EqualTo(csFileContents), $"Unexpected content, likely code generator changed. Regenerate source from {assetPath}.");
2962+
}
2963+
2964+
private sealed class InputActionCodeGeneratorActionsStub : InputActionCodeGeneratorActions.IGameplayActions
2965+
{
2966+
public int m_Action1Count = 0;
2967+
public int m_Action2Count = 0;
2968+
2969+
public void OnAction1(InputAction.CallbackContext context)
2970+
{
2971+
if (context.performed)
2972+
++m_Action1Count;
2973+
}
2974+
2975+
public void OnAction2(InputAction.CallbackContext context)
2976+
{
2977+
if (context.performed)
2978+
++m_Action2Count;
2979+
}
2980+
}
2981+
2982+
[Test]
2983+
public void InputActionCodeGenerator_ShouldGenerateClassWithSupportForRegisteringAndUnregisteringActions()
2984+
{
2985+
// Note that this is only testing pre-generated code. See test above for consistency check on file contents of generated source code.
2986+
2987+
var instance1 = new InputActionCodeGeneratorActionsStub();
2988+
var instance2 = new InputActionCodeGeneratorActionsStub();
2989+
var actions = new InputActionCodeGeneratorActions();
2990+
2991+
// Register using SetCallbacks
2992+
actions.gameplay.SetCallbacks(instance1);
2993+
actions.Enable();
2994+
2995+
var keyboard = InputSystem.AddDevice<Keyboard>();
2996+
PressAndRelease(keyboard.spaceKey);
2997+
2998+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
2999+
Assert.That(instance1.m_Action2Count, Is.EqualTo(0));
3000+
3001+
// Unregister using SetCallbacks(null)
3002+
3003+
actions.gameplay.SetCallbacks(null);
3004+
PressAndRelease(keyboard.enterKey);
3005+
3006+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
3007+
Assert.That(instance1.m_Action2Count, Is.EqualTo(0));
3008+
3009+
// Add using AddCallbacks
3010+
actions.gameplay.AddCallbacks(instance1);
3011+
PressAndRelease(keyboard.enterKey);
3012+
3013+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
3014+
Assert.That(instance1.m_Action2Count, Is.EqualTo(1));
3015+
3016+
// Add duplicate using AddCallbacks (Expecting duplicate to be ignored)
3017+
actions.gameplay.AddCallbacks(instance1);
3018+
PressAndRelease(keyboard.enterKey);
3019+
3020+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
3021+
Assert.That(instance1.m_Action2Count, Is.EqualTo(2));
3022+
3023+
// Remove previously registered instance
3024+
actions.gameplay.RemoveCallbacks(instance1);
3025+
PressAndRelease(keyboard.spaceKey);
3026+
3027+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
3028+
Assert.That(instance1.m_Action2Count, Is.EqualTo(2));
3029+
3030+
// Attempt to remove non-existent instance
3031+
actions.gameplay.RemoveCallbacks(null);
3032+
actions.gameplay.RemoveCallbacks(instance2);
3033+
3034+
// Add multiple instances and remove single
3035+
actions.gameplay.AddCallbacks(instance1);
3036+
actions.gameplay.AddCallbacks(instance2);
3037+
3038+
actions.gameplay.RemoveCallbacks(instance1);
3039+
PressAndRelease(keyboard.spaceKey);
3040+
3041+
Assert.That(instance1.m_Action1Count, Is.EqualTo(1));
3042+
Assert.That(instance1.m_Action2Count, Is.EqualTo(2));
3043+
Assert.That(instance2.m_Action1Count, Is.EqualTo(1));
3044+
Assert.That(instance2.m_Action2Count, Is.EqualTo(0));
3045+
3046+
// Multiple callbacks
3047+
actions.gameplay.AddCallbacks(instance1);
3048+
3049+
PressAndRelease(keyboard.spaceKey);
3050+
3051+
Assert.That(instance1.m_Action1Count, Is.EqualTo(2));
3052+
Assert.That(instance1.m_Action2Count, Is.EqualTo(2));
3053+
Assert.That(instance2.m_Action1Count, Is.EqualTo(2));
3054+
Assert.That(instance2.m_Action2Count, Is.EqualTo(0));
3055+
}
3056+
29463057
#if UNITY_STANDALONE // CodeDom API not available in most players.
29473058
#if !NET_STANDARD_2_0 // Not possible to run when using .NET standard at the moment.
29483059
[Test]

0 commit comments

Comments
 (0)