GrainFramework 1.4.0
dotnet add package GrainFramework --version 1.4.0
NuGet\Install-Package GrainFramework -Version 1.4.0
<PackageReference Include="GrainFramework" Version="1.4.0" />
<PackageVersion Include="GrainFramework" Version="1.4.0" />
<PackageReference Include="GrainFramework" />
paket add GrainFramework --version 1.4.0
#r "nuget: GrainFramework, 1.4.0"
#:package GrainFramework@1.4.0
#addin nuget:?package=GrainFramework&version=1.4.0
#tool nuget:?package=GrainFramework&version=1.4.0
GrainFramework
Lightweight abstractions for building tick-driven grains/actors with typed messages, outputs, and persistable state, plus a minimal runtime for registering grains and ticking them deterministically. Designed for deterministic loops (games, simulations, headless services) where you want explicit stepping instead of background tasks.
Why GrainFramework
GrainFramework keeps your loop in the driver's seat: you control every tick, outputs stay deterministic, and replayability is trivial. Messages, outputs, events, and state are all typed so you don't lose intent to stringly APIs. There are no surprise background threads or schedulers, making it a natural fit for games, simulations, and headless services that already have a tight loop. It is tiny - just a handful of interfaces and a runtime you can swap out - and it runs anywhere .NET Standard 2.0 does. Persistence hooks are built in so you can capture and restore grain state without bolting on another abstraction.
Grain basics
- A grain is a unit of state plus behavior you tick from your host loop.
- Work arrives through
Enqueue(IGrainMessage), gets processed inTick(deltaTime), and is emitted viaDrainOutputs(). - Use
IGrainEventfor internally generated events, andIGrainOutputfor external effects you forward to your own transports/loggers/UI. - Implement
IPersistedGrainwhen you want the runtime to load and save grain state through anIGrainStateStore.
Interfaces at a glance
GrainIdstrongly typed identifier for routing/logging.IGraincore contract withEnqueue,Tick, andDrainOutputs.IGrainRuntimehost for registering grains, routing messages, ticking deterministically, draining all outputs, and saving state for persisted grains.GrainRuntimedefault implementation with deterministic ordering byGrainId.Valueand persistence integration.IGrainLifecycleoptional activation hooks (OnActivated,OnDeactivated) fired by the runtime.- Marker interfaces for your domain types:
IGrainMessage,IGrainEvent,IGrainOutput. IPersistedGrainaddsCaptureState/RestoreStateplus dirty tracking so grains can snapshot and reload state.IGrainStateStoreabstraction for loading and saving state for persisted grains.
Install
- From source:
dotnet add reference src/GrainFramework.csproj - From NuGet (when published):
dotnet add package GrainFramework
Quick start
using GrainFramework;
using System;
using System.Collections.Generic;
public sealed class CounterGrain : IPersistedGrain
{
private readonly Queue<IGrainMessage> _inbox = new();
private readonly List<IGrainOutput> _outputs = new();
private int _count;
public CounterGrain(GrainId id) => Id = id;
public GrainId Id { get; }
public bool IsDirty { get; private set; }
public void Enqueue(IGrainMessage message) => _inbox.Enqueue(message);
public void Tick(TimeSpan deltaTime)
{
while (_inbox.TryDequeue(out var message))
{
if (message is Increment increment)
{
_count += increment.Amount;
IsDirty = true;
}
}
_outputs.Add(new CounterChanged(_count));
}
public IReadOnlyList<IGrainOutput> DrainOutputs()
{
var snapshot = new List<IGrainOutput>(_outputs);
_outputs.Clear();
return snapshot;
}
public object CaptureState() => new CounterState(_count);
public void RestoreState(object state)
{
_count = ((CounterState)state).Value;
IsDirty = false;
}
public void MarkClean() => IsDirty = false;
}
public sealed record Increment(int Amount) : IGrainMessage;
public sealed record CounterChanged(int Value) : IGrainOutput;
public sealed record CounterState(int Value);
Call Enqueue to stage work, step the grain via Tick(deltaTime), then read external effects from DrainOutputs(). If persistence is required, the runtime will restore the grain on register and save it when you call SaveAll.
Runtime host
Use GrainRuntime when you want to register multiple grains, route messages by id, tick them in a deterministic order, and gather outputs in one pass. The runtime also restores and saves persisted grain state through an IGrainStateStore.
public sealed class InMemoryStore : IGrainStateStore
{
private readonly Dictionary<GrainId, object> _state = new();
public bool TryLoad(GrainId id, out object state) => _state.TryGetValue(id, out state!);
public void Save(GrainId id, object state) => _state[id] = state;
}
var runtime = new GrainRuntime(new InMemoryStore());
var grain = new CounterGrain(new GrainId("counter-1"));
runtime.Register(grain); // loads persisted state if present
runtime.Enqueue(grain.Id, new Increment(5));
// drive from your game/sim/main loop or a timer
runtime.TickAll(TimeSpan.FromMilliseconds(16));
foreach (var output in runtime.DrainAllOutputs())
{
// route to network/UI/logging/etc.
}
runtime.SaveAll(); // persists dirty grains via the store
GrainRuntime calls OnActivated on grains that implement IGrainLifecycle when they are registered. OnDeactivated is available for hosts that support teardown.
Persistence hook
GrainRuntime will call RestoreState for persisted grains during Register when the store has data, and CaptureState + MarkClean via SaveAll when grains report IsDirty.
Target frameworks
- netstandard2.0
Development
- Build locally:
dotnet build
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- OliveStudio.MissingAttributes (>= 1.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|