diff --git a/CultLib.sln b/CultLib.sln
index c60890ca..fbc20922 100644
--- a/CultLib.sln
+++ b/CultLib.sln
@@ -11,10 +11,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Caching", "src\Gam
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Caching.MessagePack", "src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj", "{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Caching.MessagePack.Generator", "src\GameCult.Caching.MessagePack.Generator\GameCult.Caching.MessagePack.Generator.csproj", "{0E990C96-3808-450E-9349-A0C3262195ED}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Caching.MessagePack.Analyzers", "src\GameCult.Caching.MessagePack.Analyzers\GameCult.Caching.MessagePack.Analyzers.csproj", "{DBE06954-EC85-4034-9049-72C7D27556DF}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Networking", "src\GameCult.Networking\GameCult.Networking.csproj", "{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameCult.Mesh", "src\GameCult.Mesh\GameCult.Mesh.csproj", "{8D1B01A7-BF9E-44E6-88E1-B17255728B42}"
@@ -99,30 +95,6 @@ Global
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x64.Build.0 = Release|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x86.ActiveCfg = Release|Any CPU
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB}.Release|x86.Build.0 = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x64.ActiveCfg = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x64.Build.0 = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x86.ActiveCfg = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Debug|x86.Build.0 = Debug|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|Any CPU.Build.0 = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x64.ActiveCfg = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x64.Build.0 = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x86.ActiveCfg = Release|Any CPU
- {0E990C96-3808-450E-9349-A0C3262195ED}.Release|x86.Build.0 = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x64.Build.0 = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Debug|x86.Build.0 = Debug|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|Any CPU.Build.0 = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x64.ActiveCfg = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x64.Build.0 = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x86.ActiveCfg = Release|Any CPU
- {DBE06954-EC85-4034-9049-72C7D27556DF}.Release|x86.Build.0 = Release|Any CPU
{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -311,8 +283,6 @@ Global
{72AEDD42-E881-4EB7-99A8-FD3B9667189C} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{9DDA917D-B4DB-4AC1-9B9A-4ECF06A5ABA1} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{26EE4C38-F06E-4E7A-A89C-908318BCB0DB} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
- {0E990C96-3808-450E-9349-A0C3262195ED} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
- {DBE06954-EC85-4034-9049-72C7D27556DF} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{67383F6C-C9C3-4C5B-95D4-5C50C20EDD4F} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{8D1B01A7-BF9E-44E6-88E1-B17255728B42} = {E733CBFF-42AE-4B6A-B5B9-0071ECFB45F1}
{E2DC8FF6-D39E-4FCA-9549-1F7681F0AA81} = {39D316AB-ABB5-49A2-8E83-CAF4C75B48F4}
diff --git a/README.md b/README.md
index aba30282..4977a02a 100644
--- a/README.md
+++ b/README.md
@@ -98,8 +98,6 @@ The solution includes:
- `GameCult.Caching`: `DatabaseEntry`-based cache, indexes, global entries, and backing-store abstractions
- `GameCult.Caching.MessagePack`: MessagePack-backed persistence for the cache
- `GameCult.Caching.NewtonsoftJson`: Newtonsoft.Json-backed persistence for the cache
-- `GameCult.Caching.MessagePack.Generator`: source generator for MessagePack formatters for cache models
-- `GameCult.Caching.MessagePack.Analyzers`: packaging project that delivers the generator to consuming projects
- `GameCult.Math` / CultMath: portable numeric primitives, deterministic random, noise, and shader-parity helpers
- `GameCult.Geometry` / CultGeometry: engine-neutral geometry algorithms plus CultCache-native domain, selected-cut, and mesh artifact documents
- `GameCult.Networking`: encrypted login/register/verify flows, schema-v0 contracts, transport adapters, and native RUDP sessions
@@ -128,8 +126,6 @@ src/
GameCult.Caching/
GameCult.Caching.MessagePack/
GameCult.Caching.NewtonsoftJson/
- GameCult.Caching.MessagePack.Generator/
- GameCult.Caching.MessagePack.Analyzers/
GameCult.Geometry/
GameCult.Networking/
GameCult.Networking.WebSockets/
@@ -349,8 +345,6 @@ Each subproject has a local README with package-specific detail:
- [GameCult.Caching](src/GameCult.Caching/README.md)
- [GameCult.Caching.MessagePack](src/GameCult.Caching.MessagePack/README.md)
- [GameCult.Caching.NewtonsoftJson](src/GameCult.Caching.NewtonsoftJson/README.md)
-- [GameCult.Caching.MessagePack.Generator](src/GameCult.Caching.MessagePack.Generator/README.md)
-- [GameCult.Caching.MessagePack.Analyzers](src/GameCult.Caching.MessagePack.Analyzers/README.md)
- [GameCult.Geometry](src/GameCult.Geometry/README.md)
- [GameCult.Networking](src/GameCult.Networking/README.md)
- [GameCult.Mesh](src/GameCult.Mesh/README.md)
diff --git a/scripts/pack-nuget.ps1 b/scripts/pack-nuget.ps1
index 2b853aab..95e5cd2f 100644
--- a/scripts/pack-nuget.ps1
+++ b/scripts/pack-nuget.ps1
@@ -16,7 +16,6 @@ $outputRoot = if ([IO.Path]::IsPathRooted($OutputDirectory)) {
$projects = @(
"src\GameCult.Logging\GameCult.Logging.csproj",
"src\GameCult.Caching\GameCult.Caching.csproj",
- "src\GameCult.Caching.MessagePack.Analyzers\GameCult.Caching.MessagePack.Analyzers.csproj",
"src\GameCult.Caching.MessagePack\GameCult.Caching.MessagePack.csproj",
"src\GameCult.Networking\GameCult.Networking.csproj",
"src\GameCult.Networking.WebSockets\GameCult.Networking.WebSockets.csproj",
@@ -53,7 +52,6 @@ $version = [regex]::Match($meshPackage.Name, '^GameCult\.Mesh\.(.+)\.nupkg$').Gr
$expectedPackages = @(
"GameCult.Logging.$version.nupkg",
"GameCult.Caching.$version.nupkg",
- "GameCult.Caching.MessagePack.Analyzers.$version.nupkg",
"GameCult.Caching.MessagePack.$version.nupkg",
"GameCult.Networking.$version.nupkg",
"GameCult.Networking.WebSockets.$version.nupkg",
diff --git a/src/GameCult.Caching.MessagePack.Analyzers/Class1.cs b/src/GameCult.Caching.MessagePack.Analyzers/Class1.cs
deleted file mode 100644
index 345e38e6..00000000
--- a/src/GameCult.Caching.MessagePack.Analyzers/Class1.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-
-namespace GameCult.Caching.MessagePack.Analyzer
-{
- ///
- /// Placeholder type for the analyzer packaging project.
- ///
- public class Class1
- {
- }
-}
diff --git a/src/GameCult.Caching.MessagePack.Analyzers/GameCult.Caching.MessagePack.Analyzers.csproj b/src/GameCult.Caching.MessagePack.Analyzers/GameCult.Caching.MessagePack.Analyzers.csproj
deleted file mode 100644
index 12c4e684..00000000
--- a/src/GameCult.Caching.MessagePack.Analyzers/GameCult.Caching.MessagePack.Analyzers.csproj
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- Source generator that emits MessagePack formatters for DatabaseEntry subclasses
- MessagePack;SourceGenerator;DatabaseEntry;Analyzer
-
- false
- $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
- true
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/GameCult.Caching.MessagePack.Analyzers/README.md b/src/GameCult.Caching.MessagePack.Analyzers/README.md
deleted file mode 100644
index e4b25bd4..00000000
--- a/src/GameCult.Caching.MessagePack.Analyzers/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# GameCult.Caching.MessagePack.Analyzers
-
-`GameCult.Caching.MessagePack.Analyzers` is the delivery mechanism for the MessagePack source generator used by the cache libraries.
-
-## Scope
-
-This project is primarily packaging infrastructure. It exists so consuming projects can receive generator output at compile time without treating the generator assembly as a normal runtime dependency.
-
-## What It Does
-
-- references the generator project
-- arranges for the generator assembly to be packed under `analyzers/dotnet/cs`
-- keeps generator assets out of the normal compile/runtime reference flow
-
-## Practical Meaning
-
-When a consuming project adds the analyzer package:
-
-- the generator runs during compilation
-- concrete `DatabaseEntry` types receive generated MessagePack formatters
-- `GameCult.Caching.MessagePack` can then serialize those types without handwritten formatter code
-
-## Current State
-
-This project is a packaging host. It does not currently expose custom analyzer diagnostics of its own.
diff --git a/src/GameCult.Caching.MessagePack.Generator/CultDocumentMessagePackGenerator.cs b/src/GameCult.Caching.MessagePack.Generator/CultDocumentMessagePackGenerator.cs
deleted file mode 100644
index ad7f934e..00000000
--- a/src/GameCult.Caching.MessagePack.Generator/CultDocumentMessagePackGenerator.cs
+++ /dev/null
@@ -1,716 +0,0 @@
-#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
-
-namespace GameCult.Caching.MessagePack.Generator
-{
-///
-/// Emits MessagePack payload serializers for CultCache document types.
-///
-[Generator]
-public sealed class CultDocumentMessagePackGenerator : IIncrementalGenerator
-{
- ///
- /// Registers the generator pipeline.
- ///
- public void Initialize(IncrementalGeneratorInitializationContext context)
- {
- var documentShapes = context.SyntaxProvider
- .CreateSyntaxProvider(
- static (node, _) => node is ClassDeclarationSyntax cds && cds.AttributeLists.Count > 0,
- static (ctx, _) => BuildDocumentShape(ctx))
- .Where(static shape => shape != null)
- .Select(static (shape, _) => shape!);
-
- var compilationAndShapes = context.CompilationProvider.Combine(documentShapes.Collect());
- context.RegisterSourceOutput(
- compilationAndShapes,
- static (spc, pair) => EmitProvider(spc, pair.Left, pair.Right));
- }
-
- private static DocumentShape? BuildDocumentShape(GeneratorSyntaxContext context)
- {
- if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol typeSymbol)
- {
- return null;
- }
-
- var documentAttribute = GetAttribute(typeSymbol, "GameCult.Caching.CultDocumentAttribute");
- if (documentAttribute == null ||
- documentAttribute.ConstructorArguments.Length < 2 ||
- documentAttribute.ConstructorArguments[0].Value is not string schemaName ||
- documentAttribute.ConstructorArguments[1].Value is not string schemaVersion)
- {
- return null;
- }
-
- var rejections = new List();
- var members = DiscoverMembers(typeSymbol, rejections);
- var nameMember = members.FirstOrDefault(member => member.IsName);
- var canConstructForDeserialization = typeSymbol.TypeKind == TypeKind.Struct ||
- typeSymbol.InstanceConstructors.Any(constructor =>
- constructor.Parameters.Length == 0 &&
- constructor.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal);
- var hasDenseSlots = members
- .OrderBy(member => member.Slot)
- .Select((member, index) => member.Slot == index)
- .All(matches => matches);
- return new DocumentShape(
- typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
- schemaName,
- schemaVersion,
- GetAttribute(typeSymbol, "GameCult.Caching.CultGlobalAttribute") != null,
- nameMember?.Name,
- nameMember?.AccessorMethodName,
- members.Where(member => member.IndexAlias != null)
- .Select(member => new IndexAccessorShape(member.IndexAlias!, member.IndexAccessorMethodName!))
- .ToImmutableArray(),
- members,
- canConstructForDeserialization,
- hasDenseSlots,
- rejections.ToImmutableArray(),
- typeSymbol.Locations.FirstOrDefault());
- }
-
- // Mirrors CultDocumentRegistry.DiscoverMembers step for step: public instance fields and get/set properties, most-derived
- // declaration first, a property's attributes resolved up its override chain, rejections in the same order and text.
- private static ImmutableArray DiscoverMembers(INamedTypeSymbol typeSymbol, List rejections)
- {
- var documentName = typeSymbol.Name;
- var documentStem = SanitizeIdentifier(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
- .Replace("global::", string.Empty).Replace(".", "_"));
- var candidates = new List();
- var seenRoots = new HashSet(SymbolEqualityComparer.Default);
- var depth = 0;
- for (var type = typeSymbol; type != null && type.SpecialType != SpecialType.System_Object; type = type.BaseType, depth++)
- {
- foreach (var member in type.GetMembers())
- {
- if (member is IFieldSymbol field && !field.IsStatic && !field.IsConst && !field.IsImplicitlyDeclared &&
- field.DeclaredAccessibility == Accessibility.Public)
- {
- if (!IsIgnored(field))
- candidates.Add(new MemberCandidate(field, field.Type, depth, !field.IsReadOnly, null));
- }
- else if (member is IPropertySymbol property && !property.IsStatic && !property.IsIndexer &&
- property.DeclaredAccessibility == Accessibility.Public && seenRoots.Add(RootOf(property)))
- {
- var setter = OverrideChain(property).Select(declaration => declaration.SetMethod).FirstOrDefault(method => method != null);
- if (OverrideChain(property).All(declaration => declaration.GetMethod == null) || setter == null)
- continue;
- var root = RootOf(property);
- var divergentRoot = OverrideChain(property).Any(declaration =>
- !Equals(KeyValue(declaration), KeyValue(root)) || IsIgnored(declaration) != IsIgnored(root))
- ? root
- : null;
- if (divergentRoot == null && IsIgnored(property))
- continue;
- candidates.Add(new MemberCandidate(property, property.Type, depth,
- !setter.IsInitOnly && setter.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal,
- divergentRoot));
- }
- }
- }
-
- var keyed = new List<(MemberCandidate Candidate, int Slot)>();
- foreach (var candidate in candidates.OrderBy(candidate => candidate.Depth).ThenBy(candidate => candidate.Member.Name, StringComparer.Ordinal))
- {
- var keyValue = KeyValue(candidate.Member);
- if (candidate.DivergentRoot != null)
- rejections.Add(DivergentOverrideMessage(documentName, Qualified(candidate.Member), Qualified(candidate.DivergentRoot)));
- else if (keyValue is string)
- rejections.Add(StringKeyMessage(documentName, candidate.Member.Name));
- else if (keyValue is not int slot)
- rejections.Add(UnkeyedMemberMessage(documentName, candidate.Member.Name));
- else
- {
- if (!candidate.Writable)
- rejections.Add(NotWritableMessage(documentName, candidate.Member.Name));
- keyed.Add((candidate, slot));
- }
- }
-
- foreach (var group in keyed.GroupBy(entry => entry.Slot).Where(group => group.Count() > 1).OrderBy(group => group.Key))
- {
- var pair = group.Take(2).ToArray();
- rejections.Add(DuplicateSlotMessage(documentName, Qualified(pair[0].Candidate.Member), Qualified(pair[1].Candidate.Member), group.Key));
- }
-
- foreach (var group in keyed.GroupBy(entry => entry.Candidate.Member.Name)
- .Where(group => group.Select(entry => entry.Slot).Distinct().Count() > 1)
- .OrderBy(group => group.Key, StringComparer.Ordinal))
- {
- var pair = group.Take(2).ToArray();
- rejections.Add(HiddenMemberMessage(documentName, Qualified(pair[0].Candidate.Member), Qualified(pair[1].Candidate.Member)));
- }
-
- return keyed
- .Select(entry => MemberShapeSeed.From(entry.Candidate.Member, entry.Candidate.Type).WithSlot(entry.Slot, documentStem))
- .OrderBy(member => member.Slot)
- .ToImmutableArray();
- }
-
- private static IPropertySymbol RootOf(IPropertySymbol property)
- {
- while (property.OverriddenProperty != null)
- property = property.OverriddenProperty;
- return property;
- }
-
- private static IEnumerable OverrideChain(IPropertySymbol property)
- {
- for (IPropertySymbol? declaration = property; declaration != null; declaration = declaration.OverriddenProperty)
- yield return declaration;
- }
-
- private static string Qualified(ISymbol member) => member.ContainingType.Name + "." + member.Name;
-
- // CultDocumentRegistry throws the same texts.
- private static string UnkeyedMemberMessage(string documentTypeName, string memberName) =>
- $"Cult document {documentTypeName} member {memberName} has no [Key]; every persisted member of a [CultDocument] type needs an explicit [Key(n)].";
-
- private static string StringKeyMessage(string documentTypeName, string memberName) =>
- $"Cult document {documentTypeName} member {memberName} has a string [Key]; string keys are not supported; use integer [Key(n)].";
-
- private static string NotWritableMessage(string documentTypeName, string memberName) =>
- $"Cult document {documentTypeName} member {memberName} is not writable; a persisted member needs a non-readonly field or a public or internal set accessor.";
-
- private static string DuplicateSlotMessage(string documentTypeName, string first, string second, int slot) =>
- $"Cult document {documentTypeName} members {first} and {second} share [Key({slot})]; every persisted member needs a distinct [Key(n)].";
-
- private static string DivergentOverrideMessage(string documentTypeName, string member, string root) =>
- $"Cult document {documentTypeName} member {member} overrides {root} with a different [Key] or [IgnoreMember]; MessagePack reads the base declaration's, so an override must repeat or omit them.";
-
- private static object? KeyValue(ISymbol member)
- {
- var key = GetMemberAttribute(member, "MessagePack.KeyAttribute");
- return key != null && key.ConstructorArguments.Length > 0 ? key.ConstructorArguments[0].Value : null;
- }
-
- private static string HiddenMemberMessage(string documentTypeName, string first, string second) =>
- $"Cult document {documentTypeName} member {first} hides persisted member {second}; persisted member names must be unique.";
-
-#pragma warning disable RS2008
- private static readonly DiagnosticDescriptor RejectedMember = new(
- "GCC001", "Cult document member cannot be persisted", "{0}", "GameCult.Caching", DiagnosticSeverity.Error, isEnabledByDefault: true);
-#pragma warning restore RS2008
-
- private static bool IsIgnored(ISymbol member)
- {
- return GetMemberAttribute(member, "MessagePack.IgnoreMemberAttribute") != null;
- }
-
- private static AttributeData? GetAttribute(ISymbol symbol, string fullyQualifiedName)
- {
- return symbol.GetAttributes().FirstOrDefault(attribute =>
- attribute.AttributeClass?.ToDisplayString() == fullyQualifiedName);
- }
-
- // Attribute.GetCustomAttributes(inherit: true) semantics: an override without the attribute inherits its base declaration's.
- private static AttributeData? GetMemberAttribute(ISymbol member, string fullyQualifiedName)
- {
- if (member is not IPropertySymbol property)
- return GetAttribute(member, fullyQualifiedName);
- return OverrideChain(property).Select(declaration => GetAttribute(declaration, fullyQualifiedName)).FirstOrDefault(attribute => attribute != null);
- }
-
- private static void EmitProvider(SourceProductionContext context, Compilation compilation, ImmutableArray shapes)
- {
- var shaped = shapes
- .Where(shape => shape != null)
- .GroupBy(shape => shape.DocumentTypeName, StringComparer.Ordinal)
- .Select(group => group.First())
- .OrderBy(shape => shape.DocumentTypeName, StringComparer.Ordinal)
- .ToArray();
- foreach (var shape in shaped)
- {
- foreach (var message in shape.Rejections)
- context.ReportDiagnostic(Diagnostic.Create(RejectedMember, shape.Location, message));
- }
-
- var documents = shaped.Where(shape => shape.Rejections.IsEmpty).ToArray();
-
- if (documents.Length == 0)
- {
- return;
- }
-
- var canEmitPayloadCodecs =
- compilation.GetTypeByMetadataName("MessagePack.MessagePackWriter") != null &&
- compilation.GetTypeByMetadataName("GameCult.Caching.MessagePack.CultDocumentMessagePackSerialization") != null;
-
- var providerName = $"GeneratedCultDocumentMetadataProvider_{SanitizeIdentifier(compilation.AssemblyName ?? "GameCult")}";
- var builder = new StringBuilder();
- builder.AppendLine("// ");
- builder.AppendLine("#nullable enable");
- builder.AppendLine($"[assembly: global::GameCult.Caching.CultGeneratedDocumentMetadataProviderAttribute(typeof(global::GameCult.Caching.Generated.{providerName}))]");
- builder.AppendLine("namespace GameCult.Caching.Generated");
- builder.AppendLine("{");
- builder.AppendLine($" internal sealed class {providerName} : global::GameCult.Caching.ICultGeneratedDocumentMetadataProvider");
- builder.AppendLine(" {");
- builder.AppendLine(" private static readonly global::GameCult.Caching.CultGeneratedDocumentDefinition[] s_documents =");
- builder.AppendLine(" {");
-
- foreach (var document in documents)
- {
- builder.AppendLine(" new global::GameCult.Caching.CultGeneratedDocumentDefinition(");
- builder.AppendLine($" typeof({document.DocumentTypeName}),");
- builder.AppendLine($" \"{Escape(document.SchemaName)}\",");
- builder.AppendLine($" \"{Escape(document.SchemaVersion)}\",");
- builder.AppendLine($" {(document.IsGlobal ? "true" : "false")},");
- builder.AppendLine(document.NameMember == null
- ? " null,"
- : $" \"{Escape(document.NameMember)}\",");
- builder.AppendLine(document.NameAccessorMethodName == null
- ? " null,"
- : $" {document.NameAccessorMethodName},");
- builder.AppendLine(canEmitPayloadCodecs && document.HasDenseSlots
- ? $" {document.PayloadSerializerMethodName},"
- : " null,");
- builder.AppendLine(canEmitPayloadCodecs && document.HasDenseSlots && document.CanConstructForDeserialization
- ? $" {document.PayloadDeserializerMethodName},"
- : " null,");
- builder.AppendLine(" new global::GameCult.Caching.CultGeneratedDocumentIndexAccessor[]");
- builder.AppendLine(" {");
- foreach (var indexAccessor in document.IndexAccessors)
- {
- builder.AppendLine($" new global::GameCult.Caching.CultGeneratedDocumentIndexAccessor(\"{Escape(indexAccessor.Alias)}\", {indexAccessor.AccessorMethodName}),");
- }
- builder.AppendLine(" },");
- builder.AppendLine(" new global::GameCult.Caching.CultGeneratedDocumentMemberDefinition[]");
- builder.AppendLine(" {");
- foreach (var member in document.Members.OrderBy(member => member.Slot))
- {
- builder.AppendLine(" new global::GameCult.Caching.CultGeneratedDocumentMemberDefinition(");
- builder.AppendLine($" \"{Escape(member.Name)}\",");
- builder.AppendLine($" {member.Slot},");
- builder.AppendLine($" \"{Escape(member.SchemaTypeName)}\",");
- builder.AppendLine($" {(member.IsReference ? "true" : "false")},");
- builder.AppendLine($" {(member.IsMany ? "true" : "false")},");
- builder.AppendLine(member.TargetSchemaName == null
- ? " null,"
- : $" \"{Escape(member.TargetSchemaName)}\",");
- builder.AppendLine($" {(member.IsName ? "true" : "false")},");
- builder.AppendLine(member.IndexAlias == null
- ? " null),"
- : $" \"{Escape(member.IndexAlias)}\"),");
- }
- builder.AppendLine(" }),");
- }
-
- builder.AppendLine(" };");
- builder.AppendLine();
- builder.AppendLine(" public global::System.Collections.Generic.IEnumerable GetDocumentDefinitions()");
- builder.AppendLine(" {");
- builder.AppendLine(" return s_documents;");
- builder.AppendLine(" }");
- builder.AppendLine();
-
- foreach (var document in documents)
- {
- if (canEmitPayloadCodecs && document.HasDenseSlots)
- {
- EmitPayloadSerializer(builder, document);
- if (document.CanConstructForDeserialization)
- {
- EmitPayloadDeserializer(builder, document);
- }
- }
-
- foreach (var member in document.Members.Where(member => member.AccessorMethodName != null))
- {
- builder.AppendLine($" private static string? {member.AccessorMethodName}(object document)");
- builder.AppendLine(" {");
- builder.AppendLine(member.CanBeNull
- ? $" return (({document.DocumentTypeName})document).{member.Name}?.ToString();"
- : $" return (({document.DocumentTypeName})document).{member.Name}.ToString();");
- builder.AppendLine(" }");
- builder.AppendLine();
- }
-
- foreach (var member in document.Members.Where(member => member.IndexAccessorMethodName != null))
- {
- builder.AppendLine($" private static string {member.IndexAccessorMethodName}(object document)");
- builder.AppendLine(" {");
- builder.AppendLine(member.CanBeNull
- ? $" return (({document.DocumentTypeName})document).{member.Name}?.ToString() ?? string.Empty;"
- : $" return (({document.DocumentTypeName})document).{member.Name}.ToString();");
- builder.AppendLine(" }");
- builder.AppendLine();
- }
- }
-
- builder.AppendLine(" }");
- builder.AppendLine("}");
- context.AddSource($"{providerName}.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
- }
-
- private static void EmitPayloadSerializer(StringBuilder builder, DocumentShape document)
- {
- builder.AppendLine($" private static byte[] {document.PayloadSerializerMethodName}(object document)");
- builder.AppendLine(" {");
- builder.AppendLine($" var typed = ({document.DocumentTypeName})document;");
- builder.AppendLine($" var options = global::GameCult.Caching.MessagePack.CultDocumentMessagePackSerialization.OptionsFor(typeof({document.DocumentTypeName}).Assembly);");
- builder.AppendLine(" var buffer = new global::System.Buffers.ArrayBufferWriter();");
- builder.AppendLine(" var writer = new global::MessagePack.MessagePackWriter(buffer);");
- builder.AppendLine($" writer.WriteArrayHeader({document.Members.Length});");
- foreach (var member in document.Members.OrderBy(member => member.Slot))
- {
- builder.AppendLine($" global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<{member.TypeSyntaxName}>(options.Resolver).Serialize(ref writer, typed.{member.Name}, options);");
- }
- builder.AppendLine(" writer.Flush();");
- builder.AppendLine(" return buffer.WrittenSpan.ToArray();");
- builder.AppendLine(" }");
- builder.AppendLine();
- }
-
- private static void EmitPayloadDeserializer(StringBuilder builder, DocumentShape document)
- {
- builder.AppendLine($" private static object {document.PayloadDeserializerMethodName}(byte[] payload)");
- builder.AppendLine(" {");
- builder.AppendLine($" var options = global::GameCult.Caching.MessagePack.CultDocumentMessagePackSerialization.OptionsFor(typeof({document.DocumentTypeName}).Assembly);");
- builder.AppendLine(" var reader = new global::MessagePack.MessagePackReader(payload);");
- builder.AppendLine(" var count = reader.ReadArrayHeader();");
- builder.AppendLine($" var value = new {document.DocumentTypeName}();");
- foreach (var member in document.Members.OrderBy(member => member.Slot))
- {
- builder.AppendLine($" if (count > {member.Slot})");
- builder.AppendLine(" {");
- builder.AppendLine($" value.{member.Name} = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<{member.TypeSyntaxName}>(options.Resolver).Deserialize(ref reader, options);");
- builder.AppendLine(" }");
- }
- builder.AppendLine($" for (var index = {document.Members.Length}; index < count; index++)");
- builder.AppendLine(" {");
- builder.AppendLine(" reader.Skip();");
- builder.AppendLine(" }");
- builder.AppendLine(" return value;");
- builder.AppendLine(" }");
- builder.AppendLine();
- }
-
- private static string Escape(string value)
- {
- return value
- .Replace("\\", "\\\\")
- .Replace("\"", "\\\"")
- .Replace("\r", "\\r")
- .Replace("\n", "\\n")
- .Replace("\t", "\\t");
- }
-
- private static string SanitizeIdentifier(string value)
- {
- var builder = new StringBuilder(value.Length);
- foreach (var character in value)
- {
- builder.Append(char.IsLetterOrDigit(character) ? character : '_');
- }
-
- if (builder.Length == 0 || !char.IsLetter(builder[0]) && builder[0] != '_')
- {
- builder.Insert(0, '_');
- }
-
- return builder.ToString();
- }
-
- private static string GetSchemaTypeName(ITypeSymbol typeSymbol)
- {
- if (typeSymbol is IArrayTypeSymbol arrayType)
- {
- return GetSchemaTypeName(arrayType.ElementType) + "[]";
- }
-
- if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType)
- {
- var baseName = GetNamedTypeBaseName(namedType.ConstructedFrom);
- var arguments = string.Join(", ", namedType.TypeArguments.Select(GetSchemaTypeName));
- return $"{baseName}<{arguments}>";
- }
-
- return typeSymbol.SpecialType switch
- {
- SpecialType.System_Boolean => "System.Boolean",
- SpecialType.System_Byte => "System.Byte",
- SpecialType.System_Char => "System.Char",
- SpecialType.System_Decimal => "System.Decimal",
- SpecialType.System_Double => "System.Double",
- SpecialType.System_Int16 => "System.Int16",
- SpecialType.System_Int32 => "System.Int32",
- SpecialType.System_Int64 => "System.Int64",
- SpecialType.System_Object => "System.Object",
- SpecialType.System_SByte => "System.SByte",
- SpecialType.System_Single => "System.Single",
- SpecialType.System_String => "System.String",
- SpecialType.System_UInt16 => "System.UInt16",
- SpecialType.System_UInt32 => "System.UInt32",
- SpecialType.System_UInt64 => "System.UInt64",
- _ => GetNamedTypeBaseName((INamedTypeSymbol)typeSymbol)
- };
- }
-
- private static string GetNamedTypeBaseName(INamedTypeSymbol typeSymbol)
- {
- var parts = new Stack();
- var current = typeSymbol;
- while (current != null)
- {
- parts.Push(current.Name);
- current = current.ContainingType;
- }
-
- var namespaceName = typeSymbol.ContainingNamespace?.ToDisplayString();
- return string.IsNullOrWhiteSpace(namespaceName)
- ? string.Join(".", parts)
- : namespaceName + "." + string.Join(".", parts);
- }
-
- private static bool CanBeNull(ITypeSymbol typeSymbol)
- {
- if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated)
- {
- return true;
- }
-
- if (typeSymbol.IsReferenceType)
- {
- return true;
- }
-
- return typeSymbol is INamedTypeSymbol namedType &&
- namedType.IsGenericType &&
- namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
- }
-
- private sealed class MemberCandidate
- {
- public MemberCandidate(ISymbol member, ITypeSymbol type, int depth, bool writable, IPropertySymbol? divergentRoot)
- {
- Member = member;
- Type = type;
- Depth = depth;
- Writable = writable;
- DivergentRoot = divergentRoot;
- }
-
- public ISymbol Member { get; }
- public ITypeSymbol Type { get; }
- public int Depth { get; }
- public bool Writable { get; }
- public IPropertySymbol? DivergentRoot { get; }
- }
-
- private sealed class MemberShapeSeed
- {
- private MemberShapeSeed(
- string name,
- string schemaTypeName,
- string typeSyntaxName,
- bool isName,
- string? indexAlias,
- bool isReference,
- bool isMany,
- bool canBeNull,
- string? targetSchemaName)
- {
- Name = name;
- SchemaTypeName = schemaTypeName;
- TypeSyntaxName = typeSyntaxName;
- IsName = isName;
- IndexAlias = indexAlias;
- IsReference = isReference;
- IsMany = isMany;
- CanBeNull = canBeNull;
- TargetSchemaName = targetSchemaName;
- }
-
- public string Name { get; }
- public string SchemaTypeName { get; }
- public string TypeSyntaxName { get; }
- public bool IsName { get; }
- public string? IndexAlias { get; }
- public bool IsReference { get; }
- public bool IsMany { get; }
- public bool CanBeNull { get; }
- public string? TargetSchemaName { get; }
-
- // Accessors are named per document: one provider class holds every document of the assembly, and documents sharing a
- // base member would otherwise emit the same method twice.
- public MemberShape WithSlot(int slot, string documentStem)
- {
- var accessorStem = documentStem + "_" + SanitizeIdentifier(Name);
- return new MemberShape(
- Name,
- slot,
- SchemaTypeName,
- TypeSyntaxName,
- IsReference,
- IsMany,
- TargetSchemaName,
- IsName,
- IndexAlias,
- CanBeNull,
- IsName ? $"Access_{accessorStem}_Name" : null,
- IndexAlias != null ? $"Access_{accessorStem}_Index" : null);
- }
-
- public static MemberShapeSeed From(ISymbol member, ITypeSymbol memberType)
- {
- var referenceAttribute = GetMemberAttribute(member, "GameCult.Caching.CultReferenceAttribute");
- var explicitTarget = referenceAttribute?.ConstructorArguments.Length > 0
- ? referenceAttribute.ConstructorArguments[0].Value as INamedTypeSymbol
- : null;
- var many = referenceAttribute?.ConstructorArguments.Length > 1 &&
- referenceAttribute.ConstructorArguments[1].Value is bool manyValue &&
- manyValue;
- var targetType = ResolveReferenceTarget(memberType, explicitTarget);
- var targetSchemaName = targetType == null
- ? null
- : GetAttribute(targetType, "GameCult.Caching.CultDocumentAttribute")?.ConstructorArguments[0].Value as string;
- var indexAttribute = GetMemberAttribute(member, "GameCult.Caching.CultIndexAttribute");
- var indexAlias = indexAttribute == null
- ? null
- : indexAttribute.ConstructorArguments.Length == 0 || indexAttribute.ConstructorArguments[0].Value is not string alias || string.IsNullOrWhiteSpace(alias)
- ? member.Name
- : alias;
-
- return new MemberShapeSeed(
- member.Name,
- GetSchemaTypeName(memberType),
- memberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
- GetMemberAttribute(member, "GameCult.Caching.CultNameAttribute") != null,
- indexAlias,
- targetType != null || referenceAttribute != null,
- many,
- CanBeNull(memberType),
- targetSchemaName);
- }
-
- private static INamedTypeSymbol? ResolveReferenceTarget(ITypeSymbol memberType, INamedTypeSymbol? explicitTarget)
- {
- if (explicitTarget != null)
- {
- return explicitTarget;
- }
-
- return memberType is INamedTypeSymbol namedType &&
- namedType.IsGenericType &&
- namedType.Name == "CultRecordRef" &&
- namedType.ContainingNamespace.ToDisplayString() == "GameCult.Caching"
- ? namedType.TypeArguments[0] as INamedTypeSymbol
- : null;
- }
- }
-
- private sealed class DocumentShape
- {
- public DocumentShape(
- string documentTypeName,
- string schemaName,
- string schemaVersion,
- bool isGlobal,
- string? nameMember,
- string? nameAccessorMethodName,
- ImmutableArray indexAccessors,
- ImmutableArray members,
- bool canConstructForDeserialization,
- bool hasDenseSlots,
- ImmutableArray rejections,
- Location? location)
- {
- Rejections = rejections;
- Location = location;
- DocumentTypeName = documentTypeName;
- SchemaName = schemaName;
- SchemaVersion = schemaVersion;
- IsGlobal = isGlobal;
- NameMember = nameMember;
- NameAccessorMethodName = nameAccessorMethodName;
- var safeStem = SanitizeIdentifier(documentTypeName.Replace("global::", string.Empty).Replace(".", "_"));
- PayloadSerializerMethodName = $"Serialize_{safeStem}_Payload";
- PayloadDeserializerMethodName = $"Deserialize_{safeStem}_Payload";
- IndexAccessors = indexAccessors;
- Members = members;
- CanConstructForDeserialization = canConstructForDeserialization;
- HasDenseSlots = hasDenseSlots;
- }
-
- public string DocumentTypeName { get; }
- public string SchemaName { get; }
- public string SchemaVersion { get; }
- public bool IsGlobal { get; }
- public string? NameMember { get; }
- public string? NameAccessorMethodName { get; }
- public string PayloadSerializerMethodName { get; }
- public string PayloadDeserializerMethodName { get; }
- public ImmutableArray IndexAccessors { get; }
- public ImmutableArray Members { get; }
- public bool CanConstructForDeserialization { get; }
- public bool HasDenseSlots { get; }
- public ImmutableArray Rejections { get; }
- public Location? Location { get; }
- }
-
- private sealed class IndexAccessorShape
- {
- public IndexAccessorShape(string alias, string accessorMethodName)
- {
- Alias = alias;
- AccessorMethodName = accessorMethodName;
- }
-
- public string Alias { get; }
- public string AccessorMethodName { get; }
- }
-
- private sealed class MemberShape
- {
- public MemberShape(
- string name,
- int slot,
- string schemaTypeName,
- string typeSyntaxName,
- bool isReference,
- bool isMany,
- string? targetSchemaName,
- bool isName,
- string? indexAlias,
- bool canBeNull,
- string? accessorMethodName,
- string? indexAccessorMethodName)
- {
- Name = name;
- Slot = slot;
- SchemaTypeName = schemaTypeName;
- TypeSyntaxName = typeSyntaxName;
- IsReference = isReference;
- IsMany = isMany;
- TargetSchemaName = targetSchemaName;
- IsName = isName;
- IndexAlias = indexAlias;
- CanBeNull = canBeNull;
- AccessorMethodName = accessorMethodName;
- IndexAccessorMethodName = indexAccessorMethodName;
- }
-
- public string Name { get; }
- public int Slot { get; }
- public string SchemaTypeName { get; }
- public string TypeSyntaxName { get; }
- public bool IsReference { get; }
- public bool IsMany { get; }
- public string? TargetSchemaName { get; }
- public bool IsName { get; }
- public string? IndexAlias { get; }
- public bool CanBeNull { get; }
- public string? AccessorMethodName { get; }
- public string? IndexAccessorMethodName { get; }
- }
- }
-}
diff --git a/src/GameCult.Caching.MessagePack.Generator/GameCult.Caching.MessagePack.Generator.csproj b/src/GameCult.Caching.MessagePack.Generator/GameCult.Caching.MessagePack.Generator.csproj
deleted file mode 100644
index 1a6e372e..00000000
--- a/src/GameCult.Caching.MessagePack.Generator/GameCult.Caching.MessagePack.Generator.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- true
- enable
- false
- netstandard2.0
- true
-
-
-
-
-
-
-
-
diff --git a/src/GameCult.Caching.MessagePack.Generator/README.md b/src/GameCult.Caching.MessagePack.Generator/README.md
deleted file mode 100644
index df3e0ecc..00000000
--- a/src/GameCult.Caching.MessagePack.Generator/README.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# GameCult.Caching.MessagePack.Generator
-
-A Roslyn incremental source generator that emits CultCache document metadata,
-and MessagePack payload codecs where it can, for classes marked
-`[CultDocument]`.
-
-## Scope
-
-Infrastructure for the caching stack. Application code does not reference it
-directly; it arrives through `GameCult.Caching.MessagePack.Analyzers`. Without
-it, `CultDocumentRegistry` builds descriptors by reflection, and the two differ:
-reflective descriptors include inherited members and order unkeyed members by
-declaration token; generated descriptors include only declared members and
-order unkeyed members by name. For derived documents or unkeyed members the two
-produce different schema ids. This is scheduled to be fixed in Cut 4.
-
-## What It Generates
-
-One `GeneratedCultDocumentMetadataProvider_` per assembly, registered
-with `[assembly: CultGeneratedDocumentMetadataProvider]`, holding for each
-`[CultDocument]` class:
-
-- schema name, schema version, and whether the type is `[CultGlobal]`
-- member definitions (name, slot, persisted type name, reference and
- cardinality, `[CultName]`, `[CultIndex]`)
-- name and index accessors
-- a payload serializer, and a deserializer when the type has a public or
- internal parameterless constructor
-
-Members are the public, non-static, non-`[IgnoreMember]` fields and settable
-properties **declared on the class itself**; inherited members are not
-discovered. Members with `[Key(n)]` take slot `n`; unkeyed members follow the
-highest key in ordinal name order. Annotate every persisted member: source
-order is not a schema.
-
-Payload codecs are emitted only when slots are dense (`0..n-1`) and the
-compilation references MessagePack and `GameCult.Caching.MessagePack`.
-Otherwise the definition carries no codec and serialization falls back to the
-reflective path. A generated payload is an array of the members in slot order;
-the deserializer leaves missing trailing slots at their initializer values and
-skips unknown extra slots. Generated codecs read
-`CultDocumentMessagePackSerialization.Options` at call time.
-
-The generator has no `[Union]` handling.
-
-## When You Need To Think About This Package
-
-- a document's descriptor or schema id differs between generated and
- reflective builds
-- a document serializes through the reflective path when you expected a codec
-- you are debugging build-time source generation
-
-## Distribution
-
-Packaged for consumers through
-[../GameCult.Caching.MessagePack.Analyzers/README.md](../GameCult.Caching.MessagePack.Analyzers/README.md).
diff --git a/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs b/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs
index 78e3eabf..1e99125e 100644
--- a/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs
+++ b/src/GameCult.Caching.MessagePack/CultDocumentMessagePackSerialization.cs
@@ -77,15 +77,7 @@ public static class CultDocumentMessagePackSerialization
public static byte[] SerializeUntyped(object value, Type type, CultDocumentRegistry registry)
{
if (registry == null) throw new ArgumentNullException(nameof(registry));
- if (value != null)
- {
- var descriptor = registry.GetRequired(type);
- if (descriptor.GeneratedPayloadSerializer != null)
- {
- return descriptor.GeneratedPayloadSerializer(value);
- }
- }
-
+ if (value != null) registry.GetRequired(type);
return MessagePackSerializer.Serialize(type, value, OptionsFor(type.Assembly));
}
@@ -97,12 +89,7 @@ public static class CultDocumentMessagePackSerialization
public static object DeserializeUntyped(Type type, byte[] payload, CultDocumentRegistry registry)
{
if (registry == null) throw new ArgumentNullException(nameof(registry));
- var descriptor = registry.GetRequired(type);
- if (descriptor.GeneratedPayloadDeserializer != null)
- {
- return descriptor.GeneratedPayloadDeserializer(payload);
- }
-
+ registry.GetRequired(type);
return MessagePackSerializer.Deserialize(type, payload, OptionsFor(type.Assembly))
?? throw new InvalidOperationException($"MessagePack returned null for Cult document type {type.FullName}.");
}
diff --git a/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj b/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj
index b089c776..794936bd 100644
--- a/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj
+++ b/src/GameCult.Caching.MessagePack/GameCult.Caching.MessagePack.csproj
@@ -6,9 +6,6 @@
-
-
diff --git a/src/GameCult.Caching.MessagePack/README.md b/src/GameCult.Caching.MessagePack/README.md
index 660da700..c69ab887 100644
--- a/src/GameCult.Caching.MessagePack/README.md
+++ b/src/GameCult.Caching.MessagePack/README.md
@@ -10,16 +10,16 @@ format for the attribute-first `GameCult.Caching` stack.
- `CultDocumentResolver`
- `CultRecordRefFormatter`
-When a project declares `[CultDocument]` models, wire in the
-`GameCult.Caching.MessagePack.Generator` analyzer there as well. It emits
-assembly-local metadata providers for the cache registry. Cult documents then
-serialize through explicit generated slot codecs, while the store snapshot
-format is written by hand against `MessagePackWriter`/`MessagePackReader`.
+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 plain attributed document payloads through explicit generated slot codecs
+- serializes attributed document payloads as MessagePack `[Key(n)]` arrays
- keeps explicit `CultRecordRef` values compact on disk/wire
- preserves the cache/store split where metadata lives outside the domain model
@@ -54,7 +54,5 @@ await cache.PullAllBackingStoresAsync();
- Payload bytes are MessagePack. Store metadata and schema catalogs are also
persisted through hand-written MessagePack array layouts in this package.
-- The generator package now targets attributed document types instead of
- `DatabaseEntry` subclasses.
- The raw CultNet document lane should treat these payload bytes as already
blessed, not decode and re-encode them for sport.
diff --git a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md
index 17609b15..b3cd9f43 100644
--- a/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md
+++ b/src/GameCult.Caching/Contracts/cultcache-schema-compatibility.md
@@ -30,8 +30,14 @@ documents and the expected receipts.
A member's slot is its MessagePack `[Key(n)]` integer. MessagePack's `[Key]` and
`[IgnoreMember]` are the single slot authority, so `GameCult.Caching` depends on
-`MessagePack.Annotations`. The registry and the generator (diagnostic GCC001)
-refuse, with the same message: a persisted member without `[Key]`, a string
+`MessagePack.Annotations`. Payloads are written by `MessagePackSerializer`, so
+the registry refuses a document type without `[MessagePackObject]` and a
+non-public document type without `[MessagePackObject(AllowPrivate = true)]`.
+`AllowPrivate` makes MessagePack read non-public members too, so every
+non-public instance field or property of such a type must be `[IgnoreMember]`;
+the registry persists public members only. A
+member's type name is the CLR full name; a nested type is written `Outer+Inner`.
+The registry also refuses a persisted member without `[Key]`, a string
`[Key]`, two members sharing a slot (including `new`-hidden members), a hidden
persisted member, an override whose `[Key]` or `[IgnoreMember]` differs from its
base declaration's (MessagePack reads the base declaration), and a persisted
diff --git a/src/GameCult.Caching/Contracts/cultcache-store-composition.md b/src/GameCult.Caching/Contracts/cultcache-store-composition.md
index 636f3ba6..697bf959 100644
--- a/src/GameCult.Caching/Contracts/cultcache-store-composition.md
+++ b/src/GameCult.Caching/Contracts/cultcache-store-composition.md
@@ -213,8 +213,7 @@ another runtime need not obey the `storedAt` rule.
- Options belong to the assembly that declares a document. That assembly names
its formatter resolvers with `[assembly: CultCacheFormatterResolver(typeof(R))]`;
- generated and reflective serializers resolve options from the document's
- assembly. There is no mutable static, no registration call, and no load-order
+ the payload serializer resolves options from the document's assembly. There is no mutable static, no registration call, and no load-order
rule.
- A generic document's options come from the generic definition's assembly:
`Doc` uses the resolvers `Doc`'s assembly declares, never `X`'s. A formatter
diff --git a/src/GameCult.Caching/CultCache.cs b/src/GameCult.Caching/CultCache.cs
index 57302419..8f0a5c80 100644
--- a/src/GameCult.Caching/CultCache.cs
+++ b/src/GameCult.Caching/CultCache.cs
@@ -12,6 +12,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MessagePack;
using R3;
namespace GameCult.Caching
@@ -120,8 +121,6 @@ namespace GameCult.Caching
bool isGlobal,
string? nameMember,
Func