CultLib/src/GameCult.Caching.MessagePack
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Meta Vens 4f742d05c3 C#: a flush refuses to overwrite a store file whose header it cannot read
SingleFileBackingStore.PushAll replaced the file without reading it, so a
truncated store or one in a format this runtime does not read was clobbered,
where Rust, TypeScript and Python refuse. PushAll now reads the header of a
non-empty file under the lock before it writes; a file it cannot read throws
and is left as it was. An empty file and a legacy envelope array carry no
header and are still replaced.

The header is read by the store's codec: SingleFileBackingStore gains a
protected virtual ReadStoreHeader (default: read the whole snapshot), and the
MessagePack store overrides it with
CultDocumentMessagePackSerialization.ReadStoreHeader. An unconditional commit
reads the file it rewrites through the same check.

Doc text: last-writer-wins now says it holds only over a file the writer can
read (CultCache.cs PushAll comment, cultcache-persistence-format.md
"Conditional commit is the only safe multi-process write" and writer rule 4,
cultcache-store-composition.md "A plain flush and an unconditional commit are
last-writer-wins").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:51:47 +02:00
..
CultCacheFormatterResolverAttribute.cs Own serialization options by the document's assembly 2026-09-13 21:12:53 +02:00
CultCacheMessagePack.cs C# core: variant records, top-level Set overrides, resolution in the admitting hold 2026-09-29 03:51:53 +02:00
CultDocumentMessagePackSerialization.cs C#: a flush refuses to overwrite a store file whose header it cannot read 2026-09-29 23:51:47 +02:00
CultMathJson.cs Bring the CultMath JSON converters to System.Text.Json parity and remove their allocations 2026-09-29 18:31:16 +02:00
CultMathResolver.cs Pin every CultMath MessagePack shape and decode a missing Color32 alpha as opaque 2026-09-29 16:57:31 +02:00
CultMessagePackSecurity.cs Own serialization options by the document's assembly 2026-09-13 21:12:53 +02:00
CultRecordRefFormatter.cs Write an unset CultRecordRef as "" in every position 2026-09-14 21:34:23 +02:00
DirectoryMessagePackBackingStore.cs C# header: one function decides it for every writer; a store's own ids are the only source 2026-09-29 21:54:01 +02:00
GameCult.Caching.MessagePack.csproj Add canonical System.Text.Json converters for every CultMath value type 2026-09-29 16:57:31 +02:00
README.md Bring the CultMath JSON converters to System.Text.Json parity and remove their allocations 2026-09-29 18:31:16 +02:00

GameCult.Caching.MessagePack

GameCult.Caching.MessagePack provides the canonical CultCache persistence format for the attribute-first GameCult.Caching stack.

Included Types

  • SingleFileMessagePackBackingStore
  • CultDocumentMessagePackSerialization
  • CultDocumentResolver
  • CultRecordRefFormatter<T>
  • CultMathResolver: the canonical component-array encoding of every public CultMath value type, part of the default resolver chain

Cult document payloads serialize through MessagePackSerializer with the options of the document's assembly (CultDocumentMessagePackSerialization.OptionsFor), after the reflective CultDocumentRegistry has accepted the document shape. The store snapshot format is written by hand against MessagePackWriter/MessagePackReader.

What It Does

  • stores whole CultCache snapshots in MessagePack
  • serializes attributed document payloads as MessagePack [Key(n)] arrays
  • keeps explicit CultRecordRef<T> values compact on disk/wire
  • preserves the cache/store split where metadata lives outside the domain model

Example Model

using GameCult.Caching;
using MessagePack;

[CultDocument("gamecult.item_data", "gamecult.item_data.v1")]
public sealed class ItemData
{
    [Key(0)] [CultName] public string Name = string.Empty;
    [Key(1)] public int Value;
}

Typical Usage

using GameCult.Caching;
using GameCult.Caching.MessagePack;

var cache = new CultCache();
var store = new SingleFileMessagePackBackingStore("Data.msgpack");

cache.AddBackingStore(store);
await cache.PullAllBackingStoresAsync();

Notes

  • Payload bytes are MessagePack. Store metadata and schema catalogs are also persisted through hand-written MessagePack array layouts in this package.
  • The raw CultNet document lane should treat these payload bytes as already blessed, not decode and re-encode them for sport.

CultMath Value Shapes

Every public CultMath value type has one MessagePack shape (CultMathResolver, in the default chain) and one JSON shape (CultMathJson, added to a consumer's options with new JsonSerializerOptions().AddCultMathConverters(); CultLib owns no JSON options of its own). MessagePack decoding skips extra elements, leaves missing ones at zero (Color32 alpha at 255) and refuses nil. JSON reading skips unknown properties (UnmappedMemberHandling.Disallow refuses them), leaves missing ones at zero, honours PropertyNameCaseInsensitive, lets a repeated property replace the whole earlier value (refused when AllowDuplicateProperties is false), and throws JsonException for a wrong token or an out-of-range number. Numbers follow JsonSerializerOptions.NumberHandling as System.Text.Json's own float does: "NaN", "Infinity" and "-Infinity" are written under AllowNamedFloatingPointLiterals or WriteAsString and read under AllowNamedFloatingPointLiterals or AllowReadingFromString; quoted numbers read under AllowReadingFromString with no surrounding whitespace. Every type is also a dictionary key: its flattened components joined by commas, invariant culture ("1,2", "true,false", matrices row by row, rect as "minX,minY,maxX,maxY"), and only that exact spelling reads back. Two intended divergences from System.Text.Json: a bare number beyond a component's range (1e40 as a float) is refused where System.Text.Json reads infinity, and a refusal on write is a JsonException where System.Text.Json throws ArgumentException. A consumer resolver or converter registered ahead of these wins.

Type MessagePack JSON
float2, double2, int2, bool2 [x, y] {"x":..,"y":..}
float3, double3, int3, bool3 [x, y, z] {"x":..,"y":..,"z":..}
float4, int4, bool4, quaternion [x, y, z, w] {"x":..,"y":..,"z":..,"w":..}
Color32 [r, g, b, a] as uint8 {"r":..,"g":..,"b":..,"a":..}
Random [state] {"state":..}
float2x2, float3x3 array of row vectors (row-major) array of row objects
rect [min, max] of float2 {"min":{..},"max":{..}}
CultCellular [nearest, edge, id] {"nearest":{..},"edge":{..},"id":..}

Floats are MessagePack float32 (0xCA), doubles float64 (0xCB), integers the shortest MessagePack integer.