the big ComponentMessage rework
parent
0b2ef55634
commit
a42123f58d
11
TODO
11
TODO
|
@ -1,11 +1,6 @@
|
|||
- guard rail tests for nonexistent lookups in manager classes
|
||||
- custom exception for nonexistent Entity ID
|
||||
- custom exception for nonexistent Component ID
|
||||
- Change "Writes" To "Sends" and make it only take Messages
|
||||
- Add "Updates" for updating components
|
||||
|
||||
- special Engine kinds (detector, modifier, spawner)
|
||||
|
||||
- maybe AddEngine should take a constructed engine similarly to AddComponent?
|
||||
|
||||
- component getters should return ValueTuple instead of KeyValuePair so we can do destructuring assignments
|
||||
- so we have four attributes: Activates, Reads, Updates, and Sends
|
||||
|
||||
- docs
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Encompass.Exceptions;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
public class Activates : Attribute
|
||||
{
|
||||
public readonly HashSet<Type> activateTypes = new HashSet<Type>();
|
||||
|
||||
public Activates(params Type[] activateTypes)
|
||||
{
|
||||
foreach (var activateType in activateTypes)
|
||||
{
|
||||
var isComponent = activateType.GetInterfaces().Contains(typeof(IComponent));
|
||||
if (!isComponent)
|
||||
{
|
||||
throw new IllegalActivateTypeException("{0} must be a Component", activateType.Name);
|
||||
}
|
||||
|
||||
this.activateTypes.Add(typeof(ComponentMessage<>).MakeGenericType(activateType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,22 +5,32 @@ using Encompass.Exceptions;
|
|||
|
||||
namespace Encompass
|
||||
{
|
||||
[System.AttributeUsage(System.AttributeTargets.Class)]
|
||||
public class Reads : System.Attribute
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class Reads : Attribute
|
||||
{
|
||||
public readonly HashSet<Type> readTypes;
|
||||
public readonly HashSet<Type> readTypes = new HashSet<Type>();
|
||||
|
||||
public Reads(params Type[] readTypes)
|
||||
{
|
||||
foreach (var readType in readTypes)
|
||||
{
|
||||
if (!readType.GetInterfaces().Contains(typeof(IMessage)))
|
||||
var isMessage = readType.GetInterfaces().Contains(typeof(IMessage));
|
||||
var isComponent = readType.GetInterfaces().Contains(typeof(IComponent));
|
||||
|
||||
if (!isMessage && !isComponent)
|
||||
{
|
||||
throw new IllegalReadTypeException("{0} must be a Message", readType.Name);
|
||||
throw new IllegalReadTypeException("{0} must be a Message or Component", readType.Name);
|
||||
}
|
||||
|
||||
if (isComponent)
|
||||
{
|
||||
this.readTypes.Add(typeof(ComponentMessage<>).MakeGenericType(readType));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.readTypes.Add(readType);
|
||||
}
|
||||
}
|
||||
|
||||
this.readTypes = new HashSet<Type>(readTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,8 +62,7 @@ namespace Encompass
|
|||
entityIDToComponentIDs[entityID].Add(componentID);
|
||||
componentIDToEntityID[componentID] = entityID;
|
||||
|
||||
inactiveComponents.Add(componentID);
|
||||
MarkForActivation(componentID);
|
||||
activeComponents.Add(componentID);
|
||||
|
||||
entitiesWithAddedComponents.Add(entityID);
|
||||
|
||||
|
@ -89,11 +88,11 @@ namespace Encompass
|
|||
return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple<Guid, IComponent>(id, IDToComponent[id]));
|
||||
}
|
||||
|
||||
internal IEnumerable<ValueTuple<Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent
|
||||
internal IEnumerable<ValueTuple<Guid, Guid, TComponent>> GetActiveComponentsByType<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return typeToComponentIDs.ContainsKey(typeof(TComponent)) ?
|
||||
typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, TComponent>(id, (TComponent)IDToComponent[id])) :
|
||||
Enumerable.Empty<ValueTuple<Guid, TComponent>>();
|
||||
typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple<Guid, Guid, TComponent>(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) :
|
||||
Enumerable.Empty<ValueTuple<Guid, Guid, TComponent>>();
|
||||
}
|
||||
|
||||
internal IEnumerable<ValueTuple<Guid, IComponent>> GetActiveComponentsByType(Type type)
|
||||
|
@ -103,16 +102,11 @@ namespace Encompass
|
|||
Enumerable.Empty<ValueTuple<Guid, IComponent>>();
|
||||
}
|
||||
|
||||
internal ValueTuple<Guid, TComponent> GetActiveComponentByType<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return GetActiveComponentsByType<TComponent>().Single();
|
||||
}
|
||||
|
||||
internal IEnumerable<ValueTuple<Guid, TComponent>> GetComponentsByEntityAndType<TComponent>(Guid entityID) where TComponent : struct, IComponent
|
||||
{
|
||||
var entityComponentsByType = GetComponentsByEntity(entityID).Where((pair) => componentIDToType[pair.Item1] == typeof(TComponent)).Select((pair) => new ValueTuple<Guid, TComponent>(pair.Item1, (TComponent)pair.Item2));
|
||||
var activeComponentsByType = GetActiveComponentsByType<TComponent>();
|
||||
return activeComponentsByType.Intersect(entityComponentsByType);
|
||||
return activeComponentsByType.Select((triple) => (triple.Item2, triple.Item3)).Intersect(entityComponentsByType);
|
||||
}
|
||||
|
||||
internal IEnumerable<ValueTuple<Guid, IComponent>> GetComponentsByEntityAndType(Guid entityID, Type type)
|
||||
|
@ -180,22 +174,7 @@ namespace Encompass
|
|||
}
|
||||
}
|
||||
|
||||
internal void MarkForActivation(Guid componentID)
|
||||
{
|
||||
componentsMarkedForActivation.Add(componentID);
|
||||
}
|
||||
|
||||
internal void ActivateMarkedComponents()
|
||||
{
|
||||
foreach (var componentID in componentsMarkedForActivation)
|
||||
{
|
||||
Activate(componentID);
|
||||
}
|
||||
|
||||
componentsMarkedForActivation.Clear();
|
||||
}
|
||||
|
||||
private void Activate(Guid componentID)
|
||||
internal void Activate(Guid componentID)
|
||||
{
|
||||
if (inactiveComponents.Remove(componentID))
|
||||
{
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
public struct ComponentMessage<TComponent> : IMessage where TComponent : struct, IComponent
|
||||
{
|
||||
public Entity entity;
|
||||
public Guid componentID;
|
||||
public TComponent component;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace Encompass
|
|||
{
|
||||
internal readonly HashSet<Type> writeTypes = new HashSet<Type>();
|
||||
internal readonly HashSet<Type> readTypes = new HashSet<Type>();
|
||||
internal readonly HashSet<Type> activateTypes = new HashSet<Type>();
|
||||
|
||||
private EntityManager entityManager;
|
||||
private ComponentManager componentManager;
|
||||
|
@ -28,6 +29,12 @@ namespace Encompass
|
|||
{
|
||||
readTypes = readsAttribute.readTypes;
|
||||
}
|
||||
|
||||
var activatesAttribute = GetType().GetCustomAttribute<Activates>(false);
|
||||
if (activatesAttribute != null)
|
||||
{
|
||||
activateTypes = activatesAttribute.activateTypes;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AssignEntityManager(EntityManager entityManager)
|
||||
|
@ -82,29 +89,64 @@ namespace Encompass
|
|||
return (TComponent)componentManager.GetComponentByID(componentID);
|
||||
}
|
||||
|
||||
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
|
||||
internal IEnumerable<ValueTuple<Entity, Guid, TComponent>> ReadComponentsFromWorld<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.GetActiveComponentsByType<TComponent>();
|
||||
}
|
||||
|
||||
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.GetActiveComponentByType<TComponent>();
|
||||
return componentManager.GetActiveComponentsByType<TComponent>().Select((triple) => (GetEntity(triple.Item1), triple.Item2, triple.Item3));
|
||||
}
|
||||
|
||||
protected Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.AddComponent(entity.ID, component);
|
||||
if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
var componentID = componentManager.AddComponent(entity.ID, component);
|
||||
|
||||
ComponentMessage<TComponent> componentMessage;
|
||||
componentMessage.entity = entity;
|
||||
componentMessage.componentID = componentID;
|
||||
componentMessage.component = component;
|
||||
EmitMessage(componentMessage);
|
||||
|
||||
return componentID;
|
||||
}
|
||||
|
||||
protected Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.AddDrawComponent(entity.ID, component, layer);
|
||||
if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
var componentID = componentManager.AddDrawComponent(entity.ID, component, layer);
|
||||
|
||||
ComponentMessage<TComponent> componentMessage;
|
||||
componentMessage.entity = entity;
|
||||
componentMessage.componentID = componentID;
|
||||
componentMessage.component = component;
|
||||
EmitMessage(componentMessage);
|
||||
|
||||
return componentID;
|
||||
}
|
||||
|
||||
protected void ActivateComponent(Guid componentID)
|
||||
protected void ActivateComponent<TComponent>(Guid componentID) where TComponent : struct, IComponent
|
||||
{
|
||||
componentManager.MarkForActivation(componentID);
|
||||
if (!activateTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
var entity = GetEntity(componentManager.GetEntityIDByComponentID(componentID));
|
||||
var component = GetComponentByID<TComponent>(componentID);
|
||||
|
||||
ComponentMessage<TComponent> componentMessage;
|
||||
componentMessage.entity = entity;
|
||||
componentMessage.componentID = componentID;
|
||||
componentMessage.component = component;
|
||||
EmitMessage(componentMessage);
|
||||
|
||||
componentManager.Activate(componentID);
|
||||
}
|
||||
|
||||
protected void DeactivateComponent(Guid componentID)
|
||||
|
@ -114,6 +156,11 @@ namespace Encompass
|
|||
|
||||
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent
|
||||
{
|
||||
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
return componentManager.GetComponentsByEntityAndType<TComponent>(entity.ID);
|
||||
}
|
||||
|
||||
|
@ -124,7 +171,12 @@ namespace Encompass
|
|||
|
||||
protected bool HasComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.EntityHasComponentOfType<TComponent>(entity.ID);
|
||||
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
return messageManager.GetMessagesByType<ComponentMessage<TComponent>>().Where((message) => message.entity == entity).Any();
|
||||
}
|
||||
|
||||
internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent
|
||||
|
@ -162,11 +214,31 @@ namespace Encompass
|
|||
return messageManager.GetMessagesByType<TMessage>();
|
||||
}
|
||||
|
||||
protected TMessage ReadMessage<TMessage>() where TMessage : struct, IMessage
|
||||
{
|
||||
return ReadMessages<TMessage>().Single();
|
||||
}
|
||||
|
||||
protected IEnumerable<(Guid, TComponent)> ReadComponents<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
return ReadMessages<ComponentMessage<TComponent>>().Select((message) => (message.componentID, message.component));
|
||||
}
|
||||
|
||||
protected (Guid, TComponent) ReadComponent<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return ReadComponents<TComponent>().Single();
|
||||
}
|
||||
|
||||
protected bool SomeMessage<TMessage>() where TMessage : struct, IMessage
|
||||
{
|
||||
if (!readTypes.Contains(typeof(TMessage)))
|
||||
{
|
||||
throw new IllegalReadException("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}", GetType().Name, typeof(TMessage).Name);
|
||||
}
|
||||
|
||||
return messageManager.GetMessagesByType<TMessage>().Any();
|
||||
|
@ -174,6 +246,11 @@ namespace Encompass
|
|||
|
||||
protected bool SomeComponent<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
if (!readTypes.Contains(typeof(ComponentMessage<TComponent>)))
|
||||
{
|
||||
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
|
||||
}
|
||||
|
||||
return componentManager.GetActiveComponentsByType<TComponent>().Any();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Encompass.Engines
|
||||
{
|
||||
internal class ComponentMessageEmitter<TComponent> : Engine where TComponent : struct, IComponent
|
||||
{
|
||||
public ComponentMessageEmitter() : base()
|
||||
{
|
||||
var writesAttribute = GetType().GetCustomAttribute<Writes>(false);
|
||||
if (writesAttribute != null)
|
||||
{
|
||||
writesAttribute.writeTypes.Add(typeof(ComponentMessage<TComponent>));
|
||||
}
|
||||
|
||||
writeTypes.Add(typeof(ComponentMessage<TComponent>));
|
||||
}
|
||||
|
||||
public override void Update(double dt)
|
||||
{
|
||||
foreach (var (entity, componentID, component) in ReadComponentsFromWorld<TComponent>())
|
||||
{
|
||||
ComponentMessage<TComponent> componentMessage;
|
||||
componentMessage.entity = entity;
|
||||
componentMessage.componentID = componentID;
|
||||
componentMessage.component = component;
|
||||
EmitMessage(componentMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Encompass
|
||||
{
|
||||
public struct Entity
|
||||
public struct Entity : IEquatable<Entity>
|
||||
{
|
||||
public readonly Guid ID;
|
||||
|
||||
|
@ -10,5 +10,35 @@ namespace Encompass
|
|||
{
|
||||
this.ID = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Entity)
|
||||
{
|
||||
return this.Equals((Entity)obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(Entity other)
|
||||
{
|
||||
return other.ID == ID;
|
||||
}
|
||||
|
||||
public static bool operator ==(Entity one, Entity two)
|
||||
{
|
||||
return one.Equals(two);
|
||||
}
|
||||
|
||||
public static bool operator !=(Entity one, Entity two)
|
||||
{
|
||||
return !one.Equals(two);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ID.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass.Exceptions
|
||||
{
|
||||
public class IllegalActivateException : Exception
|
||||
{
|
||||
public IllegalActivateException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass.Exceptions
|
||||
{
|
||||
public class IllegalActivateTypeException : Exception
|
||||
{
|
||||
public IllegalActivateTypeException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass.Exceptions
|
||||
{
|
||||
public class UnregisteredComponentReadException : Exception
|
||||
{
|
||||
public UnregisteredComponentReadException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -41,12 +41,12 @@ namespace Encompass
|
|||
|
||||
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.GetActiveComponentsByType<TComponent>();
|
||||
return componentManager.GetActiveComponentsByType<TComponent>().Select((triple) => (triple.Item2, triple.Item3));
|
||||
}
|
||||
|
||||
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
|
||||
{
|
||||
return componentManager.GetActiveComponentByType<TComponent>();
|
||||
return ReadComponents<TComponent>().Single();
|
||||
}
|
||||
|
||||
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace Encompass
|
|||
entityManager.DestroyMarkedEntities();
|
||||
|
||||
componentManager.PerformComponentUpdates();
|
||||
componentManager.ActivateMarkedComponents();
|
||||
componentManager.DeactivateMarkedComponents();
|
||||
componentManager.RemoveMarkedComponents();
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using Encompass.Exceptions;
|
||||
using Encompass.Engines;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
|
@ -17,9 +18,12 @@ namespace Encompass
|
|||
private readonly DrawLayerManager drawLayerManager;
|
||||
private readonly RenderManager renderManager;
|
||||
|
||||
private readonly Dictionary<Type, HashSet<Engine>> typeToEmitters = new Dictionary<Type, HashSet<Engine>>();
|
||||
private readonly Dictionary<Type, HashSet<Engine>> typeToReaders = new Dictionary<Type, HashSet<Engine>>();
|
||||
|
||||
private readonly HashSet<Engine> senders = new HashSet<Engine>();
|
||||
|
||||
private readonly HashSet<Type> registeredComponentTypes = new HashSet<Type>();
|
||||
|
||||
public WorldBuilder()
|
||||
{
|
||||
var entitiesWithAddedComponents = new HashSet<Guid>();
|
||||
|
@ -56,6 +60,12 @@ namespace Encompass
|
|||
componentManager.MarkForDeactivation(componentID);
|
||||
}
|
||||
|
||||
internal void RegisterComponent(Type componentType)
|
||||
{
|
||||
registeredComponentTypes.Add(componentType);
|
||||
AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType)));
|
||||
}
|
||||
|
||||
public Engine AddEngine<TEngine>(TEngine engine) where TEngine : Engine
|
||||
{
|
||||
engine.AssignEntityManager(entityManager);
|
||||
|
@ -65,60 +75,42 @@ namespace Encompass
|
|||
engines.Add(engine);
|
||||
engineGraph.AddVertex(engine);
|
||||
|
||||
foreach (var writeType in engine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))))
|
||||
foreach (var activateType in engine.activateTypes)
|
||||
{
|
||||
if (!typeToEmitters.ContainsKey(writeType))
|
||||
{
|
||||
typeToEmitters.Add(writeType, new HashSet<Engine>());
|
||||
}
|
||||
|
||||
typeToEmitters[writeType].Add(engine);
|
||||
|
||||
if (typeToReaders.ContainsKey(writeType))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
engine.writeTypes.Add(activateType);
|
||||
}
|
||||
|
||||
foreach (var readType in engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))))
|
||||
var messageReadTypes = engine.readTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)));
|
||||
var messageSendTypes = engine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage)));
|
||||
|
||||
if (messageReadTypes.Intersect(messageSendTypes).Any())
|
||||
{
|
||||
var type = messageReadTypes.Intersect(messageSendTypes).First();
|
||||
throw new EngineMessageSelfCycleException("Engine {0} both reads and writes Message {1}", engine.GetType().Name, type.Name);
|
||||
}
|
||||
|
||||
if (messageSendTypes.Any())
|
||||
{
|
||||
senders.Add(engine);
|
||||
}
|
||||
|
||||
foreach (var readType in engine.readTypes)
|
||||
{
|
||||
if (readType.IsGenericType && readType.GetGenericTypeDefinition() == typeof(ComponentMessage<>))
|
||||
{
|
||||
var componentType = readType.GetGenericArguments().Single();
|
||||
if (!registeredComponentTypes.Contains(componentType))
|
||||
{
|
||||
RegisterComponent(componentType);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -148,8 +140,27 @@ namespace Encompass
|
|||
return renderer;
|
||||
}
|
||||
|
||||
private void BuildEngineGraph()
|
||||
{
|
||||
foreach (var senderEngine in senders)
|
||||
{
|
||||
foreach (var messageType in senderEngine.writeTypes.Where((type) => type.GetInterfaces().Contains(typeof(IMessage))))
|
||||
{
|
||||
if (typeToReaders.ContainsKey(messageType))
|
||||
{
|
||||
foreach (var readerEngine in typeToReaders[messageType])
|
||||
{
|
||||
engineGraph.AddEdge(senderEngine, readerEngine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public World Build()
|
||||
{
|
||||
BuildEngineGraph();
|
||||
|
||||
if (engineGraph.Cyclic())
|
||||
{
|
||||
var cycles = engineGraph.SimpleCycles();
|
||||
|
@ -225,7 +236,6 @@ namespace Encompass
|
|||
);
|
||||
|
||||
componentManager.PerformComponentUpdates();
|
||||
componentManager.ActivateMarkedComponents();
|
||||
componentManager.DeactivateMarkedComponents();
|
||||
componentManager.RemoveMarkedComponents();
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace Tests
|
|||
static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>();
|
||||
static (Guid, MockComponent) gottenMockComponentIDPair;
|
||||
|
||||
[Reads(typeof(EntityMessage))]
|
||||
[Reads(typeof(EntityMessage), typeof(MockComponent))]
|
||||
class GetMockComponentsEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -38,7 +38,7 @@ namespace Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(EntityMessage))]
|
||||
[Reads(typeof(EntityMessage), typeof(MockComponent))]
|
||||
class GetMockComponentEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -56,8 +56,8 @@ namespace Tests
|
|||
public MockComponent mockComponent;
|
||||
}
|
||||
|
||||
[Reads(typeof(AddComponentTestMessage))]
|
||||
class AddComponentEngine : Engine
|
||||
[Reads(typeof(AddComponentTestMessage), typeof(MockComponent))]
|
||||
class AddComponentTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
|
@ -73,8 +73,7 @@ namespace Tests
|
|||
public void AddComponent()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine(new AddComponentEngine());
|
||||
worldBuilder.AddEngine(new GetMockComponentEngine());
|
||||
worldBuilder.AddEngine(new AddComponentTestEngine());
|
||||
|
||||
var entity = worldBuilder.CreateEntity();
|
||||
|
||||
|
@ -94,6 +93,79 @@ namespace Tests
|
|||
world.Update(0.01);
|
||||
}
|
||||
|
||||
struct AddMockComponentMessage : IMessage
|
||||
{
|
||||
public Entity entity;
|
||||
public MockComponent mockComponent;
|
||||
}
|
||||
|
||||
[Writes(typeof(AddMockComponentMessage))]
|
||||
class EmitMockComponentMessageEngine : Engine
|
||||
{
|
||||
private Entity entity;
|
||||
|
||||
public EmitMockComponentMessageEngine(Entity entity)
|
||||
{
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public override void Update(double dt)
|
||||
{
|
||||
MockComponent mockComponent;
|
||||
mockComponent.myInt = 10;
|
||||
mockComponent.myString = "four";
|
||||
|
||||
AddMockComponentMessage addMockComponentMessage;
|
||||
addMockComponentMessage.entity = entity;
|
||||
addMockComponentMessage.mockComponent = mockComponent;
|
||||
EmitMessage(addMockComponentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[Activates(typeof(MockComponent))]
|
||||
[Reads(typeof(AddMockComponentMessage))]
|
||||
class AddMockComponentEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
foreach (var message in ReadMessages<AddMockComponentMessage>())
|
||||
{
|
||||
AddComponent(message.entity, message.mockComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class HasMockComponentEngine : Engine
|
||||
{
|
||||
private Entity entity;
|
||||
|
||||
public HasMockComponentEngine(Entity entity)
|
||||
{
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public override void Update(double dt)
|
||||
{
|
||||
Assert.IsTrue(HasComponent<MockComponent>(entity));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddComponentAndReadSameFrame()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
var entity = worldBuilder.CreateEntity();
|
||||
|
||||
worldBuilder.AddEngine(new EmitMockComponentMessageEngine(entity));
|
||||
worldBuilder.AddEngine(new AddMockComponentEngine());
|
||||
worldBuilder.AddEngine(new HasMockComponentEngine(entity));
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
world.Update(0.01);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetComponents()
|
||||
{
|
||||
|
@ -161,7 +233,7 @@ namespace Tests
|
|||
public Entity entity;
|
||||
}
|
||||
|
||||
[Reads(typeof(HasComponentTestMessage))]
|
||||
[Reads(typeof(HasComponentTestMessage), typeof(MockComponent))]
|
||||
class HasComponentTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -201,7 +273,7 @@ namespace Tests
|
|||
public Entity entity;
|
||||
}
|
||||
|
||||
[Reads(typeof(HasComponentWhenInactiveTestMessage))]
|
||||
[Reads(typeof(HasComponentWhenInactiveTestMessage), typeof(MockComponent))]
|
||||
class HasComponentWhenInactiveTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -244,7 +316,6 @@ namespace Tests
|
|||
}
|
||||
|
||||
[Reads(typeof(RemoveComponentTestMessage))]
|
||||
[Writes(typeof(MockComponent))]
|
||||
class RemoveComponentTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -318,15 +389,15 @@ namespace Tests
|
|||
public Guid componentID;
|
||||
}
|
||||
|
||||
[Activates(typeof(MockComponent))]
|
||||
[Reads(typeof(ActivateComponentMessage))]
|
||||
[Writes(typeof(MockComponent))]
|
||||
class ActivateComponentEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
foreach (var activateComponentMessage in ReadMessages<ActivateComponentMessage>())
|
||||
{
|
||||
ActivateComponent(activateComponentMessage.componentID);
|
||||
ActivateComponent<MockComponent>(activateComponentMessage.componentID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +438,7 @@ namespace Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(CheckHasMockComponentMessage))]
|
||||
[Reads(typeof(CheckHasMockComponentMessage), typeof(MockComponent))]
|
||||
class CheckHasMockComponentEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -386,6 +457,8 @@ namespace Tests
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: need to rethink this test because ActivateComponent is instant now
|
||||
|
||||
[Test]
|
||||
public void ActivateComponent()
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Tests
|
|||
|
||||
static List<MockMessage> resultMessages;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
public class ReadComponentsTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -32,6 +33,7 @@ namespace Tests
|
|||
}
|
||||
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
public class ReadComponentTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -117,6 +119,7 @@ namespace Tests
|
|||
Assert.Throws<InvalidOperationException>(() => world.Update(0.01f));
|
||||
}
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
[Writes(typeof(MockComponent))]
|
||||
public class UpdateComponentTestEngine : Engine
|
||||
{
|
||||
|
@ -136,6 +139,7 @@ namespace Tests
|
|||
public void UpdateComponent()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
|
||||
worldBuilder.AddEngine(new UpdateComponentTestEngine());
|
||||
worldBuilder.AddEngine(new ReadComponentTestEngine());
|
||||
|
||||
|
@ -156,11 +160,12 @@ namespace Tests
|
|||
Assert.AreEqual("blaze it", resultComponent.myString);
|
||||
}
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
public class UndeclaredUpdateComponentTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
{
|
||||
(var componentID, var component) = this.ReadComponent<MockComponent>();
|
||||
(var componentID, var component) = ReadComponent<MockComponent>();
|
||||
|
||||
component.myInt = 420;
|
||||
component.myString = "blaze it";
|
||||
|
@ -358,6 +363,7 @@ namespace Tests
|
|||
static ValueTuple<Guid, MockComponent> pairA;
|
||||
static ValueTuple<Guid, MockComponent> pairB;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class SameValueComponentReadEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -397,6 +403,7 @@ namespace Tests
|
|||
|
||||
static IEnumerable<ValueTuple<Guid, MockComponent>> emptyComponentReadResult;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class ReadEmptyMockComponentsEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -419,6 +426,7 @@ namespace Tests
|
|||
|
||||
struct DestroyerComponent : IComponent { }
|
||||
|
||||
[Reads(typeof(DestroyerComponent))]
|
||||
class DestroyerEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -434,6 +442,7 @@ namespace Tests
|
|||
|
||||
static IEnumerable<ValueTuple<Guid, MockComponent>> results;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class ReaderEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -471,6 +480,7 @@ namespace Tests
|
|||
Assert.That(results, Does.Not.Contain((componentBID, mockComponent)));
|
||||
}
|
||||
|
||||
[Reads(typeof(DestroyerComponent), typeof(MockComponent))]
|
||||
[Writes(typeof(MockComponent))]
|
||||
class DestroyAndAddComponentEngine : Engine
|
||||
{
|
||||
|
@ -506,6 +516,7 @@ namespace Tests
|
|||
|
||||
static Entity entityFromComponentIDResult;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class GetEntityFromComponentIDEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -536,6 +547,7 @@ namespace Tests
|
|||
|
||||
static MockComponent mockComponentByIDResult;
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class GetComponentByIDEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -565,6 +577,7 @@ namespace Tests
|
|||
|
||||
struct OtherComponent : IComponent { }
|
||||
|
||||
[Reads(typeof(MockComponent))]
|
||||
class GetComponentByIDWithTypeMismatchEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
@ -595,6 +608,7 @@ namespace Tests
|
|||
struct EntityIDComponent : IComponent { public Guid entityID; }
|
||||
static bool hasEntity;
|
||||
|
||||
[Reads(typeof(EntityIDComponent))]
|
||||
class HasEntityTestEngine : Engine
|
||||
{
|
||||
public override void Update(double dt)
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Writes(typeof(TestComponent))]
|
||||
[Activates(typeof(TestComponent))]
|
||||
class TestSpawner : Spawner<SpawnMessageA>
|
||||
{
|
||||
protected override void Spawn(SpawnMessageA message)
|
||||
|
|
Loading…
Reference in New Issue