Create Entities Without Prototypes (#815)

* Retrofitted ComponentManager_Test to use DependencyCollection.

* Renamed ComponentManager_Test to ComponentManager_Tests to follow naming conventions.

* Added component add/remove/delete events to IComponentManager.
Removed IEntity dependency from ComponentManager.

* Entities can now be spawned without a prototype.

* Removed unused function.

* CreateEntity now actually works with a null prototype ID. The other spawn functions should work as well.
Updated doc comments for IEntityManager.SpawnEntity().
This commit is contained in:
Acruid
2019-05-24 12:37:28 -07:00
committed by Pieter-Jan Briers
parent dc4cc71c72
commit 61aba8fc50
7 changed files with 95 additions and 51 deletions

View File

@@ -22,9 +22,15 @@ namespace Robust.Shared.GameObjects
[Dependency]
private readonly IComponentFactory _componentFactory;
/// <inheritdoc />
public event EventHandler<ComponentEventArgs> ComponentAdded;
[Dependency]
private readonly IEntityManager _entityManager;
/// <inheritdoc />
public event EventHandler<ComponentEventArgs> ComponentRemoved;
/// <inheritdoc />
public event EventHandler<ComponentEventArgs> ComponentDeleted;
/// <inheritdoc />
public void Clear()
@@ -105,6 +111,8 @@ namespace Robust.Shared.GameObjects
// mark the component as dirty for networking
component.Dirty();
ComponentAdded?.Invoke(this, new ComponentEventArgs(component));
}
component.OnAdd();
@@ -174,6 +182,7 @@ namespace Robust.Shared.GameObjects
component.Shutdown();
component.OnRemove();
ComponentRemoved?.Invoke(this, new ComponentEventArgs(component));
}
private void RemoveComponentImmediate(Component component)
@@ -188,6 +197,7 @@ namespace Robust.Shared.GameObjects
component.Shutdown();
component.OnRemove();
ComponentRemoved?.Invoke(this, new ComponentEventArgs(component));
DeleteComponent(component);
}
@@ -206,9 +216,7 @@ namespace Robust.Shared.GameObjects
private void DeleteComponent(Component component)
{
var reg = _componentFactory.GetRegistration(component.GetType());
_entityManager.RemoveSubscribedEvents(component);
var entityUid = component.Owner.Uid;
foreach (var refType in reg.References)
@@ -225,6 +233,8 @@ namespace Robust.Shared.GameObjects
// mark the owning entity as dirty for networking
component.Owner.Dirty();
ComponentDeleted?.Invoke(this, new ComponentEventArgs(component));
}
/// <inheritdoc />
@@ -387,4 +397,24 @@ namespace Robust.Shared.GameObjects
}
#endregion
}
/// <summary>
///
/// </summary>
public class ComponentEventArgs : EventArgs
{
/// <summary>
/// Component that this event relates to.
/// </summary>
public IComponent Component { get; }
/// <summary>
/// Constructs a new instance of <see cref="ComponentEventArgs"/>.
/// </summary>
/// <param name="component"></param>
public ComponentEventArgs(IComponent component)
{
Component = component;
}
}
}

View File

@@ -81,6 +81,8 @@ namespace Robust.Shared.GameObjects
public virtual void Initialize()
{
_network.RegisterNetMessage<MsgEntity>(MsgEntity.NAME, HandleEntityNetworkMessage);
_componentManager.ComponentRemoved += (sender, args) => RemoveSubscribedEvents(args.Component);
}
public virtual void Startup()
@@ -228,6 +230,22 @@ namespace Robust.Shared.GameObjects
/// Allocates an entity and stores it but does not load components or do initialization.
/// </summary>
private protected Entity AllocEntity(string prototypeName, EntityUid? uid = null)
{
var entity = AllocEntity(uid);
if (string.IsNullOrWhiteSpace(prototypeName))
return entity;
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
entity.Prototype = prototype;
return entity;
}
/// <summary>
/// Allocates an entity and stores it but does not load components or do initialization.
/// </summary>
private protected Entity AllocEntity(EntityUid? uid = null)
{
if (uid == null)
{
@@ -239,10 +257,17 @@ namespace Robust.Shared.GameObjects
throw new InvalidOperationException($"UID already taken: {uid}");
}
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
var entity = prototype.AllocEntity(uid.Value, this);
var entity = new Entity();
entity.SetManagers(this);
entity.SetUid(uid.Value);
// allocate the required MetaDataComponent
_componentManager.AddComponent<MetaDataComponent>(entity);
Entities[entity.Uid] = entity;
_allEntities.Add(entity);
return entity;
}
@@ -251,6 +276,9 @@ namespace Robust.Shared.GameObjects
/// </summary>
private protected Entity CreateEntity(string prototypeName, EntityUid? uid = null)
{
if (prototypeName == null)
return AllocEntity(uid);
var entity = AllocEntity(prototypeName, uid);
entity.Prototype.LoadEntity(entity, ComponentFactory, null);
return entity;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Interfaces.GameObjects
@@ -7,8 +8,25 @@ namespace Robust.Shared.Interfaces.GameObjects
/// <summary>
/// Holds a collection of ECS components that are attached to entities.
/// </summary>
[PublicAPI]
public interface IComponentManager
{
/// <summary>
/// A component was added to the manager.
/// </summary>
event EventHandler<ComponentEventArgs> ComponentAdded;
/// <summary>
/// A component was removed from the manager.
/// </summary>
event EventHandler<ComponentEventArgs> ComponentRemoved;
/// <summary>
/// A component was deleted. This is usually deferred until some time after it was removed.
/// Usually you will want to subscribe to <see cref="ComponentRemoved"/>.
/// </summary>
event EventHandler<ComponentEventArgs> ComponentDeleted;
/// <summary>
/// Instantly clears all components from the manager. This will NOT shut them down gracefully.
/// Any entities relying on existing components will be broken.

View File

@@ -30,10 +30,10 @@ namespace Robust.Shared.Interfaces.GameObjects
#region Entity Management
/// <summary>
/// Spawns an initialized entity at the default location.
/// Spawns an initialized entity at the default location, using the given prototype.
/// </summary>
/// <param name="protoName"></param>
/// <returns></returns>
/// <param name="protoName">The prototype to clone. If this is null, the entity won't have a prototype.</param>
/// <returns>Newly created entity.</returns>
IEntity SpawnEntity(string protoName);
/// <summary>

View File

@@ -407,21 +407,6 @@ namespace Robust.Shared.GameObjects
}
}
internal Entity AllocEntity(EntityUid uid, IEntityManager manager)
{
var entity = (Entity)Activator.CreateInstance(ClassType ?? typeof(Entity));
entity.SetManagers(manager);
entity.SetUid(uid);
// allocate the required MetaDataComponent
manager.ComponentManager.AddComponent<MetaDataComponent>(entity);
entity.Prototype = this;
return entity;
}
internal void LoadEntity(Entity entity, IComponentFactory factory, IEntityLoadContext context)
{
YamlObjectSerializer.Context defaultContext = null;

View File

@@ -101,7 +101,7 @@
<Compile Include="Server\GameObjects\Components\Container_Test.cs" />
<Compile Include="Server\GameObjects\Components\Transform_Test.cs" />
<Compile Include="Shared\ContentPack\ResourceManagerTest.cs" />
<Compile Include="Shared\GameObjects\ComponentManager_Test.cs" />
<Compile Include="Shared\GameObjects\ComponentManager_Tests.cs" />
<Compile Include="Shared\GameObjects\EntityState_Tests.cs" />
<Compile Include="Shared\IoC\IoCManager_Test.cs" />
<Compile Include="Shared\Map\MapChunk_Tests.cs" />

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using NUnit.Framework;
using Robust.Shared.GameObjects;
@@ -10,9 +9,8 @@ using Robust.Shared.IoC;
namespace Robust.UnitTesting.Shared.GameObjects
{
[TestFixture]
[TestOf(typeof(ComponentManager))]
class ComponentManager_Test
[TestFixture, Parallelizable ,TestOf(typeof(ComponentManager))]
class ComponentManager_Tests
{
private const uint CompNetId = 3;
@@ -237,11 +235,10 @@ namespace Robust.UnitTesting.Shared.GameObjects
}
#endregion
// mimics the IoC system.
private static IComponentManager ManagerFactory(out IEntityManager entityManager)
{
var manager = new ComponentManager();
var dependencies = new DependencyCollection();
// set up the registration
var mockRegistration = new Mock<IComponentRegistration>();
@@ -252,33 +249,19 @@ namespace Robust.UnitTesting.Shared.GameObjects
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());
dependencies.RegisterInstance<IComponentFactory>(mockFactory.Object);
// set up the entity manager
var mockEntMan = new Mock<IEntityManager>();
dependencies.RegisterInstance<IEntityManager>(mockEntMan.Object);
// Inject the dependency into manager
foreach (var field in GetDepFields(typeof(ComponentManager)))
{
if (field.FieldType.IsAssignableFrom(typeof(IComponentFactory)))
{
field.SetValue(manager, mockFactory.Object);
}
else if (field.FieldType.IsAssignableFrom(typeof(IEntityManager)))
{
field.SetValue(manager, mockEntMan.Object);
}
}
var manager = new ComponentManager();
dependencies.InjectDependencies(manager);
entityManager = mockEntMan.Object;
return manager;
}
private static IEnumerable<FieldInfo> GetDepFields(Type targetType)
{
return targetType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(p => Attribute.GetCustomAttribute(p, typeof(DependencyAttribute)) != null);
}
private class DummyComponent : Component, ICompType1, ICompType2
{
public override string Name => null;