From a42123f58da385d559cb4b517d6b12ca54e0308c Mon Sep 17 00:00:00 2001 From: thatcosmonaut Date: Thu, 18 Jul 2019 14:02:57 -0700 Subject: [PATCH] the big ComponentMessage rework --- TODO | 11 +- encompass-cs/Attributes/Activates.cs | 26 +++++ encompass-cs/Attributes/Reads.cs | 24 ++-- encompass-cs/ComponentManager.cs | 33 +----- encompass-cs/ComponentMessage.cs | 11 ++ encompass-cs/Engine.cs | 103 +++++++++++++++--- .../Engines/ComponentMessageEmitter.cs | 30 +++++ encompass-cs/Entity.cs | 32 +++++- .../Exceptions/IllegalActivateException.cs | 12 ++ .../IllegalActivateTypeException.cs | 12 ++ .../UnregisteredComponentReadException.cs | 12 ++ encompass-cs/Renderer.cs | 4 +- encompass-cs/World.cs | 1 - encompass-cs/WorldBuilder.cs | 102 +++++++++-------- test/ComponentTest.cs | 97 +++++++++++++++-- test/EngineTest.cs | 16 ++- test/SpawnerTest.cs | 2 +- 17 files changed, 409 insertions(+), 119 deletions(-) create mode 100644 encompass-cs/Attributes/Activates.cs create mode 100644 encompass-cs/ComponentMessage.cs create mode 100644 encompass-cs/Engines/ComponentMessageEmitter.cs create mode 100644 encompass-cs/Exceptions/IllegalActivateException.cs create mode 100644 encompass-cs/Exceptions/IllegalActivateTypeException.cs create mode 100644 encompass-cs/Exceptions/UnregisteredComponentReadException.cs diff --git a/TODO b/TODO index ff167bf..b4f2eff 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,6 @@ -- guard rail tests for nonexistent lookups in manager classes -- custom exception for nonexistent Entity ID -- custom exception for nonexistent Component ID +- Change "Writes" To "Sends" and make it only take Messages +- Add "Updates" for updating components -- special Engine kinds (detector, modifier, spawner) - -- maybe AddEngine should take a constructed engine similarly to AddComponent? - -- component getters should return ValueTuple instead of KeyValuePair so we can do destructuring assignments +- so we have four attributes: Activates, Reads, Updates, and Sends - docs diff --git a/encompass-cs/Attributes/Activates.cs b/encompass-cs/Attributes/Activates.cs new file mode 100644 index 0000000..d35e72b --- /dev/null +++ b/encompass-cs/Attributes/Activates.cs @@ -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 activateTypes = new HashSet(); + + 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)); + } + } + } +} \ No newline at end of file diff --git a/encompass-cs/Attributes/Reads.cs b/encompass-cs/Attributes/Reads.cs index b222f38..c060f15 100644 --- a/encompass-cs/Attributes/Reads.cs +++ b/encompass-cs/Attributes/Reads.cs @@ -5,22 +5,32 @@ using Encompass.Exceptions; namespace Encompass { - [System.AttributeUsage(System.AttributeTargets.Class)] - public class Reads : System.Attribute + [AttributeUsage(AttributeTargets.Class)] + public class Reads : Attribute { - public readonly HashSet readTypes; + public readonly HashSet readTypes = new HashSet(); public Reads(params Type[] 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); + } + + if (isComponent) + { + this.readTypes.Add(typeof(ComponentMessage<>).MakeGenericType(readType)); + } + else + { + this.readTypes.Add(readType); } } - - this.readTypes = new HashSet(readTypes); } } } diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index 9f6ca8d..2e908db 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -62,8 +62,7 @@ namespace Encompass entityIDToComponentIDs[entityID].Add(componentID); componentIDToEntityID[componentID] = entityID; - inactiveComponents.Add(componentID); - MarkForActivation(componentID); + activeComponents.Add(componentID); entitiesWithAddedComponents.Add(entityID); @@ -89,11 +88,11 @@ namespace Encompass return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple(id, IDToComponent[id])); } - internal IEnumerable> GetActiveComponentsByType() where TComponent : struct, IComponent + internal IEnumerable> GetActiveComponentsByType() where TComponent : struct, IComponent { return typeToComponentIDs.ContainsKey(typeof(TComponent)) ? - typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple(id, (TComponent)IDToComponent[id])) : - Enumerable.Empty>(); + typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) : + Enumerable.Empty>(); } internal IEnumerable> GetActiveComponentsByType(Type type) @@ -103,16 +102,11 @@ namespace Encompass Enumerable.Empty>(); } - internal ValueTuple GetActiveComponentByType() where TComponent : struct, IComponent - { - return GetActiveComponentsByType().Single(); - } - internal IEnumerable> GetComponentsByEntityAndType(Guid entityID) where TComponent : struct, IComponent { var entityComponentsByType = GetComponentsByEntity(entityID).Where((pair) => componentIDToType[pair.Item1] == typeof(TComponent)).Select((pair) => new ValueTuple(pair.Item1, (TComponent)pair.Item2)); var activeComponentsByType = GetActiveComponentsByType(); - return activeComponentsByType.Intersect(entityComponentsByType); + return activeComponentsByType.Select((triple) => (triple.Item2, triple.Item3)).Intersect(entityComponentsByType); } internal IEnumerable> GetComponentsByEntityAndType(Guid entityID, Type type) @@ -180,22 +174,7 @@ namespace Encompass } } - internal void MarkForActivation(Guid componentID) - { - componentsMarkedForActivation.Add(componentID); - } - - internal void ActivateMarkedComponents() - { - foreach (var componentID in componentsMarkedForActivation) - { - Activate(componentID); - } - - componentsMarkedForActivation.Clear(); - } - - private void Activate(Guid componentID) + internal void Activate(Guid componentID) { if (inactiveComponents.Remove(componentID)) { diff --git a/encompass-cs/ComponentMessage.cs b/encompass-cs/ComponentMessage.cs new file mode 100644 index 0000000..1805a67 --- /dev/null +++ b/encompass-cs/ComponentMessage.cs @@ -0,0 +1,11 @@ +using System; + +namespace Encompass +{ + public struct ComponentMessage : IMessage where TComponent : struct, IComponent + { + public Entity entity; + public Guid componentID; + public TComponent component; + } +} \ No newline at end of file diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 568bc77..c7f6f9b 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -10,6 +10,7 @@ namespace Encompass { internal readonly HashSet writeTypes = new HashSet(); internal readonly HashSet readTypes = new HashSet(); + internal readonly HashSet activateTypes = new HashSet(); private EntityManager entityManager; private ComponentManager componentManager; @@ -28,6 +29,12 @@ namespace Encompass { readTypes = readsAttribute.readTypes; } + + var activatesAttribute = GetType().GetCustomAttribute(false); + if (activatesAttribute != null) + { + activateTypes = activatesAttribute.activateTypes; + } } internal void AssignEntityManager(EntityManager entityManager) @@ -82,29 +89,64 @@ namespace Encompass return (TComponent)componentManager.GetComponentByID(componentID); } - protected IEnumerable> ReadComponents() where TComponent : struct, IComponent + internal IEnumerable> ReadComponentsFromWorld() where TComponent : struct, IComponent { - return componentManager.GetActiveComponentsByType(); - } - - protected ValueTuple ReadComponent() where TComponent : struct, IComponent - { - return componentManager.GetActiveComponentByType(); + return componentManager.GetActiveComponentsByType().Select((triple) => (GetEntity(triple.Item1), triple.Item2, triple.Item3)); } protected Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { - return componentManager.AddComponent(entity.ID, component); + if (!activateTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + + var componentID = componentManager.AddComponent(entity.ID, component); + + ComponentMessage componentMessage; + componentMessage.entity = entity; + componentMessage.componentID = componentID; + componentMessage.component = component; + EmitMessage(componentMessage); + + return componentID; } protected Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent { - return componentManager.AddDrawComponent(entity.ID, component, layer); + if (!activateTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + + var componentID = componentManager.AddDrawComponent(entity.ID, component, layer); + + ComponentMessage componentMessage; + componentMessage.entity = entity; + componentMessage.componentID = componentID; + componentMessage.component = component; + EmitMessage(componentMessage); + + return componentID; } - protected void ActivateComponent(Guid componentID) + protected void ActivateComponent(Guid componentID) where TComponent : struct, IComponent { - componentManager.MarkForActivation(componentID); + if (!activateTypes.Contains(typeof(ComponentMessage))) + { + 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(componentID); + + ComponentMessage componentMessage; + componentMessage.entity = entity; + componentMessage.componentID = componentID; + componentMessage.component = component; + EmitMessage(componentMessage); + + componentManager.Activate(componentID); } protected void DeactivateComponent(Guid componentID) @@ -114,6 +156,11 @@ namespace Encompass protected IEnumerable> GetComponents(Entity entity) where TComponent : struct, IComponent { + if (!readTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + return componentManager.GetComponentsByEntityAndType(entity.ID); } @@ -124,7 +171,12 @@ namespace Encompass protected bool HasComponent(Entity entity) where TComponent : struct, IComponent { - return componentManager.EntityHasComponentOfType(entity.ID); + if (!readTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + + return messageManager.GetMessagesByType>().Where((message) => message.entity == entity).Any(); } internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent @@ -162,11 +214,31 @@ namespace Encompass return messageManager.GetMessagesByType(); } + protected TMessage ReadMessage() where TMessage : struct, IMessage + { + return ReadMessages().Single(); + } + + protected IEnumerable<(Guid, TComponent)> ReadComponents() where TComponent : struct, IComponent + { + if (!readTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + + return ReadMessages>().Select((message) => (message.componentID, message.component)); + } + + protected (Guid, TComponent) ReadComponent() where TComponent : struct, IComponent + { + return ReadComponents().Single(); + } + protected bool SomeMessage() where TMessage : struct, IMessage { 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().Any(); @@ -174,6 +246,11 @@ namespace Encompass protected bool SomeComponent() where TComponent : struct, IComponent { + if (!readTypes.Contains(typeof(ComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name); + } + return componentManager.GetActiveComponentsByType().Any(); } diff --git a/encompass-cs/Engines/ComponentMessageEmitter.cs b/encompass-cs/Engines/ComponentMessageEmitter.cs new file mode 100644 index 0000000..bbb5cfd --- /dev/null +++ b/encompass-cs/Engines/ComponentMessageEmitter.cs @@ -0,0 +1,30 @@ +using System.Reflection; + +namespace Encompass.Engines +{ + internal class ComponentMessageEmitter : Engine where TComponent : struct, IComponent + { + public ComponentMessageEmitter() : base() + { + var writesAttribute = GetType().GetCustomAttribute(false); + if (writesAttribute != null) + { + writesAttribute.writeTypes.Add(typeof(ComponentMessage)); + } + + writeTypes.Add(typeof(ComponentMessage)); + } + + public override void Update(double dt) + { + foreach (var (entity, componentID, component) in ReadComponentsFromWorld()) + { + ComponentMessage componentMessage; + componentMessage.entity = entity; + componentMessage.componentID = componentID; + componentMessage.component = component; + EmitMessage(componentMessage); + } + } + } +} \ No newline at end of file diff --git a/encompass-cs/Entity.cs b/encompass-cs/Entity.cs index fd45ad5..080697d 100644 --- a/encompass-cs/Entity.cs +++ b/encompass-cs/Entity.cs @@ -2,7 +2,7 @@ namespace Encompass { - public struct Entity + public struct Entity : IEquatable { public readonly Guid ID; @@ -10,5 +10,35 @@ namespace Encompass { 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(); + } } } diff --git a/encompass-cs/Exceptions/IllegalActivateException.cs b/encompass-cs/Exceptions/IllegalActivateException.cs new file mode 100644 index 0000000..f13c9e8 --- /dev/null +++ b/encompass-cs/Exceptions/IllegalActivateException.cs @@ -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)) { } + } +} diff --git a/encompass-cs/Exceptions/IllegalActivateTypeException.cs b/encompass-cs/Exceptions/IllegalActivateTypeException.cs new file mode 100644 index 0000000..f167151 --- /dev/null +++ b/encompass-cs/Exceptions/IllegalActivateTypeException.cs @@ -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)) { } + } +} diff --git a/encompass-cs/Exceptions/UnregisteredComponentReadException.cs b/encompass-cs/Exceptions/UnregisteredComponentReadException.cs new file mode 100644 index 0000000..13891cc --- /dev/null +++ b/encompass-cs/Exceptions/UnregisteredComponentReadException.cs @@ -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)) { } + } +} diff --git a/encompass-cs/Renderer.cs b/encompass-cs/Renderer.cs index 1e122b2..800ffb9 100644 --- a/encompass-cs/Renderer.cs +++ b/encompass-cs/Renderer.cs @@ -41,12 +41,12 @@ namespace Encompass protected IEnumerable> ReadComponents() where TComponent : struct, IComponent { - return componentManager.GetActiveComponentsByType(); + return componentManager.GetActiveComponentsByType().Select((triple) => (triple.Item2, triple.Item3)); } protected ValueTuple ReadComponent() where TComponent : struct, IComponent { - return componentManager.GetActiveComponentByType(); + return ReadComponents().Single(); } protected IEnumerable> GetComponents(Entity entity) where TComponent : struct, IComponent diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index 9e4feb3..a17623c 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -36,7 +36,6 @@ namespace Encompass entityManager.DestroyMarkedEntities(); componentManager.PerformComponentUpdates(); - componentManager.ActivateMarkedComponents(); componentManager.DeactivateMarkedComponents(); componentManager.RemoveMarkedComponents(); diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 32ba70a..c7ed01a 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Reflection; using System.Linq; using Encompass.Exceptions; +using Encompass.Engines; namespace Encompass { @@ -17,9 +18,12 @@ namespace Encompass private readonly DrawLayerManager drawLayerManager; private readonly RenderManager renderManager; - private readonly Dictionary> typeToEmitters = new Dictionary>(); private readonly Dictionary> typeToReaders = new Dictionary>(); + private readonly HashSet senders = new HashSet(); + + private readonly HashSet registeredComponentTypes = new HashSet(); + public WorldBuilder() { var entitiesWithAddedComponents = new HashSet(); @@ -56,6 +60,12 @@ namespace Encompass componentManager.MarkForDeactivation(componentID); } + internal void RegisterComponent(Type componentType) + { + registeredComponentTypes.Add(componentType); + AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType))); + } + public Engine AddEngine(TEngine engine) where TEngine : Engine { engine.AssignEntityManager(entityManager); @@ -65,60 +75,42 @@ namespace Encompass engines.Add(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)) - { - typeToEmitters.Add(writeType, new HashSet()); - } - - typeToEmitters[writeType].Add(engine); - - if (typeToReaders.ContainsKey(writeType)) - { - foreach (var reader in typeToReaders[writeType]) - { - if (engine == reader) - { - if (writeType.GetInterfaces().Contains(typeof(IMessage))) - { - throw new EngineMessageSelfCycleException("Engine both reads and writes Message {0}", writeType.Name); - } - } - else - { - engineGraph.AddEdge(engine, reader); - } - } - } + engine.writeTypes.Add(activateType); } - foreach (var readType in engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)))) + var messageReadTypes = engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))); + var messageSendTypes = engine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))); + + if (messageReadTypes.Intersect(messageSendTypes).Any()) { + var type = messageReadTypes.Intersect(messageSendTypes).First(); + throw new EngineMessageSelfCycleException("Engine {0} both reads and writes Message {1}", engine.GetType().Name, type.Name); + } + + if (messageSendTypes.Any()) + { + 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); + } + } + if (!typeToReaders.ContainsKey(readType)) { typeToReaders.Add(readType, new HashSet()); } 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; @@ -148,8 +140,27 @@ namespace Encompass 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() { + BuildEngineGraph(); + if (engineGraph.Cyclic()) { var cycles = engineGraph.SimpleCycles(); @@ -225,7 +236,6 @@ namespace Encompass ); componentManager.PerformComponentUpdates(); - componentManager.ActivateMarkedComponents(); componentManager.DeactivateMarkedComponents(); componentManager.RemoveMarkedComponents(); diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index a32018f..c8aa299 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -24,7 +24,7 @@ namespace Tests static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>(); static (Guid, MockComponent) gottenMockComponentIDPair; - [Reads(typeof(EntityMessage))] + [Reads(typeof(EntityMessage), typeof(MockComponent))] class GetMockComponentsEngine : Engine { public override void Update(double dt) @@ -38,7 +38,7 @@ namespace Tests } } - [Reads(typeof(EntityMessage))] + [Reads(typeof(EntityMessage), typeof(MockComponent))] class GetMockComponentEngine : Engine { public override void Update(double dt) @@ -56,8 +56,8 @@ namespace Tests public MockComponent mockComponent; } - [Reads(typeof(AddComponentTestMessage))] - class AddComponentEngine : Engine + [Reads(typeof(AddComponentTestMessage), typeof(MockComponent))] + class AddComponentTestEngine : Engine { public override void Update(double dt) { @@ -73,8 +73,7 @@ namespace Tests public void AddComponent() { var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new AddComponentEngine()); - worldBuilder.AddEngine(new GetMockComponentEngine()); + worldBuilder.AddEngine(new AddComponentTestEngine()); var entity = worldBuilder.CreateEntity(); @@ -94,6 +93,79 @@ namespace Tests 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()) + { + 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(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] public void GetComponents() { @@ -161,7 +233,7 @@ namespace Tests public Entity entity; } - [Reads(typeof(HasComponentTestMessage))] + [Reads(typeof(HasComponentTestMessage), typeof(MockComponent))] class HasComponentTestEngine : Engine { public override void Update(double dt) @@ -201,7 +273,7 @@ namespace Tests public Entity entity; } - [Reads(typeof(HasComponentWhenInactiveTestMessage))] + [Reads(typeof(HasComponentWhenInactiveTestMessage), typeof(MockComponent))] class HasComponentWhenInactiveTestEngine : Engine { public override void Update(double dt) @@ -244,7 +316,6 @@ namespace Tests } [Reads(typeof(RemoveComponentTestMessage))] - [Writes(typeof(MockComponent))] class RemoveComponentTestEngine : Engine { public override void Update(double dt) @@ -318,15 +389,15 @@ namespace Tests public Guid componentID; } + [Activates(typeof(MockComponent))] [Reads(typeof(ActivateComponentMessage))] - [Writes(typeof(MockComponent))] class ActivateComponentEngine : Engine { public override void Update(double dt) { foreach (var activateComponentMessage in ReadMessages()) { - ActivateComponent(activateComponentMessage.componentID); + ActivateComponent(activateComponentMessage.componentID); } } } @@ -367,7 +438,7 @@ namespace Tests } } - [Reads(typeof(CheckHasMockComponentMessage))] + [Reads(typeof(CheckHasMockComponentMessage), typeof(MockComponent))] class CheckHasMockComponentEngine : Engine { public override void Update(double dt) @@ -386,6 +457,8 @@ namespace Tests } } + // TODO: need to rethink this test because ActivateComponent is instant now + [Test] public void ActivateComponent() { diff --git a/test/EngineTest.cs b/test/EngineTest.cs index d975185..a71982c 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -23,6 +23,7 @@ namespace Tests static List resultMessages; + [Reads(typeof(MockComponent))] public class ReadComponentsTestEngine : Engine { public override void Update(double dt) @@ -32,6 +33,7 @@ namespace Tests } + [Reads(typeof(MockComponent))] public class ReadComponentTestEngine : Engine { public override void Update(double dt) @@ -117,6 +119,7 @@ namespace Tests Assert.Throws(() => world.Update(0.01f)); } + [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { @@ -136,6 +139,7 @@ namespace Tests public void UpdateComponent() { var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new UpdateComponentTestEngine()); worldBuilder.AddEngine(new ReadComponentTestEngine()); @@ -156,11 +160,12 @@ namespace Tests Assert.AreEqual("blaze it", resultComponent.myString); } + [Reads(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) { - (var componentID, var component) = this.ReadComponent(); + (var componentID, var component) = ReadComponent(); component.myInt = 420; component.myString = "blaze it"; @@ -358,6 +363,7 @@ namespace Tests static ValueTuple pairA; static ValueTuple pairB; + [Reads(typeof(MockComponent))] class SameValueComponentReadEngine : Engine { public override void Update(double dt) @@ -397,6 +403,7 @@ namespace Tests static IEnumerable> emptyComponentReadResult; + [Reads(typeof(MockComponent))] class ReadEmptyMockComponentsEngine : Engine { public override void Update(double dt) @@ -419,6 +426,7 @@ namespace Tests struct DestroyerComponent : IComponent { } + [Reads(typeof(DestroyerComponent))] class DestroyerEngine : Engine { public override void Update(double dt) @@ -434,6 +442,7 @@ namespace Tests static IEnumerable> results; + [Reads(typeof(MockComponent))] class ReaderEngine : Engine { public override void Update(double dt) @@ -471,6 +480,7 @@ namespace Tests Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); } + [Reads(typeof(DestroyerComponent), typeof(MockComponent))] [Writes(typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { @@ -506,6 +516,7 @@ namespace Tests static Entity entityFromComponentIDResult; + [Reads(typeof(MockComponent))] class GetEntityFromComponentIDEngine : Engine { public override void Update(double dt) @@ -536,6 +547,7 @@ namespace Tests static MockComponent mockComponentByIDResult; + [Reads(typeof(MockComponent))] class GetComponentByIDEngine : Engine { public override void Update(double dt) @@ -565,6 +577,7 @@ namespace Tests struct OtherComponent : IComponent { } + [Reads(typeof(MockComponent))] class GetComponentByIDWithTypeMismatchEngine : Engine { public override void Update(double dt) @@ -595,6 +608,7 @@ namespace Tests struct EntityIDComponent : IComponent { public Guid entityID; } static bool hasEntity; + [Reads(typeof(EntityIDComponent))] class HasEntityTestEngine : Engine { public override void Update(double dt) diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index 564eeba..bfc7646 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -23,7 +23,7 @@ namespace Tests } } - [Writes(typeof(TestComponent))] + [Activates(typeof(TestComponent))] class TestSpawner : Spawner { protected override void Spawn(SpawnMessageA message)