From 547053215800b0dfd7448ae4be6d33f80eae42d5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:34:20 -0700 Subject: [PATCH] fix: size migration content SaveFlag enums like the live writer (v4.1.1) The V{N}Content struct emitted one int-backed V{N}SaveFlag regardless of flag count. The live generator that wrote vN uses ulong past 32 flags and one extra ulong enum per 64 past 64, so migrating from a version with 33+ save-flagged fields failed with CS0266, and 65+ would misread the stream. The content struct now partitions flags with the same rule, reads every flag enum before any field, and tests each field against its own enum. Output for 32 or fewer flags is unchanged. Adds a round-trip test (20/40/70 flags) that serializes with the live generator and migrates through MigrateFrom(V0Content), plus a MigrationSaveFlagsMultiEnum snapshot. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../ModernUO.Serialization.Annotations.csproj | 4 +- .../Helpers/SourceGeneratorTestHelper.cs | 63 +- .../MigrationSaveFlagTests.cs | 280 ++++++ ...ntent.WideMigratingItem.Serialization.g.cs | 823 ++++++++++++++++++ .../MigrationSaveFlagsMultiEnum/Input.cs | 93 ++ ...rver.TestContent.WideMigratingItem.v0.json | 407 +++++++++ .../ModernUO.Serialization.Generator.csproj | 4 +- ...alizationEntityGeneration.ContentStruct.cs | 90 +- ...ernUO.Serialization.SchemaGenerator.csproj | 4 +- 9 files changed, 1731 insertions(+), 37 deletions(-) create mode 100644 ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs create mode 100644 ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs create mode 100644 ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs create mode 100644 ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json diff --git a/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj b/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj index 85ff783..8fe1b6a 100644 --- a/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj +++ b/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj @@ -4,8 +4,8 @@ ModernUO.Serialization.Annotations netstandard2.0 preview - 4.1.0 - 4.1.0 + 4.1.1 + 4.1.1 ModernUO.Serialization.Annotations ModernUO.Serialization true diff --git a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs index 7edd5a3..036f9eb 100644 --- a/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs +++ b/ModernUO.Serialization.Generator.Tests/Helpers/SourceGeneratorTestHelper.cs @@ -255,6 +255,55 @@ ImmutableArray CompileErrors ) RunGeneratorAllOutputs( string sourceCode, IEnumerable<(string fileName, string content)>? additionalTexts = null) + { + var (diagnostics, outputCompilation) = RunGeneratorOnCompilation("TestAssembly", sourceCode, additionalTexts); + + var sources = outputCompilation.SyntaxTrees + .Where(st => st.FilePath.EndsWith(".Serialization.g.cs")) + .Select(st => (Path.GetFileName(st.FilePath), st.GetText().ToString())) + .ToImmutableArray(); + + var compileErrors = outputCompilation + .GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToImmutableArray(); + + return (diagnostics, sources, compileErrors); + } + + /// + /// Runs the generator, emits the output compilation and loads it, so tests can execute + /// generated code. Fails with every generator and compile error when the output does not build. + /// + public static Assembly CompileAndLoad( + string assemblyName, + string sourceCode, + IEnumerable<(string fileName, string content)>? additionalTexts = null) + { + var (diagnostics, outputCompilation) = RunGeneratorOnCompilation(assemblyName, sourceCode, additionalTexts); + + using var stream = new MemoryStream(); + var result = outputCompilation.Emit(stream); + + var errors = diagnostics + .Concat(result.Diagnostics) + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + + if (!result.Success || errors.Count > 0) + { + throw new InvalidOperationException( + $"Generated code did not build:\n{string.Join("\n", errors.Take(20))}" + ); + } + + return Assembly.Load(stream.ToArray()); + } + + private static (ImmutableArray Diagnostics, Compilation OutputCompilation) RunGeneratorOnCompilation( + string assemblyName, + string sourceCode, + IEnumerable<(string fileName, string content)>? additionalTexts) { var syntaxTrees = new List { @@ -273,7 +322,7 @@ ImmutableArray CompileErrors .ToList(); var compilation = CSharpCompilation.Create( - "TestAssembly", + assemblyName, syntaxTrees, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) @@ -295,17 +344,7 @@ ImmutableArray CompileErrors driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); - var sources = outputCompilation.SyntaxTrees - .Where(st => st.FilePath.EndsWith(".Serialization.g.cs")) - .Select(st => (Path.GetFileName(st.FilePath), st.GetText().ToString())) - .ToImmutableArray(); - - var compileErrors = outputCompilation - .GetDiagnostics() - .Where(d => d.Severity == DiagnosticSeverity.Error) - .ToImmutableArray(); - - return (diagnostics, sources, compileErrors); + return (diagnostics, outputCompilation); } public static bool HasDiagnostic(ImmutableArray diagnostics, string diagnosticId) diff --git a/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs b/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs new file mode 100644 index 0000000..62a4372 --- /dev/null +++ b/ModernUO.Serialization.Generator.Tests/MigrationSaveFlagTests.cs @@ -0,0 +1,280 @@ +using System.Text; +using ModernUO.Serialization.Generator.Tests.Helpers; +using Xunit; + +namespace ModernUO.Serialization.Generator.Tests; + +/// +/// A migration content struct reads a stream written by the live generator of an older version, so +/// its save flag enums must be sized and partitioned exactly like the live SaveFlag enums were: +/// int for up to 32 flags, ulong for 33-64, and several ulong enums past 64. +/// +public class MigrationSaveFlagTests +{ + // Binary reader/writer that sizes enums by their underlying type, like ModernUO's, so a width + // mismatch between writer and content struct misaligns the stream instead of going unnoticed. + private const string BinaryStreams = """ + namespace Server.TestContent + { + using System; + using System.IO; + using System.Runtime.CompilerServices; + + public sealed class BinaryGenericWriter : Server.IGenericWriter + { + private readonly BinaryWriter _writer; + public BinaryGenericWriter(Stream stream) => _writer = new BinaryWriter(stream); + public void Flush() => _writer.Flush(); + + public void Write(string value) + { + _writer.Write(value != null); + if (value != null) _writer.Write(value); + } + + public void Write(int value) => _writer.Write(value); + public void Write(uint value) => _writer.Write(value); + public void Write(long value) => _writer.Write(value); + public void Write(ulong value) => _writer.Write(value); + public void Write(short value) => _writer.Write(value); + public void Write(ushort value) => _writer.Write(value); + public void Write(byte value) => _writer.Write(value); + public void Write(sbyte value) => _writer.Write(value); + public void Write(bool value) => _writer.Write(value); + public void Write(float value) => _writer.Write(value); + public void Write(double value) => _writer.Write(value); + public void Write(decimal value) => _writer.Write(value); + public void Write(DateTime value) => throw new NotSupportedException(); + public void WriteDeltaTime(DateTime value) => throw new NotSupportedException(); + public void WriteAnchoredTime(DateTime value) => throw new NotSupportedException(); + public void Write(TimeSpan value) => throw new NotSupportedException(); + public void Write(Guid value) => throw new NotSupportedException(); + public void WriteEncodedInt(int value) => _writer.Write7BitEncodedInt(value); + public void Write(T value) where T : struct, Enum => WriteEnum(value); + + public void WriteEnum(T value) where T : struct, Enum + { + switch (Unsafe.SizeOf()) + { + case 1: _writer.Write(Unsafe.As(ref value)); break; + case 2: _writer.Write(Unsafe.As(ref value)); break; + case 4: _writer.Write(Unsafe.As(ref value)); break; + default: _writer.Write(Unsafe.As(ref value)); break; + } + } + + public void Write(Serial value) => throw new NotSupportedException(); + public void Write(Point2D value) => throw new NotSupportedException(); + public void Write(Point3D value) => throw new NotSupportedException(); + public void Write(Rectangle2D value) => throw new NotSupportedException(); + public void Write(Rectangle3D value) => throw new NotSupportedException(); + } + + public sealed class BinaryGenericReader : Server.IGenericReader + { + private readonly BinaryReader _reader; + public BinaryGenericReader(Stream stream) => _reader = new BinaryReader(stream); + public bool AtEnd => _reader.BaseStream.Position == _reader.BaseStream.Length; + + public string ReadString(bool intern = false) => _reader.ReadBoolean() ? _reader.ReadString() : null; + public int ReadInt() => _reader.ReadInt32(); + public uint ReadUInt() => _reader.ReadUInt32(); + public long ReadLong() => _reader.ReadInt64(); + public ulong ReadULong() => _reader.ReadUInt64(); + public short ReadShort() => _reader.ReadInt16(); + public ushort ReadUShort() => _reader.ReadUInt16(); + public byte ReadByte() => _reader.ReadByte(); + public sbyte ReadSByte() => _reader.ReadSByte(); + public bool ReadBool() => _reader.ReadBoolean(); + public float ReadFloat() => _reader.ReadSingle(); + public double ReadDouble() => _reader.ReadDouble(); + public decimal ReadDecimal() => _reader.ReadDecimal(); + public DateTime ReadDateTime() => throw new NotSupportedException(); + public DateTime ReadDeltaTime() => throw new NotSupportedException(); + public DateTime ReadAnchoredTime() => throw new NotSupportedException(); + public TimeSpan ReadTimeSpan() => throw new NotSupportedException(); + public Guid ReadGuid() => throw new NotSupportedException(); + public int ReadEncodedInt() => _reader.Read7BitEncodedInt(); + + public T ReadEnum() where T : struct, Enum + { + T value = default; + switch (Unsafe.SizeOf()) + { + case 1: Unsafe.As(ref value) = _reader.ReadByte(); break; + case 2: Unsafe.As(ref value) = _reader.ReadUInt16(); break; + case 4: Unsafe.As(ref value) = _reader.ReadUInt32(); break; + default: Unsafe.As(ref value) = _reader.ReadUInt64(); break; + } + + return value; + } + + public Serial ReadSerial() => throw new NotSupportedException(); + public Point2D ReadPoint2D() => throw new NotSupportedException(); + public Point3D ReadPoint3D() => throw new NotSupportedException(); + public Rectangle2D ReadRect2D() => throw new NotSupportedException(); + public Rectangle3D ReadRect3D() => throw new NotSupportedException(); + } + } + """; + + // Field i holds i + 1 when present; 0 (the default) is skipped by its save flag. + private static bool IsPresent(int index, int flagCount) => + index is 0 or 1 or 30 or 31 or 32 or 33 or 62 or 63 or 64 or 65 || index == flagCount - 1; + + [Theory] + [InlineData(20)] // int SaveFlag + [InlineData(40)] // one ulong SaveFlag; bits 31 and 32 straddle the int boundary + [InlineData(70)] // SaveFlag + SaveFlag2; bits 63 and 64 straddle the enum boundary + public void MigrateFrom_ReadsEveryFieldWrittenByTheLiveGenerator(int flagCount) + { + var bytes = WriteV0(flagCount); + var migrated = ReadAsV1(flagCount, bytes); + + var expected = new StringBuilder(); + for (var i = 0; i < flagCount; i++) + { + expected.Append(IsPresent(i, flagCount) ? $"{i + 1}" : "null").Append(','); + } + + expected.Append("True"); + + Assert.Equal($"tag|{expected}", migrated); + } + + private static byte[] WriteV0(int flagCount) + { + var source = new StringBuilder(); + source.AppendLine("using ModernUO.Serialization;"); + source.AppendLine("namespace Server.TestContent {"); + source.AppendLine("[SerializationGenerator(0)]"); + source.AppendLine("public partial class WideFlagsItem : Server.ISerializable {"); + AppendFields(source, flagCount); + AppendEntityMembers(source); + + source.AppendLine("public static byte[] WriteSample() {"); + source.AppendLine("var item = new WideFlagsItem { _tag = \"tag\" };"); + for (var i = 0; i < flagCount; i++) + { + if (IsPresent(i, flagCount)) + { + source.AppendLine($"item._f{i} = {i + 1};"); + } + } + + source.AppendLine("item._flag = true;"); + source.AppendLine("var stream = new System.IO.MemoryStream();"); + source.AppendLine("var writer = new BinaryGenericWriter(stream);"); + source.AppendLine("item.Serialize(writer);"); + source.AppendLine("writer.Flush();"); + source.AppendLine("return stream.ToArray();"); + source.AppendLine("}"); + source.AppendLine("}"); + source.AppendLine("}"); + + var assembly = SourceGeneratorTestHelper.CompileAndLoad( + $"WideFlagsV0_{flagCount}", + source + BinaryStreams + ); + + return (byte[])assembly.GetType("Server.TestContent.WideFlagsItem")! + .GetMethod("WriteSample")! + .Invoke(null, null)!; + } + + private static string ReadAsV1(int flagCount, byte[] bytes) + { + var source = new StringBuilder(); + source.AppendLine("using ModernUO.Serialization;"); + source.AppendLine("namespace Server.TestContent {"); + source.AppendLine("[SerializationGenerator(1)]"); + source.AppendLine("public partial class WideFlagsItem : Server.ISerializable {"); + source.AppendLine("[SerializableField(0)] private string _migrated;"); + AppendEntityMembers(source); + + source.AppendLine("private void MigrateFrom(V0Content content) {"); + source.AppendLine("var sb = new System.Text.StringBuilder();"); + source.AppendLine("sb.Append(content.Tag).Append('|');"); + for (var i = 0; i < flagCount; i++) + { + source.AppendLine($"sb.Append(content.F{i}?.ToString() ?? \"null\").Append(',');"); + } + + source.AppendLine("sb.Append(content.Flag);"); + source.AppendLine("_migrated = sb.ToString();"); + source.AppendLine("}"); + + source.AppendLine("public static string ReadSample(byte[] bytes) {"); + source.AppendLine("var reader = new BinaryGenericReader(new System.IO.MemoryStream(bytes));"); + source.AppendLine("var item = new WideFlagsItem();"); + source.AppendLine("item.Deserialize(reader);"); + source.AppendLine("if (!reader.AtEnd) throw new System.InvalidOperationException(\"Unread bytes remain\");"); + source.AppendLine("return item._migrated;"); + source.AppendLine("}"); + source.AppendLine("}"); + source.AppendLine("}"); + + var assembly = SourceGeneratorTestHelper.CompileAndLoad( + $"WideFlagsV1_{flagCount}", + source + BinaryStreams, + [("Server.TestContent.WideFlagsItem.v0.json", MigrationJson(flagCount))] + ); + + return (string)assembly.GetType("Server.TestContent.WideFlagsItem")! + .GetMethod("ReadSample")! + .Invoke(null, [bytes])!; + } + + // One unflagged string, flagCount flagged ints, then a flagged bool (stored in its bit only). + private static void AppendFields(StringBuilder source, int flagCount) + { + source.AppendLine("[SerializableField(0)] private string _tag;"); + for (var i = 0; i < flagCount; i++) + { + source.AppendLine($"[SerializableField({i + 1})] [SaveFlag(nameof(ShouldSerializeF{i}))] private int _f{i};"); + source.AppendLine($"private bool ShouldSerializeF{i}() => _f{i} != 0;"); + } + + source.AppendLine($"[SerializableField({flagCount + 1})] [SaveFlag(nameof(ShouldSerializeFlag))] private bool _flag;"); + source.AppendLine("private bool ShouldSerializeFlag() => _flag;"); + } + + private static void AppendEntityMembers(StringBuilder source) + { + source.AppendLine("public WideFlagsItem() { }"); + source.AppendLine("public System.DateTime Created { get; set; }"); + source.AppendLine("public Server.Serial Serial { get; }"); + source.AppendLine("public bool Deleted => false;"); + source.AppendLine("public void Delete() { }"); + } + + private static string MigrationJson(int flagCount) + { + var properties = new List + { + """{ "name": "Tag", "type": "string", "rule": "PrimitiveTypeMigrationRule" }""" + }; + + for (var i = 0; i < flagCount; i++) + { + properties.Add( + $$"""{ "name": "F{{i}}", "type": "int", "usesSaveFlag": true, "rule": "PrimitiveTypeMigrationRule" }""" + ); + } + + properties.Add( + """{ "name": "Flag", "type": "bool", "usesSaveFlag": true, "rule": "PrimitiveTypeMigrationRule" }""" + ); + + return $$""" + { + "version": 0, + "type": "Server.TestContent.WideFlagsItem", + "properties": [ + {{string.Join(",\n", properties)}} + ] + } + """; + } +} diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs new file mode 100644 index 0000000..1d0e701 --- /dev/null +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Expected/Server.TestContent.WideMigratingItem.Serialization.g.cs @@ -0,0 +1,823 @@ +// +// This code was generated by the ModernUO Serialization Generator tool. +// Version: {VERSION} +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +#pragma warning disable + +namespace Server.TestContent +{ + [System.CodeDom.Compiler.GeneratedCode("ModernUO.Serialization.Generator", "{VERSION}")] + public partial class WideMigratingItem + { + private const int SerializationVersion = 1; + + public string Name + { + get => _name; + set + { + if (value != _name) + { + _name = value; + Server.ISerializableExtensions.MarkDirty(this); + } + } + } + + public int Total + { + get => _total; + set + { + if (value != _total) + { + _total = value; + Server.ISerializableExtensions.MarkDirty(this); + } + } + } + + public WideMigratingItem(Server.Serial serial) + { + Serial = serial; + } + + ref struct V0Content + { + internal readonly string Name; + internal readonly int? Field0; + internal readonly int? Field1; + internal readonly int? Field2; + internal readonly int? Field3; + internal readonly int? Field4; + internal readonly int? Field5; + internal readonly int? Field6; + internal readonly int? Field7; + internal readonly int? Field8; + internal readonly int? Field9; + internal readonly int? Field10; + internal readonly int? Field11; + internal readonly int? Field12; + internal readonly int? Field13; + internal readonly int? Field14; + internal readonly int? Field15; + internal readonly int? Field16; + internal readonly int? Field17; + internal readonly int? Field18; + internal readonly int? Field19; + internal readonly int? Field20; + internal readonly int? Field21; + internal readonly int? Field22; + internal readonly int? Field23; + internal readonly int? Field24; + internal readonly int? Field25; + internal readonly int? Field26; + internal readonly int? Field27; + internal readonly int? Field28; + internal readonly int? Field29; + internal readonly int? Field30; + internal readonly int? Field31; + internal readonly int? Field32; + internal readonly int? Field33; + internal readonly int? Field34; + internal readonly int? Field35; + internal readonly int? Field36; + internal readonly int? Field37; + internal readonly int? Field38; + internal readonly int? Field39; + internal readonly int? Field40; + internal readonly int? Field41; + internal readonly int? Field42; + internal readonly int? Field43; + internal readonly int? Field44; + internal readonly int? Field45; + internal readonly int? Field46; + internal readonly int? Field47; + internal readonly int? Field48; + internal readonly int? Field49; + internal readonly int? Field50; + internal readonly int? Field51; + internal readonly int? Field52; + internal readonly int? Field53; + internal readonly int? Field54; + internal readonly int? Field55; + internal readonly int? Field56; + internal readonly int? Field57; + internal readonly int? Field58; + internal readonly int? Field59; + internal readonly int? Field60; + internal readonly int? Field61; + internal readonly int? Field62; + internal readonly int? Field63; + internal readonly int? Field64; + internal readonly int? Field65; + + [System.Flags] + private enum V0SaveFlag : ulong + { + None = 0x0000000000000000, + Field0 = 0x0000000000000001, + Field1 = 0x0000000000000002, + Field2 = 0x0000000000000004, + Field3 = 0x0000000000000008, + Field4 = 0x0000000000000010, + Field5 = 0x0000000000000020, + Field6 = 0x0000000000000040, + Field7 = 0x0000000000000080, + Field8 = 0x0000000000000100, + Field9 = 0x0000000000000200, + Field10 = 0x0000000000000400, + Field11 = 0x0000000000000800, + Field12 = 0x0000000000001000, + Field13 = 0x0000000000002000, + Field14 = 0x0000000000004000, + Field15 = 0x0000000000008000, + Field16 = 0x0000000000010000, + Field17 = 0x0000000000020000, + Field18 = 0x0000000000040000, + Field19 = 0x0000000000080000, + Field20 = 0x0000000000100000, + Field21 = 0x0000000000200000, + Field22 = 0x0000000000400000, + Field23 = 0x0000000000800000, + Field24 = 0x0000000001000000, + Field25 = 0x0000000002000000, + Field26 = 0x0000000004000000, + Field27 = 0x0000000008000000, + Field28 = 0x0000000010000000, + Field29 = 0x0000000020000000, + Field30 = 0x0000000040000000, + Field31 = 0x0000000080000000, + Field32 = 0x0000000100000000, + Field33 = 0x0000000200000000, + Field34 = 0x0000000400000000, + Field35 = 0x0000000800000000, + Field36 = 0x0000001000000000, + Field37 = 0x0000002000000000, + Field38 = 0x0000004000000000, + Field39 = 0x0000008000000000, + Field40 = 0x0000010000000000, + Field41 = 0x0000020000000000, + Field42 = 0x0000040000000000, + Field43 = 0x0000080000000000, + Field44 = 0x0000100000000000, + Field45 = 0x0000200000000000, + Field46 = 0x0000400000000000, + Field47 = 0x0000800000000000, + Field48 = 0x0001000000000000, + Field49 = 0x0002000000000000, + Field50 = 0x0004000000000000, + Field51 = 0x0008000000000000, + Field52 = 0x0010000000000000, + Field53 = 0x0020000000000000, + Field54 = 0x0040000000000000, + Field55 = 0x0080000000000000, + Field56 = 0x0100000000000000, + Field57 = 0x0200000000000000, + Field58 = 0x0400000000000000, + Field59 = 0x0800000000000000, + Field60 = 0x1000000000000000, + Field61 = 0x2000000000000000, + Field62 = 0x4000000000000000, + Field63 = 0x8000000000000000, + } + + [System.Flags] + private enum V0SaveFlag2 : ulong + { + None = 0x0000000000000000, + Field64 = 0x0000000000000001, + Field65 = 0x0000000000000002, + } + internal V0Content(Server.IGenericReader reader, Server.TestContent.WideMigratingItem entity) + { + var saveFlags = reader.ReadEnum(); + var saveFlags2 = reader.ReadEnum(); + Name = reader.ReadString(); + + if ((saveFlags & V0SaveFlag.Field0) != 0) + { + Field0 = reader.ReadInt(); + } + else + { + Field0 = default; + } + + if ((saveFlags & V0SaveFlag.Field1) != 0) + { + Field1 = reader.ReadInt(); + } + else + { + Field1 = default; + } + + if ((saveFlags & V0SaveFlag.Field2) != 0) + { + Field2 = reader.ReadInt(); + } + else + { + Field2 = default; + } + + if ((saveFlags & V0SaveFlag.Field3) != 0) + { + Field3 = reader.ReadInt(); + } + else + { + Field3 = default; + } + + if ((saveFlags & V0SaveFlag.Field4) != 0) + { + Field4 = reader.ReadInt(); + } + else + { + Field4 = default; + } + + if ((saveFlags & V0SaveFlag.Field5) != 0) + { + Field5 = reader.ReadInt(); + } + else + { + Field5 = default; + } + + if ((saveFlags & V0SaveFlag.Field6) != 0) + { + Field6 = reader.ReadInt(); + } + else + { + Field6 = default; + } + + if ((saveFlags & V0SaveFlag.Field7) != 0) + { + Field7 = reader.ReadInt(); + } + else + { + Field7 = default; + } + + if ((saveFlags & V0SaveFlag.Field8) != 0) + { + Field8 = reader.ReadInt(); + } + else + { + Field8 = default; + } + + if ((saveFlags & V0SaveFlag.Field9) != 0) + { + Field9 = reader.ReadInt(); + } + else + { + Field9 = default; + } + + if ((saveFlags & V0SaveFlag.Field10) != 0) + { + Field10 = reader.ReadInt(); + } + else + { + Field10 = default; + } + + if ((saveFlags & V0SaveFlag.Field11) != 0) + { + Field11 = reader.ReadInt(); + } + else + { + Field11 = default; + } + + if ((saveFlags & V0SaveFlag.Field12) != 0) + { + Field12 = reader.ReadInt(); + } + else + { + Field12 = default; + } + + if ((saveFlags & V0SaveFlag.Field13) != 0) + { + Field13 = reader.ReadInt(); + } + else + { + Field13 = default; + } + + if ((saveFlags & V0SaveFlag.Field14) != 0) + { + Field14 = reader.ReadInt(); + } + else + { + Field14 = default; + } + + if ((saveFlags & V0SaveFlag.Field15) != 0) + { + Field15 = reader.ReadInt(); + } + else + { + Field15 = default; + } + + if ((saveFlags & V0SaveFlag.Field16) != 0) + { + Field16 = reader.ReadInt(); + } + else + { + Field16 = default; + } + + if ((saveFlags & V0SaveFlag.Field17) != 0) + { + Field17 = reader.ReadInt(); + } + else + { + Field17 = default; + } + + if ((saveFlags & V0SaveFlag.Field18) != 0) + { + Field18 = reader.ReadInt(); + } + else + { + Field18 = default; + } + + if ((saveFlags & V0SaveFlag.Field19) != 0) + { + Field19 = reader.ReadInt(); + } + else + { + Field19 = default; + } + + if ((saveFlags & V0SaveFlag.Field20) != 0) + { + Field20 = reader.ReadInt(); + } + else + { + Field20 = default; + } + + if ((saveFlags & V0SaveFlag.Field21) != 0) + { + Field21 = reader.ReadInt(); + } + else + { + Field21 = default; + } + + if ((saveFlags & V0SaveFlag.Field22) != 0) + { + Field22 = reader.ReadInt(); + } + else + { + Field22 = default; + } + + if ((saveFlags & V0SaveFlag.Field23) != 0) + { + Field23 = reader.ReadInt(); + } + else + { + Field23 = default; + } + + if ((saveFlags & V0SaveFlag.Field24) != 0) + { + Field24 = reader.ReadInt(); + } + else + { + Field24 = default; + } + + if ((saveFlags & V0SaveFlag.Field25) != 0) + { + Field25 = reader.ReadInt(); + } + else + { + Field25 = default; + } + + if ((saveFlags & V0SaveFlag.Field26) != 0) + { + Field26 = reader.ReadInt(); + } + else + { + Field26 = default; + } + + if ((saveFlags & V0SaveFlag.Field27) != 0) + { + Field27 = reader.ReadInt(); + } + else + { + Field27 = default; + } + + if ((saveFlags & V0SaveFlag.Field28) != 0) + { + Field28 = reader.ReadInt(); + } + else + { + Field28 = default; + } + + if ((saveFlags & V0SaveFlag.Field29) != 0) + { + Field29 = reader.ReadInt(); + } + else + { + Field29 = default; + } + + if ((saveFlags & V0SaveFlag.Field30) != 0) + { + Field30 = reader.ReadInt(); + } + else + { + Field30 = default; + } + + if ((saveFlags & V0SaveFlag.Field31) != 0) + { + Field31 = reader.ReadInt(); + } + else + { + Field31 = default; + } + + if ((saveFlags & V0SaveFlag.Field32) != 0) + { + Field32 = reader.ReadInt(); + } + else + { + Field32 = default; + } + + if ((saveFlags & V0SaveFlag.Field33) != 0) + { + Field33 = reader.ReadInt(); + } + else + { + Field33 = default; + } + + if ((saveFlags & V0SaveFlag.Field34) != 0) + { + Field34 = reader.ReadInt(); + } + else + { + Field34 = default; + } + + if ((saveFlags & V0SaveFlag.Field35) != 0) + { + Field35 = reader.ReadInt(); + } + else + { + Field35 = default; + } + + if ((saveFlags & V0SaveFlag.Field36) != 0) + { + Field36 = reader.ReadInt(); + } + else + { + Field36 = default; + } + + if ((saveFlags & V0SaveFlag.Field37) != 0) + { + Field37 = reader.ReadInt(); + } + else + { + Field37 = default; + } + + if ((saveFlags & V0SaveFlag.Field38) != 0) + { + Field38 = reader.ReadInt(); + } + else + { + Field38 = default; + } + + if ((saveFlags & V0SaveFlag.Field39) != 0) + { + Field39 = reader.ReadInt(); + } + else + { + Field39 = default; + } + + if ((saveFlags & V0SaveFlag.Field40) != 0) + { + Field40 = reader.ReadInt(); + } + else + { + Field40 = default; + } + + if ((saveFlags & V0SaveFlag.Field41) != 0) + { + Field41 = reader.ReadInt(); + } + else + { + Field41 = default; + } + + if ((saveFlags & V0SaveFlag.Field42) != 0) + { + Field42 = reader.ReadInt(); + } + else + { + Field42 = default; + } + + if ((saveFlags & V0SaveFlag.Field43) != 0) + { + Field43 = reader.ReadInt(); + } + else + { + Field43 = default; + } + + if ((saveFlags & V0SaveFlag.Field44) != 0) + { + Field44 = reader.ReadInt(); + } + else + { + Field44 = default; + } + + if ((saveFlags & V0SaveFlag.Field45) != 0) + { + Field45 = reader.ReadInt(); + } + else + { + Field45 = default; + } + + if ((saveFlags & V0SaveFlag.Field46) != 0) + { + Field46 = reader.ReadInt(); + } + else + { + Field46 = default; + } + + if ((saveFlags & V0SaveFlag.Field47) != 0) + { + Field47 = reader.ReadInt(); + } + else + { + Field47 = default; + } + + if ((saveFlags & V0SaveFlag.Field48) != 0) + { + Field48 = reader.ReadInt(); + } + else + { + Field48 = default; + } + + if ((saveFlags & V0SaveFlag.Field49) != 0) + { + Field49 = reader.ReadInt(); + } + else + { + Field49 = default; + } + + if ((saveFlags & V0SaveFlag.Field50) != 0) + { + Field50 = reader.ReadInt(); + } + else + { + Field50 = default; + } + + if ((saveFlags & V0SaveFlag.Field51) != 0) + { + Field51 = reader.ReadInt(); + } + else + { + Field51 = default; + } + + if ((saveFlags & V0SaveFlag.Field52) != 0) + { + Field52 = reader.ReadInt(); + } + else + { + Field52 = default; + } + + if ((saveFlags & V0SaveFlag.Field53) != 0) + { + Field53 = reader.ReadInt(); + } + else + { + Field53 = default; + } + + if ((saveFlags & V0SaveFlag.Field54) != 0) + { + Field54 = reader.ReadInt(); + } + else + { + Field54 = default; + } + + if ((saveFlags & V0SaveFlag.Field55) != 0) + { + Field55 = reader.ReadInt(); + } + else + { + Field55 = default; + } + + if ((saveFlags & V0SaveFlag.Field56) != 0) + { + Field56 = reader.ReadInt(); + } + else + { + Field56 = default; + } + + if ((saveFlags & V0SaveFlag.Field57) != 0) + { + Field57 = reader.ReadInt(); + } + else + { + Field57 = default; + } + + if ((saveFlags & V0SaveFlag.Field58) != 0) + { + Field58 = reader.ReadInt(); + } + else + { + Field58 = default; + } + + if ((saveFlags & V0SaveFlag.Field59) != 0) + { + Field59 = reader.ReadInt(); + } + else + { + Field59 = default; + } + + if ((saveFlags & V0SaveFlag.Field60) != 0) + { + Field60 = reader.ReadInt(); + } + else + { + Field60 = default; + } + + if ((saveFlags & V0SaveFlag.Field61) != 0) + { + Field61 = reader.ReadInt(); + } + else + { + Field61 = default; + } + + if ((saveFlags & V0SaveFlag.Field62) != 0) + { + Field62 = reader.ReadInt(); + } + else + { + Field62 = default; + } + + if ((saveFlags & V0SaveFlag.Field63) != 0) + { + Field63 = reader.ReadInt(); + } + else + { + Field63 = default; + } + + if ((saveFlags2 & V0SaveFlag2.Field64) != 0) + { + Field64 = reader.ReadInt(); + } + else + { + Field64 = default; + } + + if ((saveFlags2 & V0SaveFlag2.Field65) != 0) + { + Field65 = reader.ReadInt(); + } + else + { + Field65 = default; + } + } + } + + public virtual void Serialize(Server.IGenericWriter writer) + { + writer.WriteEncodedInt(SerializationVersion); + + writer.Write(_name); + + writer.Write(_total); + } + + public virtual void Deserialize(Server.IGenericReader reader) + { + var version = reader.ReadEncodedInt(); + + if (version == 0) + { + MigrateFrom(new V0Content(reader, this)); + Server.ISerializableExtensions.MarkDirty(this); + return; + } + + _name = reader.ReadString(); + + _total = reader.ReadInt(); + } + } +} diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs new file mode 100644 index 0000000..3665fa6 --- /dev/null +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Input.cs @@ -0,0 +1,93 @@ +using System; +using ModernUO.Serialization; +using Server; + +namespace Server.TestContent +{ + // v0 had 66 save-flagged fields: a ulong V0SaveFlag holding 64 and a V0SaveFlag2 holding the rest. + [SerializationGenerator(1)] + public partial class WideMigratingItem : ISerializable + { + [SerializableField(0)] + private string _name; + + [SerializableField(1)] + private int _total; + + public DateTime Created { get; set; } + public Serial Serial { get; } + public bool Deleted => false; + public void Delete() { } + + private void MigrateFrom(V0Content content) + { + _name = content.Name; + _total += content.Field0 ?? 0; + _total += content.Field1 ?? 0; + _total += content.Field2 ?? 0; + _total += content.Field3 ?? 0; + _total += content.Field4 ?? 0; + _total += content.Field5 ?? 0; + _total += content.Field6 ?? 0; + _total += content.Field7 ?? 0; + _total += content.Field8 ?? 0; + _total += content.Field9 ?? 0; + _total += content.Field10 ?? 0; + _total += content.Field11 ?? 0; + _total += content.Field12 ?? 0; + _total += content.Field13 ?? 0; + _total += content.Field14 ?? 0; + _total += content.Field15 ?? 0; + _total += content.Field16 ?? 0; + _total += content.Field17 ?? 0; + _total += content.Field18 ?? 0; + _total += content.Field19 ?? 0; + _total += content.Field20 ?? 0; + _total += content.Field21 ?? 0; + _total += content.Field22 ?? 0; + _total += content.Field23 ?? 0; + _total += content.Field24 ?? 0; + _total += content.Field25 ?? 0; + _total += content.Field26 ?? 0; + _total += content.Field27 ?? 0; + _total += content.Field28 ?? 0; + _total += content.Field29 ?? 0; + _total += content.Field30 ?? 0; + _total += content.Field31 ?? 0; + _total += content.Field32 ?? 0; + _total += content.Field33 ?? 0; + _total += content.Field34 ?? 0; + _total += content.Field35 ?? 0; + _total += content.Field36 ?? 0; + _total += content.Field37 ?? 0; + _total += content.Field38 ?? 0; + _total += content.Field39 ?? 0; + _total += content.Field40 ?? 0; + _total += content.Field41 ?? 0; + _total += content.Field42 ?? 0; + _total += content.Field43 ?? 0; + _total += content.Field44 ?? 0; + _total += content.Field45 ?? 0; + _total += content.Field46 ?? 0; + _total += content.Field47 ?? 0; + _total += content.Field48 ?? 0; + _total += content.Field49 ?? 0; + _total += content.Field50 ?? 0; + _total += content.Field51 ?? 0; + _total += content.Field52 ?? 0; + _total += content.Field53 ?? 0; + _total += content.Field54 ?? 0; + _total += content.Field55 ?? 0; + _total += content.Field56 ?? 0; + _total += content.Field57 ?? 0; + _total += content.Field58 ?? 0; + _total += content.Field59 ?? 0; + _total += content.Field60 ?? 0; + _total += content.Field61 ?? 0; + _total += content.Field62 ?? 0; + _total += content.Field63 ?? 0; + _total += content.Field64 ?? 0; + _total += content.Field65 ?? 0; + } + } +} diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json new file mode 100644 index 0000000..4d546e4 --- /dev/null +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/MigrationSaveFlagsMultiEnum/Server.TestContent.WideMigratingItem.v0.json @@ -0,0 +1,407 @@ +{ + "version": 0, + "type": "Server.TestContent.WideMigratingItem", + "properties": [ + { + "name": "Name", + "type": "string", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field0", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field1", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field2", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field3", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field4", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field5", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field6", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field7", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field8", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field9", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field10", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field11", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field12", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field13", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field14", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field15", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field16", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field17", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field18", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field19", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field20", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field21", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field22", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field23", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field24", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field25", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field26", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field27", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field28", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field29", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field30", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field31", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field32", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field33", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field34", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field35", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field36", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field37", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field38", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field39", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field40", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field41", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field42", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field43", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field44", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field45", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field46", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field47", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field48", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field49", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field50", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field51", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field52", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field53", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field54", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field55", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field56", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field57", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field58", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field59", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field60", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field61", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field62", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field63", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field64", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "Field65", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule" + } + ] +} diff --git a/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj b/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj index 186c390..f3f8ca2 100755 --- a/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj +++ b/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj @@ -4,8 +4,8 @@ ModernUO.Serialization.Generator netstandard2.0 preview - 4.1.0 - 4.1.0 + 4.1.1 + 4.1.1 ModernUO.Serialization.Generator true false diff --git a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs index 2cb27fb..f381280 100644 --- a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs +++ b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs @@ -42,52 +42,80 @@ string classDisplayString var innerIndent = $"{indent} "; - var usesSaveFlags = properties.Any(p => p.UsesSaveFlag == true); - - if (usesSaveFlags) + // The stream was written by the live generator of that version, so partition the flags + // exactly like the live SaveFlag enums: int up to 32 flags, ulong up to 64, then one more + // ulong enum per 64 flags. Readonly fields never reach the schema, so none are skipped here. + var saveFlagCount = properties.Count(p => p.UsesSaveFlag == true); + var saveFlagUseUlong = saveFlagCount > 32; + var saveFlagEnumCount = saveFlagCount == 0 ? 0 : saveFlagCount <= 64 ? 1 : (saveFlagCount + 63) / 64; + var bitsPerEnum = saveFlagUseUlong ? 64 : 32; + + var flagIndex = 0; + foreach (var property in properties) { - source.AppendLine(); - source.GenerateEnumStart( - $"V{migration.Version}SaveFlag", - $"{indent} ", - true, - Accessibility.Private - ); + if (property.UsesSaveFlag != true) + { + continue; + } - source.GenerateEnumValue(innerIndent, true, "None", -1); - int index = 0; - foreach (var property in properties) + var bitIndex = flagIndex % bitsPerEnum; + if (bitIndex == 0) { - if (property.UsesSaveFlag == true) + if (flagIndex > 0) { - source.GenerateEnumValue(innerIndent, true, property.Name, index++); + source.GenerateEnumEnd($"{indent} "); } + + source.AppendLine(); + source.GenerateEnumStart( + GetContentSaveFlagEnumName(migration.Version, flagIndex / bitsPerEnum), + $"{indent} ", + true, + Accessibility.Private, + saveFlagUseUlong ? "ulong" : null + ); + + source.GenerateContentSaveFlagValue(innerIndent, saveFlagUseUlong, "None", -1); } + source.GenerateContentSaveFlagValue(innerIndent, saveFlagUseUlong, property.Name, bitIndex); + flagIndex++; + } + + if (flagIndex > 0) + { source.GenerateEnumEnd($"{indent} "); } source.AppendLine($"{indent} internal V{migration.Version}Content(Server.IGenericReader reader, {classDisplayString} entity)"); source.AppendLine($"{indent} {{"); - if (usesSaveFlags) + // The writer emits every flag enum before any field. + for (var i = 0; i < saveFlagEnumCount; i++) { - source.AppendLine($"{innerIndent}var saveFlags = reader.ReadEnum();"); + source.AppendLine( + $"{innerIndent}var {GetContentSaveFlagVariableName(i)} = reader.ReadEnum<{GetContentSaveFlagEnumName(migration.Version, i)}>();" + ); } + flagIndex = 0; foreach (var property in properties) { if (property.UsesSaveFlag == true) { + var enumIndex = flagIndex++ / bitsPerEnum; + var flagTest = + $"({GetContentSaveFlagVariableName(enumIndex)} & {GetContentSaveFlagEnumName(migration.Version, enumIndex)}.{property.Name}) != 0"; + source.AppendLine(); // Special case if (property.Type == "bool") { - source.AppendLine($"{innerIndent}{property.Name} = (saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0;"); + source.AppendLine($"{innerIndent}{property.Name} = {flagTest};"); } else { - source.AppendLine($"{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{"); + source.AppendLine($"{innerIndent}if ({flagTest})\n{innerIndent}{{"); SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod( source, @@ -118,4 +146,28 @@ string classDisplayString source.AppendLine($"{indent}}}"); } + + private static string GetContentSaveFlagEnumName(int version, int enumIndex) => + enumIndex == 0 ? $"V{version}SaveFlag" : $"V{version}SaveFlag{enumIndex + 1}"; + + private static string GetContentSaveFlagVariableName(int enumIndex) => + enumIndex == 0 ? "saveFlags" : $"saveFlags{enumIndex + 1}"; + + private static void GenerateContentSaveFlagValue( + this StringBuilder source, + string indent, + bool useUlong, + string name, + int bitIndex + ) + { + if (useUlong) + { + source.GenerateEnumValueLong(indent, true, name, bitIndex); + } + else + { + source.GenerateEnumValue(indent, true, name, bitIndex); + } + } } diff --git a/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj b/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj index 38e2dc6..5380973 100755 --- a/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj +++ b/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj @@ -6,8 +6,8 @@ x64;arm64 preview Exe - 4.1.0 - 4.1.0 + 4.1.1 + 4.1.1 true ModernUOSchemaGenerator true