From 4a3a5a71ea4f139512725df31c2383150ea2a250 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:44:38 -0700 Subject: [PATCH] feat: collection helpers return whether they changed the collection (v4.3.0) RemoveFromX, and AddToX on sets and dictionaries, now return bool, mirroring the BCL's Remove, HashSet.Add and TryAdd. Callers that branch on the outcome no longer need ISerializableExtensions.Remove or a Contains check. Co-Authored-By: Claude Opus 5.5 --- .../ModernUO.Serialization.Annotations.csproj | 4 +- .../DataStructureMethodTests.cs | 107 ++++++++++++------ ...Content.CollectionsItem.Serialization.g.cs | 25 +++- ...tent.FieldModifiersItem.Serialization.g.cs | 10 +- ...stContent.ExtendedOwner.Serialization.g.cs | 5 +- ...erver.TestContent.Owner.Serialization.g.cs | 5 +- ...ent.NoDirtyTargetRecord.Serialization.g.cs | 12 +- ...NullableCollectionsItem.Serialization.g.cs | 35 ++++-- ...tent.NullableValuesItem.Serialization.g.cs | 15 ++- ...rver.TestContent.Roster.Serialization.g.cs | 15 ++- ...stContent.SortedSetItem.Serialization.g.cs | 10 +- .../ModernUO.Serialization.Generator.csproj | 4 +- ...ializableEntityGeneration.DataStructure.cs | 38 ++++--- ...ernUO.Serialization.SchemaGenerator.csproj | 4 +- README.md | 7 +- 15 files changed, 209 insertions(+), 87 deletions(-) diff --git a/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj b/ModernUO.Serialization.Annotations/ModernUO.Serialization.Annotations.csproj index 552ed73..e127dbd 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.2.0 - 4.2.0 + 4.3.0 + 4.3.0 ModernUO.Serialization.Annotations ModernUO.Serialization true diff --git a/ModernUO.Serialization.Generator.Tests/DataStructureMethodTests.cs b/ModernUO.Serialization.Generator.Tests/DataStructureMethodTests.cs index faa2c75..eb3893f 100644 --- a/ModernUO.Serialization.Generator.Tests/DataStructureMethodTests.cs +++ b/ModernUO.Serialization.Generator.Tests/DataStructureMethodTests.cs @@ -7,14 +7,19 @@ namespace ModernUO.Serialization.Generator.Tests; /// /// Runs the generated AddToX/RemoveFromX/InsertIntoX/ReplaceInX/ClearX helpers against the /// Snapshots/NullableCollections entity: removals and clears tolerate a null collection, adds create -/// it lazily (keeping a [SortedSetComparer]), and MarkDirty fires only when the collection changed. +/// it lazily (keeping a [SortedSetComparer]), MarkDirty fires only when the collection changed, and +/// helpers that can be a no-op return whether they changed anything. /// public class DataStructureMethodTests { - private static string FixtureSource([CallerFilePath] string thisFile = "") => - File.ReadAllText( - Path.Combine(Path.GetDirectoryName(thisFile)!, "Snapshots", "NullableCollections", "Input.cs") - ); + private static string FixtureSource(string snapshot, [CallerFilePath] string thisFile = "") => + File.ReadAllText(Path.Combine(Path.GetDirectoryName(thisFile)!, "Snapshots", snapshot, "Input.cs")); + + private static string Run(string assemblyName, string source) => + (string)SourceGeneratorTestHelper.CompileAndLoad(assemblyName, source) + .GetType("Server.TestContent.Driver")! + .GetMethod("Run")! + .Invoke(null, null)!; private const string Driver = """ namespace Server.TestContent @@ -28,42 +33,37 @@ public static string Run() void Step(string name, object? state = null) => log.Append(name).Append('=').Append(item.DirtyCount).Append(state != null ? $":{state}" : "").Append(';'); - item.RemoveFromCharges(1); + var removed = item.RemoveFromCharges(1) | item.RemoveFromKeywords("a") | + item.RemoveFromLabels(1) | item.RemoveFromNames("a"); item.RemoveFromChargesAt(0); item.ClearCharges(); - item.RemoveFromKeywords("a"); item.ClearKeywords(); - item.RemoveFromLabels(1); item.ClearLabels(); - item.RemoveFromNames("a"); item.ClearNames(); - Step("null", item.Charges == null && item.Keywords == null && item.Labels == null && item.Names == null); + Step("null", $"{removed}:{item.Charges == null && item.Keywords == null && item.Labels == null && item.Names == null}"); item.AddToCharges(5); Step("addList"); item.InsertIntoCharges(0, 4); Step("insert", string.Join(",", item.Charges!)); - item.AddToKeywords("a"); - Step("addSet"); - item.AddToKeywords("a"); - Step("addSetDuplicate"); + Step("addSet", item.AddToKeywords("a")); + Step("addSetDuplicate", item.AddToKeywords("a")); - item.AddToLabels(1, "x"); - Step("addDictionary"); - item.AddToLabels(1, "y"); - Step("addDictionaryDuplicate", item.Labels![1]); + Step("addDictionary", item.AddToLabels(1, "x")); + Step("addDictionaryDuplicate", $"{item.AddToLabels(1, "y")}:{item.Labels![1]}"); item.ReplaceInLabels(1, null); Step("replace", item.Labels[1] ?? "null"); item.AddToNames("Bob"); - item.AddToNames("BOB"); - Step("comparer", item.Names!.Count); + Step("comparer", $"{item.AddToNames("BOB")}:{item.Names!.Count}"); - item.RemoveFromKeywords("missing"); - Step("removeMissing"); - item.RemoveFromKeywords("a"); - Step("remove"); + Step("removeMissing", item.RemoveFromKeywords("missing")); + Step("remove", item.RemoveFromKeywords("a")); + Step("removeListMissing", item.RemoveFromCharges(9)); + Step("removeList", item.RemoveFromCharges(4)); + Step("removeDictionaryMissing", item.RemoveFromLabels(9)); + Step("removeDictionary", item.RemoveFromLabels(1)); item.ClearKeywords(); Step("clearEmpty"); item.ClearCharges(); @@ -79,20 +79,61 @@ void Step(string name, object? state = null) => } """; + // Without a dirty target the helpers still report whether they changed anything. + private const string UntrackedSource = """ + using System.Collections.Generic; + using ModernUO.Serialization; + + namespace Server.TestContent + { + [SerializationGenerator(0)] + public partial class UntrackedRecord + { + [SerializableField(0)] + private List _values = new(); + + [SerializableField(1)] + private HashSet _set = new(); + + [SerializableField(2)] + private Dictionary _lookup = new(); + } + + public static class Driver + { + public static string Run() + { + var r = new UntrackedRecord(); + r.AddToValues(1); + return string.Join( + ",", + r.RemoveFromValues(2), r.RemoveFromValues(1), + r.AddToSet(1), r.AddToSet(1), r.RemoveFromSet(2), r.RemoveFromSet(1), + r.AddToLookup(1, "a"), r.AddToLookup(1, "b"), r.RemoveFromLookup(2), r.RemoveFromLookup(1) + ); + } + } + } + """; + [Fact] public void Helpers_TolerateNullAndMarkDirtyOnlyOnChange() { - var assembly = SourceGeneratorTestHelper.CompileAndLoad("NullableCollections", FixtureSource() + Driver); - - var result = (string)assembly.GetType("Server.TestContent.Driver")! - .GetMethod("Run")! - .Invoke(null, null)!; + Assert.Equal( + "null=0:False:True;addList=1;insert=2:4,5;addSet=3:True;addSetDuplicate=3:False;addDictionary=4:True;" + + "addDictionaryDuplicate=4:False:x;replace=5:null;comparer=6:False:1;removeMissing=6:False;remove=7:True;" + + "removeListMissing=7:False;removeList=8:True;removeDictionaryMissing=8:False;removeDictionary=9:True;" + + "clearEmpty=9;clear=10:0;replaceNull=1:z", + Run("NullableCollections", FixtureSource("NullableCollections") + Driver) + ); + } + [Fact] + public void Helpers_ReportChangesWithoutADirtyTarget() + { Assert.Equal( - "null=0:True;addList=1;insert=2:4,5;addSet=3;addSetDuplicate=3;addDictionary=4;" + - "addDictionaryDuplicate=4:x;replace=5:null;comparer=6:1;removeMissing=6;remove=7;" + - "clearEmpty=7;clear=8:0;replaceNull=1:z", - result + "False,True,True,False,False,True,True,False,False,True", + Run("UntrackedRecord", UntrackedSource) ); } } diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/Collections/Expected/Server.TestContent.CollectionsItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/Collections/Expected/Server.TestContent.CollectionsItem.Serialization.g.cs index d239fea..20db23d 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/Collections/Expected/Server.TestContent.CollectionsItem.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/Collections/Expected/Server.TestContent.CollectionsItem.Serialization.g.cs @@ -55,12 +55,15 @@ public void AddToCharges(int value) Server.ISerializableExtensions.MarkDirty(this); } - public void RemoveFromCharges(int value) + public bool RemoveFromCharges(int value) { if (_charges?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void InsertIntoCharges(int index, int value) @@ -101,21 +104,27 @@ public System.Collections.Generic.HashSet Keywords } } - public void AddToKeywords(string value) + public bool AddToKeywords(string value) { _keywords ??= new System.Collections.Generic.HashSet(); if (_keywords.Add(value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromKeywords(string value) + public bool RemoveFromKeywords(string value) { if (_keywords?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ClearKeywords() @@ -140,21 +149,27 @@ public System.Collections.Generic.Dictionary Labels } } - public void AddToLabels(int key, string value) + public bool AddToLabels(int key, string value) { _labels ??= new System.Collections.Generic.Dictionary(); if (_labels.TryAdd(key, value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromLabels(int key) + public bool RemoveFromLabels(int key) { if (_labels?.Remove(key) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ReplaceInLabels(int key, string value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/FieldModifiers/Expected/Server.TestContent.FieldModifiersItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/FieldModifiers/Expected/Server.TestContent.FieldModifiersItem.Serialization.g.cs index d5f7a73..210c0f4 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/FieldModifiers/Expected/Server.TestContent.FieldModifiersItem.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/FieldModifiers/Expected/Server.TestContent.FieldModifiersItem.Serialization.g.cs @@ -41,21 +41,27 @@ public System.Collections.Generic.Dictionary Entries } } - public void AddToEntries(int key, string value) + public bool AddToEntries(int key, string value) { _entries ??= new System.Collections.Generic.Dictionary(); if (_entries.TryAdd(key, value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromEntries(int key) + public bool RemoveFromEntries(int key) { if (_entries?.Remove(key) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ReplaceInEntries(int key, string value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.ExtendedOwner.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.ExtendedOwner.Serialization.g.cs index 4be42ac..8e3b85d 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.ExtendedOwner.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.ExtendedOwner.Serialization.g.cs @@ -35,12 +35,15 @@ public void AddToExtendedEntries(Server.TestContent.DerivedEntry value) Server.ISerializableExtensions.MarkDirty(this); } - public void RemoveFromExtendedEntries(Server.TestContent.DerivedEntry value) + public bool RemoveFromExtendedEntries(Server.TestContent.DerivedEntry value) { if (_extendedEntries?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void InsertIntoExtendedEntries(int index, Server.TestContent.DerivedEntry value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.Owner.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.Owner.Serialization.g.cs index c168f12..631ca1e 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.Owner.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NestedInheritance/Expected/Server.TestContent.Owner.Serialization.g.cs @@ -35,12 +35,15 @@ public void AddToEntries(Server.TestContent.BaseEntry value) Server.ISerializableExtensions.MarkDirty(this); } - public void RemoveFromEntries(Server.TestContent.BaseEntry value) + public bool RemoveFromEntries(Server.TestContent.BaseEntry value) { if (_entries?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void InsertIntoEntries(int index, Server.TestContent.BaseEntry value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NoDirtyTarget/Expected/Server.TestContent.NoDirtyTargetRecord.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NoDirtyTarget/Expected/Server.TestContent.NoDirtyTargetRecord.Serialization.g.cs index 1c9da54..232aca7 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/NoDirtyTarget/Expected/Server.TestContent.NoDirtyTargetRecord.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NoDirtyTarget/Expected/Server.TestContent.NoDirtyTargetRecord.Serialization.g.cs @@ -45,9 +45,9 @@ public void AddToValues(int value) _values.Add(value); } - public void RemoveFromValues(int value) + public bool RemoveFromValues(int value) { - _values?.Remove(value); + return _values?.Remove(value) == true; } public void InsertIntoValues(int index, int value) @@ -84,15 +84,15 @@ public System.Collections.Generic.Dictionary Lookup } } - public void AddToLookup(int key, string value) + public bool AddToLookup(int key, string value) { _lookup ??= new System.Collections.Generic.Dictionary(); - _lookup.TryAdd(key, value); + return _lookup.TryAdd(key, value); } - public void RemoveFromLookup(int key) + public bool RemoveFromLookup(int key) { - _lookup?.Remove(key); + return _lookup?.Remove(key) == true; } public void ReplaceInLookup(int key, string value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NullableCollections/Expected/Server.TestContent.NullableCollectionsItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NullableCollections/Expected/Server.TestContent.NullableCollectionsItem.Serialization.g.cs index fae98bc..278b34f 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/NullableCollections/Expected/Server.TestContent.NullableCollectionsItem.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NullableCollections/Expected/Server.TestContent.NullableCollectionsItem.Serialization.g.cs @@ -48,12 +48,15 @@ public void AddToCharges(int value) MarkDirty(); } - public void RemoveFromCharges(int value) + public bool RemoveFromCharges(int value) { if (_charges?.Remove(value) == true) { MarkDirty(); + return true; } + + return false; } public void InsertIntoCharges(int index, int value) @@ -94,21 +97,27 @@ public System.Collections.Generic.HashSet? Keywords } } - public void AddToKeywords(string value) + public bool AddToKeywords(string value) { _keywords ??= new System.Collections.Generic.HashSet(); if (_keywords.Add(value)) { MarkDirty(); + return true; } + + return false; } - public void RemoveFromKeywords(string value) + public bool RemoveFromKeywords(string value) { if (_keywords?.Remove(value) == true) { MarkDirty(); + return true; } + + return false; } public void ClearKeywords() @@ -133,21 +142,27 @@ public void ClearKeywords() } } - public void AddToLabels(int key, string? value) + public bool AddToLabels(int key, string? value) { _labels ??= new System.Collections.Generic.Dictionary(); if (_labels.TryAdd(key, value)) { MarkDirty(); + return true; } + + return false; } - public void RemoveFromLabels(int key) + public bool RemoveFromLabels(int key) { if (_labels?.Remove(key) == true) { MarkDirty(); + return true; } + + return false; } public void ReplaceInLabels(int key, string? value) @@ -179,21 +194,27 @@ public System.Collections.Generic.SortedSet? Names } } - public void AddToNames(string value) + public bool AddToNames(string value) { _names ??= new System.Collections.Generic.SortedSet(new Server.TestContent.CaseInsensitiveComparer()); if (_names.Add(value)) { MarkDirty(); + return true; } + + return false; } - public void RemoveFromNames(string value) + public bool RemoveFromNames(string value) { if (_names?.Remove(value) == true) { MarkDirty(); + return true; } + + return false; } public void ClearNames() diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/NullableValueTypes/Expected/Server.TestContent.NullableValuesItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/NullableValueTypes/Expected/Server.TestContent.NullableValuesItem.Serialization.g.cs index 4764128..d6e5800 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/NullableValueTypes/Expected/Server.TestContent.NullableValuesItem.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/NullableValueTypes/Expected/Server.TestContent.NullableValuesItem.Serialization.g.cs @@ -113,12 +113,15 @@ public void AddToSamples(int? value) Server.ISerializableExtensions.MarkDirty(this); } - public void RemoveFromSamples(int? value) + public bool RemoveFromSamples(int? value) { if (_samples?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void InsertIntoSamples(int index, int? value) @@ -159,21 +162,27 @@ public void ClearSamples() } } - public void AddToWeights(int key, double? value) + public bool AddToWeights(int key, double? value) { _weights ??= new System.Collections.Generic.Dictionary(); if (_weights.TryAdd(key, value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromWeights(int key) + public bool RemoveFromWeights(int key) { if (_weights?.Remove(key) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ReplaceInWeights(int key, double? value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/ParentCtorPreferred/Expected/Server.TestContent.Roster.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/ParentCtorPreferred/Expected/Server.TestContent.Roster.Serialization.g.cs index 5e4940d..8b06cfe 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/ParentCtorPreferred/Expected/Server.TestContent.Roster.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/ParentCtorPreferred/Expected/Server.TestContent.Roster.Serialization.g.cs @@ -35,12 +35,15 @@ public void AddToEntries(Server.TestContent.RosterEntry value) Server.ISerializableExtensions.MarkDirty(this); } - public void RemoveFromEntries(Server.TestContent.RosterEntry value) + public bool RemoveFromEntries(Server.TestContent.RosterEntry value) { if (_entries?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void InsertIntoEntries(int index, Server.TestContent.RosterEntry value) @@ -81,21 +84,27 @@ public void ClearEntries() } } - public void AddToById(int key, Server.TestContent.RosterEntry value) + public bool AddToById(int key, Server.TestContent.RosterEntry value) { _byId ??= new System.Collections.Generic.Dictionary(); if (_byId.TryAdd(key, value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromById(int key) + public bool RemoveFromById(int key) { if (_byId?.Remove(key) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ReplaceInById(int key, Server.TestContent.RosterEntry value) diff --git a/ModernUO.Serialization.Generator.Tests/Snapshots/SortedSetWithComparer/Expected/Server.TestContent.SortedSetItem.Serialization.g.cs b/ModernUO.Serialization.Generator.Tests/Snapshots/SortedSetWithComparer/Expected/Server.TestContent.SortedSetItem.Serialization.g.cs index 1b77384..b9f0883 100644 --- a/ModernUO.Serialization.Generator.Tests/Snapshots/SortedSetWithComparer/Expected/Server.TestContent.SortedSetItem.Serialization.g.cs +++ b/ModernUO.Serialization.Generator.Tests/Snapshots/SortedSetWithComparer/Expected/Server.TestContent.SortedSetItem.Serialization.g.cs @@ -28,21 +28,27 @@ public System.Collections.Generic.SortedSet Names } } - public void AddToNames(string value) + public bool AddToNames(string value) { _names ??= new System.Collections.Generic.SortedSet(new Server.TestContent.CaseInsensitiveComparer()); if (_names.Add(value)) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } - public void RemoveFromNames(string value) + public bool RemoveFromNames(string value) { if (_names?.Remove(value) == true) { Server.ISerializableExtensions.MarkDirty(this); + return true; } + + return false; } public void ClearNames() diff --git a/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj b/ModernUO.Serialization.Generator/ModernUO.Serialization.Generator.csproj index d09c2d1..41b305e 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.2.0 - 4.2.0 + 4.3.0 + 4.3.0 ModernUO.Serialization.Generator true false diff --git a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DataStructure.cs b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DataStructure.cs index 262086b..1934a42 100644 --- a/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DataStructure.cs +++ b/ModernUO.Serialization.Generator/SerializableEntityGeneration/SerializableEntityGeneration.DataStructure.cs @@ -21,21 +21,22 @@ private static void AppendLazyCreate(this StringBuilder source, string indent, F } } - // Marks dirty only when the mutation reports a change. Without a dirty target, just mutates. - private static void AppendIfChanged( - this StringBuilder source, string indent, string changed, string? markDirtyMethod, string? statement = null - ) + // Returns whether the mutation reported a change, marking dirty only when it did. + private static void AppendReturnIfChanged(this StringBuilder source, string indent, string changed, string? markDirtyMethod) { if (markDirtyMethod == null) { - source.AppendLine($"{indent} {statement ?? changed};"); + source.AppendLine($"{indent} return {changed};"); return; } source.AppendLine($"{indent} if ({changed})"); source.AppendLine($"{indent} {{"); source.AppendLine($"{indent} {markDirtyMethod};"); + source.AppendLine($"{indent} return true;"); source.AppendLine($"{indent} }}"); + source.AppendLine(); + source.AppendLine($"{indent} return false;"); } public static bool GenerateDataStructureMethods( @@ -59,34 +60,38 @@ public static bool GenerateDataStructureMethods( // Mutations go through the backing field: lazily creating the collection through the // property setter would run fieldChanged/allowFieldChange for what is not a replacement. // Removals and clears tolerate a null collection, and only mark dirty on an actual change. + // Return types mirror the BCL: a mutation that can be a no-op returns whether it changed anything. if (field.DsIsDictionary) { var valueTypeName = field.DsValueType; // Add - source.AppendLine($"{indent}{propertyAccessor} void AddTo{propertyName}({elementTypeName} key, {valueTypeName} value)"); + source.AppendLine($"{indent}{propertyAccessor} bool AddTo{propertyName}({elementTypeName} key, {valueTypeName} value)"); source.AppendLine($"{indent}{{"); source.AppendLazyCreate(indent, field); if (field.DsHasTryAdd) { - source.AppendIfChanged(indent, $"{fieldName}.TryAdd(key, value)", markDirtyMethod); + source.AppendReturnIfChanged(indent, $"{fieldName}.TryAdd(key, value)", markDirtyMethod); } else { - source.AppendLine($"{indent} if (!{fieldName}.ContainsKey(key))"); + source.AppendLine($"{indent} if ({fieldName}.ContainsKey(key))"); source.AppendLine($"{indent} {{"); - source.AppendLine($"{indent} {fieldName}.Add(key, value);"); - source.AppendMarkDirty($"{indent} ", markDirtyMethod); + source.AppendLine($"{indent} return false;"); source.AppendLine($"{indent} }}"); + source.AppendLine(); + source.AppendLine($"{indent} {fieldName}.Add(key, value);"); + source.AppendMarkDirty(indent, markDirtyMethod); + source.AppendLine($"{indent} return true;"); } source.AppendLine($"{indent}}}"); source.AppendLine(); // Remove - source.AppendLine($"{indent}{propertyAccessor} void RemoveFrom{propertyName}({elementTypeName} key)"); + source.AppendLine($"{indent}{propertyAccessor} bool RemoveFrom{propertyName}({elementTypeName} key)"); source.AppendLine($"{indent}{{"); - source.AppendIfChanged(indent, $"{fieldName}?.Remove(key) == true", markDirtyMethod, $"{fieldName}?.Remove(key)"); + source.AppendReturnIfChanged(indent, $"{fieldName}?.Remove(key) == true", markDirtyMethod); source.AppendLine($"{indent}}}"); source.AppendLine(); @@ -102,12 +107,13 @@ public static bool GenerateDataStructureMethods( else if (field.DsIsCollection) { // Add - source.AppendLine($"{indent}{propertyAccessor} void AddTo{propertyName}({elementTypeName} value)"); + var addReturnType = field.DsIsSet ? "bool" : "void"; + source.AppendLine($"{indent}{propertyAccessor} {addReturnType} AddTo{propertyName}({elementTypeName} value)"); source.AppendLine($"{indent}{{"); source.AppendLazyCreate(indent, field); if (field.DsIsSet) { - source.AppendIfChanged(indent, $"{fieldName}.Add(value)", markDirtyMethod); + source.AppendReturnIfChanged(indent, $"{fieldName}.Add(value)", markDirtyMethod); } else { @@ -119,9 +125,9 @@ public static bool GenerateDataStructureMethods( source.AppendLine(); // Remove - source.AppendLine($"{indent}{propertyAccessor} void RemoveFrom{propertyName}({elementTypeName} value)"); + source.AppendLine($"{indent}{propertyAccessor} bool RemoveFrom{propertyName}({elementTypeName} value)"); source.AppendLine($"{indent}{{"); - source.AppendIfChanged(indent, $"{fieldName}?.Remove(value) == true", markDirtyMethod, $"{fieldName}?.Remove(value)"); + source.AppendReturnIfChanged(indent, $"{fieldName}?.Remove(value) == true", markDirtyMethod); source.AppendLine($"{indent}}}"); } diff --git a/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj b/ModernUO.Serialization.SchemaGenerator/ModernUO.Serialization.SchemaGenerator.csproj index e19d7b6..11cc681 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.2.0 - 4.2.0 + 4.3.0 + 4.3.0 true ModernUOSchemaGenerator true diff --git a/README.md b/README.md index c5783fe..34bc107 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ While it is not the most elegant solution (recommendations and contributions are Add `ModernUO.Serialization.Generator` and `ModernUO.Serialization.Annotations` as package references: ```xml - - + + ``` @@ -128,6 +128,9 @@ They tolerate a null collection: adds create it on first use (a `[SortedSetCompa do nothing. They mark the entity dirty only when the collection actually changed, so adding a duplicate to a set or dictionary, or removing a missing entry, does not. +`RemoveFromX`, and `AddToX` on a set or dictionary, return whether the collection changed, like the BCL's `Remove`, +`HashSet.Add` and `TryAdd`. The rest return `void`. + Nullable reference annotations (`string?`, `List?`) are ignored for serialization. Nullable value types (`int?`, `Point3D?`, a nullable `[SerializationGenerator]` struct, or `List`) are written as a `bool` followed by the value when present.