From 26d84fbc6553f8da3ce8bc6640c46094b0e08715 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 12:22:50 +0900 Subject: [PATCH 01/27] Introduce EventCallback --- src/Stateless/EventCallback.cs | 151 +++++++++++++++++++ src/Stateless/EventCallbackFactory.cs | 58 +++++++ test/Stateless.Tests/EventCallbackFixture.cs | 126 ++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 src/Stateless/EventCallback.cs create mode 100644 src/Stateless/EventCallbackFactory.cs create mode 100644 test/Stateless.Tests/EventCallbackFixture.cs diff --git a/src/Stateless/EventCallback.cs b/src/Stateless/EventCallback.cs new file mode 100644 index 00000000..4c307adb --- /dev/null +++ b/src/Stateless/EventCallback.cs @@ -0,0 +1,151 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace Stateless +{ + internal class EventCallback + { + protected readonly MulticastDelegate Delegate; + + public EventCallback(MulticastDelegate @delegate) + { + Delegate = @delegate; + } + + public Task InvokeAsync() + { + switch (Delegate) + { + case Action action: + action(); + return Task.CompletedTask; + + case Func func: + return func(); + + default: + return DynamicInvokeAsync(); + } + } + + public Task InvokeAsync(params object[] args) + { + return DynamicInvokeAsync(args); + } + + protected Task DynamicInvokeAsync(params object[] args) + { + switch (Delegate) + { + case null: + return Task.CompletedTask; + + default: + try + { + return Delegate.DynamicInvoke(args) as Task ?? Task.CompletedTask; + } + catch (TargetInvocationException e) + { + // Since we fell into the DynamicInvoke case, any exception will be wrapped + // in a TIE. We can expect this to be thrown synchronously, so it's low overhead + // to unwrap it. + return Task.FromException(e.InnerException); + } + } + } + } + + internal class EventCallback : EventCallback + { + public EventCallback(MulticastDelegate @delegate) : base(@delegate) + { + } + + public Task InvokeAsync(T arg) + { + switch (Delegate) + { + case Action action: + action(arg); + return Task.CompletedTask; + + case Func func: + return func(arg); + + default: + return DynamicInvokeAsync(arg); + } + } + } + + internal class EventCallback : EventCallback + { + public EventCallback(MulticastDelegate @delegate) : base(@delegate) + { + } + + public Task InvokeAsync(T1 arg1, T2 arg2) + { + switch (Delegate) + { + case Action action: + action(arg1, arg2); + return Task.CompletedTask; + + case Func func: + return func(arg1, arg2); + + default: + return DynamicInvokeAsync(arg1, arg2); + } + } + } + + internal class EventCallback : EventCallback + { + public EventCallback(MulticastDelegate @delegate) : base(@delegate) + { + } + + public Task InvokeAsync(T1 arg1, T2 arg2, T3 arg3) + { + switch (Delegate) + { + case Action action: + action(arg1, arg2, arg3); + return Task.CompletedTask; + + case Func func: + return func(arg1, arg2, arg3); + + default: + return DynamicInvokeAsync(arg1, arg2, arg3); + } + } + } + + internal class EventCallback : EventCallback + { + public EventCallback(MulticastDelegate @delegate) : base(@delegate) + { + } + + public Task InvokeAsync(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + switch (Delegate) + { + case Action action: + action(arg1, arg2, arg3, arg4); + return Task.CompletedTask; + + case Func func: + return func(arg1, arg2, arg3, arg4); + + default: + return DynamicInvokeAsync(arg1, arg2, arg3, arg4); + } + } + } +} diff --git a/src/Stateless/EventCallbackFactory.cs b/src/Stateless/EventCallbackFactory.cs new file mode 100644 index 00000000..305e389c --- /dev/null +++ b/src/Stateless/EventCallbackFactory.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; + +namespace Stateless +{ + internal static class EventCallbackFactory + { + public static EventCallback Create(Action callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Func callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Action callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Func callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Action callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Func callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Action callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Func callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Action callback) + { + return new EventCallback(callback); + } + + public static EventCallback Create(Func callback) + { + return new EventCallback(callback); + } + } +} diff --git a/test/Stateless.Tests/EventCallbackFixture.cs b/test/Stateless.Tests/EventCallbackFixture.cs new file mode 100644 index 00000000..66d007e8 --- /dev/null +++ b/test/Stateless.Tests/EventCallbackFixture.cs @@ -0,0 +1,126 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +using Xunit; + +namespace Stateless.Tests +{ + + public class EventCallbackFixture + { + [Fact] + public async Task CanInvokeDelegate() + { + int count = 0; + await EventCallbackFactory.Create(() => count = 1).InvokeAsync(); + + Assert.Equal(1, count); + } + + [Fact] + public async Task CanInvokeDelegateOneArgument() + { + int[] count = { }; + await EventCallbackFactory.Create((int first) => + { + count = new int[] { first, }; + }).InvokeAsync(1); + + Assert.Equal(new[] { 1, }, count); + } + + [Fact] + public async Task CanInvokeDelegateTwoArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second) => + { + count = new int[] { first, second, }; + }).InvokeAsync(1, 2); + + Assert.Equal(new[] { 1, 2, }, count); + } + + [Fact] + public async Task CanInvokeDelegateThreeArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second, int third) => + { + count = new int[] { first, second, third, }; + }).InvokeAsync(1, 2, 3); + + Assert.Equal(new[] { 1, 2, 3, }, count); + } + + [Fact] + public async Task CanInvokeDelegateFourArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second, int third, int four) => + { + count = new int[] { first, second, third, four, }; + }).InvokeAsync(1, 2, 3, 4); + + Assert.Equal(new[] { 1, 2, 3, 4, }, count); + } + + [Fact] + public async Task CanInvokeAsyncDelegate() + { + int count = 0; + await EventCallbackFactory.Create(() => Task.Run(() => count = 1)).InvokeAsync(); + + Assert.Equal(1, count); + } + + [Fact] + public async Task CanInvokeAsyncDelegateOneArgument() + { + int[] count = { }; + await EventCallbackFactory.Create((int first) => + { + count = new int[] { first, }; + }).InvokeAsync(1); + + Assert.Equal(new[] { 1, }, count); + } + + [Fact] + public async Task CanInvokeAsyncDelegateTwoArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second) => Task.Run(() => + { + count = new int[] { first, second, }; + })).InvokeAsync(1, 2); + + Assert.Equal(new[] { 1, 2, }, count); + } + + [Fact] + public async Task CanInvokeAsyncDelegateThreeArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second, int third) => Task.Run(() => + { + count = new int[] { first, second, third, }; + })).InvokeAsync(1, 2, 3); + + Assert.Equal(new[] { 1, 2, 3, }, count); + } + + [Fact] + public async Task CanInvokeAsyncDelegateFourArguments() + { + int[] count = { }; + await EventCallbackFactory.Create((int first, int second, int third, int four) => Task.Run(() => + { + count = new int[] { first, second, third, four, }; + })).InvokeAsync(1, 2, 3, 4); + + Assert.Equal(new[] { 1, 2, 3, 4, }, count); + } + } +} From 3708be8198806c673af893a69ebfa688a8f79de2 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 13:05:22 +0900 Subject: [PATCH 02/27] Use EventCallback in ActivateAction --- src/Stateless/ActivateActionBehaviour.cs | 56 ++++----------------- src/Stateless/StateRepresentation.Async.cs | 6 +-- src/Stateless/StateRepresentation.cs | 20 ++++---- test/Stateless.Tests/AsyncActionsFixture.cs | 29 ++++------- 4 files changed, 32 insertions(+), 79 deletions(-) diff --git a/src/Stateless/ActivateActionBehaviour.cs b/src/Stateless/ActivateActionBehaviour.cs index af012496..0185791c 100644 --- a/src/Stateless/ActivateActionBehaviour.cs +++ b/src/Stateless/ActivateActionBehaviour.cs @@ -5,9 +5,16 @@ namespace Stateless { public partial class StateMachine { - internal abstract class ActivateActionBehaviour + internal class ActivateActionBehaviour { readonly TState _state; + private readonly EventCallback _callback; + + public ActivateActionBehaviour(TState state, EventCallback action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) + { + _callback = action; + } protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) { @@ -17,52 +24,9 @@ protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo action internal Reflection.InvocationInfo Description { get; } - public abstract void Execute(); - public abstract Task ExecuteAsync(); - - public class Sync : ActivateActionBehaviour + public virtual Task Execute() { - readonly Action _action; - - public Sync(TState state, Action action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } - - public override void Execute() - { - _action(); - } - - public override Task ExecuteAsync() - { - Execute(); - return TaskResult.Done; - } - } - - public class Async : ActivateActionBehaviour - { - readonly Func _action; - - public Async(TState state, Func action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } - - public override void Execute() - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnActivateAsync for '{_state}' state. " + - "Use asynchronous version of Activate [ActivateAsync]"); - } - - public override Task ExecuteAsync() - { - return _action(); - } + return _callback.InvokeAsync(); } } } diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index 8041e154..30c431bc 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -11,7 +11,7 @@ internal partial class StateRepresentation { public void AddActivateAction(Func action, Reflection.InvocationInfo activateActionDescription) { - ActivateActions.Add(new ActivateActionBehaviour.Async(_state, action, activateActionDescription)); + ActivateActions.Add(new ActivateActionBehaviour(_state, EventCallbackFactory.Create(action), activateActionDescription)); } public void AddDeactivateAction(Func action, Reflection.InvocationInfo deactivateActionDescription) @@ -66,7 +66,7 @@ public async Task DeactivateAsync() async Task ExecuteActivationActionsAsync() { foreach (var action in ActivateActions) - await action.ExecuteAsync().ConfigureAwait(false); + await action.Execute().ConfigureAwait(false); } async Task ExecuteDeactivationActionsAsync() @@ -90,7 +90,7 @@ public async Task EnterAsync(Transition transition, params object[] entryArgs) await ExecuteEntryActionsAsync(transition, entryArgs).ConfigureAwait(false); } } - + public async Task ExitAsync(Transition transition) { if (transition.IsReentry) diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index c08f797f..3780c042 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -64,7 +64,7 @@ private bool TryFindLocalHandler(TTrigger trigger, object[] args, out TriggerBeh handlerResult = null; return false; } - + // Guard functions are executed here var actual = possible .Select(h => new TriggerBehaviourResult(h, h.UnmetGuardConditions(args))) @@ -117,7 +117,7 @@ private static TriggerBehaviourResult TryFindLocalHandlerResultWithUnmetGuardCon public void AddActivateAction(Action action, Reflection.InvocationInfo activateActionDescription) { - ActivateActions.Add(new ActivateActionBehaviour.Sync(_state, action, activateActionDescription)); + ActivateActions.Add(new ActivateActionBehaviour(_state, EventCallbackFactory.Create(action), activateActionDescription)); } public void AddDeactivateAction(Action action, Reflection.InvocationInfo deactivateActionDescription) @@ -159,7 +159,7 @@ public void Deactivate() void ExecuteActivationActions() { foreach (var action in ActivateActions) - action.Execute(); + action.Execute().GetAwaiter().GetResult(); } void ExecuteDeactivationActions() @@ -308,13 +308,13 @@ public bool IsIncludedIn(TState state) (_superstate != null && _superstate.IsIncludedIn(state)); } - public IEnumerable PermittedTriggers - { - get - { - return GetPermittedTriggers(); - } - } + public IEnumerable PermittedTriggers + { + get + { + return GetPermittedTriggers(); + } + } public IEnumerable GetPermittedTriggers(params object[] args) { diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 4e9b63ba..64f5a95a 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -21,7 +21,7 @@ public void StateMutatorShouldBeCalledOnlyOnce() sm.FireAsync(Trigger.X); Assert.Equal(1, count); } - + [Fact] public async Task SuperStateShouldNotExitOnSubStateTransition_WhenUsingAsyncTriggers() { @@ -33,7 +33,7 @@ public async Task SuperStateShouldNotExitOnSubStateTransition_WhenUsingAsyncTrig .OnEntryAsync(() => Task.Run(() => record.Add("Entered state A"))) .OnExitAsync(() => Task.Run(() => record.Add("Exited state A"))) .Permit(Trigger.X, State.B); - + sm.Configure(State.B) // Our super state. .InitialTransition(State.C) .OnEntryAsync(() => Task.Run(() => record.Add("Entered super state B"))) @@ -49,11 +49,11 @@ public async Task SuperStateShouldNotExitOnSubStateTransition_WhenUsingAsyncTrig .OnExitAsync(() => Task.Run(() => record.Add("Exited sub state D"))) .SubstateOf(State.B); - + // Act. await sm.FireAsync(Trigger.X).ConfigureAwait(false); await sm.FireAsync(Trigger.Y).ConfigureAwait(false); - + // Assert. Assert.Equal("Exited state A", record[0]); Assert.Equal("Entered super state B", record[1]); @@ -73,7 +73,7 @@ public void SuperStateShouldNotExitOnSubStateTransition_WhenUsingSyncTriggers() .OnEntry(() => record.Add("Entered state A")) .OnExit(() => record.Add("Exited state A")) .Permit(Trigger.X, State.B); - + sm.Configure(State.B) // Our super state. .InitialTransition(State.C) .OnEntry(() => record.Add("Entered super state B")) @@ -89,11 +89,11 @@ public void SuperStateShouldNotExitOnSubStateTransition_WhenUsingSyncTriggers() .OnExit(() => record.Add("Exited sub state D")) .SubstateOf(State.B); - + // Act. sm.Fire(Trigger.X); sm.Fire(Trigger.Y); - + // Assert. Assert.Equal("Exited state A", record[0]); Assert.Equal("Entered super state B", record[1]); @@ -101,7 +101,7 @@ public void SuperStateShouldNotExitOnSubStateTransition_WhenUsingSyncTriggers() Assert.Equal("Exited sub state C", record[3]); Assert.Equal("Entered sub state D", record[4]); } - + [Fact] public async Task CanFireAsyncEntryAction() { @@ -352,17 +352,6 @@ public async Task WhenDeactivateAsync() Assert.Equal(true, deactivated); // Should await action } - [Fact] - public void WhenSyncActivateAsyncOnActivateAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .OnActivateAsync(() => TaskResult.Done); - - Assert.Throws(() => sm.Activate()); - } - [Fact] public void WhenSyncDeactivateAsyncOnDeactivateAction() { @@ -443,7 +432,7 @@ public async void IgnoredTriggerMustBeIgnoredAsync() // >>> The following statement should not throw a NullReferenceException await stateMachine.FireAsync(Trigger.X); } - catch (NullReferenceException ) + catch (NullReferenceException) { nullRefExcThrown = true; } From b6c78c23fecc2e768297b5a0b357a8588fe19eae Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 13:10:20 +0900 Subject: [PATCH 03/27] Use EventCallback in DeactivateAction --- src/Stateless/DeactivateActionBehaviour.cs | 56 ++++----------------- src/Stateless/StateRepresentation.Async.cs | 4 +- src/Stateless/StateRepresentation.cs | 4 +- test/Stateless.Tests/AsyncActionsFixture.cs | 12 ----- 4 files changed, 14 insertions(+), 62 deletions(-) diff --git a/src/Stateless/DeactivateActionBehaviour.cs b/src/Stateless/DeactivateActionBehaviour.cs index 6f2bd588..5da99fe1 100644 --- a/src/Stateless/DeactivateActionBehaviour.cs +++ b/src/Stateless/DeactivateActionBehaviour.cs @@ -5,9 +5,16 @@ namespace Stateless { public partial class StateMachine { - internal abstract class DeactivateActionBehaviour + internal class DeactivateActionBehaviour { readonly TState _state; + private readonly EventCallback _callback; + + public DeactivateActionBehaviour(TState state, EventCallback action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) + { + _callback = action; + } protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) { @@ -17,52 +24,9 @@ protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo acti internal Reflection.InvocationInfo Description { get; } - public abstract void Execute(); - public abstract Task ExecuteAsync(); - - public class Sync : DeactivateActionBehaviour + public virtual Task Execute() { - readonly Action _action; - - public Sync(TState state, Action action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } - - public override void Execute() - { - _action(); - } - - public override Task ExecuteAsync() - { - Execute(); - return TaskResult.Done; - } - } - - public class Async : DeactivateActionBehaviour - { - readonly Func _action; - - public Async(TState state, Func action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } - - public override void Execute() - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnDeactivateAsync for '{_state}' state. " + - "Use asynchronous version of Deactivate [DeactivateAsync]"); - } - - public override Task ExecuteAsync() - { - return _action(); - } + return _callback.InvokeAsync(); } } } diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index 30c431bc..e9b545f7 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -16,7 +16,7 @@ public void AddActivateAction(Func action, Reflection.InvocationInfo activ public void AddDeactivateAction(Func action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour.Async(_state, action, deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, EventCallbackFactory.Create(action), deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Func action, Reflection.InvocationInfo entryActionDescription) @@ -72,7 +72,7 @@ async Task ExecuteActivationActionsAsync() async Task ExecuteDeactivationActionsAsync() { foreach (var action in DeactivateActions) - await action.ExecuteAsync().ConfigureAwait(false); + await action.Execute().ConfigureAwait(false); } diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index 3780c042..5cd86dac 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -122,7 +122,7 @@ public void AddActivateAction(Action action, Reflection.InvocationInfo activateA public void AddDeactivateAction(Action action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour.Sync(_state, action, deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, EventCallbackFactory.Create(action), deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Action action, Reflection.InvocationInfo entryActionDescription) @@ -165,7 +165,7 @@ void ExecuteActivationActions() void ExecuteDeactivationActions() { foreach (var action in DeactivateActions) - action.Execute(); + action.Execute().GetAwaiter().GetResult(); } public void Enter(Transition transition, params object[] entryArgs) diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 64f5a95a..6ed0a62d 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -352,18 +352,6 @@ public async Task WhenDeactivateAsync() Assert.Equal(true, deactivated); // Should await action } - [Fact] - public void WhenSyncDeactivateAsyncOnDeactivateAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .OnDeactivateAsync(() => TaskResult.Done); - - sm.Activate(); - - Assert.Throws(() => sm.Deactivate()); - } [Fact] public async void IfSelfTransitionPermited_ActionsFire_InSubstate_async() { From 641cf3065a3646f61d5cb25af95df436af70ccb1 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 13:19:13 +0900 Subject: [PATCH 04/27] Use EventCallback in ExitAction --- src/Stateless/ExitActionBehaviour.cs | 53 ++++----------------- src/Stateless/StateRepresentation.Async.cs | 4 +- src/Stateless/StateRepresentation.cs | 4 +- test/Stateless.Tests/AsyncActionsFixture.cs | 12 ----- 4 files changed, 14 insertions(+), 59 deletions(-) diff --git a/src/Stateless/ExitActionBehaviour.cs b/src/Stateless/ExitActionBehaviour.cs index 71f58764..23d9a2af 100644 --- a/src/Stateless/ExitActionBehaviour.cs +++ b/src/Stateless/ExitActionBehaviour.cs @@ -5,10 +5,15 @@ namespace Stateless { public partial class StateMachine { - internal abstract class ExitActionBehavior + internal class ExitActionBehavior { - public abstract void Execute(Transition transition); - public abstract Task ExecuteAsync(Transition transition); + private readonly EventCallback _callback; + + public ExitActionBehavior(EventCallback action, Reflection.InvocationInfo actionDescription) + : this(actionDescription) + { + _callback = action; + } protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) { @@ -17,47 +22,9 @@ protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) internal Reflection.InvocationInfo Description { get; } - public class Sync : ExitActionBehavior - { - readonly Action _action; - - public Sync(Action action, Reflection.InvocationInfo actionDescription) : base(actionDescription) - { - _action = action; - } - - public override void Execute(Transition transition) - { - _action(transition); - } - - public override Task ExecuteAsync(Transition transition) - { - Execute(transition); - return TaskResult.Done; - } - } - - public class Async : ExitActionBehavior + public virtual Task Execute(Transition transition) { - readonly Func _action; - - public Async(Func action, Reflection.InvocationInfo actionDescription) : base(actionDescription) - { - _action = action; - } - - public override void Execute(Transition transition) - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnExit event for '{transition.Source}' state. " + - "Use asynchronous version of Fire [FireAsync]"); - } - - public override Task ExecuteAsync(Transition transition) - { - return _action(transition); - } + return _callback.InvokeAsync(transition); } } } diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index e9b545f7..d5e053bb 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -44,7 +44,7 @@ public void AddEntryAction(Func action, Reflection.I public void AddExitAction(Func action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior.Async(action, exitActionDescription)); + ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); } public async Task ActivateAsync() @@ -132,7 +132,7 @@ async Task ExecuteEntryActionsAsync(Transition transition, object[] entryArgs) async Task ExecuteExitActionsAsync(Transition transition) { foreach (var action in ExitActions) - await action.ExecuteAsync(transition).ConfigureAwait(false); + await action.Execute(transition).ConfigureAwait(false); } } } diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index 5cd86dac..be360b1f 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -137,7 +137,7 @@ public void AddEntryAction(Action action, Reflection.Invoc public void AddExitAction(Action action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior.Sync(action, exitActionDescription)); + ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); } public void Activate() @@ -224,7 +224,7 @@ void ExecuteEntryActions(Transition transition, object[] entryArgs) void ExecuteExitActions(Transition transition) { foreach (var action in ExitActions) - action.Execute(transition); + action.Execute(transition).GetAwaiter().GetResult(); } internal void InternalAction(Transition transition, object[] args) { diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 6ed0a62d..66ebecda 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -150,18 +150,6 @@ public async Task CanFireAsyncExitAction() Assert.Equal(State.B, sm.State); // Should transition to destination state } - [Fact] - public void WhenSyncFireAsyncExitAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .OnExitAsync(() => TaskResult.Done) - .Permit(Trigger.X, State.B); - - Assert.Throws(() => sm.Fire(Trigger.X)); - } - [Fact] public async Task CanFireInternalAsyncAction() { From ca8d0734bd0bd6b730d497b7dd05e8e91ab8631d Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 13:31:47 +0900 Subject: [PATCH 05/27] Use EventCallback in EntryAction --- src/Stateless/EntryActionBehaviour.cs | 69 +++++---------------- src/Stateless/Reflection/ActionInfo.cs | 4 +- src/Stateless/StateRepresentation.Async.cs | 18 +++--- src/Stateless/StateRepresentation.cs | 6 +- test/Stateless.Tests/AsyncActionsFixture.cs | 14 ----- 5 files changed, 28 insertions(+), 83 deletions(-) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 2289fc99..a9242cc4 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -5,8 +5,16 @@ namespace Stateless { public partial class StateMachine { - internal abstract class EntryActionBehavior + internal class EntryActionBehavior { + private readonly EventCallback _callback; + + public EntryActionBehavior(EventCallback action, Reflection.InvocationInfo description) + : this(description) + { + _callback = action; + } + protected EntryActionBehavior(Reflection.InvocationInfo description) { Description = description; @@ -14,72 +22,27 @@ protected EntryActionBehavior(Reflection.InvocationInfo description) public Reflection.InvocationInfo Description { get; } - public abstract void Execute(Transition transition, object[] args); - public abstract Task ExecuteAsync(Transition transition, object[] args); - - public class Sync : EntryActionBehavior + public virtual Task Execute(Transition transition, object[] args) { - readonly Action _action; - - public Sync(Action action, Reflection.InvocationInfo description) : base(description) - { - _action = action; - } - - public override void Execute(Transition transition, object[] args) - { - _action(transition, args); - } - - public override Task ExecuteAsync(Transition transition, object[] args) - { - Execute(transition, args); - return TaskResult.Done; - } + return _callback.InvokeAsync(transition, args); } - public class SyncFrom : Sync + public class From : EntryActionBehavior { internal TTriggerType Trigger { get; private set; } - public SyncFrom(TTriggerType trigger, Action action, Reflection.InvocationInfo description) + public From(TTriggerType trigger, EventCallback action, Reflection.InvocationInfo description) : base(action, description) { Trigger = trigger; } - public override void Execute(Transition transition, object[] args) + public override Task 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); - return TaskResult.Done; - } - } - - public class Async : EntryActionBehavior - { - readonly Func _action; + return base.Execute(transition, args); - public Async(Func action, Reflection.InvocationInfo description) : base(description) - { - _action = action; - } - - public override void Execute(Transition transition, object[] args) - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnEntry event for '{transition.Destination}' state. " + - "Use asynchronous version of Fire [FireAsync]"); - } - - public override Task ExecuteAsync(Transition transition, object[] args) - { - return _action(transition, args); + return Task.CompletedTask; } } } diff --git a/src/Stateless/Reflection/ActionInfo.cs b/src/Stateless/Reflection/ActionInfo.cs index 7f05d5c9..6a44816d 100644 --- a/src/Stateless/Reflection/ActionInfo.cs +++ b/src/Stateless/Reflection/ActionInfo.cs @@ -17,14 +17,14 @@ public class ActionInfo internal static ActionInfo Create(StateMachine.EntryActionBehavior entryAction) { - StateMachine.EntryActionBehavior.SyncFrom syncFrom = entryAction as StateMachine.EntryActionBehavior.SyncFrom; + StateMachine.EntryActionBehavior.From syncFrom = entryAction as StateMachine.EntryActionBehavior.From; if (syncFrom != null) return new ActionInfo(entryAction.Description, syncFrom.Trigger.ToString()); else return new ActionInfo(entryAction.Description, null); } - + /// /// Constructor /// diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index d5e053bb..9eb13644 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -24,21 +24,17 @@ public void AddEntryAction(TTrigger trigger, Func ac if (action == null) throw new ArgumentNullException(nameof(action)); EntryActions.Add( - new EntryActionBehavior.Async((t, args) => - { - if (t.Trigger.Equals(trigger)) - return action(t, args); - - return TaskResult.Done; - }, - entryActionDescription)); + new EntryActionBehavior.From( + trigger, + EventCallbackFactory.Create(action), + entryActionDescription)); } public void AddEntryAction(Func action, Reflection.InvocationInfo entryActionDescription) { EntryActions.Add( - new EntryActionBehavior.Async( - action, + new EntryActionBehavior( + EventCallbackFactory.Create(action), entryActionDescription)); } @@ -126,7 +122,7 @@ public async Task ExitAsync(Transition transition) async Task ExecuteEntryActionsAsync(Transition transition, object[] entryArgs) { foreach (var action in EntryActions) - await action.ExecuteAsync(transition, entryArgs).ConfigureAwait(false); + await action.Execute(transition, entryArgs).ConfigureAwait(false); } async Task ExecuteExitActionsAsync(Transition transition) diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index be360b1f..36b39214 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -127,12 +127,12 @@ public void AddDeactivateAction(Action action, Reflection.InvocationInfo deactiv public void AddEntryAction(TTrigger trigger, Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior.SyncFrom(trigger, action, entryActionDescription)); + EntryActions.Add(new EntryActionBehavior.From(trigger, EventCallbackFactory.Create(action), entryActionDescription)); } public void AddEntryAction(Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior.Sync(action, entryActionDescription)); + EntryActions.Add(new EntryActionBehavior(EventCallbackFactory.Create(action), entryActionDescription)); } public void AddExitAction(Action action, Reflection.InvocationInfo exitActionDescription) @@ -218,7 +218,7 @@ public Transition Exit(Transition transition) void ExecuteEntryActions(Transition transition, object[] entryArgs) { foreach (var action in EntryActions) - action.Execute(transition, entryArgs); + action.Execute(transition, entryArgs).GetAwaiter().GetResult(); } void ExecuteExitActions(Transition transition) diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 66ebecda..3e664bbe 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -120,20 +120,6 @@ public async Task CanFireAsyncEntryAction() Assert.Equal(State.B, sm.State); // Should transition to destination state } - [Fact] - public void WhenSyncFireAsyncEntryAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .Permit(Trigger.X, State.B); - - sm.Configure(State.B) - .OnEntryAsync(() => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.X)); - } - [Fact] public async Task CanFireAsyncExitAction() { From aeefd3d4a2fe6e9b23260f40f8d9e0035a694267 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 15:42:57 +0900 Subject: [PATCH 06/27] Use EventCallback in InternalAction --- src/Stateless/InternalTriggerBehaviour.cs | 65 ++++++--------------- src/Stateless/StateConfiguration.Async.cs | 16 ++--- src/Stateless/StateConfiguration.cs | 52 ++++++++--------- src/Stateless/StateMachine.Async.cs | 14 ++--- src/Stateless/StateRepresentation.cs | 9 +-- test/Stateless.Tests/AsyncActionsFixture.cs | 11 ---- 6 files changed, 60 insertions(+), 107 deletions(-) diff --git a/src/Stateless/InternalTriggerBehaviour.cs b/src/Stateless/InternalTriggerBehaviour.cs index fe6dd927..edde54b7 100644 --- a/src/Stateless/InternalTriggerBehaviour.cs +++ b/src/Stateless/InternalTriggerBehaviour.cs @@ -5,66 +5,37 @@ namespace Stateless { public partial class StateMachine { - internal abstract class InternalTriggerBehaviour : TriggerBehaviour + internal class InternalTriggerBehaviour : TriggerBehaviour { - protected InternalTriggerBehaviour(TTrigger trigger, TransitionGuard guard) : base(trigger, guard) + private readonly EventCallback _callback; + + public InternalTriggerBehaviour(TTrigger trigger, Func guard, EventCallback internalAction, string guardDescription = null) + : this(trigger, new TransitionGuard(guard, guardDescription)) { + _callback = internalAction; } - public abstract void Execute(Transition transition, object[] args); - public abstract Task ExecuteAsync(Transition transition, object[] args); - - public override bool ResultsInTransitionFrom(TState source, object[] args, out TState destination) + // FIXME: inconsistent with synchronous version of StateConfiguration + public InternalTriggerBehaviour(TTrigger trigger, Func guard, EventCallback internalAction, string guardDescription = null) + : this(trigger, new TransitionGuard(guard, guardDescription)) { - destination = source; - return false; + _callback = internalAction; } - - public class Sync: InternalTriggerBehaviour + protected InternalTriggerBehaviour(TTrigger trigger, TransitionGuard guard) : base(trigger, guard) { - public Action InternalAction { get; } - - public Sync(TTrigger trigger, Func guard, Action internalAction, string guardDescription = null) : base(trigger, new TransitionGuard(guard, guardDescription)) - { - InternalAction = internalAction; - } - public override void Execute(Transition transition, object[] args) - { - InternalAction(transition, args); - } - - public override Task ExecuteAsync(Transition transition, object[] args) - { - Execute(transition, args); - return TaskResult.Done; - } } - public class Async : InternalTriggerBehaviour + public override bool ResultsInTransitionFrom(TState source, object[] args, out TState destination) { - readonly Func InternalAction; - - public Async(TTrigger trigger, Func guard,Func internalAction, string guardDescription = null) : base(trigger, new TransitionGuard(guard, guardDescription)) - { - InternalAction = internalAction; - } - - public override void Execute(Transition transition, object[] args) - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnEntry event for '{transition.Destination}' state. " + - "Use asynchronous version of Fire [FireAsync]"); - } - - public override Task ExecuteAsync(Transition transition, object[] args) - { - return InternalAction(transition, args); - } - + destination = source; + return false; } - + public virtual Task Execute(Transition transition, object[] args) + { + return _callback.InvokeAsync(transition, args); + } } } } \ No newline at end of file diff --git a/src/Stateless/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index 6a89fbc3..f9a0a05f 100644 --- a/src/Stateless/StateConfiguration.Async.cs +++ b/src/Stateless/StateConfiguration.Async.cs @@ -20,7 +20,7 @@ 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(trigger, guard, EventCallbackFactory.Create((t, args) => entryAction(t)))); return this; } @@ -35,7 +35,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(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction()))); return this; } @@ -51,7 +51,7 @@ 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(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction(t)))); return this; } @@ -68,7 +68,7 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters 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(ParameterConversion.Unpack(args, 0), t))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction(ParameterConversion.Unpack(args, 0), t)))); return this; } @@ -86,9 +86,9 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithPar 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(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t))); + ParameterConversion.Unpack(args, 1), t)))); return this; } @@ -107,10 +107,10 @@ public StateConfiguration InternalTransitionAsyncIf(Trigger 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(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t))); + ParameterConversion.Unpack(args, 2), t)))); return this; } diff --git a/src/Stateless/StateConfiguration.cs b/src/Stateless/StateConfiguration.cs index 3ddd78d3..99cf4533 100644 --- a/src/Stateless/StateConfiguration.cs +++ b/src/Stateless/StateConfiguration.cs @@ -68,7 +68,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func entryAction(t), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => entryAction(t)), guardDescription)); return this; } @@ -95,7 +95,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func internalAction(), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction()), guardDescription)); return this; } @@ -112,7 +112,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func internalAction(t), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction(t)), guardDescription)); return this; } @@ -154,7 +154,7 @@ public StateConfiguration InternalTransitionIf(TriggerWithParameters internalAction(ParameterConversion.Unpack(args, 0), t), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), EventCallbackFactory.Create((t, args) => internalAction(ParameterConversion.Unpack(args, 0), t)), guardDescription)); return this; } @@ -187,9 +187,9 @@ public StateConfiguration InternalTransitionIf(TriggerWithParamete if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Sync(trigger.Trigger, guard, (t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t), + ParameterConversion.Unpack(args, 1), t)), guardDescription)); return this; } @@ -209,12 +209,12 @@ public StateConfiguration InternalTransitionIf(TriggerWithParamete if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Sync( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour( trigger.Trigger, TransitionGuard.ToPackedGuard(guard), - (t, args) => internalAction( + EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t), + ParameterConversion.Unpack(args, 1), t)), guardDescription )); return this; @@ -236,10 +236,10 @@ public StateConfiguration InternalTransitionIf(TriggerWithP if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Sync(trigger.Trigger, guard, (t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t), + ParameterConversion.Unpack(args, 2), t)), guardDescription)); return this; } @@ -260,10 +260,10 @@ public StateConfiguration InternalTransitionIf(TriggerWithP if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour.Sync(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), EventCallbackFactory.Create((t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t), + ParameterConversion.Unpack(args, 2), t)), guardDescription)); return this; } @@ -321,7 +321,7 @@ public StateConfiguration PermitIf(TTrigger trigger, TState destinationState, pa } /// - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// The accepted trigger. @@ -365,7 +365,7 @@ public StateConfiguration PermitIf(TriggerWithParameters trigger, } /// - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -388,7 +388,7 @@ public StateConfiguration PermitIf(TriggerWithParameters - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -412,7 +412,7 @@ public StateConfiguration PermitIf(TriggerWithParameters - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -436,7 +436,7 @@ public StateConfiguration PermitIf(TriggerWithParameters - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -517,7 +517,7 @@ public StateConfiguration PermitReentryIf(TTrigger trigger, params Tuple - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// The accepted trigger. @@ -557,7 +557,7 @@ public StateConfiguration PermitReentryIf(TriggerWithParameters tr } /// - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -578,7 +578,7 @@ public StateConfiguration PermitReentryIf(TriggerWithParameters - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -600,7 +600,7 @@ public StateConfiguration PermitReentryIf(TriggerWithParameters - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -622,7 +622,7 @@ public StateConfiguration PermitReentryIf(TriggerWithParame } /// - /// Accept the specified trigger, transition to the destination state, and guard condition. + /// Accept the specified trigger, transition to the destination state, and guard condition. /// /// /// @@ -1306,7 +1306,7 @@ public StateConfiguration PermitDynamicIf(TTrigger trigger, Func destina /// dynamically by the supplied function. /// /// The accepted trigger. - /// Function to calculate the state + /// Function to calculate the state /// that the trigger will cause a transition to. /// Description of the function to calculate the state /// Function that must return true in order for the @@ -1348,7 +1348,7 @@ public StateConfiguration PermitDynamicIf(TTrigger trigger, Func destina /// dynamically by the supplied function. /// /// The accepted trigger. - /// Function to calculate the state + /// Function to calculate the state /// that the trigger will cause a transition to. /// Description of the function to calculate the state /// Functions and their descriptions that must return true in order for the @@ -1401,7 +1401,7 @@ public StateConfiguration PermitDynamicIf(TriggerWithParameters tr /// /// The accepted trigger. /// Function to calculate the state - /// that the trigger will cause a transition to. + /// that the trigger will cause a transition to. /// The receiver. /// Type of the first trigger argument. public StateConfiguration PermitDynamicIf(TriggerWithParameters trigger, Func destinationStateSelector) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index f46a4825..dcfb3602 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -11,7 +11,7 @@ public partial class StateMachine { /// /// Activates current state in asynchronous fashion. Actions associated with activating the currrent state - /// will be invoked. The activation is idempotent and subsequent activation of the same current state + /// will be invoked. The activation is idempotent and subsequent activation of the same current state /// will not lead to re-execution of activation callbacks. /// public Task ActivateAsync() @@ -22,7 +22,7 @@ public Task ActivateAsync() /// /// Deactivates current state in asynchronous fashion. Actions associated with deactivating the currrent state - /// will be invoked. The deactivation is idempotent and subsequent deactivation of the same current state + /// will be invoked. The deactivation is idempotent and subsequent deactivation of the same current state /// will not lead to re-execution of deactivation callbacks. /// public Task DeactivateAsync() @@ -201,11 +201,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) { // Internal transitions does not update the current state, but must execute the associated action. var transition = new Transition(source, source, trigger, args); - - if (itb is InternalTriggerBehaviour.Async ita) - await ita.ExecuteAsync(transition, args); - else - await Task.Run(() => itb.Execute(transition, args)); + await itb.Execute(transition, args).ConfigureAwait(false); break; } default: @@ -247,7 +243,7 @@ private async Task HandleTransitioningTriggerAsync(object[] args, StateRepresent //Alert all listeners of state transition await _onTransitionedEvent.InvokeAsync(transition); - var representation =await EnterStateAsync(newRepresentation, transition, args); + var representation = await EnterStateAsync(newRepresentation, transition, args); // Check if state has changed by entering new state (by fireing triggers in OnEntry or such) if (!representation.UnderlyingState.Equals(State)) @@ -256,7 +252,7 @@ private async Task HandleTransitioningTriggerAsync(object[] args, StateRepresent State = representation.UnderlyingState; } - await _onTransitionCompletedEvent.InvokeAsync(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); + await _onTransitionCompletedEvent.InvokeAsync(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); } diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index 36b39214..f428804b 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -228,7 +228,7 @@ void ExecuteExitActions(Transition transition) } internal void InternalAction(Transition transition, object[] args) { - InternalTriggerBehaviour.Sync internalTransition = null; + InternalTriggerBehaviour internalTransition = null; // Look for actions in superstate(s) recursivly until we hit the topmost superstate, or we actually find some trigger handlers. StateRepresentation aStateRep = this; @@ -237,10 +237,7 @@ internal void InternalAction(Transition transition, object[] args) if (aStateRep.TryFindLocalHandler(transition.Trigger, args, out TriggerBehaviourResult result)) { // Trigger handler found in this state - if (result.Handler is InternalTriggerBehaviour.Async) - throw new InvalidOperationException("Running Async internal actions in synchronous mode is not allowed"); - - internalTransition = result.Handler as InternalTriggerBehaviour.Sync; + internalTransition = result.Handler as InternalTriggerBehaviour; break; } // Try to look for trigger handlers in superstate (if it exists) @@ -249,7 +246,7 @@ internal void InternalAction(Transition transition, object[] args) // Execute internal transition event handler if (internalTransition == null) throw new ArgumentNullException("The configuration is incorrect, no action assigned to this internal transition."); - internalTransition.InternalAction(transition, args); + internalTransition.Execute(transition, args).GetAwaiter().GetResult(); } public void AddTriggerBehaviour(TriggerBehaviour triggerBehaviour) { diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 3e664bbe..27157575 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -150,17 +150,6 @@ public async Task CanFireInternalAsyncAction() Assert.Equal("foo", test); // Should await action } - [Fact] - public void WhenSyncFireInternalAsyncAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .InternalTransitionAsync(Trigger.X, () => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.X)); - } - [Fact] public async Task CanInvokeOnTransitionedAsyncAction() { From 7ad8b3755001af480a8cc0beff5e2ea99fc25fbe Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 15:58:31 +0900 Subject: [PATCH 07/27] Use EventCallback in TransitionedAction --- src/Stateless/OnTransitionedEvent.cs | 26 ++++++++--------- test/Stateless.Tests/AsyncActionsFixture.cs | 32 ++++----------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/Stateless/OnTransitionedEvent.cs b/src/Stateless/OnTransitionedEvent.cs index c8dbbc2e..c281db2f 100644 --- a/src/Stateless/OnTransitionedEvent.cs +++ b/src/Stateless/OnTransitionedEvent.cs @@ -8,37 +8,35 @@ public partial class StateMachine { class OnTransitionedEvent { - event Action _onTransitioned; - readonly List> _onTransitionedAsync = new List>(); + private readonly ICollection> _callbacks; - public void Invoke(Transition transition) + public OnTransitionedEvent() { - if (_onTransitionedAsync.Count != 0) - throw new InvalidOperationException( - "Cannot execute asynchronous action specified as OnTransitioned callback. " + - "Use asynchronous version of Fire [FireAsync]"); + _callbacks = new List>(); + } - _onTransitioned?.Invoke(transition); + public void Invoke(Transition transition) + { + foreach (var callback in _callbacks) + callback.InvokeAsync(transition).GetAwaiter().GetResult(); } #if TASKS public async Task InvokeAsync(Transition transition) { - _onTransitioned?.Invoke(transition); - - foreach (var callback in _onTransitionedAsync) - await callback(transition).ConfigureAwait(false); + foreach (var callback in _callbacks) + await callback.InvokeAsync(transition).ConfigureAwait(false); } #endif public void Register(Action action) { - _onTransitioned += action; + _callbacks.Add(EventCallbackFactory.Create(action)); } public void Register(Func action) { - _onTransitionedAsync.Add(action); + _callbacks.Add(EventCallbackFactory.Create(action)); } } } diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 27157575..a4aba66f 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -192,13 +192,16 @@ public async Task WillInvokeSyncOnTransitionedIfRegisteredAlongWithAsyncAction() var test1 = ""; var test2 = ""; + var test3 = ""; sm.OnTransitioned(_ => test1 = "foo1"); sm.OnTransitionedAsync(_ => Task.Run(() => test2 = "foo2")); + sm.OnTransitioned(_ => test3 = "foo3"); await sm.FireAsync(Trigger.X).ConfigureAwait(false); Assert.Equal("foo1", test1); Assert.Equal("foo2", test2); + Assert.Equal("foo3", test3); } [Fact] @@ -211,39 +214,16 @@ public async Task WillInvokeSyncOnTransitionCompletedIfRegisteredAlongWithAsyncA var test1 = ""; var test2 = ""; + var test3 = ""; sm.OnTransitionCompleted(_ => test1 = "foo1"); sm.OnTransitionCompletedAsync(_ => Task.Run(() => test2 = "foo2")); + sm.OnTransitionCompleted(_ => test3 = "foo3"); await sm.FireAsync(Trigger.X).ConfigureAwait(false); Assert.Equal("foo1", test1); Assert.Equal("foo2", test2); - } - - [Fact] - public void WhenSyncFireAsyncOnTransitionedAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .Permit(Trigger.X, State.B); - - sm.OnTransitionedAsync(_ => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.X)); - } - - [Fact] - public void WhenSyncFireAsyncOnTransitionCompletedAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .Permit(Trigger.X, State.B); - - sm.OnTransitionCompletedAsync(_ => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.X)); + Assert.Equal("foo3", test3); } [Fact] From 7d81abefb8ce5b6760425f22347759028a3605df Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 16:31:47 +0900 Subject: [PATCH 08/27] Use EventCallback in UnhandledTriggerAction --- src/Stateless/StateMachine.Async.cs | 6 +-- src/Stateless/StateMachine.cs | 50 ++++++++++----------- src/Stateless/UnhandledTriggerAction.cs | 46 +++---------------- test/Stateless.Tests/AsyncActionsFixture.cs | 24 ---------- 4 files changed, 34 insertions(+), 92 deletions(-) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index dcfb3602..515de187 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -170,7 +170,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) // Try to find a trigger handler, either in the current state or a super state. if (!representativeState.TryFindHandler(trigger, args, out TriggerBehaviourResult result)) { - await _unhandledTriggerAction.ExecuteAsync(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions); + await _unhandledTriggerAction.Execute(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).ConfigureAwait(false); return; } @@ -298,7 +298,7 @@ private async Task EnterStateAsync(StateRepresentation repr public void OnUnhandledTriggerAsync(Func unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction.Async((s, t, c) => unhandledTriggerAction(s, t)); + _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>((s, t, c) => unhandledTriggerAction(s, t))); } /// @@ -309,7 +309,7 @@ public void OnUnhandledTriggerAsync(Func unhandledTrigge public void OnUnhandledTriggerAsync(Func, Task> unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction.Async(unhandledTriggerAction); + _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create(unhandledTriggerAction)); } /// diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 803ed7ba..787a5c80 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -46,7 +46,7 @@ private class QueuedTrigger /// /// A function that will be called to read the current state value. /// An action that will be called to write new state values. - public StateMachine(Func stateAccessor, Action stateMutator) :this(stateAccessor, stateMutator, FiringMode.Queued) + public StateMachine(Func stateAccessor, Action stateMutator) : this(stateAccessor, stateMutator, FiringMode.Queued) { } @@ -92,7 +92,7 @@ public StateMachine(TState initialState, FiringMode firingMode) : this() /// StateMachine() { - _unhandledTriggerAction = new UnhandledTriggerAction.Sync(DefaultUnhandledTriggerAction); + _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>(DefaultUnhandledTriggerAction)); _onTransitionedEvent = new OnTransitionedEvent(); _onTransitionCompletedEvent = new OnTransitionedEvent(); } @@ -386,7 +386,7 @@ void InternalFireOne(TTrigger trigger, params object[] args) // Try to find a trigger handler, either in the current state or a super state. if (!representativeState.TryFindHandler(trigger, args, out TriggerBehaviourResult result)) { - _unhandledTriggerAction.Execute(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions); + _unhandledTriggerAction.Execute(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).GetAwaiter().GetResult(); return; } @@ -398,28 +398,28 @@ void InternalFireOne(TTrigger trigger, params object[] args) // Handle special case, re-entry in superstate // Check if it is an internal transition, or a transition from one state to another. case ReentryTriggerBehaviour handler: - { - // Handle transition, and set new state - var transition = new Transition(source, handler.Destination, trigger, args); - HandleReentryTrigger(args, representativeState, transition); - break; - } + { + // Handle transition, and set new state + var transition = new Transition(source, handler.Destination, trigger, args); + HandleReentryTrigger(args, representativeState, transition); + break; + } case DynamicTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out var destination)): case TransitioningTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out destination)): - { - // Handle transition, and set new state - var transition = new Transition(source, destination, trigger, args); - HandleTransitioningTrigger(args, representativeState, transition); + { + // Handle transition, and set new state + var transition = new Transition(source, destination, trigger, args); + HandleTransitioningTrigger(args, representativeState, transition); - break; - } + break; + } case InternalTriggerBehaviour _: - { - // Internal transitions does not update the current state, but must execute the associated action. - var transition = new Transition(source, source, trigger, args); - CurrentRepresentation.InternalAction(transition, args); - break; - } + { + // Internal transitions does not update the current state, but must execute the associated action. + var transition = new Transition(source, source, trigger, args); + CurrentRepresentation.InternalAction(transition, args); + break; + } default: throw new InvalidOperationException("State machine configuration incorrect, no handler for trigger."); } @@ -451,7 +451,7 @@ private void HandleReentryTrigger(object[] args, StateRepresentation representat State = representation.UnderlyingState; } - private void HandleTransitioningTrigger( object[] args, StateRepresentation representativeState, Transition transition) + private void HandleTransitioningTrigger(object[] args, StateRepresentation representativeState, Transition transition) { transition = representativeState.Exit(transition); @@ -472,7 +472,7 @@ private void HandleTransitioningTrigger( object[] args, StateRepresentation repr _onTransitionCompletedEvent.Invoke(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); } - private StateRepresentation EnterState(StateRepresentation representation, Transition transition, object [] args) + private StateRepresentation EnterState(StateRepresentation representation, Transition transition, object[] args) { // Enter the new state representation.Enter(transition, args); @@ -514,7 +514,7 @@ private StateRepresentation EnterState(StateRepresentation representation, Trans public void OnUnhandledTrigger(Action unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction.Sync((s, t, c) => unhandledTriggerAction(s, t)); + _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>((s, t, c) => unhandledTriggerAction(s, t))); } /// @@ -525,7 +525,7 @@ public void OnUnhandledTrigger(Action unhandledTriggerAction) public void OnUnhandledTrigger(Action> unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction.Sync(unhandledTriggerAction); + _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create(unhandledTriggerAction)); } /// diff --git a/src/Stateless/UnhandledTriggerAction.cs b/src/Stateless/UnhandledTriggerAction.cs index 52a40d90..b61efacf 100644 --- a/src/Stateless/UnhandledTriggerAction.cs +++ b/src/Stateless/UnhandledTriggerAction.cs @@ -6,52 +6,18 @@ namespace Stateless { public partial class StateMachine { - abstract class UnhandledTriggerAction + internal class UnhandledTriggerAction { - public abstract void Execute(TState state, TTrigger trigger, ICollection unmetGuards); - public abstract Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards); + private readonly EventCallback> _callback; - internal class Sync : UnhandledTriggerAction + public UnhandledTriggerAction(EventCallback> action = null) { - readonly Action> _action; - - internal Sync(Action> action = null) - { - _action = action; - } - - public override void Execute(TState state, TTrigger trigger, ICollection unmetGuards) - { - _action(state, trigger, unmetGuards); - } - - public override Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) - { - Execute(state, trigger, unmetGuards); - return TaskResult.Done; - } + _callback = action; } - internal class Async : UnhandledTriggerAction + public virtual Task Execute(TState state, TTrigger trigger, ICollection unmetGuards) { - readonly Func, Task> _action; - - internal Async(Func, Task> action) - { - _action = action; - } - - public override void Execute(TState state, TTrigger trigger, ICollection unmetGuards) - { - throw new InvalidOperationException( - "Cannot execute asynchronous action specified in OnUnhandledTrigger. " + - "Use asynchronous version of Fire [FireAsync]"); - } - - public override Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) - { - return _action(state, trigger, unmetGuards); - } + return _callback.InvokeAsync(state, trigger, unmetGuards); } } } diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index a4aba66f..b22bc42c 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -241,30 +241,6 @@ public async Task CanInvokeOnUnhandledTriggerAsyncAction() Assert.Equal("foo", test); // Should await action } - [Fact] - public void WhenSyncFireOnUnhandledTriggerAsyncTask() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .Permit(Trigger.X, State.B); - - sm.OnUnhandledTriggerAsync((s, t) => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.Z)); - } - [Fact] - public void WhenSyncFireOnUnhandledTriggerAsyncAction() - { - var sm = new StateMachine(State.A); - - sm.Configure(State.A) - .Permit(Trigger.X, State.B); - - sm.OnUnhandledTriggerAsync((s, t, u) => TaskResult.Done); - - Assert.Throws(() => sm.Fire(Trigger.Z)); - } [Fact] public async Task WhenActivateAsync() From b33bdc5358a8a7550d5906107ab330290f1281fb Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 16:53:44 +0900 Subject: [PATCH 09/27] Use TaskResult.Done for old .net sdk Further considerations: add Microsoft.Bcl.Async package for support below * `Task.GetAwaiter().GetResult()` is not supported before .NET Framework 4.5 * `Task.FromException(Exception)` is not supported before .NET Framework 4.6 --- src/Stateless/EntryActionBehaviour.cs | 2 +- src/Stateless/EventCallback.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index a9242cc4..6740b4f7 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -42,7 +42,7 @@ public override Task Execute(Transition transition, object[] args) if (transition.Trigger.Equals(Trigger)) return base.Execute(transition, args); - return Task.CompletedTask; + return TaskResult.Done; } } } diff --git a/src/Stateless/EventCallback.cs b/src/Stateless/EventCallback.cs index 4c307adb..e77fa27f 100644 --- a/src/Stateless/EventCallback.cs +++ b/src/Stateless/EventCallback.cs @@ -19,7 +19,7 @@ public Task InvokeAsync() { case Action action: action(); - return Task.CompletedTask; + return TaskResult.Done; case Func func: return func(); @@ -39,12 +39,12 @@ protected Task DynamicInvokeAsync(params object[] args) switch (Delegate) { case null: - return Task.CompletedTask; + return TaskResult.Done; default: try { - return Delegate.DynamicInvoke(args) as Task ?? Task.CompletedTask; + return Delegate.DynamicInvoke(args) as Task ?? TaskResult.Done; } catch (TargetInvocationException e) { @@ -69,7 +69,7 @@ public Task InvokeAsync(T arg) { case Action action: action(arg); - return Task.CompletedTask; + return TaskResult.Done; case Func func: return func(arg); @@ -92,7 +92,7 @@ public Task InvokeAsync(T1 arg1, T2 arg2) { case Action action: action(arg1, arg2); - return Task.CompletedTask; + return TaskResult.Done; case Func func: return func(arg1, arg2); @@ -115,7 +115,7 @@ public Task InvokeAsync(T1 arg1, T2 arg2, T3 arg3) { case Action action: action(arg1, arg2, arg3); - return Task.CompletedTask; + return TaskResult.Done; case Func func: return func(arg1, arg2, arg3); @@ -138,7 +138,7 @@ public Task InvokeAsync(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { case Action action: action(arg1, arg2, arg3, arg4); - return Task.CompletedTask; + return TaskResult.Done; case Func func: return func(arg1, arg2, arg3, arg4); From 75c429c2605d44e4ad2eafce3899ab69bcd9d002 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 17:53:29 +0900 Subject: [PATCH 10/27] Delete internal synchronous code --- src/Stateless/StateMachine.cs | 6 +-- src/Stateless/StateRepresentation.cs | 74 ++-------------------------- 2 files changed, 6 insertions(+), 74 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 787a5c80..aa8ea3a2 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -298,8 +298,7 @@ public void Fire(TriggerWithParameters /// public void Activate() { - var representativeState = GetRepresentation(State); - representativeState.Activate(); + ActivateAsync().GetAwaiter().GetResult(); } /// @@ -309,8 +308,7 @@ public void Activate() /// public void Deactivate() { - var representativeState = GetRepresentation(State); - representativeState.Deactivate(); + DeactivateAsync().GetAwaiter().GetResult(); } /// diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index f428804b..816c934b 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -142,90 +142,24 @@ public void AddExitAction(Action action, Reflection.InvocationInfo e public void Activate() { - if (_superstate != null) - _superstate.Activate(); - - ExecuteActivationActions(); + ActivateAsync().GetAwaiter().GetResult(); } public void Deactivate() { - ExecuteDeactivationActions(); - - if (_superstate != null) - _superstate.Deactivate(); - } - - void ExecuteActivationActions() - { - foreach (var action in ActivateActions) - action.Execute().GetAwaiter().GetResult(); - } - - void ExecuteDeactivationActions() - { - foreach (var action in DeactivateActions) - action.Execute().GetAwaiter().GetResult(); + DeactivateAsync().GetAwaiter().GetResult(); } public void Enter(Transition transition, params object[] entryArgs) { - if (transition.IsReentry) - { - ExecuteEntryActions(transition, entryArgs); - } - else if (!Includes(transition.Source)) - { - if (_superstate != null && !(transition is InitialTransition)) - _superstate.Enter(transition, entryArgs); - - ExecuteEntryActions(transition, entryArgs); - } + EnterAsync(transition, entryArgs).GetAwaiter().GetResult(); } public Transition Exit(Transition transition) { - if (transition.IsReentry) - { - ExecuteExitActions(transition); - } - else if (!Includes(transition.Destination)) - { - ExecuteExitActions(transition); - - // Must check if there is a superstate, and if we are leaving that superstate - if (_superstate != null) - { - // Check if destination is within the state list - if (IsIncludedIn(transition.Destination)) - { - // Destination state is within the list, exit first superstate only if it is NOT the the first - if (!_superstate.UnderlyingState.Equals(transition.Destination)) - { - return _superstate.Exit(transition); - } - } - else - { - // Exit the superstate as well - return _superstate.Exit(transition); - } - } - } - return transition; + return ExitAsync(transition).GetAwaiter().GetResult(); } - void ExecuteEntryActions(Transition transition, object[] entryArgs) - { - foreach (var action in EntryActions) - action.Execute(transition, entryArgs).GetAwaiter().GetResult(); - } - - void ExecuteExitActions(Transition transition) - { - foreach (var action in ExitActions) - action.Execute(transition).GetAwaiter().GetResult(); - } internal void InternalAction(Transition transition, object[] args) { InternalTriggerBehaviour internalTransition = null; From 89e1b03eb0aebfcc1bde3272f1240afc03ffe9ac Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:21:43 +0900 Subject: [PATCH 11/27] Delete internal synchronous methods --- src/Stateless/StateMachine.cs | 8 +-- src/Stateless/StateRepresentation.cs | 20 ------ .../StateRepresentationFixture.cs | 65 ++++++++++--------- 3 files changed, 37 insertions(+), 56 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index aa8ea3a2..740de2ce 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -426,14 +426,14 @@ void InternalFireOne(TTrigger trigger, params object[] args) private void HandleReentryTrigger(object[] args, StateRepresentation representativeState, Transition transition) { StateRepresentation representation; - transition = representativeState.Exit(transition); + transition = representativeState.ExitAsync(transition).GetAwaiter().GetResult(); var newRepresentation = GetRepresentation(transition.Destination); if (!transition.Source.Equals(transition.Destination)) { // Then Exit the final superstate transition = new Transition(transition.Destination, transition.Destination, transition.Trigger, args); - newRepresentation.Exit(transition); + newRepresentation.ExitAsync(transition).GetAwaiter().GetResult(); _onTransitionedEvent.Invoke(transition); representation = EnterState(newRepresentation, transition, args); @@ -451,7 +451,7 @@ private void HandleReentryTrigger(object[] args, StateRepresentation representat private void HandleTransitioningTrigger(object[] args, StateRepresentation representativeState, Transition transition) { - transition = representativeState.Exit(transition); + transition = representativeState.ExitAsync(transition).GetAwaiter().GetResult(); State = transition.Destination; var newRepresentation = GetRepresentation(transition.Destination); @@ -473,7 +473,7 @@ private void HandleTransitioningTrigger(object[] args, StateRepresentation repre private StateRepresentation EnterState(StateRepresentation representation, Transition transition, object[] args) { // Enter the new state - representation.Enter(transition, args); + representation.EnterAsync(transition, args).GetAwaiter().GetResult(); if (FiringMode.Immediate.Equals(_firingMode) && !State.Equals(transition.Destination)) { diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index 816c934b..eafedee4 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -140,26 +140,6 @@ public void AddExitAction(Action action, Reflection.InvocationInfo e ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); } - public void Activate() - { - ActivateAsync().GetAwaiter().GetResult(); - } - - public void Deactivate() - { - DeactivateAsync().GetAwaiter().GetResult(); - } - - public void Enter(Transition transition, params object[] entryArgs) - { - EnterAsync(transition, entryArgs).GetAwaiter().GetResult(); - } - - public Transition Exit(Transition transition) - { - return ExitAsync(transition).GetAwaiter().GetResult(); - } - internal void InternalAction(Transition transition, object[] args) { InternalTriggerBehaviour internalTransition = null; diff --git a/test/Stateless.Tests/StateRepresentationFixture.cs b/test/Stateless.Tests/StateRepresentationFixture.cs index ce621c38..e67269ac 100644 --- a/test/Stateless.Tests/StateRepresentationFixture.cs +++ b/test/Stateless.Tests/StateRepresentationFixture.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace Stateless.Tests @@ -9,50 +10,50 @@ namespace Stateless.Tests public class StateRepresentationFixture { [Fact] - public void UponEntering_EnteringActionsExecuted() + public async Task UponEntering_EnteringActionsExecuted() { var stateRepresentation = CreateRepresentation(State.B); StateMachine.Transition transition = new StateMachine.Transition(State.A, State.B, Trigger.X), actualTransition = null; stateRepresentation.AddEntryAction((t, a) => actualTransition = t, Reflection.InvocationInfo.Create(null, "entryActionDescription")); - stateRepresentation.Enter(transition); + await stateRepresentation.EnterAsync(transition); Assert.Equal(transition, actualTransition); } [Fact] - public void UponLeaving_EnteringActionsNotExecuted() + public async Task UponLeaving_EnteringActionsNotExecuted() { var stateRepresentation = CreateRepresentation(State.B); StateMachine.Transition transition = new StateMachine.Transition(State.A, State.B, Trigger.X), actualTransition = null; stateRepresentation.AddEntryAction((t, a) => actualTransition = t, Reflection.InvocationInfo.Create(null, "entryActionDescription")); - stateRepresentation.Exit(transition); + await stateRepresentation.ExitAsync(transition); Assert.Null(actualTransition); } [Fact] - public void UponLeaving_LeavingActionsExecuted() + public async Task UponLeaving_LeavingActionsExecuted() { var stateRepresentation = CreateRepresentation(State.A); StateMachine.Transition transition = new StateMachine.Transition(State.A, State.B, Trigger.X), actualTransition = null; stateRepresentation.AddExitAction(t => actualTransition = t, Reflection.InvocationInfo.Create(null, "entryActionDescription")); - stateRepresentation.Exit(transition); + await stateRepresentation.ExitAsync(transition); Assert.Equal(transition, actualTransition); } [Fact] - public void UponEntering_LeavingActionsNotExecuted() + public async Task UponEntering_LeavingActionsNotExecuted() { var stateRepresentation = CreateRepresentation(State.A); StateMachine.Transition transition = new StateMachine.Transition(State.A, State.B, Trigger.X), actualTransition = null; stateRepresentation.AddExitAction(t => actualTransition = t, Reflection.InvocationInfo.Create(null, "exitActionDescription")); - stateRepresentation.Enter(transition); + await stateRepresentation.EnterAsync(transition); Assert.Null(actualTransition); } @@ -117,79 +118,79 @@ public void IsIncludedInSuperstate() } [Fact] - public void WhenTransitioningFromSubToSuperstate_SubstateEntryActionsExecuted() + public async Task WhenTransitioningFromSubToSuperstate_SubstateEntryActionsExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; sub.AddEntryAction((t, a) => executed = true, Reflection.InvocationInfo.Create(null, "entryActionDescription")); var transition = new StateMachine.Transition(super.UnderlyingState, sub.UnderlyingState, Trigger.X); - sub.Enter(transition); + await sub.EnterAsync(transition); Assert.True(executed); } [Fact] - public void WhenTransitioningFromSubToSuperstate_SubstateExitActionsExecuted() + public async Task WhenTransitioningFromSubToSuperstate_SubstateExitActionsExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; sub.AddExitAction(t => executed = true, Reflection.InvocationInfo.Create(null, "exitActionDescription")); var transition = new StateMachine.Transition(sub.UnderlyingState, super.UnderlyingState, Trigger.X); - sub.Exit(transition); + await sub.ExitAsync(transition); Assert.True(executed); } [Fact] - public void WhenTransitioningToSuperFromSubstate_SuperEntryActionsNotExecuted() + public async Task WhenTransitioningToSuperFromSubstate_SuperEntryActionsNotExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; super.AddEntryAction((t, a) => executed = true, Reflection.InvocationInfo.Create(null, "entryActionDescription")); var transition = new StateMachine.Transition(super.UnderlyingState, sub.UnderlyingState, Trigger.X); - super.Enter(transition); + await super.EnterAsync(transition); Assert.False(executed); } [Fact] - public void WhenTransitioningFromSuperToSubstate_SuperExitActionsNotExecuted() + public async Task WhenTransitioningFromSuperToSubstate_SuperExitActionsNotExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; super.AddExitAction(t => executed = true, Reflection.InvocationInfo.Create(null, "exitActionDescription")); var transition = new StateMachine.Transition(super.UnderlyingState, sub.UnderlyingState, Trigger.X); - super.Exit(transition); + await super.ExitAsync(transition); Assert.False(executed); } [Fact] - public void WhenEnteringSubstate_SuperEntryActionsExecuted() + public async Task WhenEnteringSubstate_SuperEntryActionsExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; super.AddEntryAction((t, a) => executed = true, Reflection.InvocationInfo.Create(null, "entryActionDescription")); var transition = new StateMachine.Transition(State.C, sub.UnderlyingState, Trigger.X); - sub.Enter(transition); + await sub.EnterAsync(transition); Assert.True(executed); } [Fact] - public void WhenLeavingSubstate_SuperExitActionsExecuted() + public async Task WhenLeavingSubstate_SuperExitActionsExecuted() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); var executed = false; super.AddExitAction(t => executed = true, Reflection.InvocationInfo.Create(null, "exitActionDescription")); var transition = new StateMachine.Transition(sub.UnderlyingState, State.C, Trigger.X); - sub.Exit(transition); + await sub.ExitAsync(transition); Assert.True(executed); } [Fact] - public void EntryActionsExecuteInOrder() + public async Task EntryActionsExecuteInOrder() { var actual = new List(); @@ -197,7 +198,7 @@ public void EntryActionsExecuteInOrder() rep.AddEntryAction((t, a) => actual.Add(0), Reflection.InvocationInfo.Create(null, "entryActionDescription")); rep.AddEntryAction((t, a) => actual.Add(1), Reflection.InvocationInfo.Create(null, "entryActionDescription")); - rep.Enter(new StateMachine.Transition(State.A, State.B, Trigger.X)); + await rep.EnterAsync(new StateMachine.Transition(State.A, State.B, Trigger.X)); Assert.Equal(2, actual.Count); Assert.Equal(0, actual[0]); @@ -205,7 +206,7 @@ public void EntryActionsExecuteInOrder() } [Fact] - public void ExitActionsExecuteInOrder() + public async Task ExitActionsExecuteInOrder() { var actual = new List(); @@ -213,7 +214,7 @@ public void ExitActionsExecuteInOrder() rep.AddExitAction(t => actual.Add(0), Reflection.InvocationInfo.Create(null, "entryActionDescription")); rep.AddExitAction(t => actual.Add(1), Reflection.InvocationInfo.Create(null, "entryActionDescription")); - rep.Exit(new StateMachine.Transition(State.B, State.C, Trigger.X)); + await rep.ExitAsync(new StateMachine.Transition(State.B, State.C, Trigger.X)); Assert.Equal(2, actual.Count); Assert.Equal(0, actual[0]); @@ -247,7 +248,7 @@ public void WhenTransitionExistsInSupersate_TriggerCanBeFired() } [Fact] - public void WhenEnteringSubstate_SuperstateEntryActionsExecuteBeforeSubstate() + public async Task WhenEnteringSubstate_SuperstateEntryActionsExecuteBeforeSubstate() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); @@ -255,12 +256,12 @@ public void WhenEnteringSubstate_SuperstateEntryActionsExecuteBeforeSubstate() super.AddEntryAction((t, a) => superOrder = order++, Reflection.InvocationInfo.Create(null, "entryActionDescription")); sub.AddEntryAction((t, a) => subOrder = order++, Reflection.InvocationInfo.Create(null, "entryActionDescription")); var transition = new StateMachine.Transition(State.C, sub.UnderlyingState, Trigger.X); - sub.Enter(transition); + await sub.EnterAsync(transition); Assert.True(superOrder < subOrder); } [Fact] - public void WhenExitingSubstate_SubstateEntryActionsExecuteBeforeSuperstate() + public async Task WhenExitingSubstate_SubstateEntryActionsExecuteBeforeSuperstate() { CreateSuperSubstatePair(out StateMachine.StateRepresentation super, out StateMachine.StateRepresentation sub); @@ -268,7 +269,7 @@ public void WhenExitingSubstate_SubstateEntryActionsExecuteBeforeSuperstate() super.AddExitAction(t => superOrder = order++, Reflection.InvocationInfo.Create(null, "entryActionDescription")); sub.AddExitAction(t => subOrder = order++, Reflection.InvocationInfo.Create(null, "entryActionDescription")); var transition = new StateMachine.Transition(sub.UnderlyingState, State.C, Trigger.X); - sub.Exit(transition); + await sub.ExitAsync(transition); Assert.True(subOrder < superOrder); } @@ -319,12 +320,12 @@ public void WhenTransitionExistAndSuperstateUnmetGuardConditions_FireNotPossible var transition = new StateMachine.TransitioningTriggerBehaviour(Trigger.X, State.C, transitionGuard); super.AddTriggerBehaviour(transition); - var reslt= sub.TryFindHandler(Trigger.X, new object[0], out StateMachine.TriggerBehaviourResult result); + var reslt = sub.TryFindHandler(Trigger.X, new object[0], out StateMachine.TriggerBehaviourResult result); Assert.False(reslt); Assert.False(sub.CanHandle(Trigger.X)); Assert.False(super.CanHandle(Trigger.X)); - + } [Fact] public void WhenTransitionExistSuperstateMetGuardConditions_CanBeFired() @@ -343,7 +344,7 @@ public void WhenTransitionExistSuperstateMetGuardConditions_CanBeFired() Assert.True(sub.CanHandle(Trigger.X)); Assert.True(super.CanHandle(Trigger.X)); - Assert.NotNull(result); + Assert.NotNull(result); Assert.True(result?.Handler.GuardConditionsMet()); Assert.False(result?.UnmetGuardConditions.Any()); @@ -401,7 +402,7 @@ public void AddAllGuardDescriptionsWhenMultipleGuardsFailForSameTrigger() Assert.Equal(fsm.State, State.A); Assert.True(guardDescriptions != null); Assert.Equal(2, guardDescriptions.Count); - foreach(var description in guardDescriptions) + foreach (var description in guardDescriptions) { Assert.True(expectedGuardDescriptions.Contains(description)); } From 9f3bfaf65cc8e6f1dedc035e5dddd57aa0541fde Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:29:33 +0900 Subject: [PATCH 12/27] Delete synchronous EnterState --- src/Stateless/StateMachine.cs | 40 +++-------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 740de2ce..f2cb0ea9 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -436,14 +436,14 @@ private void HandleReentryTrigger(object[] args, StateRepresentation representat newRepresentation.ExitAsync(transition).GetAwaiter().GetResult(); _onTransitionedEvent.Invoke(transition); - representation = EnterState(newRepresentation, transition, args); + representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); _onTransitionCompletedEvent.Invoke(transition); } else { _onTransitionedEvent.Invoke(transition); - representation = EnterState(newRepresentation, transition, args); + representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); _onTransitionCompletedEvent.Invoke(transition); } State = representation.UnderlyingState; @@ -458,7 +458,7 @@ private void HandleTransitioningTrigger(object[] args, StateRepresentation repre //Alert all listeners of state transition _onTransitionedEvent.Invoke(transition); - var representation = EnterState(newRepresentation, transition, args); + var representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); // Check if state has changed by entering new state (by fireing triggers in OnEntry or such) if (!representation.UnderlyingState.Equals(State)) @@ -470,40 +470,6 @@ private void HandleTransitioningTrigger(object[] args, StateRepresentation repre _onTransitionCompletedEvent.Invoke(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); } - private StateRepresentation EnterState(StateRepresentation representation, Transition transition, object[] args) - { - // Enter the new state - representation.EnterAsync(transition, args).GetAwaiter().GetResult(); - - if (FiringMode.Immediate.Equals(_firingMode) && !State.Equals(transition.Destination)) - { - // This can happen if triggers are fired in OnEntry - // Must update current representation with updated State - representation = GetRepresentation(State); - transition = new Transition(transition.Source, State, transition.Trigger, args); - } - - // Recursively enter substates that have an initial transition - if (representation.HasInitialTransition) - { - // Verify that the target state is a substate - // Check if state has substate(s), and if an initial transition(s) has been set up. - if (!representation.GetSubstates().Any(s => s.UnderlyingState.Equals(representation.InitialTransitionTarget))) - { - throw new InvalidOperationException($"The target ({representation.InitialTransitionTarget}) for the initial transition is not a substate."); - } - - var initialTransition = new InitialTransition(transition.Source, representation.InitialTransitionTarget, transition.Trigger, args); - representation = GetRepresentation(representation.InitialTransitionTarget); - - // Alert all listeners of initial state transition - _onTransitionedEvent.Invoke(new Transition(transition.Destination, initialTransition.Destination, transition.Trigger, transition.Parameters)); - representation = EnterState(representation, initialTransition, args); - } - - return representation; - } - /// /// Override the default behaviour of throwing an exception when an unhandled trigger /// is fired. From 4e72686a906cc03a25e7e579acc5488beb821d02 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:36:15 +0900 Subject: [PATCH 13/27] Delete synchronous HandleReentryTrigger --- src/Stateless/StateMachine.cs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index f2cb0ea9..93132794 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -399,7 +399,7 @@ void InternalFireOne(TTrigger trigger, params object[] args) { // Handle transition, and set new state var transition = new Transition(source, handler.Destination, trigger, args); - HandleReentryTrigger(args, representativeState, transition); + HandleReentryTriggerAsync(args, representativeState, transition).GetAwaiter().GetResult(); break; } case DynamicTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out var destination)): @@ -423,32 +423,6 @@ void InternalFireOne(TTrigger trigger, params object[] args) } } - private void HandleReentryTrigger(object[] args, StateRepresentation representativeState, Transition transition) - { - StateRepresentation representation; - transition = representativeState.ExitAsync(transition).GetAwaiter().GetResult(); - var newRepresentation = GetRepresentation(transition.Destination); - - if (!transition.Source.Equals(transition.Destination)) - { - // Then Exit the final superstate - transition = new Transition(transition.Destination, transition.Destination, transition.Trigger, args); - newRepresentation.ExitAsync(transition).GetAwaiter().GetResult(); - - _onTransitionedEvent.Invoke(transition); - representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); - _onTransitionCompletedEvent.Invoke(transition); - - } - else - { - _onTransitionedEvent.Invoke(transition); - representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); - _onTransitionCompletedEvent.Invoke(transition); - } - State = representation.UnderlyingState; - } - private void HandleTransitioningTrigger(object[] args, StateRepresentation representativeState, Transition transition) { transition = representativeState.ExitAsync(transition).GetAwaiter().GetResult(); From d3b460bd5737ff43a32ad7abf8a47ec3861bda36 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:41:27 +0900 Subject: [PATCH 14/27] Delete synchronous HandleTransitioningTrigger --- src/Stateless/StateMachine.cs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 93132794..dc5d59d2 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -407,7 +407,7 @@ void InternalFireOne(TTrigger trigger, params object[] args) { // Handle transition, and set new state var transition = new Transition(source, destination, trigger, args); - HandleTransitioningTrigger(args, representativeState, transition); + HandleTransitioningTriggerAsync(args, representativeState, transition).GetAwaiter().GetResult(); break; } @@ -423,27 +423,6 @@ void InternalFireOne(TTrigger trigger, params object[] args) } } - private void HandleTransitioningTrigger(object[] args, StateRepresentation representativeState, Transition transition) - { - transition = representativeState.ExitAsync(transition).GetAwaiter().GetResult(); - - State = transition.Destination; - var newRepresentation = GetRepresentation(transition.Destination); - - //Alert all listeners of state transition - _onTransitionedEvent.Invoke(transition); - var representation = EnterStateAsync(newRepresentation, transition, args).GetAwaiter().GetResult(); - - // Check if state has changed by entering new state (by fireing triggers in OnEntry or such) - if (!representation.UnderlyingState.Equals(State)) - { - // The state has been changed after entering the state, must update current state to new one - State = representation.UnderlyingState; - } - - _onTransitionCompletedEvent.Invoke(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); - } - /// /// Override the default behaviour of throwing an exception when an unhandled trigger /// is fired. From 61d3c1010a3ce430c59713ba798968e7d96dc2a9 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:49:40 +0900 Subject: [PATCH 15/27] Delete synchronous HandleInternalTrigger --- src/Stateless/StateMachine.cs | 4 ++-- src/Stateless/StateRepresentation.cs | 22 ---------------------- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index dc5d59d2..b56f457d 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -411,11 +411,11 @@ void InternalFireOne(TTrigger trigger, params object[] args) break; } - case InternalTriggerBehaviour _: + case InternalTriggerBehaviour itb: { // Internal transitions does not update the current state, but must execute the associated action. var transition = new Transition(source, source, trigger, args); - CurrentRepresentation.InternalAction(transition, args); + itb.Execute(transition, args).GetAwaiter().GetResult(); break; } default: diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index eafedee4..e291f26f 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -140,28 +140,6 @@ public void AddExitAction(Action action, Reflection.InvocationInfo e ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); } - internal void InternalAction(Transition transition, object[] args) - { - InternalTriggerBehaviour internalTransition = null; - - // Look for actions in superstate(s) recursivly until we hit the topmost superstate, or we actually find some trigger handlers. - StateRepresentation aStateRep = this; - while (aStateRep != null) - { - if (aStateRep.TryFindLocalHandler(transition.Trigger, args, out TriggerBehaviourResult result)) - { - // Trigger handler found in this state - internalTransition = result.Handler as InternalTriggerBehaviour; - break; - } - // Try to look for trigger handlers in superstate (if it exists) - aStateRep = aStateRep._superstate; - } - - // Execute internal transition event handler - if (internalTransition == null) throw new ArgumentNullException("The configuration is incorrect, no action assigned to this internal transition."); - internalTransition.Execute(transition, args).GetAwaiter().GetResult(); - } public void AddTriggerBehaviour(TriggerBehaviour triggerBehaviour) { if (!TriggerBehaviours.TryGetValue(triggerBehaviour.Trigger, out ICollection allowed)) From 6b98c755b085a9ee08d297936bb3a31904fdebfb Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:55:40 +0900 Subject: [PATCH 16/27] Delete synchronous InternalFireOne --- src/Stateless/StateMachine.Async.cs | 6 +++ src/Stateless/StateMachine.cs | 61 +---------------------------- 2 files changed, 8 insertions(+), 59 deletions(-) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index 515de187..2d7ad091 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -158,6 +158,12 @@ async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args) } } + /// + /// This method handles the execution of a trigger handler. It finds a + /// handle, then updates the current state information. + /// + /// + /// async Task InternalFireOneAsync(TTrigger trigger, params object[] args) { // If this is a trigger with parameters, we must validate the parameter(s) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index b56f457d..153e9464 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -321,7 +321,7 @@ void InternalFire(TTrigger trigger, params object[] args) switch (_firingMode) { case FiringMode.Immediate: - InternalFireOne(trigger, args); + InternalFireOneAsync(trigger, args).GetAwaiter().GetResult(); break; case FiringMode.Queued: InternalFireQueued(trigger, args); @@ -357,7 +357,7 @@ private void InternalFireQueued(TTrigger trigger, params object[] args) while (_eventQueue.Any()) { var queuedEvent = _eventQueue.Dequeue(); - InternalFireOne(queuedEvent.Trigger, queuedEvent.Args); + InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).GetAwaiter().GetResult(); } } finally @@ -366,63 +366,6 @@ private void InternalFireQueued(TTrigger trigger, params object[] args) } } - /// - /// This method handles the execution of a trigger handler. It finds a - /// handle, then updates the current state information. - /// - /// - /// - void InternalFireOne(TTrigger trigger, params object[] args) - { - // If this is a trigger with parameters, we must validate the parameter(s) - if (_triggerConfiguration.TryGetValue(trigger, out TriggerWithParameters configuration)) - configuration.ValidateParameters(args); - - var source = State; - var representativeState = GetRepresentation(source); - - // Try to find a trigger handler, either in the current state or a super state. - if (!representativeState.TryFindHandler(trigger, args, out TriggerBehaviourResult result)) - { - _unhandledTriggerAction.Execute(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).GetAwaiter().GetResult(); - return; - } - - switch (result.Handler) - { - // Check if this trigger should be ignored - case IgnoredTriggerBehaviour _: - return; - // Handle special case, re-entry in superstate - // Check if it is an internal transition, or a transition from one state to another. - case ReentryTriggerBehaviour handler: - { - // Handle transition, and set new state - var transition = new Transition(source, handler.Destination, trigger, args); - HandleReentryTriggerAsync(args, representativeState, transition).GetAwaiter().GetResult(); - break; - } - case DynamicTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out var destination)): - case TransitioningTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out destination)): - { - // Handle transition, and set new state - var transition = new Transition(source, destination, trigger, args); - HandleTransitioningTriggerAsync(args, representativeState, transition).GetAwaiter().GetResult(); - - break; - } - case InternalTriggerBehaviour itb: - { - // Internal transitions does not update the current state, but must execute the associated action. - var transition = new Transition(source, source, trigger, args); - itb.Execute(transition, args).GetAwaiter().GetResult(); - break; - } - default: - throw new InvalidOperationException("State machine configuration incorrect, no handler for trigger."); - } - } - /// /// Override the default behaviour of throwing an exception when an unhandled trigger /// is fired. From 61db67fb3a381126c211009fddb624f4f9c1aae4 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 18:58:08 +0900 Subject: [PATCH 17/27] Delete synchronous InternalFireQueued --- src/Stateless/StateMachine.Async.cs | 10 ++++---- src/Stateless/StateMachine.cs | 36 +---------------------------- 2 files changed, 7 insertions(+), 39 deletions(-) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index 2d7ad091..e2782abf 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -134,9 +134,12 @@ async Task InternalFireAsync(TTrigger trigger, params object[] args) /// A variable-length parameters list containing arguments. async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args) { + // Add trigger to queue + _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args }); + + // If a trigger is already being handled then the trigger will be queued (FIFO) and processed later. if (_firing) { - _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args }); return; } @@ -144,9 +147,8 @@ async Task InternalFireQueuedAsync(TTrigger trigger, params object[] args) { _firing = true; - await InternalFireOneAsync(trigger, args).ConfigureAwait(false); - - while (_eventQueue.Count != 0) + // Empty queue for triggers + while (_eventQueue.Any()) { var queuedEvent = _eventQueue.Dequeue(); await InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).ConfigureAwait(false); diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 153e9464..4e43d4eb 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -324,7 +324,7 @@ void InternalFire(TTrigger trigger, params object[] args) InternalFireOneAsync(trigger, args).GetAwaiter().GetResult(); break; case FiringMode.Queued: - InternalFireQueued(trigger, args); + InternalFireQueuedAsync(trigger, args).GetAwaiter().GetResult(); break; default: // If something is completely messed up we let the user know ;-) @@ -332,40 +332,6 @@ void InternalFire(TTrigger trigger, params object[] args) } } - /// - /// Queue events and then fire in order. - /// If only one event is queued, this behaves identically to the non-queued version. - /// - /// The trigger. - /// A variable-length parameters list containing arguments. - private void InternalFireQueued(TTrigger trigger, params object[] args) - { - // Add trigger to queue - _eventQueue.Enqueue(new QueuedTrigger { Trigger = trigger, Args = args }); - - // If a trigger is already being handled then the trigger will be queued (FIFO) and processed later. - if (_firing) - { - return; - } - - try - { - _firing = true; - - // Empty queue for triggers - while (_eventQueue.Any()) - { - var queuedEvent = _eventQueue.Dequeue(); - InternalFireOneAsync(queuedEvent.Trigger, queuedEvent.Args).GetAwaiter().GetResult(); - } - } - finally - { - _firing = false; - } - } - /// /// Override the default behaviour of throwing an exception when an unhandled trigger /// is fired. From 21ec879ea746e56bef09c1101e795b83b4bbe873 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 19:00:43 +0900 Subject: [PATCH 18/27] Delete synchronous InternalFire --- src/Stateless/StateMachine.cs | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 4e43d4eb..bc347179 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -201,7 +201,7 @@ public StateConfiguration Configure(TState state) /// not allow the trigger to be fired. public void Fire(TTrigger trigger) { - InternalFire(trigger, new object[0]); + InternalFireAsync(trigger, new object[0]).GetAwaiter().GetResult(); } /// @@ -217,7 +217,7 @@ public void Fire(TTrigger trigger) public void Fire(TriggerWithParameters trigger, params object[] args) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFire(trigger.Trigger, args); + InternalFireAsync(trigger.Trigger, args).GetAwaiter().GetResult(); } /// @@ -248,7 +248,7 @@ public TriggerWithParameters SetTriggerParameters(TTrigger trigger, params Type[ public void Fire(TriggerWithParameters trigger, TArg0 arg0) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFire(trigger.Trigger, arg0); + InternalFireAsync(trigger.Trigger, arg0).GetAwaiter().GetResult(); } /// @@ -267,7 +267,7 @@ public void Fire(TriggerWithParameters trigger, TArg0 arg0) public void Fire(TriggerWithParameters trigger, TArg0 arg0, TArg1 arg1) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFire(trigger.Trigger, arg0, arg1); + InternalFireAsync(trigger.Trigger, arg0, arg1).GetAwaiter().GetResult(); } /// @@ -288,7 +288,7 @@ public void Fire(TriggerWithParameters trigger, TArg public void Fire(TriggerWithParameters trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFire(trigger.Trigger, arg0, arg1, arg2); + InternalFireAsync(trigger.Trigger, arg0, arg1, arg2).GetAwaiter().GetResult(); } /// @@ -311,27 +311,6 @@ public void Deactivate() DeactivateAsync().GetAwaiter().GetResult(); } - /// - /// Determine how to Fire the trigger - /// - /// The trigger. - /// A variable-length parameters list containing arguments. - void InternalFire(TTrigger trigger, params object[] args) - { - switch (_firingMode) - { - case FiringMode.Immediate: - InternalFireOneAsync(trigger, args).GetAwaiter().GetResult(); - break; - case FiringMode.Queued: - InternalFireQueuedAsync(trigger, args).GetAwaiter().GetResult(); - break; - default: - // If something is completely messed up we let the user know ;-) - throw new InvalidOperationException("The firing mode has not been configured!"); - } - } - /// /// Override the default behaviour of throwing an exception when an unhandled trigger /// is fired. From bff894d769e07636ee8ea16030829eeb17fb6a7e Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 19:08:32 +0900 Subject: [PATCH 19/27] Make synchronous Fire proxy async method --- src/Stateless/StateMachine.Async.cs | 17 +++++++++++++++++ src/Stateless/StateMachine.cs | 14 +++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index e2782abf..74b8176f 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -45,6 +45,23 @@ public Task FireAsync(TTrigger trigger) return InternalFireAsync(trigger, new object[0]); } + /// + /// Transition from the current state via the specified trigger. + /// The target state is determined by the configuration of the current state. + /// Actions associated with leaving the current state and entering the new one + /// will be invoked. + /// + /// The trigger to fire. + /// A variable-length parameters list containing arguments. + /// The current state does + /// not allow the trigger to be fired. + public Task FireAsync(TriggerWithParameters trigger, params object[] args) + { + if (trigger == null) throw new ArgumentNullException(nameof(trigger)); + + return InternalFireAsync(trigger.Trigger, args); + } + /// /// Transition from the current state via the specified trigger in async fashion. /// The target state is determined by the configuration of the current state. diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index bc347179..e22df203 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -201,7 +201,7 @@ public StateConfiguration Configure(TState state) /// not allow the trigger to be fired. public void Fire(TTrigger trigger) { - InternalFireAsync(trigger, new object[0]).GetAwaiter().GetResult(); + FireAsync(trigger).GetAwaiter().GetResult(); } /// @@ -216,8 +216,7 @@ public void Fire(TTrigger trigger) /// not allow the trigger to be fired. public void Fire(TriggerWithParameters trigger, params object[] args) { - if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFireAsync(trigger.Trigger, args).GetAwaiter().GetResult(); + FireAsync(trigger, args).GetAwaiter().GetResult(); } /// @@ -247,8 +246,7 @@ public TriggerWithParameters SetTriggerParameters(TTrigger trigger, params Type[ /// not allow the trigger to be fired. public void Fire(TriggerWithParameters trigger, TArg0 arg0) { - if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFireAsync(trigger.Trigger, arg0).GetAwaiter().GetResult(); + FireAsync(trigger, arg0).GetAwaiter().GetResult(); } /// @@ -266,8 +264,7 @@ public void Fire(TriggerWithParameters trigger, TArg0 arg0) /// not allow the trigger to be fired. public void Fire(TriggerWithParameters trigger, TArg0 arg0, TArg1 arg1) { - if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFireAsync(trigger.Trigger, arg0, arg1).GetAwaiter().GetResult(); + FireAsync(trigger, arg0, arg1).GetAwaiter().GetResult(); } /// @@ -287,8 +284,7 @@ public void Fire(TriggerWithParameters trigger, TArg /// not allow the trigger to be fired. public void Fire(TriggerWithParameters trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2) { - if (trigger == null) throw new ArgumentNullException(nameof(trigger)); - InternalFireAsync(trigger.Trigger, arg0, arg1, arg2).GetAwaiter().GetResult(); + FireAsync(trigger, arg0, arg1, arg2).GetAwaiter().GetResult(); } /// From 9b4f5724495821fd83e4a32b3f321d952dcd83c0 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 19:16:34 +0900 Subject: [PATCH 20/27] Add postfix to async methods --- src/Stateless/ActivateActionBehaviour.cs | 2 +- src/Stateless/DeactivateActionBehaviour.cs | 2 +- src/Stateless/EntryActionBehaviour.cs | 6 +++--- src/Stateless/ExitActionBehaviour.cs | 2 +- src/Stateless/InternalTriggerBehaviour.cs | 2 +- src/Stateless/OnTransitionedEvent.cs | 8 -------- src/Stateless/StateMachine.Async.cs | 4 ++-- src/Stateless/StateRepresentation.Async.cs | 8 ++++---- src/Stateless/UnhandledTriggerAction.cs | 2 +- 9 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Stateless/ActivateActionBehaviour.cs b/src/Stateless/ActivateActionBehaviour.cs index 0185791c..50b628e5 100644 --- a/src/Stateless/ActivateActionBehaviour.cs +++ b/src/Stateless/ActivateActionBehaviour.cs @@ -24,7 +24,7 @@ protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo action internal Reflection.InvocationInfo Description { get; } - public virtual Task Execute() + public virtual Task ExecuteAsync() { return _callback.InvokeAsync(); } diff --git a/src/Stateless/DeactivateActionBehaviour.cs b/src/Stateless/DeactivateActionBehaviour.cs index 5da99fe1..8b185677 100644 --- a/src/Stateless/DeactivateActionBehaviour.cs +++ b/src/Stateless/DeactivateActionBehaviour.cs @@ -24,7 +24,7 @@ protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo acti internal Reflection.InvocationInfo Description { get; } - public virtual Task Execute() + public virtual Task ExecuteAsync() { return _callback.InvokeAsync(); } diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 6740b4f7..706f6c5c 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -22,7 +22,7 @@ protected EntryActionBehavior(Reflection.InvocationInfo description) public Reflection.InvocationInfo Description { get; } - public virtual Task Execute(Transition transition, object[] args) + public virtual Task ExecuteAsync(Transition transition, object[] args) { return _callback.InvokeAsync(transition, args); } @@ -37,10 +37,10 @@ public From(TTriggerType trigger, EventCallback action, Re Trigger = trigger; } - public override Task Execute(Transition transition, object[] args) + public override Task ExecuteAsync(Transition transition, object[] args) { if (transition.Trigger.Equals(Trigger)) - return base.Execute(transition, args); + return base.ExecuteAsync(transition, args); return TaskResult.Done; } diff --git a/src/Stateless/ExitActionBehaviour.cs b/src/Stateless/ExitActionBehaviour.cs index 23d9a2af..ae5110e2 100644 --- a/src/Stateless/ExitActionBehaviour.cs +++ b/src/Stateless/ExitActionBehaviour.cs @@ -22,7 +22,7 @@ protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) internal Reflection.InvocationInfo Description { get; } - public virtual Task Execute(Transition transition) + public virtual Task ExecuteAsync(Transition transition) { return _callback.InvokeAsync(transition); } diff --git a/src/Stateless/InternalTriggerBehaviour.cs b/src/Stateless/InternalTriggerBehaviour.cs index edde54b7..3e3b1ba1 100644 --- a/src/Stateless/InternalTriggerBehaviour.cs +++ b/src/Stateless/InternalTriggerBehaviour.cs @@ -32,7 +32,7 @@ public override bool ResultsInTransitionFrom(TState source, object[] args, out T return false; } - public virtual Task Execute(Transition transition, object[] args) + public virtual Task ExecuteAsync(Transition transition, object[] args) { return _callback.InvokeAsync(transition, args); } diff --git a/src/Stateless/OnTransitionedEvent.cs b/src/Stateless/OnTransitionedEvent.cs index c281db2f..1d9f59eb 100644 --- a/src/Stateless/OnTransitionedEvent.cs +++ b/src/Stateless/OnTransitionedEvent.cs @@ -15,19 +15,11 @@ public OnTransitionedEvent() _callbacks = new List>(); } - public void Invoke(Transition transition) - { - foreach (var callback in _callbacks) - callback.InvokeAsync(transition).GetAwaiter().GetResult(); - } - -#if TASKS public async Task InvokeAsync(Transition transition) { foreach (var callback in _callbacks) await callback.InvokeAsync(transition).ConfigureAwait(false); } -#endif public void Register(Action action) { diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index 74b8176f..3a949385 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -195,7 +195,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) // Try to find a trigger handler, either in the current state or a super state. if (!representativeState.TryFindHandler(trigger, args, out TriggerBehaviourResult result)) { - await _unhandledTriggerAction.Execute(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).ConfigureAwait(false); + await _unhandledTriggerAction.ExecuteAsync(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).ConfigureAwait(false); return; } @@ -226,7 +226,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) { // Internal transitions does not update the current state, but must execute the associated action. var transition = new Transition(source, source, trigger, args); - await itb.Execute(transition, args).ConfigureAwait(false); + await itb.ExecuteAsync(transition, args).ConfigureAwait(false); break; } default: diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index 9eb13644..d2135948 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -62,13 +62,13 @@ public async Task DeactivateAsync() async Task ExecuteActivationActionsAsync() { foreach (var action in ActivateActions) - await action.Execute().ConfigureAwait(false); + await action.ExecuteAsync().ConfigureAwait(false); } async Task ExecuteDeactivationActionsAsync() { foreach (var action in DeactivateActions) - await action.Execute().ConfigureAwait(false); + await action.ExecuteAsync().ConfigureAwait(false); } @@ -122,13 +122,13 @@ public async Task ExitAsync(Transition transition) async Task ExecuteEntryActionsAsync(Transition transition, object[] entryArgs) { foreach (var action in EntryActions) - await action.Execute(transition, entryArgs).ConfigureAwait(false); + await action.ExecuteAsync(transition, entryArgs).ConfigureAwait(false); } async Task ExecuteExitActionsAsync(Transition transition) { foreach (var action in ExitActions) - await action.Execute(transition).ConfigureAwait(false); + await action.ExecuteAsync(transition).ConfigureAwait(false); } } } diff --git a/src/Stateless/UnhandledTriggerAction.cs b/src/Stateless/UnhandledTriggerAction.cs index b61efacf..c606ed08 100644 --- a/src/Stateless/UnhandledTriggerAction.cs +++ b/src/Stateless/UnhandledTriggerAction.cs @@ -15,7 +15,7 @@ public UnhandledTriggerAction(EventCallback unmetGuards) + public virtual Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) { return _callback.InvokeAsync(state, trigger, unmetGuards); } From 5d53d5f5924aa892ab37b5206af0e6cf0038445d Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 19:28:15 +0900 Subject: [PATCH 21/27] Delete TASKS compilation guards --- src/Stateless/StateConfiguration.Async.cs | 5 +---- src/Stateless/StateMachine.Async.cs | 4 ---- src/Stateless/StateRepresentation.Async.cs | 4 ---- test/Stateless.Tests/AsyncActionsFixture.cs | 4 ---- test/Stateless.Tests/AsyncFireingModesFixture.cs | 6 ++---- 5 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/Stateless/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index f9a0a05f..917407ce 100644 --- a/src/Stateless/StateConfiguration.Async.cs +++ b/src/Stateless/StateConfiguration.Async.cs @@ -1,6 +1,4 @@ -#if TASKS - -using System; +using System; using System.Threading.Tasks; namespace Stateless @@ -462,4 +460,3 @@ public StateConfiguration OnExitAsync(Func exitAction, string } } } -#endif diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index 3a949385..a44c3a2c 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -1,5 +1,3 @@ -#if TASKS - using System; using System.Collections.Generic; using System.Linq; @@ -363,5 +361,3 @@ public void OnTransitionCompletedAsync(Func onTransitionAction } } } - -#endif diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index d2135948..380d8be4 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -1,5 +1,3 @@ -#if TASKS - using System; using System.Threading.Tasks; @@ -133,5 +131,3 @@ async Task ExecuteExitActionsAsync(Transition transition) } } } - -#endif diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index b22bc42c..ca638132 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -1,5 +1,3 @@ -#if TASKS - using System; using System.Threading.Tasks; using System.Collections.Generic; @@ -370,5 +368,3 @@ public void VerifyNotEnterSuperstateWhenDoingInitialTransition() } } } - -#endif diff --git a/test/Stateless.Tests/AsyncFireingModesFixture.cs b/test/Stateless.Tests/AsyncFireingModesFixture.cs index 5f7b6484..cfc9b43d 100644 --- a/test/Stateless.Tests/AsyncFireingModesFixture.cs +++ b/test/Stateless.Tests/AsyncFireingModesFixture.cs @@ -1,5 +1,4 @@ -#if TASKS -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -151,7 +150,7 @@ public async Task ImmediateModeTransitionsAreInCorrectOrderWithAsyncDriving() await sm.FireAsync(Trigger.X); - Assert.Equal(new List() { + Assert.Equal(new List() { State.B, State.C, State.A @@ -204,4 +203,3 @@ public async void EntersSubStateofSubstateAsyncOnEntryCountAndOrder() } } } -#endif \ No newline at end of file From 5f42cb97250406ed0dad5f79c52a48ef38fed70a Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 20:11:49 +0900 Subject: [PATCH 22/27] Add Microsoft.Bcl.Async on .NET FW 4.0 build --- src/Stateless/EventCallback.cs | 2 +- src/Stateless/Stateless.csproj | 7 ++++++- src/Stateless/TaskResult.cs | 10 +++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Stateless/EventCallback.cs b/src/Stateless/EventCallback.cs index e77fa27f..28ec9b26 100644 --- a/src/Stateless/EventCallback.cs +++ b/src/Stateless/EventCallback.cs @@ -51,7 +51,7 @@ protected Task DynamicInvokeAsync(params object[] args) // Since we fell into the DynamicInvoke case, any exception will be wrapped // in a TIE. We can expect this to be thrown synchronously, so it's low overhead // to unwrap it. - return Task.FromException(e.InnerException); + return TaskResult.FromException(e.InnerException); } } } diff --git a/src/Stateless/Stateless.csproj b/src/Stateless/Stateless.csproj index 5edb08c1..e9a41b42 100644 --- a/src/Stateless/Stateless.csproj +++ b/src/Stateless/Stateless.csproj @@ -29,6 +29,11 @@ - + + + + + + diff --git a/src/Stateless/TaskResult.cs b/src/Stateless/TaskResult.cs index aa201f52..02726741 100644 --- a/src/Stateless/TaskResult.cs +++ b/src/Stateless/TaskResult.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Stateless { @@ -6,6 +7,13 @@ internal static class TaskResult { internal static readonly Task Done = FromResult(1); + internal static Task FromException(Exception exception) + { + var tcs = new TaskCompletionSource(); + tcs.SetException(exception); + return tcs.Task; + } + static Task FromResult(T value) { var tcs = new TaskCompletionSource(); From 0dcaf3c0e1dcfb1efd44836961723564d321e39f Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sat, 22 May 2021 20:23:51 +0900 Subject: [PATCH 23/27] Add missing ConfigureAwait(false) --- src/Stateless/StateMachine.Async.cs | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index a44c3a2c..974ce85f 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -130,10 +130,10 @@ async Task InternalFireAsync(TTrigger trigger, params object[] args) switch (_firingMode) { case FiringMode.Immediate: - await InternalFireOneAsync(trigger, args); + await InternalFireOneAsync(trigger, args).ConfigureAwait(false); break; case FiringMode.Queued: - await InternalFireQueuedAsync(trigger, args); + await InternalFireQueuedAsync(trigger, args).ConfigureAwait(false); break; default: // If something is completely messed up we let the user know ;-) @@ -208,7 +208,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) { // Handle transition, and set new state var transition = new Transition(source, handler.Destination, trigger, args); - await HandleReentryTriggerAsync(args, representativeState, transition); + await HandleReentryTriggerAsync(args, representativeState, transition).ConfigureAwait(false); break; } case DynamicTriggerBehaviour _ when (result.Handler.ResultsInTransitionFrom(source, args, out var destination)): @@ -216,7 +216,7 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) { // Handle transition, and set new state var transition = new Transition(source, destination, trigger, args); - await HandleTransitioningTriggerAsync(args, representativeState, transition); + await HandleTransitioningTriggerAsync(args, representativeState, transition).ConfigureAwait(false); break; } @@ -235,38 +235,38 @@ async Task InternalFireOneAsync(TTrigger trigger, params object[] args) private async Task HandleReentryTriggerAsync(object[] args, StateRepresentation representativeState, Transition transition) { StateRepresentation representation; - transition = await representativeState.ExitAsync(transition); + transition = await representativeState.ExitAsync(transition).ConfigureAwait(false); var newRepresentation = GetRepresentation(transition.Destination); if (!transition.Source.Equals(transition.Destination)) { // Then Exit the final superstate transition = new Transition(transition.Destination, transition.Destination, transition.Trigger, args); - await newRepresentation.ExitAsync(transition); + await newRepresentation.ExitAsync(transition).ConfigureAwait(false); - await _onTransitionedEvent.InvokeAsync(transition); - representation = await EnterStateAsync(newRepresentation, transition, args); - await _onTransitionCompletedEvent.InvokeAsync(transition); + await _onTransitionedEvent.InvokeAsync(transition).ConfigureAwait(false); + representation = await EnterStateAsync(newRepresentation, transition, args).ConfigureAwait(false); + await _onTransitionCompletedEvent.InvokeAsync(transition).ConfigureAwait(false); } else { - await _onTransitionedEvent.InvokeAsync(transition); - representation = await EnterStateAsync(newRepresentation, transition, args); - await _onTransitionCompletedEvent.InvokeAsync(transition); + await _onTransitionedEvent.InvokeAsync(transition).ConfigureAwait(false); + representation = await EnterStateAsync(newRepresentation, transition, args).ConfigureAwait(false); + await _onTransitionCompletedEvent.InvokeAsync(transition).ConfigureAwait(false); } State = representation.UnderlyingState; } private async Task HandleTransitioningTriggerAsync(object[] args, StateRepresentation representativeState, Transition transition) { - transition = await representativeState.ExitAsync(transition); + transition = await representativeState.ExitAsync(transition).ConfigureAwait(false); State = transition.Destination; var newRepresentation = GetRepresentation(transition.Destination); //Alert all listeners of state transition - await _onTransitionedEvent.InvokeAsync(transition); - var representation = await EnterStateAsync(newRepresentation, transition, args); + await _onTransitionedEvent.InvokeAsync(transition).ConfigureAwait(false); + var representation = await EnterStateAsync(newRepresentation, transition, args).ConfigureAwait(false); // Check if state has changed by entering new state (by fireing triggers in OnEntry or such) if (!representation.UnderlyingState.Equals(State)) @@ -275,14 +275,14 @@ private async Task HandleTransitioningTriggerAsync(object[] args, StateRepresent State = representation.UnderlyingState; } - await _onTransitionCompletedEvent.InvokeAsync(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)); + await _onTransitionCompletedEvent.InvokeAsync(new Transition(transition.Source, State, transition.Trigger, transition.Parameters)).ConfigureAwait(false); } private async Task EnterStateAsync(StateRepresentation representation, Transition transition, object[] args) { // Enter the new state - await representation.EnterAsync(transition, args); + await representation.EnterAsync(transition, args).ConfigureAwait(false); if (FiringMode.Immediate.Equals(_firingMode) && !State.Equals(transition.Destination)) { @@ -306,8 +306,8 @@ private async Task EnterStateAsync(StateRepresentation repr representation = GetRepresentation(representation.InitialTransitionTarget); // Alert all listeners of initial state transition - await _onTransitionedEvent.InvokeAsync(new Transition(transition.Destination, initialTransition.Destination, transition.Trigger, transition.Parameters)); - representation = await EnterStateAsync(representation, initialTransition, args); + await _onTransitionedEvent.InvokeAsync(new Transition(transition.Destination, initialTransition.Destination, transition.Trigger, transition.Parameters)).ConfigureAwait(false); + representation = await EnterStateAsync(representation, initialTransition, args).ConfigureAwait(false); } return representation; From ba36bf1101abfb5e4843010c518ef639b700c31a Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sun, 23 May 2021 11:40:18 +0900 Subject: [PATCH 24/27] Move EventCallbackFactory call to ctor --- src/Stateless/ActivateActionBehaviour.cs | 10 +++++++-- src/Stateless/DeactivateActionBehaviour.cs | 10 +++++++-- src/Stateless/EntryActionBehaviour.cs | 18 +++++++++++++--- src/Stateless/ExitActionBehaviour.cs | 10 +++++++-- src/Stateless/InternalTriggerBehaviour.cs | 8 ++++---- src/Stateless/StateConfiguration.Async.cs | 16 +++++++-------- src/Stateless/StateConfiguration.cs | 24 +++++++++++----------- src/Stateless/StateMachine.Async.cs | 4 ++-- src/Stateless/StateMachine.cs | 6 +++--- src/Stateless/StateRepresentation.Async.cs | 10 ++++----- src/Stateless/StateRepresentation.cs | 10 ++++----- src/Stateless/UnhandledTriggerAction.cs | 9 ++++++-- 12 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/Stateless/ActivateActionBehaviour.cs b/src/Stateless/ActivateActionBehaviour.cs index 50b628e5..49bfecb7 100644 --- a/src/Stateless/ActivateActionBehaviour.cs +++ b/src/Stateless/ActivateActionBehaviour.cs @@ -10,10 +10,16 @@ internal class ActivateActionBehaviour readonly TState _state; private readonly EventCallback _callback; - public ActivateActionBehaviour(TState state, EventCallback action, Reflection.InvocationInfo actionDescription) + public ActivateActionBehaviour(TState state, Action action, Reflection.InvocationInfo actionDescription) : this(state, actionDescription) { - _callback = action; + _callback = EventCallbackFactory.Create(action); + } + + public ActivateActionBehaviour(TState state, Func action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) + { + _callback = EventCallbackFactory.Create(action); } protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) diff --git a/src/Stateless/DeactivateActionBehaviour.cs b/src/Stateless/DeactivateActionBehaviour.cs index 8b185677..ff1ad4e5 100644 --- a/src/Stateless/DeactivateActionBehaviour.cs +++ b/src/Stateless/DeactivateActionBehaviour.cs @@ -10,10 +10,16 @@ internal class DeactivateActionBehaviour readonly TState _state; private readonly EventCallback _callback; - public DeactivateActionBehaviour(TState state, EventCallback action, Reflection.InvocationInfo actionDescription) + public DeactivateActionBehaviour(TState state, Action action, Reflection.InvocationInfo actionDescription) : this(state, actionDescription) { - _callback = action; + _callback = EventCallbackFactory.Create(action); + } + + public DeactivateActionBehaviour(TState state, Func action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) + { + _callback = EventCallbackFactory.Create(action); } protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 706f6c5c..2a4c843c 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -9,10 +9,16 @@ internal class EntryActionBehavior { private readonly EventCallback _callback; - public EntryActionBehavior(EventCallback action, Reflection.InvocationInfo description) + public EntryActionBehavior(Action action, Reflection.InvocationInfo description) : this(description) { - _callback = action; + _callback = EventCallbackFactory.Create(action); + } + + public EntryActionBehavior(Func action, Reflection.InvocationInfo description) + : this(description) + { + _callback = EventCallbackFactory.Create(action); } protected EntryActionBehavior(Reflection.InvocationInfo description) @@ -31,7 +37,13 @@ public class From : EntryActionBehavior { internal TTriggerType Trigger { get; private set; } - public From(TTriggerType trigger, EventCallback action, Reflection.InvocationInfo description) + public From(TTriggerType trigger, Action action, Reflection.InvocationInfo description) + : base(action, description) + { + Trigger = trigger; + } + + public From(TTriggerType trigger, Func action, Reflection.InvocationInfo description) : base(action, description) { Trigger = trigger; diff --git a/src/Stateless/ExitActionBehaviour.cs b/src/Stateless/ExitActionBehaviour.cs index ae5110e2..ec69adde 100644 --- a/src/Stateless/ExitActionBehaviour.cs +++ b/src/Stateless/ExitActionBehaviour.cs @@ -9,10 +9,16 @@ internal class ExitActionBehavior { private readonly EventCallback _callback; - public ExitActionBehavior(EventCallback action, Reflection.InvocationInfo actionDescription) + public ExitActionBehavior(Action action, Reflection.InvocationInfo actionDescription) : this(actionDescription) { - _callback = action; + _callback = EventCallbackFactory.Create(action); + } + + public ExitActionBehavior(Func action, Reflection.InvocationInfo actionDescription) + : this(actionDescription) + { + _callback = EventCallbackFactory.Create(action); } protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) diff --git a/src/Stateless/InternalTriggerBehaviour.cs b/src/Stateless/InternalTriggerBehaviour.cs index 3e3b1ba1..8f8847d3 100644 --- a/src/Stateless/InternalTriggerBehaviour.cs +++ b/src/Stateless/InternalTriggerBehaviour.cs @@ -9,17 +9,17 @@ internal class InternalTriggerBehaviour : TriggerBehaviour { private readonly EventCallback _callback; - public InternalTriggerBehaviour(TTrigger trigger, Func guard, EventCallback internalAction, string guardDescription = null) + public InternalTriggerBehaviour(TTrigger trigger, Func guard, Action internalAction, string guardDescription = null) : this(trigger, new TransitionGuard(guard, guardDescription)) { - _callback = internalAction; + _callback = EventCallbackFactory.Create(internalAction); } // FIXME: inconsistent with synchronous version of StateConfiguration - public InternalTriggerBehaviour(TTrigger trigger, Func guard, EventCallback internalAction, string guardDescription = null) + public InternalTriggerBehaviour(TTrigger trigger, Func guard, Func internalAction, string guardDescription = null) : this(trigger, new TransitionGuard(guard, guardDescription)) { - _callback = internalAction; + _callback = EventCallbackFactory.Create(internalAction); } protected InternalTriggerBehaviour(TTrigger trigger, TransitionGuard guard) : base(trigger, guard) diff --git a/src/Stateless/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index 917407ce..47d7fc1b 100644 --- a/src/Stateless/StateConfiguration.Async.cs +++ b/src/Stateless/StateConfiguration.Async.cs @@ -18,7 +18,7 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Func { if (entryAction == null) throw new ArgumentNullException(nameof(entryAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => entryAction(t)))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => entryAction(t))); return this; } @@ -33,7 +33,7 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Func { if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction()))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => internalAction())); return this; } @@ -49,7 +49,7 @@ public StateConfiguration InternalTransitionAsyncIf(TTrigger trigger, Fun { if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, EventCallbackFactory.Create((t, args) => internalAction(t)))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => internalAction(t))); return this; } @@ -66,7 +66,7 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithParameters if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction(ParameterConversion.Unpack(args, 0), t)))); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, (t, args) => internalAction(ParameterConversion.Unpack(args, 0), t))); return this; } @@ -84,9 +84,9 @@ public StateConfiguration InternalTransitionAsyncIf(TriggerWithPar if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t)))); + ParameterConversion.Unpack(args, 1), t))); return this; } @@ -105,10 +105,10 @@ public StateConfiguration InternalTransitionAsyncIf(Trigger if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t)))); + ParameterConversion.Unpack(args, 2), t))); return this; } diff --git a/src/Stateless/StateConfiguration.cs b/src/Stateless/StateConfiguration.cs index 99cf4533..11d6ef59 100644 --- a/src/Stateless/StateConfiguration.cs +++ b/src/Stateless/StateConfiguration.cs @@ -68,7 +68,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func((t, args) => entryAction(t)), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => entryAction(t), guardDescription)); return this; } @@ -95,7 +95,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func((t, args) => internalAction()), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => internalAction(), guardDescription)); return this; } @@ -112,7 +112,7 @@ public StateConfiguration InternalTransitionIf(TTrigger trigger, Func((t, args) => internalAction(t)), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger, guard, (t, args) => internalAction(t), guardDescription)); return this; } @@ -154,7 +154,7 @@ public StateConfiguration InternalTransitionIf(TriggerWithParameters((t, args) => internalAction(ParameterConversion.Unpack(args, 0), t)), guardDescription)); + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction(ParameterConversion.Unpack(args, 0), t), guardDescription)); return this; } @@ -187,9 +187,9 @@ public StateConfiguration InternalTransitionIf(TriggerWithParamete if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t)), + ParameterConversion.Unpack(args, 1), t), guardDescription)); return this; } @@ -212,9 +212,9 @@ public StateConfiguration InternalTransitionIf(TriggerWithParamete _representation.AddTriggerBehaviour(new InternalTriggerBehaviour( trigger.Trigger, TransitionGuard.ToPackedGuard(guard), - EventCallbackFactory.Create((t, args) => internalAction( + (t, args) => internalAction( ParameterConversion.Unpack(args, 0), - ParameterConversion.Unpack(args, 1), t)), + ParameterConversion.Unpack(args, 1), t), guardDescription )); return this; @@ -236,10 +236,10 @@ public StateConfiguration InternalTransitionIf(TriggerWithP if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, EventCallbackFactory.Create((t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, guard, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t)), + ParameterConversion.Unpack(args, 2), t), guardDescription)); return this; } @@ -260,10 +260,10 @@ public StateConfiguration InternalTransitionIf(TriggerWithP if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (internalAction == null) throw new ArgumentNullException(nameof(internalAction)); - _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), EventCallbackFactory.Create((t, args) => internalAction( + _representation.AddTriggerBehaviour(new InternalTriggerBehaviour(trigger.Trigger, TransitionGuard.ToPackedGuard(guard), (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), - ParameterConversion.Unpack(args, 2), t)), + ParameterConversion.Unpack(args, 2), t), guardDescription)); return this; } diff --git a/src/Stateless/StateMachine.Async.cs b/src/Stateless/StateMachine.Async.cs index 974ce85f..db949b64 100644 --- a/src/Stateless/StateMachine.Async.cs +++ b/src/Stateless/StateMachine.Async.cs @@ -321,7 +321,7 @@ private async Task EnterStateAsync(StateRepresentation repr public void OnUnhandledTriggerAsync(Func unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>((s, t, c) => unhandledTriggerAction(s, t))); + _unhandledTriggerAction = new UnhandledTriggerAction((s, t, c) => unhandledTriggerAction(s, t)); } /// @@ -332,7 +332,7 @@ public void OnUnhandledTriggerAsync(Func unhandledTrigge public void OnUnhandledTriggerAsync(Func, Task> unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create(unhandledTriggerAction)); + _unhandledTriggerAction = new UnhandledTriggerAction(unhandledTriggerAction); } /// diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index e22df203..77411ada 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -92,7 +92,7 @@ public StateMachine(TState initialState, FiringMode firingMode) : this() /// StateMachine() { - _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>(DefaultUnhandledTriggerAction)); + _unhandledTriggerAction = new UnhandledTriggerAction(DefaultUnhandledTriggerAction); _onTransitionedEvent = new OnTransitionedEvent(); _onTransitionCompletedEvent = new OnTransitionedEvent(); } @@ -315,7 +315,7 @@ public void Deactivate() public void OnUnhandledTrigger(Action unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create>((s, t, c) => unhandledTriggerAction(s, t))); + _unhandledTriggerAction = new UnhandledTriggerAction((s, t, c) => unhandledTriggerAction(s, t)); } /// @@ -326,7 +326,7 @@ public void OnUnhandledTrigger(Action unhandledTriggerAction) public void OnUnhandledTrigger(Action> unhandledTriggerAction) { if (unhandledTriggerAction == null) throw new ArgumentNullException(nameof(unhandledTriggerAction)); - _unhandledTriggerAction = new UnhandledTriggerAction(EventCallbackFactory.Create(unhandledTriggerAction)); + _unhandledTriggerAction = new UnhandledTriggerAction(unhandledTriggerAction); } /// diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index 380d8be4..27e6b306 100644 --- a/src/Stateless/StateRepresentation.Async.cs +++ b/src/Stateless/StateRepresentation.Async.cs @@ -9,12 +9,12 @@ internal partial class StateRepresentation { public void AddActivateAction(Func action, Reflection.InvocationInfo activateActionDescription) { - ActivateActions.Add(new ActivateActionBehaviour(_state, EventCallbackFactory.Create(action), activateActionDescription)); + ActivateActions.Add(new ActivateActionBehaviour(_state, action, activateActionDescription)); } public void AddDeactivateAction(Func action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour(_state, EventCallbackFactory.Create(action), deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, action, deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Func action, Reflection.InvocationInfo entryActionDescription) @@ -24,7 +24,7 @@ public void AddEntryAction(TTrigger trigger, Func ac EntryActions.Add( new EntryActionBehavior.From( trigger, - EventCallbackFactory.Create(action), + action, entryActionDescription)); } @@ -32,13 +32,13 @@ public void AddEntryAction(Func action, Reflection.I { EntryActions.Add( new EntryActionBehavior( - EventCallbackFactory.Create(action), + action, entryActionDescription)); } public void AddExitAction(Func action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); + ExitActions.Add(new ExitActionBehavior(action, exitActionDescription)); } public async Task ActivateAsync() diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index e291f26f..64ef0890 100644 --- a/src/Stateless/StateRepresentation.cs +++ b/src/Stateless/StateRepresentation.cs @@ -117,27 +117,27 @@ private static TriggerBehaviourResult TryFindLocalHandlerResultWithUnmetGuardCon public void AddActivateAction(Action action, Reflection.InvocationInfo activateActionDescription) { - ActivateActions.Add(new ActivateActionBehaviour(_state, EventCallbackFactory.Create(action), activateActionDescription)); + ActivateActions.Add(new ActivateActionBehaviour(_state, action, activateActionDescription)); } public void AddDeactivateAction(Action action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour(_state, EventCallbackFactory.Create(action), deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, action, deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior.From(trigger, EventCallbackFactory.Create(action), entryActionDescription)); + EntryActions.Add(new EntryActionBehavior.From(trigger, action, entryActionDescription)); } public void AddEntryAction(Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior(EventCallbackFactory.Create(action), entryActionDescription)); + EntryActions.Add(new EntryActionBehavior(action, entryActionDescription)); } public void AddExitAction(Action action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior(EventCallbackFactory.Create(action), exitActionDescription)); + ExitActions.Add(new ExitActionBehavior(action, exitActionDescription)); } public void AddTriggerBehaviour(TriggerBehaviour triggerBehaviour) diff --git a/src/Stateless/UnhandledTriggerAction.cs b/src/Stateless/UnhandledTriggerAction.cs index c606ed08..609a37bb 100644 --- a/src/Stateless/UnhandledTriggerAction.cs +++ b/src/Stateless/UnhandledTriggerAction.cs @@ -10,9 +10,14 @@ internal class UnhandledTriggerAction { private readonly EventCallback> _callback; - public UnhandledTriggerAction(EventCallback> action = null) + public UnhandledTriggerAction(Action> action = null) { - _callback = action; + _callback = EventCallbackFactory.Create(action); + } + + public UnhandledTriggerAction(Func, Task> action = null) + { + _callback = EventCallbackFactory.Create(action); } public virtual Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) From 815b8ef1f7ebdbff02de3ce1fd09da1cf364424b Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sun, 23 May 2021 12:45:23 +0900 Subject: [PATCH 25/27] Add test cases for OnTransitionedEvent --- test/Stateless.Tests/AsyncActionsFixture.cs | 43 ++++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index ca638132..2d078e7e 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Xunit; +using System.Linq; namespace Stateless.Tests { @@ -190,16 +191,13 @@ public async Task WillInvokeSyncOnTransitionedIfRegisteredAlongWithAsyncAction() var test1 = ""; var test2 = ""; - var test3 = ""; sm.OnTransitioned(_ => test1 = "foo1"); sm.OnTransitionedAsync(_ => Task.Run(() => test2 = "foo2")); - sm.OnTransitioned(_ => test3 = "foo3"); await sm.FireAsync(Trigger.X).ConfigureAwait(false); Assert.Equal("foo1", test1); Assert.Equal("foo2", test2); - Assert.Equal("foo3", test3); } [Fact] @@ -212,16 +210,49 @@ public async Task WillInvokeSyncOnTransitionCompletedIfRegisteredAlongWithAsyncA var test1 = ""; var test2 = ""; - var test3 = ""; sm.OnTransitionCompleted(_ => test1 = "foo1"); sm.OnTransitionCompletedAsync(_ => Task.Run(() => test2 = "foo2")); - sm.OnTransitionCompleted(_ => test3 = "foo3"); await sm.FireAsync(Trigger.X).ConfigureAwait(false); Assert.Equal("foo1", test1); Assert.Equal("foo2", test2); - Assert.Equal("foo3", test3); + } + + [Fact] + public async Task WillInvokeOnTransitionedInOrderRegardlessOfSyncOrAsync() + { + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B); + + var tests = new List(); + sm.OnTransitioned(_ => tests.Add(1)); + sm.OnTransitionedAsync(_ => Task.Run(() => tests.Add(2))); + sm.OnTransitioned(_ => tests.Add(3)); + + await sm.FireAsync(Trigger.X); + + Assert.Equal(Enumerable.Range(1, 3), tests); + } + + [Fact] + public async Task WillInvokeOnTransitionCompletedInOrderRegardlessOfSyncOrAsync() + { + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B); + + var tests = new List(); + sm.OnTransitionCompleted(_ => tests.Add(1)); + sm.OnTransitionCompletedAsync(_ => Task.Run(() => tests.Add(2))); + sm.OnTransitionCompleted(_ => tests.Add(3)); + + await sm.FireAsync(Trigger.X); + + Assert.Equal(Enumerable.Range(1, 3), tests); } [Fact] From 82690a76a0460e1333a9ff6d4868edd605f3cbd4 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sun, 23 May 2021 13:20:17 +0900 Subject: [PATCH 26/27] Add test case for async code in a sync action --- test/Stateless.Tests/AsyncActionsFixture.cs | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 2d078e7e..5ff7d53b 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -255,6 +255,28 @@ public async Task WillInvokeOnTransitionCompletedInOrderRegardlessOfSyncOrAsync( Assert.Equal(Enumerable.Range(1, 3), tests); } + [Fact] + public async Task WillInvokeOnTransitionedNotInOrderAsyncInSync() + { + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B); + + var tests = new List(); + sm.OnTransitioned(_ => tests.Add(1)); + sm.OnTransitioned(_ => Task.Run(async () => + { + await Task.Yield(); + tests.Add(2); + })); + sm.OnTransitioned(_ => tests.Add(3)); + + await sm.FireAsync(Trigger.X); + + Assert.NotEqual(Enumerable.Range(1, 3), tests); + } + [Fact] public async Task CanInvokeOnUnhandledTriggerAsyncAction() { From e4219d755e977aa71003b47e745403f06f627c07 Mon Sep 17 00:00:00 2001 From: Sunghwan Bang Date: Sun, 23 May 2021 19:59:58 +0900 Subject: [PATCH 27/27] Add missing api of StateConfiguration We need TransitionConfiguration for InternalTransitionAsyncIf, PermitDynamicAsyncIf, etc... --- src/Stateless/StateConfiguration.Async.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Stateless/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index 47d7fc1b..833c1800 100644 --- a/src/Stateless/StateConfiguration.Async.cs +++ b/src/Stateless/StateConfiguration.Async.cs @@ -286,6 +286,27 @@ public StateConfiguration OnEntryFromAsync(TTrigger trigger, Func + /// Specify an asynchronous action that will execute when transitioning into + /// the configured state. + /// + /// Action to execute, providing details of the transition. + /// The trigger by which the state must be entered in order for the action to execute. + /// Action description. + /// The receiver. + public StateConfiguration OnEntryFromAsync(TriggerWithParameters trigger, Func entryAction, string entryActionDescription = null) + { + if (trigger == null) throw new ArgumentNullException(nameof(trigger)); + if (entryAction == null) throw new ArgumentNullException(nameof(entryAction)); + + _representation.AddEntryAction( + trigger.Trigger, + (t, args) => entryAction(t), + Reflection.InvocationInfo.Create(entryAction, entryActionDescription, Reflection.InvocationInfo.Timing.Asynchronous)); + return this; + } + /// /// Specify an asynchronous action that will execute when transitioning into /// the configured state.