Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/drittich/state-machine
A simple convention-based finite state machine that lets you pass event data through to your transition actions.
https://github.com/drittich/state-machine
state-machine state-machine-cs state-machines statemachine statemachine-library statemachines
Last synced: 24 days ago
JSON representation
A simple convention-based finite state machine that lets you pass event data through to your transition actions.
- Host: GitHub
- URL: https://github.com/drittich/state-machine
- Owner: drittich
- Created: 2023-05-10T02:59:46.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-11-11T04:52:02.000Z (2 months ago)
- Last Synced: 2024-12-03T14:16:52.814Z (about 1 month ago)
- Topics: state-machine, state-machine-cs, state-machines, statemachine, statemachine-library, statemachines
- Language: C#
- Homepage:
- Size: 53.7 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# State Machine
[![.NET 8 - Build](https://github.com/drittich/state-machine/actions/workflows/build.yml/badge.svg)](https://github.com/drittich/state-machine/actions/workflows/build.yml)
[![.NET 8 - Tests](https://github.com/drittich/state-machine/actions/workflows/tests.yml/badge.svg)](https://github.com/drittich/state-machine/actions/workflows/tests.yml)A simple, extensible finite state machine that allows you to define states, events, transitions, and pass event data through to your transition actions.
## Installation
The `drittich.StateMachine` library is available on [NuGet](https://www.nuget.org/packages/drittich.StateMachine). You can install it using the Package Manager Console:
```bash
Install-Package drittich.StateMachine
```Or using the .NET CLI:
```bash
dotnet add package drittich.StateMachine
```This will install the library and its dependencies.
## Example Usage
The `StateMachine` class lets you define your own states, events, transitions, and a data transfer object (DTO) to pass data with events. This data can then be provided to the action that runs when a transition occurs.
You need to:
- Define enums for your states and events.
- Create a DTO class for event data.
- Initialize the state machine with an initial state and a logger.
- Add transitions that specify how the state machine moves from one state to another in response to events.### Define States and Events
```csharp
enum MyStates
{
Initial,
SomeState,
SomeOtherState,
Complete
}enum MyEvents
{
SomethingHappened,
SomethingElseHappened,
SomeOtherRandomEvent
}
```### Create a DTO for Event Data
```csharp
public class MyDto
{
public int Prop1 { get; set; }
}
```### Initialize the State Machine
```csharp
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;// Create a logger (use NullLogger if you don't need logging)
ILogger> logger = new NullLogger>();// Initialize the state machine with the initial state
var sm = new StateMachine(MyStates.Initial, logger);
```### Define the Transitions
With the simplified AddTransition method, you can now add transitions directly without needing to create Transition objects explicitly:
```csharp
// Add transitions to the state machine
sm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);
sm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);
```### Define the Action Methods
```csharp
using System.Threading;
using System.Threading.Tasks;// Action method for the first transition
async Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
// Your action code here
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeMethodToExecuteAsync");
}// Action method for the second transition
async Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
// Your action code here
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeOtherMethodToExecuteAsync");
}
```### Execute Transitions
```csharp
var data = new MyDto { Prop1 = 1 };// Execute the first transition
var resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);
// resultingState is MyStates.SomeState// Execute the second transition
var resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);
// resultingState2 is MyStates.Complete
```### Handle Invalid Transitions
If an invalid transition is attempted (no transition is defined for the current state and event), an InvalidTransitionException is thrown.
```csharp
try
{
// Attempt an invalid transition
var resultingState3 = await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);
}
catch (InvalidTransitionException ex)
{
// Handle the exception
Console.WriteLine($"Invalid transition: {ex.Message}");
}
```## Additional Features
### Guard Conditions
You can add guard conditions to transitions to control whether the transition should occur based on the event data.
```csharp
sm.AddTransition(
MyStates.SomeState,
MyEvents.SomeOtherRandomEvent,
MyStates.Complete,
SomeOtherMethodToExecuteAsync,
guard: data => data.Prop1 > 0
);```
If the guard condition returns false, a GuardConditionFailedException is thrown, and the transition does not occur.
### Cancellation Support
The action methods accept a CancellationToken, allowing transitions to be canceled if needed.
```csharp
var cts = new CancellationTokenSource();
cts.CancelAfter(500); // Cancel after 500mstry
{
await sm.GetNextAsync(MyEvents.SomethingHappened, data, cts.Token);
}
catch (TaskCanceledException)
{
Console.WriteLine("Transition was canceled.");
}
```### Logging
The state machine uses ILogger to log information about transitions, warnings, and errors.
- **Information:** Successful transitions.
- **Warning:** Undefined transitions or guard condition failures.
- **Error:** Exceptions thrown during actions.## Exception Handling
- **InvalidTransitionException:** Thrown when no transition is defined for the current state and event.
- **GuardConditionFailedException:** Thrown when a guard condition evaluates to false.
- **TaskCanceledException:** Thrown when a transition is canceled via a CancellationToken.
- **InvalidOperationException:** Thrown when attempting to add a duplicate transition.## Thread Safety
The `StateMachine` class is thread-safe and can handle concurrent transition attempts appropriately. It ensures that only one transition occurs at a time, maintaining the integrity of the `CurrentState`.
## Complete Example
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;// Define states and events
enum MyStates
{
Initial,
SomeState,
SomeOtherState,
Complete
}enum MyEvents
{
SomethingHappened,
SomethingElseHappened,
SomeOtherRandomEvent
}// DTO for event data
public class MyDto
{
public int Prop1 { get; set; }
}class Program
{
static async Task Main(string[] args)
{
// Create a logger
ILogger> logger = new NullLogger>();// Initialize the state machine
var sm = new StateMachine(MyStates.Initial, logger);// Define transitions
sm.AddTransition(MyStates.Initial, MyEvents.SomethingHappened, MyStates.SomeState, SomeMethodToExecuteAsync);sm.AddTransition(MyStates.SomeState, MyEvents.SomethingElseHappened, MyStates.Complete, SomeOtherMethodToExecuteAsync);
// Event data
var data = new MyDto { Prop1 = 1 };// Execute transitions
var resultingState = await sm.GetNextAsync(MyEvents.SomethingHappened, data);
Console.WriteLine($"State after first transition: {resultingState}");var resultingState2 = await sm.GetNextAsync(MyEvents.SomethingElseHappened, data);
Console.WriteLine($"State after second transition: {resultingState2}");// Handle invalid transition
try
{
await sm.GetNextAsync(MyEvents.SomeOtherRandomEvent, data);
}
catch (InvalidTransitionException ex)
{
Console.WriteLine($"Invalid transition: {ex.Message}");
}
}// Action methods
static async Task SomeMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeMethodToExecuteAsync");
}static async Task SomeOtherMethodToExecuteAsync(MyDto data, CancellationToken cancellationToken)
{
await Task.Delay(100, cancellationToken);
Console.WriteLine("Executed SomeOtherMethodToExecuteAsync");
}
}```
## Installation
To use the `StateMachine` class in your project, include the source code or compile it into a library that you can reference.## Dependencies
- **.NET Standard 2.0** or higher.
- `Microsoft.Extensions.Logging.Abstractions` for logging interfaces.Install via NuGet:
```bash
Install-Package Microsoft.Extensions.Logging.Abstractions
```## License
This project is licensed under the MIT License.## Contributing
Contributions are welcome! Please submit a pull request or open an issue to discuss improvements or features.## Contact
For questions or support, please open an issue on the GitHub repository.***
**Note:** Replace `SomeMethodToExecuteAsync` and `SomeOtherMethodToExecuteAsync` with your actual action methods. The DTO MyDto should contain the data relevant to your application.