Skip to content

Commit

Permalink
Merge pull request #562 from dotnet-state-machine/dev
Browse files Browse the repository at this point in the history
Release 5.15.0
  • Loading branch information
mclift authored Dec 29, 2023
2 parents 4a4ac06 + 66eee8f commit 7d7b44d
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 67 deletions.
41 changes: 40 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 5.15.0 - 2023.12.29
### Changed
- Updated net6.0 build target to net8.0 [#551]
- New abstract method, `GetInitialTransition`, added to `GraphStyleBase` to remove DOT graph implementation from `StateGraph` [#557]
- Classes that are derived from `GraphStyleBase` and are being migrated from an earlier release of Stateless will need to implement this method.
### Added
- Added license information and README file to NuGet package [#539], [#553]

## 5.14.0 - 2023.11.14
### Added
- Enable Source Link & Deterministic Builds [#501]
Expand Down Expand Up @@ -44,7 +52,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added CanFire overload to return unmet guard descriptions [#443]
### Fixed
- Inconsistency in sync/async statemachine execution [#444]
- Added support for spaces in state/trigger names in Graphviz node graphs by wrapping them in escaped quotes #447
- Added support for spaces in state/trigger names in Graphviz node graphs by wrapping them in escaped quotes [#447]

## 5.10.1
Re-releasing 5.2.0 as v5.10.1.
Expand Down Expand Up @@ -202,6 +210,37 @@ Version 5.10.0 is now listed as the newest, since it has the highest version num
### Removed
### Fixed

[#551]: https://github.com/dotnet-state-machine/stateless/pull/551
[#557]: https://github.com/dotnet-state-machine/stateless/issues/557
[#553]: https://github.com/dotnet-state-machine/stateless/issues/553
[#539]: https://github.com/dotnet-state-machine/stateless/issues/539
[#501]: https://github.com/dotnet-state-machine/stateless/pull/501
[#519]: https://github.com/dotnet-state-machine/stateless/pull/519
[#520]: https://github.com/dotnet-state-machine/stateless/pull/520
[#526]: https://github.com/dotnet-state-machine/stateless/pull/526
[#524]: https://github.com/dotnet-state-machine/stateless/pull/524
[#536]: https://github.com/dotnet-state-machine/stateless/pull/536
[#514]: https://github.com/dotnet-state-machine/stateless/pull/514
[#511]: https://github.com/dotnet-state-machine/stateless/pull/511
[#522]: https://github.com/dotnet-state-machine/stateless/pull/522
[#521]: https://github.com/dotnet-state-machine/stateless/pull/521
[#512]: https://github.com/dotnet-state-machine/stateless/pull/512
[#528]: https://github.com/dotnet-state-machine/stateless/pull/528
[#532]: https://github.com/dotnet-state-machine/stateless/pull/532
[#530]: https://github.com/dotnet-state-machine/stateless/pull/530
[#544]: https://github.com/dotnet-state-machine/stateless/pull/544
[#494]: https://github.com/dotnet-state-machine/stateless/pull/494
[#495]: https://github.com/dotnet-state-machine/stateless/pull/495
[#479]: https://github.com/dotnet-state-machine/stateless/pull/479
[#484]: https://github.com/dotnet-state-machine/stateless/pull/484
[#488]: https://github.com/dotnet-state-machine/stateless/pull/488
[#471]: https://github.com/dotnet-state-machine/stateless/pull/471
[#478]: https://github.com/dotnet-state-machine/stateless/pull/478
[#480]: https://github.com/dotnet-state-machine/stateless/pull/480
[#487]: https://github.com/dotnet-state-machine/stateless/pull/487
[#443]: https://github.com/dotnet-state-machine/stateless/pull/443
[#444]: https://github.com/dotnet-state-machine/stateless/issues/444
[#447]: https://github.com/dotnet-state-machine/stateless/pull/447
[#422]: https://github.com/dotnet-state-machine/stateless/issues/422
[#416]: https://github.com/dotnet-state-machine/stateless/issues/416
[#413]: https://github.com/dotnet-state-machine/stateless/issues/413
Expand Down
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Entry/Exit action handlers can be supplied with a parameter of type `Transition`

### Internal transitions

Sometimes a trigger does needs to be handled, but the state shouldn't change. This is an internal transition. Use `InternalTransition` for this.
Sometimes a trigger needs to be handled, but the state shouldn't change. This is an internal transition. Use `InternalTransition` for this.

### Initial state transitions

Expand All @@ -84,7 +84,7 @@ Due to Stateless' internal structure, it does not know when it is "started". Thi

```csharp
sm.Configure(InitialState)
.OnActivate(() => sm.Fire(LetsGo)))
.OnActivate(() => sm.Fire(LetsGo))
.Permit(LetsGo, StateA)
```

Expand All @@ -110,7 +110,7 @@ It might be necessary to perform some code before storing the object state, and

### Introspection

The state machine can provide a list of the triggers that can be successfully fired within the current state via the `StateMachine.PermittedTriggers` property. Use `StateMachine.GetInfo()` to retreive information about the state configuration.
The state machine can provide a list of the triggers that can be successfully fired within the current state via the `StateMachine.PermittedTriggers` property. Use `StateMachine.GetInfo()` to retrieve information about the state configuration.

### Guard Clauses

Expand Down Expand Up @@ -182,7 +182,7 @@ This event will be invoked every time the state machine changes state.
```csharp
stateMachine.OnTransitionCompleted((transition) => { });
```
This event will be invoked at the very end of the trigger handling, after the last entry action have been executed.
This event will be invoked at the very end of the trigger handling, after the last entry action has been executed.

### Export to DOT graph

Expand All @@ -208,7 +208,7 @@ Command line example: `dot -T pdf -o phoneCall.pdf phoneCall.dot` to generate a

### Async triggers

On platforms that provide `Task<T>`, the `StateMachine` supports `async` entry/exit actions and so-on:
On platforms that provide `Task<T>`, the `StateMachine` supports `async` entry/exit actions and so on:

```csharp
stateMachine.Configure(State.Assigned)
Expand All @@ -228,7 +228,7 @@ await stateMachine.FireAsync(Trigger.Assigned);
## 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:
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)
Expand All @@ -237,11 +237,11 @@ var stateMachine = new StateMachine<State, Trigger>(initialState)
};
```

Setting this is vital within a Microsoft Orleans Grain for example, which requires the SynchronizationContext in order to make calls to other Grains.
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.
Stateless runs on .NET runtime version 4+ and practically all modern .NET platforms by targeting .NET Framework 4.6.2, .NET Standard 2.0 and .NET 8.0. Visual Studio 2017 or later is required to build the solution.


## Contributing
Expand All @@ -253,8 +253,6 @@ We welcome contributions to this project. Check [CONTRIBUTING.md](CONTRIBUTING.m

This page is an almost-complete description of Stateless, and its explicit aim is to remain minimal.

Please use the issue tracker or the if you'd like to report problems or discuss features.
Please use the issue tracker or the Discussions page if you'd like to report problems or discuss features.

(_Why the name? Stateless implements the set of rules regarding state transitions, but, at least when the delegate version of the constructor is used, doesn't maintain any internal state itself._)

[Visual Studio 2015 and .NET Core]: https://www.microsoft.com/net/core
2 changes: 1 addition & 1 deletion example/AlarmExample/AlarmExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
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>net6.0</TargetFramework>
<TargetFramework>net8.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>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
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>net6.0</TargetFramework>
<TargetFramework>net8.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>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>TelephoneCallExample</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>TelephoneCallExample</PackageId>
Expand Down
45 changes: 26 additions & 19 deletions src/Stateless/Graph/GraphStyleBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,53 @@ public abstract class GraphStyleBase
/// For example, for DOT files the prefix text would be
/// digraph {
/// </summary>
/// <returns>Prefix text</returns>
/// <returns>Prefix text.</returns>
public abstract string GetPrefix();

/// <summary>
/// Get initial transition if present.
/// </summary>
/// <param name="initialState">The initial state.</param>
/// <returns>Description of the initial transition, if present.</returns>
public abstract string GetInitialTransition(Reflection.StateInfo initialState);

/// <summary>
/// Returns the formatted text for a single state.
/// For example, for DOT files this would be the description of a single node:
/// nodename [label="statename"];
/// Usually the information on exit and entry actions would also be included here.
/// </summary>
/// <param name="state">The state to generate text for</param>
/// <returns>Description of the state in the desired format</returns>
/// <param name="state">The state to generate text for.</param>
/// <returns>Description of the state in the desired format.</returns>
public abstract string FormatOneState(State state);

/// <summary>
/// Returns the formatted text for a single superstate and its substates.
/// For example, for DOT files this would be a subgraph containing nodes for all the substates.
/// </summary>
/// <param name="stateInfo">The superstate to generate text for</param>
/// <returns>Description of the superstate, and all its substates, in the desired format</returns>
/// <param name="stateInfo">The superstate to generate text for.</param>
/// <returns>Description of the superstate, and all its substates, in the desired format.</returns>
public abstract string FormatOneCluster(SuperState stateInfo);

/// <summary>
/// Returns the formatted text for a single decision node.
/// A decision node is created each time there is a PermitDynamic() transition. There will be a transition
/// A decision node is created each time there is a PermitDynamic() transition. There will be a transition
/// from the state that has the dynamic transition to the decision node, and transitions from the decision
/// node to each of the destination nodes. If the caller did not specify the possible destination states,
/// node to each of the destination nodes. If the caller did not specify the possible destination states,
/// there will be no transitions leaving the decision node.
/// </summary>
/// <param name="nodeName">Name of the node</param>
/// <param name="label">Label for the node</param>
/// <returns></returns>
/// <param name="nodeName">Name of the node.</param>
/// <param name="label">Label for the node.</param>
/// <returns>Description of the decision node for a dynamic transition, in the desired format.</returns>
public abstract string FormatOneDecisionNode(string nodeName, string label);

/// <summary>
/// Returns the formatted text for all the transitions found in the state graph.
/// This form, which can be overridden, determines the type of each transition and passes the appropriate
/// parameters to the virtual FormatOneTransition() function.
/// </summary>
/// <param name="transitions">List of all transitions in the state graph</param>
/// <returns>Description of all transitions, in the desired format</returns>
/// <param name="transitions">List of all transitions in the state graph.</param>
/// <returns>Description of all transitions, in the desired format.</returns>
public virtual List<string> FormatAllTransitions(List<Transition> transitions)
{
List<string> lines = new List<string>();
Expand Down Expand Up @@ -111,15 +118,15 @@ public virtual List<string> FormatAllTransitions(List<Transition> transitions)
}

/// <summary>
/// Returns the formatted text for a single transition. Only required if the default version of
/// Returns the formatted text for a single transition. Only required if the default version of
/// FormatAllTransitions() is used.
/// </summary>
/// <param name="sourceNodeName">Node name of the source state node</param>
/// <param name="trigger">Name of the trigger</param>
/// <param name="actions">List of entry and exit actions (if any)</param>
/// <param name="destinationNodeName"></param>
/// <param name="guards">List of guards (if any)</param>
/// <returns></returns>
/// <param name="sourceNodeName">Node name of the source state node.</param>
/// <param name="trigger">Name of the trigger.</param>
/// <param name="actions">List of entry and exit actions (if any).</param>
/// <param name="destinationNodeName">Name of the destination state node.</param>
/// <param name="guards">List of guards (if any).</param>
/// <returns>Description of the initial state transition, in the desired format.</returns>
public virtual string FormatOneTransition(string sourceNodeName, string trigger, IEnumerable<string> actions, string destinationNodeName, IEnumerable<string> guards)
{
throw new InvalidOperationException("If you use IGraphStyle.FormatAllTransitions() you must implement an override of FormatOneTransition()");
Expand Down
6 changes: 1 addition & 5 deletions src/Stateless/Graph/StateGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ public string ToGraph(GraphStyleBase style)
dirgraphText += System.Environment.NewLine + transit;

// Add initial transition if present
var initialStateName = initialState.UnderlyingState.ToString();
dirgraphText += System.Environment.NewLine + $" init [label=\"\", shape=point];";
dirgraphText += System.Environment.NewLine + $" init -> \"{initialStateName}\"[style = \"solid\"]";

dirgraphText += System.Environment.NewLine + "}";
dirgraphText += style.GetInitialTransition(initialState);

return dirgraphText;
}
Expand Down
53 changes: 32 additions & 21 deletions src/Stateless/Graph/UmlDotGraphStyle.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using System;
using Stateless.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Stateless.Graph
{
/// <summary>
/// Generate DOT graphs in basic UML style
/// Generate DOT graphs in basic UML style.
/// </summary>
public class UmlDotGraphStyle : GraphStyleBase
{
/// <summary>Get the text that starts a new graph</summary>
/// <returns></returns>
/// <summary>Get the text that starts a new graph.</summary>
/// <returns>The prefix for the DOT graph document.</returns>
public override string GetPrefix()
{
return "digraph {\n"
Expand All @@ -22,10 +23,9 @@ public override string GetPrefix()

/// <summary>
/// Returns the formatted text for a single superstate and its substates.
/// For example, for DOT files this would be a subgraph containing nodes for all the substates.
/// </summary>
/// <param name="stateInfo">The superstate to generate text for</param>
/// <returns>Description of the superstate, and all its substates, in the desired format</returns>
/// <returns>A DOT graph representation of the superstate and all its substates.</returns>
/// <inheritdoc/>
public override string FormatOneCluster(SuperState stateInfo)
{
string stateRepresentationString = "";
Expand Down Expand Up @@ -56,10 +56,10 @@ public override string FormatOneCluster(SuperState stateInfo)
}

/// <summary>
/// Generate the text for a single state
/// Generate the text for a single state.
/// </summary>
/// <param name="state">The state to generate text for</param>
/// <returns></returns>
/// <returns>A DOT graph representation of the state.</returns>
/// <inheritdoc/>
public override string FormatOneState(State state)
{
if (state.EntryActions.Count == 0 && state.ExitActions.Count == 0)
Expand All @@ -79,14 +79,10 @@ public override string FormatOneState(State state)
}

/// <summary>
/// Generate text for a single transition
/// Generate text for a single transition.
/// </summary>
/// <param name="sourceNodeName"></param>
/// <param name="trigger"></param>
/// <param name="actions"></param>
/// <param name="destinationNodeName"></param>
/// <param name="guards"></param>
/// <returns></returns>
/// <returns>A DOT graph representation of a state transition.</returns>
/// <inheritdoc/>
public override string FormatOneTransition(string sourceNodeName, string trigger, IEnumerable<string> actions, string destinationNodeName, IEnumerable<string> guards)
{
string label = trigger ?? "";
Expand All @@ -108,16 +104,31 @@ public override string FormatOneTransition(string sourceNodeName, string trigger
}

/// <summary>
/// Generate the text for a single decision node
/// Generate the text for a single decision node.
/// </summary>
/// <param name="nodeName">Name of the node</param>
/// <param name="label">Label for the node</param>
/// <returns></returns>
/// <returns>A DOT graph representation of the decision node for a dynamic transition.</returns>
/// <inheritdoc/>
public override string FormatOneDecisionNode(string nodeName, string label)
{
return $"\"{nodeName}\" [shape = \"diamond\", label = \"{label}\"];\n";
}

/// <summary>
/// Get initial transition if present.
/// </summary>
/// <returns>A DOT graph representation of the initial state transition.</returns>
/// <inheritdoc/>
public override string GetInitialTransition(StateInfo initialState)
{
var initialStateName = initialState.UnderlyingState.ToString();
string dirgraphText = System.Environment.NewLine + $" init [label=\"\", shape=point];";
dirgraphText += System.Environment.NewLine + $" init -> \"{initialStateName}\"[style = \"solid\"]";

dirgraphText += System.Environment.NewLine + "}";

return dirgraphText;
}

internal string FormatOneLine(string fromNodeName, string toNodeName, string label)
{
return $"\"{fromNodeName}\" -> \"{toNodeName}\" [style=\"solid\", label=\"{label}\"];";
Expand Down
Loading

0 comments on commit 7d7b44d

Please sign in to comment.