diff --git a/Modspec.Model/Generation/ModspecModelGenerator.cs b/Modspec.Model/Generation/ModspecModelGenerator.cs index 913106f..f26cdd9 100644 --- a/Modspec.Model/Generation/ModspecModelGenerator.cs +++ b/Modspec.Model/Generation/ModspecModelGenerator.cs @@ -77,7 +77,8 @@ namespace {schema.Name}; List bufferInitialisers = []; List fieldInitialisers = []; List constructorParams = []; - WriteGroups(schema.Groups, mainWriter, appendixWriter, bufferInitialisers, fieldInitialisers, constructorParams); + List bitfieldPointsWithLevels = []; + WriteGroups(schema.Groups, mainWriter, appendixWriter, bufferInitialisers, fieldInitialisers, constructorParams, bitfieldPointsWithLevels: bitfieldPointsWithLevels); foreach (RepeatingGroup repeatingGroup in schema.RepeatingGroups) { @@ -137,6 +138,11 @@ namespace {schema.Name}; mainWriter.WriteLine("}"); mainWriter.WriteLine(); + if (schema.GenerateChangeDetectionFactory && bitfieldPointsWithLevels.Count > 0) + { + WriteChangeDetectionFactory(schema.Name, bitfieldPointsWithLevels, appendixWriter); + } + result = mainWriter.ToString() + appendixWriter.ToString(); return true; } @@ -168,7 +174,7 @@ private static void WriteFieldsAndConstructor(string name, StringWriter mainWrit mainWriter.WriteLine($"{indent}\t}}"); } - private static void WriteGroups(IReadOnlyCollection groups, StringWriter mainWriter, StringWriter appendixWriter, List bufferInitialisers, List fieldInitialisers, List constructorParams, string indent = "", string readOffsetField = "") + private static void WriteGroups(IReadOnlyCollection groups, StringWriter mainWriter, StringWriter appendixWriter, List bufferInitialisers, List fieldInitialisers, List constructorParams, string indent = "", string readOffsetField = "", List? bitfieldPointsWithLevels = null) { foreach (Group group in groups) { @@ -185,7 +191,7 @@ private static void WriteGroups(IReadOnlyCollection groups, StringWriter // supplied count of elements, rather than max size of array) throw new InvalidOperationException($"An array must be the last (or only) element in a group."); } - WritePoint(point, bufferName, group.Table, mainWriter, appendixWriter, fieldInitialisers, constructorParams, ref maxOffset, ref bufferSize, indent); + WritePoint(point, bufferName, group.Table, mainWriter, appendixWriter, fieldInitialisers, constructorParams, ref maxOffset, ref bufferSize, indent, bitfieldPointsWithLevels); } if (String.IsNullOrEmpty(bufferSize)) { @@ -209,7 +215,7 @@ private static void WriteGroups(IReadOnlyCollection groups, StringWriter } } - private static void WritePoint(Point point, string bufferName, Table table, StringWriter mainWriter, StringWriter appendixWriter, List fieldInitialisers, List constructorParams, ref int maxOffset, ref string bufferSize, string indent = "") + private static void WritePoint(Point point, string bufferName, Table table, StringWriter mainWriter, StringWriter appendixWriter, List fieldInitialisers, List constructorParams, ref int maxOffset, ref string bufferSize, string indent = "", List? bitfieldPointsWithLevels = null) { string type; string readMethod; @@ -337,6 +343,7 @@ private static void WritePoint(Point point, string bufferName, Table table, Stri appendixWriter.WriteLine(); if (isFlags && masksByLevel.Count > 0) { + bitfieldPointsWithLevels?.Add(point.Name); appendixWriter.WriteLine($"public static class {point.Name}Extensions"); appendixWriter.WriteLine("{"); appendixWriter.WriteLine($"\tpublic static Level GetLevel(this {point.Name} self)"); @@ -421,6 +428,24 @@ private static void WritePoint(Point point, string bufferName, Table table, Stri maxOffset += point.SizeInBytes * (point.Count?.MaxValue ?? 1); } + private static void WriteChangeDetectionFactory(string schemaName, List points, StringWriter writer) + { + string clientName = $"{schemaName}Client"; + writer.WriteLine($"public static class {schemaName}ChangeDetection"); + writer.WriteLine("{"); + writer.WriteLine($"\tpublic static BitfieldChangeDetector<{clientName}> CreateDetector()"); + writer.WriteLine("\t{"); + writer.WriteLine($"\t\treturn new BitfieldChangeDetector<{clientName}>()"); + for (int i = 0; i < points.Count; i++) + { + string terminator = i < points.Count - 1 ? "" : ";"; + writer.WriteLine($"\t\t\t.Track(c => c.{points[i]}, v => v.GetLevel()){terminator}"); + } + writer.WriteLine("\t}"); + writer.WriteLine("}"); + writer.WriteLine(); + } + private static string ToFieldName(string name) { Span result = stackalloc char[name.Length + 1]; diff --git a/Modspec.Model/Schema.cs b/Modspec.Model/Schema.cs index 936818f..48502b2 100644 --- a/Modspec.Model/Schema.cs +++ b/Modspec.Model/Schema.cs @@ -48,6 +48,19 @@ public class Schema /// public List RepeatingGroups { get; set; } = []; + /// + /// If true, the generator will emit a static factory class for creating a + /// BitfieldChangeDetector that tracks all bitfield points with level annotations. + /// The consuming project must provide a BitfieldChangeDetector<TClient> class + /// in the Modspec.Model namespace with the following method: + /// + /// BitfieldChangeDetector<TClient> Track<T>(Func<TClient, T> getter, Func<T, Level> getLevel) + /// where T : struct, Enum + /// + /// The Track method must return the detector instance to support fluent chaining. + /// + public bool GenerateChangeDetectionFactory { get; set; } + public void Serialise(Stream stream) { JsonSerializer.Serialize(stream, this, Options);