2026-04-18 13:01:24 +01:00
# GameCult.Caching.MessagePack
2026-05-08 02:42:11 +01:00
`GameCult.Caching.MessagePack` provides the canonical CultCache persistence
format for the attribute-first `GameCult.Caching` stack.
2026-04-18 13:01:24 +01:00
## Included Types
- `SingleFileMessagePackBackingStore`
2026-05-08 01:13:15 +01:00
- `CultDocumentMessagePackSerialization`
- `CultDocumentResolver`
- `CultRecordRefFormatter<T>`
2026-09-29 16:18:09 +02:00
- `CultMathResolver` : the canonical component-array encoding of every public CultMath value type, part of the default resolver chain
2026-10-14 06:05:19 +02:00
- `CultMathExprResolver` : the canonical encoding of `CultMath.Expr.ExprGraph` , decoded through its validator, part of the default resolver chain
2026-04-18 13:01:24 +01:00
Delete the CultCache MessagePack source generator
The reflective CultDocumentRegistry is now the only descriptor authority,
and payloads always go through MessagePackSerializer with
OptionsFor(document assembly). No consumer builds AOT, IL2CPP or trimmed,
Unity always ran the reflective path, and no benchmark showed a gain,
while the generator duplicated the registry's shape rules.
Removed: GameCult.Caching.MessagePack.Generator and .Analyzers (solution,
project and analyzer references, NuGet pack list), CultGeneratedDocumentMetadata
(provider attribute, interface, definitions, loader), the generated descriptor
and codec branches in CultCache and CultDocumentMessagePackSerialization,
DocumentShapeSweepTests and the generator half of the unkeyed test, the
Microsoft.CodeAnalysis.CSharp test reference and InternalsVisibleTo.
The registry rejects, with a message naming the fix, what MessagePackSerializer
cannot serialize and what the generator used to paper over: a document without
[MessagePackObject], a non-public document without AllowPrivate = true, and a
non-public member of an AllowPrivate document that is not [IgnoreMember].
DocumentShapeTests pins every rejection message and the accepted shapes'
round trips against MessagePackSerializer bytes (keeps the 93A16E0305 pin).
Rejected shapes are emitted into a dynamic assembly, which registry
discovery skips.
Nested member type names use reflection's Outer+Inner form, not the
generator's dot form, so ReferenceHolderEntry's fixture id is re-pinned
(bd8506... -> 4ec0c5...). A scan of the schema catalogs in all 2786 .cc
stores under F:\Projects (AetheriaEve and CultLib-* worktrees, bin and obj
excluded) found no member type name naming a nested type in either form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:38:08 +02:00
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` .
2026-05-08 01:50:15 +01:00
2026-05-08 01:13:15 +01:00
## What It Does
2026-04-18 13:01:24 +01:00
2026-05-08 02:42:11 +01:00
- stores whole CultCache snapshots in MessagePack
Delete the CultCache MessagePack source generator
The reflective CultDocumentRegistry is now the only descriptor authority,
and payloads always go through MessagePackSerializer with
OptionsFor(document assembly). No consumer builds AOT, IL2CPP or trimmed,
Unity always ran the reflective path, and no benchmark showed a gain,
while the generator duplicated the registry's shape rules.
Removed: GameCult.Caching.MessagePack.Generator and .Analyzers (solution,
project and analyzer references, NuGet pack list), CultGeneratedDocumentMetadata
(provider attribute, interface, definitions, loader), the generated descriptor
and codec branches in CultCache and CultDocumentMessagePackSerialization,
DocumentShapeSweepTests and the generator half of the unkeyed test, the
Microsoft.CodeAnalysis.CSharp test reference and InternalsVisibleTo.
The registry rejects, with a message naming the fix, what MessagePackSerializer
cannot serialize and what the generator used to paper over: a document without
[MessagePackObject], a non-public document without AllowPrivate = true, and a
non-public member of an AllowPrivate document that is not [IgnoreMember].
DocumentShapeTests pins every rejection message and the accepted shapes'
round trips against MessagePackSerializer bytes (keeps the 93A16E0305 pin).
Rejected shapes are emitted into a dynamic assembly, which registry
discovery skips.
Nested member type names use reflection's Outer+Inner form, not the
generator's dot form, so ReferenceHolderEntry's fixture id is re-pinned
(bd8506... -> 4ec0c5...). A scan of the schema catalogs in all 2786 .cc
stores under F:\Projects (AetheriaEve and CultLib-* worktrees, bin and obj
excluded) found no member type name naming a nested type in either form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 23:38:08 +02:00
- serializes attributed document payloads as MessagePack `[Key(n)]` arrays
2026-05-08 01:13:15 +01:00
- keeps explicit `CultRecordRef<T>` values compact on disk/wire
- preserves the cache/store split where metadata lives outside the domain model
2026-04-18 13:01:24 +01:00
2026-05-08 01:13:15 +01:00
## Example Model
2026-04-18 13:01:24 +01:00
2026-05-08 01:13:15 +01:00
```csharp
using GameCult.Caching;
using MessagePack;
2026-04-18 13:01:24 +01:00
2026-05-08 01:13:15 +01:00
[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;
}
```
2026-04-18 13:01:24 +01:00
## Typical Usage
```csharp
using GameCult.Caching;
using GameCult.Caching.MessagePack;
var cache = new CultCache();
2026-05-08 02:42:11 +01:00
var store = new SingleFileMessagePackBackingStore("Data.msgpack");
2026-04-18 13:01:24 +01:00
cache.AddBackingStore(store);
await cache.PullAllBackingStoresAsync();
```
2026-05-08 01:13:15 +01:00
## Notes
2026-05-06 11:50:10 +01:00
2026-05-08 01:13:15 +01:00
- Payload bytes are MessagePack. Store metadata and schema catalogs are also
2026-05-08 02:42:11 +01:00
persisted through hand-written MessagePack array layouts in this package.
2026-05-08 01:13:15 +01:00
- The raw CultNet document lane should treat these payload bytes as already
blessed, not decode and re-encode them for sport.
2026-09-29 16:57:31 +02:00
## 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
2026-09-29 18:31:16 +02:00
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
2026-09-29 17:29:02 +02:00
registered ahead of these wins.
2026-09-29 16:57:31 +02:00
2026-10-14 06:05:19 +02:00
`CultMath.Expr.ExprGraph` is a class, not a value type, and has its own shape
(`CultMathExprResolver` , in the default chain): `[version, inputs, params, outputs, nodes]` ,
with a param `[name, default float bits]` , an output `[name, node]` and a node
`[op name, operands, payload]` . Decoding runs the graph's one validator and refuses a
broken graph whole (an `ExprGraphException` naming the rule), so a consumer
2026-10-14 07:54:32 +02:00
`[CultDocument]` record with an `ExprGraph` field needs no registration. A nil graph is nil, as in every reference slot; decoding refuses any encoding but the canonical one.
2026-10-14 06:05:19 +02:00
2026-09-29 16:57:31 +02:00
| 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":..}` |
2026-10-17 19:28:25 +02:00
| `CultPhasor` | `[cosine, sine]` of `float4` | `{"cosine":{..},"sine":{..}}` (reads legacy `cos` /`sin` ) |
2026-10-16 18:16:37 +02:00
| `CultSiteCells` | `[nearest, second, nearestSite, secondSite]` | `{"nearest":{..},"second":{..},"nearestSite":..,"secondSite":..}` |
2026-09-29 16:57:31 +02:00
Floats are MessagePack float32 (`0xCA` ), doubles float64 (`0xCB` ), integers the
shortest MessagePack integer.