The generator and CultDocumentRegistry now discover members by the same
steps: most-derived declaration first, override chain collapsed to one
member, attributes read up the override chain, rejections in one order
with one text (GCC001 / InvalidOperationException).
- Accessors are named per document, so documents sharing a [CultName] or
[CultIndex] base member no longer emit duplicate methods (CS0111).
- Duplicate [Key] slots are refused, including new-hidden fields and
properties; a hidden persisted member with a different slot is refused.
- String [Key("x")] gets its own message instead of "has no [Key]".
- A persisted property whose set accessor is private, protected or init,
or a readonly field, is refused instead of CS0272 in generated code.
- Overrides: MessagePack's DynamicObjectResolver reads the base
declaration's [Key]/[IgnoreMember] (it throws "key is duplicated" for a
re-keyed override and serializes an override marked [IgnoreMember]).
An override that changes them is refused in both paths; an override
that repeats or omits them is accepted and byte-pinned.
- Typed Serialize<T>/Deserialize<T> used static Options and skipped
generated codecs and declared resolvers; deleted. CultNetDocumentBinding
and CultMeshSnapshots route through the untyped path with their registry.
DocumentShapeSweepTests compiles each shape through the generator and
compares generated vs reflective schema id, catalog, name/index values
and payload against MessagePack's own bytes, or equal rejection text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
68 lines
2.4 KiB
C#
68 lines
2.4 KiB
C#
using GameCult.Caching;
|
|
using GameCult.Caching.MessagePack;
|
|
using GameCult.Networking;
|
|
using MessagePack;
|
|
using MessagePack.Formatters;
|
|
using NUnit.Framework;
|
|
|
|
[assembly: CultCacheFormatterResolver(typeof(GameCult.Networking.Tests.BindingPairResolver))]
|
|
|
|
namespace GameCult.Networking.Tests
|
|
{
|
|
public class CultNetDocumentBindingResolverTests
|
|
{
|
|
[Test]
|
|
public void TypedBindingUsesTheDocumentAssemblyResolver()
|
|
{
|
|
var registry = CultDocumentRegistry.ForTypes(new[] { typeof(BindingPairDocument) });
|
|
var binding = CultNetDocumentBinding.ForDocument<BindingPairDocument>(registry);
|
|
|
|
var payload = binding.PayloadSerializer(new BindingPairDocument { Name = "p", Value = new BindingPair { A = 1.5f, B = -2f } });
|
|
var decoded = (BindingPairDocument)binding.PayloadDeserializer(payload);
|
|
|
|
Assert.That(decoded.Name, Is.EqualTo("p"));
|
|
Assert.That(decoded.Value.A, Is.EqualTo(1.5f));
|
|
Assert.That(decoded.Value.B, Is.EqualTo(-2f));
|
|
}
|
|
}
|
|
|
|
public struct BindingPair
|
|
{
|
|
public float A;
|
|
public float B;
|
|
}
|
|
|
|
[CultDocument("tests.binding_pair", "tests.binding_pair.v1")]
|
|
[MessagePackObject]
|
|
public sealed class BindingPairDocument
|
|
{
|
|
[Key(0)] [CultName] public string Name = string.Empty;
|
|
[Key(1)] public BindingPair Value;
|
|
}
|
|
|
|
public sealed class BindingPairResolver : IFormatterResolver
|
|
{
|
|
public static readonly IFormatterResolver Instance = new BindingPairResolver();
|
|
|
|
public IMessagePackFormatter<T>? GetFormatter<T>() =>
|
|
typeof(T) == typeof(BindingPair) ? (IMessagePackFormatter<T>)(object)PairFormatter.Instance : null;
|
|
|
|
private sealed class PairFormatter : IMessagePackFormatter<BindingPair>
|
|
{
|
|
public static readonly PairFormatter Instance = new();
|
|
|
|
public void Serialize(ref MessagePackWriter writer, BindingPair value, MessagePackSerializerOptions options)
|
|
{
|
|
writer.WriteArrayHeader(2);
|
|
writer.Write(value.A);
|
|
writer.Write(value.B);
|
|
}
|
|
|
|
public BindingPair Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
|
|
{
|
|
reader.ReadArrayHeader();
|
|
return new BindingPair { A = reader.ReadSingle(), B = reader.ReadSingle() };
|
|
}
|
|
}
|
|
}
|
|
}
|