From 3262ec6b553fa2d1d9a83c19b3613436f5e604ac Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Sat, 8 Aug 2026 14:56:42 -0700 Subject: [PATCH] Fix AlwaysPushInheritance serialization read writes resulting in inconsistent ordering for sequences --- .../AlwaysPushInheritanceTest.cs | 73 +++++++++++++++++++ .../SerializationManager.Composition.cs | 3 +- 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Robust.Shared.IntegrationTests/Serialization/AlwaysPushInheritanceTest.cs diff --git a/Robust.Shared.IntegrationTests/Serialization/AlwaysPushInheritanceTest.cs b/Robust.Shared.IntegrationTests/Serialization/AlwaysPushInheritanceTest.cs new file mode 100644 index 0000000000..6734a7d6ed --- /dev/null +++ b/Robust.Shared.IntegrationTests/Serialization/AlwaysPushInheritanceTest.cs @@ -0,0 +1,73 @@ +using NUnit.Framework; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.Manager.Definition; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.UnitTesting.Shared.Serialization; + +namespace Robust.Shared.IntegrationTests.Serialization; + +public sealed partial class AlwaysPushInheritanceTest : OurSerializationTest +{ + [Test] + public void ReadWriteOrderPreservedTest() + { + var listTag = DataDefinitionUtility.AutoGenerateTag(nameof(AlwaysPushInheritanceTestDefinition.List)); + var parent = new MappingDataNode + { + [IdDataFieldAttribute.Name] = new ValueDataNode("parent"), + [listTag] = new SequenceDataNode("0", "1", "2") + }; + + var child = new MappingDataNode + { + [ParentDataFieldAttribute.Name] = new ValueDataNode("parent"), + [IdDataFieldAttribute.Name] = new ValueDataNode("child"), + [listTag] = new SequenceDataNode("3", "4", "5") + }; + + var combined = Serialization.PushComposition(parent, child); + + Assert.That(combined, Does.ContainKey(ParentDataFieldAttribute.Name)); + Assert.That(combined, Does.ContainKey(IdDataFieldAttribute.Name)); + Assert.That(combined, Does.ContainKey(listTag)); + Assert.That(combined, Has.Count.EqualTo(3)); + + var sequence = combined[listTag] as SequenceDataNode; + Assert.That(sequence, Is.Not.Null); + Assert.That(sequence, Has.Count.EqualTo(6)); + + for (var i = 0; i < 6; i++) + { + Assert.That(sequence.Sequence[i], Is.TypeOf()); + + var value = (ValueDataNode)sequence.Sequence[i]; + Assert.That(value.Value, Is.EqualTo($"{i}")); + } + + var read = Serialization.Read(combined, notNullableOverride: true); + Assert.That(read.List, Has.Count.EqualTo(6)); + + for (var i = 0; i < 6; i++) + { + Assert.That(read.List[i], Is.EqualTo(i)); + } + + var write = (MappingDataNode) Serialization.WriteValue(read, notNullableOverride: true); + Assert.That(write, Does.ContainKey(listTag)); + Assert.That(write[listTag], Is.EquivalentTo((SequenceDataNode) combined[listTag])); + + var read2 = Serialization.Read(write, notNullableOverride: true); + Assert.That(read.List, Is.EquivalentTo(read2.List)); + } + + [DataDefinition] + private sealed partial class AlwaysPushInheritanceTestDefinition + { + [DataField] + [AlwaysPushInheritance] + public List List = new(); + } +} diff --git a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs index 8675fff33c..84fa9095ba 100644 --- a/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs +++ b/Robust.Shared/Serialization/Manager/SerializationManager.Composition.cs @@ -152,9 +152,10 @@ public partial class SerializationManager // I have NFI what this comment means. var result = child.Copy(); + var i = 0; foreach (var entry in parent) { - result.Add(entry.Copy()); + result.Insert(i++, entry.Copy()); } return result;