SharedMeta
Democratizing client-server game development. A deterministic framework bridging Unity and Microsoft Orleans. Write game logic once in C# - it runs on the server (Orleans grains) and replays on the client (Unity / .NET).
Install / Use
/learn @CoreGameIO/SharedMetaREADME
Write game logic once in C# — it runs on the server (Orleans grains) and replays on the client (Unity / .NET) with optimistic execution, automatic rollback, and desync detection.
What You Can Build
Player profiles and progression — experience, levels, inventory, currencies. State is persisted per-player, changes are optimistic (instant on client, validated on server).
Turn-based and card games — shared game rules execute identically on both sides. Matchmaking, lobbies, multi-entity game sessions with deterministic random for shuffles and draws.
Cooperative and async multiplayer — one player's action modifies another player's state via cross-entity calls. Energy systems, trading, expeditions that span multiple entities.
Economy and resource systems — crafting, shops, timers, regeneration. Server-only random for loot drops and rewards (client can't predict or cheat). ServerPatch mode for bandwidth-efficient state diffs.
Reactive UI with change tracking — mark state fields with [Tracked], subscribe to typed change notifications. Push-based — no polling or snapshot diffs. Client-only, zero server overhead.
Live-ops and admin tools — server-side triggers push events to clients. Subscribers react to state changes. Hot-swappable transport (WebSocket or HTTP polling) and serializer (MemoryPack or MessagePack).
Query and inspection — check any entity's status without subscribing. Get brief info about other players, check if a game session is active, preview inventory — all via lightweight read-only calls with optional open access.
Quick Start (Unity)
1. Install the Package
Add to Packages/manifest.json:
{
"dependencies": {
"com.coregame.sharedmeta": "https://github.com/CoreGameIO/SharedMeta.git#upm"
}
}
2. Open the Project Wizard
SharedMeta > Project Wizard in Unity menu.
Configure:
- Project name — your shared namespace (e.g.
MyGame.Shared) - State name — entity state class (e.g.
PlayerProfile) - Transport — SignalR (WebSocket, real-time) or HTTP Polling (universal, no extra DLLs)
- Serializer — MemoryPack (default) or MessagePack
The Dependencies section auto-detects and installs required packages (serializer, transport).
3. Generate Projects
Use the three generation tabs:
| Tab | Generates | Output |
|-----|-----------|--------|
| Shared Project | State class, service interface, implementation, .csproj | Unity folder + .NET mirror with linked sources |
| Server Project | ASP.NET Core server with Orleans, transport, auth | Standalone .NET project |
| Client Scripts | MetaGameClient.cs MonoBehaviour + logger | Unity Assets folder |
4. Run
Start the server from Unity: SharedMeta > Server Runner — click Start.
Or from terminal:
cd MyGame.Server
dotnet run
Press Play in Unity — MetaGameClient connects automatically.
Quick Start (.NET Client — Godot, Console, etc.)
Add NuGet packages to your .csproj:
<ItemGroup>
<PackageReference Include="CoreGame.SharedMeta.Core" Version="0.5.1" />
<PackageReference Include="CoreGame.SharedMeta.Client" Version="0.5.1" />
<PackageReference Include="CoreGame.SharedMeta.Serialization.MemoryPack" Version="0.5.1" />
<PackageReference Include="CoreGame.SharedMeta.Transport.SignalR.Client" Version="0.5.1" />
<PackageReference Include="CoreGame.SharedMeta.Generator" Version="0.5.1"
PrivateAssets="all" OutputItemType="analyzer" />
</ItemGroup>
Client transport packages have no server dependencies (no Orleans, no ASP.NET). Works with Godot (Godot.NET.Sdk), console apps, or any net8.0+ project.
For MessagePack SignalR protocol (optional, better performance):
<PackageReference Include="CoreGame.SharedMeta.Transport.SignalR.MessagePack" Version="0.5.1" />
Quick Start (examples)
dotnet run --project examples/CardGame_TheFool/CardGame.Server
dotnet run --project examples/CardGame_TheFool/CardGame.Client
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Meta Layer (SharedMeta.Core, YourGame.Shared) │
│ Business logic: services, state, [MetaService] / [MetaMethod] │
│ Code generation: dispatchers, API clients, context injection │
└─────────────────────────────────────────────────────────────────┘
↕
┌──────────────────────────────────────────────────────────────────────────────┐
│ Middleware Layer (SharedMeta.Client, SharedMeta.Server) │
│ MetaContext, replay mechanism, execution modes │
│ Optimistic / Server / Local / CrossOptimistic / ServerPatch / ServerReplace │
└──────────────────────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────────┐
│ Serialization Layer (SharedMeta.Serialization.*) │
│ IMetaSerializer, MemoryPack / MessagePack implementations │
└─────────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────────┐
│ Transport Layer (SharedMeta.Transport.*) │
│ IConnection: SignalR WebSocket, HTTP long-polling, InProcess │
└─────────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────────┐
│ Server Backend (SharedMeta.Server.Core, Orleans) │
│ IMetaProvider<TState>, EntityGrain, SessionManager │
└─────────────────────────────────────────────────────────────────┘
Each layer depends only on the layers above it. Swap serializers, transports, or backends without changing game logic.
Key Concepts
Define Shared State
[MemoryPackable(GenerateType.VersionTolerant), MessagePackObject] // pick one or both
public partial class GameState : ISharedState
{
[Key(0), MemoryPackOrder(0)] public int Score { get; set; }
[Key(1), MemoryPackOrder(1)] public List<string> Items { get; set; } = new();
}
[MemoryPackable(GenerateType.VersionTolerant)]+[MemoryPackOrder(n)]— MemoryPack (default, zero-copy, safe field evolution)[MessagePackObject]+[Key(n)]— MessagePack (cross-platform, schema-flexible)
Choose one serializer or use both. Orleans [GenerateSerializer]/[Id(n)] are not needed on game state — they're only used internally by the framework. The wizard configures this automatically.
Define a Service
[MetaService("IGameService")]
public interface IGameService
{
[MetaMethod(ExecutionMode.Optimistic)]
void AddItem(string itemId);
[MetaMethod(ExecutionMode.Server)]
void GrantReward(int amount);
}
Implement the Service
[MetaServiceImpl(typeof(IGameService))]
public partial class GameServiceImpl : IGameService
{
// Context is injected by source generator
public void AddItem(string itemId)
{
State.Items.Add(itemId);
}
public void GrantReward(int amount)
{
// ServerRandom only generates on server; client replays from payload
int bonus = Context.ServerRandom!.Next(10);
State.Score += amount + bonus;
}
}
Execution Modes
| Mode | Client | Server | Use Case | |------|--------|--------|----------| | Optimistic | Executes immediately, rolls back on mismatch | Authoritative execution | UI-responsive actions (move, play card) | | Server | Waits for server response | Executes with ServerRandom | Loot drops, matchmaking, secrets | | Local | Local-only, no RPC | — | UI state, client-side filtering | | CrossOptimistic | Executes on own state | Routes to target entity's grain | Cross-entity interactions | | ServerPatch | Receives state diff from server | Sends patch instead of full state | Large state, bandwidth optimization | | ServerReplace | Receives full state from server | Executes and sends complete state | Map generation, full reset, bulk state changes |
Deterministic Random
// Optimistic random — same algorithm & seed on both sides
int roll = Context.Random!.Next(6) + 1;
// Server random — generated on server, replayed on client
int loot = Context.ServerRandom!.Next(100);
Query Calls
Read-only calls to any entity without subscribing — useful for checking status, fetching brief info in multiplayer.
[MetaService(StateType = typeof(GameState))]
public interface IGameService : IMetaServi
