diff --git a/src/Stateless/ActivateActionBehaviour.cs b/src/Stateless/ActivateActionBehaviour.cs index af012496..49bfecb7 100644 --- a/src/Stateless/ActivateActionBehaviour.cs +++ b/src/Stateless/ActivateActionBehaviour.cs @@ -5,64 +5,34 @@ namespace Stateless { public partial class StateMachine { - internal abstract class ActivateActionBehaviour + internal class ActivateActionBehaviour { readonly TState _state; + private readonly EventCallback _callback; - protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) + public ActivateActionBehaviour(TState state, Action action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) { - _state = state; - Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + _callback = EventCallbackFactory.Create(action); } - internal Reflection.InvocationInfo Description { get; } - - public abstract void Execute(); - public abstract Task ExecuteAsync(); - - public class Sync : ActivateActionBehaviour + public ActivateActionBehaviour(TState state, Func action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) { - 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; - } + _callback = EventCallbackFactory.Create(action); } - public class Async : ActivateActionBehaviour + protected ActivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) { - readonly Func _action; - - public Async(TState state, Func action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } + _state = state; + Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + } - public override void Execute() - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnActivateAsync for '{_state}' state. " + - "Use asynchronous version of Activate [ActivateAsync]"); - } + internal Reflection.InvocationInfo Description { get; } - public override Task ExecuteAsync() - { - return _action(); - } + public virtual Task ExecuteAsync() + { + return _callback.InvokeAsync(); } } } diff --git a/src/Stateless/DeactivateActionBehaviour.cs b/src/Stateless/DeactivateActionBehaviour.cs index 6f2bd588..ff1ad4e5 100644 --- a/src/Stateless/DeactivateActionBehaviour.cs +++ b/src/Stateless/DeactivateActionBehaviour.cs @@ -5,64 +5,34 @@ namespace Stateless { public partial class StateMachine { - internal abstract class DeactivateActionBehaviour + internal class DeactivateActionBehaviour { readonly TState _state; + private readonly EventCallback _callback; - protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) + public DeactivateActionBehaviour(TState state, Action action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) { - _state = state; - Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + _callback = EventCallbackFactory.Create(action); } - internal Reflection.InvocationInfo Description { get; } - - public abstract void Execute(); - public abstract Task ExecuteAsync(); - - public class Sync : DeactivateActionBehaviour + public DeactivateActionBehaviour(TState state, Func action, Reflection.InvocationInfo actionDescription) + : this(state, actionDescription) { - 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; - } + _callback = EventCallbackFactory.Create(action); } - public class Async : DeactivateActionBehaviour + protected DeactivateActionBehaviour(TState state, Reflection.InvocationInfo actionDescription) { - readonly Func _action; - - public Async(TState state, Func action, Reflection.InvocationInfo actionDescription) - : base(state, actionDescription) - { - _action = action; - } + _state = state; + Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + } - public override void Execute() - { - throw new InvalidOperationException( - $"Cannot execute asynchronous action specified in OnDeactivateAsync for '{_state}' state. " + - "Use asynchronous version of Deactivate [DeactivateAsync]"); - } + internal Reflection.InvocationInfo Description { get; } - public override Task ExecuteAsync() - { - return _action(); - } + public virtual Task ExecuteAsync() + { + return _callback.InvokeAsync(); } } } diff --git a/src/Stateless/EntryActionBehaviour.cs b/src/Stateless/EntryActionBehaviour.cs index 2289fc99..2a4c843c 100644 --- a/src/Stateless/EntryActionBehaviour.cs +++ b/src/Stateless/EntryActionBehaviour.cs @@ -5,8 +5,22 @@ namespace Stateless { public partial class StateMachine { - internal abstract class EntryActionBehavior + internal class EntryActionBehavior { + private readonly EventCallback _callback; + + public EntryActionBehavior(Action action, Reflection.InvocationInfo description) + : this(description) + { + _callback = EventCallbackFactory.Create(action); + } + + public EntryActionBehavior(Func action, Reflection.InvocationInfo description) + : this(description) + { + _callback = EventCallbackFactory.Create(action); + } + protected EntryActionBehavior(Reflection.InvocationInfo description) { Description = description; @@ -14,72 +28,33 @@ 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 ExecuteAsync(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, Action action, Reflection.InvocationInfo description) : base(action, description) { Trigger = trigger; } - public override void Execute(Transition transition, object[] args) + public From(TTriggerType trigger, Func action, Reflection.InvocationInfo description) + : base(action, description) { - if (transition.Trigger.Equals(Trigger)) - base.Execute(transition, args); + Trigger = trigger; } public override Task ExecuteAsync(Transition transition, object[] args) { - Execute(transition, args); - return TaskResult.Done; - } - } - - public class Async : EntryActionBehavior - { - readonly Func _action; - - 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]"); - } + if (transition.Trigger.Equals(Trigger)) + return base.ExecuteAsync(transition, args); - public override Task ExecuteAsync(Transition transition, object[] args) - { - return _action(transition, args); + return TaskResult.Done; } } } diff --git a/src/Stateless/EventCallback.cs b/src/Stateless/EventCallback.cs new file mode 100644 index 00000000..28ec9b26 --- /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 TaskResult.Done; + + 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 TaskResult.Done; + + default: + try + { + return Delegate.DynamicInvoke(args) as Task ?? TaskResult.Done; + } + 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 TaskResult.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 TaskResult.Done; + + 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 TaskResult.Done; + + 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 TaskResult.Done; + + 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 TaskResult.Done; + + 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/src/Stateless/ExitActionBehaviour.cs b/src/Stateless/ExitActionBehaviour.cs index 71f58764..ec69adde 100644 --- a/src/Stateless/ExitActionBehaviour.cs +++ b/src/Stateless/ExitActionBehaviour.cs @@ -5,59 +5,32 @@ 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; - protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) + public ExitActionBehavior(Action action, Reflection.InvocationInfo actionDescription) + : this(actionDescription) { - Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + _callback = EventCallbackFactory.Create(action); } - internal Reflection.InvocationInfo Description { get; } - - public class Sync : ExitActionBehavior + public ExitActionBehavior(Func action, Reflection.InvocationInfo actionDescription) + : this(actionDescription) { - 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; - } + _callback = EventCallbackFactory.Create(action); } - public class Async : ExitActionBehavior + protected ExitActionBehavior(Reflection.InvocationInfo actionDescription) { - readonly Func _action; - - public Async(Func action, Reflection.InvocationInfo actionDescription) : base(actionDescription) - { - _action = action; - } + Description = actionDescription ?? throw new ArgumentNullException(nameof(actionDescription)); + } - 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]"); - } + internal Reflection.InvocationInfo Description { get; } - public override Task ExecuteAsync(Transition transition) - { - return _action(transition); - } + public virtual Task ExecuteAsync(Transition transition) + { + return _callback.InvokeAsync(transition); } } } diff --git a/src/Stateless/InternalTriggerBehaviour.cs b/src/Stateless/InternalTriggerBehaviour.cs index fe6dd927..8f8847d3 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, Action internalAction, string guardDescription = null) + : this(trigger, new TransitionGuard(guard, guardDescription)) { + _callback = EventCallbackFactory.Create(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, Func internalAction, string guardDescription = null) + : this(trigger, new TransitionGuard(guard, guardDescription)) { - destination = source; - return false; + _callback = EventCallbackFactory.Create(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 ExecuteAsync(Transition transition, object[] args) + { + return _callback.InvokeAsync(transition, args); + } } } } \ No newline at end of file diff --git a/src/Stateless/OnTransitionedEvent.cs b/src/Stateless/OnTransitionedEvent.cs index c8dbbc2e..1d9f59eb 100644 --- a/src/Stateless/OnTransitionedEvent.cs +++ b/src/Stateless/OnTransitionedEvent.cs @@ -8,37 +8,27 @@ 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]"); - - _onTransitioned?.Invoke(transition); + _callbacks = new List>(); } -#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/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/StateConfiguration.Async.cs b/src/Stateless/StateConfiguration.Async.cs index 6a89fbc3..833c1800 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 @@ -20,7 +18,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, (t, args) => entryAction(t))); return this; } @@ -35,7 +33,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, (t, args) => internalAction())); return this; } @@ -51,7 +49,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, (t, args) => internalAction(t))); return this; } @@ -68,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.Async(trigger.Trigger, guard, (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; } @@ -86,7 +84,7 @@ 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, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), t))); return this; @@ -107,7 +105,7 @@ 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, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), ParameterConversion.Unpack(args, 2), t))); @@ -288,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. @@ -462,4 +481,3 @@ public StateConfiguration OnExitAsync(Func exitAction, string } } } -#endif diff --git a/src/Stateless/StateConfiguration.cs b/src/Stateless/StateConfiguration.cs index 3ddd78d3..11d6ef59 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, (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, (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, (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), (t, args) => internalAction(ParameterConversion.Unpack(args, 0), t), guardDescription)); return this; } @@ -187,7 +187,7 @@ 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, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), t), guardDescription)); @@ -209,7 +209,7 @@ 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( @@ -236,7 +236,7 @@ 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, (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), ParameterConversion.Unpack(args, 2), t), @@ -260,7 +260,7 @@ 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), (t, args) => internalAction( ParameterConversion.Unpack(args, 0), ParameterConversion.Unpack(args, 1), ParameterConversion.Unpack(args, 2), t), @@ -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..db949b64 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; @@ -11,7 +9,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 +20,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() @@ -45,6 +43,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. @@ -115,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 ;-) @@ -134,9 +149,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 +162,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); @@ -158,6 +175,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) @@ -170,7 +193,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.ExecuteAsync(representativeState.UnderlyingState, trigger, result?.UnmetGuardConditions).ConfigureAwait(false); return; } @@ -185,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)): @@ -193,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; } @@ -201,11 +224,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.ExecuteAsync(transition, args).ConfigureAwait(false); break; } default: @@ -216,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)) @@ -256,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)) { @@ -287,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; @@ -302,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.Async((s, t, c) => unhandledTriggerAction(s, t)); + _unhandledTriggerAction = new UnhandledTriggerAction((s, t, c) => unhandledTriggerAction(s, t)); } /// @@ -313,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.Async(unhandledTriggerAction); + _unhandledTriggerAction = new UnhandledTriggerAction(unhandledTriggerAction); } /// @@ -342,5 +361,3 @@ public void OnTransitionCompletedAsync(Func onTransitionAction } } } - -#endif diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 803ed7ba..77411ada 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(DefaultUnhandledTriggerAction); _onTransitionedEvent = new OnTransitionedEvent(); _onTransitionCompletedEvent = new OnTransitionedEvent(); } @@ -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]); + 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)); - InternalFire(trigger.Trigger, args); + 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)); - InternalFire(trigger.Trigger, arg0); + 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)); - InternalFire(trigger.Trigger, arg0, arg1); + 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)); - InternalFire(trigger.Trigger, arg0, arg1, arg2); + FireAsync(trigger, arg0, arg1, arg2).GetAwaiter().GetResult(); } /// @@ -298,8 +294,7 @@ public void Fire(TriggerWithParameters /// public void Activate() { - var representativeState = GetRepresentation(State); - representativeState.Activate(); + ActivateAsync().GetAwaiter().GetResult(); } /// @@ -309,201 +304,7 @@ public void Activate() /// public void Deactivate() { - var representativeState = GetRepresentation(State); - representativeState.Deactivate(); - } - - /// - /// 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: - InternalFireOne(trigger, args); - break; - case FiringMode.Queued: - InternalFireQueued(trigger, args); - break; - default: - // If something is completely messed up we let the user know ;-) - throw new InvalidOperationException("The firing mode has not been configured!"); - } - } - - /// - /// 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(); - InternalFireOne(queuedEvent.Trigger, queuedEvent.Args); - } - } - finally - { - _firing = false; - } - } - - /// - /// 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); - 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); - 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); - - 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; - } - default: - throw new InvalidOperationException("State machine configuration incorrect, no handler for trigger."); - } - } - - private void HandleReentryTrigger(object[] args, StateRepresentation representativeState, Transition transition) - { - StateRepresentation representation; - transition = representativeState.Exit(transition); - 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); - - _onTransitionedEvent.Invoke(transition); - representation = EnterState(newRepresentation, transition, args); - _onTransitionCompletedEvent.Invoke(transition); - - } - else - { - _onTransitionedEvent.Invoke(transition); - representation = EnterState(newRepresentation, transition, args); - _onTransitionCompletedEvent.Invoke(transition); - } - State = representation.UnderlyingState; - } - - private void HandleTransitioningTrigger( object[] args, StateRepresentation representativeState, Transition transition) - { - transition = representativeState.Exit(transition); - - State = transition.Destination; - var newRepresentation = GetRepresentation(transition.Destination); - - //Alert all listeners of state transition - _onTransitionedEvent.Invoke(transition); - var representation = EnterState(newRepresentation, transition, args); - - // 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)); - } - - private StateRepresentation EnterState(StateRepresentation representation, Transition transition, object [] args) - { - // Enter the new state - representation.Enter(transition, args); - - 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; + DeactivateAsync().GetAwaiter().GetResult(); } /// @@ -514,7 +315,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((s, t, c) => unhandledTriggerAction(s, t)); } /// @@ -525,7 +326,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(unhandledTriggerAction); } /// diff --git a/src/Stateless/StateRepresentation.Async.cs b/src/Stateless/StateRepresentation.Async.cs index 8041e154..27e6b306 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; @@ -11,12 +9,12 @@ 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, action, activateActionDescription)); } public void AddDeactivateAction(Func action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour.Async(_state, action, deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, action, deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Func action, Reflection.InvocationInfo entryActionDescription) @@ -24,27 +22,23 @@ 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, + action, + entryActionDescription)); } public void AddEntryAction(Func action, Reflection.InvocationInfo entryActionDescription) { EntryActions.Add( - new EntryActionBehavior.Async( + new EntryActionBehavior( action, entryActionDescription)); } public void AddExitAction(Func action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior.Async(action, exitActionDescription)); + ExitActions.Add(new ExitActionBehavior(action, exitActionDescription)); } public async Task ActivateAsync() @@ -90,7 +84,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) @@ -137,5 +131,3 @@ async Task ExecuteExitActionsAsync(Transition transition) } } } - -#endif diff --git a/src/Stateless/StateRepresentation.cs b/src/Stateless/StateRepresentation.cs index c08f797f..64ef0890 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,140 +117,29 @@ 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, action, activateActionDescription)); } public void AddDeactivateAction(Action action, Reflection.InvocationInfo deactivateActionDescription) { - DeactivateActions.Add(new DeactivateActionBehaviour.Sync(_state, action, deactivateActionDescription)); + DeactivateActions.Add(new DeactivateActionBehaviour(_state, action, deactivateActionDescription)); } public void AddEntryAction(TTrigger trigger, Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior.SyncFrom(trigger, action, entryActionDescription)); + EntryActions.Add(new EntryActionBehavior.From(trigger, action, entryActionDescription)); } public void AddEntryAction(Action action, Reflection.InvocationInfo entryActionDescription) { - EntryActions.Add(new EntryActionBehavior.Sync(action, entryActionDescription)); + EntryActions.Add(new EntryActionBehavior(action, entryActionDescription)); } public void AddExitAction(Action action, Reflection.InvocationInfo exitActionDescription) { - ExitActions.Add(new ExitActionBehavior.Sync(action, exitActionDescription)); - } - - public void Activate() - { - if (_superstate != null) - _superstate.Activate(); - - ExecuteActivationActions(); - } - - public void Deactivate() - { - ExecuteDeactivationActions(); - - if (_superstate != null) - _superstate.Deactivate(); - } - - void ExecuteActivationActions() - { - foreach (var action in ActivateActions) - action.Execute(); - } - - void ExecuteDeactivationActions() - { - foreach (var action in DeactivateActions) - action.Execute(); - } - - 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); - } - } - - 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; - } - - void ExecuteEntryActions(Transition transition, object[] entryArgs) - { - foreach (var action in EntryActions) - action.Execute(transition, entryArgs); - } - - void ExecuteExitActions(Transition transition) - { - foreach (var action in ExitActions) - action.Execute(transition); + ExitActions.Add(new ExitActionBehavior(action, exitActionDescription)); } - internal void InternalAction(Transition transition, object[] args) - { - InternalTriggerBehaviour.Sync 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 - 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; - 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.InternalAction(transition, args); - } public void AddTriggerBehaviour(TriggerBehaviour triggerBehaviour) { if (!TriggerBehaviours.TryGetValue(triggerBehaviour.Trigger, out ICollection allowed)) @@ -308,13 +197,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/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(); diff --git a/src/Stateless/UnhandledTriggerAction.cs b/src/Stateless/UnhandledTriggerAction.cs index 52a40d90..609a37bb 100644 --- a/src/Stateless/UnhandledTriggerAction.cs +++ b/src/Stateless/UnhandledTriggerAction.cs @@ -6,52 +6,23 @@ 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(Action> 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 = EventCallbackFactory.Create(action); } - internal class Async : UnhandledTriggerAction + public UnhandledTriggerAction(Func, Task> action = null) { - 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]"); - } + _callback = EventCallbackFactory.Create(action); + } - public override Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) - { - return _action(state, trigger, unmetGuards); - } + public virtual Task ExecuteAsync(TState state, TTrigger trigger, ICollection unmetGuards) + { + return _callback.InvokeAsync(state, trigger, unmetGuards); } } } diff --git a/test/Stateless.Tests/AsyncActionsFixture.cs b/test/Stateless.Tests/AsyncActionsFixture.cs index 4e9b63ba..5ff7d53b 100644 --- a/test/Stateless.Tests/AsyncActionsFixture.cs +++ b/test/Stateless.Tests/AsyncActionsFixture.cs @@ -1,10 +1,9 @@ -#if TASKS - using System; using System.Threading.Tasks; using System.Collections.Generic; using Xunit; +using System.Linq; namespace Stateless.Tests { @@ -21,7 +20,7 @@ public void StateMutatorShouldBeCalledOnlyOnce() sm.FireAsync(Trigger.X); Assert.Equal(1, count); } - + [Fact] public async Task SuperStateShouldNotExitOnSubStateTransition_WhenUsingAsyncTriggers() { @@ -33,7 +32,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 +48,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 +72,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 +88,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 +100,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() { @@ -120,20 +119,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() { @@ -150,18 +135,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() { @@ -176,17 +149,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() { @@ -258,69 +220,77 @@ public async Task WillInvokeSyncOnTransitionCompletedIfRegisteredAlongWithAsyncA } [Fact] - public void WhenSyncFireAsyncOnTransitionedAction() + public async Task WillInvokeOnTransitionedInOrderRegardlessOfSyncOrAsync() { var sm = new StateMachine(State.A); sm.Configure(State.A) .Permit(Trigger.X, State.B); - sm.OnTransitionedAsync(_ => TaskResult.Done); + 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.Throws(() => sm.Fire(Trigger.X)); + Assert.Equal(Enumerable.Range(1, 3), tests); } [Fact] - public void WhenSyncFireAsyncOnTransitionCompletedAction() + public async Task WillInvokeOnTransitionCompletedInOrderRegardlessOfSyncOrAsync() { var sm = new StateMachine(State.A); sm.Configure(State.A) .Permit(Trigger.X, State.B); - sm.OnTransitionCompletedAsync(_ => TaskResult.Done); + var tests = new List(); + sm.OnTransitionCompleted(_ => tests.Add(1)); + sm.OnTransitionCompletedAsync(_ => Task.Run(() => tests.Add(2))); + sm.OnTransitionCompleted(_ => tests.Add(3)); - Assert.Throws(() => sm.Fire(Trigger.X)); + await sm.FireAsync(Trigger.X); + + Assert.Equal(Enumerable.Range(1, 3), tests); } [Fact] - public async Task CanInvokeOnUnhandledTriggerAsyncAction() + public async Task WillInvokeOnTransitionedNotInOrderAsyncInSync() { var sm = new StateMachine(State.A); sm.Configure(State.A) .Permit(Trigger.X, State.B); - var test = ""; - sm.OnUnhandledTriggerAsync((s, t, u) => Task.Run(() => test = "foo")); + 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.Z).ConfigureAwait(false); + await sm.FireAsync(Trigger.X); - Assert.Equal("foo", test); // Should await action + Assert.NotEqual(Enumerable.Range(1, 3), tests); } - [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() + public async Task CanInvokeOnUnhandledTriggerAsyncAction() { var sm = new StateMachine(State.A); sm.Configure(State.A) .Permit(Trigger.X, State.B); - sm.OnUnhandledTriggerAsync((s, t, u) => TaskResult.Done); + var test = ""; + sm.OnUnhandledTriggerAsync((s, t, u) => Task.Run(() => test = "foo")); + + await sm.FireAsync(Trigger.Z).ConfigureAwait(false); - Assert.Throws(() => sm.Fire(Trigger.Z)); + Assert.Equal("foo", test); // Should await action } [Fact] @@ -352,29 +322,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() - { - 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() { @@ -443,7 +390,7 @@ public async void IgnoredTriggerMustBeIgnoredAsync() // >>> The following statement should not throw a NullReferenceException await stateMachine.FireAsync(Trigger.X); } - catch (NullReferenceException ) + catch (NullReferenceException) { nullRefExcThrown = true; } @@ -474,5 +421,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 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); + } + } +} 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)); }