mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Split up test project
Robust.UnitTesting was both ALL tests for RT, and also API surface for content tests. Tests are now split into separate projects as appropriate, and the API side has also been split off.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(ComponentFactory))]
|
||||
internal sealed partial class ComponentFactory_Tests : OurRobustUnitTest
|
||||
{
|
||||
private const string TestComponentName = "A";
|
||||
private const string LowercaseTestComponentName = "a";
|
||||
private const string NonexistentComponentName = "B";
|
||||
protected override Type[]? ExtraComponents => new[] {typeof(TestComponent)};
|
||||
|
||||
[Test]
|
||||
public void GetComponentAvailabilityTest()
|
||||
{
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
// Should not exist
|
||||
Assert.That(componentFactory.GetComponentAvailability(NonexistentComponentName), Is.EqualTo(ComponentAvailability.Unknown));
|
||||
Assert.That(componentFactory.GetComponentAvailability(NonexistentComponentName, true), Is.EqualTo(ComponentAvailability.Unknown));
|
||||
|
||||
// Normal casing, do not ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponentAvailability(TestComponentName), Is.EqualTo(ComponentAvailability.Available));
|
||||
|
||||
// Normal casing, ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponentAvailability(TestComponentName, true), Is.EqualTo(ComponentAvailability.Available));
|
||||
|
||||
// Lower casing, do not ignore case, should not exist
|
||||
Assert.That(componentFactory.GetComponentAvailability(LowercaseTestComponentName), Is.EqualTo(ComponentAvailability.Unknown));
|
||||
|
||||
// Lower casing, ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponentAvailability(LowercaseTestComponentName, true), Is.EqualTo(ComponentAvailability.Available));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetComponentTest()
|
||||
{
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
// Should not exist
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(NonexistentComponentName));
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(NonexistentComponentName, true));
|
||||
|
||||
// Normal casing, do not ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponent(TestComponentName), Is.InstanceOf<TestComponent>());
|
||||
|
||||
// Normal casing, ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponent(TestComponentName, true), Is.InstanceOf<TestComponent>());
|
||||
|
||||
// Lower casing, do not ignore case, should not exist
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetComponent(LowercaseTestComponentName));
|
||||
|
||||
// Lower casing, ignore case, should exist
|
||||
Assert.That(componentFactory.GetComponent(LowercaseTestComponentName, true), Is.InstanceOf<TestComponent>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetRegistrationTest()
|
||||
{
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
// Should not exist
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(NonexistentComponentName));
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(NonexistentComponentName, true));
|
||||
|
||||
// Normal casing, do not ignore case, should exist
|
||||
Assert.DoesNotThrow(() => componentFactory.GetRegistration(TestComponentName));
|
||||
|
||||
// Normal casing, ignore case, should exist
|
||||
Assert.DoesNotThrow(() => componentFactory.GetRegistration(TestComponentName, true));
|
||||
|
||||
// Lower casing, do not ignore case, should not exist
|
||||
Assert.Throws<UnknownComponentException>(() => componentFactory.GetRegistration(LowercaseTestComponentName));
|
||||
|
||||
// Lower casing, ignore case, should exist
|
||||
Assert.DoesNotThrow(() => componentFactory.GetRegistration(LowercaseTestComponentName, true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetRegistrationTest()
|
||||
{
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
// Should not exist
|
||||
Assert.That(componentFactory.TryGetRegistration(NonexistentComponentName, out _), Is.False);
|
||||
Assert.That(componentFactory.TryGetRegistration(NonexistentComponentName, out _, true), Is.False);
|
||||
|
||||
// Normal casing, do not ignore case, should exist
|
||||
Assert.That(componentFactory.TryGetRegistration(TestComponentName, out _));
|
||||
|
||||
// Normal casing, ignore case, should exist
|
||||
Assert.That(componentFactory.TryGetRegistration(TestComponentName, out _, true));
|
||||
|
||||
// Lower casing, do not ignore case, should not exist
|
||||
Assert.That(componentFactory.TryGetRegistration(LowercaseTestComponentName, out _), Is.False);
|
||||
|
||||
// Lower casing, ignore case, should exist
|
||||
Assert.That(componentFactory.TryGetRegistration(LowercaseTestComponentName, out _, true));
|
||||
}
|
||||
|
||||
[ComponentProtoName(TestComponentName)]
|
||||
private sealed partial class TestComponent : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
370
Robust.Shared.IntegrationTests/GameObjects/ContainerTests.cs
Normal file
370
Robust.Shared.IntegrationTests/GameObjects/ContainerTests.cs
Normal file
@@ -0,0 +1,370 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
internal sealed class ContainerTests : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests container states with children that do not exist on the client
|
||||
/// and tests that said children are added to the container when they do arrive on the client.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test]
|
||||
public async Task TestContainerNonexistantItems()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
var cEntManager = client.ResolveDependency<IEntityManager>();
|
||||
var clientNetManager = client.ResolveDependency<IClientNetManager>();
|
||||
|
||||
var sEntManager = server.ResolveDependency<IEntityManager>();
|
||||
var sPlayerManager = server.ResolveDependency<IPlayerManager>();
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
client.Post(() =>
|
||||
{
|
||||
clientNetManager.ClientConnect(null!, 0, null!);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Setup
|
||||
var mapId = MapId.Nullspace;
|
||||
var mapPos = MapCoordinates.Nullspace;
|
||||
|
||||
EntityUid entityUid = default!;
|
||||
|
||||
var cContainerSys = cEntManager.System<ContainerSystem>();
|
||||
var sContainerSys = sEntManager.System<SharedContainerSystem>();
|
||||
var sMetadataSys = sEntManager.System<MetaDataSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
sEntManager.System<SharedMapSystem>().CreateMap(out mapId);
|
||||
mapPos = new MapCoordinates(new Vector2(0, 0), mapId);
|
||||
|
||||
entityUid = sEntManager.SpawnEntity(null, mapPos);
|
||||
sMetadataSys.SetEntityName(entityUid, "Container");
|
||||
sContainerSys.EnsureContainer<Container>(entityUid, "dummy");
|
||||
|
||||
// Setup PVS
|
||||
sEntManager.AddComponent<EyeComponent>(entityUid);
|
||||
var player = sPlayerManager.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(player, entityUid);
|
||||
sPlayerManager.JoinGame(player);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
EntityUid itemUid = default!;
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
itemUid = sEntManager.SpawnEntity(null, mapPos);
|
||||
sMetadataSys.SetEntityName(itemUid, "Item");
|
||||
var container = sContainerSys.EnsureContainer<Container>(entityUid, "dummy");
|
||||
Assert.That(sContainerSys.Insert(itemUid, container));
|
||||
|
||||
// Modify visibility layer so that the item does not get sent ot the player
|
||||
sEntManager.System<SharedVisibilitySystem>().AddLayer(itemUid, 10 );
|
||||
});
|
||||
|
||||
// Needs minimum 4 to sync to client because buffer size is 3
|
||||
await server.WaitRunTicks(4);
|
||||
await client.WaitRunTicks(10);
|
||||
|
||||
EntityUid cEntityUid = default!;
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
cEntityUid = client.EntMan.GetEntity(server.EntMan.GetNetEntity(entityUid));
|
||||
if (!cEntManager.TryGetComponent<ContainerManagerComponent>(cEntityUid, out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = cContainerSys.GetContainer(cEntityUid, "dummy", containerManagerComp);
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(0));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.That(cContainerSys.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid)));
|
||||
Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Modify visibility layer so it now gets sent to the client
|
||||
sEntManager.System<SharedVisibilitySystem>().RemoveLayer(itemUid, 10 );
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
if (!cEntManager.TryGetComponent<ContainerManagerComponent>(cEntityUid, out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = cContainerSys.GetContainer(cEntityUid, "dummy", containerManagerComp);
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
Assert.That(!cContainerSys.ExpectedEntities.ContainsKey(sEntManager.GetNetEntity(itemUid)));
|
||||
Assert.That(cContainerSys.ExpectedEntities, Is.Empty);
|
||||
});
|
||||
|
||||
await client.WaitPost(() => clientNetManager.ClientDisconnect(""));
|
||||
await server.WaitRunTicks(5);
|
||||
await client.WaitRunTicks(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests container states with children that do not exist on the client
|
||||
/// and that if those children are deleted that they get properly removed from the expected entities list.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test]
|
||||
public async Task TestContainerExpectedEntityDeleted()
|
||||
{
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
|
||||
await Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync());
|
||||
|
||||
var cEntManager = client.ResolveDependency<IEntityManager>();
|
||||
var clientTime = client.ResolveDependency<IClientGameTiming>();
|
||||
var clientNetManager = client.ResolveDependency<IClientNetManager>();
|
||||
|
||||
var sMapManager = server.ResolveDependency<IMapManager>();
|
||||
var sEntManager = server.ResolveDependency<IEntityManager>();
|
||||
var sPlayerManager = server.ResolveDependency<IPlayerManager>();
|
||||
var serverTime = server.ResolveDependency<IGameTiming>();
|
||||
|
||||
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
|
||||
await client.WaitPost(() =>
|
||||
{
|
||||
clientNetManager.ClientConnect(null!, 0, null!);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
// Setup
|
||||
MapId mapId;
|
||||
var mapPos = MapCoordinates.Nullspace;
|
||||
|
||||
EntityUid sEntityUid = default!;
|
||||
EntityUid sItemUid = default!;
|
||||
NetEntity netEnt = default;
|
||||
|
||||
var cContainerSys = cEntManager.System<ContainerSystem>();
|
||||
var sContainerSys = sEntManager.System<SharedContainerSystem>();
|
||||
var sMetadataSys = sEntManager.System<MetaDataSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
sEntManager.System<SharedMapSystem>().CreateMap(out mapId);
|
||||
mapPos = new MapCoordinates(new Vector2(0, 0), mapId);
|
||||
|
||||
sEntityUid = sEntManager.SpawnEntity(null, mapPos);
|
||||
sMetadataSys.SetEntityName(sEntityUid, "Container");
|
||||
sContainerSys.EnsureContainer<Container>(sEntityUid, "dummy");
|
||||
|
||||
// Setup PVS
|
||||
sEntManager.AddComponent<EyeComponent>(sEntityUid);
|
||||
var player = sPlayerManager.Sessions.First();
|
||||
server.PlayerMan.SetAttachedEntity(player, sEntityUid);
|
||||
sPlayerManager.JoinGame(player);
|
||||
});
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
sItemUid = sEntManager.SpawnEntity(null, mapPos);
|
||||
netEnt = sEntManager.GetNetEntity(sItemUid);
|
||||
sMetadataSys.SetEntityName(sItemUid, "Item");
|
||||
var container = sContainerSys.GetContainer(sEntityUid, "dummy");
|
||||
sContainerSys.Insert(sItemUid, container);
|
||||
|
||||
// Modify visibility layer so that the item does not get sent ot the player
|
||||
sEntManager.System<SharedVisibilitySystem>().AddLayer(sItemUid, 10 );
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
while (clientTime.LastRealTick < serverTime.CurTick - 1)
|
||||
{
|
||||
await client.WaitRunTicks(1);
|
||||
}
|
||||
|
||||
var cUid = cEntManager.GetEntity(sEntManager.GetNetEntity(sEntityUid));
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
if (!cEntManager.TryGetComponent<ContainerManagerComponent>(cUid, out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = cContainerSys.GetContainer(cUid, "dummy", containerManagerComp);
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(0));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.That(cContainerSys.ExpectedEntities.ContainsKey(netEnt));
|
||||
Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(1));
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// If possible it'd be best to only have the DeleteEntity, but right now
|
||||
// the entity deleted event is not played on the client if the entity does not exist on the client.
|
||||
if (sEntManager.EntityExists(sItemUid)
|
||||
// && itemUid.TryGetContainer(out var container))
|
||||
&& sContainerSys.TryGetContainingContainer(sItemUid, out var container))
|
||||
{
|
||||
sContainerSys.Remove(sItemUid, container, force: true);
|
||||
}
|
||||
|
||||
sEntManager.DeleteEntity(sItemUid);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
await client.WaitRunTicks(4);
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
if (!cEntManager.TryGetComponent<ContainerManagerComponent>(cUid, out var containerManagerComp))
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
|
||||
var container = cContainerSys.GetContainer(cUid, "dummy", containerManagerComp);
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(0));
|
||||
Assert.That(container.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
|
||||
Assert.That(!cContainerSys.ExpectedEntities.ContainsKey(netEnt));
|
||||
Assert.That(cContainerSys.ExpectedEntities.Count, Is.EqualTo(0));
|
||||
});
|
||||
|
||||
await client.WaitPost(() => clientNetManager.ClientDisconnect(""));
|
||||
await server.WaitRunTicks(5);
|
||||
await client.WaitRunTicks(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a new container, initializes map, saves the map, then loads it again on another map. The contained entity should still
|
||||
/// be inside the container.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Container_DeserializeGrid_IsStillContained()
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
await Task.WhenAll(server.WaitIdleAsync());
|
||||
|
||||
var sEntManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapSys = sEntManager.System<SharedMapSystem>();
|
||||
var sContainerSys = sEntManager.System<SharedContainerSystem>();
|
||||
var sMetadataSys = sEntManager.System<MetaDataSystem>();
|
||||
var path = new ResPath("container_test.yml");
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// build the map
|
||||
sEntManager.System<SharedMapSystem>().CreateMap(out var mapIdOne);
|
||||
Assert.That(mapSys.IsInitialized(mapIdOne), Is.True);
|
||||
|
||||
var containerEnt = sEntManager.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne));
|
||||
sMetadataSys.SetEntityName(containerEnt, "ContainerEnt");
|
||||
|
||||
var containeeEnt = sEntManager.SpawnEntity(null, new MapCoordinates(2, 2, mapIdOne));
|
||||
sMetadataSys.SetEntityName(containeeEnt, "ContaineeEnt");
|
||||
|
||||
var container = sContainerSys.MakeContainer<Container>(containerEnt, "testContainer");
|
||||
container.OccludesLight = true;
|
||||
container.ShowContents = true;
|
||||
sContainerSys.Insert(containeeEnt, container);
|
||||
|
||||
// save the map
|
||||
var mapLoader = sEntManager.EntitySysManager.GetEntitySystem<MapLoaderSystem>();
|
||||
|
||||
Assert.That(mapLoader.TrySaveMap(mapIdOne, path));
|
||||
mapSys.DeleteMap(mapIdOne);
|
||||
});
|
||||
|
||||
// A few moments later...
|
||||
await server.WaitRunTicks(10);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapLoader = sEntManager.System<MapLoaderSystem>();
|
||||
|
||||
// load the map
|
||||
Assert.That(mapLoader.TryLoadMap(path, out var map, out _));
|
||||
Assert.That(mapSys.IsInitialized(map), Is.True); // Map Initialize-ness is saved in the map file.
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// verify container
|
||||
Entity<ContainerManagerComponent> container = default;
|
||||
var query = sEntManager.EntityQueryEnumerator<ContainerManagerComponent>();
|
||||
while (query.MoveNext(out var uid, out var containerComp))
|
||||
{
|
||||
container = (uid, containerComp);
|
||||
}
|
||||
|
||||
var containerEnt = container.Owner;
|
||||
Assert.That(container.Comp, Is.Not.Null);
|
||||
|
||||
Assert.That(sEntManager.GetComponent<MetaDataComponent>(containerEnt).EntityName, Is.EqualTo("ContainerEnt"));
|
||||
|
||||
Assert.That(container.Comp!.Containers.ContainsKey("testContainer"));
|
||||
|
||||
var baseContainer = sContainerSys.GetContainer(containerEnt, "testContainer", container.Comp);
|
||||
Assert.That(baseContainer.ContainedEntities, Has.Count.EqualTo(1));
|
||||
|
||||
var containeeEnt = baseContainer.ContainedEntities[0];
|
||||
Assert.That(sEntManager.GetComponent<MetaDataComponent>(containeeEnt).EntityName, Is.EqualTo("ContaineeEnt"));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects;
|
||||
|
||||
internal sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest
|
||||
{
|
||||
// This test ensures that deferred deletion can be used while handling events without issue, and that deleting an
|
||||
// entity after deferring component removal doesn't cause any issues.
|
||||
|
||||
[Test]
|
||||
public async Task TestDeferredEntityDeletion()
|
||||
{
|
||||
var options = new ServerIntegrationOptions();
|
||||
options.Pool = false;
|
||||
options.BeforeRegisterComponents += () =>
|
||||
{
|
||||
var fact = IoCManager.Resolve<IComponentFactory>();
|
||||
fact.RegisterClass<DeferredDeletionTestComponent>();
|
||||
fact.RegisterClass<OtherDeferredDeletionTestComponent>();
|
||||
};
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
sysMan.LoadExtraSystemType<DeferredDeletionTestSystem>();
|
||||
sysMan.LoadExtraSystemType<OtherDeferredDeletionTestSystem>();
|
||||
};
|
||||
|
||||
var server = StartServer(options);
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
EntityUid uid1 = default, uid2 = default, uid3 = default, uid4 = default;
|
||||
DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!, comp4 = default!;
|
||||
IEntityManager entMan = default!;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var sys = entMan.EntitySysManager.GetEntitySystem<DeferredDeletionTestSystem>();
|
||||
|
||||
uid1 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid2 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid3 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
uid4 = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
|
||||
comp1 = entMan.AddComponent<DeferredDeletionTestComponent>(uid1);
|
||||
comp2 = entMan.AddComponent<DeferredDeletionTestComponent>(uid2);
|
||||
comp3 = entMan.AddComponent<DeferredDeletionTestComponent>(uid3);
|
||||
comp4 = entMan.AddComponent<DeferredDeletionTestComponent>(uid4);
|
||||
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid1);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid2);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid3);
|
||||
entMan.AddComponent<OtherDeferredDeletionTestComponent>(uid4);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
// first: test that deferring deletion while handling events doesn't cause issues
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(comp1.Running);
|
||||
var ev = new DeferredDeletionTestEvent();
|
||||
entMan.EventBus.RaiseLocalEvent(uid1, ev);
|
||||
Assert.That(comp1.LifeStage == ComponentLifeStage.Stopped);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
Assert.That(comp1.LifeStage == ComponentLifeStage.Deleted);
|
||||
|
||||
// next check that entity deletion doesn't cause issues:
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var ev = new DeferredDeletionTestEvent();
|
||||
entMan.EventBus.RaiseLocalEvent(uid2, ev);
|
||||
entMan.EventBus.RaiseLocalEvent(uid3, ev);
|
||||
entMan.EventBus.RaiseLocalEvent(uid4, ev);
|
||||
entMan.DeleteEntity(uid2);
|
||||
entMan.QueueDeleteEntity(uid3);
|
||||
entMan.TryQueueDeleteEntity(uid4);
|
||||
Assert.That(entMan.Deleted(uid2));
|
||||
Assert.That(!entMan.Deleted(uid3));
|
||||
Assert.That(!entMan.Deleted(uid4));
|
||||
Assert.That(comp2.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(comp3.LifeStage == ComponentLifeStage.Stopped);
|
||||
Assert.That(comp4.LifeStage == ComponentLifeStage.Stopped);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
Assert.That(comp3.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(comp4.LifeStage == ComponentLifeStage.Deleted);
|
||||
Assert.That(entMan.Deleted(uid3));
|
||||
Assert.That(entMan.Deleted(uid4));
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class DeferredDeletionTestSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DeferredDeletionTestComponent, DeferredDeletionTestEvent>(OnTestEvent);
|
||||
}
|
||||
|
||||
private void OnTestEvent(EntityUid uid, DeferredDeletionTestComponent component, DeferredDeletionTestEvent args)
|
||||
{
|
||||
// remove both this component, and some other component that this entity has that also subscribes to this event.
|
||||
RemCompDeferred<DeferredDeletionTestComponent>(uid);
|
||||
RemCompDeferred<OtherDeferredDeletionTestComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class OtherDeferredDeletionTestSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize() => SubscribeLocalEvent<OtherDeferredDeletionTestComponent, DeferredDeletionTestEvent>(OnTestEvent);
|
||||
|
||||
private void OnTestEvent(EntityUid uid, OtherDeferredDeletionTestComponent component, DeferredDeletionTestEvent args)
|
||||
{
|
||||
// remove both this component, and some other component that this entity has that also subscribes to this event.
|
||||
RemCompDeferred<DeferredDeletionTestComponent>(uid);
|
||||
RemCompDeferred<OtherDeferredDeletionTestComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
[Reflect(false)]
|
||||
private sealed partial class DeferredDeletionTestComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed partial class OtherDeferredDeletionTestComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class DeferredDeletionTestEvent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
[Test]
|
||||
public void SubscribeCompEvent()
|
||||
{
|
||||
var (bus, sim, entUid, compInstance) = EntFactory();
|
||||
var compFactory = sim.Resolve<IComponentFactory>();
|
||||
|
||||
// Subscribe
|
||||
int calledCount = 0;
|
||||
bus.SubscribeLocalEvent<MetaDataComponent, TestEvent>(HandleTestEvent);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// add a component to the system
|
||||
bus.OnEntityAdded(entUid);
|
||||
|
||||
var reg = compFactory.GetRegistration(CompIdx.Index<MetaDataComponent>());
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid), reg));
|
||||
|
||||
// Raise
|
||||
var evntArgs = new TestEvent(5);
|
||||
bus.RaiseLocalEvent(entUid, evntArgs, true);
|
||||
|
||||
// Assert
|
||||
Assert.That(calledCount, Is.EqualTo(1));
|
||||
void HandleTestEvent(EntityUid uid, MetaDataComponent component, TestEvent args)
|
||||
{
|
||||
calledCount++;
|
||||
Assert.That(uid, Is.EqualTo(entUid));
|
||||
Assert.That(component, Is.EqualTo(compInstance));
|
||||
Assert.That(args.TestNumber, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UnsubscribeCompEvent()
|
||||
{
|
||||
var (bus, sim, entUid, compInstance) = EntFactory();
|
||||
var compFactory = sim.Resolve<IComponentFactory>();
|
||||
|
||||
// Subscribe
|
||||
int calledCount = 0;
|
||||
bus.SubscribeLocalEvent<MetaDataComponent, TestEvent>(HandleTestEvent);
|
||||
bus.UnsubscribeLocalEvent<MetaDataComponent, TestEvent>();
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// add a component to the system
|
||||
bus.OnEntityAdded(entUid);
|
||||
|
||||
var reg = compFactory.GetRegistration(CompIdx.Index<MetaDataComponent>());
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid), reg));
|
||||
|
||||
// Raise
|
||||
var evntArgs = new TestEvent(5);
|
||||
bus.RaiseLocalEvent(entUid, evntArgs, true);
|
||||
|
||||
// Assert
|
||||
Assert.That(calledCount, Is.EqualTo(0));
|
||||
void HandleTestEvent(EntityUid uid, MetaDataComponent component, TestEvent args)
|
||||
{
|
||||
calledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscribeCompLifeEvent()
|
||||
{
|
||||
var (bus, sim, entUid, compInstance) = EntFactory();
|
||||
var entMan = sim.Resolve<EntityManager>();
|
||||
var fact = sim.Resolve<IComponentFactory>();
|
||||
|
||||
// Subscribe
|
||||
int calledCount = 0;
|
||||
bus.SubscribeLocalEvent<MetaDataComponent, ComponentInit>(HandleTestEvent);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Raise
|
||||
bus.RaiseComponentEvent(entUid, compInstance, new ComponentInit());
|
||||
|
||||
// Assert
|
||||
Assert.That(calledCount, Is.EqualTo(1));
|
||||
void HandleTestEvent(EntityUid uid, MetaDataComponent component, ComponentInit args)
|
||||
{
|
||||
calledCount++;
|
||||
Assert.That(uid, Is.EqualTo(entUid));
|
||||
Assert.That(component, Is.EqualTo(compInstance));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CompEventOrdered()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(f =>
|
||||
{
|
||||
f.RegisterClass<OrderAComponent>();
|
||||
f.RegisterClass<OrderBComponent>();
|
||||
f.RegisterClass<OrderCComponent>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var entMan = sim.Resolve<EntityManager>();
|
||||
var entUid = entMan.Spawn();
|
||||
var instA = entMan.AddComponent<OrderAComponent>(entUid);
|
||||
var instB = entMan.AddComponent<OrderBComponent>(entUid);
|
||||
var instC = entMan.AddComponent<OrderCComponent>(entUid);
|
||||
var bus = entMan.EventBusInternal;
|
||||
bus.ClearSubscriptions();
|
||||
|
||||
var fact = sim.Resolve<IComponentFactory>();
|
||||
|
||||
// Subscribe
|
||||
var a = false;
|
||||
var b = false;
|
||||
var c = false;
|
||||
|
||||
void HandlerA(EntityUid uid, Component comp, TestEvent ev)
|
||||
{
|
||||
Assert.That(b, Is.False, "A should run before B");
|
||||
Assert.That(c, Is.False, "A should run before C");
|
||||
|
||||
a = true;
|
||||
}
|
||||
|
||||
void HandlerB(EntityUid uid, Component comp, TestEvent ev)
|
||||
{
|
||||
Assert.That(c, Is.True, "B should run after C");
|
||||
b = true;
|
||||
}
|
||||
|
||||
void HandlerC(EntityUid uid, Component comp, TestEvent ev) => c = true;
|
||||
|
||||
bus.SubscribeLocalEvent<OrderAComponent, TestEvent>(HandlerA, typeof(OrderAComponent), before: new []{typeof(OrderBComponent), typeof(OrderCComponent)});
|
||||
bus.SubscribeLocalEvent<OrderBComponent, TestEvent>(HandlerB, typeof(OrderBComponent), after: new []{typeof(OrderCComponent)});
|
||||
bus.SubscribeLocalEvent<OrderCComponent, TestEvent>(HandlerC, typeof(OrderCComponent));
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// add a component to the system
|
||||
bus.OnEntityAdded(entUid);
|
||||
|
||||
var regA = fact.GetRegistration(CompIdx.Index<OrderAComponent>());
|
||||
var regB = fact.GetRegistration(CompIdx.Index<OrderBComponent>());
|
||||
var regC = fact.GetRegistration(CompIdx.Index<OrderCComponent>());
|
||||
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instA, entUid), regA));
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instB, entUid), regB));
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instC, entUid), regC));
|
||||
|
||||
// Raise
|
||||
var evntArgs = new TestEvent(5);
|
||||
bus.RaiseLocalEvent(entUid, evntArgs, true);
|
||||
|
||||
// Assert
|
||||
Assert.That(a, Is.True, "A did not fire");
|
||||
Assert.That(b, Is.True, "B did not fire");
|
||||
Assert.That(c, Is.True, "C did not fire");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CompEventLoop()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(f =>
|
||||
{
|
||||
f.RegisterClass<OrderAComponent>();
|
||||
f.RegisterClass<OrderBComponent>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var entMan = sim.Resolve<EntityManager>();
|
||||
var entUid = entMan.Spawn();
|
||||
var instA = entMan.AddComponent<OrderAComponent>(entUid);
|
||||
var instB = entMan.AddComponent<OrderBComponent>(entUid);
|
||||
var bus = entMan.EventBusInternal;
|
||||
bus.ClearSubscriptions();
|
||||
|
||||
var fact = sim.Resolve<IComponentFactory>();
|
||||
var regA = fact.GetRegistration(CompIdx.Index<OrderAComponent>());
|
||||
var regB = fact.GetRegistration(CompIdx.Index<OrderBComponent>());
|
||||
|
||||
var handlerACount = 0;
|
||||
void HandlerA(EntityUid uid, Component comp, TestEvent ev)
|
||||
{
|
||||
Assert.That(handlerACount, Is.EqualTo(0));
|
||||
handlerACount++;
|
||||
|
||||
// add and then remove component B
|
||||
bus.OnComponentRemoved(new RemovedComponentEventArgs(new ComponentEventArgs(instB, entUid), false, default!, CompIdx.Index<OrderBComponent>()));
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instB, entUid), regB));
|
||||
}
|
||||
|
||||
var handlerBCount = 0;
|
||||
void HandlerB(EntityUid uid, Component comp, TestEvent ev)
|
||||
{
|
||||
Assert.That(handlerBCount, Is.EqualTo(0));
|
||||
handlerBCount++;
|
||||
|
||||
// add and then remove component A
|
||||
bus.OnComponentRemoved(new RemovedComponentEventArgs(new ComponentEventArgs(instA, entUid), false, default!, CompIdx.Index<OrderAComponent>()));
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instA, entUid), regA));
|
||||
}
|
||||
|
||||
bus.SubscribeLocalEvent<OrderAComponent, TestEvent>(HandlerA, typeof(OrderAComponent));
|
||||
bus.SubscribeLocalEvent<OrderBComponent, TestEvent>(HandlerB, typeof(OrderBComponent));
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// add a component to the system
|
||||
bus.OnEntityAdded(entUid);
|
||||
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instA, entUid), regA));
|
||||
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instB, entUid), regB));
|
||||
|
||||
// Event subscriptions currently use a linked list.
|
||||
// Currently expect event subscriptions to be raised in order: handlerB -> handlerA
|
||||
// If a component gets removed and added again, it gets moved back to the front of the linked list.
|
||||
// I.e., adding and then removing compA changes the linked list order: handlerA -> handlerB
|
||||
//
|
||||
// This could in principle cause the event raising code to enter an infinite loop.
|
||||
// Adding and removing a comp in an event handler may seem silly but:
|
||||
// - it doesn't have to be the same component if you had a chain of three or more components
|
||||
// - some event handlers raise other events and can lead to convoluted chains of interactions that might inadvertently trigger something like this.
|
||||
|
||||
// Raise
|
||||
bus.RaiseLocalEvent(entUid, new TestEvent(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(handlerACount, Is.LessThanOrEqualTo(1));
|
||||
Assert.That(handlerBCount, Is.LessThanOrEqualTo(1));
|
||||
Assert.That(handlerACount+handlerBCount, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
private sealed partial class DummyComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
private sealed partial class OrderAComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
private sealed partial class OrderBComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
private sealed partial class OrderCComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class TestEvent : EntityEventArgs
|
||||
{
|
||||
public int TestNumber { get; }
|
||||
|
||||
public TestEvent(int testNumber)
|
||||
{
|
||||
TestNumber = testNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects;
|
||||
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
// Explanation of what bug this is testing:
|
||||
// Because event ordering is keyed on system type, we have a problem.
|
||||
// If you register to a directed event like FooEvent twice for different components,
|
||||
// you now have different subscriptions with the same key.
|
||||
//
|
||||
// To trigger this, at least one subscription to this event (possibly another system entirely)
|
||||
// needs to demand some ordering calculation to happen.
|
||||
|
||||
[Test]
|
||||
public void TestDifferentComponentsOrderedSameKeySub()
|
||||
{
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(factory =>
|
||||
{
|
||||
factory.LoadExtraSystemType<DifferentComponentsSameKeySubSystem>();
|
||||
factory.LoadExtraSystemType<DifferentComponentsSameKeySubSystem2>();
|
||||
})
|
||||
.RegisterComponents(factory => factory.RegisterClass<FooComponent>())
|
||||
.InitializeInstance();
|
||||
|
||||
var map = simulation.CreateMap().MapId;
|
||||
|
||||
var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map));
|
||||
simulation.Resolve<IEntityManager>().AddComponent<FooComponent>(entity);
|
||||
|
||||
var foo = new FooEvent();
|
||||
simulation.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(entity, foo, true);
|
||||
|
||||
Assert.That(foo.EventOrder, Is.EquivalentTo(new[]{"Foo", "Transform", "Metadata"}).Or.EquivalentTo(new[]{"Foo", "Metadata", "Transform"}));
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class DifferentComponentsSameKeySubSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TransformComponent, FooEvent>((_, _, e) => { e.EventOrder.Add("Transform"); });
|
||||
SubscribeLocalEvent<MetaDataComponent, FooEvent>((_, _, e) => { e.EventOrder.Add("Metadata"); });
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class DifferentComponentsSameKeySubSystem2 : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, FooEvent>(
|
||||
(_, _, e) => e.EventOrder.Add("Foo"),
|
||||
before: new[] {typeof(DifferentComponentsSameKeySubSystem)});
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed partial class FooComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private sealed class FooEvent
|
||||
{
|
||||
public List<string> EventOrder = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture]
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
[Test]
|
||||
public void SubscribeCompRefBroadcastEvent()
|
||||
{
|
||||
// Arrange.
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(factory => factory.LoadExtraSystemType<SubscribeCompRefBroadcastSystem>())
|
||||
.InitializeInstance();
|
||||
|
||||
var ev = new TestStructEvent() {TestNumber = 5};
|
||||
simulation.Resolve<IEntityManager>().EventBus.RaiseEvent(EventSource.Local, ref ev);
|
||||
Assert.That(ev.TestNumber, Is.EqualTo(15));
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
internal sealed class SubscribeCompRefBroadcastSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TestStructEvent>(OnTestEvent);
|
||||
}
|
||||
|
||||
private void OnTestEvent(ref TestStructEvent ev)
|
||||
{
|
||||
Assert.That(ev.TestNumber, Is.EqualTo(5));
|
||||
ev.TestNumber += 10;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscriptionNoMixedRefValueBroadcastEvent()
|
||||
{
|
||||
// Arrange.
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(factory =>
|
||||
factory.LoadExtraSystemType<SubscriptionNoMixedRefValueBroadcastEventSystem>());
|
||||
|
||||
// Act. No mixed ref and value subscriptions are allowed.
|
||||
Assert.Throws(typeof(InvalidOperationException), () => simulation.InitializeInstance());
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class SubscriptionNoMixedRefValueBroadcastEventSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// The below is not allowed, as you're subscribing by-ref and by-value to the same event...
|
||||
#pragma warning disable RA0013
|
||||
SubscribeLocalEvent<TestStructEvent>(MyRefHandler);
|
||||
SubscribeLocalEvent<TestStructEvent>(MyValueHandler);
|
||||
#pragma warning restore RA0013
|
||||
}
|
||||
|
||||
private void MyValueHandler(TestStructEvent args) { }
|
||||
private void MyRefHandler(ref TestStructEvent args) { }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SortedBroadcastRefEvents()
|
||||
{
|
||||
// Arrange.
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(factory =>
|
||||
{
|
||||
factory.LoadExtraSystemType<BroadcastOrderASystem>();
|
||||
factory.LoadExtraSystemType<BroadcastOrderBSystem>();
|
||||
factory.LoadExtraSystemType<BroadcastOrderCSystem>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
// Act.
|
||||
var testEvent = new TestStructEvent {TestNumber = 5};
|
||||
var eventBus = simulation.Resolve<IEntityManager>().EventBus;
|
||||
eventBus.RaiseEvent(EventSource.Local, ref testEvent);
|
||||
|
||||
// Check that the entity systems changed the value correctly
|
||||
Assert.That(testEvent.TestNumber, Is.EqualTo(15));
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class BroadcastOrderASystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TestStructEvent>(OnA, new[]{typeof(BroadcastOrderBSystem)}, new[]{typeof(BroadcastOrderCSystem)});
|
||||
}
|
||||
|
||||
private void OnA(ref TestStructEvent args)
|
||||
{
|
||||
// Second handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(0));
|
||||
args.TestNumber = 10;
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class BroadcastOrderBSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TestStructEvent>(OnB, null, new []{typeof(BroadcastOrderASystem)});
|
||||
}
|
||||
|
||||
private void OnB(ref TestStructEvent args)
|
||||
{
|
||||
// Last handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(10));
|
||||
args.TestNumber = 15;
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class BroadcastOrderCSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TestStructEvent>(OnC);
|
||||
}
|
||||
|
||||
private void OnC(ref TestStructEvent args)
|
||||
{
|
||||
// First handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(5));
|
||||
args.TestNumber = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void SubscribeCompRefDirectedEvent()
|
||||
{
|
||||
// Arrange.
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory => factory.RegisterClass<DummyComponent>())
|
||||
.RegisterEntitySystems(factory => factory.LoadExtraSystemType<SubscribeCompRefDirectedEventSystem>())
|
||||
.InitializeInstance();
|
||||
|
||||
var map = simulation.CreateMap().MapId;
|
||||
var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map));
|
||||
IoCManager.Resolve<IEntityManager>().AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act.
|
||||
var testEvent = new TestStructEvent {TestNumber = 5};
|
||||
var eventBus = simulation.Resolve<IEntityManager>().EventBus;
|
||||
eventBus.RaiseLocalEvent(entity, ref testEvent, true);
|
||||
|
||||
// Check that the entity system changed the value correctly
|
||||
Assert.That(testEvent.TestNumber, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class SubscribeCompRefDirectedEventSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<DummyComponent, TestStructEvent>(MyRefHandler);
|
||||
}
|
||||
|
||||
private void MyRefHandler(EntityUid uid, DummyComponent component, ref TestStructEvent args)
|
||||
{
|
||||
Assert.That(args.TestNumber, Is.EqualTo(5));
|
||||
args.TestNumber = 10;
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class SubscriptionNoMixedRefValueDirectedEventSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// The below is not allowed, as you're subscribing by-ref and by-value to the same event...
|
||||
SubscribeLocalEvent<DummyComponent, TestStructEvent>(MyRefHandler);
|
||||
#pragma warning disable RA0013
|
||||
SubscribeLocalEvent<DummyTwoComponent, TestStructEvent>(MyValueHandler);
|
||||
#pragma warning restore RA0013
|
||||
}
|
||||
|
||||
private void MyValueHandler(EntityUid uid, DummyTwoComponent component, TestStructEvent args) { }
|
||||
private void MyRefHandler(EntityUid uid, DummyComponent component, ref TestStructEvent args) { }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SortedDirectedRefEvents()
|
||||
{
|
||||
// Arrange.
|
||||
var simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory =>
|
||||
{
|
||||
factory.RegisterClass<OrderAComponent>();
|
||||
factory.RegisterClass<OrderBComponent>();
|
||||
factory.RegisterClass<OrderCComponent>();
|
||||
})
|
||||
.RegisterEntitySystems(factory =>
|
||||
{
|
||||
factory.LoadExtraSystemType<OrderASystem>();
|
||||
factory.LoadExtraSystemType<OrderBSystem>();
|
||||
factory.LoadExtraSystemType<OrderCSystem>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var map = simulation.CreateMap().MapId;
|
||||
var entity = simulation.SpawnEntity(null, new MapCoordinates(0, 0, map));
|
||||
IoCManager.Resolve<IEntityManager>().AddComponent<OrderAComponent>(entity);
|
||||
IoCManager.Resolve<IEntityManager>().AddComponent<OrderBComponent>(entity);
|
||||
IoCManager.Resolve<IEntityManager>().AddComponent<OrderCComponent>(entity);
|
||||
|
||||
// Act.
|
||||
var testEvent = new TestStructEvent {TestNumber = 5};
|
||||
var eventBus = simulation.Resolve<IEntityManager>().EventBus;
|
||||
eventBus.RaiseLocalEvent(entity, ref testEvent, true);
|
||||
|
||||
// Check that the entity systems changed the value correctly
|
||||
Assert.That(testEvent.TestNumber, Is.EqualTo(15));
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class OrderASystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<OrderAComponent, TestStructEvent>(OnA, new[]{typeof(OrderBSystem)}, new[]{typeof(OrderCSystem)});
|
||||
}
|
||||
|
||||
private void OnA(EntityUid uid, OrderAComponent component, ref TestStructEvent args)
|
||||
{
|
||||
// Second handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(0));
|
||||
args.TestNumber = 10;
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class OrderBSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<OrderBComponent, TestStructEvent>(OnB, null, new []{typeof(OrderASystem)});
|
||||
}
|
||||
|
||||
private void OnB(EntityUid uid, OrderBComponent component, ref TestStructEvent args)
|
||||
{
|
||||
// Last handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(10));
|
||||
args.TestNumber = 15;
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class OrderCSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<OrderCComponent, TestStructEvent>(OnC);
|
||||
}
|
||||
|
||||
private void OnC(EntityUid uid, OrderCComponent component, ref TestStructEvent args)
|
||||
{
|
||||
// First handler being ran.
|
||||
Assert.That(args.TestNumber, Is.EqualTo(5));
|
||||
args.TestNumber = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed partial class DummyTwoComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
private struct TestStructEvent
|
||||
{
|
||||
public int TestNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(EntityEventBus))]
|
||||
internal sealed partial class EntityEventBusTests
|
||||
{
|
||||
private static (EntityEventBus Bus, ISimulation Sim, EntityUid Uid, MetaDataComponent Comp) EntFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
var entMan = sim.Resolve<EntityManager>();
|
||||
var uid = entMan.Spawn();
|
||||
var comp = entMan.MetaQuery.Comp(uid);
|
||||
var bus = entMan.EventBusInternal;
|
||||
bus.ClearSubscriptions();
|
||||
return (bus, sim, uid, comp);
|
||||
}
|
||||
|
||||
private static EntityEventBus BusFactory()
|
||||
{
|
||||
return EntFactory().Bus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to subscribe a null handler causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_NullHandler_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
void Code() => bus.SubscribeEvent(EventSource.Local, subscriber, (EntityEventHandler<TestEventArgs>) null!);
|
||||
|
||||
//Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to subscribe with a null subscriber causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.SubscribeEvent<TestEventArgs>(EventSource.Local, null!, ev => {});
|
||||
|
||||
//Assert: this should do nothing
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate Event subscriptions are not allowed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_DuplicateSubscription_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delegateCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delegateCallCount++;
|
||||
|
||||
// 2 subscriptions 1 handler
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler));
|
||||
}
|
||||
|
||||
// TODO if ever duplicate events are allowed, re-enable these tests.
|
||||
/*
|
||||
/// <summary>
|
||||
/// Unlike C# events, the set of event handler delegates is unique.
|
||||
/// Subscribing the same delegate multiple times will only call the handler once.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_DuplicateSubscription_RaisedOnce()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delegateCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delegateCallCount++;
|
||||
|
||||
// 2 subscriptions 1 handler
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
//Assert
|
||||
Assert.That(delegateCallCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribing two different delegates to a single event type causes both events
|
||||
/// to be raised in an indeterminate order.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_MultipleDelegates_BothRaised()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delFooCount = 0;
|
||||
int delBarCount = 0;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, ev => delFooCount++);
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, ev => delBarCount++);
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delFooCount, Is.EqualTo(1));
|
||||
Assert.That(delBarCount, Is.EqualTo(1));
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// A subscriber's handlers are properly called only when the specified event type is raised.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_MultipleSubscriptions_IndividuallyCalled()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delFooCount = 0;
|
||||
int delBarCount = 0;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, ev => delFooCount++);
|
||||
bus.SubscribeEvent<TestEventTwoArgs>(EventSource.Local, subscriber, ev => delBarCount++);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act & Assert
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
Assert.That(delFooCount, Is.EqualTo(1));
|
||||
Assert.That(delBarCount, Is.EqualTo(0));
|
||||
|
||||
delFooCount = delBarCount = 0;
|
||||
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventTwoArgs());
|
||||
Assert.That(delFooCount, Is.EqualTo(0));
|
||||
Assert.That(delBarCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to subscribe with <see cref="EventSource.None"/> makes no sense and causes
|
||||
/// a <see cref="ArgumentOutOfRangeException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SubscribeEvent_SourceNone_ArgOutOfRange()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
void TestEventHandler(TestEventArgs args) { }
|
||||
|
||||
// Act
|
||||
void Code() => bus.SubscribeEvent(EventSource.None, subscriber, (EntityEventHandler<TestEventArgs>)TestEventHandler);
|
||||
|
||||
//Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a handler twice does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_DoubleUnsubscribe_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
void Handler(TestEventArgs ev) { }
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
bus.UnsubscribeEvent<TestEventArgs>(EventSource.Local, subscriber);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvent<TestEventArgs>(EventSource.Local, subscriber);
|
||||
|
||||
// Assert: Does not throw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a handler that was never subscribed in the first place does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_NoSubscription_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvent<TestEventArgs>(EventSource.Local, subscriber);
|
||||
|
||||
// Assert: Does not throw
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to unsubscribe with a null subscriber causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.UnsubscribeEvent<TestEventArgs>(EventSource.Local, null!);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event cannot be subscribed to with <see cref="EventSource.None"/>, so trying to unsubscribe
|
||||
/// with an <see cref="EventSource.None"/> causes a <see cref="ArgumentOutOfRangeException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvent_SourceNone_ArgOutOfRange()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
void Code() => bus.UnsubscribeEvent<TestEventArgs>(EventSource.None, subscriber);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raising an event with no handlers subscribed to it does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RaiseEvent_NoSubscriptions_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCalledCount = 0;
|
||||
bus.SubscribeEvent<TestEventTwoArgs>(EventSource.Local, subscriber, ev => delCalledCount++);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCalledCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raising an event when a handler has been unsubscribed no longer calls the handler.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RaiseEvent_Unsubscribed_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delCallCount++;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
bus.UnsubscribeEvent<TestEventArgs>(EventSource.Local, subscriber);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to raise an event with <see cref="EventSource.None"/> makes no sense and causes
|
||||
/// a <see cref="ArgumentOutOfRangeException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RaiseEvent_SourceNone_ArgOutOfRange()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.RaiseEvent(EventSource.None, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to unsubscribe all of a null subscriber's events causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvents_NullSubscriber_NullArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.UnsubscribeEvents(null!);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribing a subscriber with no subscriptions does nothing.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvents_NoSubscriptions_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
// Act
|
||||
bus.UnsubscribeEvents(subscriber);
|
||||
|
||||
// Assert: no exception
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The subscriber's handlers are not raised after they are unsubscribed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnsubscribeEvents_UnsubscribedHandler_Nop()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delCallCount++;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
bus.UnsubscribeEvents(subscriber);
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to queue a null event causes a <see cref="ArgumentNullException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueueEvent_NullEvent_ArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.QueueEvent(EventSource.Local, null!);
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queuing an event does not immediately raise the event unless the queue is processed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueueEvent_EventQueued_DoesNotImmediatelyRaise()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delCallCount++;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
|
||||
// Act
|
||||
bus.QueueEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trying to queue an event with <see cref="EventSource.None"/> makes no sense and causes
|
||||
/// a <see cref="ArgumentOutOfRangeException"/> to be thrown.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueueEvent_SourceNone_ArgOutOfRange()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Act
|
||||
void Code() => bus.QueueEvent(EventSource.None, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentOutOfRangeException>(Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queued events are raised when the queue is processed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ProcessQueue_EventQueued_HandlerRaised()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
var subscriber = new TestEventSubscriber();
|
||||
|
||||
int delCallCount = 0;
|
||||
void Handler(TestEventArgs ev) => delCallCount++;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, subscriber, Handler);
|
||||
bus.LockSubscriptions();
|
||||
bus.QueueEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Act
|
||||
bus.ProcessEventQueue();
|
||||
|
||||
// Assert
|
||||
Assert.That(delCallCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RaiseEvent_Ordered()
|
||||
{
|
||||
// Arrange
|
||||
var bus = BusFactory();
|
||||
|
||||
// Expected order is A -> C -> B
|
||||
var a = false;
|
||||
var b = false;
|
||||
var c = false;
|
||||
|
||||
void HandlerA(TestEventArgs ev)
|
||||
{
|
||||
Assert.That(b, Is.False, "A should run before B");
|
||||
Assert.That(c, Is.False, "A should run before C");
|
||||
|
||||
a = true;
|
||||
}
|
||||
|
||||
void HandlerB(TestEventArgs ev)
|
||||
{
|
||||
Assert.That(c, Is.True, "B should run after C");
|
||||
b = true;
|
||||
}
|
||||
|
||||
void HandlerC(TestEventArgs ev) => c = true;
|
||||
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, new SubA(), HandlerA, typeof(SubA), before: new []{typeof(SubB), typeof(SubC)});
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, new SubB(), HandlerB, typeof(SubB), after: new []{typeof(SubC)});
|
||||
bus.SubscribeEvent<TestEventArgs>(EventSource.Local, new SubC(), HandlerC, typeof(SubC));
|
||||
bus.LockSubscriptions();
|
||||
|
||||
// Act
|
||||
bus.RaiseEvent(EventSource.Local, new TestEventArgs());
|
||||
|
||||
// Assert
|
||||
Assert.That(a, Is.True, "A did not fire");
|
||||
Assert.That(b, Is.True, "B did not fire");
|
||||
Assert.That(c, Is.True, "C did not fire");
|
||||
}
|
||||
|
||||
internal sealed class SubA : IEntityEventSubscriber
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class SubB : IEntityEventSubscriber
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class SubC : IEntityEventSubscriber
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TestEventSubscriber : IEntityEventSubscriber { }
|
||||
|
||||
internal sealed class TestEventArgs : EntityEventArgs { }
|
||||
internal sealed class TestEventTwoArgs : EntityEventArgs { }
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects;
|
||||
|
||||
[TestFixture]
|
||||
internal sealed partial class EntityManagerCopyTests
|
||||
{
|
||||
[Test]
|
||||
public void CopyComponentGeneric()
|
||||
{
|
||||
var instant = RobustServerSimulation.NewSimulation();
|
||||
instant.RegisterComponents(fac =>
|
||||
{
|
||||
fac.RegisterClass<AComponent>();
|
||||
});
|
||||
|
||||
var sim = instant.InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
|
||||
var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var comp = entManager.AddComponent<AComponent>(original);
|
||||
|
||||
Assert.That(comp.Value, Is.EqualTo(false));
|
||||
comp.Value = true;
|
||||
|
||||
var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
Assert.That(!entManager.HasComponent<AComponent>(target));
|
||||
|
||||
var targetComp = entManager.CopyComponent(original, target, comp);
|
||||
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyComponentNonGeneric()
|
||||
{
|
||||
var instant = RobustServerSimulation.NewSimulation();
|
||||
instant.RegisterComponents(fac =>
|
||||
{
|
||||
fac.RegisterClass<AComponent>();
|
||||
});
|
||||
|
||||
var sim = instant.InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
|
||||
var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var comp = entManager.AddComponent<AComponent>(original);
|
||||
|
||||
Assert.That(comp.Value, Is.EqualTo(false));
|
||||
comp.Value = true;
|
||||
|
||||
var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
Assert.That(!entManager.HasComponent<AComponent>(target));
|
||||
|
||||
var targetComp = entManager.CopyComponent(original, target, (IComponent) comp);
|
||||
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(((AComponent) targetComp).Value, Is.EqualTo(comp.Value));
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyComponentMultiple()
|
||||
{
|
||||
var instant = RobustServerSimulation.NewSimulation();
|
||||
instant.RegisterComponents(fac =>
|
||||
{
|
||||
fac.RegisterClass<AComponent>();
|
||||
fac.RegisterClass<BComponent>();
|
||||
});
|
||||
|
||||
var sim = instant.InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
|
||||
var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var comp = entManager.AddComponent<AComponent>(original);
|
||||
var comp2 = entManager.AddComponent<BComponent>(original);
|
||||
|
||||
Assert.That(comp.Value, Is.EqualTo(false));
|
||||
comp.Value = true;
|
||||
|
||||
var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
Assert.That(!entManager.HasComponent<AComponent>(target));
|
||||
|
||||
entManager.CopyComponents(original, target, null, comp, comp2);
|
||||
var targetComp = entManager.GetComponent<AComponent>(target);
|
||||
var targetComp2 = entManager.GetComponent<BComponent>(target);
|
||||
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
|
||||
Assert.That(entManager.GetComponent<BComponent>(target), Is.EqualTo(targetComp2));
|
||||
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
|
||||
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
Assert.That(!ReferenceEquals(comp2, targetComp2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyComponentMultipleViaTry()
|
||||
{
|
||||
var instant = RobustServerSimulation.NewSimulation();
|
||||
instant.RegisterComponents(fac =>
|
||||
{
|
||||
fac.RegisterClass<AComponent>();
|
||||
fac.RegisterClass<BComponent>();
|
||||
});
|
||||
|
||||
var sim = instant.InitializeInstance();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
|
||||
var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var comp = entManager.AddComponent<AComponent>(original);
|
||||
var comp2 = entManager.AddComponent<BComponent>(original);
|
||||
|
||||
Assert.That(comp.Value, Is.EqualTo(false));
|
||||
comp.Value = true;
|
||||
|
||||
var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
Assert.That(!entManager.HasComponent<AComponent>(target));
|
||||
|
||||
entManager.TryCopyComponents(original, target, null, comp.GetType(), comp2.GetType());
|
||||
var targetComp = entManager.GetComponent<AComponent>(target);
|
||||
var targetComp2 = entManager.GetComponent<BComponent>(target);
|
||||
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
|
||||
Assert.That(entManager.GetComponent<BComponent>(target), Is.EqualTo(targetComp2));
|
||||
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
|
||||
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
Assert.That(!ReferenceEquals(comp2, targetComp2));
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
private sealed partial class AComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Value = false;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
private sealed partial class BComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Value = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture, Parallelizable ,TestOf(typeof(EntityManager))]
|
||||
internal sealed partial class EntityManager_Components_Tests
|
||||
{
|
||||
private const string DummyLoadId = "DummyLoad";
|
||||
private const string DummyLoad = $@"
|
||||
- type: entity
|
||||
id: {DummyLoadId}
|
||||
name: weh
|
||||
components:
|
||||
- type: Joint
|
||||
- type: Physics
|
||||
";
|
||||
|
||||
[Test]
|
||||
public void AddRegistryComponentTest()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(fac => fac.LoadString(DummyLoad))
|
||||
.InitializeInstance();
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var protoManager = sim.Resolve<IPrototypeManager>();
|
||||
|
||||
var map = sim.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
Assert.That(!entMan.HasComponent<PhysicsComponent>(entity));
|
||||
var proto = protoManager.Index<EntityPrototype>(DummyLoadId);
|
||||
|
||||
entMan.AddComponents(entity, proto);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(entMan.HasComponent<JointComponent>(entity));
|
||||
Assert.That(entMan.HasComponent<PhysicsComponent>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveRegistryComponentTest()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterPrototypes(fac => fac.LoadString(DummyLoad))
|
||||
.InitializeInstance();
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var protoManager = sim.Resolve<IPrototypeManager>();
|
||||
|
||||
var map = sim.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
var entity = entMan.SpawnEntity(DummyLoadId, coords);
|
||||
var proto = protoManager.Index<EntityPrototype>(DummyLoadId);
|
||||
|
||||
entMan.RemoveComponents(entity, proto);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(!entMan.HasComponent<JointComponent>(entity));
|
||||
Assert.That(!entMan.HasComponent<PhysicsComponent>(entity));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = new DummyComponent();
|
||||
|
||||
// Act
|
||||
entMan.AddComponent(entity, component);
|
||||
|
||||
// Assert
|
||||
var result = entMan.GetComponent<DummyComponent>(entity);
|
||||
Assert.That(result, Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddComponentOverwriteTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = new DummyComponent();
|
||||
|
||||
// Act
|
||||
entMan.AddComponent(entity, component, true);
|
||||
|
||||
// Assert
|
||||
var result = entMan.GetComponent<DummyComponent>(entity);
|
||||
Assert.That(result, Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddComponent_ExistingDeleted()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var firstComp = new DummyComponent();
|
||||
entMan.AddComponent(entity, firstComp);
|
||||
entMan.RemoveComponent<DummyComponent>(entity);
|
||||
var secondComp = new DummyComponent();
|
||||
|
||||
// Act
|
||||
entMan.AddComponent(entity, secondComp);
|
||||
|
||||
// Assert
|
||||
var result = entMan.GetComponent<DummyComponent>(entity);
|
||||
Assert.That(result, Is.EqualTo(secondComp));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HasComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.HasComponent<DummyComponent>(entity);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HasComponentNoGenericTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.HasComponent(entity, typeof(DummyComponent));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HasNetComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
|
||||
var factory = sim.Resolve<IComponentFactory>();
|
||||
var netId = factory.GetRegistration<DummyComponent>().NetID!;
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.HasComponent(entity, netId.Value);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetNetComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
|
||||
var factory = sim.Resolve<IComponentFactory>();
|
||||
var netId = factory.GetRegistration<DummyComponent>().NetID!;
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.GetComponent(entity, netId.Value);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.TryGetComponent<DummyComponent>(entity, out var comp);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(comp, Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetNetComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
|
||||
var factory = sim.Resolve<IComponentFactory>();
|
||||
var netId = factory.GetRegistration<DummyComponent>().NetID!;
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.TryGetComponent(entity, netId.Value, out var comp);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(comp, Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
entMan.RemoveComponent<DummyComponent>(entity);
|
||||
entMan.CullRemovedComponents();
|
||||
|
||||
// Assert
|
||||
Assert.That(entMan.HasComponent(entity, component.GetType()), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EnsureQueuedComponentDeletion()
|
||||
{
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
Assert.That(component.LifeStage, Is.LessThanOrEqualTo(ComponentLifeStage.Running));
|
||||
entMan.RemoveComponentDeferred(entity, component);
|
||||
Assert.That(component.LifeStage, Is.EqualTo(ComponentLifeStage.Stopped));
|
||||
|
||||
Assert.That(entMan.EnsureComponent<DummyComponent>(entity, out var comp2), Is.False);
|
||||
Assert.That(comp2.LifeStage, Is.LessThanOrEqualTo(ComponentLifeStage.Running));
|
||||
Assert.That(component.LifeStage, Is.EqualTo(ComponentLifeStage.Deleted));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveNetComponentTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
|
||||
var factory = sim.Resolve<IComponentFactory>();
|
||||
var netId = factory.GetRegistration<DummyComponent>().NetID!;
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
entMan.RemoveComponent(entity, netId.Value);
|
||||
entMan.CullRemovedComponents();
|
||||
|
||||
// Assert
|
||||
Assert.That(entMan.HasComponent(entity, component.GetType()), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetComponentsTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.GetComponents<DummyComponent>(entity);
|
||||
|
||||
// Assert
|
||||
var list = result.ToList();
|
||||
Assert.That(list.Count, Is.EqualTo(1));
|
||||
Assert.That(list[0], Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllComponentsTest()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.EntityQuery<DummyComponent>(true);
|
||||
|
||||
// Assert
|
||||
var list = result.ToList();
|
||||
Assert.That(list.Count, Is.EqualTo(1));
|
||||
Assert.That(list[0], Is.EqualTo(component));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllComponentInstances()
|
||||
{
|
||||
// Arrange
|
||||
var (sim, coords) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var fac = sim.Resolve<IComponentFactory>();
|
||||
var entity = entMan.SpawnEntity(null, coords);
|
||||
var component = entMan.AddComponent<DummyComponent>(entity);
|
||||
|
||||
// Act
|
||||
var result = entMan.GetComponents(entity);
|
||||
|
||||
// Assert
|
||||
var list = result.Where(c=>fac.GetComponentName(c.GetType()) == "Dummy").ToList();
|
||||
Assert.That(list.Count, Is.EqualTo(1));
|
||||
Assert.That(list[0], Is.EqualTo(component));
|
||||
}
|
||||
|
||||
private static (ISimulation, EntityCoordinates) SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(factory => factory.RegisterClass<DummyComponent>())
|
||||
.InitializeInstance();
|
||||
|
||||
var map = sim.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
return (sim, coords);
|
||||
}
|
||||
|
||||
[NetworkedComponent()]
|
||||
private sealed partial class DummyComponent : Component, ICompType1, ICompType2
|
||||
{
|
||||
}
|
||||
|
||||
private interface ICompType1 { }
|
||||
|
||||
private interface ICompType2 { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Configuration;
|
||||
using Robust.Server.Reflection;
|
||||
using Robust.Server.Serialization;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture, Serializable]
|
||||
sealed class EntityState_Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to measure the size of <see cref="object"/>s in bytes. This is not actually a test,
|
||||
/// but a useful benchmark tool, so i'm leaving it here.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ComponentChangedSerialized()
|
||||
{
|
||||
var container = new DependencyCollection();
|
||||
container.Register<ILogManager, LogManager>();
|
||||
container.Register<IConfigurationManager, ServerNetConfigurationManager>();
|
||||
container.Register<IConfigurationManagerInternal, ServerNetConfigurationManager>();
|
||||
container.Register<INetManager, NetManager>();
|
||||
container.Register<IHWId, DummyHWId>();
|
||||
container.Register<IReflectionManager, ServerReflectionManager>();
|
||||
container.Register<IRobustSerializer, ServerRobustSerializer>();
|
||||
container.Register<IRobustMappedStringSerializer, RobustMappedStringSerializer>();
|
||||
container.Register<IAuthManager, AuthManager>();
|
||||
container.Register<IGameTiming, GameTiming>();
|
||||
container.Register<ProfManager, ProfManager>();
|
||||
container.Register<HttpClientHolder>();
|
||||
container.RegisterInstance<IReplayRecordingManager>(new Mock<IReplayRecordingManager>().Object);
|
||||
container.BuildGraph();
|
||||
|
||||
var cfg = container.Resolve<IConfigurationManagerInternal>();
|
||||
cfg.Initialize(true);
|
||||
cfg.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly);
|
||||
|
||||
container.Resolve<IReflectionManager>().LoadAssemblies(AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"));
|
||||
|
||||
IoCManager.InitThread(container, replaceExisting: true);
|
||||
|
||||
cfg.LoadCVarsFromAssembly(typeof(IConfigurationManager).Assembly); // Robust.Shared
|
||||
|
||||
container.Resolve<INetManager>().Initialize(true);
|
||||
|
||||
var serializer = container.Resolve<IRobustSerializer>();
|
||||
serializer.Initialize();
|
||||
IoCManager.Resolve<IRobustMappedStringSerializer>().LockStrings();
|
||||
|
||||
byte[] array;
|
||||
using(var stream = new MemoryStream())
|
||||
{
|
||||
var payload = new EntityState(
|
||||
new NetEntity(64),
|
||||
new []
|
||||
{
|
||||
new ComponentChange(0, new MapGridComponentDeltaState(16, chunkData: null, default), default)
|
||||
}, default);
|
||||
|
||||
serializer.Serialize(stream, payload);
|
||||
array = stream.ToArray();
|
||||
}
|
||||
|
||||
IoCManager.Clear();
|
||||
|
||||
Assert.Pass($"Size in Bytes: {array.Length.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Configuration;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(EntitySystemManager))]
|
||||
internal sealed class EntitySystemManagerOrderTest
|
||||
{
|
||||
private sealed class Counter
|
||||
{
|
||||
public int X;
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private abstract class TestSystemBase : IEntitySystem
|
||||
{
|
||||
public Counter? Counter;
|
||||
public int LastUpdate;
|
||||
|
||||
public virtual IEnumerable<Type> UpdatesAfter => Enumerable.Empty<Type>();
|
||||
public virtual IEnumerable<Type> UpdatesBefore => Enumerable.Empty<Type>();
|
||||
public bool UpdatesOutsidePrediction => true;
|
||||
public void Initialize() { }
|
||||
public void Shutdown() { }
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
LastUpdate = Counter!.X++;
|
||||
}
|
||||
public void FrameUpdate(float frameTime) { }
|
||||
}
|
||||
|
||||
// Expected update order is is A -> D -> C -> B
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class TestSystemA : TestSystemBase
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class TestSystemB : TestSystemBase
|
||||
{
|
||||
public override IEnumerable<Type> UpdatesAfter => new[] {typeof(TestSystemA)};
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class TestSystemC : TestSystemBase
|
||||
{
|
||||
public override IEnumerable<Type> UpdatesBefore => new[] {typeof(TestSystemB)};
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class TestSystemD : TestSystemBase
|
||||
{
|
||||
public override IEnumerable<Type> UpdatesAfter => new[] {typeof(TestSystemA)};
|
||||
public override IEnumerable<Type> UpdatesBefore => new[] {typeof(TestSystemC)};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
var deps = new DependencyCollection();
|
||||
deps.Register<IRuntimeLog, RuntimeLog>();
|
||||
deps.Register<ILogManager, LogManager>();
|
||||
deps.Register<IGameTiming, GameTiming>();
|
||||
deps.RegisterInstance<INetManager>(new Mock<INetManager>().Object);
|
||||
deps.Register<IConfigurationManager, ServerNetConfigurationManager>();
|
||||
deps.Register<IServerNetConfigurationManager, ServerNetConfigurationManager>();
|
||||
deps.Register<ProfManager, ProfManager>();
|
||||
deps.Register<IDynamicTypeFactory, DynamicTypeFactory>();
|
||||
deps.Register<IDynamicTypeFactoryInternal, DynamicTypeFactory>();
|
||||
deps.RegisterInstance<IModLoader>(new Mock<IModLoader>().Object);
|
||||
deps.Register<IEntitySystemManager, EntitySystemManager>();
|
||||
deps.RegisterInstance<IEntityManager>(new Mock<IEntityManager>().Object);
|
||||
// WHEN WILL THE SUFFERING END
|
||||
deps.RegisterInstance<IReplayRecordingManager>(new Mock<IReplayRecordingManager>().Object);
|
||||
|
||||
var reflectionMock = new Mock<IReflectionManager>();
|
||||
reflectionMock.Setup(p => p.GetAllChildren<IEntitySystem>(false))
|
||||
.Returns(new[]
|
||||
{
|
||||
typeof(TestSystemA),
|
||||
typeof(TestSystemB),
|
||||
typeof(TestSystemC),
|
||||
typeof(TestSystemD),
|
||||
});
|
||||
|
||||
deps.RegisterInstance<IReflectionManager>(reflectionMock.Object);
|
||||
|
||||
deps.BuildGraph();
|
||||
|
||||
IoCManager.InitThread(deps, true);
|
||||
|
||||
var systems = deps.Resolve<IEntitySystemManager>();
|
||||
systems.Initialize();
|
||||
|
||||
var counter = new Counter();
|
||||
|
||||
systems.GetEntitySystem<TestSystemA>().Counter = counter;
|
||||
systems.GetEntitySystem<TestSystemB>().Counter = counter;
|
||||
systems.GetEntitySystem<TestSystemC>().Counter = counter;
|
||||
systems.GetEntitySystem<TestSystemD>().Counter = counter;
|
||||
|
||||
systems.TickUpdate(1, noPredictions: false);
|
||||
|
||||
Assert.That(counter.X, Is.EqualTo(4));
|
||||
|
||||
Assert.That(systems.GetEntitySystem<TestSystemA>().LastUpdate, Is.EqualTo(0));
|
||||
Assert.That(systems.GetEntitySystem<TestSystemB>().LastUpdate, Is.EqualTo(3));
|
||||
Assert.That(systems.GetEntitySystem<TestSystemC>().LastUpdate, Is.EqualTo(2));
|
||||
Assert.That(systems.GetEntitySystem<TestSystemD>().LastUpdate, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
IoCManager.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture, TestOf(typeof(EntitySystemManager))]
|
||||
internal sealed class EntitySystemManager_Tests: OurRobustUnitTest
|
||||
{
|
||||
|
||||
public abstract class ESystemBase : IEntitySystem
|
||||
{
|
||||
public virtual IEnumerable<Type> UpdatesAfter => Enumerable.Empty<Type>();
|
||||
public virtual IEnumerable<Type> UpdatesBefore => Enumerable.Empty<Type>();
|
||||
public bool UpdatesOutsidePrediction => true;
|
||||
public void Initialize() { }
|
||||
public void Shutdown() { }
|
||||
public void Update(float frameTime) { }
|
||||
public void FrameUpdate(float frameTime) { }
|
||||
}
|
||||
[Virtual]
|
||||
public class ESystemA : ESystemBase { }
|
||||
internal sealed class ESystemC : ESystemA { }
|
||||
public abstract class ESystemBase2 : ESystemBase { }
|
||||
internal sealed class ESystemB : ESystemBase2 { }
|
||||
|
||||
internal sealed class ESystemDepA : ESystemBase
|
||||
{
|
||||
[Dependency] public readonly ESystemDepB ESystemDepB = default!;
|
||||
}
|
||||
|
||||
internal sealed class ESystemDepB : ESystemBase
|
||||
{
|
||||
[Dependency] public readonly ESystemDepA ESystemDepA = default!;
|
||||
}
|
||||
|
||||
/*
|
||||
ESystemBase (Abstract)
|
||||
- ESystemA
|
||||
- ESystemC
|
||||
- EsystemBase2 (Abstract)
|
||||
- ESystemB
|
||||
|
||||
*/
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var syssy = IoCManager.Resolve<IEntitySystemManager>();
|
||||
syssy.Clear();
|
||||
syssy.LoadExtraSystemType<ESystemA>();
|
||||
syssy.LoadExtraSystemType<ESystemB>();
|
||||
syssy.LoadExtraSystemType<ESystemC>();
|
||||
syssy.LoadExtraSystemType<ESystemDepA>();
|
||||
syssy.LoadExtraSystemType<ESystemDepB>();
|
||||
syssy.Initialize(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetsByTypeOrSupertype()
|
||||
{
|
||||
var esm = IoCManager.Resolve<IEntitySystemManager>();
|
||||
|
||||
// getting type by the exact type should work fine
|
||||
Assert.That(esm.GetEntitySystem<ESystemB>(), Is.TypeOf<ESystemB>());
|
||||
|
||||
// getting type by an abstract supertype should work fine
|
||||
// because there are no other subtypes of that supertype it would conflict with
|
||||
// it should return the only concrete subtype
|
||||
Assert.That(esm.GetEntitySystem<ESystemBase2>(), Is.TypeOf<ESystemB>());
|
||||
|
||||
// getting ESystemA type by its exact type should work fine,
|
||||
// even though EsystemC is a subtype - it should return an instance of ESystemA
|
||||
var esysA = esm.GetEntitySystem<ESystemA>();
|
||||
Assert.That(esysA, Is.TypeOf<ESystemA>());
|
||||
Assert.That(esysA, Is.Not.TypeOf<ESystemC>());
|
||||
|
||||
var esysC = esm.GetEntitySystem<ESystemC>();
|
||||
Assert.That(esysC, Is.TypeOf<ESystemC>());
|
||||
|
||||
// this should not work - it's abstract and there are multiple
|
||||
// concrete subtypes
|
||||
Assert.Throws<UnregisteredTypeException>(() =>
|
||||
{
|
||||
esm.GetEntitySystem<ESystemBase>();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DependencyTest()
|
||||
{
|
||||
var esm = IoCManager.Resolve<IEntitySystemManager>();
|
||||
|
||||
var sysA = esm.GetEntitySystem<ESystemDepA>();
|
||||
var sysB = esm.GetEntitySystem<ESystemDepB>();
|
||||
|
||||
Assert.That(sysA.ESystemDepB, Is.EqualTo(sysB));
|
||||
Assert.That(sysB.ESystemDepA, Is.EqualTo(sysA));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
226
Robust.Shared.IntegrationTests/GameObjects/GenericEntityPrint.cs
Normal file
226
Robust.Shared.IntegrationTests/GameObjects/GenericEntityPrint.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects;
|
||||
|
||||
internal sealed class GenericEntityPrint
|
||||
{
|
||||
//[Test]
|
||||
public void Print()
|
||||
{
|
||||
// Using the test framework for things it was not meant for is my passion
|
||||
//
|
||||
// Its pretty fucked that just occasionally running this manually is so much easier than setting up a porper
|
||||
// source generator.
|
||||
var i = 8;
|
||||
|
||||
IEnumerable<string> Generics(int n, bool nullable, bool forceIncludeNumber = false)
|
||||
{
|
||||
for (var j = 1; j <= n; j++)
|
||||
{
|
||||
var jStr = n == 1 && !forceIncludeNumber ? string.Empty : j.ToString();
|
||||
yield return $"T{jStr}{(nullable ? "?" : string.Empty)}";
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<string> PartiallyNullableGenerics(int n, int notNullCount)
|
||||
{
|
||||
bool nullable;
|
||||
for (var j = 1; j <= n; j++)
|
||||
{
|
||||
nullable = j > notNullCount;
|
||||
var jStr = n == 1 ? string.Empty : j.ToString();
|
||||
yield return $"T{jStr}{(nullable ? "?" : string.Empty)}";
|
||||
}
|
||||
}
|
||||
|
||||
var structs = new StringBuilder();
|
||||
var constraints = new StringBuilder();
|
||||
var fields = new StringBuilder();
|
||||
var parameters = new StringBuilder();
|
||||
var asserts = new StringBuilder();
|
||||
var assignments = new StringBuilder();
|
||||
var tupleParameters = new StringBuilder();
|
||||
var tupleAccess = new StringBuilder();
|
||||
var entityAccess = new StringBuilder();
|
||||
var selfAccess = new StringBuilder();
|
||||
var entityNumberedAccess = new StringBuilder();
|
||||
var defaults = new StringBuilder();
|
||||
var compOperators = new StringBuilder();
|
||||
var deConstructorParameters = new StringBuilder();
|
||||
var deConstructorAccess = new StringBuilder();
|
||||
var partialTupleCasts = new StringBuilder();
|
||||
var partialEntityCasts = new StringBuilder();
|
||||
var entitySubCast = new StringBuilder();
|
||||
var castRegion = new StringBuilder();
|
||||
|
||||
for (var j = 1; j <= i; j++)
|
||||
{
|
||||
constraints.Clear();
|
||||
fields.Clear();
|
||||
parameters.Clear();
|
||||
asserts.Clear();
|
||||
assignments.Clear();
|
||||
tupleParameters.Clear();
|
||||
tupleAccess.Clear();
|
||||
entityAccess.Clear();
|
||||
selfAccess.Clear();
|
||||
entityNumberedAccess.Clear();
|
||||
defaults.Clear();
|
||||
compOperators.Clear();
|
||||
deConstructorParameters.Clear();
|
||||
deConstructorAccess.Clear();
|
||||
partialTupleCasts.Clear();
|
||||
partialEntityCasts.Clear();
|
||||
entitySubCast.Clear();
|
||||
castRegion.Clear();
|
||||
|
||||
var generics = string.Join(", ", Generics(j, false));
|
||||
var nullableGenerics = string.Join(", ", Generics(j, true));
|
||||
|
||||
for (var k = 1; k <= j; k++)
|
||||
{
|
||||
var kStr = j == 1 ? string.Empty : k.ToString();
|
||||
fields.AppendLine($" public T{kStr} Comp{kStr};");
|
||||
constraints.Append($"where T{kStr} : IComponent? ");
|
||||
parameters.Append($", T{kStr} comp{kStr}");
|
||||
asserts.AppendLine($" DebugTools.AssertOwner(owner, comp{kStr});");
|
||||
assignments.AppendLine($" Comp{kStr} = comp{kStr};");
|
||||
tupleParameters.Append($", T{kStr} Comp{kStr}");
|
||||
tupleAccess.Append($", tuple.Comp{kStr}");
|
||||
var suffix = (j >= 2 && k == 1) ? string.Empty : kStr;
|
||||
var prefix = (j >= 2 && k == 2) ? "1" : string.Empty;
|
||||
entityAccess.Append($"{prefix}, ent.Comp{suffix}");
|
||||
selfAccess.Append($"{prefix}, Comp{suffix}");
|
||||
entityNumberedAccess.Append($", ent.Comp{kStr}");
|
||||
defaults.Append(", default");
|
||||
compOperators.AppendLine($$"""
|
||||
public static implicit operator T{{kStr}}(Entity<{{generics}}> ent)
|
||||
{
|
||||
return ent.Comp{{kStr}};
|
||||
}
|
||||
|
||||
""");
|
||||
deConstructorParameters.Append($", out T{kStr} comp{kStr}");
|
||||
deConstructorAccess.AppendLine($" comp{kStr} = Comp{kStr};");
|
||||
|
||||
if (k == j)
|
||||
continue;
|
||||
|
||||
// Cast a (EntityUid, T1) tuple to an Entity<T1, T2?>
|
||||
// We could also casts for going from a (Uid, T2) tuple to a Entity<T1?, T2> but once we get to 4 or
|
||||
// more components there are just too many combinations and I CBF writing the code to generate all those.
|
||||
var partiallyNullableGenerics = string.Join(", ", PartiallyNullableGenerics(j, k));
|
||||
var defaultArgs = string.Concat(Enumerable.Repeat(", default", j-k));
|
||||
partialTupleCasts.Append($$"""
|
||||
|
||||
public static implicit operator Entity<{{partiallyNullableGenerics}}>((EntityUid Owner{{tupleParameters}}) tuple)
|
||||
{
|
||||
return new Entity<{{partiallyNullableGenerics}}>(tuple.Owner{{tupleAccess}}{{defaultArgs}});
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
// Cast an Entity<T1> to an Entity<T1, T2?>
|
||||
// As with the tuple casts, we could in principle generate more here.
|
||||
var subGenerics = string.Join(", ", Generics(k, false, true));
|
||||
partialEntityCasts.Append($$"""
|
||||
|
||||
public static implicit operator Entity<{{partiallyNullableGenerics}}>(Entity<{{subGenerics}}> ent)
|
||||
{
|
||||
return new Entity<{{partiallyNullableGenerics}}>(ent.Owner{{entityAccess}}{{defaultArgs}});
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
// Cast an Entity<T1, T2> to an Entity<T1/2>
|
||||
entitySubCast.Append($$"""
|
||||
|
||||
public static implicit operator Entity<{{subGenerics}}>(Entity<{{generics}}> ent)
|
||||
{
|
||||
return new Entity<{{subGenerics}}>(ent.Owner{{entityNumberedAccess}});
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
if (j == 2)
|
||||
{
|
||||
castRegion.Append($$"""
|
||||
{{partialTupleCasts.ToString().TrimEnd()}}
|
||||
{{partialEntityCasts.ToString().TrimEnd()}}
|
||||
{{entitySubCast.ToString().TrimEnd()}}
|
||||
""");
|
||||
}
|
||||
else if (j > 2)
|
||||
{
|
||||
castRegion.Append($$"""
|
||||
|
||||
#region Partial Tuple Casts
|
||||
{{partialTupleCasts}}
|
||||
#endregion
|
||||
|
||||
#region Partial Entity Casts
|
||||
{{partialEntityCasts}}
|
||||
#endregion
|
||||
|
||||
#region Entity Sub casts
|
||||
{{entitySubCast}}
|
||||
#endregion
|
||||
""");
|
||||
}
|
||||
|
||||
structs.Append($$"""
|
||||
[NotYamlSerializable]
|
||||
public record struct Entity<{{generics}}> : IFluentEntityUid, IAsType<EntityUid>
|
||||
{{constraints.ToString().TrimEnd()}}
|
||||
{
|
||||
public EntityUid Owner;
|
||||
{{fields.ToString().TrimEnd()}}
|
||||
readonly EntityUid IFluentEntityUid.FluentOwner => Owner;
|
||||
|
||||
public Entity(EntityUid owner{{parameters}})
|
||||
{
|
||||
{{asserts}}
|
||||
Owner = owner;
|
||||
{{assignments.ToString().TrimEnd()}}
|
||||
}
|
||||
|
||||
public static implicit operator Entity<{{generics}}>((EntityUid Owner{{tupleParameters}}) tuple)
|
||||
{
|
||||
return new Entity<{{generics}}>(tuple.Owner{{tupleAccess}});
|
||||
}
|
||||
|
||||
public static implicit operator Entity<{{nullableGenerics}}>(EntityUid owner)
|
||||
{
|
||||
return new Entity<{{nullableGenerics}}>(owner{{defaults}});
|
||||
}
|
||||
|
||||
public static implicit operator EntityUid(Entity<{{generics}}> ent)
|
||||
{
|
||||
return ent.Owner;
|
||||
}
|
||||
|
||||
{{compOperators.ToString().TrimEnd()}}
|
||||
|
||||
public readonly void Deconstruct(out EntityUid owner{{deConstructorParameters}})
|
||||
{
|
||||
owner = Owner;
|
||||
{{deConstructorAccess.ToString().TrimEnd()}}
|
||||
}
|
||||
{{castRegion}}
|
||||
|
||||
public override readonly int GetHashCode() => Owner.GetHashCode();
|
||||
public readonly Entity<{{nullableGenerics}}> AsNullable() => new(Owner{{selfAccess}});
|
||||
public readonly EntityUid AsType() => Owner;
|
||||
}
|
||||
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
Console.WriteLine(structs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture, Parallelizable]
|
||||
sealed partial class EntityManagerTests
|
||||
{
|
||||
private static ISimulation SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The entity prototype can define field on the TransformComponent, just like any other component.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SpawnEntity_PrototypeTransform_Works()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var map = sim.CreateMap().MapId;
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var newEnt = entMan.SpawnEntity(null, new MapCoordinates(0, 0, map));
|
||||
Assert.That(newEnt, Is.Not.EqualTo(EntityUid.Invalid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComponentCount_Works()
|
||||
{
|
||||
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
|
||||
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
|
||||
|
||||
var mapId = sim.CreateMap().MapId;
|
||||
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(1));
|
||||
mapSystem.DeleteMap(mapId);
|
||||
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
{
|
||||
[TestFixture, Parallelizable]
|
||||
internal sealed partial class AnchoredSystemTests
|
||||
{
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
name: anchoredEnt
|
||||
id: anchoredEnt
|
||||
components:
|
||||
- type: Transform
|
||||
anchored: true";
|
||||
|
||||
private static (ISimulation, Entity<MapGridComponent> grid, MapCoordinates, SharedTransformSystem xformSys, SharedMapSystem mapSys) SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(f => f.LoadExtraSystemType<MoveEventTestSystem>())
|
||||
.RegisterPrototypes(f=>
|
||||
{
|
||||
f.LoadString(Prototypes);
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
var testMapId = sim.CreateMap().MapId;
|
||||
var coords = new MapCoordinates(new Vector2(7, 7), testMapId);
|
||||
// Add grid 1, as the default grid to anchor things to.
|
||||
var grid = mapManager.CreateGridEntity(testMapId);
|
||||
|
||||
return (sim, grid, coords, sim.System<SharedTransformSystem>(), sim.System<SharedMapSystem>());
|
||||
}
|
||||
|
||||
// An entity is anchored to the tile it is over on the target grid.
|
||||
// An entity is anchored by setting the flag on the transform.
|
||||
// An anchored entity is defined as an entity with the TransformComponent.Anchored flag set.
|
||||
// The Anchored field is used for serialization of anchored state.
|
||||
|
||||
// TODO: The grid SnapGrid functions are internal, expose the query functions to content.
|
||||
// PhysicsComponent.BodyType is not able to be changed by content.
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is anchored to a grid tile, it's world position is centered on the tile.
|
||||
/// Otherwise you can anchor an entity to a tile without the entity actually being on top of the tile.
|
||||
/// This movement will trigger a MoveEvent.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_WorldPosition_TileCenter()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
|
||||
|
||||
// Act
|
||||
sim.System<MoveEventTestSystem>().ResetCounters();
|
||||
xformSys.AnchorEntity(ent1);
|
||||
Assert.That(xformSys.GetWorldPosition(ent1), Is.EqualTo(new Vector2(7.5f, 7.5f))); // centered on tile
|
||||
sim.System<MoveEventTestSystem>().AssertMoved(false);
|
||||
}
|
||||
|
||||
[ComponentProtoName("AnchorOnInit")]
|
||||
[Reflect(false)]
|
||||
private sealed partial class AnchorOnInitComponent : Component;
|
||||
|
||||
[Reflect(false)]
|
||||
private sealed class AnchorOnInitTestSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AnchorOnInitComponent, ComponentInit>((e, _, _) => Transform(e).Anchored = true);
|
||||
}
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
internal sealed class MoveEventTestSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_transform.OnGlobalMoveEvent += OnMove;
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnReparent);
|
||||
}
|
||||
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_transform.OnGlobalMoveEvent -= OnMove;
|
||||
}
|
||||
|
||||
public bool FailOnMove;
|
||||
public int MoveCounter;
|
||||
public int ParentCounter;
|
||||
|
||||
private void OnMove(ref MoveEvent ev)
|
||||
{
|
||||
MoveCounter++;
|
||||
if (FailOnMove)
|
||||
Assert.Fail($"Move event was raised");
|
||||
}
|
||||
private void OnReparent(ref EntParentChangedMessage ev)
|
||||
{
|
||||
ParentCounter++;
|
||||
if (FailOnMove)
|
||||
Assert.Fail($"Move event was raised");
|
||||
}
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
ParentCounter = 0;
|
||||
MoveCounter = 0;
|
||||
}
|
||||
|
||||
public void AssertMoved(bool parentChanged = true)
|
||||
{
|
||||
if (parentChanged)
|
||||
Assert.That(ParentCounter, Is.EqualTo(1));
|
||||
Assert.That(MoveCounter, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that if an entity gets added to lookups when anchored during init by some system.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See space-wizards/RobustToolbox/issues/3444
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public void OnInitAnchored_AddedToLookup()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(f => f.LoadExtraSystemType<AnchorOnInitTestSystem>())
|
||||
.RegisterComponents(f => f.RegisterClass<AnchorOnInitComponent>())
|
||||
.InitializeInstance();
|
||||
|
||||
var mapSys = sim.System<SharedMapSystem>();
|
||||
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var mapMan = sim.Resolve<IMapManager>();
|
||||
var mapId = sim.CreateMap().MapId;
|
||||
var grid = mapMan.CreateGridEntity(mapId);
|
||||
var coordinates = new MapCoordinates(new Vector2(7, 7), mapId);
|
||||
var pos = mapSys.TileIndicesFor(grid, coordinates);
|
||||
mapSys.SetTile(grid, pos, new Tile(1));
|
||||
|
||||
var ent1 = entMan.SpawnEntity(null, coordinates);
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
Assert.That(!mapSys.GetAnchoredEntities(grid, pos).Any());
|
||||
entMan.DeleteEntity(ent1);
|
||||
|
||||
var ent2 = entMan.CreateEntityUninitialized(null, coordinates);
|
||||
entMan.AddComponent<AnchorOnInitComponent>(ent2);
|
||||
entMan.InitializeAndStartEntity(ent2);
|
||||
Assert.That(sim.Transform(ent2).Anchored);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, pos).Count(), Is.EqualTo(1));
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, pos).Contains(ent2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is anchored to a grid tile, it's parent is set to the grid.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_Parent_SetToGrid()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
|
||||
var traversal = sim.System<SharedGridTraversalSystem>();
|
||||
traversal.Enabled = false;
|
||||
var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
|
||||
|
||||
// Act
|
||||
xformSys.AnchorEntity(ent1);
|
||||
Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(grid.Owner));
|
||||
traversal.Enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entities cannot be anchored to empty tiles. Attempting this is a no-op, and silently fails.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_EmptyTile_Nop()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, Tile.Empty);
|
||||
|
||||
// Act
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entities can be anchored to any non-empty grid tile. A physics component is not required on either
|
||||
/// the grid or the entity to anchor it.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_NonEmptyTile_Anchors()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
|
||||
// Act
|
||||
sim.Transform(ent1).Anchored = true;
|
||||
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).First(), Is.EqualTo(ent1));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.Not.EqualTo(Tile.Empty));
|
||||
Assert.That(sim.HasComp<PhysicsComponent>(ent1), Is.False);
|
||||
var tempQualifier = grid.Owner;
|
||||
Assert.That(sim.HasComp<PhysicsComponent>(tempQualifier), Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local position of an anchored entity cannot be changed (can still change world position via parent).
|
||||
/// Writing to the property is a no-op and is silently ignored.
|
||||
/// Because the position cannot be changed, MoveEvents are not raised when setting the property.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_SetPosition_Nop()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
// coordinates are already tile centered to prevent snapping and MoveEvent
|
||||
coordinates = coordinates.Offset(new Vector2(0.5f, 0.5f));
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
|
||||
sim.Transform(ent1).Anchored = true;
|
||||
sim.System<MoveEventTestSystem>().FailOnMove = true;
|
||||
|
||||
// Act
|
||||
sim.Transform(ent1).WorldPosition = new Vector2(99, 99);
|
||||
sim.Transform(ent1).LocalPosition = new Vector2(99, 99);
|
||||
|
||||
Assert.That(xformSys.GetMapCoordinates(ent1), Is.EqualTo(coordinates));
|
||||
sim.System<MoveEventTestSystem>().FailOnMove = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changing the parent of the entity un-anchors it.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_ChangeParent_Unanchors()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coordinates);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
// Act
|
||||
xformSys.SetParent(ent1, mapSys.GetMap(coordinates.MapId));
|
||||
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setting the parent of an anchored entity to the same parent is a no-op (it will not be un-anchored).
|
||||
/// This is an specific case to the base functionality of TransformComponent, where in general setting the same
|
||||
/// parent is a no-op.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_SetParentSame_Nop()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
var ent1 = entMan.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
sim.Transform(ent1).Anchored = true;
|
||||
|
||||
// Act
|
||||
xformSys.SetParent(ent1, grid.Owner);
|
||||
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).First(), Is.EqualTo(ent1));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.Not.EqualTo(Tile.Empty));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a tile is changed to a space tile, all entities anchored to that tile are unanchored.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_TileToSpace_Unanchors()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
mapSys.SetTile(grid, new Vector2i(100, 100), new Tile(1)); // Prevents the grid from being deleted when the Act happens
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
// Act
|
||||
mapSys.SetTile(grid, tileIndices, Tile.Empty);
|
||||
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adding an anchored entity to a container un-anchors an entity. There should be no way to have an anchored entity
|
||||
/// inside a container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The only way you can do this without changing the parent is to make the parent grid a ContainerManager, then add the anchored entity to it.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public void Anchored_AddToContainer_Unanchors()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
// Act
|
||||
// We purposefully use the grid as container so parent stays the same, reparent will unanchor
|
||||
var containerSys = entMan.System<SharedContainerSystem>();
|
||||
var containerMan = entMan.AddComponent<ContainerManagerComponent>(grid);
|
||||
var container = containerSys.MakeContainer<Container>(grid, "TestContainer", containerMan);
|
||||
containerSys.Insert(ent1, container);
|
||||
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1)));
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adding a physics component should poll TransformComponent.Anchored for the correct body type.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_AddPhysComp_IsStaticBody()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
// Act
|
||||
// assumed default body is Dynamic
|
||||
var physComp = entMan.AddComponent<PhysicsComponent>(ent1);
|
||||
|
||||
Assert.That(physComp.BodyType, Is.EqualTo(BodyType.Static));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is anchored, it's physics body type is set to <see cref="BodyType.Static"/>.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_HasPhysicsComp_IsStaticBody()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var physSystem = sim.System<SharedPhysicsSystem>();
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
|
||||
var ent1 = entMan.SpawnEntity(null, coordinates);
|
||||
var physComp = entMan.AddComponent<PhysicsComponent>(ent1);
|
||||
physSystem.SetBodyType(ent1, BodyType.Dynamic, body: physComp);
|
||||
|
||||
// Act
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
Assert.That(physComp.BodyType, Is.EqualTo(BodyType.Static));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When an entity is unanchored, it's physics body type is set to <see cref="BodyType.Dynamic"/>.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnUnanchored_HasPhysicsComp_IsDynamicBody()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
var physComp = entMan.AddComponent<PhysicsComponent>(ent1);
|
||||
sim.Transform(ent1).Anchored = true;
|
||||
|
||||
// Act
|
||||
xformSys.Unanchor(ent1);
|
||||
|
||||
Assert.That(physComp.BodyType, Is.EqualTo(BodyType.Dynamic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an entity with an anchored prototype is spawned in an invalid location, the entity is unanchored.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SpawnAnchored_EmptyTile_Unanchors()
|
||||
{
|
||||
var (sim, grid, coords, _, mapSys) = SimulationFactory();
|
||||
|
||||
// Act
|
||||
var ent1 = sim.SpawnEntity("anchoredEnt", coords);
|
||||
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(Tile.Empty));
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If an entity is inside a container, setting Anchored silently fails.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnAnchored_InContainer_Nop()
|
||||
{
|
||||
var (sim, grid, coords, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
var ent1 = sim.SpawnEntity(null, coords);
|
||||
var tileIndices = mapSys.TileIndicesFor(grid, sim.Transform(ent1).Coordinates);
|
||||
mapSys.SetTile(grid, tileIndices, new Tile(1));
|
||||
|
||||
var containerSys = entMan.System<SharedContainerSystem>();
|
||||
var containerMan = entMan.AddComponent<ContainerManagerComponent>(grid);
|
||||
var container = containerSys.MakeContainer<Container>(grid, "TestContainer", containerMan);
|
||||
containerSys.Insert(ent1, container);
|
||||
|
||||
// Act
|
||||
xformSys.AnchorEntity(ent1);
|
||||
|
||||
Assert.That(sim.Transform(ent1).Anchored, Is.False);
|
||||
Assert.That(mapSys.GetAnchoredEntities(grid, tileIndices).Count(), Is.EqualTo(0));
|
||||
Assert.That(mapSys.GetTileRef(grid, tileIndices).Tile, Is.EqualTo(new Tile(1)));
|
||||
Assert.That(container.ContainedEntities.Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unanchoring an unanchored entity is a no-op.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Unanchored_Unanchor_Nop()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
|
||||
var traversal = sim.System<SharedGridTraversalSystem>();
|
||||
traversal.Enabled = false;
|
||||
var ent1 = sim.SpawnEntity(null, coordinates); // this raises MoveEvent, subscribe after
|
||||
|
||||
// Act
|
||||
sim.System<MoveEventTestSystem>().FailOnMove = true;
|
||||
xformSys.Unanchor(ent1);
|
||||
Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(grid.Owner));
|
||||
sim.System<MoveEventTestSystem>().FailOnMove = false;
|
||||
traversal.Enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unanchoring an entity should leave it parented to the grid it was anchored to.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Anchored_Unanchored_ParentUnchanged()
|
||||
{
|
||||
var (sim, grid, coordinates, xformSys, mapSys) = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
|
||||
// can only be anchored to a tile
|
||||
mapSys.SetTile(grid, mapSys.TileIndicesFor(grid, coordinates), new Tile(1));
|
||||
var ent1 = entMan.SpawnEntity("anchoredEnt", mapSys.MapToGrid(grid, coordinates));
|
||||
|
||||
xformSys.Unanchor(ent1);
|
||||
|
||||
Assert.That(sim.Transform(ent1).ParentUid, Is.EqualTo(grid.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
{
|
||||
[TestFixture, Parallelizable]
|
||||
sealed class TransformSystemTests
|
||||
{
|
||||
private static ISimulation SimulationFactory()
|
||||
{
|
||||
var sim = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterEntitySystems(f => f.LoadExtraSystemType<AnchoredSystemTests.MoveEventTestSystem>())
|
||||
.InitializeInstance();
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the local position of the transform changes, a MoveEvent is raised.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void OnMove_LocalPosChanged_RaiseMoveEvent()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entMan = sim.Resolve<IEntityManager>();
|
||||
var map = sim.CreateMap().MapId;
|
||||
var ent1 = entMan.SpawnEntity(null, new MapCoordinates(Vector2.Zero, map));
|
||||
|
||||
entMan.System<AnchoredSystemTests.MoveEventTestSystem>().ResetCounters();
|
||||
entMan.System<TransformSystem>().SetLocalPosition(ent1, Vector2.One);
|
||||
entMan.System<AnchoredSystemTests.MoveEventTestSystem>().AssertMoved(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the MoverCoordinates between parent and children is correct.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void MoverCoordinatesCorrect()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var xformSystem = sim.Resolve<IEntitySystemManager>().GetEntitySystem<SharedTransformSystem>();
|
||||
var mapId = sim.CreateMap().MapId;
|
||||
|
||||
var parent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId));
|
||||
var parentXform = entManager.GetComponent<TransformComponent>(parent);
|
||||
Assert.That(parentXform.LocalPosition, Is.EqualTo(Vector2.One));
|
||||
|
||||
var child1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId));
|
||||
var child2 = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(10f, 10f), mapId));
|
||||
|
||||
var child1Xform = entManager.GetComponent<TransformComponent>(child1);
|
||||
var child2Xform = entManager.GetComponent<TransformComponent>(child2);
|
||||
xformSystem.SetParent(child1, child1Xform, parent, parentXform: parentXform);
|
||||
xformSystem.SetParent(child2, child2Xform, parent, parentXform: parentXform);
|
||||
|
||||
var mover1 = xformSystem.GetMoverCoordinates(child1, child1Xform);
|
||||
var mover2 = xformSystem.GetMoverCoordinates(child2, child2Xform);
|
||||
|
||||
Assert.That(mover1.Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(mover2.Position, Is.EqualTo(new Vector2(10f, 10f)));
|
||||
|
||||
var child3 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId));
|
||||
var child3Xform = entManager.GetComponent<TransformComponent>(child3);
|
||||
xformSystem.SetParent(child3, child3Xform, child2, parentXform: child2Xform);
|
||||
|
||||
Assert.That(xformSystem.GetMoverCoordinates(child3, child3Xform).Position, Is.EqualTo(Vector2.One));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that when a transformcomponent is detached to null all of its children update their mapids.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DetachMapRecursive()
|
||||
{
|
||||
var sim = SimulationFactory();
|
||||
var entManager = sim.Resolve<IEntityManager>();
|
||||
var xformSystem = sim.Resolve<IEntitySystemManager>().GetEntitySystem<SharedTransformSystem>();
|
||||
var mapId = sim.CreateMap().MapId;
|
||||
|
||||
var parent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.One, mapId));
|
||||
var parentXform = entManager.GetComponent<TransformComponent>(parent);
|
||||
|
||||
var child = entManager.SpawnEntity(null, new EntityCoordinates(parent, Vector2.Zero));
|
||||
var childXform = entManager.GetComponent<TransformComponent>(child);
|
||||
|
||||
Assert.That(parentXform.MapID, Is.EqualTo(mapId));
|
||||
Assert.That(childXform.MapID, Is.EqualTo(mapId));
|
||||
|
||||
xformSystem.DetachEntity(parent, parentXform);
|
||||
Assert.That(parentXform.MapID, Is.EqualTo(MapId.Nullspace));
|
||||
Assert.That(childXform.MapID, Is.EqualTo(MapId.Nullspace));
|
||||
}
|
||||
|
||||
private sealed class Subscriber : IEntityEventSubscriber { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
[TestFixture]
|
||||
internal sealed class TransformComponent_Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verify that WorldPosition and WorldRotation return the same result as the faster helper method.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestGetWorldMatches()
|
||||
{
|
||||
var server = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = server.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var xform = entManager.System<TransformSystem>();
|
||||
|
||||
var ent1 = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
var ent2 = entManager.SpawnEntity(null, new MapCoordinates(new Vector2(100f, 0f), mapId));
|
||||
|
||||
var xform1 = entManager.GetComponent<TransformComponent>(ent1);
|
||||
var xform2 = entManager.GetComponent<TransformComponent>(ent2);
|
||||
|
||||
xform.SetParent(ent2, ent1);
|
||||
|
||||
xform1.LocalRotation = MathF.PI;
|
||||
|
||||
var (worldPos, worldRot, worldMatrix) = xform.GetWorldPositionRotationMatrix(xform2);
|
||||
|
||||
Assert.That(worldPos, Is.EqualTo(xform.GetWorldPosition(xform2)));
|
||||
Assert.That(worldRot, Is.EqualTo(xform.GetWorldRotation(xform2)));
|
||||
Assert.That(worldMatrix, Is.EqualTo(xform.GetWorldMatrix(xform2)));
|
||||
|
||||
var (_, _, invWorldMatrix) = xform.GetWorldPositionRotationInvMatrix(xform2);
|
||||
|
||||
Assert.That(invWorldMatrix, Is.EqualTo(xform.GetInvWorldMatrix(xform2)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts that when AttachToGridOrMap is called the entity remains in the same position.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AttachToGridOrMap()
|
||||
{
|
||||
var server = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = server.Resolve<IEntityManager>();
|
||||
var mapManager = server.Resolve<IMapManager>();
|
||||
var mapSystem = entManager.System<SharedMapSystem>();
|
||||
var xformSystem = entManager.System<TransformSystem>();
|
||||
|
||||
mapSystem.CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
mapSystem.SetTile(grid, new Vector2i(0, 0), new Tile(1));
|
||||
xformSystem.SetLocalPosition(grid, new Vector2(0f, 100f));
|
||||
|
||||
var ent1 = entManager.SpawnEntity(null, new EntityCoordinates(grid, Vector2.One * grid.Comp.TileSize / 2));
|
||||
var ent2 = entManager.SpawnEntity(null, new EntityCoordinates(ent1, Vector2.Zero));
|
||||
|
||||
var xform2 = entManager.GetComponent<TransformComponent>(ent2);
|
||||
Assert.That(xformSystem.GetWorldPosition(ent2), Is.EqualTo(new Vector2(0.5f, 100.5f)));
|
||||
|
||||
xformSystem.AttachToGridOrMap(ent2);
|
||||
Assert.That(xform2.LocalPosition, Is.EqualTo(Vector2.One * grid.Comp.TileSize / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user