From 89154f21d7937bfe20ec5ffc397ca4a08834ea0f Mon Sep 17 00:00:00 2001 From: thatcosmonaut Date: Fri, 19 Jul 2019 16:15:48 -0700 Subject: [PATCH] introduce concept of pending component --- TODO | 5 -- encompass-cs/Attributes/Activates.cs | 2 +- encompass-cs/Attributes/Detects.cs | 16 ----- encompass-cs/Attributes/ReadsNew.cs | 28 -------- encompass-cs/Attributes/ReadsPending.cs | 28 ++++++++ encompass-cs/Engine.cs | 69 ++++++++++++++----- .../Engines/NewComponentMessageEmitter.cs | 4 +- ...ception.cs => EngineSelfCycleException.cs} | 4 +- ...on.cs => EngineUpdateConflictException.cs} | 4 +- encompass-cs/NewComponentMessage.cs | 2 +- encompass-cs/WorldBuilder.cs | 56 ++++++++------- test/ComponentTest.cs | 7 +- test/EngineTest.cs | 4 +- test/EntityRendererTest.cs | 2 - test/WorldBuilderTest.cs | 4 +- 15 files changed, 123 insertions(+), 112 deletions(-) delete mode 100644 encompass-cs/Attributes/Detects.cs delete mode 100644 encompass-cs/Attributes/ReadsNew.cs create mode 100644 encompass-cs/Attributes/ReadsPending.cs rename encompass-cs/Exceptions/{EngineWriteConflictException.cs => EngineSelfCycleException.cs} (62%) rename encompass-cs/Exceptions/{EngineMessageSelfCycleException.cs => EngineUpdateConflictException.cs} (61%) diff --git a/TODO b/TODO index b4f2eff..07668db 100644 --- a/TODO +++ b/TODO @@ -1,6 +1 @@ -- Change "Writes" To "Sends" and make it only take Messages -- Add "Updates" for updating components - -- 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 index 777ca8f..d566633 100644 --- a/encompass-cs/Attributes/Activates.cs +++ b/encompass-cs/Attributes/Activates.cs @@ -19,7 +19,7 @@ namespace Encompass throw new IllegalActivateTypeException("{0} must be a Component", activateType.Name); } - this.activateTypes.Add(typeof(NewComponentMessage<>).MakeGenericType(activateType)); + this.activateTypes.Add(typeof(PendingComponentMessage<>).MakeGenericType(activateType)); } } } diff --git a/encompass-cs/Attributes/Detects.cs b/encompass-cs/Attributes/Detects.cs deleted file mode 100644 index 533de18..0000000 --- a/encompass-cs/Attributes/Detects.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Encompass -{ - [AttributeUsage(AttributeTargets.Class)] - public class Detects : Attribute - { - public readonly List componentTypes; - - public Detects(params Type[] componentTypes) - { - this.componentTypes = new List(componentTypes); - } - } -} diff --git a/encompass-cs/Attributes/ReadsNew.cs b/encompass-cs/Attributes/ReadsNew.cs deleted file mode 100644 index 82f168f..0000000 --- a/encompass-cs/Attributes/ReadsNew.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Encompass.Exceptions; - -namespace Encompass -{ - [AttributeUsage(AttributeTargets.Class)] - public class ReadsNew : Attribute - { - public readonly HashSet newComponentReadTypes = new HashSet(); - - public ReadsNew(params Type[] readTypes) - { - foreach (var readType in readTypes) - { - var isComponent = readType.GetInterfaces().Contains(typeof(IComponent)); - - if (!isComponent) - { - throw new IllegalReadTypeException("{0} must be a Component", readType.Name); - } - - this.newComponentReadTypes.Add(typeof(ComponentMessage<>).MakeGenericType(readType)); - } - } - } -} diff --git a/encompass-cs/Attributes/ReadsPending.cs b/encompass-cs/Attributes/ReadsPending.cs new file mode 100644 index 0000000..e0a774d --- /dev/null +++ b/encompass-cs/Attributes/ReadsPending.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Encompass.Exceptions; + +namespace Encompass +{ + [AttributeUsage(AttributeTargets.Class)] + public class ReadsPending : Attribute + { + public readonly HashSet readPendingTypes = new HashSet(); + + public ReadsPending(params Type[] readPendingTypes) + { + foreach (var readPendingType in readPendingTypes) + { + var isComponent = readPendingType.GetInterfaces().Contains(typeof(IComponent)); + + if (!isComponent) + { + throw new IllegalReadTypeException("{0} must be a Component", readPendingType.Name); + } + + this.readPendingTypes.Add(typeof(PendingComponentMessage<>).MakeGenericType(readPendingType)); + } + } + } +} diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 1cee385..3770bc1 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -42,10 +42,10 @@ namespace Encompass receiveTypes.UnionWith(readsAttribute.readTypes); } - var readsNewAttribute = GetType().GetCustomAttribute(false); - if (readsNewAttribute != null) + var readsPendingAttribute = GetType().GetCustomAttribute(false); + if (readsPendingAttribute != null) { - receiveTypes.UnionWith(readsNewAttribute.newComponentReadTypes); + receiveTypes.UnionWith(readsPendingAttribute.readPendingTypes); } var updatesAttribute = GetType().GetCustomAttribute(false); @@ -118,14 +118,14 @@ namespace Encompass protected Guid AddComponent(Entity entity, TComponent component) where TComponent : struct, IComponent { - if (!sendTypes.Contains(typeof(NewComponentMessage))) + if (!sendTypes.Contains(typeof(PendingComponentMessage))) { throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } var componentID = componentManager.AddComponent(entity.ID, component); - NewComponentMessage componentMessage; + PendingComponentMessage componentMessage; componentMessage.entity = entity; componentMessage.componentID = componentID; componentMessage.component = component; @@ -136,14 +136,14 @@ namespace Encompass protected Guid AddDrawComponent(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent { - if (!sendTypes.Contains(typeof(NewComponentMessage))) + if (!sendTypes.Contains(typeof(PendingComponentMessage))) { throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } var componentID = componentManager.AddDrawComponent(entity.ID, component, layer); - NewComponentMessage newComponentMessage; + PendingComponentMessage newComponentMessage; newComponentMessage.entity = entity; newComponentMessage.componentID = componentID; newComponentMessage.component = component; @@ -154,7 +154,7 @@ namespace Encompass protected void ActivateComponent(Guid componentID) where TComponent : struct, IComponent { - if (!sendTypes.Contains(typeof(NewComponentMessage))) + if (!sendTypes.Contains(typeof(PendingComponentMessage))) { throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name); } @@ -162,7 +162,7 @@ namespace Encompass var entity = GetEntity(componentManager.GetEntityIDByComponentID(componentID)); var component = GetComponentByID(componentID); - NewComponentMessage newComponentMessage; + PendingComponentMessage newComponentMessage; newComponentMessage.entity = entity; newComponentMessage.componentID = componentID; newComponentMessage.component = component; @@ -186,19 +186,19 @@ namespace Encompass return ReadMessages>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component)); } - private IEnumerable> NewComponentsOnEntity(Entity entity) where TComponent : struct, IComponent + private IEnumerable> PendingComponentsOnEntity(Entity entity) where TComponent : struct, IComponent { - if (!receiveTypes.Contains(typeof(NewComponentMessage))) + if (!receiveTypes.Contains(typeof(PendingComponentMessage))) { - throw new IllegalReadException("Engine {0} tried to read undeclared new Component {1}", GetType().Name, typeof(TComponent).Name); + throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name); } - return ReadMessages>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component)); + return ReadMessages>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component)); } - protected IEnumerable> GetComponentsIncludingNew(Entity entity) where TComponent : struct, IComponent + protected IEnumerable> GetPendingComponents(Entity entity) where TComponent : struct, IComponent { - return ExistingComponentsOnEntity(entity).Union(NewComponentsOnEntity(entity)); + return PendingComponentsOnEntity(entity); } protected IEnumerable> GetComponents(Entity entity) where TComponent : struct, IComponent @@ -206,11 +206,21 @@ namespace Encompass return ExistingComponentsOnEntity(entity); } + protected ValueTuple GetPendingComponent(Entity entity) where TComponent : struct, IComponent + { + return PendingComponentsOnEntity(entity).First(); + } + protected ValueTuple GetComponent(Entity entity) where TComponent : struct, IComponent { return GetComponents(entity).First(); } + protected bool HasPendingComponent(Entity entity) where TComponent : struct, IComponent + { + return GetPendingComponents(entity).Any(); + } + protected bool HasComponent(Entity entity) where TComponent : struct, IComponent { return GetComponents(entity).Any(); @@ -220,7 +230,7 @@ namespace Encompass { if (!updateTypes.Contains(typeof(TComponent))) { - throw new IllegalSendException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); + throw new IllegalSendException("Engine {0} tried to update undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name); } componentManager.AddUpdateComponentOperation(componentID, newComponent); @@ -235,7 +245,7 @@ namespace Encompass { if (!sendTypes.Contains(typeof(TMessage))) { - throw new IllegalSendException("Engine {0} tried to write undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); + throw new IllegalSendException("Engine {0} tried to send undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name); } messageManager.AddMessage(message); @@ -256,6 +266,16 @@ namespace Encompass return ReadMessages().Single(); } + protected IEnumerable> ReadPendingComponents() where TComponent : struct, IComponent + { + if (!receiveTypes.Contains(typeof(PendingComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name); + } + + return ReadMessages>().Select((message) => (message.componentID, message.component)); + } + protected IEnumerable> ReadComponents() where TComponent : struct, IComponent { if (!receiveTypes.Contains(typeof(ComponentMessage))) @@ -266,6 +286,11 @@ namespace Encompass return ReadMessages>().Select((message) => (message.componentID, message.component)); } + protected ValueTuple ReadPendingComponent() where TComponent : struct, IComponent + { + return ReadPendingComponents().Single(); + } + protected ValueTuple ReadComponent() where TComponent : struct, IComponent { return ReadComponents().Single(); @@ -281,6 +306,16 @@ namespace Encompass return ReadMessages().Any(); } + protected bool SomePendingComponent() where TComponent : struct, IComponent + { + if (!receiveTypes.Contains(typeof(PendingComponentMessage))) + { + throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name); + } + + return ReadMessages>().Any(); + } + protected bool SomeComponent() where TComponent : struct, IComponent { if (!receiveTypes.Contains(typeof(ComponentMessage))) diff --git a/encompass-cs/Engines/NewComponentMessageEmitter.cs b/encompass-cs/Engines/NewComponentMessageEmitter.cs index 8f87378..e6eec90 100644 --- a/encompass-cs/Engines/NewComponentMessageEmitter.cs +++ b/encompass-cs/Engines/NewComponentMessageEmitter.cs @@ -6,14 +6,14 @@ namespace Encompass.Engines { public NewComponentMessageEmitter() : base() { - sendTypes.Add(typeof(NewComponentMessage)); + sendTypes.Add(typeof(PendingComponentMessage)); } public override void Update(double dt) { foreach (var (entity, componentID, component) in ReadComponentsFromWorld()) { - NewComponentMessage newComponentMessage; + PendingComponentMessage newComponentMessage; newComponentMessage.entity = entity; newComponentMessage.componentID = componentID; newComponentMessage.component = component; diff --git a/encompass-cs/Exceptions/EngineWriteConflictException.cs b/encompass-cs/Exceptions/EngineSelfCycleException.cs similarity index 62% rename from encompass-cs/Exceptions/EngineWriteConflictException.cs rename to encompass-cs/Exceptions/EngineSelfCycleException.cs index ab1ac08..3598bdb 100644 --- a/encompass-cs/Exceptions/EngineWriteConflictException.cs +++ b/encompass-cs/Exceptions/EngineSelfCycleException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class EngineWriteConflictException : Exception + public class EngineSelfCycleException : Exception { - public EngineWriteConflictException( + public EngineSelfCycleException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/Exceptions/EngineMessageSelfCycleException.cs b/encompass-cs/Exceptions/EngineUpdateConflictException.cs similarity index 61% rename from encompass-cs/Exceptions/EngineMessageSelfCycleException.cs rename to encompass-cs/Exceptions/EngineUpdateConflictException.cs index b345295..3046f3d 100644 --- a/encompass-cs/Exceptions/EngineMessageSelfCycleException.cs +++ b/encompass-cs/Exceptions/EngineUpdateConflictException.cs @@ -2,9 +2,9 @@ using System; namespace Encompass.Exceptions { - public class EngineMessageSelfCycleException : Exception + public class EngineUpdateConflictException : Exception { - public EngineMessageSelfCycleException( + public EngineUpdateConflictException( string format, params object[] args ) : base(string.Format(format, args)) { } diff --git a/encompass-cs/NewComponentMessage.cs b/encompass-cs/NewComponentMessage.cs index e0ca1a6..8cc765a 100644 --- a/encompass-cs/NewComponentMessage.cs +++ b/encompass-cs/NewComponentMessage.cs @@ -2,7 +2,7 @@ using System; namespace Encompass { - public struct NewComponentMessage : IMessage where TComponent : struct, IComponent + public struct PendingComponentMessage : IMessage where TComponent : struct, IComponent { public Entity entity; public Guid componentID; diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index bf376e8..7e34110 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -87,13 +87,13 @@ namespace Encompass foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes)) { - // ComponentMessages can safely self-cycle - // this does introduce a gotcha though: if you AddComponent and then HasComponent or GetComponent you will receive a false negative - // there is no point to doing this but it is a gotcha i suppose - if (!(messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(ComponentMessage<>))) + if ((messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(PendingComponentMessage<>))) { - throw new EngineMessageSelfCycleException("Engine {0} both reads and writes Message {1}", engine.GetType().Name, messageType.Name); + var componentType = messageType.GetGenericArguments().Single(); + throw new EngineSelfCycleException("Engine {0} both activates and reads pending Component {1}", engine.GetType().Name, componentType.Name); } + + throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageType.Name); } if (messageSendTypes.Any()) @@ -106,7 +106,7 @@ namespace Encompass if (receiveType.IsGenericType) { var genericTypeDefinition = receiveType.GetGenericTypeDefinition(); - if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(NewComponentMessage<>)) + if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(PendingComponentMessage<>)) { var componentType = receiveType.GetGenericArguments().Single(); if (!registeredComponentTypes.Contains(componentType)) @@ -129,7 +129,7 @@ namespace Encompass if (sendType.IsGenericType) { var genericTypeDefinition = sendType.GetGenericTypeDefinition(); - if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(NewComponentMessage<>)) + if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(PendingComponentMessage<>)) { var componentType = sendType.GetGenericArguments().Single(); if (!registeredNewComponentTypes.Contains(componentType)) @@ -195,12 +195,13 @@ namespace Encompass { var cycles = engineGraph.SimpleCycles(); var errorString = "Cycle(s) found in Engines: "; - foreach (var cycle in cycles.Reverse()) + foreach (var cycle in cycles) { + var reversed = cycle.Reverse(); errorString += "\n" + - string.Join(" -> ", cycle.Select((engine) => engine.GetType().Name)) + + string.Join(" -> ", reversed.Select((engine) => engine.GetType().Name)) + " -> " + - cycle.First().GetType().Name; + reversed.First().GetType().Name; } throw new EngineCycleException(errorString); } @@ -213,38 +214,35 @@ namespace Encompass { foreach (var updateType in engine.updateTypes) { - if (updateType.GetInterfaces().Contains(typeof(IComponent))) // if our write type is a component + if (mutatedComponentTypes.Contains(updateType)) { - if (mutatedComponentTypes.Contains(updateType)) - { - duplicateMutations.Add(updateType); - } - else - { - mutatedComponentTypes.Add(updateType); - } - - if (!componentToEngines.ContainsKey(updateType)) - { - componentToEngines[updateType] = new List(); - } - - componentToEngines[updateType].Add(engine); + duplicateMutations.Add(updateType); } + else + { + mutatedComponentTypes.Add(updateType); + } + + if (!componentToEngines.ContainsKey(updateType)) + { + componentToEngines[updateType] = new List(); + } + + componentToEngines[updateType].Add(engine); } } if (duplicateMutations.Count > 0) { - var errorString = "Multiple Engines write the same Component: "; + var errorString = "Multiple Engines update the same Component: "; foreach (var componentType in duplicateMutations) { errorString += "\n" + - componentType.Name + " written by: " + + componentType.Name + " updated by: " + string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name)); } - throw new EngineWriteConflictException(errorString); + throw new EngineUpdateConflictException(errorString); } var engineOrder = new List(); diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index 1fed047..36a41a0 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -124,6 +124,7 @@ namespace Tests } [Reads(typeof(MockComponent))] + [ReadsPending(typeof(MockComponent))] class HasMockComponentEngine : Engine { private Entity entity; @@ -135,7 +136,7 @@ namespace Tests public override void Update(double dt) { - Assert.IsTrue(HasComponent(entity)); + Assert.IsTrue(HasPendingComponent(entity)); } } @@ -468,14 +469,14 @@ namespace Tests } [Receives(typeof(CheckHasMockComponentMessage))] - [Reads(typeof(MockComponent))] + [ReadsPending(typeof(MockComponent))] class CheckHasMockComponentEngine : Engine { public override void Update(double dt) { foreach (var checkHasMockComponentMessage in ReadMessages()) { - Assert.IsTrue(HasComponent(checkHasMockComponentMessage.entity)); + Assert.IsTrue(HasPendingComponent(checkHasMockComponentMessage.entity)); } } } diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 172d763..e754b46 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -192,7 +192,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 write undeclared Component MockComponent")); + Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent")); } struct MockMessage : IMessage @@ -279,7 +279,7 @@ namespace Tests var world = worldBuilder.Build(); var ex = Assert.Throws(() => world.Update(0.01f)); - Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to write undeclared Message MockMessage")); + Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); } static bool someTest; diff --git a/test/EntityRendererTest.cs b/test/EntityRendererTest.cs index 5a52d89..b85f09f 100644 --- a/test/EntityRendererTest.cs +++ b/test/EntityRendererTest.cs @@ -48,8 +48,6 @@ namespace Tests world.Update(0.01f); - Console.WriteLine(renderer.IsTracking(entityNotToTrack.ID)); - Assert.IsTrue(renderer.IsTracking(entityToTrack.ID)); Assert.IsFalse(renderer.IsTracking(entityNotToTrack.ID)); Assert.IsFalse(renderer.IsTracking(entityWithoutDrawComponent.ID)); diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 6daa5f5..589c994 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -133,7 +133,7 @@ namespace Tests worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new BEngine()); - Assert.Throws(() => worldBuilder.Build()); + Assert.Throws(() => worldBuilder.Build()); } } @@ -156,7 +156,7 @@ namespace Tests { var worldBuilder = new WorldBuilder(); - Assert.Throws(() => worldBuilder.AddEngine(new AEngine()), "Engine both reads and writes Message AMessage"); + Assert.Throws(() => worldBuilder.AddEngine(new AEngine()), "Engine both sends and receives Message AMessage"); } }