started reworking component mutation

pull/5/head
Evan Hemsley 2019-07-16 09:47:58 -07:00
parent ff21ebcee0
commit 7ab512e522
11 changed files with 57 additions and 260 deletions

View File

@ -8,9 +8,8 @@ namespace Encompass
{ {
public abstract class Engine public abstract class Engine
{ {
internal readonly List<Type> mutateComponentTypes = new List<Type>(); internal readonly List<Type> writeTypes = new List<Type>();
internal readonly List<Type> emitMessageTypes = new List<Type>(); internal readonly List<Type> readTypes = new List<Type>();
internal readonly List<Type> readMessageTypes = new List<Type>();
private EntityManager entityManager; private EntityManager entityManager;
private ComponentManager componentManager; private ComponentManager componentManager;
@ -18,22 +17,16 @@ namespace Encompass
protected Engine() protected Engine()
{ {
var mutatesAttribute = GetType().GetCustomAttribute<Mutates>(false); var emitsAttribute = GetType().GetCustomAttribute<Writes>(false);
if (mutatesAttribute != null)
{
mutateComponentTypes = mutatesAttribute.mutateComponentTypes;
}
var emitsAttribute = GetType().GetCustomAttribute<Emits>(false);
if (emitsAttribute != null) if (emitsAttribute != null)
{ {
emitMessageTypes = emitsAttribute.emitMessageTypes; writeTypes = emitsAttribute.writeTypes;
} }
var readsAttribute = GetType().GetCustomAttribute<Reads>(false); var readsAttribute = GetType().GetCustomAttribute<Reads>(false);
if (readsAttribute != null) if (readsAttribute != null)
{ {
readMessageTypes = readsAttribute.readMessageTypes; readTypes = readsAttribute.readTypes;
} }
} }
@ -81,6 +74,7 @@ namespace Encompass
protected TComponent GetComponentByID<TComponent>(Guid componentID) where TComponent : struct, IComponent protected TComponent GetComponentByID<TComponent>(Guid componentID) where TComponent : struct, IComponent
{ {
if (componentManager.GetComponentTypeByID(componentID) == typeof(TComponent)) if (componentManager.GetComponentTypeByID(componentID) == typeof(TComponent))
{ {
return (TComponent)componentManager.GetComponentByID(componentID); return (TComponent)componentManager.GetComponentByID(componentID);
@ -103,13 +97,13 @@ namespace Encompass
internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent
{ {
if (mutateComponentTypes.Contains(typeof(TComponent))) if (writeTypes.Contains(typeof(TComponent)))
{ {
componentManager.UpdateComponent(componentID, newComponent); componentManager.UpdateComponent(componentID, newComponent);
} }
else 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>(TMessage message) where TMessage : struct, IMessage protected void EmitMessage<TMessage>(TMessage message) where TMessage : struct, IMessage
{ {
if (emitMessageTypes.Contains(typeof(TMessage))) if (writeTypes.Contains(typeof(TMessage)))
{ {
messageManager.AddMessage(message); messageManager.AddMessage(message);
} }
@ -132,7 +126,7 @@ namespace Encompass
protected IEnumerable<TMessage> ReadMessages<TMessage>() where TMessage : struct, IMessage protected IEnumerable<TMessage> ReadMessages<TMessage>() where TMessage : struct, IMessage
{ {
if (readMessageTypes.Contains(typeof(TMessage))) if (readTypes.Contains(typeof(TMessage)))
{ {
return messageManager.GetMessagesByType<TMessage>(); return messageManager.GetMessagesByType<TMessage>();
} }
@ -144,7 +138,7 @@ namespace Encompass
protected bool Some<TMessage>() where TMessage : struct, IMessage protected bool Some<TMessage>() where TMessage : struct, IMessage
{ {
if (readMessageTypes.Contains(typeof(TMessage))) if (readTypes.Contains(typeof(TMessage)))
{ {
return messageManager.GetMessagesByType<TMessage>().Any(); return messageManager.GetMessagesByType<TMessage>().Any();
} }

View File

@ -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<Type> componentTypes = new List<Type>();
private readonly EntityTracker entityTracker = new EntityTracker();
public IEnumerable<Type> ComponentTypes { get { return componentTypes; } }
protected Detector() : base()
{
var detectsAttribute = GetType().GetCustomAttribute<Detects>(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);
}
}
}

View File

@ -9,10 +9,10 @@ namespace Encompass.Engines
var readsAttribute = GetType().GetCustomAttribute<Reads>(false); var readsAttribute = GetType().GetCustomAttribute<Reads>(false);
if (readsAttribute != null) 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) public override void Update(double dt)

View File

@ -55,7 +55,7 @@ namespace Encompass
entityManager.RegisterEntityTracker(engine as IEntityTracker); entityManager.RegisterEntityTracker(engine as IEntityTracker);
} }
foreach (var emitMessageType in engine.emitMessageTypes) foreach (var emitMessageType in engine.writeTypes)
{ {
if (!messageTypeToEmitters.ContainsKey(emitMessageType)) 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)) if (!messageTypeToReaders.ContainsKey(readMessageType))
{ {
@ -140,37 +140,40 @@ namespace Encompass
foreach (var engine in engines) foreach (var engine in engines)
{ {
var mutateAttribute = engine.GetType().GetCustomAttribute<Mutates>(false); var writeAttribute = engine.GetType().GetCustomAttribute<Writes>(false);
if (mutateAttribute != null) if (writeAttribute != null)
{ {
foreach (var mutateComponentType in engine.GetType().GetCustomAttribute<Mutates>(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); if (mutatedComponentTypes.Contains(writeType))
{
duplicateMutations.Add(writeType);
} }
else else
{ {
mutatedComponentTypes.Add(mutateComponentType); mutatedComponentTypes.Add(writeType);
} }
if (!componentToEngines.ContainsKey(mutateComponentType)) if (!componentToEngines.ContainsKey(writeType))
{ {
componentToEngines[mutateComponentType] = new List<Engine>(); componentToEngines[writeType] = new List<Engine>();
} }
componentToEngines[mutateComponentType].Add(engine); componentToEngines[writeType].Add(engine);
}
} }
} }
} }
if (duplicateMutations.Count > 0) 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) foreach (var componentType in duplicateMutations)
{ {
errorString += "\n" + errorString += "\n" +
componentType.Name + " mutated by: " + componentType.Name + " written by: " +
string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name)); string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name));
} }

View File

@ -4,13 +4,13 @@ using System.Collections.Generic;
namespace Encompass namespace Encompass
{ {
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class Emits : Attribute public class Writes : Attribute
{ {
public readonly List<Type> emitMessageTypes; public readonly List<Type> writeTypes;
public Emits(params Type[] emitMessageTypes) public Writes(params Type[] writeTypes)
{ {
this.emitMessageTypes = new List<Type>(emitMessageTypes); this.writeTypes = new List<Type>(writeTypes);
} }
} }
} }

View File

@ -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<Type> mutateComponentTypes;
public Mutates(params Type[] mutateComponentTypes)
{
this.mutateComponentTypes = new List<Type>(mutateComponentTypes);
}
}
}

View File

@ -6,11 +6,11 @@ namespace Encompass
[System.AttributeUsage(System.AttributeTargets.Class)] [System.AttributeUsage(System.AttributeTargets.Class)]
public class Reads : System.Attribute public class Reads : System.Attribute
{ {
public readonly List<Type> readMessageTypes; public readonly List<Type> readTypes;
public Reads(params Type[] readMessageTypes) public Reads(params Type[] readMessageTypes)
{ {
this.readMessageTypes = new List<Type>(readMessageTypes); this.readTypes = new List<Type>(readMessageTypes);
} }
} }
} }

View File

@ -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<DetectorWithoutComponentTypesException>();
}
struct AComponent : IComponent { }
struct BComponent : IComponent { }
struct CComponent : IComponent { }
static List<Entity> trackedEntities = new List<Entity>();
[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);
}
}
}

View File

@ -110,7 +110,7 @@ namespace Tests
Assert.Throws<InvalidOperationException>(() => world.Update(0.01f)); Assert.Throws<InvalidOperationException>(() => world.Update(0.01f));
} }
[Mutates(typeof(MockComponent))] [Writes(typeof(MockComponent))]
public class UpdateComponentTestEngine : Engine public class UpdateComponentTestEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -178,7 +178,7 @@ namespace Tests
var world = worldBuilder.Build(); var world = worldBuilder.Build();
var ex = Assert.Throws<IllegalComponentMutationException>(() => world.Update(0.01f)); var ex = Assert.Throws<IllegalComponentMutationException>(() => 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 struct MockMessage : IMessage
@ -186,7 +186,7 @@ namespace Tests
public string myString; public string myString;
} }
[Emits(typeof(MockMessage))] [Writes(typeof(MockMessage))]
public class MessageEmitEngine : Engine public class MessageEmitEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -269,7 +269,7 @@ namespace Tests
static bool someTest; static bool someTest;
[Emits(typeof(MockMessage))] [Writes(typeof(MockMessage))]
class EmitMockMessageEngine : Engine class EmitMockMessageEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)

View File

@ -14,7 +14,7 @@ namespace Tests
static Entity resultEntity; static Entity resultEntity;
[Emits(typeof(SpawnMessageA))] [Writes(typeof(SpawnMessageA))]
class MessageEmitter : Engine class MessageEmitter : Engine
{ {
public override void Update(double dt) public override void Update(double dt)

View File

@ -14,7 +14,7 @@ namespace Tests
struct BMessage : IMessage { } struct BMessage : IMessage { }
[Reads(typeof(AMessage))] [Reads(typeof(AMessage))]
[Emits(typeof(BMessage))] [Writes(typeof(BMessage))]
class AEngine : Engine class AEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -25,7 +25,7 @@ namespace Tests
} }
[Reads(typeof(BMessage))] [Reads(typeof(BMessage))]
[Emits(typeof(AMessage))] [Writes(typeof(AMessage))]
class BEngine : Engine class BEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -54,7 +54,7 @@ namespace Tests
struct DMessage : IMessage { } struct DMessage : IMessage { }
[Reads(typeof(AMessage))] [Reads(typeof(AMessage))]
[Emits(typeof(BMessage))] [Writes(typeof(BMessage))]
class AEngine : Engine class AEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -65,7 +65,7 @@ namespace Tests
} }
[Reads(typeof(BMessage))] [Reads(typeof(BMessage))]
[Emits(typeof(CMessage))] [Writes(typeof(CMessage))]
class BEngine : Engine class BEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -76,7 +76,7 @@ namespace Tests
} }
[Reads(typeof(CMessage))] [Reads(typeof(CMessage))]
[Emits(typeof(DMessage))] [Writes(typeof(DMessage))]
class CEngine : Engine class CEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -87,7 +87,7 @@ namespace Tests
} }
[Reads(typeof(DMessage))] [Reads(typeof(DMessage))]
[Emits(typeof(AMessage))] [Writes(typeof(AMessage))]
class DEngine : Engine class DEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -114,13 +114,13 @@ namespace Tests
{ {
struct AComponent : IComponent { } struct AComponent : IComponent { }
[Mutates(typeof(AComponent))] [Writes(typeof(AComponent))]
class AEngine : Engine class AEngine : Engine
{ {
public override void Update(double dt) { } public override void Update(double dt) { }
} }
[Mutates(typeof(AComponent))] [Writes(typeof(AComponent))]
class BEngine : Engine class BEngine : Engine
{ {
public override void Update(double dt) { } public override void Update(double dt) { }
@ -149,8 +149,7 @@ namespace Tests
struct CMessage : IMessage { } struct CMessage : IMessage { }
struct DMessage : IMessage { } struct DMessage : IMessage { }
[Mutates(typeof(AComponent))] [Writes(typeof(AComponent), typeof(AMessage))]
[Emits(typeof(AMessage))]
class AEngine : Engine class AEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -159,8 +158,7 @@ namespace Tests
} }
} }
[Mutates(typeof(BComponent))] [Writes(typeof(BComponent), typeof(BMessage))]
[Emits(typeof(BMessage))]
class BEngine : Engine class BEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -170,7 +168,7 @@ namespace Tests
} }
[Reads(typeof(AMessage), typeof(BMessage))] [Reads(typeof(AMessage), typeof(BMessage))]
[Emits(typeof(DMessage))] [Writes(typeof(DMessage))]
class CEngine : Engine class CEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)