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);