the big ComponentMessage rework

pull/5/head
thatcosmonaut 2019-07-18 14:02:57 -07:00
parent 0b2ef55634
commit a42123f58d
17 changed files with 409 additions and 119 deletions

11
TODO
View File

@ -1,11 +1,6 @@
- guard rail tests for nonexistent lookups in manager classes - Change "Writes" To "Sends" and make it only take Messages
- custom exception for nonexistent Entity ID - Add "Updates" for updating components
- custom exception for nonexistent Component ID
- special Engine kinds (detector, modifier, spawner) - so we have four attributes: Activates, Reads, Updates, and Sends
- maybe AddEngine should take a constructed engine similarly to AddComponent?
- component getters should return ValueTuple instead of KeyValuePair so we can do destructuring assignments
- docs - docs

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Encompass.Exceptions;
namespace Encompass
{
public class Activates : Attribute
{
public readonly HashSet<Type> activateTypes = new HashSet<Type>();
public Activates(params Type[] activateTypes)
{
foreach (var activateType in activateTypes)
{
var isComponent = activateType.GetInterfaces().Contains(typeof(IComponent));
if (!isComponent)
{
throw new IllegalActivateTypeException("{0} must be a Component", activateType.Name);
}
this.activateTypes.Add(typeof(ComponentMessage<>).MakeGenericType(activateType));
}
}
}
}

View File

@ -5,22 +5,32 @@ using Encompass.Exceptions;
namespace Encompass namespace Encompass
{ {
[System.AttributeUsage(System.AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class Reads : System.Attribute public class Reads : Attribute
{ {
public readonly HashSet<Type> readTypes; public readonly HashSet<Type> readTypes = new HashSet<Type>();
public Reads(params Type[] readTypes) public Reads(params Type[] readTypes)
{ {
foreach (var readType in readTypes) foreach (var readType in readTypes)
{ {
if (!readType.GetInterfaces().Contains(typeof(IMessage))) var isMessage = readType.GetInterfaces().Contains(typeof(IMessage));
var isComponent = readType.GetInterfaces().Contains(typeof(IComponent));
if (!isMessage && !isComponent)
{ {
throw new IllegalReadTypeException("{0} must be a Message", readType.Name); throw new IllegalReadTypeException("{0} must be a Message or Component", readType.Name);
}
} }
this.readTypes = new HashSet<Type>(readTypes); if (isComponent)
{
this.readTypes.Add(typeof(ComponentMessage<>).MakeGenericType(readType));
}
else
{
this.readTypes.Add(readType);
}
}
} }
} }
} }

View File

@ -62,8 +62,7 @@ namespace Encompass
entityIDToComponentIDs[entityID].Add(componentID); entityIDToComponentIDs[entityID].Add(componentID);
componentIDToEntityID[componentID] = entityID; componentIDToEntityID[componentID] = entityID;
inactiveComponents.Add(componentID); activeComponents.Add(componentID);
MarkForActivation(componentID);
entitiesWithAddedComponents.Add(entityID); entitiesWithAddedComponents.Add(entityID);
@ -89,11 +88,11 @@ namespace Encompass
return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id])); return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id]));
} }
internal IEnumerable<ValueTuple<Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent internal IEnumerable<ValueTuple<Guid, Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent
{ {
return typeToComponentIDs.ContainsKey(typeof(TComponent)) ? return typeToComponentIDs.ContainsKey(typeof(TComponent)) ?
typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, TComponent>(id, (TComponent)IDToComponent[id])) : typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, Guid, TComponent>(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) :
Enumerable.Empty<ValueTuple<Guid, TComponent>>(); Enumerable.Empty<ValueTuple<Guid, Guid, TComponent>>();
} }
internal IEnumerable<ValueTuple<Guid, IComponent>> GetActiveComponentsByType(Type type) internal IEnumerable<ValueTuple<Guid, IComponent>> GetActiveComponentsByType(Type type)
@ -103,16 +102,11 @@ namespace Encompass
Enumerable.Empty<ValueTuple<Guid, IComponent>>(); Enumerable.Empty<ValueTuple<Guid, IComponent>>();
} }
internal ValueTuple<Guid, TComponent> GetActiveComponentByType<TComponent>() where TComponent : struct, IComponent
{
return GetActiveComponentsByType<TComponent>().Single();
}
internal IEnumerable<ValueTuple<Guid, TComponent>> GetComponentsByEntityAndType<TComponent>(Guid entityID) where TComponent : struct, IComponent internal IEnumerable<ValueTuple<Guid, TComponent>> GetComponentsByEntityAndType<TComponent>(Guid entityID) where TComponent : struct, IComponent
{ {
var entityComponentsByType = GetComponentsByEntity(entityID).Where((pair) => componentIDToType[pair.Item1] == typeof(TComponent)).Select((pair) => new ValueTuple<Guid, TComponent>(pair.Item1, (TComponent)pair.Item2)); var entityComponentsByType = GetComponentsByEntity(entityID).Where((pair) => componentIDToType[pair.Item1] == typeof(TComponent)).Select((pair) => new ValueTuple<Guid, TComponent>(pair.Item1, (TComponent)pair.Item2));
var activeComponentsByType = GetActiveComponentsByType<TComponent>(); var activeComponentsByType = GetActiveComponentsByType<TComponent>();
return activeComponentsByType.Intersect(entityComponentsByType); return activeComponentsByType.Select((triple) => (triple.Item2, triple.Item3)).Intersect(entityComponentsByType);
} }
internal IEnumerable<ValueTuple<Guid, IComponent>> GetComponentsByEntityAndType(Guid entityID, Type type) internal IEnumerable<ValueTuple<Guid, IComponent>> GetComponentsByEntityAndType(Guid entityID, Type type)
@ -180,22 +174,7 @@ namespace Encompass
} }
} }
internal void MarkForActivation(Guid componentID) internal void Activate(Guid componentID)
{
componentsMarkedForActivation.Add(componentID);
}
internal void ActivateMarkedComponents()
{
foreach (var componentID in componentsMarkedForActivation)
{
Activate(componentID);
}
componentsMarkedForActivation.Clear();
}
private void Activate(Guid componentID)
{ {
if (inactiveComponents.Remove(componentID)) if (inactiveComponents.Remove(componentID))
{ {

View File

@ -0,0 +1,11 @@
using System;
namespace Encompass
{
public struct ComponentMessage<TComponent> : IMessage where TComponent : struct, IComponent
{
public Entity entity;
public Guid componentID;
public TComponent component;
}
}

View File

@ -10,6 +10,7 @@ namespace Encompass
{ {
internal readonly HashSet<Type> writeTypes = new HashSet<Type>(); internal readonly HashSet<Type> writeTypes = new HashSet<Type>();
internal readonly HashSet<Type> readTypes = new HashSet<Type>(); internal readonly HashSet<Type> readTypes = new HashSet<Type>();
internal readonly HashSet<Type> activateTypes = new HashSet<Type>();
private EntityManager entityManager; private EntityManager entityManager;
private ComponentManager componentManager; private ComponentManager componentManager;
@ -28,6 +29,12 @@ namespace Encompass
{ {
readTypes = readsAttribute.readTypes; readTypes = readsAttribute.readTypes;
} }
var activatesAttribute = GetType().GetCustomAttribute<Activates>(false);
if (activatesAttribute != null)
{
activateTypes = activatesAttribute.activateTypes;
}
} }
internal void AssignEntityManager(EntityManager entityManager) internal void AssignEntityManager(EntityManager entityManager)
@ -82,29 +89,64 @@ namespace Encompass
return (TComponent)componentManager.GetComponentByID(componentID); return (TComponent)componentManager.GetComponentByID(componentID);
} }
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent internal IEnumerable<ValueTuple<Entity, Guid, TComponent>> ReadComponentsFromWorld<TComponent>() where TComponent : struct, IComponent
{ {
return componentManager.GetActiveComponentsByType<TComponent>(); return componentManager.GetActiveComponentsByType<TComponent>().Select((triple) => (GetEntity(triple.Item1), triple.Item2, triple.Item3));
}
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
{
return componentManager.GetActiveComponentByType<TComponent>();
} }
protected Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent protected Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{ {
return componentManager.AddComponent(entity.ID, component); if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
var componentID = componentManager.AddComponent(entity.ID, component);
ComponentMessage<TComponent> componentMessage;
componentMessage.entity = entity;
componentMessage.componentID = componentID;
componentMessage.component = component;
EmitMessage(componentMessage);
return componentID;
} }
protected Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent protected Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent
{ {
return componentManager.AddDrawComponent(entity.ID, component, layer); if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
} }
protected void ActivateComponent(Guid componentID) var componentID = componentManager.AddDrawComponent(entity.ID, component, layer);
ComponentMessage<TComponent> componentMessage;
componentMessage.entity = entity;
componentMessage.componentID = componentID;
componentMessage.component = component;
EmitMessage(componentMessage);
return componentID;
}
protected void ActivateComponent<TComponent>(Guid componentID) where TComponent : struct, IComponent
{ {
componentManager.MarkForActivation(componentID); if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
var entity = GetEntity(componentManager.GetEntityIDByComponentID(componentID));
var component = GetComponentByID<TComponent>(componentID);
ComponentMessage<TComponent> componentMessage;
componentMessage.entity = entity;
componentMessage.componentID = componentID;
componentMessage.component = component;
EmitMessage(componentMessage);
componentManager.Activate(componentID);
} }
protected void DeactivateComponent(Guid componentID) protected void DeactivateComponent(Guid componentID)
@ -114,6 +156,11 @@ namespace Encompass
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent
{ {
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
return componentManager.GetComponentsByEntityAndType<TComponent>(entity.ID); return componentManager.GetComponentsByEntityAndType<TComponent>(entity.ID);
} }
@ -124,7 +171,12 @@ namespace Encompass
protected bool HasComponent<TComponent>(Entity entity) where TComponent : struct, IComponent protected bool HasComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{ {
return componentManager.EntityHasComponentOfType<TComponent>(entity.ID); if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
return messageManager.GetMessagesByType<ComponentMessage<TComponent>>().Where((message) => message.entity == entity).Any();
} }
internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent
@ -162,11 +214,31 @@ namespace Encompass
return messageManager.GetMessagesByType<TMessage>(); return messageManager.GetMessagesByType<TMessage>();
} }
protected TMessage ReadMessage<TMessage>() where TMessage : struct, IMessage
{
return ReadMessages<TMessage>().Single();
}
protected IEnumerable<(Guid, TComponent)> ReadComponents<TComponent>() where TComponent : struct, IComponent
{
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
}
return ReadMessages<ComponentMessage<TComponent>>().Select((message) => (message.componentID, message.component));
}
protected (Guid, TComponent) ReadComponent<TComponent>() where TComponent : struct, IComponent
{
return ReadComponents<TComponent>().Single();
}
protected bool SomeMessage<TMessage>() where TMessage : struct, IMessage protected bool SomeMessage<TMessage>() where TMessage : struct, IMessage
{ {
if (!readTypes.Contains(typeof(TMessage))) if (!readTypes.Contains(typeof(TMessage)))
{ {
throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", GetType().Name, typeof(TMessage).Name);
} }
return messageManager.GetMessagesByType<TMessage>().Any(); return messageManager.GetMessagesByType<TMessage>().Any();
@ -174,6 +246,11 @@ namespace Encompass
protected bool SomeComponent<TComponent>() where TComponent : struct, IComponent protected bool SomeComponent<TComponent>() where TComponent : struct, IComponent
{ {
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
return componentManager.GetActiveComponentsByType<TComponent>().Any(); return componentManager.GetActiveComponentsByType<TComponent>().Any();
} }

View File

@ -0,0 +1,30 @@
using System.Reflection;
namespace Encompass.Engines
{
internal class ComponentMessageEmitter<TComponent> : Engine where TComponent : struct, IComponent
{
public ComponentMessageEmitter() : base()
{
var writesAttribute = GetType().GetCustomAttribute<Writes>(false);
if (writesAttribute != null)
{
writesAttribute.writeTypes.Add(typeof(ComponentMessage<TComponent>));
}
writeTypes.Add(typeof(ComponentMessage<TComponent>));
}
public override void Update(double dt)
{
foreach (var (entity, componentID, component) in ReadComponentsFromWorld<TComponent>())
{
ComponentMessage<TComponent> componentMessage;
componentMessage.entity = entity;
componentMessage.componentID = componentID;
componentMessage.component = component;
EmitMessage(componentMessage);
}
}
}
}

View File

@ -2,7 +2,7 @@
namespace Encompass namespace Encompass
{ {
public struct Entity public struct Entity : IEquatable<Entity>
{ {
public readonly Guid ID; public readonly Guid ID;
@ -10,5 +10,35 @@ namespace Encompass
{ {
this.ID = id; this.ID = id;
} }
public override bool Equals(object obj)
{
if (obj is Entity)
{
return this.Equals((Entity)obj);
}
return false;
}
public bool Equals(Entity other)
{
return other.ID == ID;
}
public static bool operator ==(Entity one, Entity two)
{
return one.Equals(two);
}
public static bool operator !=(Entity one, Entity two)
{
return !one.Equals(two);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
} }
} }

View File

@ -0,0 +1,12 @@
using System;
namespace Encompass.Exceptions
{
public class IllegalActivateException : Exception
{
public IllegalActivateException(
string format,
params object[] args
) : base(string.Format(format, args)) { }
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Encompass.Exceptions
{
public class IllegalActivateTypeException : Exception
{
public IllegalActivateTypeException(
string format,
params object[] args
) : base(string.Format(format, args)) { }
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace Encompass.Exceptions
{
public class UnregisteredComponentReadException : Exception
{
public UnregisteredComponentReadException(
string format,
params object[] args
) : base(string.Format(format, args)) { }
}
}

View File

@ -41,12 +41,12 @@ namespace Encompass
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
{ {
return componentManager.GetActiveComponentsByType<TComponent>(); return componentManager.GetActiveComponentsByType<TComponent>().Select((triple) => (triple.Item2, triple.Item3));
} }
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
{ {
return componentManager.GetActiveComponentByType<TComponent>(); return ReadComponents<TComponent>().Single();
} }
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent

View File

@ -36,7 +36,6 @@ namespace Encompass
entityManager.DestroyMarkedEntities(); entityManager.DestroyMarkedEntities();
componentManager.PerformComponentUpdates(); componentManager.PerformComponentUpdates();
componentManager.ActivateMarkedComponents();
componentManager.DeactivateMarkedComponents(); componentManager.DeactivateMarkedComponents();
componentManager.RemoveMarkedComponents(); componentManager.RemoveMarkedComponents();

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Linq; using System.Linq;
using Encompass.Exceptions; using Encompass.Exceptions;
using Encompass.Engines;
namespace Encompass namespace Encompass
{ {
@ -17,9 +18,12 @@ namespace Encompass
private readonly DrawLayerManager drawLayerManager; private readonly DrawLayerManager drawLayerManager;
private readonly RenderManager renderManager; private readonly RenderManager renderManager;
private readonly Dictionary<Type, HashSet<Engine>> typeToEmitters = new Dictionary<Type, HashSet<Engine>>();
private readonly Dictionary<Type, HashSet<Engine>> typeToReaders = new Dictionary<Type, HashSet<Engine>>(); private readonly Dictionary<Type, HashSet<Engine>> typeToReaders = new Dictionary<Type, HashSet<Engine>>();
private readonly HashSet<Engine> senders = new HashSet<Engine>();
private readonly HashSet<Type> registeredComponentTypes = new HashSet<Type>();
public WorldBuilder() public WorldBuilder()
{ {
var entitiesWithAddedComponents = new HashSet<Guid>(); var entitiesWithAddedComponents = new HashSet<Guid>();
@ -56,6 +60,12 @@ namespace Encompass
componentManager.MarkForDeactivation(componentID); componentManager.MarkForDeactivation(componentID);
} }
internal void RegisterComponent(Type componentType)
{
registeredComponentTypes.Add(componentType);
AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType)));
}
public Engine AddEngine<TEngine>(TEngine engine) where TEngine : Engine public Engine AddEngine<TEngine>(TEngine engine) where TEngine : Engine
{ {
engine.AssignEntityManager(entityManager); engine.AssignEntityManager(entityManager);
@ -65,60 +75,42 @@ namespace Encompass
engines.Add(engine); engines.Add(engine);
engineGraph.AddVertex(engine); engineGraph.AddVertex(engine);
foreach (var writeType in engine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)))) foreach (var activateType in engine.activateTypes)
{ {
if (!typeToEmitters.ContainsKey(writeType)) engine.writeTypes.Add(activateType);
{
typeToEmitters.Add(writeType, new HashSet<Engine>());
} }
typeToEmitters[writeType].Add(engine); var messageReadTypes = engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)));
var messageSendTypes = engine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)));
if (typeToReaders.ContainsKey(writeType)) if (messageReadTypes.Intersect(messageSendTypes).Any())
{ {
foreach (var reader in typeToReaders[writeType]) var type = messageReadTypes.Intersect(messageSendTypes).First();
{ throw new EngineMessageSelfCycleException("Engine {0} both reads and writes Message {1}", engine.GetType().Name, type.Name);
if (engine == reader)
{
if (writeType.GetInterfaces().Contains(typeof(IMessage)))
{
throw new EngineMessageSelfCycleException("Engine both reads and writes Message {0}", writeType.Name);
} }
}
else if (messageSendTypes.Any())
{ {
engineGraph.AddEdge(engine, reader); senders.Add(engine);
}
} }
foreach (var readType in engine.readTypes)
{
if (readType.IsGenericType && readType.GetGenericTypeDefinition() == typeof(ComponentMessage<>))
{
var componentType = readType.GetGenericArguments().Single();
if (!registeredComponentTypes.Contains(componentType))
{
RegisterComponent(componentType);
} }
} }
foreach (var readType in engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))))
{
if (!typeToReaders.ContainsKey(readType)) if (!typeToReaders.ContainsKey(readType))
{ {
typeToReaders.Add(readType, new HashSet<Engine>()); typeToReaders.Add(readType, new HashSet<Engine>());
} }
typeToReaders[readType].Add(engine); typeToReaders[readType].Add(engine);
if (typeToEmitters.ContainsKey(readType))
{
foreach (var emitter in typeToEmitters[readType])
{
if (emitter == engine)
{
if (readType.GetInterfaces().Contains(typeof(IMessage)))
{
throw new EngineMessageSelfCycleException("Engine both reads and writes Message {0}", readType.Name);
}
}
else
{
engineGraph.AddEdge(emitter, engine);
}
}
}
} }
return engine; return engine;
@ -148,8 +140,27 @@ namespace Encompass
return renderer; return renderer;
} }
private void BuildEngineGraph()
{
foreach (var senderEngine in senders)
{
foreach (var messageType in senderEngine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))))
{
if (typeToReaders.ContainsKey(messageType))
{
foreach (var readerEngine in typeToReaders[messageType])
{
engineGraph.AddEdge(senderEngine, readerEngine);
}
}
}
}
}
public World Build() public World Build()
{ {
BuildEngineGraph();
if (engineGraph.Cyclic()) if (engineGraph.Cyclic())
{ {
var cycles = engineGraph.SimpleCycles(); var cycles = engineGraph.SimpleCycles();
@ -225,7 +236,6 @@ namespace Encompass
); );
componentManager.PerformComponentUpdates(); componentManager.PerformComponentUpdates();
componentManager.ActivateMarkedComponents();
componentManager.DeactivateMarkedComponents(); componentManager.DeactivateMarkedComponents();
componentManager.RemoveMarkedComponents(); componentManager.RemoveMarkedComponents();

View File

@ -24,7 +24,7 @@ namespace Tests
static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>(); static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>();
static (Guid, MockComponent) gottenMockComponentIDPair; static (Guid, MockComponent) gottenMockComponentIDPair;
[Reads(typeof(EntityMessage))] [Reads(typeof(EntityMessage), typeof(MockComponent))]
class GetMockComponentsEngine : Engine class GetMockComponentsEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -38,7 +38,7 @@ namespace Tests
} }
} }
[Reads(typeof(EntityMessage))] [Reads(typeof(EntityMessage), typeof(MockComponent))]
class GetMockComponentEngine : Engine class GetMockComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -56,8 +56,8 @@ namespace Tests
public MockComponent mockComponent; public MockComponent mockComponent;
} }
[Reads(typeof(AddComponentTestMessage))] [Reads(typeof(AddComponentTestMessage), typeof(MockComponent))]
class AddComponentEngine : Engine class AddComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
@ -73,8 +73,7 @@ namespace Tests
public void AddComponent() public void AddComponent()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AddComponentEngine()); worldBuilder.AddEngine(new AddComponentTestEngine());
worldBuilder.AddEngine(new GetMockComponentEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
@ -94,6 +93,79 @@ namespace Tests
world.Update(0.01); world.Update(0.01);
} }
struct AddMockComponentMessage : IMessage
{
public Entity entity;
public MockComponent mockComponent;
}
[Writes(typeof(AddMockComponentMessage))]
class EmitMockComponentMessageEngine : Engine
{
private Entity entity;
public EmitMockComponentMessageEngine(Entity entity)
{
this.entity = entity;
}
public override void Update(double dt)
{
MockComponent mockComponent;
mockComponent.myInt = 10;
mockComponent.myString = "four";
AddMockComponentMessage addMockComponentMessage;
addMockComponentMessage.entity = entity;
addMockComponentMessage.mockComponent = mockComponent;
EmitMessage(addMockComponentMessage);
}
}
[Activates(typeof(MockComponent))]
[Reads(typeof(AddMockComponentMessage))]
class AddMockComponentEngine : Engine
{
public override void Update(double dt)
{
foreach (var message in ReadMessages<AddMockComponentMessage>())
{
AddComponent(message.entity, message.mockComponent);
}
}
}
[Reads(typeof(MockComponent))]
class HasMockComponentEngine : Engine
{
private Entity entity;
public HasMockComponentEngine(Entity entity)
{
this.entity = entity;
}
public override void Update(double dt)
{
Assert.IsTrue(HasComponent<MockComponent>(entity));
}
}
[Test]
public void AddComponentAndReadSameFrame()
{
var worldBuilder = new WorldBuilder();
var entity = worldBuilder.CreateEntity();
worldBuilder.AddEngine(new EmitMockComponentMessageEngine(entity));
worldBuilder.AddEngine(new AddMockComponentEngine());
worldBuilder.AddEngine(new HasMockComponentEngine(entity));
var world = worldBuilder.Build();
world.Update(0.01);
}
[Test] [Test]
public void GetComponents() public void GetComponents()
{ {
@ -161,7 +233,7 @@ namespace Tests
public Entity entity; public Entity entity;
} }
[Reads(typeof(HasComponentTestMessage))] [Reads(typeof(HasComponentTestMessage), typeof(MockComponent))]
class HasComponentTestEngine : Engine class HasComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -201,7 +273,7 @@ namespace Tests
public Entity entity; public Entity entity;
} }
[Reads(typeof(HasComponentWhenInactiveTestMessage))] [Reads(typeof(HasComponentWhenInactiveTestMessage), typeof(MockComponent))]
class HasComponentWhenInactiveTestEngine : Engine class HasComponentWhenInactiveTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -244,7 +316,6 @@ namespace Tests
} }
[Reads(typeof(RemoveComponentTestMessage))] [Reads(typeof(RemoveComponentTestMessage))]
[Writes(typeof(MockComponent))]
class RemoveComponentTestEngine : Engine class RemoveComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -318,15 +389,15 @@ namespace Tests
public Guid componentID; public Guid componentID;
} }
[Activates(typeof(MockComponent))]
[Reads(typeof(ActivateComponentMessage))] [Reads(typeof(ActivateComponentMessage))]
[Writes(typeof(MockComponent))]
class ActivateComponentEngine : Engine class ActivateComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var activateComponentMessage in ReadMessages<ActivateComponentMessage>()) foreach (var activateComponentMessage in ReadMessages<ActivateComponentMessage>())
{ {
ActivateComponent(activateComponentMessage.componentID); ActivateComponent<MockComponent>(activateComponentMessage.componentID);
} }
} }
} }
@ -367,7 +438,7 @@ namespace Tests
} }
} }
[Reads(typeof(CheckHasMockComponentMessage))] [Reads(typeof(CheckHasMockComponentMessage), typeof(MockComponent))]
class CheckHasMockComponentEngine : Engine class CheckHasMockComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -386,6 +457,8 @@ namespace Tests
} }
} }
// TODO: need to rethink this test because ActivateComponent is instant now
[Test] [Test]
public void ActivateComponent() public void ActivateComponent()
{ {

View File

@ -23,6 +23,7 @@ namespace Tests
static List<MockMessage> resultMessages; static List<MockMessage> resultMessages;
[Reads(typeof(MockComponent))]
public class ReadComponentsTestEngine : Engine public class ReadComponentsTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -32,6 +33,7 @@ namespace Tests
} }
[Reads(typeof(MockComponent))]
public class ReadComponentTestEngine : Engine public class ReadComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -117,6 +119,7 @@ namespace Tests
Assert.Throws<InvalidOperationException>(() => world.Update(0.01f)); Assert.Throws<InvalidOperationException>(() => world.Update(0.01f));
} }
[Reads(typeof(MockComponent))]
[Writes(typeof(MockComponent))] [Writes(typeof(MockComponent))]
public class UpdateComponentTestEngine : Engine public class UpdateComponentTestEngine : Engine
{ {
@ -136,6 +139,7 @@ namespace Tests
public void UpdateComponent() public void UpdateComponent()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new UpdateComponentTestEngine()); worldBuilder.AddEngine(new UpdateComponentTestEngine());
worldBuilder.AddEngine(new ReadComponentTestEngine()); worldBuilder.AddEngine(new ReadComponentTestEngine());
@ -156,11 +160,12 @@ namespace Tests
Assert.AreEqual("blaze it", resultComponent.myString); Assert.AreEqual("blaze it", resultComponent.myString);
} }
[Reads(typeof(MockComponent))]
public class UndeclaredUpdateComponentTestEngine : Engine public class UndeclaredUpdateComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
(var componentID, var component) = this.ReadComponent<MockComponent>(); (var componentID, var component) = ReadComponent<MockComponent>();
component.myInt = 420; component.myInt = 420;
component.myString = "blaze it"; component.myString = "blaze it";
@ -358,6 +363,7 @@ namespace Tests
static ValueTuple<Guid, MockComponent> pairA; static ValueTuple<Guid, MockComponent> pairA;
static ValueTuple<Guid, MockComponent> pairB; static ValueTuple<Guid, MockComponent> pairB;
[Reads(typeof(MockComponent))]
class SameValueComponentReadEngine : Engine class SameValueComponentReadEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -397,6 +403,7 @@ namespace Tests
static IEnumerable<ValueTuple<Guid, MockComponent>> emptyComponentReadResult; static IEnumerable<ValueTuple<Guid, MockComponent>> emptyComponentReadResult;
[Reads(typeof(MockComponent))]
class ReadEmptyMockComponentsEngine : Engine class ReadEmptyMockComponentsEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -419,6 +426,7 @@ namespace Tests
struct DestroyerComponent : IComponent { } struct DestroyerComponent : IComponent { }
[Reads(typeof(DestroyerComponent))]
class DestroyerEngine : Engine class DestroyerEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -434,6 +442,7 @@ namespace Tests
static IEnumerable<ValueTuple<Guid, MockComponent>> results; static IEnumerable<ValueTuple<Guid, MockComponent>> results;
[Reads(typeof(MockComponent))]
class ReaderEngine : Engine class ReaderEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -471,6 +480,7 @@ namespace Tests
Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); Assert.That(results, Does.Not.Contain((componentBID, mockComponent)));
} }
[Reads(typeof(DestroyerComponent), typeof(MockComponent))]
[Writes(typeof(MockComponent))] [Writes(typeof(MockComponent))]
class DestroyAndAddComponentEngine : Engine class DestroyAndAddComponentEngine : Engine
{ {
@ -506,6 +516,7 @@ namespace Tests
static Entity entityFromComponentIDResult; static Entity entityFromComponentIDResult;
[Reads(typeof(MockComponent))]
class GetEntityFromComponentIDEngine : Engine class GetEntityFromComponentIDEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -536,6 +547,7 @@ namespace Tests
static MockComponent mockComponentByIDResult; static MockComponent mockComponentByIDResult;
[Reads(typeof(MockComponent))]
class GetComponentByIDEngine : Engine class GetComponentByIDEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -565,6 +577,7 @@ namespace Tests
struct OtherComponent : IComponent { } struct OtherComponent : IComponent { }
[Reads(typeof(MockComponent))]
class GetComponentByIDWithTypeMismatchEngine : Engine class GetComponentByIDWithTypeMismatchEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -595,6 +608,7 @@ namespace Tests
struct EntityIDComponent : IComponent { public Guid entityID; } struct EntityIDComponent : IComponent { public Guid entityID; }
static bool hasEntity; static bool hasEntity;
[Reads(typeof(EntityIDComponent))]
class HasEntityTestEngine : Engine class HasEntityTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)

View File

@ -23,7 +23,7 @@ namespace Tests
} }
} }
[Writes(typeof(TestComponent))] [Activates(typeof(TestComponent))]
class TestSpawner : Spawner<SpawnMessageA> class TestSpawner : Spawner<SpawnMessageA>
{ {
protected override void Spawn(SpawnMessageA message) protected override void Spawn(SpawnMessageA message)