using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; using Robust.UnitTesting.Server; namespace Robust.Shared.IntegrationTests.Prototypes; [TestFixture] internal sealed class PrototypeComponentInterningTest { private const string ComponentName = "PrototypeInterned"; private const string ParentId = "PrototypeComponentParent"; private const string ChildAId = "PrototypeComponentChildA"; private const string ChildBId = "PrototypeComponentChildB"; private const string OverrideId = "PrototypeComponentOverride"; private const string EqualAId = "PrototypeComponentEqualA"; private const string EqualBId = "PrototypeComponentEqualB"; private const string NullId = "PrototypeComponentNull"; private const string StringNullId = "PrototypeComponentStringNull"; [Test] public void InternsInheritedAndEquivalentComponents() { var sim = RobustServerSimulation .NewSimulation() .RegisterComponents(factory => { factory.RegisterClass(); factory.RegisterClass(); }) .RegisterPrototypes(factory => factory.LoadString(InitialPrototypes)) .InitializeInstance(); var componentFactory = sim.Resolve(); var prototypes = sim.Resolve(); var childA = prototypes.Index(ChildAId); var childB = prototypes.Index(ChildBId); var overridden = prototypes.Index(OverrideId); var equalA = prototypes.Index(EqualAId); var equalB = prototypes.Index(EqualBId); var nullPrototype = prototypes.Index(NullId); var stringNullPrototype = prototypes.Index(StringNullId); var childAEntry = childA.Components[ComponentName]; Assert.Multiple(() => { Assert.That(childAEntry.Component, Is.SameAs(childB.Components[ComponentName].Component), "Unchanged inherited components should share their prototype component."); Assert.That(childAEntry.Component, Is.Not.SameAs(overridden.Components[ComponentName].Component), "An overridden component must retain a distinct prototype component."); Assert.That(equalA.Components[ComponentName].Component, Is.SameAs(equalB.Components[ComponentName].Component), "Equivalent component mappings should share their prototype component."); Assert.That(childA.Components["Transform"].Component, Is.SameAs(childB.Components["Transform"].Component)); Assert.That(childA.Components["MetaData"].Component, Is.SameAs(childB.Components["MetaData"].Component)); Assert.That(nullPrototype.Components["PrototypeNullableString"].Component, Is.Not.SameAs(stringNullPrototype.Components["PrototypeNullableString"].Component)); Assert.That(((PrototypeNullableStringComponent) nullPrototype.Components["PrototypeNullableString"].Component).Value, Is.Null); Assert.That(((PrototypeNullableStringComponent) stringNullPrototype.Components["PrototypeNullableString"].Component).Value, Is.EqualTo("null")); }); var copied = (PrototypeInternedComponent) componentFactory.GetComponent(childAEntry); copied.Value = 100; var copiedTransform = componentFactory.GetComponent(childA.Components["Transform"]); var copiedMetaData = componentFactory.GetComponent(childA.Components["MetaData"]); copiedTransform.NetSyncEnabled = false; copiedMetaData.NetSyncEnabled = false; Assert.Multiple(() => { Assert.That(copiedTransform, Is.Not.SameAs(childA.Components["Transform"].Component)); Assert.That(copiedMetaData, Is.Not.SameAs(childA.Components["MetaData"].Component)); Assert.That(childA.Components["Transform"].Component.NetSyncEnabled, Is.True); Assert.That(childA.Components["MetaData"].Component.NetSyncEnabled, Is.True); }); Assert.Multiple(() => { Assert.That(((PrototypeInternedComponent) childAEntry.Component).Value, Is.EqualTo(42)); Assert.That(((PrototypeInternedComponent) childB.Components[ComponentName].Component).Value, Is.EqualTo(42)); }); var changed = new Dictionary>(); prototypes.LoadString(ReloadedParent, true, changed); prototypes.ReloadPrototypes(changed); childA = prototypes.Index(ChildAId); childB = prototypes.Index(ChildBId); // Assumption MAY change at some point Assert.Multiple(() => { Assert.That(childA.Components[ComponentName].Component, Is.SameAs(childB.Components[ComponentName].Component), "The cache should be rebuilt after reload."); Assert.That(((PrototypeInternedComponent) childA.Components[ComponentName].Component).Value, Is.EqualTo(99)); }); } private static readonly string InitialPrototypes = $@" - type: entity id: {ParentId} abstract: true components: - type: Transform - type: MetaData - type: {ComponentName} value: 42 - type: entity id: {ChildAId} parent: {ParentId} - type: entity id: {ChildBId} parent: {ParentId} - type: entity id: {OverrideId} parent: {ParentId} components: - type: {ComponentName} value: 77 - type: entity id: {EqualAId} components: - type: {ComponentName} value: 12 - type: entity id: {EqualBId} components: - type: {ComponentName} value: 12 - type: entity id: {NullId} components: - type: PrototypeNullableString value: null - type: entity id: {StringNullId} components: - type: PrototypeNullableString value: ""null"" "; private static readonly string ReloadedParent = $@" - type: entity id: {ParentId} abstract: true components: - type: {ComponentName} value: 99 "; } internal sealed partial class PrototypeInternedComponent : Component { [DataField("value")] public int Value; } internal sealed partial class PrototypeNullableStringComponent : Component { [DataField("value")] public string? Value; }