engines must declare component reads + validation for Reads and Writes arguments
parent
7ab512e522
commit
9b58473ae8
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)) { }
|
|
@ -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)) { }
|
|
@ -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)) { }
|
|
@ -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)) { }
|
|
@ -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)) { }
|
||||
}
|
||||
}
|
|
@ -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)) { }
|
||||
}
|
||||
}
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue