From 7ab512e52289b3dedbe0cf2ef6c379fe0b15259e Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Tue, 16 Jul 2019 09:47:58 -0700 Subject: [PATCH 1/6] started reworking component mutation --- encompass-cs/Engine.cs | 28 +++---- encompass-cs/Engines/Detector.cs | 59 -------------- encompass-cs/Engines/Spawner.cs | 4 +- encompass-cs/WorldBuilder.cs | 41 +++++----- encompass-cs/attributes/Emits.cs | 8 +- encompass-cs/attributes/Mutates.cs | 16 ---- encompass-cs/attributes/Reads.cs | 4 +- test/DetectorTest.cs | 123 ----------------------------- test/EngineTest.cs | 8 +- test/SpawnerTest.cs | 2 +- test/WorldBuilderTest.cs | 24 +++--- 11 files changed, 57 insertions(+), 260 deletions(-) delete mode 100644 encompass-cs/Engines/Detector.cs delete mode 100644 encompass-cs/attributes/Mutates.cs delete mode 100644 test/DetectorTest.cs diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 027830e..ba5861c 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -8,9 +8,8 @@ namespace Encompass { public abstract class Engine { - internal readonly List mutateComponentTypes = new List(); - internal readonly List emitMessageTypes = new List(); - internal readonly List readMessageTypes = new List(); + internal readonly List writeTypes = new List(); + internal readonly List readTypes = new List(); private EntityManager entityManager; private ComponentManager componentManager; @@ -18,22 +17,16 @@ namespace Encompass protected Engine() { - var mutatesAttribute = GetType().GetCustomAttribute(false); - if (mutatesAttribute != null) - { - mutateComponentTypes = mutatesAttribute.mutateComponentTypes; - } - - var emitsAttribute = GetType().GetCustomAttribute(false); + var emitsAttribute = GetType().GetCustomAttribute(false); if (emitsAttribute != null) { - emitMessageTypes = emitsAttribute.emitMessageTypes; + writeTypes = emitsAttribute.writeTypes; } var readsAttribute = GetType().GetCustomAttribute(false); if (readsAttribute != null) { - readMessageTypes = readsAttribute.readMessageTypes; + readTypes = readsAttribute.readTypes; } } @@ -81,6 +74,7 @@ namespace Encompass protected TComponent GetComponentByID(Guid componentID) where TComponent : struct, IComponent { + if (componentManager.GetComponentTypeByID(componentID) == typeof(TComponent)) { return (TComponent)componentManager.GetComponentByID(componentID); @@ -103,13 +97,13 @@ namespace Encompass internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent { - if (mutateComponentTypes.Contains(typeof(TComponent))) + if (writeTypes.Contains(typeof(TComponent))) { componentManager.UpdateComponent(componentID, newComponent); } else { - throw new IllegalComponentMutationException("Engine {0} tried to mutate undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + throw new IllegalComponentMutationException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); } } @@ -120,7 +114,7 @@ namespace Encompass protected void EmitMessage(TMessage message) where TMessage : struct, IMessage { - if (emitMessageTypes.Contains(typeof(TMessage))) + if (writeTypes.Contains(typeof(TMessage))) { messageManager.AddMessage(message); } @@ -132,7 +126,7 @@ namespace Encompass protected IEnumerable ReadMessages() where TMessage : struct, IMessage { - if (readMessageTypes.Contains(typeof(TMessage))) + if (readTypes.Contains(typeof(TMessage))) { return messageManager.GetMessagesByType(); } @@ -144,7 +138,7 @@ namespace Encompass protected bool Some() where TMessage : struct, IMessage { - if (readMessageTypes.Contains(typeof(TMessage))) + if (readTypes.Contains(typeof(TMessage))) { return messageManager.GetMessagesByType().Any(); } diff --git a/encompass-cs/Engines/Detector.cs b/encompass-cs/Engines/Detector.cs deleted file mode 100644 index 4ead6ac..0000000 --- a/encompass-cs/Engines/Detector.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Reflection; -using System.Collections.Generic; -using Encompass.Exceptions; - -namespace Encompass.Engines -{ - public abstract class Detector : Engine, IEntityTracker - { - private readonly List componentTypes = new List(); - private readonly EntityTracker entityTracker = new EntityTracker(); - - public IEnumerable ComponentTypes { get { return componentTypes; } } - - protected Detector() : base() - { - var detectsAttribute = GetType().GetCustomAttribute(false); - if (detectsAttribute != null) - { - componentTypes = detectsAttribute.componentTypes; - } - else - { - throw new DetectorWithoutComponentTypesException("Detector {0} does not have any component types declared. Use the Detects attribute to declare component types", GetType().Name); - } - } - - public override void Update(double dt) - { - foreach (var id in entityTracker.TrackedEntityIDs) - { - Detect(GetEntity(id), dt); - } - } - - public abstract void Detect(Entity entity, double dt); - - public bool CheckAndTrackEntity(Guid entityID) - { - var entity = GetEntity(entityID); - var shouldTrack = CheckEntity(entity); - if (shouldTrack) { entityTracker.TrackEntity(entityID); } - return shouldTrack; - } - - public bool CheckAndUntrackEntity(Guid entityID) - { - var entity = GetEntity(entityID); - var shouldUntrack = !CheckEntity(entity); - if (shouldUntrack) { entityTracker.UntrackEntity(entityID); } - return shouldUntrack; - } - - private bool CheckEntity(Entity entity) - { - return EntityChecker.CheckEntity(entity, componentTypes); - } - } -} diff --git a/encompass-cs/Engines/Spawner.cs b/encompass-cs/Engines/Spawner.cs index c91e4bf..b4c1901 100644 --- a/encompass-cs/Engines/Spawner.cs +++ b/encompass-cs/Engines/Spawner.cs @@ -9,10 +9,10 @@ namespace Encompass.Engines var readsAttribute = GetType().GetCustomAttribute(false); if (readsAttribute != null) { - readsAttribute.readMessageTypes.Add(typeof(TMessage)); + readsAttribute.readTypes.Add(typeof(TMessage)); } - readMessageTypes.Add(typeof(TMessage)); + readTypes.Add(typeof(TMessage)); } public override void Update(double dt) diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 849dfa4..6c4edf1 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -55,7 +55,7 @@ namespace Encompass entityManager.RegisterEntityTracker(engine as IEntityTracker); } - foreach (var emitMessageType in engine.emitMessageTypes) + foreach (var emitMessageType in engine.writeTypes) { if (!messageTypeToEmitters.ContainsKey(emitMessageType)) { @@ -73,7 +73,7 @@ namespace Encompass } } - foreach (var readMessageType in engine.readMessageTypes) + foreach (var readMessageType in engine.readTypes) { if (!messageTypeToReaders.ContainsKey(readMessageType)) { @@ -140,37 +140,40 @@ namespace Encompass foreach (var engine in engines) { - var mutateAttribute = engine.GetType().GetCustomAttribute(false); - if (mutateAttribute != null) + var writeAttribute = engine.GetType().GetCustomAttribute(false); + if (writeAttribute != null) { - foreach (var mutateComponentType in engine.GetType().GetCustomAttribute(false).mutateComponentTypes) + foreach (var writeType in writeAttribute.writeTypes) { - if (mutatedComponentTypes.Contains(mutateComponentType)) + if (writeType.GetInterfaces().Contains(typeof(IComponent))) // if our write type is a component { - duplicateMutations.Add(mutateComponentType); - } - else - { - mutatedComponentTypes.Add(mutateComponentType); - } + if (mutatedComponentTypes.Contains(writeType)) + { + duplicateMutations.Add(writeType); + } + else + { + mutatedComponentTypes.Add(writeType); + } - if (!componentToEngines.ContainsKey(mutateComponentType)) - { - componentToEngines[mutateComponentType] = new List(); - } + if (!componentToEngines.ContainsKey(writeType)) + { + componentToEngines[writeType] = new List(); + } - componentToEngines[mutateComponentType].Add(engine); + componentToEngines[writeType].Add(engine); + } } } } if (duplicateMutations.Count > 0) { - var errorString = "Multiple Engines mutate the same Component: "; + var errorString = "Multiple Engines write the same Component: "; foreach (var componentType in duplicateMutations) { errorString += "\n" + - componentType.Name + " mutated by: " + + componentType.Name + " written by: " + string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name)); } diff --git a/encompass-cs/attributes/Emits.cs b/encompass-cs/attributes/Emits.cs index ff6082f..75ab92d 100644 --- a/encompass-cs/attributes/Emits.cs +++ b/encompass-cs/attributes/Emits.cs @@ -4,13 +4,13 @@ using System.Collections.Generic; namespace Encompass { [AttributeUsage(AttributeTargets.Class)] - public class Emits : Attribute + public class Writes : Attribute { - public readonly List emitMessageTypes; + public readonly List writeTypes; - public Emits(params Type[] emitMessageTypes) + public Writes(params Type[] writeTypes) { - this.emitMessageTypes = new List(emitMessageTypes); + this.writeTypes = new List(writeTypes); } } } diff --git a/encompass-cs/attributes/Mutates.cs b/encompass-cs/attributes/Mutates.cs deleted file mode 100644 index ed147b6..0000000 --- a/encompass-cs/attributes/Mutates.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Encompass -{ - [System.AttributeUsage(System.AttributeTargets.Class)] - public class Mutates : System.Attribute - { - public readonly List mutateComponentTypes; - - public Mutates(params Type[] mutateComponentTypes) - { - this.mutateComponentTypes = new List(mutateComponentTypes); - } - } -} diff --git a/encompass-cs/attributes/Reads.cs b/encompass-cs/attributes/Reads.cs index d738d20..0ca3ee6 100644 --- a/encompass-cs/attributes/Reads.cs +++ b/encompass-cs/attributes/Reads.cs @@ -6,11 +6,11 @@ namespace Encompass [System.AttributeUsage(System.AttributeTargets.Class)] public class Reads : System.Attribute { - public readonly List readMessageTypes; + public readonly List readTypes; public Reads(params Type[] readMessageTypes) { - this.readMessageTypes = new List(readMessageTypes); + this.readTypes = new List(readMessageTypes); } } } diff --git a/test/DetectorTest.cs b/test/DetectorTest.cs deleted file mode 100644 index 174ef2f..0000000 --- a/test/DetectorTest.cs +++ /dev/null @@ -1,123 +0,0 @@ -using NUnit.Framework; -using FluentAssertions; - -using Encompass; -using Encompass.Engines; -using Encompass.Exceptions; -using System; -using System.Collections.Generic; - -namespace Tests -{ - class DetectorTest - { - class NoComponentTypesDetector : Detector - { - public override void Detect(Entity entity, double dt) { } - } - - [Test] - public void DetectorWithNoComponentTypes() - { - var worldBuilder = new WorldBuilder(); - - Action addEngine = () => worldBuilder.AddEngine(new NoComponentTypesDetector()); - addEngine.Should().Throw(); - } - - struct AComponent : IComponent { } - struct BComponent : IComponent { } - struct CComponent : IComponent { } - - static List trackedEntities = new List(); - - [Detects(typeof(AComponent), typeof(BComponent))] - class TestDetector : Detector - { - public override void Detect(Entity entity, double dt) - { - trackedEntities.Add(entity); - } - } - - [Test] - public void CheckAndTrackEntities() - { - var worldBuilder = new WorldBuilder(); - var detector = worldBuilder.AddEngine(new TestDetector()); - - var entityToTrack = worldBuilder.CreateEntity(); - entityToTrack.AddComponent(new AComponent()); - entityToTrack.AddComponent(new BComponent()); - - var entityNotToTrack = worldBuilder.CreateEntity(); - entityNotToTrack.AddComponent(new AComponent()); - entityNotToTrack.AddComponent(new CComponent()); - - var entityWithDeactivatedComponents = worldBuilder.CreateEntity(); - var aComponent = entityWithDeactivatedComponents.AddComponent(new AComponent()); - entityWithDeactivatedComponents.AddComponent(new BComponent()); - entityWithDeactivatedComponents.DeactivateComponent(aComponent); - - var entityWithOneDeactivatedComponent = worldBuilder.CreateEntity(); - var inactiveComponent = entityWithOneDeactivatedComponent.AddComponent(new AComponent()); - entityWithOneDeactivatedComponent.AddComponent(new AComponent()); - entityWithOneDeactivatedComponent.AddComponent(new BComponent()); - entityWithOneDeactivatedComponent.DeactivateComponent(inactiveComponent); - - var world = worldBuilder.Build(); - world.Update(0.01); - - trackedEntities.Should().Contain(entityToTrack); - trackedEntities.Should().NotContain(entityNotToTrack); - trackedEntities.Should().NotContain(entityWithDeactivatedComponents); - trackedEntities.Should().Contain(entityWithOneDeactivatedComponent); - } - - [Test] - public void EntityUntrackedWhenComponentRemoved() - { - var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new TestDetector()); - - var entityToUntrack = worldBuilder.CreateEntity(); - entityToUntrack.AddComponent(new AComponent()); - var bComponent = entityToUntrack.AddComponent(new BComponent()); - - var world = worldBuilder.Build(); - - // have to update twice because we are updating from outside the world - entityToUntrack.RemoveComponent(bComponent); - world.Update(0.01); - - trackedEntities.Clear(); - world.Update(0.01); - - trackedEntities.Should().NotContain(entityToUntrack); - } - - [Test] - public void DetectCalledPerTrackedEntityOnWorldUpdat() - { - var worldBuilder = new WorldBuilder(); - worldBuilder.AddEngine(new TestDetector()); - - var entityOne = worldBuilder.CreateEntity(); - entityOne.AddComponent(new AComponent()); - entityOne.AddComponent(new BComponent()); - - var entityTwo = worldBuilder.CreateEntity(); - entityTwo.AddComponent(new AComponent()); - entityTwo.AddComponent(new BComponent()); - - trackedEntities.Clear(); - - var world = worldBuilder.Build(); - - world.Update(0.01); - - trackedEntities.Should().Contain(entityOne); - trackedEntities.Should().Contain(entityTwo); - } - } -} diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 4c844d3..f47781d 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -110,7 +110,7 @@ namespace Tests Assert.Throws(() => world.Update(0.01f)); } - [Mutates(typeof(MockComponent))] + [Writes(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { public override void Update(double dt) @@ -178,7 +178,7 @@ namespace Tests var world = worldBuilder.Build(); var ex = Assert.Throws(() => world.Update(0.01f)); - Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to mutate undeclared Component MockComponent")); + Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to write undeclared Component MockComponent")); } struct MockMessage : IMessage @@ -186,7 +186,7 @@ namespace Tests public string myString; } - [Emits(typeof(MockMessage))] + [Writes(typeof(MockMessage))] public class MessageEmitEngine : Engine { public override void Update(double dt) @@ -269,7 +269,7 @@ namespace Tests static bool someTest; - [Emits(typeof(MockMessage))] + [Writes(typeof(MockMessage))] class EmitMockMessageEngine : Engine { public override void Update(double dt) diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index ed924d2..446fe4e 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -14,7 +14,7 @@ namespace Tests static Entity resultEntity; - [Emits(typeof(SpawnMessageA))] + [Writes(typeof(SpawnMessageA))] class MessageEmitter : Engine { public override void Update(double dt) diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 59efb82..3267281 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -14,7 +14,7 @@ namespace Tests struct BMessage : IMessage { } [Reads(typeof(AMessage))] - [Emits(typeof(BMessage))] + [Writes(typeof(BMessage))] class AEngine : Engine { public override void Update(double dt) @@ -25,7 +25,7 @@ namespace Tests } [Reads(typeof(BMessage))] - [Emits(typeof(AMessage))] + [Writes(typeof(AMessage))] class BEngine : Engine { public override void Update(double dt) @@ -54,7 +54,7 @@ namespace Tests struct DMessage : IMessage { } [Reads(typeof(AMessage))] - [Emits(typeof(BMessage))] + [Writes(typeof(BMessage))] class AEngine : Engine { public override void Update(double dt) @@ -65,7 +65,7 @@ namespace Tests } [Reads(typeof(BMessage))] - [Emits(typeof(CMessage))] + [Writes(typeof(CMessage))] class BEngine : Engine { public override void Update(double dt) @@ -76,7 +76,7 @@ namespace Tests } [Reads(typeof(CMessage))] - [Emits(typeof(DMessage))] + [Writes(typeof(DMessage))] class CEngine : Engine { public override void Update(double dt) @@ -87,7 +87,7 @@ namespace Tests } [Reads(typeof(DMessage))] - [Emits(typeof(AMessage))] + [Writes(typeof(AMessage))] class DEngine : Engine { public override void Update(double dt) @@ -114,13 +114,13 @@ namespace Tests { struct AComponent : IComponent { } - [Mutates(typeof(AComponent))] + [Writes(typeof(AComponent))] class AEngine : Engine { public override void Update(double dt) { } } - [Mutates(typeof(AComponent))] + [Writes(typeof(AComponent))] class BEngine : Engine { public override void Update(double dt) { } @@ -149,8 +149,7 @@ namespace Tests struct CMessage : IMessage { } struct DMessage : IMessage { } - [Mutates(typeof(AComponent))] - [Emits(typeof(AMessage))] + [Writes(typeof(AComponent), typeof(AMessage))] class AEngine : Engine { public override void Update(double dt) @@ -159,8 +158,7 @@ namespace Tests } } - [Mutates(typeof(BComponent))] - [Emits(typeof(BMessage))] + [Writes(typeof(BComponent), typeof(BMessage))] class BEngine : Engine { public override void Update(double dt) @@ -170,7 +168,7 @@ namespace Tests } [Reads(typeof(AMessage), typeof(BMessage))] - [Emits(typeof(DMessage))] + [Writes(typeof(DMessage))] class CEngine : Engine { public override void Update(double dt) From 9b58473ae8864b83387eb2998ab0067a4bc11527 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Tue, 16 Jul 2019 11:17:07 -0700 Subject: [PATCH 2/6] engines must declare component reads + validation for Reads and Writes arguments --- encompass-cs/Engine.cs | 61 ++++++++-------- encompass-cs/WorldBuilder.cs | 54 ++++++++++----- encompass-cs/attributes/Emits.cs | 16 ----- encompass-cs/attributes/Reads.cs | 14 +++- encompass-cs/attributes/Writes.cs | 26 +++++++ ....cs => EngineMessageSelfCycleException.cs} | 4 +- ...ion.cs => EngineWriteConflictException.cs} | 4 +- ...adException.cs => IllegalReadException.cs} | 4 +- ...ception.cs => IllegalReadTypeException.cs} | 4 +- .../exceptions/IllegalWriteException.cs | 12 ++++ .../exceptions/IllegalWriteTypeException.cs | 12 ++++ encompass-cs/graph/DirectedGraph.cs | 4 +- test/EngineTest.cs | 23 ++++++- test/WorldBuilderTest.cs | 69 ++++++++++++++++++- 14 files changed, 230 insertions(+), 77 deletions(-) delete mode 100644 encompass-cs/attributes/Emits.cs create mode 100644 encompass-cs/attributes/Writes.cs rename encompass-cs/exceptions/{EngineMutationConflictException.cs => EngineMessageSelfCycleException.cs} (61%) rename encompass-cs/exceptions/{IllegalComponentMutationException.cs => EngineWriteConflictException.cs} (60%) rename encompass-cs/exceptions/{IllegalMessageReadException.cs => IllegalReadException.cs} (63%) rename encompass-cs/exceptions/{IllegalMessageEmitException.cs => IllegalReadTypeException.cs} (63%) create mode 100644 encompass-cs/exceptions/IllegalWriteException.cs create mode 100644 encompass-cs/exceptions/IllegalWriteTypeException.cs diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index ba5861c..9c336df 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -74,37 +74,47 @@ namespace Encompass protected TComponent GetComponentByID(Guid componentID) where TComponent : struct, IComponent { - - if (componentManager.GetComponentTypeByID(componentID) == typeof(TComponent)) + if (!readTypes.Contains(typeof(TComponent))) { - return (TComponent)componentManager.GetComponentByID(componentID); + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); } - else + + if (componentManager.GetComponentTypeByID(componentID) != typeof(TComponent)) { throw new ComponentTypeMismatchException("Expected Component to be of type {0} but was actually of type {1}", typeof(TComponent).Name, componentManager.GetComponentTypeByID(componentID).Name); } + + return (TComponent)componentManager.GetComponentByID(componentID); } protected IEnumerable> ReadComponents() where TComponent : struct, IComponent { + if (!readTypes.Contains(typeof(TComponent))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + return componentManager.GetActiveComponentsByType(); } protected ValueTuple ReadComponent() where TComponent : struct, IComponent { + if (!readTypes.Contains(typeof(TComponent))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + return componentManager.GetActiveComponentByType(); } internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent { - if (writeTypes.Contains(typeof(TComponent))) + if (!writeTypes.Contains(typeof(TComponent))) { - componentManager.UpdateComponent(componentID, newComponent); - } - else - { - throw new IllegalComponentMutationException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); } + + componentManager.UpdateComponent(componentID, newComponent); } protected void UpdateComponent(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent @@ -114,38 +124,33 @@ namespace Encompass protected void EmitMessage(TMessage message) where TMessage : struct, IMessage { - if (writeTypes.Contains(typeof(TMessage))) + if (!writeTypes.Contains(typeof(TMessage))) { - messageManager.AddMessage(message); - } - else - { - throw new IllegalMessageEmitException("Engine {0} tried to emit undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); + throw new IllegalWriteException("Engine {0} tried to emit undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); } + + messageManager.AddMessage(message); + } protected IEnumerable ReadMessages() where TMessage : struct, IMessage { - if (readTypes.Contains(typeof(TMessage))) + if (!readTypes.Contains(typeof(TMessage))) { - return messageManager.GetMessagesByType(); - } - else - { - throw new IllegalMessageReadException("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}", this.GetType().Name, typeof(TMessage).Name); } + + return messageManager.GetMessagesByType(); } protected bool Some() where TMessage : struct, IMessage { - if (readTypes.Contains(typeof(TMessage))) + if (!readTypes.Contains(typeof(TMessage))) { - return messageManager.GetMessagesByType().Any(); - } - else - { - throw new IllegalMessageReadException("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}", this.GetType().Name, typeof(TMessage).Name); } + + return messageManager.GetMessagesByType().Any(); } protected void Destroy(Guid entityID) diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 6c4edf1..3026171 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -17,8 +17,8 @@ namespace Encompass private readonly DrawLayerManager drawLayerManager; private readonly RenderManager renderManager; - private readonly Dictionary> messageTypeToEmitters = new Dictionary>(); - private readonly Dictionary> messageTypeToReaders = new Dictionary>(); + private readonly Dictionary> typeToEmitters = new Dictionary>(); + private readonly Dictionary> typeToReaders = new Dictionary>(); public WorldBuilder() { @@ -55,38 +55,58 @@ namespace Encompass entityManager.RegisterEntityTracker(engine as IEntityTracker); } - foreach (var emitMessageType in engine.writeTypes) + foreach (var writeType in engine.writeTypes) { - if (!messageTypeToEmitters.ContainsKey(emitMessageType)) + if (!typeToEmitters.ContainsKey(writeType)) { - messageTypeToEmitters.Add(emitMessageType, new HashSet()); + typeToEmitters.Add(writeType, new HashSet()); } - messageTypeToEmitters[emitMessageType].Add(engine); + typeToEmitters[writeType].Add(engine); - if (messageTypeToReaders.ContainsKey(emitMessageType)) + if (typeToReaders.ContainsKey(writeType)) { - foreach (var reader in messageTypeToReaders[emitMessageType]) + foreach (var reader in typeToReaders[writeType]) { - engineGraph.AddEdge(engine, reader); + 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); + } } } } - foreach (var readMessageType in engine.readTypes) + foreach (var readType in engine.readTypes) { - if (!messageTypeToReaders.ContainsKey(readMessageType)) + if (!typeToReaders.ContainsKey(readType)) { - messageTypeToReaders.Add(readMessageType, new HashSet()); + typeToReaders.Add(readType, new HashSet()); } - messageTypeToReaders[readMessageType].Add(engine); + typeToReaders[readType].Add(engine); - if (messageTypeToEmitters.ContainsKey(readMessageType)) + if (typeToEmitters.ContainsKey(readType)) { - foreach (var emitter in messageTypeToEmitters[readMessageType]) + foreach (var emitter in typeToEmitters[readType]) { - engineGraph.AddEdge(emitter, engine); + 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); + } } } } @@ -177,7 +197,7 @@ namespace Encompass string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name)); } - throw new EngineMutationConflictException(errorString); + throw new EngineWriteConflictException(errorString); } var engineOrder = new List(); diff --git a/encompass-cs/attributes/Emits.cs b/encompass-cs/attributes/Emits.cs deleted file mode 100644 index 75ab92d..0000000 --- a/encompass-cs/attributes/Emits.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Encompass -{ - [AttributeUsage(AttributeTargets.Class)] - public class Writes : Attribute - { - public readonly List writeTypes; - - public Writes(params Type[] writeTypes) - { - this.writeTypes = new List(writeTypes); - } - } -} diff --git a/encompass-cs/attributes/Reads.cs b/encompass-cs/attributes/Reads.cs index 0ca3ee6..fbf6a0c 100644 --- a/encompass-cs/attributes/Reads.cs +++ b/encompass-cs/attributes/Reads.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; namespace Encompass { @@ -8,9 +10,17 @@ namespace Encompass { public readonly List readTypes; - public Reads(params Type[] readMessageTypes) + public Reads(params Type[] readTypes) { - this.readTypes = new List(readMessageTypes); + foreach (var readType in readTypes) + { + if (!readType.GetInterfaces().Contains(typeof(IMessage)) && !readType.GetInterfaces().Contains(typeof(IComponent))) + { + throw new IllegalReadTypeException("{0} must be a Message or Component", readType.Name); + } + } + + this.readTypes = new List(readTypes); } } } diff --git a/encompass-cs/attributes/Writes.cs b/encompass-cs/attributes/Writes.cs new file mode 100644 index 0000000..5af2c6c --- /dev/null +++ b/encompass-cs/attributes/Writes.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class Writes : Attribute + { + public readonly List writeTypes; + + public Writes(params Type[] writeTypes) + { + foreach (var writeType in writeTypes) + { + if (!writeType.GetInterfaces().Contains(typeof(IMessage)) && !writeType.GetInterfaces().Contains(typeof(IComponent))) + { + throw new IllegalWriteTypeException("{0} must be a Message or Component", writeType.Name); + } + } + + this.writeTypes = new List(writeTypes); + } + } +} diff --git a/encompass-cs/exceptions/EngineMutationConflictException.cs b/encompass-cs/exceptions/EngineMessageSelfCycleException.cs similarity index 61% rename from encompass-cs/exceptions/EngineMutationConflictException.cs rename to encompass-cs/exceptions/EngineMessageSelfCycleException.cs index 22bd8ce..b345295 100644 --- a/encompass-cs/exceptions/EngineMutationConflictException.cs +++ b/encompass-cs/exceptions/EngineMessageSelfCycleException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class EngineMutationConflictException : Exception + public class EngineMessageSelfCycleException : Exception { - public EngineMutationConflictException( + public EngineMessageSelfCycleException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/exceptions/IllegalComponentMutationException.cs b/encompass-cs/exceptions/EngineWriteConflictException.cs similarity index 60% rename from encompass-cs/exceptions/IllegalComponentMutationException.cs rename to encompass-cs/exceptions/EngineWriteConflictException.cs index dfcd7ae..ab1ac08 100644 --- a/encompass-cs/exceptions/IllegalComponentMutationException.cs +++ b/encompass-cs/exceptions/EngineWriteConflictException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalComponentMutationException : Exception + public class EngineWriteConflictException : Exception { - public IllegalComponentMutationException( + public EngineWriteConflictException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/exceptions/IllegalMessageReadException.cs b/encompass-cs/exceptions/IllegalReadException.cs similarity index 63% rename from encompass-cs/exceptions/IllegalMessageReadException.cs rename to encompass-cs/exceptions/IllegalReadException.cs index fe8fb32..8eb5868 100644 --- a/encompass-cs/exceptions/IllegalMessageReadException.cs +++ b/encompass-cs/exceptions/IllegalReadException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalMessageReadException : Exception + public class IllegalReadException : Exception { - public IllegalMessageReadException( + public IllegalReadException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/exceptions/IllegalMessageEmitException.cs b/encompass-cs/exceptions/IllegalReadTypeException.cs similarity index 63% rename from encompass-cs/exceptions/IllegalMessageEmitException.cs rename to encompass-cs/exceptions/IllegalReadTypeException.cs index 411c829..3bc2512 100644 --- a/encompass-cs/exceptions/IllegalMessageEmitException.cs +++ b/encompass-cs/exceptions/IllegalReadTypeException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class IllegalMessageEmitException : Exception + public class IllegalReadTypeException : Exception { - public IllegalMessageEmitException( + public IllegalReadTypeException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/exceptions/IllegalWriteException.cs b/encompass-cs/exceptions/IllegalWriteException.cs new file mode 100644 index 0000000..4c7db90 --- /dev/null +++ b/encompass-cs/exceptions/IllegalWriteException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class IllegalWriteException : Exception + { + public IllegalWriteException( + string format, + params object[] args + ) : base(string.Format(format, args)) { } + } +} diff --git a/encompass-cs/exceptions/IllegalWriteTypeException.cs b/encompass-cs/exceptions/IllegalWriteTypeException.cs new file mode 100644 index 0000000..3c720b6 --- /dev/null +++ b/encompass-cs/exceptions/IllegalWriteTypeException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Encompass.Exceptions +{ + public class IllegalWriteTypeException : Exception + { + public IllegalWriteTypeException( + string format, + params object[] args + ) : base(string.Format(format, args)) { } + } +} diff --git a/encompass-cs/graph/DirectedGraph.cs b/encompass-cs/graph/DirectedGraph.cs index 8f562fa..b60db0d 100644 --- a/encompass-cs/graph/DirectedGraph.cs +++ b/encompass-cs/graph/DirectedGraph.cs @@ -27,7 +27,7 @@ namespace Encompass } protected List _vertices = new List(); - protected Dictionary> _neighbors = new Dictionary>(); + protected Dictionary> _neighbors = new Dictionary>(); public IEnumerable Vertices { get { return _vertices; } } @@ -40,7 +40,7 @@ namespace Encompass if (!VertexExists(vertex)) { _vertices.Add(vertex); - _neighbors.Add(vertex, new List()); + _neighbors.Add(vertex, new HashSet()); } } diff --git a/test/EngineTest.cs b/test/EngineTest.cs index f47781d..21a7ff9 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -17,6 +17,7 @@ namespace Tests static List resultMessages; + [Reads(typeof(MockComponent))] public class ReadComponentsTestEngine : Engine { public override void Update(double dt) @@ -25,6 +26,7 @@ namespace Tests } } + [Reads(typeof(MockComponent))] public class ReadComponentTestEngine : Engine { public override void Update(double dt) @@ -110,6 +112,7 @@ namespace Tests Assert.Throws(() => world.Update(0.01f)); } + [Reads(typeof(MockComponent))] [Writes(typeof(MockComponent))] public class UpdateComponentTestEngine : Engine { @@ -147,6 +150,7 @@ namespace Tests Assert.AreEqual("blaze it", resultComponent.myString); } + [Reads(typeof(MockComponent))] public class UndeclaredUpdateComponentTestEngine : Engine { public override void Update(double dt) @@ -177,7 +181,7 @@ namespace Tests var world = worldBuilder.Build(); - var ex = Assert.Throws(() => world.Update(0.01f)); + var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to write undeclared Component MockComponent")); } @@ -263,7 +267,7 @@ namespace Tests var world = worldBuilder.Build(); - var ex = Assert.Throws(() => world.Update(0.01f)); + var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to emit undeclared Message MockMessage")); } @@ -321,12 +325,13 @@ namespace Tests var world = worldBuilder.Build(); - Assert.Throws(() => world.Update(0.01f)); + Assert.Throws(() => world.Update(0.01f)); } static ValueTuple pairA; static ValueTuple pairB; + [Reads(typeof(MockComponent))] class SameValueComponentReadEngine : Engine { public override void Update(double dt) @@ -366,6 +371,7 @@ namespace Tests static IEnumerable> emptyComponentReadResult; + [Reads(typeof(MockComponent))] class ReadEmptyMockComponentsEngine : Engine { public override void Update(double dt) @@ -388,6 +394,7 @@ namespace Tests struct DestroyerComponent : IComponent { } + [Reads(typeof(DestroyerComponent))] class DestroyerEngine : Engine { public override void Update(double dt) @@ -402,6 +409,8 @@ namespace Tests } static IEnumerable> results; + + [Reads(typeof(MockComponent))] class ReaderEngine : Engine { public override void Update(double dt) @@ -439,6 +448,7 @@ namespace Tests Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); } + [Reads(typeof(DestroyerComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) @@ -472,6 +482,8 @@ namespace Tests } static Entity entityFromComponentIDResult; + + [Reads(typeof(MockComponent))] class GetEntityFromComponentIDEngine : Engine { public override void Update(double dt) @@ -501,6 +513,8 @@ namespace Tests } static MockComponent mockComponentByIDResult; + + [Reads(typeof(MockComponent))] class GetComponentByIDEngine : Engine { public override void Update(double dt) @@ -530,6 +544,7 @@ namespace Tests struct OtherComponent : IComponent { } + [Reads(typeof(MockComponent), typeof(OtherComponent))] class GetComponentByIDWithTypeMismatchEngine : Engine { public override void Update(double dt) @@ -559,6 +574,8 @@ 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/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 3267281..0519d4e 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -133,7 +133,74 @@ namespace Tests worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); - Assert.Throws(() => worldBuilder.Build()); + Assert.Throws(() => worldBuilder.Build()); + } + } + + public class EngineMessageSelfCycle + { + struct AMessage : IMessage { } + + [Reads(typeof(AMessage))] + [Writes(typeof(AMessage))] + class AEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Test] + public void ThrowsError() + { + var worldBuilder = new WorldBuilder(); + + Assert.Throws(() => worldBuilder.AddEngine(new AEngine()), "Engine both reads and writes Message AMessage"); + } + } + + public class IllegalReadType + { + struct ANonMessage { } + + [Reads(typeof(ANonMessage))] + class MyEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Test] + public void ThrowsError() + { + var worldBuilder = new WorldBuilder(); + + Assert.Throws(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component"); + } + } + + public class IllegalWriteType + { + struct ANonMessage { } + + [Writes(typeof(ANonMessage))] + class MyEngine : Engine + { + public override void Update(double dt) + { + + } + } + + [Test] + public void ThrowsError() + { + var worldBuilder = new WorldBuilder(); + + Assert.Throws(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component"); } } From 64f6e132164b1915efd917250775d933155fdf13 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 17 Jul 2019 11:24:21 -0700 Subject: [PATCH 3/6] move all methods off of Entity --- encompass-cs/ComponentManager.cs | 101 ++---- encompass-cs/Engine.cs | 73 +++++ encompass-cs/Entity.cs | 61 +--- encompass-cs/EntityManager.cs | 5 +- encompass-cs/Renderer.cs | 16 +- encompass-cs/World.cs | 4 - encompass-cs/WorldBuilder.cs | 19 +- encompass-cs/renderers/EntityRenderer.cs | 4 +- encompass-cs/utility/EntityChecker.cs | 4 +- test/ComponentTest.cs | 377 +++++++++++++++++++++++ test/EngineTest.cs | 59 ++-- test/EntityRendererTest.cs | 46 ++- test/EntityTest.cs | 172 ----------- test/GeneralRendererTest.cs | 6 +- test/SpawnerTest.cs | 12 +- test/WorldTest.cs | 16 +- 16 files changed, 588 insertions(+), 387 deletions(-) create mode 100644 test/ComponentTest.cs delete mode 100644 test/EntityTest.cs diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index ea5e2fc..9bf4550 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -18,10 +18,6 @@ namespace Encompass private readonly List activeComponents = new List(); private readonly List inactiveComponents = new List(); - private readonly HashSet componentsToActivate = new HashSet(); - private readonly HashSet componentsToDeactivate = new HashSet(); - private readonly HashSet componentsToRemove = new HashSet(); - //shared references with EntityManager private readonly HashSet entitiesWithAddedComponents; private readonly HashSet entitiesWithRemovedComponents; @@ -60,7 +56,7 @@ namespace Encompass componentIDToEntityID[componentID] = entityID; inactiveComponents.Add(componentID); - MarkForActivation(componentID); + Activate(componentID); entitiesWithAddedComponents.Add(entityID); @@ -158,91 +154,58 @@ namespace Encompass { var componentIDs = entityIDToComponentIDs[entityID]; - foreach (var componentID in componentIDs) + for (int i = componentIDs.Count - 1; i >= 0; i--) { - MarkForRemoval(componentID); + Remove(componentIDs[i]); } } - internal void MarkForActivation(Guid componentID) + internal void Activate(Guid componentID) { - componentsToActivate.Add(componentID); + if (inactiveComponents.Remove(componentID)) + { + activeComponents.Add(componentID); + } var entityID = GetEntityIDByComponentID(componentID); entitiesWithAddedComponents.Add(entityID); } - internal void MarkForDeactivation(Guid componentID) + internal void Deactivate(Guid componentID) { - componentsToDeactivate.Add(componentID); + if (activeComponents.Remove(componentID)) + { + inactiveComponents.Add(componentID); + } var entityID = GetEntityIDByComponentID(componentID); entitiesWithRemovedComponents.Add(entityID); } - internal void MarkForRemoval(Guid componentID) + internal void Remove(Guid componentID) { - componentsToRemove.Add(componentID); + var component = IDToComponent[componentID]; + var type = componentIDToType[componentID]; + + activeComponents.Remove(componentID); + inactiveComponents.Remove(componentID); + + var entityID = componentIDToEntityID[componentID]; + if (entityIDToComponentIDs.ContainsKey(entityID)) + { + entityIDToComponentIDs[entityID].Remove(componentID); + } + + IDToComponent.Remove(componentID); + componentIDToType.Remove(componentID); + componentIDToEntityID.Remove(componentID); + typeToComponentIDs[type].Remove(componentID); + + drawLayerManager.UnRegisterComponentWithLayer(componentID); - var entityID = GetEntityIDByComponentID(componentID); entitiesWithRemovedComponents.Add(entityID); } - internal void ActivateMarkedComponents() - { - foreach (var componentID in componentsToActivate) - { - var component = IDToComponent[componentID]; - if (inactiveComponents.Remove(componentID)) - { - activeComponents.Add(componentID); - } - } - - componentsToActivate.Clear(); - } - - internal void DeactivateMarkedComponents() - { - foreach (var componentID in componentsToDeactivate) - { - var component = IDToComponent[componentID]; - if (activeComponents.Remove(componentID)) - { - inactiveComponents.Add(componentID); - } - } - - componentsToDeactivate.Clear(); - } - - public void RemoveMarkedComponents() - { - foreach (var componentID in componentsToRemove) - { - var component = IDToComponent[componentID]; - var type = componentIDToType[componentID]; - - activeComponents.Remove(componentID); - inactiveComponents.Remove(componentID); - - var entityID = componentIDToEntityID[componentID]; - if (entityIDToComponentIDs.ContainsKey(entityID)) - { - entityIDToComponentIDs[entityID].Remove(componentID); - } - - IDToComponent.Remove(componentID); - componentIDToType.Remove(componentID); - componentIDToEntityID.Remove(componentID); - typeToComponentIDs[type].Remove(componentID); - - drawLayerManager.UnRegisterComponentWithLayer(componentID); - } - - componentsToRemove.Clear(); - } - public void RegisterDestroyedEntity(Guid entityID) { entityIDToComponentIDs.Remove(entityID); diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 9c336df..d51c8ca 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -107,6 +107,68 @@ namespace Encompass return componentManager.GetActiveComponentByType(); } + protected Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent + { + if (!writeTypes.Contains(typeof(TComponent))) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + + return componentManager.AddComponent(entity.ID, component); + } + + protected Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent + { + if (!writeTypes.Contains(typeof(TComponent))) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + + return componentManager.AddDrawComponent(entity.ID, component, layer); + } + + protected void ActivateComponent(Guid componentID) + { + var type = componentManager.GetComponentTypeByID(componentID); + if (!writeTypes.Contains(type)) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, type.Name); + } + + componentManager.Activate(componentID); + } + + protected void DeactivateComponent(Guid componentID) + { + var type = componentManager.GetComponentTypeByID(componentID); + if (!writeTypes.Contains(type)) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, type.Name); + } + + componentManager.Deactivate(componentID); + } + + protected IEnumerable> GetComponents(Entity entity) where TComponent : struct, IComponent + { + if (!readTypes.Contains(typeof(TComponent))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + + return componentManager.GetComponentsByEntityAndType(entity.ID); + } + + protected ValueTuple GetComponent(Entity entity) where TComponent : struct, IComponent + { + return GetComponents(entity).First(); + } + + protected bool HasComponent(Entity entity) where TComponent : struct, IComponent + { + return componentManager.EntityHasComponentOfType(entity.ID); + } + internal void UpdateComponentInWorld(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent { if (!writeTypes.Contains(typeof(TComponent))) @@ -157,5 +219,16 @@ namespace Encompass { entityManager.MarkForDestroy(entityID); } + + protected void RemoveComponent(Guid componentID) + { + var type = componentManager.GetComponentTypeByID(componentID); + if (!writeTypes.Contains(type)) + { + throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, type.Name); + } + + componentManager.Remove(componentID); + } } } diff --git a/encompass-cs/Entity.cs b/encompass-cs/Entity.cs index 20b3101..fd45ad5 100644 --- a/encompass-cs/Entity.cs +++ b/encompass-cs/Entity.cs @@ -1,69 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; namespace Encompass { public struct Entity { - public readonly Guid id; + public readonly Guid ID; - private readonly ComponentManager componentManager; - - internal Entity(Guid id, ComponentManager componentManager) + internal Entity(Guid id) { - this.id = id; - this.componentManager = componentManager; - } - - public Guid AddComponent(TComponent component) where TComponent : struct, IComponent - { - return componentManager.AddComponent(id, component); - } - - public Guid AddDrawComponent(TComponent component, int layer = 0) where TComponent : struct, IComponent - { - return componentManager.AddDrawComponent(id, component, layer); - } - - public IEnumerable> GetComponents() where TComponent : struct, IComponent - { - return componentManager.GetComponentsByEntityAndType(id); - } - - public ValueTuple GetComponent() where TComponent : struct, IComponent - { - return GetComponents().First(); - } - - public bool HasComponent() where TComponent : struct, IComponent - { - return componentManager.EntityHasComponentOfType(id); - } - - internal bool HasComponent(Type type) - { - return componentManager.EntityHasComponentOfType(id, type); - } - - public void ActivateComponent(Guid componentID) - { - componentManager.MarkForActivation(componentID); - } - - public void DeactivateComponent(Guid componentID) - { - componentManager.MarkForDeactivation(componentID); - } - - public void RemoveComponent(Guid componentID) - { - componentManager.MarkForRemoval(componentID); - } - - internal void RemoveAllComponents() - { - componentManager.RemoveAllComponentsFromEntity(id); + this.ID = id; } } } diff --git a/encompass-cs/EntityManager.cs b/encompass-cs/EntityManager.cs index f58fe19..a01969f 100644 --- a/encompass-cs/EntityManager.cs +++ b/encompass-cs/EntityManager.cs @@ -31,7 +31,7 @@ namespace Encompass public Entity CreateEntity() { var id = NextID(); - var entity = new Entity(id, componentManager); + var entity = new Entity(id); IDToEntity[id] = entity; componentManager.RegisterEntity(id); return entity; @@ -56,8 +56,7 @@ namespace Encompass { foreach (var entityID in entitiesMarkedForDestroy) { - var entity = IDToEntity[entityID]; - entity.RemoveAllComponents(); + componentManager.RemoveAllComponentsFromEntity(entityID); IDToEntity.Remove(entityID); entityToEntityTrackers.Remove(entityID); componentManager.RegisterDestroyedEntity(entityID); diff --git a/encompass-cs/Renderer.cs b/encompass-cs/Renderer.cs index 1e6dee8..d96e93e 100644 --- a/encompass-cs/Renderer.cs +++ b/encompass-cs/Renderer.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Linq; namespace Encompass { public abstract class Renderer { - private EntityManager entityManager; - private ComponentManager componentManager; + internal EntityManager entityManager; + internal ComponentManager componentManager; internal void AssignEntityManager(EntityManager entityManager) { @@ -48,5 +48,15 @@ namespace Encompass { return componentManager.GetActiveComponentByType(); } + + protected IEnumerable> GetComponents(Entity entity) where TComponent : struct, IComponent + { + return componentManager.GetComponentsByEntityAndType(entity.ID); + } + + protected ValueTuple GetComponent(Entity entity) where TComponent : struct, IComponent + { + return GetComponents(entity).First(); + } } } diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index f349877..9d9abe6 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -35,10 +35,6 @@ namespace Encompass messageManager.ClearMessages(); entityManager.DestroyMarkedEntities(); - componentManager.ActivateMarkedComponents(); - componentManager.DeactivateMarkedComponents(); - componentManager.RemoveMarkedComponents(); - entityManager.CheckEntitiesWithAddedComponents(); entityManager.CheckEntitiesWithRemovedComponents(); } diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 3026171..4732fe4 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -41,6 +41,21 @@ namespace Encompass messageManager.AddMessage(message); } + public Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent + { + return componentManager.AddComponent(entity.ID, component); + } + + public Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent + { + return componentManager.AddDrawComponent(entity.ID, component, layer); + } + + public void DeactivateComponent(Guid componentID) + { + componentManager.Deactivate(componentID); + } + public Engine AddEngine(TEngine engine) where TEngine : Engine { engine.AssignEntityManager(entityManager); @@ -214,10 +229,6 @@ namespace Encompass renderManager ); - componentManager.ActivateMarkedComponents(); - componentManager.DeactivateMarkedComponents(); - componentManager.RemoveMarkedComponents(); - entityManager.CheckEntitiesWithAddedComponents(); entityManager.CheckEntitiesWithRemovedComponents(); diff --git a/encompass-cs/renderers/EntityRenderer.cs b/encompass-cs/renderers/EntityRenderer.cs index f491362..b384a8b 100644 --- a/encompass-cs/renderers/EntityRenderer.cs +++ b/encompass-cs/renderers/EntityRenderer.cs @@ -47,8 +47,8 @@ namespace Encompass private bool CheckEntity(Entity entity) { - return EntityChecker.CheckEntity(entity, componentTypes) - && entity.HasComponent(DrawComponentType); + return EntityChecker.CheckEntity(componentManager, entity, componentTypes) + && componentManager.EntityHasComponentOfType(entity.ID, DrawComponentType); } } } diff --git a/encompass-cs/utility/EntityChecker.cs b/encompass-cs/utility/EntityChecker.cs index eaabb76..75483e1 100644 --- a/encompass-cs/utility/EntityChecker.cs +++ b/encompass-cs/utility/EntityChecker.cs @@ -6,9 +6,9 @@ namespace Encompass { internal static class EntityChecker { - public static bool CheckEntity(Entity entity, IEnumerable componentTypes) + public static bool CheckEntity(ComponentManager componentManager, Entity entity, IEnumerable componentTypes) { - return componentTypes.All((componentType) => entity.HasComponent(componentType)); + return componentTypes.All((componentType) => componentManager.EntityHasComponentOfType(entity.ID, componentType)); } } } diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs new file mode 100644 index 0000000..30e1ba6 --- /dev/null +++ b/test/ComponentTest.cs @@ -0,0 +1,377 @@ +using NUnit.Framework; +using FluentAssertions; + +using Encompass; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace Tests +{ + public class ComponentTests + { + struct MockComponent : IComponent + { + public string myString; + public int myInt; + } + + struct EntityMessage : IMessage + { + public Entity entity; + } + + static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>(); + static (Guid, MockComponent) gottenMockComponentIDPair; + + [Reads(typeof(EntityMessage), typeof(MockComponent))] + class GetMockComponentsEngine : Engine + { + public override void Update(double dt) + { + gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>(); + + foreach (var entityMessage in ReadMessages()) + { + gottenMockComponentIDPairs = GetComponents(entityMessage.entity); + } + } + } + + [Reads(typeof(EntityMessage), typeof(MockComponent))] + class GetMockComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var entityMessage in ReadMessages()) + { + gottenMockComponentIDPair = GetComponent(entityMessage.entity); + } + } + } + + struct AddComponentTestMessage : IMessage + { + public Entity entity; + public MockComponent mockComponent; + } + + [Reads(typeof(AddComponentTestMessage), typeof(MockComponent))] + class AddComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var addComponentTestMessage in ReadMessages()) + { + Assert.IsTrue(HasComponent(addComponentTestMessage.entity)); + Assert.That(GetComponent(addComponentTestMessage.entity).Item2, Is.EqualTo(addComponentTestMessage.mockComponent)); + } + } + } + + [Test] + public void AddComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new AddComponentEngine()); + worldBuilder.AddEngine(new GetMockComponentEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + worldBuilder.AddComponent(entity, mockComponent); + + AddComponentTestMessage addComponentTestMessage; + addComponentTestMessage.entity = entity; + addComponentTestMessage.mockComponent = mockComponent; + worldBuilder.EmitMessage(addComponentTestMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01); + } + + [Test] + public void GetComponents() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new GetMockComponentsEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponentA; + mockComponentA.myInt = 3; + mockComponentA.myString = "hello"; + + MockComponent mockComponentB; + mockComponentB.myInt = 5; + mockComponentB.myString = "wassup"; + + MockComponent mockComponentC; + mockComponentC.myInt = 1; + mockComponentC.myString = "howdy"; + + var componentAID = worldBuilder.AddComponent(entity, mockComponentA); + var componentBID = worldBuilder.AddComponent(entity, mockComponentB); + var componentCID = worldBuilder.AddComponent(entity, mockComponentC); + + EntityMessage entityMessage; + entityMessage.entity = entity; + worldBuilder.EmitMessage(entityMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + gottenMockComponentIDPairs.Should().Contain((componentAID, mockComponentA)); + gottenMockComponentIDPairs.Should().Contain((componentBID, mockComponentB)); + gottenMockComponentIDPairs.Should().Contain((componentCID, mockComponentC)); + } + + [Test] + public void GetComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new GetMockComponentEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + var componentID = worldBuilder.AddComponent(entity, mockComponent); + + EntityMessage entityMessage; + entityMessage.entity = entity; + worldBuilder.EmitMessage(entityMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01); + + Assert.AreEqual((componentID, mockComponent), gottenMockComponentIDPair); + } + + struct HasComponentTestMessage : IMessage + { + public Entity entity; + } + + [Reads(typeof(HasComponentTestMessage), typeof(MockComponent))] + class HasComponentTestEngine : Engine + { + public override void Update(double dt) + { + foreach (var hasComponentTestEngine in ReadMessages()) + { + Assert.IsTrue(HasComponent(hasComponentTestEngine.entity)); + } + } + } + + [Test] + public void HasComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new HasComponentTestEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + worldBuilder.AddComponent(entity, mockComponent); + + HasComponentTestMessage hasComponentTestMessage; + hasComponentTestMessage.entity = entity; + worldBuilder.EmitMessage(hasComponentTestMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01); + } + + struct HasComponentWhenInactiveTestMessage : IMessage + { + public Entity entity; + } + + [Reads(typeof(HasComponentWhenInactiveTestMessage))] + class HasComponentWhenInactiveTestEngine : Engine + { + public override void Update(double dt) + { + foreach (var hasComponentTestEngine in ReadMessages()) + { + Assert.IsFalse(HasComponent(hasComponentTestEngine.entity)); + } + } + } + + [Test] + public void HasComponentWhenInactive() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new HasComponentWhenInactiveTestEngine()); + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + var componentID = worldBuilder.AddComponent(entity, mockComponent); + + HasComponentWhenInactiveTestMessage testMessage; + testMessage.entity = entity; + worldBuilder.EmitMessage(testMessage); + + worldBuilder.DeactivateComponent(componentID); + + var world = worldBuilder.Build(); + + world.Update(0.01f); + } + + struct RemoveComponentTestMessage : IMessage + { + public Entity entity; + public Guid componentID; + } + + [Reads(typeof(RemoveComponentTestMessage), typeof(MockComponent))] + [Writes(typeof(MockComponent))] + class RemoveComponentTestEngine : Engine + { + public override void Update(double dt) + { + foreach (var removeComponentMessage in ReadMessages()) + { + RemoveComponent(removeComponentMessage.componentID); + + Assert.IsFalse(HasComponent(removeComponentMessage.entity)); + Assert.IsEmpty(GetComponents(removeComponentMessage.entity)); + } + } + } + + [Test] + public void RemoveComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new RemoveComponentTestEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + var componentID = worldBuilder.AddComponent(entity, mockComponent); + + RemoveComponentTestMessage removeComponentMessage; + removeComponentMessage.entity = entity; + removeComponentMessage.componentID = componentID; + worldBuilder.EmitMessage(removeComponentMessage); + + var world = worldBuilder.Build(); + + + world.Update(0.01f); + } + + struct ActivateComponentMessage : IMessage + { + public Entity entity; + public Guid componentID; + } + + [Reads(typeof(ActivateComponentMessage))] + [Writes(typeof(MockComponent))] + class ActivateComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var activateComponentMessage in ReadMessages()) + { + ActivateComponent(activateComponentMessage.componentID); + Assert.IsTrue(HasComponent(activateComponentMessage.entity)); + } + } + } + + [Test] + public void ActivateComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new ActivateComponentEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + var componentID = worldBuilder.AddComponent(entity, mockComponent); + + worldBuilder.DeactivateComponent(componentID); + + ActivateComponentMessage activateMessage; + activateMessage.entity = entity; + activateMessage.componentID = componentID; + worldBuilder.EmitMessage(activateMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01f); + } + + struct DeactivateComponentMessage : IMessage + { + public Entity entity; + public Guid componentID; + } + + [Reads(typeof(DeactivateComponentMessage))] + [Writes(typeof(MockComponent))] + class DeactivateComponentEngine : Engine + { + public override void Update(double dt) + { + foreach (var deactivateComponentMessage in ReadMessages()) + { + DeactivateComponent(deactivateComponentMessage.componentID); + Assert.IsFalse(HasComponent(deactivateComponentMessage.entity)); + } + } + } + + [Test] + public void DeactivateComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new DeactivateComponentEngine()); + + var entity = worldBuilder.CreateEntity(); + + MockComponent mockComponent; + mockComponent.myInt = 3; + mockComponent.myString = "hello"; + + var componentID = worldBuilder.AddComponent(entity, mockComponent); + + DeactivateComponentMessage deactivateComponentMessage; + deactivateComponentMessage.entity = entity; + deactivateComponentMessage.componentID = componentID; + worldBuilder.EmitMessage(deactivateComponentMessage); + + var world = worldBuilder.Build(); + + world.Update(0.01); + } + } +} diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 21a7ff9..1ae33f6 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -10,6 +10,12 @@ using Encompass.Exceptions; namespace Tests { + struct MockComponent : IComponent + { + public int myInt; + public string myString; + } + public class EngineTest { static List> resultComponents; @@ -51,11 +57,11 @@ namespace Tests mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; - var componentAID = entity.AddComponent(mockComponent); - var componentBID = entity.AddComponent(mockComponentB); - var inactiveComponentAID = entity.AddComponent(mockComponent); + var componentAID = worldBuilder.AddComponent(entity, mockComponent); + var componentBID = worldBuilder.AddComponent(entity, mockComponentB); + var inactiveComponentAID = worldBuilder.AddComponent(entity, mockComponent); - entity.DeactivateComponent(inactiveComponentAID); + worldBuilder.DeactivateComponent(inactiveComponentAID); var world = worldBuilder.Build(); @@ -79,7 +85,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - entity.AddComponent(mockComponent); + worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -104,8 +110,8 @@ namespace Tests mockComponentB.myInt = 1; mockComponentB.myString = "howdy"; - entity.AddComponent(mockComponent); - entity.AddComponent(mockComponentB); + worldBuilder.AddComponent(entity, mockComponent); + worldBuilder.AddComponent(entity, mockComponentB); var world = worldBuilder.Build(); @@ -140,7 +146,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - entity.AddComponent(mockComponent); + worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -177,7 +183,7 @@ namespace Tests mockComponent.myInt = 0; mockComponent.myString = "hello"; - entity.AddComponent(mockComponent); + worldBuilder.AddComponent(entity, mockComponent); var world = worldBuilder.Build(); @@ -359,8 +365,8 @@ namespace Tests componentB.myString = "hello"; var entity = worldBuilder.CreateEntity(); - entity.AddComponent(componentA); - entity.AddComponent(componentB); + worldBuilder.AddComponent(entity, componentA); + worldBuilder.AddComponent(entity, componentB); var world = worldBuilder.Build(); world.Update(0.01f); @@ -434,11 +440,11 @@ namespace Tests mockComponent.myInt = 2; mockComponent.myString = "blah"; - entity.AddComponent(destroyerComponent); - var componentID = entity.AddComponent(mockComponent); + worldBuilder.AddComponent(entity, destroyerComponent); + var componentID = worldBuilder.AddComponent(entity, mockComponent); - entityB.AddComponent(destroyerComponent); - var componentBID = entityB.AddComponent(mockComponent); + worldBuilder.AddComponent(entityB, destroyerComponent); + var componentBID = worldBuilder.AddComponent(entityB, mockComponent); var world = worldBuilder.Build(); @@ -448,7 +454,8 @@ namespace Tests Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); } - [Reads(typeof(DestroyerComponent))] + [Reads(typeof(DestroyerComponent), typeof(MockComponent))] + [Writes(typeof(MockComponent))] class DestroyAndAddComponentEngine : Engine { public override void Update(double dt) @@ -457,9 +464,9 @@ namespace Tests { var componentID = componentPair.Item1; var entity = GetEntityByComponentID(componentID); - var (id, _) = entity.GetComponent(); - entity.RemoveComponent(id); - Destroy(entity.id); + var (id, _) = GetComponent(entity); + RemoveComponent(id); + Destroy(entity.ID); } } } @@ -473,8 +480,8 @@ namespace Tests var entity = worldBuilder.CreateEntity(); - entity.AddComponent(new DestroyerComponent()); - entity.AddComponent(new MockComponent()); + worldBuilder.AddComponent(entity, new DestroyerComponent()); + worldBuilder.AddComponent(entity, new MockComponent()); var world = worldBuilder.Build(); @@ -504,7 +511,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - entity.AddComponent(component); + worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); world.Update(0.01f); @@ -534,7 +541,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - entity.AddComponent(component); + worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); world.Update(0.01f); @@ -565,7 +572,7 @@ namespace Tests component.myString = "howdy"; var entity = worldBuilder.CreateEntity(); - entity.AddComponent(component); + worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); @@ -598,9 +605,9 @@ namespace Tests var entityTwo = worldBuilder.CreateEntity(); EntityIDComponent entityIDComponent; - entityIDComponent.entityID = entityTwo.id; + entityIDComponent.entityID = entityTwo.ID; - entity.AddComponent(entityIDComponent); + worldBuilder.AddComponent(entity, entityIDComponent); var world = worldBuilder.Build(); diff --git a/test/EntityRendererTest.cs b/test/EntityRendererTest.cs index 75367cf..5a52d89 100644 --- a/test/EntityRendererTest.cs +++ b/test/EntityRendererTest.cs @@ -32,27 +32,27 @@ namespace Tests TestDrawComponent testDrawComponent = default(TestDrawComponent); var entityToTrack = worldBuilder.CreateEntity(); - entityToTrack.AddComponent(aComponent); - entityToTrack.AddComponent(bComponent); - entityToTrack.AddComponent(testDrawComponent); + worldBuilder.AddComponent(entityToTrack, aComponent); + worldBuilder.AddComponent(entityToTrack, bComponent); + worldBuilder.AddComponent(entityToTrack, testDrawComponent); var entityNotToTrack = worldBuilder.CreateEntity(); - entityNotToTrack.AddComponent(aComponent); - entityNotToTrack.AddComponent(testDrawComponent); + worldBuilder.AddComponent(entityNotToTrack, aComponent); + worldBuilder.AddComponent(entityNotToTrack, testDrawComponent); var entityWithoutDrawComponent = worldBuilder.CreateEntity(); - entityWithoutDrawComponent.AddComponent(aComponent); - entityWithoutDrawComponent.AddComponent(bComponent); + worldBuilder.AddComponent(entityWithoutDrawComponent, aComponent); + worldBuilder.AddComponent(entityWithoutDrawComponent, bComponent); var world = worldBuilder.Build(); world.Update(0.01f); - Console.WriteLine(renderer.IsTracking(entityNotToTrack.id)); + Console.WriteLine(renderer.IsTracking(entityNotToTrack.ID)); - Assert.IsTrue(renderer.IsTracking(entityToTrack.id)); - Assert.IsFalse(renderer.IsTracking(entityNotToTrack.id)); - Assert.IsFalse(renderer.IsTracking(entityWithoutDrawComponent.id)); + Assert.IsTrue(renderer.IsTracking(entityToTrack.ID)); + Assert.IsFalse(renderer.IsTracking(entityNotToTrack.ID)); + Assert.IsFalse(renderer.IsTracking(entityWithoutDrawComponent.ID)); } static bool called = false; @@ -75,19 +75,17 @@ namespace Tests TestDrawComponent testDrawComponent = default(TestDrawComponent); var entity = worldBuilder.CreateEntity(); - entity.AddComponent(aComponent); - entity.AddComponent(bComponent); - var testDrawComponentID = entity.AddDrawComponent(testDrawComponent, 1); + worldBuilder.AddComponent(entity, aComponent); + worldBuilder.AddComponent(entity, bComponent); + var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 1); + + worldBuilder.DeactivateComponent(testDrawComponentID); var world = worldBuilder.Build(); world.Update(0.01f); - entity.DeactivateComponent(testDrawComponentID); - - world.Update(0.01f); - - Assert.IsFalse(renderer.IsTracking(entity.id)); + Assert.IsFalse(renderer.IsTracking(entity.ID)); world.Draw(); @@ -101,7 +99,7 @@ namespace Tests { public override void Render(Entity entity) { - resultComponents = entity.GetComponents(); + resultComponents = GetComponents(entity); calledOnDraw = true; } } @@ -117,16 +115,16 @@ namespace Tests TestDrawComponent testDrawComponent; var entity = worldBuilder.CreateEntity(); - entity.AddComponent(aComponent); - entity.AddComponent(cComponent); - var testDrawComponentID = entity.AddDrawComponent(testDrawComponent, 2); + worldBuilder.AddComponent(entity, aComponent); + worldBuilder.AddComponent(entity, cComponent); + var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 2); var world = worldBuilder.Build(); world.Update(0.01f); world.Draw(); - Assert.IsTrue(renderer.IsTracking(entity.id)); + Assert.IsTrue(renderer.IsTracking(entity.ID)); Assert.IsTrue(calledOnDraw); resultComponents.Should().Contain(new ValueTuple(testDrawComponentID, testDrawComponent)); } diff --git a/test/EntityTest.cs b/test/EntityTest.cs deleted file mode 100644 index bbd4d9e..0000000 --- a/test/EntityTest.cs +++ /dev/null @@ -1,172 +0,0 @@ -using NUnit.Framework; -using FluentAssertions; - -using Encompass; -using System.Collections.Generic; -using System; - -namespace Tests -{ - struct MockComponent : IComponent - { - public string myString; - public int myInt; - } - - public class EntityTest - { - [Test] - public void AddComponent() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - Assert.IsTrue(entity.HasComponent()); - Assert.That(entity.GetComponent().Item2, Is.EqualTo(mockComponent)); - } - - [Test] - public void GetComponents() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponentA; - mockComponentA.myInt = 3; - mockComponentA.myString = "hello"; - - MockComponent mockComponentB; - mockComponentB.myInt = 5; - mockComponentB.myString = "wassup"; - - MockComponent mockComponentC; - mockComponentC.myInt = 1; - mockComponentC.myString = "howdy"; - - var componentAID = entity.AddComponent(mockComponentA); - var componentBID = entity.AddComponent(mockComponentB); - var componentCID = entity.AddComponent(mockComponentC); - - var world = worldBuilder.Build(); - - var components = entity.GetComponents(); - components.Should().Contain(new ValueTuple(componentAID, mockComponentA)); - components.Should().Contain(new ValueTuple(componentBID, mockComponentB)); - components.Should().Contain(new ValueTuple(componentCID, mockComponentC)); - } - - [Test] - public void GetComponent() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - var componentID = entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - Assert.AreEqual(new ValueTuple(componentID, mockComponent), entity.GetComponent()); - } - - [Test] - public void HasComponent() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - Assert.IsTrue(entity.HasComponent()); - } - - [Test] - public void HasComponentWhenInactive() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - var componentID = entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - entity.DeactivateComponent(componentID); - - world.Update(0.01f); - - Assert.IsFalse(entity.HasComponent()); - } - - [Test] - public void RemoveComponent() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - var componentID = entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - entity.RemoveComponent(componentID); - - world.Update(0.01f); - - Assert.IsFalse(entity.HasComponent()); - Assert.IsEmpty(entity.GetComponents()); - } - - [Test] - public void ReactivateComponent() - { - var worldBuilder = new WorldBuilder(); - var entity = worldBuilder.CreateEntity(); - - MockComponent mockComponent; - mockComponent.myInt = 3; - mockComponent.myString = "hello"; - - var componentID = entity.AddComponent(mockComponent); - - var world = worldBuilder.Build(); - - entity.DeactivateComponent(componentID); - - world.Update(0.01f); - - Assert.IsFalse(entity.HasComponent()); - Assert.IsEmpty(entity.GetComponents()); - - entity.ActivateComponent(componentID); - - world.Update(0.01f); - - Assert.IsTrue(entity.HasComponent()); - Assert.IsNotEmpty(entity.GetComponents()); - } - } -} diff --git a/test/GeneralRendererTest.cs b/test/GeneralRendererTest.cs index fd0e099..497464d 100644 --- a/test/GeneralRendererTest.cs +++ b/test/GeneralRendererTest.cs @@ -32,7 +32,7 @@ namespace Tests AComponent aComponent; var entity = worldBuilder.CreateEntity(); - var componentID = entity.AddComponent(aComponent); + var componentID = worldBuilder.AddComponent(entity, aComponent); var world = worldBuilder.Build(); @@ -52,8 +52,8 @@ namespace Tests AComponent aComponentTwo; var entity = worldBuilder.CreateEntity(); - var componentID = entity.AddComponent(aComponent); - var componentTwoID = entity.AddComponent(aComponentTwo); + var componentID = worldBuilder.AddComponent(entity, aComponent); + var componentTwoID = worldBuilder.AddComponent(entity, aComponentTwo); var world = worldBuilder.Build(); world.Update(0.01f); diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index 446fe4e..564eeba 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -23,12 +23,14 @@ namespace Tests } } + [Writes(typeof(TestComponent))] class TestSpawner : Spawner { protected override void Spawn(SpawnMessageA message) { resultEntity = CreateEntity(); - resultEntity.AddComponent(new TestComponent()); + AddComponent(resultEntity, new TestComponent()); + Assert.Pass(); } } @@ -42,14 +44,6 @@ namespace Tests var world = worldBuilder.Build(); world.Update(0.01); - - Assert.That(resultEntity.HasComponent(), Is.True); - - var id = resultEntity.id; - - world.Update(0.01); - - Assert.That(resultEntity.id, Is.Not.EqualTo(id)); } } } diff --git a/test/WorldTest.cs b/test/WorldTest.cs index 675a75e..fc1b8ff 100644 --- a/test/WorldTest.cs +++ b/test/WorldTest.cs @@ -44,26 +44,26 @@ namespace Tests TestDrawComponent testDrawComponent = default(TestDrawComponent); var entity = worldBuilder.CreateEntity(); - entity.AddComponent(testComponent); - entity.AddDrawComponent(testDrawComponent, 3); + worldBuilder.AddComponent(entity, testComponent); + worldBuilder.AddDrawComponent(entity, testDrawComponent, 3); TestDrawComponent testDrawComponentTwo = default(TestDrawComponent); var entityTwo = worldBuilder.CreateEntity(); - entityTwo.AddComponent(testComponent); - entityTwo.AddDrawComponent(testDrawComponentTwo, 1); + worldBuilder.AddComponent(entityTwo, testComponent); + worldBuilder.AddDrawComponent(entityTwo, testDrawComponentTwo, 1); TestDrawComponent testDrawComponentThree = default(TestDrawComponent); var entityThree = worldBuilder.CreateEntity(); - entityThree.AddComponent(testComponent); - entityThree.AddDrawComponent(testDrawComponentThree, 5); + worldBuilder.AddComponent(entityThree, testComponent); + worldBuilder.AddDrawComponent(entityThree, testDrawComponentThree, 5); TestDrawComponent testDrawComponentFour = default(TestDrawComponent); var entityFour = worldBuilder.CreateEntity(); - entityFour.AddComponent(testComponent); - entityFour.AddDrawComponent(testDrawComponentFour, -5); + worldBuilder.AddComponent(entityFour, testComponent); + worldBuilder.AddDrawComponent(entityFour, testDrawComponentFour, -5); var world = worldBuilder.Build(); From d5a196b298a806efc076ad8a5519e9983ac8b4fa Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 17 Jul 2019 11:31:18 -0700 Subject: [PATCH 4/6] read write lookups use hashset --- encompass-cs/Engine.cs | 10 +++++----- encompass-cs/attributes/Reads.cs | 4 ++-- encompass-cs/attributes/Writes.cs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index d51c8ca..5090cde 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -8,8 +8,8 @@ namespace Encompass { public abstract class Engine { - internal readonly List writeTypes = new List(); - internal readonly List readTypes = new List(); + internal readonly HashSet writeTypes = new HashSet(); + internal readonly HashSet readTypes = new HashSet(); private EntityManager entityManager; private ComponentManager componentManager; @@ -17,10 +17,10 @@ namespace Encompass protected Engine() { - var emitsAttribute = GetType().GetCustomAttribute(false); - if (emitsAttribute != null) + var writesAttribute = GetType().GetCustomAttribute(false); + if (writesAttribute != null) { - writeTypes = emitsAttribute.writeTypes; + writeTypes = writesAttribute.writeTypes; } var readsAttribute = GetType().GetCustomAttribute(false); diff --git a/encompass-cs/attributes/Reads.cs b/encompass-cs/attributes/Reads.cs index fbf6a0c..bd10431 100644 --- a/encompass-cs/attributes/Reads.cs +++ b/encompass-cs/attributes/Reads.cs @@ -8,7 +8,7 @@ namespace Encompass [System.AttributeUsage(System.AttributeTargets.Class)] public class Reads : System.Attribute { - public readonly List readTypes; + public readonly HashSet readTypes; public Reads(params Type[] readTypes) { @@ -20,7 +20,7 @@ namespace Encompass } } - this.readTypes = new List(readTypes); + this.readTypes = new HashSet(readTypes); } } } diff --git a/encompass-cs/attributes/Writes.cs b/encompass-cs/attributes/Writes.cs index 5af2c6c..67d03ae 100644 --- a/encompass-cs/attributes/Writes.cs +++ b/encompass-cs/attributes/Writes.cs @@ -8,7 +8,7 @@ namespace Encompass [AttributeUsage(AttributeTargets.Class)] public class Writes : Attribute { - public readonly List writeTypes; + public readonly HashSet writeTypes; public Writes(params Type[] writeTypes) { @@ -20,7 +20,7 @@ namespace Encompass } } - this.writeTypes = new List(writeTypes); + this.writeTypes = new HashSet(writeTypes); } } } From c344ab0c67888cfa2dd4ce34e0f304bfd95f86af Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 17 Jul 2019 11:34:29 -0700 Subject: [PATCH 5/6] project structure cleanup --- encompass-cs/{attributes => Attributes}/Reads.cs | 0 encompass-cs/{attributes => Attributes}/Renders.cs | 0 encompass-cs/{attributes => Attributes}/Writes.cs | 0 .../ComponentTypeMismatchException.cs | 0 .../DetectorWithoutComponentTypesException.cs | 12 ------------ .../EngineCycleException.cs | 0 .../EngineMessageSelfCycleException.cs | 0 .../EngineWriteConflictException.cs | 0 .../IllegalReadException.cs | 0 .../IllegalReadTypeException.cs | 0 .../IllegalWriteException.cs | 0 .../IllegalWriteTypeException.cs | 0 encompass-cs/{graph => Graph}/DirectedGraph.cs | 0 .../{renderers => Renderers}/EntityRenderer.cs | 0 .../{renderers => Renderers}/GeneralRenderer.cs | 0 encompass-cs/{utility => Utility}/EntityChecker.cs | 0 encompass-cs/{utility => Utility}/EntityTracker.cs | 0 17 files changed, 12 deletions(-) rename encompass-cs/{attributes => Attributes}/Reads.cs (100%) rename encompass-cs/{attributes => Attributes}/Renders.cs (100%) rename encompass-cs/{attributes => Attributes}/Writes.cs (100%) rename encompass-cs/{exceptions => Exceptions}/ComponentTypeMismatchException.cs (100%) delete mode 100644 encompass-cs/Exceptions/DetectorWithoutComponentTypesException.cs rename encompass-cs/{exceptions => Exceptions}/EngineCycleException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/EngineMessageSelfCycleException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/EngineWriteConflictException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/IllegalReadException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/IllegalReadTypeException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/IllegalWriteException.cs (100%) rename encompass-cs/{exceptions => Exceptions}/IllegalWriteTypeException.cs (100%) rename encompass-cs/{graph => Graph}/DirectedGraph.cs (100%) rename encompass-cs/{renderers => Renderers}/EntityRenderer.cs (100%) rename encompass-cs/{renderers => Renderers}/GeneralRenderer.cs (100%) rename encompass-cs/{utility => Utility}/EntityChecker.cs (100%) rename encompass-cs/{utility => Utility}/EntityTracker.cs (100%) diff --git a/encompass-cs/attributes/Reads.cs b/encompass-cs/Attributes/Reads.cs similarity index 100% rename from encompass-cs/attributes/Reads.cs rename to encompass-cs/Attributes/Reads.cs diff --git a/encompass-cs/attributes/Renders.cs b/encompass-cs/Attributes/Renders.cs similarity index 100% rename from encompass-cs/attributes/Renders.cs rename to encompass-cs/Attributes/Renders.cs diff --git a/encompass-cs/attributes/Writes.cs b/encompass-cs/Attributes/Writes.cs similarity index 100% rename from encompass-cs/attributes/Writes.cs rename to encompass-cs/Attributes/Writes.cs diff --git a/encompass-cs/exceptions/ComponentTypeMismatchException.cs b/encompass-cs/Exceptions/ComponentTypeMismatchException.cs similarity index 100% rename from encompass-cs/exceptions/ComponentTypeMismatchException.cs rename to encompass-cs/Exceptions/ComponentTypeMismatchException.cs diff --git a/encompass-cs/Exceptions/DetectorWithoutComponentTypesException.cs b/encompass-cs/Exceptions/DetectorWithoutComponentTypesException.cs deleted file mode 100644 index f31d3af..0000000 --- a/encompass-cs/Exceptions/DetectorWithoutComponentTypesException.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Encompass.Exceptions -{ - public class DetectorWithoutComponentTypesException : Exception - { - public DetectorWithoutComponentTypesException( - string format, - params object[] args - ) : base(string.Format(format, args)) { } - } -} diff --git a/encompass-cs/exceptions/EngineCycleException.cs b/encompass-cs/Exceptions/EngineCycleException.cs similarity index 100% rename from encompass-cs/exceptions/EngineCycleException.cs rename to encompass-cs/Exceptions/EngineCycleException.cs diff --git a/encompass-cs/exceptions/EngineMessageSelfCycleException.cs b/encompass-cs/Exceptions/EngineMessageSelfCycleException.cs similarity index 100% rename from encompass-cs/exceptions/EngineMessageSelfCycleException.cs rename to encompass-cs/Exceptions/EngineMessageSelfCycleException.cs diff --git a/encompass-cs/exceptions/EngineWriteConflictException.cs b/encompass-cs/Exceptions/EngineWriteConflictException.cs similarity index 100% rename from encompass-cs/exceptions/EngineWriteConflictException.cs rename to encompass-cs/Exceptions/EngineWriteConflictException.cs diff --git a/encompass-cs/exceptions/IllegalReadException.cs b/encompass-cs/Exceptions/IllegalReadException.cs similarity index 100% rename from encompass-cs/exceptions/IllegalReadException.cs rename to encompass-cs/Exceptions/IllegalReadException.cs diff --git a/encompass-cs/exceptions/IllegalReadTypeException.cs b/encompass-cs/Exceptions/IllegalReadTypeException.cs similarity index 100% rename from encompass-cs/exceptions/IllegalReadTypeException.cs rename to encompass-cs/Exceptions/IllegalReadTypeException.cs diff --git a/encompass-cs/exceptions/IllegalWriteException.cs b/encompass-cs/Exceptions/IllegalWriteException.cs similarity index 100% rename from encompass-cs/exceptions/IllegalWriteException.cs rename to encompass-cs/Exceptions/IllegalWriteException.cs diff --git a/encompass-cs/exceptions/IllegalWriteTypeException.cs b/encompass-cs/Exceptions/IllegalWriteTypeException.cs similarity index 100% rename from encompass-cs/exceptions/IllegalWriteTypeException.cs rename to encompass-cs/Exceptions/IllegalWriteTypeException.cs diff --git a/encompass-cs/graph/DirectedGraph.cs b/encompass-cs/Graph/DirectedGraph.cs similarity index 100% rename from encompass-cs/graph/DirectedGraph.cs rename to encompass-cs/Graph/DirectedGraph.cs diff --git a/encompass-cs/renderers/EntityRenderer.cs b/encompass-cs/Renderers/EntityRenderer.cs similarity index 100% rename from encompass-cs/renderers/EntityRenderer.cs rename to encompass-cs/Renderers/EntityRenderer.cs diff --git a/encompass-cs/renderers/GeneralRenderer.cs b/encompass-cs/Renderers/GeneralRenderer.cs similarity index 100% rename from encompass-cs/renderers/GeneralRenderer.cs rename to encompass-cs/Renderers/GeneralRenderer.cs diff --git a/encompass-cs/utility/EntityChecker.cs b/encompass-cs/Utility/EntityChecker.cs similarity index 100% rename from encompass-cs/utility/EntityChecker.cs rename to encompass-cs/Utility/EntityChecker.cs diff --git a/encompass-cs/utility/EntityTracker.cs b/encompass-cs/Utility/EntityTracker.cs similarity index 100% rename from encompass-cs/utility/EntityTracker.cs rename to encompass-cs/Utility/EntityTracker.cs From 918621db73a5a8d30fd79624ec61b36ac86aa50c Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Wed, 17 Jul 2019 11:46:54 -0700 Subject: [PATCH 6/6] adds SomeComponent to Engine and renames Some to SomeMessage --- encompass-cs/Engine.cs | 12 ++++++++- test/EngineTest.cs | 60 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 5090cde..ab44af6 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -205,7 +205,7 @@ namespace Encompass return messageManager.GetMessagesByType(); } - protected bool Some() where TMessage : struct, IMessage + protected bool SomeMessage() where TMessage : struct, IMessage { if (!readTypes.Contains(typeof(TMessage))) { @@ -215,6 +215,16 @@ namespace Encompass return messageManager.GetMessagesByType().Any(); } + protected bool SomeComponent() where TComponent : struct, IComponent + { + if (!readTypes.Contains(typeof(TComponent))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + } + + return componentManager.GetActiveComponentsByType().Any(); + } + protected void Destroy(Guid entityID) { entityManager.MarkForDestroy(entityID); diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 1ae33f6..95b6fe9 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -292,20 +292,20 @@ namespace Tests } [Reads(typeof(MockMessage))] - class SomeTestEngine : Engine + class SomeMessageTestEngine : Engine { public override void Update(double dt) { - someTest = this.Some(); + someTest = this.SomeMessage(); } } [Test] - public void Some() + public void SomeMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); - worldBuilder.AddEngine(new SomeTestEngine()); + worldBuilder.AddEngine(new SomeMessageTestEngine()); var world = worldBuilder.Build(); @@ -314,26 +314,70 @@ namespace Tests Assert.That(someTest, Is.True); } - class UndeclaredSomeEngine : Engine + class UndeclaredSomeMessageEngine : Engine { public override void Update(double dt) { - someTest = this.Some(); + someTest = this.SomeMessage(); } } [Test] - public void IllegalSome() + public void UndeclaredSomeMessage() { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); - worldBuilder.AddEngine(new UndeclaredSomeEngine()); + worldBuilder.AddEngine(new UndeclaredSomeMessageEngine()); var world = worldBuilder.Build(); Assert.Throws(() => world.Update(0.01f)); } + [Reads(typeof(MockComponent))] + class SomeComponentTestEngine : Engine + { + public override void Update(double dt) + { + Assert.IsTrue(SomeComponent()); + } + } + + [Test] + public void SomeComponent() + { + var worldBuilder = new WorldBuilder(); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.AddComponent(entity, new MockComponent()); + + var world = worldBuilder.Build(); + + world.Update(0.01); + } + + class UndeclaredSomeComponentEngine : Engine + { + public override void Update(double dt) + { + SomeComponent(); + } + } + + [Test] + public void UndeclaredSomeComponent() + { + var worldBuilder = new WorldBuilder(); + worldBuilder.AddEngine(new UndeclaredSomeComponentEngine()); + + var entity = worldBuilder.CreateEntity(); + worldBuilder.AddComponent(entity, new MockComponent()); + + var world = worldBuilder.Build(); + + Assert.Throws(() => world.Update(0.01)); + } + static ValueTuple pairA; static ValueTuple pairB;