Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/archi-Doc/BigMachines
BigMachines is State Machine library for .NET
https://github.com/archi-Doc/BigMachines
Last synced: about 1 month ago
JSON representation
BigMachines is State Machine library for .NET
- Host: GitHub
- URL: https://github.com/archi-Doc/BigMachines
- Owner: archi-Doc
- License: mit
- Created: 2021-06-26T14:47:50.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-09-18T13:38:01.000Z (3 months ago)
- Last Synced: 2024-11-01T21:36:40.310Z (about 1 month ago)
- Language: C#
- Size: 842 KB
- Stars: 37
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- RSCG_Examples - BigMachines - Doc/BigMachines (Contributors Welcome for those / 1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category)
- csharp-source-generators - BigMachines - ![stars](https://img.shields.io/github/stars/archi-Doc/BigMachines?style=flat-square&cacheSeconds=604800) ![last commit](https://img.shields.io/github/last-commit/archi-Doc/BigMachines?style=flat-square&cacheSeconds=86400) BigMachines is State Machine library for .NET. (Source Generators / Other)
README
## BigMachines is State Machine library for .NET
![Nuget](https://img.shields.io/nuget/v/BigMachines) ![Build and Test](https://github.com/archi-Doc/BigMachines/workflows/Build%20and%20Test/badge.svg)- Very versatile and easy to use.
- Running machines and sending commands to each machine is designed to be **lock-free**.
- Full serialization features integrated with [Tinyhand](https://github.com/archi-Doc/Tinyhand), [ValueLink](https://github.com/archi-Doc/ValueLink), [CrystalData](https://github.com/archi-Doc/CrystalData).
- Simplifies complex and long-running tasks.
## Table of Contents
- [Requirements](#requirements)
- [Quick Start](#quick-start)
- [Machine Types](#machine-types)
- [Identifier](#identifier)
- [Machine Class](#machine-class)
- [Other](#other)
- [Service provider](#service-provider)
- [Generic machine](#generic-machine)
- [Loop checker](#loop-checker)
- [Exception handling](#exception-handling)## Requirements
**Visual Studio 2022** or later for Source Generator V2.
**C# 12** or later for generated codes.
**.NET 8** or later target framework.
## Quick Start
Install **BigMachines** using Package Manager Console.
```
Install-Package BigMachines
```This is a small sample code to use **BigMachines**.
```csharp
using System;
using System.Threading.Tasks;
using Arc.Threading;
using BigMachines;namespace QuickStart;
// Create a BigMachine class that acts as the root for managing machines.
// In particular, define an empty partial class, add a BigMachineObject attribute, and then add AddMachine attributes for the Machine you want to include.
[BigMachineObject]
[AddMachine]
public partial class BigMachine { }[MachineObject] // Add a MachineObject attribute.
public partial class FirstMachine : Machine // Inherit Machine class. The type of an identifier is int.
{
public FirstMachine()
{
this.DefaultTimeout = TimeSpan.FromSeconds(1); // The default time interval for machine execution.
this.Lifespan = TimeSpan.FromSeconds(5); // The time until the machine automatically terminates.
}public int Count { get; set; }
[StateMethod(0)] // Add a StateMethod attribute and set the state method id 0 (default state).
protected StateResult Initial(StateParameter parameter)
{// This code is inside the machine's exclusive lock.
Console.WriteLine($"FirstMachine {this.Identifier}: Initial");
this.ChangeState(FirstMachine.State.One); // Change to state One.
return StateResult.Continue; // Continue (StateResult.Terminate to terminate machine).
}[StateMethod] // If a state method id is not specified, the hash of the method name is used.
protected StateResult One(StateParameter parameter)
{
Console.WriteLine($"FirstMachine {this.Identifier}: One - {this.Count++}");
return StateResult.Continue;
}[CommandMethod] // Add a CommandMethod attribute to a method which receives and processes commands.
protected CommandResult TestCommand(string message)
{
Console.WriteLine($"Command received: {message}");
return CommandResult.Success;
}protected override void OnTermination()
{
Console.WriteLine($"FirstMachine {this.Identifier}: Terminated");
ThreadCore.Root.Terminate(); // Send a termination signal to the root.
}
}public class Program
{
public static async Task Main(string[] args)
{
var bigMachine = new BigMachine(); // Create a BigMachine instance.
bigMachine.Start(ThreadCore.Root); // Launch BigMachine to run machines and change the parent of the BigMachine thread to the application thread.var testMachine = bigMachine.FirstMachine.GetOrCreate(42); // Machine is created via an interface class and the identifier, not the machine class itself.
testMachine.TryGetState(out var state); // Get the current state. You can operate machines using the interface class.
Console.WriteLine($"FirstMachine state: {state}");testMachine = bigMachine.FirstMachine.GetOrCreate(42); // Get the created machine.
testMachine.RunAsync().Wait(); // Run the machine manually.
Console.WriteLine();var testControl = bigMachine.FirstMachine; // Control is a collection of machines.
Console.WriteLine("Enumerates identifiers.");
foreach (var x in testControl.GetIdentifiers())
{
Console.WriteLine($"Machine Id: {x}");
}Console.WriteLine();
await testMachine.Command.TestCommand("Test message"); // Send a command to the machine.
Console.WriteLine();await ThreadCore.Root.WaitForTerminationAsync(-1); // Wait for the termination infinitely.
}
}
```## Machine Types
**BigMachines** supports several types of machine.
### Passive Machine
Passive machine can be run and the state can be changed by an external operation.
```csharp
[MachineObject]
public partial class PassiveMachine : Machine
{
public static async Task Test(BigMachine bigMachine)
{
var machine = bigMachine.PassiveMachine.GetOrCreate(0);await machine.Command.ReceiveString("message 1"); // Send a command.
await machine.RunAsync(); // Manually run the machine.
var result = machine.ChangeState(State.First); // Change the state from State.Initial to State.First
Console.WriteLine(result.ToString());
await machine.RunAsync(); // Manually run the machine.result = machine.ChangeState(State.Second); // Change the state from State.First to State.Second (denied)
Console.WriteLine(result.ToString());
await machine.RunAsync(); // Manually run the machine.result = machine.ChangeState(State.Second); // Change the state from State.First to State.Second (approved)
Console.WriteLine(result.ToString());
await machine.RunAsync(); // Manually run the machine.
}public PassiveMachine()
{
}public int Count { get; set; }
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
{
Console.WriteLine($"PassiveMachine: Initial - {this.Count++}");
return StateResult.Continue;
}[StateMethod]
protected StateResult First(StateParameter parameter)
{
Console.WriteLine($"PassiveMachine: First - {this.Count++}");
return StateResult.Continue;
}protected bool FirstCanExit()
{// State Name + "CanExit": Determines if it is possible to change from the state.
return true;
}[StateMethod]
protected StateResult Second(StateParameter parameter)
{
Console.WriteLine($"PassiveMachine: Second - {this.Count++}");
return StateResult.Continue;
}protected bool SecondCanEnter()
{// State Name + "CanEnter": Determines if it is possible to change to the state.
var result = this.Count > 2;
var message = result ? "Approved" : "Denied";
Console.WriteLine($"PassiveMachine: {this.GetState().ToString()} -> {State.Second.ToString()}: {message}");
return result;
}[CommandMethod]
protected CommandResult ReceiveString(string message)
{
Console.WriteLine($"PassiveMachine command: {message}");
return CommandResult.Success;
}
}
```### Intermittent Machine
Intermittent machine is a machine that runs at regular intervals.
```csharp
[MachineObject]
public partial class IntermittentMachine : Machine
{
public static void Test(BigMachine bigMachine)
{
// The machine will run at regular intervals (1 second).
var machine = bigMachine.IntermittentMachine.GetOrCreate(0);
}public IntermittentMachine()
{
this.DefaultTimeout = TimeSpan.FromSeconds(1); // Default time interval for machine execution.
this.Lifespan = TimeSpan.FromSeconds(5); // The time until the machine automatically terminates.
}public int Count { get; set; }
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
{
Console.WriteLine($"IntermittentMachine: Initial - {this.Count++}");
if (this.Count > 2)
{
this.ChangeState(State.First);
}return StateResult.Continue;
}[StateMethod(1)]
protected StateResult First(StateParameter parameter)
{
Console.WriteLine($"IntermittentMachine: First - {this.Count++}");
this.TimeUntilRun = TimeSpan.FromSeconds(0.5); // Change the timeout of the machine.
return StateResult.Continue;
}
}
```### Continuous Machine
Continuous machine is different from passive and intermittent machine (passive and intermittent machine are virtually the same).
It's designed for heavy and time-consuming tasks.
```csharp
[TinyhandObject]
[MachineObject(Control = MachineControlKind.Sequential, NumberOfTasks = 1)]
public partial class SequentialMachine : Machine
{// SequentialMachine executes one at a time, in the order of their creation.
public static void Test(BigMachine bigMachine)
{
bigMachine.SequentialMachine.TryCreate(1);
bigMachine.SequentialMachine.TryCreate(2);
bigMachine.SequentialMachine.TryCreate(3);
}public SequentialMachine()
{
this.Lifespan = TimeSpan.FromSeconds(5);
this.DefaultTimeout = TimeSpan.FromSeconds(1);
}[Key(10)]
public int Count { get; set; }[StateMethod(0)]
protected async Task Initial(StateParameter parameter)
{
Console.WriteLine($"SequentialMachine machine[{this.Identifier}]: {this.Count++}");await Task.Delay(500).ConfigureAwait(false); // Some heavy task
if (this.Count >= 3)
{
return StateResult.Terminate;
}return StateResult.Continue;
}
}
```To improve response and share resource, heavy task should not be done at once, but divided into several smaller tasks.
## Serialization
Thanks to [Tinyhand](https://github.com/archi-Doc/CrystalData) and [CrystalData](https://github.com/archi-Doc/CrystalData), serialization and persistence of **BigMachine** is very easy.
Add the `TinyhandObject` attribute to the **Machine** class, and use the code below to serialize and deserialize.
```csharp
[TinyhandObject]
[MachineObject]
public partial class TestMachine : Machine {}
``````csharp
var bin = TinyhandSerializer.Serialize(bigMachine);
var bigMachine2 = TinyhandSerializer.Deserialize(bin);
```If you want to save to a file, register it with **CrystalData** as shown in the following code.
```csharp
var builder = new CrystalControl.Builder()
.ConfigureCrystal(context =>
{
context.AddCrystal(new()
{
FileConfiguration = new LocalFileConfiguration("Data/BigMachine.tinyhand"),
SavePolicy = SavePolicy.Manual,
SaveFormat = SaveFormat.Utf8,
NumberOfFileHistories = 3,
});
});
```## Identifier
`Identifier` is a key concept of `BigMachines`.
## Other
### Virtual methods
These are virtual functions of `Machine` class.
You can override and use them when necessary.
```csharp
///
/// Called when the machine is newly created.
/// Note that it is not called after deserialization.
/// -> -> .
///
/// The parameters used when creating a machine.
protected virtual void OnCreate(object? createParam)
{
}///
/// Called when the machine is ready to start
/// Note that it is called before the actual state method.
/// -> -> .
///
protected virtual void OnStart()
{
}///
/// Called when the machine is terminating.
/// This code is inside a semaphore lock.
/// -> -> .
///
protected virtual void OnTerminate()
{
}
```### Service provider
Since the machine is independent, you cannot pass parameters directly when creating an instance (and mainly for the deserialization process).
```csharp
```
```csharp
public class SomeService
{
public void Print(string? text) => Console.WriteLine($"Some service : {text}");
}// Machine depends on SomeService.
[MachineObject(UseServiceProvider = true)]
public partial class ServiceProviderMachine : Machine
{
public static void Test(BigMachine bigMachine)
{
bigMachine.ServiceProviderMachine.GetOrCreate(0, "A"); // Create a machine and set a parameter.
}public ServiceProviderMachine(SomeService service)
: base()
{
this.Service = service;
this.DefaultTimeout = TimeSpan.FromSeconds(1);
this.Lifespan = TimeSpan.FromSeconds(3);
}protected override void OnCreation(object? createParam)
{// Receives the parameter at the time of creation. Note that it is not called during deserialization.
this.Text = (string?)createParam;
}public SomeService Service { get; }
public string? Text { get; set; }
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
{
this.Service.Print(this.Text);
return StateResult.Continue;
}
}
```### Recursive calls checker
Relationships between machines can become complicated, and may lead to circular command issuing.
```csharp
```
### Exception handling
Each machine is designed to run independently.
So exceptions thrown in machines are handled by **BigMachine**'s main thread (`BigMachine.Core`), not by the caller.
In detail, exceptions are registered to **BigMachine** using `BigMachine.ReportException()`, and handled by the following method in **BigMachine**'s main thread.
```cahrp
private static void DefaultExceptionHandler(BigMachineException exception)
{
throw exception.Exception;
}
```You can set a custom exception handler using `BigMachine.SetExceptionHandler()`.