diff --git a/.gitattributes b/.gitattributes index 1ff0c42..b105bf7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -61,3 +61,6 @@ #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain + +appveyor.yml merge=ours +VersionInfo.cs merge=ours diff --git a/LiquidState.sln b/LiquidState.sln index 690c867..10beefa 100644 --- a/LiquidState.sln +++ b/LiquidState.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiquidState.Sample", "Liqui EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F654A99E-F9FF-418E-8A2D-6CAD0406788E}" ProjectSection(SolutionItems) = preProject + .gitattributes = .gitattributes + .gitignore = .gitignore appveyor.yml = appveyor.yml LiquidState\LiquidState.nuspec = LiquidState\LiquidState.nuspec README.md = README.md diff --git a/LiquidState/Machines/AsyncStateMachine.cs b/LiquidState/Awaitable/AsyncStateMachine.cs similarity index 95% rename from LiquidState/Machines/AsyncStateMachine.cs rename to LiquidState/Awaitable/AsyncStateMachine.cs index f629b54..bcd39f8 100644 --- a/LiquidState/Machines/AsyncStateMachine.cs +++ b/LiquidState/Awaitable/AsyncStateMachine.cs @@ -6,12 +6,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Threading; using System.Threading.Tasks; +using LiquidState.Awaitable.Core; using LiquidState.Common; -using LiquidState.Configuration; +using LiquidState.Core; +using LiquidState.Scaffold; -namespace LiquidState.Machines +namespace LiquidState.Awaitable { public class AsyncStateMachine : IAwaitableStateMachine { @@ -132,6 +133,7 @@ public async Task FireAsync(ParameterizedTrigger queueMonitor.Enter(); if (machine.Monitor.TryEnter()) { + // Try to execute inline if the process queue is empty. if (queueCount == 0) { queueMonitor.Exit(); @@ -161,6 +163,8 @@ public async Task FireAsync(ParameterizedTrigger if (flag) { + // Fast path was not taken. Queue up the delgates. + var tcs = new TaskCompletionSource(); actionsQueue = actionsQueue.Enqueue(async () => { @@ -191,6 +195,7 @@ public async Task FireAsync(TTrigger trigger) queueMonitor.Enter(); if (machine.Monitor.TryEnter()) { + // Try to execute inline if the process queue is empty. if (queueCount == 0) { queueMonitor.Exit(); @@ -218,8 +223,11 @@ public async Task FireAsync(TTrigger trigger) } } + if (flag) { + // Fast path was not taken. Queue up the delgates. + var tcs = new TaskCompletionSource(); actionsQueue = actionsQueue.Enqueue(async () => { diff --git a/LiquidState/Machines/AwaitableStateMachine.cs b/LiquidState/Awaitable/AwaitableStateMachine.cs similarity index 99% rename from LiquidState/Machines/AwaitableStateMachine.cs rename to LiquidState/Awaitable/AwaitableStateMachine.cs index 8bbe39a..145ad60 100644 --- a/LiquidState/Machines/AwaitableStateMachine.cs +++ b/LiquidState/Awaitable/AwaitableStateMachine.cs @@ -8,11 +8,13 @@ using System.Diagnostics.Contracts; using System.Threading; using System.Threading.Tasks; +using LiquidState.Awaitable.Core; using LiquidState.Common; using LiquidState.Configuration; +using LiquidState.Core; using LiquidState.Representations; -namespace LiquidState.Machines +namespace LiquidState.Awaitable { public class AwaitableStateMachine : IAwaitableStateMachine { diff --git a/LiquidState/Machines/AwaitableStateMachineWithScheduler.cs b/LiquidState/Awaitable/AwaitableStateMachineWithScheduler.cs similarity index 97% rename from LiquidState/Machines/AwaitableStateMachineWithScheduler.cs rename to LiquidState/Awaitable/AwaitableStateMachineWithScheduler.cs index effb6c1..69a0c46 100644 --- a/LiquidState/Machines/AwaitableStateMachineWithScheduler.cs +++ b/LiquidState/Awaitable/AwaitableStateMachineWithScheduler.cs @@ -8,10 +8,10 @@ using System.Diagnostics.Contracts; using System.Threading; using System.Threading.Tasks; -using LiquidState.Common; -using LiquidState.Configuration; +using LiquidState.Awaitable.Core; +using LiquidState.Core; -namespace LiquidState.Machines +namespace LiquidState.Awaitable { public class AwaitableStateMachineWithScheduler : IAwaitableStateMachine { diff --git a/LiquidState/Awaitable/Core/AbstractStateMachine.cs b/LiquidState/Awaitable/Core/AbstractStateMachine.cs new file mode 100644 index 0000000..3fbc984 --- /dev/null +++ b/LiquidState/Awaitable/Core/AbstractStateMachine.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using LiquidState.Core; +using LiquidState.Synchronous.Core; + +namespace LiquidState.Awaitable.Core +{ + public abstract class AbstractStateMachine : AbstractStateMachineCore + { + protected internal StateRepresentation CurrentStateRepresentation; + protected internal Dictionary> Representations; + + public abstract void Fire(ParameterizedTrigger parameterizedTrigger, + TArgument argument); + + public abstract void Fire(TTrigger trigger); + public abstract void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default); + + public override TState CurrentState + { + get { return CurrentStateRepresentation.State; } + } + + public override IEnumerable CurrentPermittedTriggers + { + get + { + foreach (var triggerRepresentation in CurrentStateRepresentation.Triggers) + { + yield return triggerRepresentation.Trigger; + } + } + } + } +} \ No newline at end of file diff --git a/LiquidState/Configuration/AwaitableStateConfigurationHelper.cs b/LiquidState/Awaitable/Core/AwaitableStateConfigurationHelper.cs similarity index 95% rename from LiquidState/Configuration/AwaitableStateConfigurationHelper.cs rename to LiquidState/Awaitable/Core/AwaitableStateConfigurationHelper.cs index bb50dd5..69db8ee 100644 --- a/LiquidState/Configuration/AwaitableStateConfigurationHelper.cs +++ b/LiquidState/Awaitable/Core/AwaitableStateConfigurationHelper.cs @@ -8,6 +8,7 @@ using System.Diagnostics.Contracts; using System.Threading.Tasks; using LiquidState.Common; +using LiquidState.Core; using LiquidState.Representations; namespace LiquidState.Configuration @@ -340,7 +341,7 @@ internal static AwaitableStateRepresentation FindOrCreateState return rep; } - internal static AwaitableTriggerRepresentation FindOrCreateTriggerConfig(TTrigger trigger, + internal static AwaitableTriggerRepresentation FindOrCreateTriggerRepresentation(TTrigger trigger, AwaitableStateRepresentation stateRepresentation) { Contract.Requires(stateRepresentation != null); @@ -369,7 +370,7 @@ private AwaitableStateConfigurationHelper PermitInternalSync(F Contract.Requires(trigger != null); Contract.Requires(resultingState != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAction; @@ -387,7 +388,7 @@ private AwaitableStateConfigurationHelper PermitInternalSync PermitInternalAsync( Contract.Requires(trigger != null); Contract.Requires(resultingState != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -424,7 +425,7 @@ private AwaitableStateConfigurationHelper PermitInternalAsync< Contract.Assume(trigger.Trigger != null); - var rep = FindOrCreateTriggerConfig(trigger.Trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger.Trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -442,7 +443,7 @@ private AwaitableStateConfigurationHelper PermitInternalTrigge Contract.Requires(trigger != null); Contract.Requires(resultingState != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -462,7 +463,7 @@ private AwaitableStateConfigurationHelper PermitInternalTrigge Contract.Assume(trigger.Trigger != null); - var rep = FindOrCreateTriggerConfig(trigger.Trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger.Trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -480,7 +481,7 @@ private AwaitableStateConfigurationHelper PermitInternalPredic Contract.Requires(trigger != null); Contract.Requires(resultingState != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -500,7 +501,7 @@ private AwaitableStateConfigurationHelper PermitInternalPredic Contract.Assume(trigger.Trigger != null); - var rep = FindOrCreateTriggerConfig(trigger.Trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger.Trigger, currentStateRepresentation); rep.NextStateRepresentation = FindOrCreateStateRepresentation(resultingState, config); rep.OnTriggerAction = onEntryAsyncAction; @@ -515,7 +516,7 @@ private AwaitableStateConfigurationHelper IgnoreInternal(Func< { Contract.Requires(trigger != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = null; rep.ConditionalTriggerPredicate = predicate; @@ -529,7 +530,7 @@ private AwaitableStateConfigurationHelper IgnoreInternalPredic { Contract.Requires(trigger != null); - var rep = FindOrCreateTriggerConfig(trigger, currentStateRepresentation); + var rep = FindOrCreateTriggerRepresentation(trigger, currentStateRepresentation); rep.NextStateRepresentation = null; rep.ConditionalTriggerPredicate = predicate; diff --git a/LiquidState/Configuration/AwaitableStateMachineConfiguration.cs b/LiquidState/Awaitable/Core/AwaitableStateMachineConfiguration.cs similarity index 95% rename from LiquidState/Configuration/AwaitableStateMachineConfiguration.cs rename to LiquidState/Awaitable/Core/AwaitableStateMachineConfiguration.cs index 87e6e45..72e7247 100644 --- a/LiquidState/Configuration/AwaitableStateMachineConfiguration.cs +++ b/LiquidState/Awaitable/Core/AwaitableStateMachineConfiguration.cs @@ -7,10 +7,11 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; -using LiquidState.Common; +using LiquidState.Configuration; +using LiquidState.Core; using LiquidState.Representations; -namespace LiquidState.Configuration +namespace LiquidState.Awaitable.Core { public class AwaitableStateMachineConfiguration { diff --git a/LiquidState/Representations/AwaitableStateRepresentation.cs b/LiquidState/Awaitable/Core/AwaitableStateRepresentation.cs similarity index 100% rename from LiquidState/Representations/AwaitableStateRepresentation.cs rename to LiquidState/Awaitable/Core/AwaitableStateRepresentation.cs diff --git a/LiquidState/Machines/IAwaitableStateMachine.cs b/LiquidState/Awaitable/Core/IAwaitableStateMachine.cs similarity index 97% rename from LiquidState/Machines/IAwaitableStateMachine.cs rename to LiquidState/Awaitable/Core/IAwaitableStateMachine.cs index d21ef36..4f1e31d 100644 --- a/LiquidState/Machines/IAwaitableStateMachine.cs +++ b/LiquidState/Awaitable/Core/IAwaitableStateMachine.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Threading.Tasks; -using LiquidState.Common; +using LiquidState.Core; -namespace LiquidState.Machines +namespace LiquidState.Awaitable.Core { [ContractClass(typeof (AwaitableStateMachineContract<,>))] public interface IAwaitableStateMachine diff --git a/LiquidState/Core/AbstractStateMachineCore.cs b/LiquidState/Core/AbstractStateMachineCore.cs new file mode 100644 index 0000000..7ca2d3b --- /dev/null +++ b/LiquidState/Core/AbstractStateMachineCore.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using LiquidState.Common; + +namespace LiquidState.Core +{ + public abstract class AbstractStateMachineCore : IStateMachineCore + { + public event Action> UnhandledTrigger; + public event Action> InvalidState; + public event Action> TransitionStarted; + public event Action> TransitionExecuted; + + public void Pause() + { + Interlocked.Exchange(ref isEnabled, 0); + } + + public void Resume() + { + Interlocked.Exchange(ref isEnabled, 1); + } + + private int isEnabled = 1; + + public abstract IEnumerable CurrentPermittedTriggers { get; } + public abstract TState CurrentState { get; } + + public bool IsEnabled + { + get { return Interlocked.CompareExchange(ref isEnabled, -1, -1) == 1; } + } + + public void RaiseInvalidTrigger(TTrigger trigger) + { + var handler = UnhandledTrigger; + if (handler != null) handler.Invoke(new TriggerStateEventArgs(CurrentState, trigger)); + } + + public void RaiseInvalidState(TState targetState) + { + var handler = InvalidState; + if (handler != null) + handler.Invoke(new TransitionEventArgs(CurrentState, targetState)); + } + + public void RaiseInvalidState(TState targetState, TTrigger trigger) + { + var handler = InvalidState; + if (handler != null) + handler.Invoke(new TransitionEventArgs(CurrentState, targetState, trigger)); + } + + public void RaiseTransitionStarted(TState targetState) + { + var handler = TransitionStarted; + if (handler != null) + handler.Invoke(new TransitionEventArgs(CurrentState, targetState)); + } + + + public void RaiseTransitionStarted(TState targetState, TTrigger trigger) + { + var handler = TransitionStarted; + if (handler != null) + handler.Invoke(new TransitionEventArgs(CurrentState, targetState, trigger)); + } + + public void RaiseTransitionExecuted(TState pastState) + { + var handler = TransitionExecuted; + if (handler != null) + handler.Invoke(new TransitionExecutedEventArgs(CurrentState, pastState)); + } + + + public void RaiseTransitionExecuted(TState pastState, TTrigger trigger) + { + var handler = TransitionExecuted; + if (handler != null) + handler.Invoke(new TransitionExecutedEventArgs(CurrentState, pastState, trigger)); + } + } +} \ No newline at end of file diff --git a/LiquidState/Common/Exceptions.cs b/LiquidState/Core/Exceptions.cs similarity index 64% rename from LiquidState/Common/Exceptions.cs rename to LiquidState/Core/Exceptions.cs index ec3dec5..0425f82 100644 --- a/LiquidState/Common/Exceptions.cs +++ b/LiquidState/Core/Exceptions.cs @@ -5,8 +5,30 @@ using System; -namespace LiquidState.Common +namespace LiquidState.Core { + public class InvalidStateException : Exception + { + public InvalidStateException(TState state) + : base("Invalid state: " + state.ToString()) + { + InvalidState = state; + } + + public InvalidStateException(TState state, string message) + : base(message) + { + InvalidState = state; + } + + public TState InvalidState { get; private set; } + + public static void Throw(TExceptionState state) + { + throw new InvalidStateException(state); + } + } + public class InvalidTriggerException : Exception { public InvalidTriggerException(TTrigger trigger, TState state) @@ -26,7 +48,13 @@ public InvalidTriggerException(TTrigger trigger, TState state, string message) : public TState CurrentState { get; private set; } public static void Throw( - TExceptionTrigger trigger, TExceptionState state) + TriggerStateEventArgs eventArgs) + { + throw new InvalidTriggerException(eventArgs.Trigger, + eventArgs.CurrentState); + } + + public static void Throw(TExceptionTrigger trigger, TExceptionState state) { throw new InvalidTriggerException(trigger, state); } diff --git a/LiquidState/Core/IStateMachineCore.cs b/LiquidState/Core/IStateMachineCore.cs new file mode 100644 index 0000000..ba72088 --- /dev/null +++ b/LiquidState/Core/IStateMachineCore.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using LiquidState.Common; + +namespace LiquidState.Core +{ + public interface IStateMachineCore + { + IEnumerable CurrentPermittedTriggers { get; } + TState CurrentState { get; } + bool IsEnabled { get; } + void Pause(); + void Resume(); + + event Action> UnhandledTrigger; + event Action> InvalidState; + event Action> TransitionStarted; + event Action> TransitionExecuted; + } +} \ No newline at end of file diff --git a/LiquidState/Common/ParameterizedTrigger.cs b/LiquidState/Core/ParameterizedTrigger.cs similarity index 94% rename from LiquidState/Common/ParameterizedTrigger.cs rename to LiquidState/Core/ParameterizedTrigger.cs index a7f2a65..8192d6c 100644 --- a/LiquidState/Common/ParameterizedTrigger.cs +++ b/LiquidState/Core/ParameterizedTrigger.cs @@ -5,7 +5,7 @@ using System.Diagnostics.Contracts; -namespace LiquidState.Common +namespace LiquidState.Core { public class ParameterizedTrigger { diff --git a/LiquidState/Common/StateTransitionOptions.cs b/LiquidState/Core/StateTransitionOptions.cs similarity index 92% rename from LiquidState/Common/StateTransitionOptions.cs rename to LiquidState/Core/StateTransitionOptions.cs index c3b3491..bb607b6 100644 --- a/LiquidState/Common/StateTransitionOptions.cs +++ b/LiquidState/Core/StateTransitionOptions.cs @@ -3,7 +3,7 @@ // Project: LiquidState // License: http://www.apache.org/licenses/LICENSE-2.0 -namespace LiquidState.Common +namespace LiquidState.Core { public enum StateTransitionOption : byte { diff --git a/LiquidState/Core/TransitionEventArgs.cs b/LiquidState/Core/TransitionEventArgs.cs new file mode 100644 index 0000000..1c89e2e --- /dev/null +++ b/LiquidState/Core/TransitionEventArgs.cs @@ -0,0 +1,64 @@ +using System; + +namespace LiquidState.Core +{ + public class TriggerStateEventArgs : EventArgs + { + public TTrigger Trigger { get; set; } + public TState CurrentState { get; set; } + + public TriggerStateEventArgs(TState currentState, TTrigger trigger) + { + Trigger = trigger; + CurrentState = currentState; + } + } + + public class OptionalTriggerStateEventArgs : TriggerStateEventArgs + { + public bool HasTrigger { get; private set; } + + protected OptionalTriggerStateEventArgs(TState currentState, TTrigger trigger, bool hasTrigger = true) : base(currentState, trigger) + { + HasTrigger = hasTrigger; + } + + protected OptionalTriggerStateEventArgs(TState currentState) : base(currentState, default(TTrigger)) + { + HasTrigger = false; + } + } + + + public class TransitionEventArgs : OptionalTriggerStateEventArgs + { + public TState TargetState { get; set; } + + public TransitionEventArgs(TState currentState, TState targetState, TTrigger trigger, bool hasTrigger = true) : base(currentState, trigger, hasTrigger) + { + TargetState = targetState; + } + + public TransitionEventArgs(TState currentState, TState targetState) : base(currentState) + { + TargetState = targetState; + } + } + + public class TransitionExecutedEventArgs : OptionalTriggerStateEventArgs + { + public TState PastState { get; set; } + + public TransitionExecutedEventArgs(TState currentState, TState pastState, TTrigger trigger, bool hasTrigger = true) + : base(currentState, trigger, hasTrigger) + { + PastState = pastState; + } + + public TransitionExecutedEventArgs(TState currentState, TState pastState) + : base(currentState) + { + PastState = pastState; + } + } +} diff --git a/LiquidState/LiquidState.csproj b/LiquidState/LiquidState.csproj index 7607bec..ac23b87 100644 --- a/LiquidState/LiquidState.csproj +++ b/LiquidState/LiquidState.csproj @@ -124,26 +124,33 @@ Properties\VersionInfo.cs - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + diff --git a/LiquidState/Machines/BlockingStateMachine.cs b/LiquidState/Machines/BlockingStateMachine.cs deleted file mode 100644 index aa8cbf7..0000000 --- a/LiquidState/Machines/BlockingStateMachine.cs +++ /dev/null @@ -1,279 +0,0 @@ -// Author: Prasanna V. Loganathar -// Created: 2:12 AM 27-11-2014 -// Project: LiquidState -// License: http://www.apache.org/licenses/LICENSE-2.0 - -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Threading; -using LiquidState.Common; -using LiquidState.Configuration; -using LiquidState.Representations; - -namespace LiquidState.Machines -{ - public class BlockingStateMachine : IStateMachine - { - internal StateRepresentation CurrentStateRepresentation; - private readonly Dictionary> configDictionary; - private readonly object syncRoot = new object(); - private int isEnabled = 1; - - internal BlockingStateMachine(TState initialState, StateMachineConfiguration configuration) - { - Contract.Requires(configuration != null); - Contract.Requires(initialState != null); - - CurrentStateRepresentation = configuration.GetInitialStateRepresentation(initialState); - if (CurrentStateRepresentation == null) - { - throw new InvalidOperationException("StateMachine has an unreachable state"); - } - - configDictionary = configuration.Config; - } - - public event Action UnhandledTriggerExecuted; - public event Action StateChanged; - - public void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default) - { - lock (syncRoot) - { - if (!IsEnabled) return; - StateRepresentation rep; - if (configDictionary.TryGetValue(state, out rep)) - { - if ((option & StateTransitionOption.CurrentStateExitTransition) == - StateTransitionOption.CurrentStateExitTransition) - { - ExecuteAction(CurrentStateRepresentation.OnExitAction); - } - if ((option & StateTransitionOption.NewStateEntryTransition) == - StateTransitionOption.NewStateEntryTransition) - { - ExecuteAction(rep.OnEntryAction); - } - - CurrentStateRepresentation = rep; - } - else - { - throw new InvalidOperationException("Invalid state: " + state.ToString()); - } - } - } - - public bool CanHandleTrigger(TTrigger trigger) - { - foreach (var current in CurrentStateRepresentation.Triggers) - { - if (current.Trigger.Equals(trigger)) - { - var predicate = current.ConditionalTriggerPredicate; - return predicate == null || predicate(); - } - } - return false; - } - - public bool CanTransitionTo(TState state) - { - foreach (var current in CurrentStateRepresentation.Triggers) - { - if (current.NextStateRepresentation.State.Equals(state)) - return true; - } - - return false; - } - - public void Pause() - { - Interlocked.Exchange(ref isEnabled, 0); - } - - public void Resume() - { - Interlocked.Exchange(ref isEnabled, 1); - } - - public void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument) - { - lock (syncRoot) - { - if (!IsEnabled) return; - var trigger = parameterizedTrigger.Trigger; - var triggerRep = StateConfigurationHelper.FindTriggerRepresentation(trigger, - CurrentStateRepresentation); - - if (triggerRep == null) - { - HandleInvalidTrigger(trigger); - return; - } - - var previousState = CurrentState; - - var predicate = triggerRep.ConditionalTriggerPredicate; - if (predicate != null) - { - if (!predicate()) - { - HandleInvalidTrigger(trigger); - return; - } - } - - // Handle ignored trigger - - if (triggerRep.NextStateRepresentation == null) - { - return; - } - - // Catch invalid paramters before execution. - - Action triggerAction = null; - try - { - triggerAction = (Action) triggerRep.OnTriggerAction; - } - catch (InvalidCastException) - { - InvalidTriggerParameterException.Throw(trigger); - return; - } - - - // Current exit - var currentExit = CurrentStateRepresentation.OnExitAction; - ExecuteAction(currentExit); - - // Trigger entry - if (triggerAction != null) triggerAction.Invoke(argument); - - - var nextStateRep = triggerRep.NextStateRepresentation; - - // Next entry - var nextEntry = nextStateRep.OnEntryAction; - ExecuteAction(nextEntry); - - CurrentStateRepresentation = nextStateRep; - - // Raise state change event - var stateChangedHandler = StateChanged; - if (stateChangedHandler != null) - stateChangedHandler.Invoke(previousState, CurrentStateRepresentation.State); - } - } - - public void Fire(TTrigger trigger) - { - lock (syncRoot) - { - if (!IsEnabled) return; - var triggerRep = StateConfigurationHelper.FindTriggerRepresentation(trigger, - CurrentStateRepresentation); - - if (triggerRep == null) - { - HandleInvalidTrigger(trigger); - return; - } - - var previousState = CurrentState; - - var predicate = triggerRep.ConditionalTriggerPredicate; - if (predicate != null) - { - if (!predicate()) - { - HandleInvalidTrigger(trigger); - return; - } - } - - // Handle ignored trigger - - if (triggerRep.NextStateRepresentation == null) - { - return; - } - - // Catch invalid paramters before execution. - - Action triggerAction = null; - try - { - triggerAction = (Action) triggerRep.OnTriggerAction; - } - catch (InvalidCastException) - { - InvalidTriggerParameterException.Throw(trigger); - return; - } - - - // Current exit - var currentExit = CurrentStateRepresentation.OnExitAction; - ExecuteAction(currentExit); - - // Trigger entry - ExecuteAction(triggerAction); - - var nextStateRep = triggerRep.NextStateRepresentation; - - // Next entry - var nextEntry = nextStateRep.OnEntryAction; - ExecuteAction(nextEntry); - - CurrentStateRepresentation = nextStateRep; - - // Raise state change event - var stateChangedHandler = StateChanged; - if (stateChangedHandler != null) - stateChangedHandler.Invoke(previousState, CurrentStateRepresentation.State); - } - } - - public bool IsInTransition - { - get { return Monitor.IsEntered(syncRoot); } - } - - public TState CurrentState - { - get { return CurrentStateRepresentation.State; } - } - - public IEnumerable CurrentPermittedTriggers - { - get - { - foreach (var triggerRepresentation in CurrentStateRepresentation.Triggers) - { - yield return triggerRepresentation.Trigger; - } - } - } - - public bool IsEnabled - { - get { return Interlocked.CompareExchange(ref isEnabled, -1, -1) == 1; } - } - - private void ExecuteAction(Action action) - { - if (action != null) action.Invoke(); - } - - private void HandleInvalidTrigger(TTrigger trigger) - { - var handler = UnhandledTriggerExecuted; - if (handler != null) handler.Invoke(trigger, CurrentStateRepresentation.State); - } - } -} diff --git a/LiquidState/Machines/IStateMachine.cs b/LiquidState/Machines/IStateMachine.cs deleted file mode 100644 index 2fc5a52..0000000 --- a/LiquidState/Machines/IStateMachine.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Author: Prasanna V. Loganathar -// Created: 1:33 AM 05-12-2014 -// Project: LiquidState -// License: http://www.apache.org/licenses/LICENSE-2.0 - -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using LiquidState.Common; - -namespace LiquidState.Machines -{ - [ContractClass(typeof (StateMachineContract<,>))] - public interface IStateMachine - { - IEnumerable CurrentPermittedTriggers { get; } - TState CurrentState { get; } - bool IsEnabled { get; } - bool IsInTransition { get; } - event Action StateChanged; - event Action UnhandledTriggerExecuted; - bool CanHandleTrigger(TTrigger trigger); - bool CanTransitionTo(TState state); - void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument); - void Fire(TTrigger trigger); - void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default); - void Pause(); - void Resume(); - } - - [ContractClassFor(typeof (IStateMachine<,>))] - public abstract class StateMachineContract : IStateMachine - { - public abstract event Action UnhandledTriggerExecuted; - public abstract event Action StateChanged; - public abstract bool CanHandleTrigger(U trigger); - public abstract bool CanTransitionTo(T state); - public abstract void MoveToState(T state, StateTransitionOption option = StateTransitionOption.Default); - public abstract void Pause(); - public abstract void Resume(); - - public void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument) - { - Contract.Requires(parameterizedTrigger != null); - } - - public abstract void Fire(U trigger); - public abstract T CurrentState { get; } - public abstract IEnumerable CurrentPermittedTriggers { get; } - public abstract bool IsEnabled { get; } - public abstract bool IsInTransition { get; } - } -} diff --git a/LiquidState/Machines/StateMachine.cs b/LiquidState/Machines/StateMachine.cs deleted file mode 100644 index c9ffdf0..0000000 --- a/LiquidState/Machines/StateMachine.cs +++ /dev/null @@ -1,315 +0,0 @@ -// Author: Prasanna V. Loganathar -// Created: 2:12 AM 27-11-2014 -// Project: LiquidState -// License: http://www.apache.org/licenses/LICENSE-2.0 - -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Threading; -using LiquidState.Common; -using LiquidState.Configuration; -using LiquidState.Representations; - -namespace LiquidState.Machines -{ - public class StateMachine : IStateMachine - { - internal StateRepresentation CurrentStateRepresentation; - private readonly Dictionary> configDictionary; - private InterlockedMonitor monitor = new InterlockedMonitor(); - private int isEnabled = 1; - - internal StateMachine(TState initialState, StateMachineConfiguration configuration) - { - Contract.Requires(configuration != null); - Contract.Requires(initialState != null); - - CurrentStateRepresentation = configuration.GetInitialStateRepresentation(initialState); - if (CurrentStateRepresentation == null) - { - throw new InvalidOperationException("StateMachine has an unreachable state"); - } - - configDictionary = configuration.Config; - } - - public event Action UnhandledTriggerExecuted; - public event Action StateChanged; - - public void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default) - { - if (monitor.TryEnter()) - { - try - { - if (!IsEnabled) return; - StateRepresentation rep; - if (configDictionary.TryGetValue(state, out rep)) - { - if ((option & StateTransitionOption.CurrentStateExitTransition) == - StateTransitionOption.CurrentStateExitTransition) - { - ExecuteAction(CurrentStateRepresentation.OnExitAction); - } - if ((option & StateTransitionOption.NewStateEntryTransition) == - StateTransitionOption.NewStateEntryTransition) - { - ExecuteAction(rep.OnEntryAction); - } - - CurrentStateRepresentation = rep; - } - else - { - throw new InvalidOperationException("Invalid state: " + state.ToString()); - } - } - finally - { - monitor.Exit(); - } - } - else - { - if (IsEnabled) - throw new InvalidOperationException("State cannot be changed while in transition. Use AsyncStateMachine or a BlockingStateMachine for these semantics."); - } - } - - public bool CanHandleTrigger(TTrigger trigger) - { - foreach (var current in CurrentStateRepresentation.Triggers) - { - if (current.Trigger.Equals(trigger)) - { - var predicate = current.ConditionalTriggerPredicate; - return predicate == null || predicate(); - } - } - return false; - } - - public bool CanTransitionTo(TState state) - { - foreach (var current in CurrentStateRepresentation.Triggers) - { - if (current.NextStateRepresentation.State.Equals(state)) - return true; - } - - return false; - } - - public void Pause() - { - Interlocked.Exchange(ref isEnabled, 0); - } - - public void Resume() - { - Interlocked.Exchange(ref isEnabled, 1); - } - - public void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument) - { - if (monitor.TryEnter()) - { - try - { - if (!IsEnabled) return; - var trigger = parameterizedTrigger.Trigger; - var triggerRep = StateConfigurationHelper.FindTriggerRepresentation(trigger, - CurrentStateRepresentation); - - if (triggerRep == null) - { - HandleInvalidTrigger(trigger); - return; - } - - var previousState = CurrentState; - - var predicate = triggerRep.ConditionalTriggerPredicate; - if (predicate != null) - { - if (!predicate()) - { - HandleInvalidTrigger(trigger); - return; - } - } - - // Handle ignored trigger - - if (triggerRep.NextStateRepresentation == null) - { - return; - } - - // Catch invalid paramters before execution. - - Action triggerAction = null; - try - { - triggerAction = (Action) triggerRep.OnTriggerAction; - } - catch (InvalidCastException) - { - InvalidTriggerParameterException.Throw(trigger); - return; - } - - - // Current exit - var currentExit = CurrentStateRepresentation.OnExitAction; - ExecuteAction(currentExit); - - // Trigger entry - if (triggerAction != null) triggerAction.Invoke(argument); - - - var nextStateRep = triggerRep.NextStateRepresentation; - - // Next entry - var nextEntry = nextStateRep.OnEntryAction; - ExecuteAction(nextEntry); - - CurrentStateRepresentation = nextStateRep; - - // Raise state change event - var stateChangedHandler = StateChanged; - if (stateChangedHandler != null) - stateChangedHandler.Invoke(previousState, CurrentStateRepresentation.State); - } - finally - { - monitor.Exit(); - } - } - else - { - if (IsEnabled) - throw new InvalidOperationException("State cannot be changed while in transition. Use AsyncStateMachine or a BlockingStateMachine for these semantics."); - } - } - - public void Fire(TTrigger trigger) - { - if (monitor.TryEnter()) - { - try - { - if (!IsEnabled) return; - var triggerRep = StateConfigurationHelper.FindTriggerRepresentation(trigger, - CurrentStateRepresentation); - - if (triggerRep == null) - { - HandleInvalidTrigger(trigger); - return; - } - - var previousState = CurrentState; - - var predicate = triggerRep.ConditionalTriggerPredicate; - if (predicate != null) - { - if (!predicate()) - { - HandleInvalidTrigger(trigger); - return; - } - } - - // Handle ignored trigger - - if (triggerRep.NextStateRepresentation == null) - { - return; - } - - // Catch invalid paramters before execution. - - Action triggerAction = null; - try - { - triggerAction = (Action) triggerRep.OnTriggerAction; - } - catch (InvalidCastException) - { - InvalidTriggerParameterException.Throw(trigger); - return; - } - - - // Current exit - var currentExit = CurrentStateRepresentation.OnExitAction; - ExecuteAction(currentExit); - - // Trigger entry - ExecuteAction(triggerAction); - - var nextStateRep = triggerRep.NextStateRepresentation; - - // Next entry - var nextEntry = nextStateRep.OnEntryAction; - ExecuteAction(nextEntry); - - CurrentStateRepresentation = nextStateRep; - - // Raise state change event - var stateChangedHandler = StateChanged; - if (stateChangedHandler != null) - stateChangedHandler.Invoke(previousState, CurrentStateRepresentation.State); - } - finally - { - monitor.Exit(); - } - } - else - { - if (IsEnabled) - throw new InvalidOperationException("State cannot be changed while in transition. Use AsyncStateMachine or a BlockingStateMachine for these semantics."); - } - } - - public bool IsInTransition - { - get { return monitor.IsBusy; } - } - - public TState CurrentState - { - get { return CurrentStateRepresentation.State; } - } - - public IEnumerable CurrentPermittedTriggers - { - get - { - foreach (var triggerRepresentation in CurrentStateRepresentation.Triggers) - { - yield return triggerRepresentation.Trigger; - } - } - } - - public bool IsEnabled - { - get { return Interlocked.CompareExchange(ref isEnabled, -1, -1) == 1; } - } - - private void ExecuteAction(Action action) - { - if (action != null) action.Invoke(); - } - - private void HandleInvalidTrigger(TTrigger trigger) - { - var handler = UnhandledTriggerExecuted; - if (handler != null) handler.Invoke(trigger, CurrentStateRepresentation.State); - } - } -} diff --git a/LiquidState/Common/ImmutableCollections.cs b/LiquidState/Scaffold/ImmutableCollections.cs similarity index 99% rename from LiquidState/Common/ImmutableCollections.cs rename to LiquidState/Scaffold/ImmutableCollections.cs index b9817f7..197f2d4 100644 --- a/LiquidState/Common/ImmutableCollections.cs +++ b/LiquidState/Scaffold/ImmutableCollections.cs @@ -8,8 +8,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using LiquidState.Common; -namespace LiquidState.Common +namespace LiquidState.Scaffold { internal interface IImmutableStack : IEnumerable, IEnumerable { diff --git a/LiquidState/Common/InterlockedHelpers.cs b/LiquidState/Scaffold/InterlockedHelpers.cs similarity index 100% rename from LiquidState/Common/InterlockedHelpers.cs rename to LiquidState/Scaffold/InterlockedHelpers.cs diff --git a/LiquidState/Common/Validation.cs b/LiquidState/Scaffold/Validation.cs similarity index 100% rename from LiquidState/Common/Validation.cs rename to LiquidState/Scaffold/Validation.cs diff --git a/LiquidState/StateMachineFactory.cs b/LiquidState/StateMachineFactory.cs index 97a769d..a7c971b 100644 --- a/LiquidState/StateMachineFactory.cs +++ b/LiquidState/StateMachineFactory.cs @@ -7,16 +7,18 @@ using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Threading.Tasks; -using LiquidState.Common; -using LiquidState.Configuration; -using LiquidState.Machines; +using LiquidState.Awaitable; +using LiquidState.Awaitable.Core; +using LiquidState.Core; +using LiquidState.Synchronous; +using LiquidState.Synchronous.Core; namespace LiquidState { public static class StateMachineFactory { public static IStateMachine Create(TState initialState, - StateMachineConfiguration config, bool blocking = false) + Configuration config, bool blocking = false, bool throwOnInvalidTriggers = true) { Contract.Requires(initialState != null); Contract.Requires(config != null); @@ -28,37 +30,38 @@ public static IStateMachine Create(TState in } else { - sm = new StateMachine(initialState, config); + sm = new GuardedStateMachine(initialState, config); } - sm.UnhandledTriggerExecuted += InvalidTriggerException.Throw; + if (throwOnInvalidTriggers) + sm.UnhandledTrigger += InvalidTriggerException.Throw; return sm; } public static IAwaitableStateMachine Create( TState initialState, - AwaitableStateMachineConfiguration config, bool asyncMachine = true) + AwaitableStateMachineConfiguration config, bool asyncMachine = true, bool throwOnInvalidTriggers = true) { Contract.Requires(initialState != null); Contract.Requires(config != null); - return Create(initialState, config, asyncMachine, null); + return Create(initialState, config, asyncMachine, null, throwOnInvalidTriggers); } public static IAwaitableStateMachine Create( TState initialState, - AwaitableStateMachineConfiguration config, TaskScheduler customScheduler) + AwaitableStateMachineConfiguration config, TaskScheduler customScheduler, bool throwOnInvalidTriggers = true) { Contract.Requires(initialState != null); Contract.Requires(config != null); Contract.Requires(customScheduler != null); - return Create(initialState, config, false, null); + return Create(initialState, config, false, null, throwOnInvalidTriggers); } - public static StateMachineConfiguration CreateConfiguration() + public static Configuration CreateConfiguration() { - return new StateMachineConfiguration(); + return new Configuration(); } public static AwaitableStateMachineConfiguration CreateAwaitableConfiguration @@ -68,7 +71,7 @@ public static AwaitableStateMachineConfiguration CreateAwaitab } private static IAwaitableStateMachine Create(TState initialState, - AwaitableStateMachineConfiguration config, bool asyncMachine, TaskScheduler scheduler) + AwaitableStateMachineConfiguration config, bool asyncMachine, TaskScheduler scheduler, bool throwOnInvalidTriggers) { IAwaitableStateMachine sm; if (asyncMachine) @@ -83,7 +86,8 @@ private static IAwaitableStateMachine Create : new AwaitableStateMachineWithScheduler(initialState, config, scheduler); } - sm.UnhandledTriggerExecuted += InvalidTriggerException.Throw; + if (throwOnInvalidTriggers) + sm.UnhandledTriggerExecuted += InvalidTriggerException.Throw; return sm; } } diff --git a/LiquidState/Synchronous/BlockingStateMachine.cs b/LiquidState/Synchronous/BlockingStateMachine.cs new file mode 100644 index 0000000..aaaf782 --- /dev/null +++ b/LiquidState/Synchronous/BlockingStateMachine.cs @@ -0,0 +1,58 @@ +// Author: Prasanna V. Loganathar +// Created: 2:12 AM 27-11-2014 +// Project: LiquidState +// License: http://www.apache.org/licenses/LICENSE-2.0 + +using System.Diagnostics.Contracts; +using LiquidState.Core; +using LiquidState.Synchronous.Core; + +namespace LiquidState.Synchronous +{ + public class BlockingStateMachineBase : RawStateMachineBase + { + private readonly object syncRoot = new object(); + + internal BlockingStateMachineBase(TState initialState, Configuration configuration) + : base(initialState, configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + } + + public override void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default) + { + lock (syncRoot) + { + base.MoveToState(state, option); + } + } + + public override void Fire(ParameterizedTrigger parameterizedTrigger, + TArgument argument) + { + lock (syncRoot) + { + base.Fire(parameterizedTrigger, argument); + } + } + + public override void Fire(TTrigger trigger) + { + lock (syncRoot) + { + base.Fire(trigger); + } + } + } + + public sealed class BlockingStateMachine : BlockingStateMachineBase + { + public BlockingStateMachine(TState initialState, Configuration configuration) + : base(initialState, configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + } + } +} diff --git a/LiquidState/Synchronous/Core/AbstractStateMachine.cs b/LiquidState/Synchronous/Core/AbstractStateMachine.cs new file mode 100644 index 0000000..8917787 --- /dev/null +++ b/LiquidState/Synchronous/Core/AbstractStateMachine.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using LiquidState.Common; +using LiquidState.Core; + +namespace LiquidState.Synchronous.Core +{ + public abstract class AbstractStateMachine : AbstractStateMachineCore, IStateMachine + { + protected internal StateRepresentation CurrentStateRepresentation; + protected internal Dictionary> Representations; + + public abstract void Fire(ParameterizedTrigger parameterizedTrigger, + TArgument argument); + + public abstract void Fire(TTrigger trigger); + public abstract void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default); + + public override TState CurrentState + { + get { return CurrentStateRepresentation.State; } + } + + public override IEnumerable CurrentPermittedTriggers + { + get + { + foreach (var triggerRepresentation in CurrentStateRepresentation.Triggers) + { + yield return triggerRepresentation.Trigger; + } + } + } + } +} \ No newline at end of file diff --git a/LiquidState/Configuration/StateMachineConfiguration.cs b/LiquidState/Synchronous/Core/Configuration.cs similarity index 65% rename from LiquidState/Configuration/StateMachineConfiguration.cs rename to LiquidState/Synchronous/Core/Configuration.cs index 96e5001..a3e2c89 100644 --- a/LiquidState/Configuration/StateMachineConfiguration.cs +++ b/LiquidState/Synchronous/Core/Configuration.cs @@ -8,29 +8,29 @@ using System.Diagnostics.Contracts; using System.Linq; using LiquidState.Common; -using LiquidState.Representations; +using LiquidState.Core; -namespace LiquidState.Configuration +namespace LiquidState.Synchronous.Core { - public class StateMachineConfiguration + public class Configuration { - internal Dictionary> Config; + internal Dictionary> Representations; - internal StateMachineConfiguration(int statesConfigStoreInitalCapacity = 4) + internal Configuration(int statesConfigStoreInitalCapacity = 4) { - Config = new Dictionary>(statesConfigStoreInitalCapacity); + Representations = new Dictionary>(statesConfigStoreInitalCapacity); } - internal StateMachineConfiguration(Dictionary> config) + internal Configuration(Dictionary> representations) { - Config = config; + Representations = representations; } public StateConfigurationHelper ForState(TState state) { Contract.Requires(state != null); - return new StateConfigurationHelper(Config, state); + return new StateConfigurationHelper(Representations, state); } public ParameterizedTrigger SetTriggerParameter(TTrigger trigger) @@ -44,11 +44,11 @@ internal StateRepresentation GetInitialStateRepresentation(TSt Contract.Requires(initialState != null); StateRepresentation rep; - if (Config.TryGetValue(initialState, out rep)) + if (Representations.TryGetValue(initialState, out rep)) { return rep; } - return Config.Values.FirstOrDefault(); + return Representations.Values.FirstOrDefault(); } } } diff --git a/LiquidState/Synchronous/Core/ExecutionHelper.cs b/LiquidState/Synchronous/Core/ExecutionHelper.cs new file mode 100644 index 0000000..306fc88 --- /dev/null +++ b/LiquidState/Synchronous/Core/ExecutionHelper.cs @@ -0,0 +1,173 @@ +using System; +using System.Diagnostics.Contracts; +using LiquidState.Common; +using LiquidState.Core; + +namespace LiquidState.Synchronous.Core +{ + internal static class ExecutionHelper + { + internal static void ThrowInTransition() + { + throw new InvalidOperationException("State cannot be changed while in transition. Use AsyncStateMachine or a BlockingStateMachine for these semantics."); + } + + internal static void ExecuteAction(Action action) + { + if (action != null) action.Invoke(); + } + + internal static void MoveToStateCore(TState state, StateTransitionOption option, AbstractStateMachine machine) + { + + Contract.Requires(machine != null); + + StateRepresentation targetRep; + if (machine.Representations.TryGetValue(state, out targetRep)) + { + + var currentRep = machine.CurrentStateRepresentation; + machine.RaiseTransitionStarted(targetRep.State); + + if ((option & StateTransitionOption.CurrentStateExitTransition) == + StateTransitionOption.CurrentStateExitTransition) + { + ExecuteAction(currentRep.OnExitAction); + } + if ((option & StateTransitionOption.NewStateEntryTransition) == + StateTransitionOption.NewStateEntryTransition) + { + ExecuteAction(targetRep.OnEntryAction); + } + + var pastState = currentRep.State; + machine.CurrentStateRepresentation = targetRep; + machine.RaiseTransitionExecuted(pastState); + } + else + { + machine.RaiseInvalidState(state); + } + } + + internal static TriggerRepresentation FindAndEvaluateTriggerRepresentation(TTrigger trigger, AbstractStateMachine machine) + { + Contract.Requires(machine != null); + + var triggerRep = StateConfigurationHelper.FindTriggerRepresentation(trigger, + machine.CurrentStateRepresentation); + + if (triggerRep == null) + { + machine.RaiseInvalidTrigger(trigger); + return null; + } + + + var predicate = triggerRep.ConditionalTriggerPredicate; + if (predicate != null) + { + if (!predicate()) + { + machine.RaiseInvalidTrigger(trigger); + return null; + } + } + + // Handle ignored trigger + + if (triggerRep.NextStateRepresentation == null) + { + return null; + } + + return triggerRep; + } + + internal static void FireCore(TTrigger trigger, AbstractStateMachine machine) + { + Contract.Requires(machine != null); + + var currentStateRepresentation = machine.CurrentStateRepresentation; + var triggerRep = FindAndEvaluateTriggerRepresentation(trigger, machine); + if (triggerRep == null) + return; + + // Catch invalid paramters before execution. + + Action triggerAction = null; + try + { + triggerAction = (Action)triggerRep.OnTriggerAction; + } + catch (InvalidCastException) + { + InvalidTriggerParameterException.Throw(trigger); + return; + } + + var nextStateRep = triggerRep.NextStateRepresentation; + machine.RaiseTransitionStarted(nextStateRep.State); + + // Current exit + var currentExit = currentStateRepresentation.OnExitAction; + ExecuteAction(currentExit); + + // Trigger entry + ExecuteAction(triggerAction); + + // Next entry + var nextEntry = nextStateRep.OnEntryAction; + ExecuteAction(nextEntry); + + var pastState = machine.CurrentState; + machine.CurrentStateRepresentation = nextStateRep; + machine.RaiseTransitionExecuted(pastState); + } + + internal static void FireCore( + ParameterizedTrigger parameterizedTrigger, TArgument argument, AbstractStateMachine machine) + { + Contract.Requires(machine != null); + + var currentStateRepresentation = machine.CurrentStateRepresentation; + var trigger = parameterizedTrigger.Trigger; + + var triggerRep = FindAndEvaluateTriggerRepresentation(trigger, machine); + if (triggerRep == null) + return; + + // Catch invalid parameters before execution. + + Action triggerAction = null; + try + { + triggerAction = (Action)triggerRep.OnTriggerAction; + } + catch (InvalidCastException) + { + InvalidTriggerParameterException.Throw(trigger); + return; + } + + var nextStateRep = triggerRep.NextStateRepresentation; + machine.RaiseTransitionStarted(nextStateRep.State); + + // Current exit + var currentExit = currentStateRepresentation.OnExitAction; + ExecutionHelper.ExecuteAction(currentExit); + + // Trigger entry + if (triggerAction != null) triggerAction.Invoke(argument); + + + // Next entry + var nextEntry = nextStateRep.OnEntryAction; + ExecutionHelper.ExecuteAction(nextEntry); + + var pastState = machine.CurrentState; + machine.CurrentStateRepresentation = nextStateRep; + machine.RaiseTransitionExecuted(pastState); + } + } +} \ No newline at end of file diff --git a/LiquidState/Synchronous/Core/IStateMachine.cs b/LiquidState/Synchronous/Core/IStateMachine.cs new file mode 100644 index 0000000..5e0473e --- /dev/null +++ b/LiquidState/Synchronous/Core/IStateMachine.cs @@ -0,0 +1,45 @@ +// Author: Prasanna V. Loganathar +// Created: 1:33 AM 05-12-2014 +// Project: LiquidState +// License: http://www.apache.org/licenses/LICENSE-2.0 + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using LiquidState.Common; +using LiquidState.Core; + +namespace LiquidState.Synchronous.Core +{ + [ContractClass(typeof (StateMachineContract<,>))] + public interface IStateMachine : IStateMachineCore + { + void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument); + void Fire(TTrigger trigger); + void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default); + } + + [ContractClassFor(typeof (IStateMachine<,>))] + public abstract class StateMachineContract : IStateMachine + { + public abstract void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default); + public abstract void Pause(); + public abstract void Resume(); + + public abstract event Action> TransitionExecuted; + + public void Fire(ParameterizedTrigger parameterizedTrigger, TArgument argument) + { + Contract.Requires(parameterizedTrigger != null); + } + + public abstract void Fire(TTrigger trigger); + public abstract TState CurrentState { get; } + public abstract IEnumerable CurrentPermittedTriggers { get; } + public abstract bool IsEnabled { get; } + + public abstract event Action> UnhandledTrigger; + public abstract event Action> InvalidState; + public abstract event Action> TransitionStarted; + } +} diff --git a/LiquidState/Representations/Representation.cs b/LiquidState/Synchronous/Core/Representation.cs similarity index 88% rename from LiquidState/Representations/Representation.cs rename to LiquidState/Synchronous/Core/Representation.cs index 34daddf..9ffeb34 100644 --- a/LiquidState/Representations/Representation.cs +++ b/LiquidState/Synchronous/Core/Representation.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; -namespace LiquidState.Representations +namespace LiquidState.Synchronous.Core { - internal class StateRepresentation + public class StateRepresentation { public readonly TState State; public readonly List> Triggers; @@ -27,7 +27,7 @@ internal StateRepresentation(TState state) } } - internal class TriggerRepresentation + public class TriggerRepresentation { public readonly TTrigger Trigger; public Func ConditionalTriggerPredicate; diff --git a/LiquidState/Configuration/StateConfigurationHelper.cs b/LiquidState/Synchronous/Core/StateConfigurationHelper.cs similarity index 99% rename from LiquidState/Configuration/StateConfigurationHelper.cs rename to LiquidState/Synchronous/Core/StateConfigurationHelper.cs index da8bc73..800598a 100644 --- a/LiquidState/Configuration/StateConfigurationHelper.cs +++ b/LiquidState/Synchronous/Core/StateConfigurationHelper.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using LiquidState.Common; -using LiquidState.Representations; +using LiquidState.Core; -namespace LiquidState.Configuration +namespace LiquidState.Synchronous.Core { public class StateConfigurationHelper { diff --git a/LiquidState/Synchronous/GuardedStateMachine.cs b/LiquidState/Synchronous/GuardedStateMachine.cs new file mode 100644 index 0000000..fdf871d --- /dev/null +++ b/LiquidState/Synchronous/GuardedStateMachine.cs @@ -0,0 +1,96 @@ +// Author: Prasanna V. Loganathar +// Created: 2:12 AM 27-11-2014 +// Project: LiquidState +// License: http://www.apache.org/licenses/LICENSE-2.0 + +using System.Diagnostics.Contracts; +using LiquidState.Common; +using LiquidState.Configuration; +using LiquidState.Core; +using LiquidState.Synchronous.Core; + +namespace LiquidState.Synchronous +{ + public abstract class GuardedStateMachineBase : RawStateMachineBase + { + private InterlockedMonitor monitor = new InterlockedMonitor(); + + internal GuardedStateMachineBase(TState initialState, Configuration configuration) + : base(initialState, configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + } + + public override void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default) + { + if (monitor.TryEnter()) + { + try + { + base.MoveToState(state, option); + } + finally + { + monitor.Exit(); + } + } + else + { + if (IsEnabled) + ExecutionHelper.ThrowInTransition(); + } + } + + public override void Fire(ParameterizedTrigger parameterizedTrigger, + TArgument argument) + { + if (monitor.TryEnter()) + { + try + { + base.Fire(parameterizedTrigger, argument); + } + finally + { + monitor.Exit(); + } + } + else + { + if (IsEnabled) + ExecutionHelper.ThrowInTransition(); + } + } + + public override void Fire(TTrigger trigger) + { + if (monitor.TryEnter()) + { + try + { + base.Fire(trigger); + } + finally + { + monitor.Exit(); + } + } + else + { + if (IsEnabled) + ExecutionHelper.ThrowInTransition(); + } + } + } + + public sealed class GuardedStateMachine : GuardedStateMachineBase + { + public GuardedStateMachine(TState initialState, Configuration configuration) + : base(initialState, configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + } + } +} diff --git a/LiquidState/Synchronous/RawStateMachine.cs b/LiquidState/Synchronous/RawStateMachine.cs new file mode 100644 index 0000000..fb215be --- /dev/null +++ b/LiquidState/Synchronous/RawStateMachine.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.Contracts; +using LiquidState.Common; +using LiquidState.Configuration; +using LiquidState.Core; +using LiquidState.Synchronous.Core; + +namespace LiquidState.Synchronous +{ + public abstract class RawStateMachineBase : AbstractStateMachine + { + protected RawStateMachineBase(TState initialState, Configuration configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + + CurrentStateRepresentation = configuration.GetInitialStateRepresentation(initialState); + if (CurrentStateRepresentation == null) + { + InvalidStateException.Throw(initialState); + } + + Representations = configuration.Representations; + } + + public override void MoveToState(TState state, StateTransitionOption option = StateTransitionOption.Default) + { + if (!IsEnabled) return; + ExecutionHelper.MoveToStateCore(state, option, this); + } + + public override void Fire(ParameterizedTrigger parameterizedTrigger, + TArgument argument) + { + if (!IsEnabled) return; + ExecutionHelper.FireCore(parameterizedTrigger, argument, this); + } + + public override void Fire(TTrigger trigger) + { + if (!IsEnabled) return; + ExecutionHelper.FireCore(trigger, this); + } + } + + public sealed class RawStateMachine : RawStateMachineBase + { + public RawStateMachine(TState initialState, Configuration configuration) + : base(initialState, configuration) + { + Contract.Requires(configuration != null); + Contract.Requires(initialState != null); + } + } +} \ No newline at end of file diff --git a/VersionInfo.cs b/VersionInfo.cs index 7e0eba7..37cf622 100644 --- a/VersionInfo.cs +++ b/VersionInfo.cs @@ -1,5 +1,7 @@ using System.Reflection; -[assembly: AssemblyVersion("5.0.0")] -[assembly: AssemblyFileVersion("5.0.0")] -[assembly: AssemblyInformationalVersion("5.0.0")] \ No newline at end of file +// Version is automatically patched by publish scripts +// including all four parts - Major, Minor, Patch, Build + +[assembly: AssemblyVersion("6.0.0.*")] +[assembly: AssemblyInformationalVersion("6.0.0-dev")] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index d7bdf46..5cd6539 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,24 +1,46 @@ -version: '5.0.0.{build}' +version: '6.0.0.{build}' + +## -- Git -- + branches: only: - master +clone_depth: 1 +skip_commits: + message: '(?i)Update README(\.md)?$' + +## -- Environment -- + configuration: Release platform: Any CPU -clone_depth: 1 +matrix: + fast_finish: true +cache: + - packages -> **\packages.config + + +## -- Tool options -- + assembly_info: patch: true file: '**\VersionInfo.*' assembly_version: '{version}' assembly_file_version: '{version}' assembly_informational_version: '{version}-pre' + nuget: account_feed: true project_feed: true + disable_publish_on_pr: false + build: project: LiquidState\LiquidState.csproj publish_nuget: true include_nuget_references: true verbosity: minimal + +## -- Deployment -- + deploy: - provider: NuGet api_key: