From e469d65809afd134a4b975eb68ae44761ad59596 Mon Sep 17 00:00:00 2001 From: Herman Schoenfeld Date: Wed, 28 Jun 2023 16:13:02 +1000 Subject: [PATCH] Added support for argumented guards in InternalTransitionAsyncIf --- src/Stateless/EntryActionBehaviour.cs | 14 +---- src/Stateless/InternalTriggerBehaviour.cs | 2 +- src/Stateless/StateConfiguration.Async.cs | 58 ++++++++++++++++--- .../InternalTransitionAsyncFixture.cs | 39 +++++++++++++ 4 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 280452ef..e4a826cd 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -54,11 +54,6 @@ public override void Execute(Transition transition, object[] args) base.Execute(transition, args); } - public override Task ExecuteAsync(Transition transition, object[] args) - { - Execute(transition, args); - return TaskResult.Done; - } } public class Async : EntryActionBehavior @@ -93,15 +88,10 @@ public AsyncFrom(TTriggerType trigger, Func action, Trigger = trigger; } - public override void Execute(Transition transition, object[] args) - { - if (transition.Trigger.Equals(Trigger)) - base.Execute(transition, args); - } - public override Task ExecuteAsync(Transition transition, object[] args) { - Execute(transition, args); + if (transition.Trigger.Equals(Trigger)) + return base.ExecuteAsync(transition, args); return TaskResult.Done; } } diff --git a/src/Stateless/InternalTriggerBehaviour.cs b/src/Stateless/InternalTriggerBehaviour.cs index fe6dd927..b456e3f6 100644 --- a/src/Stateless/InternalTriggerBehaviour.cs +++ b/src/Stateless/InternalTriggerBehaviour.cs @@ -45,7 +45,7 @@ public class Async : InternalTriggerBehaviour { readonly Func InternalAction; - public Async(TTrigger trigger, Func guard,Func internalAction, string guardDescription = null) : base(trigger, new TransitionGuard(guard, guardDescription)) + public Async(TTrigger trigger, Func guard, Func internalAction, string guardDescription = null) : base(trigger, new TransitionGuard(guard, guardDescription)) { InternalAction = internalAction; } diff --git a/src/Stateless/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index 6a89fbc3..296a831e 100644 --- a/src/Stateless/StateConfiguration.Async.cs +++ b/src/Stateless/StateConfiguration.Async.cs @@ -20,10 +20,11 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Func { if (entryAction == null) throw new ArgumentNullException(nameof(entryAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, guard, (t, args) => entryAction(t))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, (args) => guard(), (t, args) => entryAction(t))); return this; } + /// /// Add an internal transition to the state machine. An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine /// @@ -35,7 +36,7 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Func { if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, guard, (t, args) => internalAction())); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, (args) => guard(), (t, args) => internalAction())); return this; } @@ -51,10 +52,11 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Fun { if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, guard, (t, args) => internalAction(t))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger, (args) => guard(), (t, args) => internalAction(t))); return this; } + /// /// Add an internal transition to the state machine. An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine /// @@ -65,10 +67,23 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Fun /// public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) { - if (trigger == null) throw new ArgumentNullException(nameof(trigger)); + return InternalTransitionAsyncIf(trigger, (args) => guard(), internalAction); + } + + + /// + /// Add an internal transition to the state machine. An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine + /// + /// + /// The accepted trigger + /// Function that must return true in order for the trigger to be accepted. + /// The asynchronous action performed by the internal transition + /// + public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) + { if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, guard, (t, args) => internalAction(ParameterConversion.Unpack(args, 0), t))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction(ParameterConversion.Unpack(args, 0), t))); return this; } @@ -82,11 +97,25 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters /// The asynchronous action performed by the internal transition /// public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) + { + return InternalTransitionAsyncIf(trigger, (arg0, arg1) => guard(), internalAction); + } + + /// + /// Add an internal transition to the state machine. An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine + /// + /// + /// + /// The accepted trigger + /// Function that must return true in order for the trigger to be accepted. + /// The asynchronous action performed by the internal transition + /// + public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, guard, (t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), t))); return this; @@ -103,11 +132,26 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithPar /// The asynchronous action performed by the internal transition /// public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) + { + return InternalTransitionAsyncIf(trigger, (arg0, arg1, arg2) => guard(), internalAction); + } + + /// + /// Add an internal transition to the state machine. An internal action does not cause the Exit and Entry actions to be triggered, and does not change the state of the state machine + /// + /// + /// + /// + /// The accepted trigger + /// Function that must return true in order for the trigger to be accepted. + /// The asynchronous action performed by the internal transition + /// + public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters trigger, Func guard, Func internalAction) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, guard, (t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Async(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), ParameterConversion.Unpack(args, 2), t))); diff --git a/test/Stateless.Tests/InternalTransitionAsyncFixture.cs b/test/Stateless.Tests/InternalTransitionAsyncFixture.cs index 0078a929..6dafd2e1 100644 --- a/test/Stateless.Tests/InternalTransitionAsyncFixture.cs +++ b/test/Stateless.Tests/InternalTransitionAsyncFixture.cs @@ -5,6 +5,45 @@ namespace Stateless.Tests { public class InternalTransitionAsyncFixture { + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task InternalTransitionAsyncIf_NoArgumentGuard(bool expected) + { + bool? actual = null; + var stateMachine = new StateMachine(State.A); + + var triggerX = stateMachine.SetTriggerParameters(Trigger.X); + + stateMachine.Configure(State.A) + .InternalTransitionAsyncIf(triggerX, () => expected, (arg, s) => Task.Run(() => actual = true )) + .InternalTransitionAsyncIf(triggerX, () => !expected, (arg, s) => Task.Run(() => actual = false)); + + await stateMachine.FireAsync(triggerX, expected); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task InternalTransitionAsyncIf_WithArgumentGuard(bool expected) + { + bool? actual = null; + var stateMachine = new StateMachine(State.A); + + var triggerX = stateMachine.SetTriggerParameters(Trigger.X); + + stateMachine.Configure(State.A) + .InternalTransitionAsyncIf(triggerX, (arg) => arg, (arg, s) => Task.Run(() => actual = true)) + .InternalTransitionAsyncIf(triggerX, (arg) => !arg, (arg, s) => Task.Run(() => actual = false)); + + await stateMachine.FireAsync(triggerX, expected); + + Assert.Equal(expected, actual); + } + /// /// This unit test demonstrated bug report #417 ///