Skip to content

Commit

Permalink
Merge branch 'dev' into celloza/alarmexample
Browse files Browse the repository at this point in the history
  • Loading branch information
celloza authored May 31, 2023
2 parents 8c03b2b + ff6ad59 commit 14472a3
Show file tree
Hide file tree
Showing 31 changed files with 566 additions and 104 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ phoneCall.Configure(State.OffHook)

Guard clauses within a state must be mutually exclusive (multiple guard clauses cannot be valid at the same time.) Substates can override transitions by respecifying them, however substates cannot disallow transitions that are allowed by the superstate.

The guard clauses will be evaluated whenever a trigger is fired. Guards should therefor be made side effect free.
The guard clauses will be evaluated whenever a trigger is fired. Guards should therefore be made side effect free.

### Parameterised Triggers

Expand Down Expand Up @@ -225,6 +225,20 @@ await stateMachine.FireAsync(Trigger.Assigned);

**Note:** while `StateMachine` may be used _asynchronously_, it remains single-threaded and may not be used _concurrently_ by multiple threads.

## Advanced Features ##

### Retaining the SynchronizationContext ###
In specific situations where all handler methods must be invoked with the consumer's SynchronizationContext, set the _RetainSynchronizationContext_ property on creation:

```csharp
var stateMachine = new StateMachine<State, Trigger>(initialState)
{
RetainSynchronizationContext = true
};
```

Setting this is vital within a Microsoft Orleans Grain for example, which requires the SynchronizationContext in order to make calls to other Grains.

## Building

Stateless runs on .NET 4.0+ and practically all modern .NET platforms by targeting .NET Standard 1.0 and .NET Standard2.0. Visual Studio 2017 or later is required to build the solution.
Expand Down
2 changes: 1 addition & 1 deletion Stateless.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33424.131
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{8DE7A8AE-D87D-46A0-9757-88BA4AF7EDA5}"
ProjectSection(SolutionItems) = preProject
Expand Down
2 changes: 1 addition & 1 deletion example/BugTrackerExample/BugTrackerExample.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>BugTrackerExample</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>BugTrackerExample</PackageId>
Expand Down
2 changes: 1 addition & 1 deletion example/JsonExample/JsonExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion example/JsonExample/Member.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static Member FromJson(string jsonString)

public bool Equals(Member anotherMember)
{
return ((State == anotherMember.State) && (Name == anotherMember.Name));
return State == anotherMember.State && Name == anotherMember.Name;
}
}

Expand Down
2 changes: 1 addition & 1 deletion example/OnOffExample/OnOffExample.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>OnOffExample</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>OnOffExample</PackageId>
Expand Down
2 changes: 1 addition & 1 deletion example/TelephoneCallExample/TelephoneCallExample.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>TelephoneCallExample</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>TelephoneCallExample</PackageId>
Expand Down
25 changes: 24 additions & 1 deletion src/Stateless/EntryActionBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override Task ExecuteAsync(Transition transition, object[] args)

public class SyncFrom<TTriggerType> : Sync
{
internal TTriggerType Trigger { get; private set; }
internal TTriggerType Trigger { get; }

public SyncFrom(TTriggerType trigger, Action<Transition, object[]> action, Reflection.InvocationInfo description)
: base(action, description)
Expand Down Expand Up @@ -82,6 +82,29 @@ public override Task ExecuteAsync(Transition transition, object[] args)
return _action(transition, args);
}
}

public class AsyncFrom<TTriggerType> : Async
{
internal TTriggerType Trigger { get; }

public AsyncFrom(TTriggerType trigger, Func<Transition, object[], Task> action, Reflection.InvocationInfo description)
: base(action, description)
{
Trigger = trigger;
}

public override void Execute(Transition transition, object[] args)
{
if (transition.Trigger.Equals(Trigger))
base.Execute(transition, args);
}

public override Task ExecuteAsync(Transition transition, object[] args)
{
Execute(transition, args);
return TaskResult.Done;
}
}
}
}
}
8 changes: 4 additions & 4 deletions src/Stateless/Graph/StateGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public string ToGraph(GraphStyleBase style)
// Next process all non-cluster states
foreach (var state in States.Values)
{
if ((state is SuperState) || (state is Decision) || (state.SuperState != null))
if (state is SuperState || state is Decision || state.SuperState != null)
continue;
dirgraphText += style.FormatOneState(state).Replace("\n", System.Environment.NewLine);
}
Expand Down Expand Up @@ -115,8 +115,8 @@ void ProcessOnEntryFrom(StateMachineInfo machineInfo)
// Does it have any incoming transitions that specify that trigger?
foreach (var transit in state.Arriving)
{
if ((transit.ExecuteEntryExitActions)
&& (transit.Trigger.UnderlyingTrigger.ToString() == entryAction.FromTrigger))
if (transit.ExecuteEntryExitActions
&& transit.Trigger.UnderlyingTrigger.ToString() == entryAction.FromTrigger)
{
transit.DestinationEntryActions.Add(entryAction);
}
Expand Down Expand Up @@ -208,7 +208,7 @@ void AddSingleStates(StateMachineInfo machineInfo)
/// <param name="machineInfo"></param>
void AddSuperstates(StateMachineInfo machineInfo)
{
foreach (var stateInfo in machineInfo.States.Where(sc => (sc.Substates?.Count() > 0) && (sc.Superstate == null)))
foreach (var stateInfo in machineInfo.States.Where(sc => sc.Substates?.Count() > 0 && sc.Superstate == null))
{
SuperState state = new SuperState(stateInfo);
States[stateInfo.UnderlyingState.ToString()] = state;
Expand Down
4 changes: 2 additions & 2 deletions src/Stateless/Graph/UmlDotGraphStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override string FormatOneCluster(SuperState stateInfo)

StringBuilder label = new StringBuilder($"{sourceName}");

if ((stateInfo.EntryActions.Count > 0) || (stateInfo.ExitActions.Count > 0))
if (stateInfo.EntryActions.Count > 0 || stateInfo.ExitActions.Count > 0)
{
label.Append("\\n----------");
label.Append(string.Concat(stateInfo.EntryActions.Select(act => "\\nentry / " + act)));
Expand Down Expand Up @@ -62,7 +62,7 @@ public override string FormatOneCluster(SuperState stateInfo)
/// <returns></returns>
public override string FormatOneState(State state)
{
if ((state.EntryActions.Count == 0) && (state.ExitActions.Count == 0))
if (state.EntryActions.Count == 0 && state.ExitActions.Count == 0)
return $"\"{state.StateName}\" [label=\"{state.StateName}\"];\n";

string f = $"\"{state.StateName}\" [label=\"{state.StateName}|";
Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/GuardCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal class GuardCondition

/// <summary>
/// Constructor that takes in a guard with no argument.
/// This is needed because we wrap the no-arg guard with a lamba and therefore method description won't match what was origianlly passed in.
/// This is needed because we wrap the no-arg guard with a lambda and therefore method description won't match what was originally passed in.
/// We need to preserve the method description before wrapping so Reflection methods will work.
/// </summary>
/// <param name="guard">No Argument Guard Condition</param>
Expand Down
6 changes: 3 additions & 3 deletions src/Stateless/OnTransitionedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class OnTransitionedEvent
{
event Action<Transition> _onTransitioned;
readonly List<Func<Transition, Task>> _onTransitionedAsync = new List<Func<Transition, Task>>();

public void Invoke(Transition transition)
{
if (_onTransitionedAsync.Count != 0)
Expand All @@ -22,12 +22,12 @@ public void Invoke(Transition transition)
}

#if TASKS
public async Task InvokeAsync(Transition transition)
public async Task InvokeAsync(Transition transition, bool retainSynchronizationContext)
{
_onTransitioned?.Invoke(transition);

foreach (var callback in _onTransitionedAsync)
await callback(transition).ConfigureAwait(false);
await callback(transition).ConfigureAwait(retainSynchronizationContext);
}
#endif

Expand Down
11 changes: 7 additions & 4 deletions src/Stateless/Reflection/ActionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ public class ActionInfo
internal static ActionInfo Create<TState, TTrigger>(StateMachine<TState, TTrigger>.EntryActionBehavior entryAction)
{
StateMachine<TState, TTrigger>.EntryActionBehavior.SyncFrom<TTrigger> syncFrom = entryAction as StateMachine<TState, TTrigger>.EntryActionBehavior.SyncFrom<TTrigger>;

if (syncFrom != null)
return new ActionInfo(entryAction.Description, syncFrom.Trigger.ToString());
else
return new ActionInfo(entryAction.Description, null);

StateMachine<TState, TTrigger>.EntryActionBehavior.AsyncFrom<TTrigger> asyncFrom = entryAction as StateMachine<TState, TTrigger>.EntryActionBehavior.AsyncFrom<TTrigger>;
if (asyncFrom != null)
return new ActionInfo(entryAction.Description, asyncFrom.Trigger.ToString());

return new ActionInfo(entryAction.Description, null);
}

/// <summary>
/// Constructor
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/Reflection/DynamicTransitionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void Add<TState>(TState destinationState, string criterion)
public class DynamicTransitionInfo : TransitionInfo
{
/// <summary>
/// Gets method informtion for the destination state selector.
/// Gets method information for the destination state selector.
/// </summary>
public InvocationInfo DestinationStateSelectorDescription { get; private set; }

Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/Reflection/FixedTransitionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal static FixedTransitionInfo Create<TState, TTrigger>(StateMachine<TState
{
Trigger = new TriggerInfo(behaviour.Trigger),
DestinationState = destinationStateInfo,
GuardConditionsMethodDescriptions = (behaviour.Guard == null)
GuardConditionsMethodDescriptions = behaviour.Guard == null
? new List<InvocationInfo>() : behaviour.Guard.Conditions.Select(c => c.MethodDescription)
};

Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/Reflection/IgnoredTransitionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal static IgnoredTransitionInfo Create<TState, TTrigger>(StateMachine<TSta
var transition = new IgnoredTransitionInfo
{
Trigger = new TriggerInfo(behaviour.Trigger),
GuardConditionsMethodDescriptions = (behaviour.Guard == null)
GuardConditionsMethodDescriptions = behaviour.Guard == null
? new List<InvocationInfo>() : behaviour.Guard.Conditions.Select(c => c.MethodDescription)
};

Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/Reflection/InvocationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ public string Description
/// <summary>
/// Returns true if the method is invoked asynchronously.
/// </summary>
public bool IsAsync => (_timing == Timing.Asynchronous);
public bool IsAsync => _timing == Timing.Asynchronous;
}
}
17 changes: 12 additions & 5 deletions src/Stateless/Reflection/StateInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ internal static void AddRelationships<TState, TTrigger>(StateInfo info, StateMac
foreach (var triggerBehaviours in stateRepresentation.TriggerBehaviours)
{
// First add all the deterministic transitions
foreach (var item in triggerBehaviours.Value.Where(behaviour => (behaviour is StateMachine<TState, TTrigger>.TransitioningTriggerBehaviour)))
foreach (var item in triggerBehaviours.Value.Where(behaviour => behaviour is StateMachine<TState, TTrigger>.TransitioningTriggerBehaviour))
{
var destinationInfo = lookupState(((StateMachine<TState, TTrigger>.TransitioningTriggerBehaviour)item).Destination);
fixedTransitions.Add(FixedTransitionInfo.Create(item, destinationInfo));
}
foreach (var item in triggerBehaviours.Value.Where(behaviour => (behaviour is StateMachine<TState, TTrigger>.ReentryTriggerBehaviour)))
foreach (var item in triggerBehaviours.Value.Where(behaviour => behaviour is StateMachine<TState, TTrigger>.ReentryTriggerBehaviour))
{
var destinationInfo = lookupState(((StateMachine<TState, TTrigger>.ReentryTriggerBehaviour)item).Destination);
fixedTransitions.Add(FixedTransitionInfo.Create(item, destinationInfo));
}
//Then add all the internal transitions
foreach (var item in triggerBehaviours.Value.Where(behaviour => (behaviour is StateMachine<TState, TTrigger>.InternalTriggerBehaviour)))
foreach (var item in triggerBehaviours.Value.Where(behaviour => behaviour is StateMachine<TState, TTrigger>.InternalTriggerBehaviour))
{
var destinationInfo = lookupState(stateRepresentation.UnderlyingState);
fixedTransitions.Add(FixedTransitionInfo.Create(item, destinationInfo));
Expand Down Expand Up @@ -136,14 +136,21 @@ private void AddRelationships(
public IEnumerable<InvocationInfo> DeactivateActions { get; private set; }

/// <summary>
/// Actions that are defined to be exectuted on state-exit.
/// Actions that are defined to be executed on state-exit.
/// </summary>
public IEnumerable<InvocationInfo> ExitActions { get; private set; }

/// <summary>
/// Transitions defined for this state.
/// </summary>
public IEnumerable<TransitionInfo> Transitions { get { return FixedTransitions.Concat<TransitionInfo>(DynamicTransitions); } }
public IEnumerable<TransitionInfo> Transitions
{
get {
return FixedTransitions == null // A quick way to check if AddRelationships has been called.
? null
: FixedTransitions.Concat<TransitionInfo>(DynamicTransitions);
}
}

/// <summary>
/// Transitions defined for this state.
Expand Down
4 changes: 2 additions & 2 deletions src/Stateless/StateConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,7 +1120,7 @@ public StateConfiguration OnExit(Action<Transition> exitAction, string exitActio
/// Substates inherit the allowed transitions of their superstate.
/// When entering directly into a substate from outside of the superstate,
/// entry actions for the superstate are executed.
/// Likewise when leaving from the substate to outside the supserstate,
/// Likewise when leaving from the substate to outside the superstate,
/// exit actions for the superstate will execute.
/// </remarks>
/// <param name="superstate">The superstate.</param>
Expand Down Expand Up @@ -1774,7 +1774,7 @@ StateConfiguration InternalPermitDynamicIf(TTrigger trigger, Func<object[], TSta
/// <returns>A stateConfiguration object</returns>
public StateConfiguration InitialTransition(TState targetState)
{
if (_representation.HasInitialTransition) throw new InvalidOperationException($"This state has already been configured with an inital transition ({_representation.InitialTransitionTarget}).");
if (_representation.HasInitialTransition) throw new InvalidOperationException($"This state has already been configured with an initial transition ({_representation.InitialTransitionTarget}).");
if (targetState.Equals(State)) throw new ArgumentException("Setting the current state as the target destination state is not allowed.", nameof(targetState));

_representation.SetInitialTransition(targetState);
Expand Down
Loading

0 comments on commit 14472a3

Please sign in to comment.