Component Lifetime Events (#1660)

* Events are now raised for component OnAdd/Initialize/Startup/Shutdown/OnRemove.
Code cleanup in the Component class.
This commit is contained in:
Acruid
2021-04-13 17:16:41 -07:00
committed by GitHub
parent c1396f1c50
commit cbd7b62ad7
4 changed files with 223 additions and 135 deletions

View File

@@ -1,18 +1,17 @@
using System;
using System;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
/// <inheritdoc />
[Reflect(false)]
[ImplicitDataDefinitionForInheritorsAttribute]
[ImplicitDataDefinitionForInheritors]
public abstract class Component : IComponent
{
/// <inheritdoc />
@@ -27,15 +26,10 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public virtual bool NetworkSynchronizeExistence => false;
[DataField("netsync")]
private bool _netSyncEnabled = true;
/// <inheritdoc />
[ViewVariables]
public bool NetSyncEnabled
{
get => _netSyncEnabled;
set => _netSyncEnabled = value;
}
[field: DataField("netsync")]
public bool NetSyncEnabled { get; set; } = true;
/// <inheritdoc />
[ViewVariables]
@@ -45,13 +39,7 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public bool Paused => Owner.Paused;
/// <summary>
/// True if this entity is a client-only entity.
/// That is, it does not exist on the server, only THIS client.
/// </summary>
[ViewVariables]
public bool IsClientSide => Owner.Uid.IsClientSide();
/// <inheritdoc />
[ViewVariables]
public bool Initialized { get; private set; }
@@ -79,12 +67,30 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public bool Deleted { get; private set; }
/// <inheritdoc />
[ViewVariables]
public GameTick CreationTick { get; private set; }
/// <inheritdoc />
[ViewVariables]
public GameTick LastModifiedTick { get; private set; }
private static readonly ComponentAdd CompAddInstance = new();
private static readonly ComponentInit CompInitInstance = new();
private static readonly ComponentStartup CompStartupInstance = new();
private static readonly ComponentShutdown CompShutdownInstance = new();
private static readonly ComponentRemove CompRemoveInstance = new();
private EntityEventBus GetBus()
{
// Apparently components are being created outside of the ComponentManager,
// and the Owner is not being set correctly.
// ReSharper disable once RedundantAssertionStatement
DebugTools.AssertNotNull(Owner);
return (EntityEventBus) Owner.EntityManager.EventBus;
}
/// <inheritdoc />
public virtual void OnRemove()
{
@@ -93,6 +99,7 @@ namespace Robust.Shared.GameObjects
// We have been marked for deletion by the Component Manager.
Deleted = true;
GetBus().RaiseComponentEvent(this, CompRemoveInstance);
}
/// <summary>
@@ -110,6 +117,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Cannot Add a Deleted component!");
CreationTick = Owner.EntityManager.CurrentTick;
GetBus().RaiseComponentEvent(this, CompAddInstance);
}
/// <inheritdoc />
@@ -125,6 +133,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Cannot Initialize a Deleted component!");
Initialized = true;
GetBus().RaiseComponentEvent(this, CompInitInstance);
}
/// <summary>
@@ -143,6 +152,7 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Cannot Start a Deleted component!");
_running = true;
GetBus().RaiseComponentEvent(this, CompStartupInstance);
}
/// <summary>
@@ -161,16 +171,19 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException("Cannot Shutdown a Deleted component!");
_running = false;
GetBus().RaiseComponentEvent(this, CompShutdownInstance);
}
/// <inheritdoc />
public void Dirty()
{
if (Owner != null)
{
Owner.Dirty();
LastModifiedTick = Owner.EntityManager.CurrentTick;
}
// Deserialization will cause this to be true.
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if(Owner is null)
return;
Owner.Dirty();
LastModifiedTick = Owner.EntityManager.CurrentTick;
}
/// <summary>
@@ -226,4 +239,34 @@ namespace Robust.Shared.GameObjects
CreationTick = GameTick.Zero;
}
}
/// <summary>
/// The component has been added to the entity. This is the first function
/// to be called after the component has been allocated and (optionally) deserialized.
/// </summary>
public class ComponentAdd : EntityEventArgs { }
/// <summary>
/// Raised when all of the entity's other components have been added and are available,
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.
/// </summary>
public class ComponentInit : EntityEventArgs { }
/// <summary>
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
/// This can be called multiple times during the component's life, and at any time.
/// </summary>
public class ComponentStartup : EntityEventArgs { }
/// <summary>
/// Shuts down the component. The is called Automatically by OnRemove. This can be called multiple times during
/// the component's life, and at any time.
/// </summary>
public class ComponentShutdown : EntityEventArgs { }
/// <summary>
/// The component has been removed from the entity. This is the last function
/// that is called before the component is freed.
/// </summary>
public class ComponentRemove : EntityEventArgs { }
}

View File

@@ -36,9 +36,25 @@ namespace Robust.Shared.GameObjects
_eventTables = new EventTables(_entMan);
}
/// <summary>
/// Dispatches an event directly to a specific component.
/// </summary>
/// <remarks>
/// This has a very specific purpose, and has massive potential to be abused.
/// DO NOT EXPOSE THIS TO CONTENT.
/// </remarks>
/// <typeparam name="TEvent">Event to dispatch.</typeparam>
/// <param name="component">Component receiving the event.</param>
/// <param name="args">Event arguments for the event.</param>
internal void RaiseComponentEvent<TEvent>(IComponent component, TEvent args)
where TEvent : EntityEventArgs
{
_eventTables.DispatchComponent(component.Owner.Uid, component.GetType(), typeof(TEvent), args);
}
/// <inheritdoc />
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = true)
where TEvent:EntityEventArgs
where TEvent : EntityEventArgs
{
_eventTables.Dispatch(uid, typeof(TEvent), args);
@@ -215,6 +231,18 @@ namespace Robust.Shared.GameObjects
}
}
public void DispatchComponent(EntityUid euid, Type compType, Type eventType, EntityEventArgs args)
{
if (!_subscriptions.TryGetValue(compType, out var compSubs))
return;
if (!compSubs.TryGetValue(eventType, out var handler))
return;
var component = _entMan.ComponentManager.GetComponent(euid, compType);
handler(euid, component, args);
}
public void ClearEntities()
{
_eventTables = new();

View File

@@ -1,29 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared.GameObjects
{
[TestFixture, Parallelizable ,TestOf(typeof(ComponentManager))]
class ComponentManager_Tests
public class ComponentManager_Tests
{
private const uint CompNetId = 3;
#region Component Management
private static readonly EntityCoordinates DefaultCoords = new(new EntityUid(1), Vector2.Zero);
[Test]
public void AddComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = new DummyComponent()
{
Owner = entity
};
// Act
manager.AddComponent(entity, component);
@@ -37,14 +38,14 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void AddComponentOverwriteTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, new DummyComponent
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = new DummyComponent()
{
Owner = entity
});
};
// Act
manager.AddComponent(entity, component, true);
@@ -58,8 +59,10 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void AddComponent_ExistingDeleted()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var firstComp = new DummyComponent {Owner = entity};
manager.AddComponent(entity, firstComp);
manager.RemoveComponent<DummyComponent>(entity.Uid);
@@ -77,11 +80,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void HasComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
entity.AddComponent<DummyComponent>();
// Act
var result = manager.HasComponent<DummyComponent>(entity.Uid);
@@ -94,11 +97,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void HasNetComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
entity.AddComponent<DummyComponent>();
// Act
var result = manager.HasComponent(entity.Uid, CompNetId);
@@ -111,11 +114,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void GetNetComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.GetComponent(entity.Uid, CompNetId);
@@ -128,11 +131,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void TryGetComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.TryGetComponent<DummyComponent>(entity.Uid, out var comp);
@@ -146,11 +149,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void TryGetNetComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.TryGetComponent(entity.Uid, CompNetId, out var comp);
@@ -164,11 +167,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void RemoveComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
manager.RemoveComponent<DummyComponent>(entity.Uid);
@@ -182,11 +185,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void RemoveNetComponentTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
manager.RemoveComponent(entity.Uid, CompNetId);
@@ -200,11 +203,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void GetComponentsTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.GetComponents<DummyComponent>(entity.Uid);
@@ -219,11 +222,11 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void GetAllComponentsTest()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.EntityQuery<DummyComponent>(true);
@@ -238,55 +241,32 @@ namespace Robust.UnitTesting.Shared.GameObjects
public void GetAllComponentInstances()
{
// Arrange
var manager = ManagerFactory(out var entityManager);
var entity = EntityFactory(entityManager);
var component = new DummyComponent();
component.Owner = entity;
manager.AddComponent(entity, component);
var sim = SimulationFactory();
var entMan = sim.Resolve<IEntityManager>();
var manager = sim.Resolve<IComponentManager>();
var entity = entMan.SpawnEntity(null, DefaultCoords);
var component = entity.AddComponent<DummyComponent>();
// Act
var result = manager.GetComponents(entity.Uid);
// Assert
var list = result.ToList();
var list = result.Where(c=>c.Name == "Dummy").ToList();
Assert.That(list.Count, Is.EqualTo(1));
Assert.That(list[0], Is.EqualTo(component));
}
#endregion
private static IComponentManager ManagerFactory(out IEntityManager entityManager)
private static ISimulation SimulationFactory()
{
var dependencies = new DependencyCollection();
var sim = RobustServerSimulation
.NewSimulation()
.RegisterComponents(factory => factory.RegisterClass<DummyComponent>())
.InitializeInstance();
var runtimeLog = new Mock<IRuntimeLog>();
dependencies.RegisterInstance<IRuntimeLog>(runtimeLog.Object);
// Adds the map with id 1, and spawns entity 1 as the map entity.
sim.AddMap(1);
// set up the registration
var mockRegistration = new Mock<IComponentRegistration>();
mockRegistration.SetupGet(x => x.References).Returns(new List<Type> { typeof(DummyComponent) });
// setup the comp factory
var mockFactory = new Mock<IComponentFactory>();
mockFactory.Setup(x => x.GetRegistration(It.IsAny<IComponent>())).Returns(mockRegistration.Object);
mockFactory.Setup(x => x.GetRegistration(It.IsAny<Type>())).Returns(mockRegistration.Object);
mockFactory.Setup(x => x.GetComponent<DummyComponent>()).Returns(new DummyComponent());
mockFactory.Setup(x => x.GetAllRefTypes()).Returns(new[] { typeof(DummyComponent) });
mockFactory.Setup(x => x.GetAllNetIds()).Returns(new[] { CompNetId });
dependencies.RegisterInstance<IComponentFactory>(mockFactory.Object);
var mockCompDependencyManager = new Mock<IComponentDependencyManager>();
dependencies.RegisterInstance<IComponentDependencyManager>(mockCompDependencyManager.Object); //todo probably not correct
// set up the entity manager
var mockEntMan = new Mock<IEntityManager>();
dependencies.RegisterInstance<IEntityManager>(mockEntMan.Object);
var manager = new ComponentManager();
dependencies.InjectDependencies(manager);
manager.Initialize();
entityManager = mockEntMan.Object;
return manager;
return sim;
}
private class DummyComponent : Component, ICompType1, ICompType2
@@ -295,15 +275,6 @@ namespace Robust.UnitTesting.Shared.GameObjects
public override uint? NetID => CompNetId;
}
private static IEntity EntityFactory(IEntityManager entityManager)
{
var mockEnt = new Mock<IEntity>();
mockEnt.SetupGet(x => x.EntityManager).Returns(entityManager);
mockEnt.SetupGet(x => x.Uid).Returns(new EntityUid(7));
mockEnt.Setup(x => x.IsValid()).Returns(true);
return mockEnt.Object;
}
private interface ICompType1 { }
private interface ICompType2 { }

View File

@@ -96,6 +96,52 @@ namespace Robust.UnitTesting.Shared.GameObjects
}
}
[Test]
public void SubscribeCompLifeEvent()
{
// Arrange
var entUid = new EntityUid(7);
var compInstance = new MetaDataComponent();
var mockEnt = new Mock<IEntity>();
mockEnt.SetupGet(m => m.Uid).Returns(entUid);
compInstance.Owner = mockEnt.Object;
var entManMock = new Mock<IEntityManager>();
var compManMock = new Mock<IComponentManager>();
IComponent? outIComponent = compInstance;
compManMock.Setup(m => m.TryGetComponent(entUid, typeof(MetaDataComponent), out outIComponent))
.Returns(true);
compManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
.Returns(compInstance);
entManMock.Setup(m => m.ComponentManager).Returns(compManMock.Object);
var bus = new EntityEventBus(entManMock.Object);
// Subscribe
int calledCount = 0;
bus.SubscribeLocalEvent<MetaDataComponent, ComponentInit>(HandleTestEvent);
// add a component to the system
entManMock.Raise(m=>m.EntityAdded += null, entManMock.Object, entUid);
compManMock.Raise(m => m.ComponentAdded += null, new AddedComponentEventArgs(compInstance, entUid));
// Raise
bus.RaiseComponentEvent(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));
}
}
private class DummyComponent : Component
{