From 6148856b6465ea0a1377ecda633c48c5dc504f6f Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Mon, 20 Jul 2026 02:49:07 -0700 Subject: [PATCH 1/4] burmbgewr --- .../Custom/OrganizedListSerializer.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs new file mode 100644 index 00000000000..b8469e2340d --- /dev/null +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +/// +/// A highly complex serializer that allows you to serialize a list with complex objects +/// as if it were an organized dictionary. +/// +/// An object which implements +public sealed class OrganizedListSerializer : ITypeSerializer, SequenceDataNode> where T : IOrganizeableCollection +{ + public ValidationNode Validate(ISerializationManager serializationManager, + SequenceDataNode node, + IDependencyCollection dependencies, + ISerializationContext? context = null) + { + var list = new List(); + foreach (var elem in node.Sequence) + { + list.Add(serializationManager.ValidateNode(elem, context)); + } + + return new ValidatedSequenceNode(list); + } + + public List Read(ISerializationManager serializationManager, + SequenceDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null, + ISerializationManager.InstantiationDelegate>? instanceProvider = null) + { + if (instanceProvider != null) + { + var sawmill = dependencies.Resolve().GetSawmill("szr"); + sawmill.Warning($"Provided value to a Read-call for a {nameof(List)}. Ignoring..."); + } + + var list = new List(); + + foreach (var sequence in node.Sequence) + { + list.Add(serializationManager.Read(sequence, hookCtx, context)); + } + + for (var i = 0; i < list.Count; i++) + { + var item = list[i]; + var modified = false; + for (var j = list.Count - 1; j > i; j--) + { + var item2 = serializationManager.Read(node.Sequence[j], hookCtx, context); + if (((Object)item).Equals(item2)) + { + list.RemoveAt(j); + item.Insert(item2); + modified = true; + } + } + + if (modified) + list[i] = item; + } + + list.Sort(); + return list; + } + + public DataNode Write(ISerializationManager serializationManager, + List value, + IDependencyCollection dependencies, + bool alwaysWrite = false, + ISerializationContext? context = null) + { + var sequence = new SequenceDataNode(); + + foreach (var elem in value) + { + sequence.Add(serializationManager.WriteValue(elem, alwaysWrite, context)); + } + + return sequence; + } +} + +/// +/// Interface for an object which can be organized and sorted internally. +/// +/// +public interface IOrganizeableCollection : IComparable, IEquatable +{ + /// + /// Combines these two objects together, typically called by a + /// + /// The other object we are combining into this one. + void Insert(T other); +} From e1f5e79562bf76db246fc5b2bdc1dcf82a5f1dc3 Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Mon, 20 Jul 2026 02:55:18 -0700 Subject: [PATCH 2/4] updat ecommnet --- .../Implementations/Custom/OrganizedListSerializer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs index b8469e2340d..bc3a5f26099 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs @@ -3,6 +3,7 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Validation; @@ -13,6 +14,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; /// /// A highly complex serializer that allows you to serialize a list with complex objects /// as if it were an organized dictionary. +/// Should be used on a Datafield with the /// /// An object which implements public sealed class OrganizedListSerializer : ITypeSerializer, SequenceDataNode> where T : IOrganizeableCollection From e970982b83bf48886f445d73d670273f51385364 Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:27:00 -0700 Subject: [PATCH 3/4] burher --- .../Custom/OrganizedListSerializerTest.cs | 245 ++++++++++++++++++ .../Custom/OrganizedListSerializer.cs | 11 +- 2 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 Robust.Shared.IntegrationTests/Serialization/TypeSerializers/Custom/OrganizedListSerializerTest.cs diff --git a/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/Custom/OrganizedListSerializerTest.cs b/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/Custom/OrganizedListSerializerTest.cs new file mode 100644 index 00000000000..3a86654ce2c --- /dev/null +++ b/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/Custom/OrganizedListSerializerTest.cs @@ -0,0 +1,245 @@ +using NUnit.Framework; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Utility; +using Robust.UnitTesting.Shared; + +namespace Robust.Shared.IntegrationTests.Serialization.TypeSerializers.Custom; + +/// +/// Tests to ensure that properly organizes a very simple list +/// +[TestFixture] +[TestOf(typeof(OrganizedListSerializer<>))] +internal sealed partial class OrganizedListSerializerTest : OurRobustUnitTest +{ + private const string ParentProto = "ProtoA"; + + private const string ChildProto = "ProtoB"; + + private List _key0Values = [2, 4, 8]; + + private List _key1Values = [1, 2, 2, 3, 4, 8]; + + private List _key2Values = [1, 3, 9]; + + private List _key1ValuesB = [1, 2, 3]; + + private List _key2ValuesB = [2, 4, 8]; + + protected override Type[] ExtraComponents => new[] {typeof(TestComponent)}; + + private static readonly string Prototypes = $@" +- type: entity + id: {ParentProto} + components: + - type: Test + listA: + - key: 1 + values: + - 2 + - 4 + - 8 + - key: 2 + values: + - 1 + - 3 + - 9 + listB: + - key: 1 + values: + - 2 + - 4 + - 8 + - key: 2 + values: + - 1 + - 3 + - 9 + +- type: entity + parent: [{ParentProto}] + id: {ChildProto} + components: + - type: Test + listA: + - key: 1 + values: + - 1 + - 2 + - 3 + - key: 0 + values: + - 2 + - 4 + - 8 + listB: + - key: 1 + values: + - 1 + - 2 + - 3 + - key: 2 + values: + - 2 + - 4 + - 8 +"; + + [Test] + public void OrganizedListTest() + { + var serializationManager = IoCManager.Resolve(); + serializationManager.Initialize(); + var prototypeManager = IoCManager.Resolve(); + + // We use entity prototypes since Serialization recognizes them without fuss, and we need complex YAML behavior for this test. + prototypeManager.RegisterKind(typeof(EntityPrototype), typeof(EntityCategoryPrototype)); + prototypeManager.LoadString(Prototypes); + prototypeManager.ResolveResults(); + + var entityManager = IoCManager.Resolve(); + entityManager.System().CreateMap(out var mapId); + + var coordinates = new MapCoordinates(0, 0, mapId); + + var parentEntity = entityManager.SpawnEntity(ParentProto, coordinates); + var childEntity = entityManager.SpawnEntity(ChildProto, coordinates); + + Assert.That(entityManager.TryGetComponent(parentEntity, out var parentComp)); + Assert.That(entityManager.TryGetComponent(childEntity, out var childComponent)); + + // Instead of 4 items, two of them should've combined into one. + Assert.That(childComponent!.ListA.Count == 3, $"Child Component's AList failed to inherit properly. List contained {childComponent!.ListA.Count} entries."); + + var index = 0; + // Ensure that the lists inherited correctly, and became organized and squashed correctly + foreach (var obj in childComponent.ListA) + { + // Items should be ordered! + Assert.That(obj.Key == index); + switch (obj.Key) + { + case 0: + // Should be an unmodified list. + Assert.That(obj.Values.SequenceEqual(_key0Values)); + break; + case 1: + for (var i = obj.Values.Count - 1; i >= 0; i--) + { + var item = obj.Values[i]; + Assert.That(obj.Values.Count == _key1Values.Count); + for (var j = _key1Values.Count - 1; j >= 0; j--) + { + if (_key1Values[j] != item) + continue; + + _key1Values.RemoveSwap(j); + obj.Values.RemoveSwap(i); + break; + } + } + Assert.That(obj.Values.Count == 0 && _key1Values.Count == 0); + break; + case 2: + Assert.That(obj.Values.SequenceEqual(_key2Values)); + break; + default: + Assert.Fail($"Key was an unexpected value {obj.Key}"); + break; + } + + index++; + } + + // We alternate between keys 1 and 2 :P + index = 1; + // Ensure that this list followed normal push inheritance rules + for (var i = 0; i < childComponent.ListB.Count; i++) + { + // The greatest code known to man :) + var childList = childComponent.ListB[i]; + switch (i) + { + case 0: + Assert.That(childList.Values.Count == _key1ValuesB.Count); + for (var j = 0; j < childList.Values.Count; j++) + { + Assert.That(childList.Values[j] == _key1ValuesB[j]); + } + index++; + break; + case 1: + Assert.That(childList.Values.Count == _key2ValuesB.Count); + for (var j = 0; j < childList.Values.Count; j++) + { + Assert.That(childList.Values[j] == _key2ValuesB[j]); + } + index--; + break; + case 2: + var parentList = parentComp!.ListB[i - 2]; + Assert.That(childList.Values.Count == parentList.Values.Count); + for (var j = 0; j < childList.Values.Count; j++) + { + Assert.That(childList.Values[j] == parentList.Values[j]); + } + index++; + break; + case 3: + parentList = parentComp!.ListB[i - 2]; + Assert.That(childList.Values.Count == parentList.Values.Count); + for (var j = 0; j < childList.Values.Count; j++) + { + Assert.That(childList.Values[j] == parentList.Values[j]); + } + index--; + break; + default: + Assert.Fail($"ListB contained more elements than expected: {childComponent.ListB.Count}"); + break; + } + } + } +} + +[DataDefinition] +public sealed partial class ComplexTestObject : IOrganizeableCollection +{ + [DataField(required: true)] + public int Key; + + [DataField(required: true)] + public List Values = new (); + + public int CompareTo(ComplexTestObject? other) + { + return Key.CompareTo(other?.Key); + } + + public bool Equals(ComplexTestObject? other) + { + return Key.Equals(other?.Key); + } + + public void Insert(ComplexTestObject other) + { + Values.AddRange(other.Values) ; + } +} + +public sealed partial class TestComponent : Component +{ + [DataField(customTypeSerializer: typeof(OrganizedListSerializer))] + [AlwaysPushInheritance] + public List ListA = new (); + + [DataField] + [AlwaysPushInheritance] + public List ListB = new (); +} diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs index bc3a5f26099..ef633c95e1f 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs @@ -24,7 +24,7 @@ public ValidationNode Validate(ISerializationManager serializationManager, IDependencyCollection dependencies, ISerializationContext? context = null) { - var list = new List(); + var list = new List(node.Count); foreach (var elem in node.Sequence) { list.Add(serializationManager.ValidateNode(elem, context)); @@ -46,7 +46,7 @@ public List Read(ISerializationManager serializationManager, sawmill.Warning($"Provided value to a Read-call for a {nameof(List)}. Ignoring..."); } - var list = new List(); + var list = new List(node.Count); foreach (var sequence in node.Sequence) { @@ -60,8 +60,11 @@ public List Read(ISerializationManager serializationManager, for (var j = list.Count - 1; j > i; j--) { var item2 = serializationManager.Read(node.Sequence[j], hookCtx, context); - if (((Object)item).Equals(item2)) + if (item.Equals(item2)) { + // Note that this does not organize the nested list items. + // If you deem that necessary, then feel free to write additional logic for that :P + // You'd likely want to do the equality checking at the read step rather than here if you were going to. list.RemoveAt(j); item.Insert(item2); modified = true; @@ -82,7 +85,7 @@ public DataNode Write(ISerializationManager serializationManager, bool alwaysWrite = false, ISerializationContext? context = null) { - var sequence = new SequenceDataNode(); + var sequence = new SequenceDataNode(value.Count); foreach (var elem in value) { From 61849329458a6bb29d474eaaefe8ad9915fa769b Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Sat, 25 Jul 2026 18:42:46 -0700 Subject: [PATCH 4/4] everyone say "thank you delta" --- .../Implementations/Custom/OrganizedListSerializer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs index ef633c95e1f..1ede09e2311 100644 --- a/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs +++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/Custom/OrganizedListSerializer.cs @@ -17,7 +17,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; /// Should be used on a Datafield with the /// /// An object which implements -public sealed class OrganizedListSerializer : ITypeSerializer, SequenceDataNode> where T : IOrganizeableCollection +public sealed class OrganizedListSerializer : BaseTypeSerializer, ITypeSerializer, SequenceDataNode> where T : IOrganizeableCollection { public ValidationNode Validate(ISerializationManager serializationManager, SequenceDataNode node, @@ -41,10 +41,7 @@ public List Read(ISerializationManager serializationManager, ISerializationManager.InstantiationDelegate>? instanceProvider = null) { if (instanceProvider != null) - { - var sawmill = dependencies.Resolve().GetSawmill("szr"); - sawmill.Warning($"Provided value to a Read-call for a {nameof(List)}. Ignoring..."); - } + Log.Warning($"Provided value to a Read-call for a {nameof(List)}. Ignoring..."); var list = new List(node.Count);