{"id":13629454,"url":"https://github.com/archi-Doc/BigMachines","last_synced_at":"2025-04-17T09:33:55.203Z","repository":{"id":41589989,"uuid":"380525751","full_name":"archi-Doc/BigMachines","owner":"archi-Doc","description":"BigMachines is State Machine library for .NET","archived":false,"fork":false,"pushed_at":"2024-09-18T13:38:01.000Z","size":862,"stargazers_count":37,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-01T21:36:40.310Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/archi-Doc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-26T14:47:50.000Z","updated_at":"2024-10-21T19:39:58.000Z","dependencies_parsed_at":"2024-01-06T09:53:30.914Z","dependency_job_id":"d0280698-8f26-4449-aae9-5961c9e7a53e","html_url":"https://github.com/archi-Doc/BigMachines","commit_stats":{"total_commits":279,"total_committers":1,"mean_commits":279.0,"dds":0.0,"last_synced_commit":"8b7813649bc99d01c9d1a26a3ab5dfb9d9504d87"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FBigMachines","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FBigMachines/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FBigMachines/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archi-Doc%2FBigMachines/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/archi-Doc","download_url":"https://codeload.github.com/archi-Doc/BigMachines/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751131,"owners_count":17196576,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T22:01:11.155Z","updated_at":"2024-11-08T20:30:57.979Z","avatar_url":"https://github.com/archi-Doc.png","language":"C#","readme":"## BigMachines is State Machine library for .NET\n![Nuget](https://img.shields.io/nuget/v/BigMachines) ![Build and Test](https://github.com/archi-Doc/BigMachines/workflows/Build%20and%20Test/badge.svg)\n\n- Very versatile and easy to use.\n\n- Running machines and sending commands to each machine is designed to be **lock-free**.\n\n- 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).\n\n- Simplifies complex and long-running tasks.\n\n  \n\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Quick Start](#quick-start)\n- [Machine Types](#machine-types)\n- [Identifier](#identifier)\n- [Machine Class](#machine-class)\n- [Other](#other)\n  - [Service provider](#service-provider)\n  - [Generic machine](#generic-machine)\n  - [Loop checker](#loop-checker)\n  - [Exception handling](#exception-handling)\n\n\n\n## Requirements\n\n**Visual Studio 2022** or later for Source Generator V2.\n\n**C# 12** or later for generated codes.\n\n**.NET 8** or later target framework.\n\n\n\n## Quick Start\n\nInstall **BigMachines** using Package Manager Console.\n\n```\nInstall-Package BigMachines\n```\n\nThis is a small sample code to use **BigMachines**.\n\n```csharp\nusing System;\nusing System.Threading.Tasks;\nusing Arc.Threading;\nusing BigMachines;\n\nnamespace QuickStart;\n\n// Create a BigMachine class that acts as the root for managing machines.\n// In particular, define an empty partial class, add a BigMachineObject attribute, and then add AddMachine attributes for the Machine you want to include.\n[BigMachineObject]\n[AddMachine\u003cFirstMachine\u003e]\npublic partial class BigMachine { }\n\n[MachineObject] // Add a MachineObject attribute.\npublic partial class FirstMachine : Machine\u003cint\u003e // Inherit Machine class. The type of an identifier is int.\n{\n    public FirstMachine()\n    {\n        this.DefaultTimeout = TimeSpan.FromSeconds(1); // The default time interval for machine execution.\n        this.Lifespan = TimeSpan.FromSeconds(5); // The time until the machine automatically terminates.\n    }\n\n    public int Count { get; set; }\n\n    [StateMethod(0)] // Add a StateMethod attribute and set the state method id 0 (default state).\n    protected StateResult Initial(StateParameter parameter)\n    {// This code is inside the machine's exclusive lock.\n        Console.WriteLine($\"FirstMachine {this.Identifier}: Initial\");\n        this.ChangeState(FirstMachine.State.One); // Change to state One.\n        return StateResult.Continue; // Continue (StateResult.Terminate to terminate machine).\n    }\n\n    [StateMethod] // If a state method id is not specified, the hash of the method name is used.\n    protected StateResult One(StateParameter parameter)\n    {\n        Console.WriteLine($\"FirstMachine {this.Identifier}: One - {this.Count++}\");\n        return StateResult.Continue;\n    }\n\n    [CommandMethod] // Add a CommandMethod attribute to a method which receives and processes commands.\n    protected CommandResult TestCommand(string message)\n    {\n        Console.WriteLine($\"Command received: {message}\");\n        return CommandResult.Success;\n    }\n\n    protected override void OnTermination()\n    {\n        Console.WriteLine($\"FirstMachine {this.Identifier}: Terminated\");\n        ThreadCore.Root.Terminate(); // Send a termination signal to the root.\n    }\n}\n\npublic class Program\n{\n    public static async Task Main(string[] args)\n    {\n        var bigMachine = new BigMachine(); // Create a BigMachine instance.\n        bigMachine.Start(ThreadCore.Root); // Launch BigMachine to run machines and change the parent of the BigMachine thread to the application thread.\n\n        var testMachine = bigMachine.FirstMachine.GetOrCreate(42); // Machine is created via an interface class and the identifier, not the machine class itself.\n        testMachine.TryGetState(out var state); // Get the current state. You can operate machines using the interface class.\n        Console.WriteLine($\"FirstMachine state: {state}\");\n\n        testMachine = bigMachine.FirstMachine.GetOrCreate(42); // Get the created machine.\n        testMachine.RunAsync().Wait(); // Run the machine manually.\n        Console.WriteLine();\n\n        var testControl = bigMachine.FirstMachine; // Control is a collection of machines.\n\n        Console.WriteLine(\"Enumerates identifiers.\");\n        foreach (var x in testControl.GetIdentifiers())\n        {\n            Console.WriteLine($\"Machine Id: {x}\");\n        }\n\n        Console.WriteLine();\n\n        await testMachine.Command.TestCommand(\"Test message\"); // Send a command to the machine.\n        Console.WriteLine();\n\n        await ThreadCore.Root.WaitForTerminationAsync(-1); // Wait for the termination infinitely.\n    }\n}\n```\n\n\n\n## Machine Types\n\n**BigMachines** supports several types of machine.\n\n### Passive Machine\n\nPassive machine can be run and the state can be changed by an external operation.\n\n```csharp\n[MachineObject]\npublic partial class PassiveMachine : Machine\u003cint\u003e\n{\n    public static async Task Test(BigMachine bigMachine)\n    {\n        var machine = bigMachine.PassiveMachine.GetOrCreate(0);\n\n        await machine.Command.ReceiveString(\"message 1\"); // Send a command.\n\n        await machine.RunAsync(); // Manually run the machine.\n\n        var result = machine.ChangeState(State.First); // Change the state from State.Initial to State.First\n        Console.WriteLine(result.ToString());\n        await machine.RunAsync(); // Manually run the machine.\n\n        result = machine.ChangeState(State.Second); // Change the state from State.First to State.Second (denied)\n        Console.WriteLine(result.ToString());\n        await machine.RunAsync(); // Manually run the machine.\n\n        result = machine.ChangeState(State.Second); // Change the state from State.First to State.Second (approved)\n        Console.WriteLine(result.ToString());\n        await machine.RunAsync(); // Manually run the machine.\n    }\n\n    public PassiveMachine()\n    {\n    }\n\n    public int Count { get; set; }\n\n    [StateMethod(0)]\n    protected StateResult Initial(StateParameter parameter)\n    {\n        Console.WriteLine($\"PassiveMachine: Initial - {this.Count++}\");\n        return StateResult.Continue;\n    }\n\n    [StateMethod]\n    protected StateResult First(StateParameter parameter)\n    {\n        Console.WriteLine($\"PassiveMachine: First - {this.Count++}\");\n        return StateResult.Continue;\n    }\n\n    protected bool FirstCanExit()\n    {// State Name + \"CanExit\": Determines if it is possible to change from the state.\n        return true;\n    }\n\n    [StateMethod]\n    protected StateResult Second(StateParameter parameter)\n    {\n        Console.WriteLine($\"PassiveMachine: Second - {this.Count++}\");\n        return StateResult.Continue;\n    }\n\n    protected bool SecondCanEnter()\n    {// State Name + \"CanEnter\": Determines if it is possible to change to the state.\n        var result = this.Count \u003e 2;\n        var message = result ? \"Approved\" : \"Denied\";\n        Console.WriteLine($\"PassiveMachine: {this.GetState().ToString()} -\u003e {State.Second.ToString()}: {message}\");\n        return result;\n    }\n\n    [CommandMethod]\n    protected CommandResult ReceiveString(string message)\n    {\n        Console.WriteLine($\"PassiveMachine command: {message}\");\n        return CommandResult.Success;\n    }\n}\n```\n\n\n\n### Intermittent Machine\n\nIntermittent machine is a machine that runs at regular intervals.\n\n```csharp\n[MachineObject]\npublic partial class IntermittentMachine : Machine\u003cint\u003e\n{\n    public static void Test(BigMachine bigMachine)\n    {\n        // The machine will run at regular intervals (1 second).\n        var machine = bigMachine.IntermittentMachine.GetOrCreate(0);\n    }\n\n    public IntermittentMachine()\n    {\n        this.DefaultTimeout = TimeSpan.FromSeconds(1); // Default time interval for machine execution.\n        this.Lifespan = TimeSpan.FromSeconds(5); // The time until the machine automatically terminates.\n    }\n\n    public int Count { get; set; }\n\n    [StateMethod(0)]\n    protected StateResult Initial(StateParameter parameter)\n    {\n        Console.WriteLine($\"IntermittentMachine: Initial - {this.Count++}\");\n        if (this.Count \u003e 2)\n        {\n            this.ChangeState(State.First);\n        }\n\n        return StateResult.Continue;\n    }\n\n    [StateMethod(1)]\n    protected StateResult First(StateParameter parameter)\n    {\n        Console.WriteLine($\"IntermittentMachine: First - {this.Count++}\");\n        this.TimeUntilRun = TimeSpan.FromSeconds(0.5); // Change the timeout of the machine.\n        return StateResult.Continue;\n    }\n}\n```\n\n\n\n### Continuous Machine\n\nContinuous machine is different from passive and intermittent machine (passive and intermittent machine are virtually the same).\n\nIt's designed for heavy and time-consuming tasks.\n\n```csharp\n[TinyhandObject]\n[MachineObject(Control = MachineControlKind.Sequential, NumberOfTasks = 1)]\npublic partial class SequentialMachine : Machine\u003cint\u003e\n{// SequentialMachine executes one at a time, in the order of their creation.\n    public static void Test(BigMachine bigMachine)\n    {\n        bigMachine.SequentialMachine.TryCreate(1);\n        bigMachine.SequentialMachine.TryCreate(2);\n        bigMachine.SequentialMachine.TryCreate(3);\n    }\n\n    public SequentialMachine()\n    {\n        this.Lifespan = TimeSpan.FromSeconds(5);\n        this.DefaultTimeout = TimeSpan.FromSeconds(1);\n    }\n\n    [Key(10)]\n    public int Count { get; set; }\n\n    [StateMethod(0)]\n    protected async Task\u003cStateResult\u003e Initial(StateParameter parameter)\n    {\n        Console.WriteLine($\"SequentialMachine machine[{this.Identifier}]: {this.Count++}\");\n\n        await Task.Delay(500).ConfigureAwait(false); // Some heavy task\n\n        if (this.Count \u003e= 3)\n        {\n            return StateResult.Terminate;\n        }\n\n        return StateResult.Continue;\n    }\n}\n```\n\nTo improve response and share resource, heavy task should not be done at once, but divided into several smaller tasks.\n\n\n\n## Serialization\n\nThanks to [Tinyhand](https://github.com/archi-Doc/CrystalData) and [CrystalData](https://github.com/archi-Doc/CrystalData), serialization and persistence of **BigMachine** is very easy.\n\nAdd the `TinyhandObject` attribute to the **Machine** class, and use the code below to serialize and deserialize.\n\n```csharp\n[TinyhandObject]\n[MachineObject]\npublic partial class TestMachine : Machine\u003cint\u003e {}\n```\n\n```csharp\nvar bin = TinyhandSerializer.Serialize(bigMachine);\nvar bigMachine2 = TinyhandSerializer.Deserialize\u003cBigMachine\u003e(bin);\n```\n\n\n\nIf you want to save to a file, register it with **CrystalData** as shown in the following code.\n\n```csharp\nvar builder = new CrystalControl.Builder()\n    .ConfigureCrystal(context =\u003e\n    {\n        context.AddCrystal\u003cBigMachine\u003e(new()\n        {\n            FileConfiguration = new LocalFileConfiguration(\"Data/BigMachine.tinyhand\"),\n            SavePolicy = SavePolicy.Manual,\n            SaveFormat = SaveFormat.Utf8,\n            NumberOfFileHistories = 3,\n        });\n    });\n```\n\n\n\n## Identifier\n\n`Identifier` is a key concept of `BigMachines`.\n\n\n\n## Other\n\n### Virtual methods\n\nThese are virtual functions of `Machine` class.\n\nYou can override and use them when necessary.\n\n```csharp\n/// \u003csummary\u003e\n/// Called when the machine is newly created.\u003cbr/\u003e\n/// Note that it is not called after deserialization.\u003cbr/\u003e\n/// \u003csee cref=\"OnCreate(object?)\"/\u003e -\u003e \u003csee cref=\"OnStart()\"/\u003e -\u003e \u003csee cref=\"OnTerminate\"/\u003e.\n/// \u003c/summary\u003e\n/// \u003cparam name=\"createParam\"\u003eThe parameters used when creating a machine.\u003c/param\u003e\nprotected virtual void OnCreate(object? createParam)\n{\n}\n\n/// \u003csummary\u003e\n/// Called when the machine is ready to start\u003cbr/\u003e\n/// Note that it is called before the actual state method.\u003cbr/\u003e\n/// \u003csee cref=\"OnCreate(object?)\"/\u003e -\u003e \u003csee cref=\"OnStart()\"/\u003e -\u003e \u003csee cref=\"OnTerminate\"/\u003e.\n/// \u003c/summary\u003e\nprotected virtual void OnStart()\n{\n}\n\n/// \u003csummary\u003e\n/// Called when the machine is terminating.\u003cbr/\u003e\n///  This code is inside a semaphore lock.\u003cbr/\u003e\n///  \u003csee cref=\"OnCreate(object?)\"/\u003e -\u003e \u003csee cref=\"OnStart()\"/\u003e -\u003e \u003csee cref=\"OnTerminate\"/\u003e.\n/// \u003c/summary\u003e\nprotected virtual void OnTerminate()\n{\n}\n```\n\n\n\n### Service provider\n\nSince the machine is independent, you cannot pass parameters directly when creating an instance (and mainly for the deserialization process).\n\n```csharp\n\n```\n\n```csharp\npublic class SomeService\n{\n    public void Print(string? text) =\u003e Console.WriteLine($\"Some service : {text}\");\n}\n\n// Machine depends on SomeService.\n[MachineObject(UseServiceProvider = true)]\npublic partial class ServiceProviderMachine : Machine\u003cint\u003e\n{\n    public static void Test(BigMachine bigMachine)\n    {\n        bigMachine.ServiceProviderMachine.GetOrCreate(0, \"A\"); // Create a machine and set a parameter.\n    }\n\n    public ServiceProviderMachine(SomeService service)\n        : base()\n    {\n        this.Service = service;\n        this.DefaultTimeout = TimeSpan.FromSeconds(1);\n        this.Lifespan = TimeSpan.FromSeconds(3);\n    }\n\n    protected override void OnCreation(object? createParam)\n    {// Receives the parameter at the time of creation. Note that it is not called during deserialization.\n        this.Text = (string?)createParam;\n    }\n\n    public SomeService Service { get; }\n\n    public string? Text { get; set; }\n\n    [StateMethod(0)]\n    protected StateResult Initial(StateParameter parameter)\n    {\n        this.Service.Print(this.Text);\n        return StateResult.Continue;\n    }\n}\n```\n\n\n\n### Recursive calls checker\n\nRelationships between machines can become complicated, and may lead to circular command issuing.\n\n```csharp\n\n```\n\n\n\n### Exception handling\n\nEach machine is designed to run independently.\n\nSo exceptions thrown in machines are handled by **BigMachine**'s main thread (`BigMachine.Core`), not by the caller.\n\nIn detail, exceptions are registered to **BigMachine** using `BigMachine.ReportException()`, and handled by the following method in **BigMachine**'s main thread.\n\n```cahrp\nprivate static void DefaultExceptionHandler(BigMachineException exception)\n{\n    throw exception.Exception;\n}\n```\n\nYou can set a custom exception handler using `BigMachine.SetExceptionHandler()`.\n\n","funding_links":[],"categories":["Contributors Welcome for those","Source Generators"],"sub_categories":["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","Other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchi-Doc%2FBigMachines","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchi-Doc%2FBigMachines","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchi-Doc%2FBigMachines/lists"}