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