BigMachines 0.57.0
dotnet add package BigMachines --version 0.57.0
NuGet\Install-Package BigMachines -Version 0.57.0
<PackageReference Include="BigMachines" Version="0.57.0" />
<PackageVersion Include="BigMachines" Version="0.57.0" />
<PackageReference Include="BigMachines" />
paket add BigMachines --version 0.57.0
#r "nuget: BigMachines, 0.57.0"
#:package BigMachines@0.57.0
#addin nuget:?package=BigMachines&version=0.57.0
#tool nuget:?package=BigMachines&version=0.57.0
BigMachines
BigMachines is a source-generated state-machine library for .NET. It provides typed machine controls, asynchronous commands, scheduled execution, lifecycle management, Tinyhand serialization, and optional CrystalData persistence.
Contents
- Requirements
- Installation
- Quick start
- Core concepts
- Machine controls
- Execution and lifecycle
- States and commands
- Serialization and persistence
- Dependency injection
- Exceptions
- Generic, external, and private machines
Requirements
- .NET 10 or later
- C# 14 or later
- Visual Studio 2026 or another build environment that supports the required .NET SDK and source generators
Installation
Install the package with the .NET CLI:
dotnet add package BigMachines
The package includes the BigMachines source generator.
Quick start
Define an empty partial root class, add the machines it owns, and mark each machine as partial.
using System;
using System.Threading.Tasks;
using Arc.Threading;
using BigMachines;
namespace QuickStart;
[BigMachineObject]
[AddMachine<CounterMachine>]
public partial class AppMachines;
[MachineObject]
public partial class CounterMachine : Machine<int>
{
public CounterMachine()
{
this.DefaultTimeout = TimeSpan.FromSeconds(1);
this.Lifespan = TimeSpan.FromSeconds(5);
}
public int Count { get; private set; }
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
{
Console.WriteLine($"Machine {this.Identifier}: Initial");
this.ChangeState(State.Counting);
return StateResult.Continue;
}
[StateMethod]
protected StateResult Counting(StateParameter parameter)
{
Console.WriteLine($"Machine {this.Identifier}: {this.Count++}");
return StateResult.Continue;
}
[CommandMethod]
protected CommandResult Print(string message)
{
Console.WriteLine(message);
return CommandResult.Success;
}
protected override void OnTerminate()
{
this.BigMachine.ExecutionGroup.RequestTermination();
}
}
public static class Program
{
public static async Task Main()
{
var root = new ExecutionRoot();
var machines = new AppMachines(root);
machines.Start();
var counter = machines.CounterMachine.GetOrCreate(42);
await counter.Command.Print("Hello from BigMachines");
await counter.RunAsync();
await root.WaitForTermination();
}
}
The generator adds the root constructor, typed controls, machine interface, state enum, and command proxy.
Core concepts
- A big-machine root derives from
BigMachineBasethrough generated code and owns the machine controls declared withAddMachine<TMachine>or discovered withBigMachineObject(Inclusive = true). - A machine derives from
MachineorMachine<TIdentifier>and contains state and command methods. - A generated machine interface is the public handle used to inspect, run, pause, unpause, or terminate a machine.
- A machine control creates, finds, enumerates, and schedules machine instances.
- An
ExecutionRootowns the execution lifetime. Construct the generated root with it, callStart(), and request termination through the root or the generated root'sExecutionGroup.
Machine controls
MachineObjectAttribute.Control selects how instances are managed.
| Control | Purpose |
|---|---|
Default |
Uses Single for Machine and Unordered for Machine<TIdentifier>. |
Single |
Manages at most one instance of a machine type. |
Unordered |
Manages multiple identified machines without ordering guarantees. |
Sequential |
Queues identified machines in creation order. NumberOfTasks sets the number of dedicated workers. |
Common control operations include:
var machine = machines.CounterMachine.GetOrCreate(42);
if (machines.CounterMachine.TryGet(42, out var existing))
{
await existing.RunAsync();
}
foreach (var identifier in machines.CounterMachine.GetIdentifiers())
{
Console.WriteLine(identifier);
}
CreateAlways terminates an existing matching instance before creating its replacement. TryCreate is available on sequential and manual controls when creation must fail instead of returning an existing machine.
Machines marked with MachineObject(Private = true) are not added to a root automatically. They can be managed through ManualControl or added explicitly when appropriate.
Execution and lifecycle
A machine can run manually, on a timer, or through a sequential control.
DefaultTimeoutsets the periodic interval.TimeSpan.Zerodisables interval execution.SetTimeUntilRunchanges the remaining delay.SetNextRunTimeschedules an absolute UTC execution time.Lifespanterminates a machine after the remaining duration reaches zero.TerminationTimeterminates a machine at an absolute time.
Use the generated interface for runtime control:
await machine.RunAsync();
machine.PauseMachine();
machine.UnpauseMachine();
machine.SetNextRunTimeFromNow(TimeSpan.FromMinutes(1));
machine.TerminateMachine();
The lifecycle callbacks are invoked in this order for a newly created machine:
OnCreate(createParam) -> OnStart() -> OnTerminate()
OnCreate is not called after deserialization. OnStart is called after both creation and deserialization. OnTerminate runs while the machine semaphore is held.
States and commands
Mark state handlers with StateMethodAttribute. If a machine defines state handlers, state ID 0 is required and is the initial state. When an ID is omitted, the generator derives it from the method name.
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
{
this.ChangeState(State.Ready, rerun: true);
return StateResult.Continue;
}
A method named <StateName>CanExit can reject leaving a state, and <StateName>CanEnter can reject entering one. Both methods return bool.
Mark command handlers with CommandMethodAttribute. The generator exposes them as asynchronous methods on machine.Command and converts thrown exceptions into CommandResult.Failure.
[CommandMethod]
protected CommandResult<string> Echo(string value)
=> new(value);
var result = await machine.Command.Echo("message");
if (result.Result == CommandResult.Success)
{
Console.WriteLine(result.Response);
}
Commands acquire the machine semaphore by default. Set CommandMethod(WithLock = false) only when the handler is safe to run concurrently. All = true generates an extension that sends the command to every instance managed by the root.
Serialization and persistence
BigMachines integrates with Tinyhand and ValueLink. Apply TinyhandObjectAttribute to each concrete machine whose state must be serialized. Do not apply it only to the abstract Machine base classes.
using Tinyhand;
[TinyhandObject]
[MachineObject]
public partial class PersistentMachine : Machine<int>
{
[Key(10)]
public int Count { get; set; }
[StateMethod(0)]
protected StateResult Initial(StateParameter parameter)
=> StateResult.Continue;
}
var data = TinyhandSerializer.Serialize(machines);
var restored = TinyhandSerializer.Deserialize<AppMachines>(data);
The base machine uses reserved Tinyhand keys for runtime state. Use key 10 or greater for machine data, as shown in the repository examples. A machine without TinyhandObjectAttribute remains runtime-only. AddMachine(Volatile = true) excludes that control from root persistence.
BigMachines emits the closed formatter registrations required by Tinyhand 0.144 and NativeAOT. Closed generic machines should be listed explicitly with AddMachine<GenericMachine<ConcreteType>> so the generator can register their concrete types.
Enable NativeAOT in the application project, not in the analyzer project:
<PropertyGroup>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
The repository's NativeAotTest project publishes with trimming warnings treated as errors and executes Tinyhand round trips for single, unordered, and sequential controls.
For file persistence, register the generated root with CrystalData:
dotnet add package CrystalData
var builder = new CrystalUnit.Builder()
.ConfigureCrystal(context =>
{
context.SetJournal(
new SimpleJournalConfiguration(
new LocalDirectoryConfiguration("Data/Journal")));
context.AddCrystal<AppMachines>(new()
{
FileConfiguration = new LocalFileConfiguration("Data/AppMachines.tinyhand"),
SaveFormat = SaveFormat.Utf8,
NumberOfFileHistories = 3,
});
});
Use CrystalData 0.47.0 or later with Tinyhand 0.144 to avoid references to the removed dynamic formatter-registration API.
Dependency injection
Set MachineObject(UseServiceProvider = true) when a machine requires constructor injection. Register the machine and its dependencies, then assign the built provider to TinyhandSerializer.ServiceProvider before machines are created or deserialized.
var services = new ServiceCollection()
.AddSingleton<Clock>()
.AddTransient<ServiceMachine>()
.BuildServiceProvider();
TinyhandSerializer.ServiceProvider = services;
[MachineObject(UseServiceProvider = true)]
public partial class ServiceMachine : Machine<int>
{
public ServiceMachine(Clock clock)
{
this.Clock = clock;
}
private Clock Clock { get; }
}
Pass per-instance data through GetOrCreate(identifier, createParam) and receive it in OnCreate. Constructor dependencies and creation parameters serve different purposes.
Exceptions
Exceptions thrown by generated state or command dispatch are wrapped in BigMachineException and queued on the root. The default handler writes them to the console. Install a custom handler through IBigMachine when the application needs logging or another policy:
((IBigMachine)machines).SetExceptionHandler(exception =>
{
Console.Error.WriteLine(exception);
});
Command callers receive CommandResult.Failure when a command handler throws. A command sent to a terminated machine returns CommandResult.Terminated.
Generic, external, and private machines
Constructed generic machines and machines from referenced assemblies can be added explicitly:
[BigMachineObject]
[AddMachine<GenericMachine<string>>]
[AddMachine<ExternalLibrary.WorkerMachine>]
public partial class AppMachines;
BigMachineObject(Inclusive = true) includes eligible non-private machines discovered in the current assembly. Explicit AddMachine<TMachine> declarations remain the clearest choice for constructed generic and external types.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Arc.Collections (>= 1.45.0)
- Arc.Threading (>= 0.52.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Tinyhand (>= 0.144.1)
- ValueLink (>= 0.118.2)
| Version | Downloads | Last Updated |
|---|---|---|
| 0.57.0 | 0 | 9/5/2026 |
| 0.56.1 | 59 | 9/1/2026 |
| 0.56.0 | 179 | 8/20/2026 |
| 0.55.2 | 179 | 5/14/2026 |
| 0.55.1 | 200 | 5/11/2026 |
| 0.55.0 | 215 | 5/9/2026 |
| 0.54.2 | 191 | 4/23/2026 |
| 0.54.1 | 139 | 4/23/2026 |
| 0.53.5 | 166 | 4/5/2026 |
| 0.53.4 | 162 | 3/31/2026 |
| 0.53.3 | 288 | 3/28/2026 |
| 0.53.2 | 184 | 3/16/2026 |
| 0.53.0 | 208 | 3/11/2026 |
| 0.52.2 | 425 | 1/30/2026 |
| 0.52.1 | 617 | 11/19/2025 |
| 0.52.0 | 303 | 11/15/2025 |
| 0.51.0 | 286 | 10/29/2025 |
| 0.50.2 | 287 | 10/29/2025 |
| 0.50.1 | 289 | 10/28/2025 |
| 0.50.0 | 279 | 10/28/2025 |