engines must declare component reads + validation for Reads and Writes arguments

pull/5/head
Evan Hemsley 2019-07-16 11:17:07 -07:00
parent 7ab512e522
commit 9b58473ae8
14 changed files with 230 additions and 77 deletions

View File

@ -74,38 +74,48 @@ namespace Encompass
protected TComponent GetComponentByID<TComponent>(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<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() 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<TComponent>();
}
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() 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<TComponent>();
}
internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent
{
if (writeTypes.Contains(typeof(TComponent)))
if (!writeTypes.Contains(typeof(TComponent)))
{
throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
}
componentManager.UpdateComponent(componentID, newComponent);
}
else
{
throw new IllegalComponentMutationException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
}
}
protected void UpdateComponent<TComponent>(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent
{
@ -114,39 +124,34 @@ namespace Encompass
protected void EmitMessage<TMessage>(TMessage message) where TMessage : struct, IMessage
{
if (writeTypes.Contains(typeof(TMessage)))
if (!writeTypes.Contains(typeof(TMessage)))
{
throw new IllegalWriteException("Engine {0} tried to emit undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
messageManager.AddMessage(message);
}
else
{
throw new IllegalMessageEmitException("Engine {0} tried to emit undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
}
protected IEnumerable<TMessage> ReadMessages<TMessage>() where TMessage : struct, IMessage
{
if (readTypes.Contains(typeof(TMessage)))
if (!readTypes.Contains(typeof(TMessage)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
return messageManager.GetMessagesByType<TMessage>();
}
else
{
throw new IllegalMessageReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
}
protected bool Some<TMessage>() where TMessage : struct, IMessage
{
if (readTypes.Contains(typeof(TMessage)))
if (!readTypes.Contains(typeof(TMessage)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
return messageManager.GetMessagesByType<TMessage>().Any();
}
else
{
throw new IllegalMessageReadException("Engine {0} tried to read undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
}
protected void Destroy(Guid entityID)
{

View File

@ -17,8 +17,8 @@ namespace Encompass
private readonly DrawLayerManager drawLayerManager;
private readonly RenderManager renderManager;
private readonly Dictionary<Type, HashSet<Engine>> messageTypeToEmitters = new Dictionary<Type, HashSet<Engine>>();
private readonly Dictionary<Type, HashSet<Engine>> messageTypeToReaders = new Dictionary<Type, HashSet<Engine>>();
private readonly Dictionary<Type, HashSet<Engine>> typeToEmitters = new Dictionary<Type, HashSet<Engine>>();
private readonly Dictionary<Type, HashSet<Engine>> typeToReaders = new Dictionary<Type, HashSet<Engine>>();
public WorldBuilder()
{
@ -55,41 +55,61 @@ 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<Engine>());
typeToEmitters.Add(writeType, new HashSet<Engine>());
}
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])
{
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)
{
if (!messageTypeToReaders.ContainsKey(readMessageType))
{
messageTypeToReaders.Add(readMessageType, new HashSet<Engine>());
}
messageTypeToReaders[readMessageType].Add(engine);
if (messageTypeToEmitters.ContainsKey(readMessageType))
foreach (var readType in engine.readTypes)
{
foreach (var emitter in messageTypeToEmitters[readMessageType])
if (!typeToReaders.ContainsKey(readType))
{
typeToReaders.Add(readType, new HashSet<Engine>());
}
typeToReaders[readType].Add(engine);
if (typeToEmitters.ContainsKey(readType))
{
foreach (var emitter in typeToEmitters[readType])
{
if (emitter == engine)
{
if (readType.GetInterfaces().Contains(typeof(IMessage)))
{
throw new EngineMessageSelfCycleException("Engine both reads and writes Message {0}", readType.Name);
}
}
else
{
engineGraph.AddEdge(emitter, engine);
}
}
}
}
return engine;
}
@ -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<Engine>();

View File

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

View File

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

View File

@ -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<Type> 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<Type>(writeTypes);
}
}
}

View File

@ -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)) { }

View File

@ -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)) { }

View File

@ -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)) { }

View File

@ -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)) { }

View File

@ -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)) { }
}
}

View File

@ -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)) { }
}
}

View File

@ -27,7 +27,7 @@ namespace Encompass
}
protected List<T> _vertices = new List<T>();
protected Dictionary<T, List<T>> _neighbors = new Dictionary<T, List<T>>();
protected Dictionary<T, HashSet<T>> _neighbors = new Dictionary<T, HashSet<T>>();
public IEnumerable<T> Vertices { get { return _vertices; } }
@ -40,7 +40,7 @@ namespace Encompass
if (!VertexExists(vertex))
{
_vertices.Add(vertex);
_neighbors.Add(vertex, new List<T>());
_neighbors.Add(vertex, new HashSet<T>());
}
}

View File

@ -17,6 +17,7 @@ namespace Tests
static List<MockMessage> 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<InvalidOperationException>(() => 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<IllegalComponentMutationException>(() => world.Update(0.01f));
var ex = Assert.Throws<IllegalWriteException>(() => 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<IllegalMessageEmitException>(() => world.Update(0.01f));
var ex = Assert.Throws<IllegalWriteException>(() => 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<IllegalMessageReadException>(() => world.Update(0.01f));
Assert.Throws<IllegalReadException>(() => world.Update(0.01f));
}
static ValueTuple<Guid, MockComponent> pairA;
static ValueTuple<Guid, MockComponent> pairB;
[Reads(typeof(MockComponent))]
class SameValueComponentReadEngine : Engine
{
public override void Update(double dt)
@ -366,6 +371,7 @@ namespace Tests
static IEnumerable<ValueTuple<Guid, MockComponent>> 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<ValueTuple<Guid, MockComponent>> 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)

View File

@ -133,7 +133,74 @@ namespace Tests
worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine());
Assert.Throws<EngineMutationConflictException>(() => worldBuilder.Build());
Assert.Throws<EngineWriteConflictException>(() => 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<EngineMessageSelfCycleException>(() => 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<IllegalReadTypeException>(() => 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<IllegalWriteTypeException>(() => worldBuilder.AddEngine(new MyEngine()), "ANonMessage must be a Message or Component");
}
}