Merge pull request #3 from thatcosmonaut/component_write_priority

Component write priority
pull/5/head
thatcosmonaut 2019-09-26 12:33:51 -07:00 committed by GitHub
commit 5872e916ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 680 additions and 334 deletions

View File

@ -1,26 +0,0 @@
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(PendingComponentMessage<>).MakeGenericType(activateType));
}
}
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Encompass.Exceptions;
namespace Encompass
{
[AttributeUsage(AttributeTargets.Class)]
public class Updates : Attribute
{
public readonly HashSet<Type> updateTypes = new HashSet<Type>();
public Updates(params Type[] updateTypes)
{
foreach (var updateType in updateTypes)
{
var isComponent = updateType.GetInterfaces().Contains(typeof(IComponent));
if (!isComponent)
{
throw new IllegalUpdateTypeException("{0} must be a Component", updateType.Name);
}
this.updateTypes.Add(typeof(ComponentUpdateMessage<>).MakeGenericType(updateType));
}
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Encompass.Exceptions;
namespace Encompass
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class Writes : Attribute
{
public readonly HashSet<Type> writeTypes = new HashSet<Type>();
public Dictionary<Type, int> priorities = new Dictionary<Type, int>();
public Writes(params Type[] writeTypes)
{
foreach (var writeType in writeTypes)
{
var isComponent = writeType.GetInterfaces().Contains(typeof(IComponent));
if (!isComponent)
{
throw new IllegalWriteTypeException("{0} must be a Component", writeType.Name);
}
this.writeTypes.Add(typeof(ComponentWriteMessage<>).MakeGenericType(writeType));
}
}
public Writes(Type writeType, int priority)
{
var isComponent = writeType.GetInterfaces().Contains(typeof(IComponent));
if (!isComponent)
{
throw new IllegalWriteTypeException("{0} must be a Component", writeType.Name);
}
this.writeTypes.Add(typeof(ComponentWriteMessage<>).MakeGenericType(writeType));
this.priorities.Add(writeType, priority);
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Encompass.Exceptions;
namespace Encompass
{
public class WritesPending : Attribute
{
public readonly HashSet<Type> writePendingTypes = new HashSet<Type>();
public WritesPending(params Type[] writePendingTypes)
{
foreach (var writePendingType in writePendingTypes)
{
var isComponent = writePendingType.GetInterfaces().Contains(typeof(IComponent));
if (!isComponent)
{
throw new IllegalWritePendingTypeException("{0} must be a Component", writePendingType.Name);
}
this.writePendingTypes.Add(typeof(PendingComponentMessage<>).MakeGenericType(writePendingType));
}
}
}
}

View File

@ -19,10 +19,10 @@ namespace Encompass
private readonly Dictionary<Type, HashSet<Guid>> typeToComponentIDs = new Dictionary<Type, HashSet<Guid>>(); private readonly Dictionary<Type, HashSet<Guid>> typeToComponentIDs = new Dictionary<Type, HashSet<Guid>>();
private readonly List<(Entity, Type, Guid, IComponent)> componentAddData = new List<(Entity, Type, Guid, IComponent)>(); private readonly Dictionary<(Entity, Type), (Guid, IComponent)> componentWriteData = new Dictionary<(Entity, Type), (Guid, IComponent)>();
private readonly HashSet<Guid> componentIDsMarkedForAdd = new HashSet<Guid>(); private readonly Dictionary<(Entity, Type), int> componentWritePriorities = new Dictionary<(Entity, Type), int>();
private readonly HashSet<Guid> componentIDsMarkedForWrite = new HashSet<Guid>();
private readonly HashSet<Guid> componentsMarkedForRemoval = new HashSet<Guid>(); private readonly HashSet<Guid> componentsMarkedForRemoval = new HashSet<Guid>();
private readonly Dictionary<Guid, IComponent> pendingUpdates = new Dictionary<Guid, IComponent>();
public ComponentManager(DrawLayerManager drawLayerManager) public ComponentManager(DrawLayerManager drawLayerManager)
{ {
@ -40,26 +40,46 @@ namespace Encompass
return Guid.NewGuid(); return Guid.NewGuid();
} }
internal Guid MarkComponentForAdd<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent internal Guid MarkComponentForWrite<TComponent>(Entity entity, TComponent component, int priority) where TComponent : struct, IComponent
{ {
var id = NextID(); Guid id;
componentAddData.Add((entity, typeof(TComponent), id, component)); if (EntityHasComponentOfType<TComponent>(entity))
componentIDsMarkedForAdd.Add(id); {
id = GetComponentByEntityAndType<TComponent>(entity).Item1;
}
else
{
id = NextID();
}
if (componentWriteData.ContainsKey((entity, typeof(TComponent))))
{
var currentPriority = componentWritePriorities[(entity, typeof(TComponent))];
if (priority < currentPriority)
{
componentWriteData[(entity, typeof(TComponent))] = (id, component);
componentWritePriorities[(entity, typeof(TComponent))] = priority;
componentIDsMarkedForWrite.Add(id);
}
}
else
{
componentWriteData.Add((entity, typeof(TComponent)), (id, component));
componentWritePriorities[(entity, typeof(TComponent))] = priority;
componentIDsMarkedForWrite.Add(id);
}
return id; return id;
} }
internal Guid MarkDrawComponentForAdd<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent internal Guid MarkDrawComponentForWrite<TComponent>(Entity entity, TComponent component, int priority, int layer = 0) where TComponent : struct, IComponent
{ {
var id = MarkComponentForAdd(entity, component); var id = MarkComponentForWrite(entity, component, priority);
drawLayerManager.RegisterComponentWithLayer(id, layer); drawLayerManager.RegisterComponentWithLayer(id, layer);
return id; return id;
} }
internal void AddComponent(Entity entity, Type type, Guid componentID, IComponent component) internal void AddComponent(Entity entity, Type type, Guid componentID, IComponent component)
{
if (!entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { return; }
if (!entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type))
{ {
IDToComponent[componentID] = component; IDToComponent[componentID] = component;
componentIDToEntityID[componentID] = entity.ID; componentIDToEntityID[componentID] = entity.ID;
@ -72,21 +92,34 @@ namespace Encompass
typeToComponentIDs[type].Add(componentID); typeToComponentIDs[type].Add(componentID);
entityIDToComponentIDs[entity.ID].Add(componentID); entityIDToComponentIDs[entity.ID].Add(componentID);
} }
else
internal void UpdateComponent(Guid componentID, IComponent component)
{ {
throw new MultipleComponentOfSameTypeException("Entity {0} cannot have multiple components of type {1}", entity.ID, type.Name); IDToComponent[componentID] = component;
}
} }
internal void AddMarkedComponents() internal void WriteComponents()
{ {
foreach (var (entity, type, componentID, component) in componentAddData) foreach (var keyValuePair in componentWriteData)
{
var (entity, type) = keyValuePair.Key;
var (componentID, component) = keyValuePair.Value;
if (!componentIDsMarkedForWrite.Contains(componentID) || !entityIDToComponentTypeToComponentID.ContainsKey(entity.ID)) { continue; }
if (entityIDToComponentTypeToComponentID[entity.ID].ContainsKey(type))
{
UpdateComponent(componentID, component);
}
else
{ {
AddComponent(entity, type, componentID, component); AddComponent(entity, type, componentID, component);
} }
}
componentAddData.Clear(); componentWriteData.Clear();
componentIDsMarkedForAdd.Clear(); componentIDsMarkedForWrite.Clear();
componentWritePriorities.Clear();
} }
internal IEnumerable<Guid> GetComponentIDsByEntityID(Guid entityID) internal IEnumerable<Guid> GetComponentIDsByEntityID(Guid entityID)
@ -133,38 +166,39 @@ namespace Encompass
} }
internal IComponent GetComponentByID(Guid componentID) internal IComponent GetComponentByID(Guid componentID)
{
if (IDToComponent.ContainsKey(componentID))
{ {
return IDToComponent[componentID]; return IDToComponent[componentID];
} }
else
{
throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
}
}
internal Type GetComponentTypeByID(Guid componentID) internal Type GetComponentTypeByID(Guid componentID)
{
if (componentIDToType.ContainsKey(componentID))
{ {
return componentIDToType[componentID]; return componentIDToType[componentID];
} }
else
{
throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
}
}
internal Guid GetEntityIDByComponentID(Guid componentID) internal Guid GetEntityIDByComponentID(Guid componentID)
{
if (componentIDToEntityID.ContainsKey(componentID))
{ {
return componentIDToEntityID[componentID]; return componentIDToEntityID[componentID];
} }
else
internal void AddUpdateComponentOperation<TComponent>(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent
{ {
if (pendingUpdates.ContainsKey(componentID)) throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
{
throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID);
} }
pendingUpdates.Add(componentID, newComponentValue);
}
internal void PerformComponentUpdates()
{
foreach (var idPair in pendingUpdates)
{
IDToComponent[idPair.Key] = idPair.Value;
}
pendingUpdates.Clear();
} }
internal void MarkAllComponentsOnEntityForRemoval(Guid entityID) internal void MarkAllComponentsOnEntityForRemoval(Guid entityID)
@ -184,11 +218,12 @@ namespace Encompass
{ {
foreach (var componentID in componentsMarkedForRemoval) foreach (var componentID in componentsMarkedForRemoval)
{ {
if (componentIDsMarkedForAdd.Contains(componentID)) if (componentIDsMarkedForWrite.Contains(componentID))
{ {
componentIDsMarkedForAdd.Remove(componentID); componentIDsMarkedForWrite.Remove(componentID);
} }
else
if (IDToComponent.ContainsKey(componentID))
{ {
Remove(componentID); Remove(componentID);
} }

View File

@ -21,10 +21,13 @@ namespace Encompass
private readonly Dictionary<Entity, PooledDictionary<Type, Guid>> entityToTypeToPendingComponentID = new Dictionary<Entity, PooledDictionary<Type, Guid>>(); private readonly Dictionary<Entity, PooledDictionary<Type, Guid>> entityToTypeToPendingComponentID = new Dictionary<Entity, PooledDictionary<Type, Guid>>();
private readonly Dictionary<Entity, PooledDictionary<Type, Guid>> entityToTypeToComponentID = new Dictionary<Entity, PooledDictionary<Type, Guid>>(); private readonly Dictionary<Entity, PooledDictionary<Type, Guid>> entityToTypeToComponentID = new Dictionary<Entity, PooledDictionary<Type, Guid>>();
private readonly Dictionary<Entity, PooledDictionary<Type, int>> entityToTypeToPendingComponentPriority = new Dictionary<Entity, PooledDictionary<Type, int>>();
internal void RegisterEntity(Entity entity) internal void RegisterEntity(Entity entity)
{ {
entityToTypeToComponentID[entity] = new PooledDictionary<Type, Guid>(); entityToTypeToComponentID[entity] = new PooledDictionary<Type, Guid>();
entityToTypeToPendingComponentID[entity] = new PooledDictionary<Type, Guid>(); entityToTypeToPendingComponentID[entity] = new PooledDictionary<Type, Guid>();
entityToTypeToPendingComponentPriority[entity] = new PooledDictionary<Type, int>();
entityToTypeToExistingComponentID[entity] = new PooledDictionary<Type, Guid>(); entityToTypeToExistingComponentID[entity] = new PooledDictionary<Type, Guid>();
} }
@ -36,6 +39,9 @@ namespace Encompass
entityToTypeToPendingComponentID[entity].Dispose(); entityToTypeToPendingComponentID[entity].Dispose();
entityToTypeToPendingComponentID.Remove(entity); entityToTypeToPendingComponentID.Remove(entity);
entityToTypeToPendingComponentPriority[entity].Dispose();
entityToTypeToPendingComponentPriority.Remove(entity);
entityToTypeToExistingComponentID[entity].Dispose(); entityToTypeToExistingComponentID[entity].Dispose();
entityToTypeToExistingComponentID.Remove(entity); entityToTypeToExistingComponentID.Remove(entity);
} }
@ -75,6 +81,11 @@ namespace Encompass
{ {
dictionary.Clear(); dictionary.Clear();
} }
foreach (var dictionary in entityToTypeToPendingComponentPriority.Values)
{
dictionary.Clear();
}
} }
internal void AddExistingComponentMessage<TComponent>(ComponentMessage<TComponent> componentMessage) where TComponent : struct, IComponent internal void AddExistingComponentMessage<TComponent>(ComponentMessage<TComponent> componentMessage) where TComponent : struct, IComponent
@ -107,15 +118,21 @@ namespace Encompass
componentMessageTypeToPendingComponentIDs.Add(typeof(TComponent), new HashSet<Guid>()); componentMessageTypeToPendingComponentIDs.Add(typeof(TComponent), new HashSet<Guid>());
} }
componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID);
if (!entityToTypeToPendingComponentID[pendingComponentMessage.entity].ContainsKey(typeof(TComponent))) if (!entityToTypeToPendingComponentID[pendingComponentMessage.entity].ContainsKey(typeof(TComponent)))
{ {
entityToTypeToPendingComponentID[pendingComponentMessage.entity].Add(typeof(TComponent), pendingComponentMessage.componentID); entityToTypeToPendingComponentID[pendingComponentMessage.entity].Add(typeof(TComponent), pendingComponentMessage.componentID);
entityToTypeToPendingComponentPriority[pendingComponentMessage.entity].Add(typeof(TComponent), pendingComponentMessage.priority);
componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID);
} }
else else
{ {
throw new MultipleComponentOfSameTypeException("Entity {0} cannot have multiple components of type {1}", pendingComponentMessage.entity.ID, typeof(TComponent).Name); if (pendingComponentMessage.priority < entityToTypeToPendingComponentPriority[pendingComponentMessage.entity][typeof(TComponent)])
{
componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Remove(entityToTypeToPendingComponentID[pendingComponentMessage.entity][typeof(TComponent)]);
entityToTypeToPendingComponentID[pendingComponentMessage.entity][typeof(TComponent)] = pendingComponentMessage.componentID;
entityToTypeToPendingComponentPriority[pendingComponentMessage.entity][typeof(TComponent)] = pendingComponentMessage.priority;
componentMessageTypeToPendingComponentIDs[typeof(TComponent)].Add(pendingComponentMessage.componentID);
}
} }
} }
@ -259,18 +276,39 @@ namespace Encompass
} }
internal IComponent GetComponentByID(Guid componentID) internal IComponent GetComponentByID(Guid componentID)
{
if (componentIDToComponent.ContainsKey(componentID))
{ {
return componentIDToComponent[componentID]; return componentIDToComponent[componentID];
} }
else
{
throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
}
}
internal Type GetComponentTypeByID(Guid componentID) internal Type GetComponentTypeByID(Guid componentID)
{
if (componentIDToType.ContainsKey(componentID))
{ {
return componentIDToType[componentID]; return componentIDToType[componentID];
} }
else
{
throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
}
}
internal Guid GetEntityIDByComponentID(Guid componentID) internal Guid GetEntityIDByComponentID(Guid componentID)
{
if (componentIDToEntityID.ContainsKey(componentID))
{ {
return componentIDToEntityID[componentID]; return componentIDToEntityID[componentID];
} }
else
{
throw new ComponentNotFoundException("Component with ID {0} does not exist.", componentID);
}
}
} }
} }

View File

@ -10,6 +10,7 @@ namespace Encompass
{ {
internal readonly HashSet<Type> sendTypes = new HashSet<Type>(); internal readonly HashSet<Type> sendTypes = new HashSet<Type>();
internal readonly HashSet<Type> receiveTypes = new HashSet<Type>(); internal readonly HashSet<Type> receiveTypes = new HashSet<Type>();
internal readonly Dictionary<Type, int> writePriorities = new Dictionary<Type, int>();
private EntityManager entityManager; private EntityManager entityManager;
private MessageManager messageManager; private MessageManager messageManager;
@ -24,16 +25,17 @@ namespace Encompass
sendTypes = sendsAttribute.sendTypes; sendTypes = sendsAttribute.sendTypes;
} }
var activatesAttribute = GetType().GetCustomAttribute<Activates>(false); var activatesAttribute = GetType().GetCustomAttribute<WritesPending>(false);
if (activatesAttribute != null) if (activatesAttribute != null)
{ {
sendTypes.UnionWith(activatesAttribute.activateTypes); sendTypes.UnionWith(activatesAttribute.writePendingTypes);
} }
var updatesAttribute = GetType().GetCustomAttribute<Updates>(false); var writesAttributes = GetType().GetCustomAttributes<Writes>(false);
if (updatesAttribute != null) foreach (var writesAttribute in writesAttributes)
{ {
sendTypes.UnionWith(updatesAttribute.updateTypes); sendTypes.UnionWith(writesAttribute.writeTypes);
writePriorities = new Dictionary<Type, int>[2] { writePriorities, writesAttribute.priorities }.SelectMany(dict => dict).ToDictionary(pair => pair.Key, pair => pair.Value);
} }
var receivesAttribute = GetType().GetCustomAttribute<Receives>(false); var receivesAttribute = GetType().GetCustomAttribute<Receives>(false);
@ -92,18 +94,34 @@ namespace Encompass
return entityManager.GetEntity(entityID); return entityManager.GetEntity(entityID);
} }
protected Guid GetEntityIDByComponentID(Guid componentID) protected Guid GetEntityIDByComponentID<TComponent>(Guid componentID) where TComponent : struct, IComponent
{ {
var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>));
var existingRead = receiveTypes.Contains(typeof(ComponentMessage<TComponent>));
if (!pendingRead && !existingRead)
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
return componentMessageManager.GetEntityIDByComponentID(componentID); return componentMessageManager.GetEntityIDByComponentID(componentID);
} }
protected Entity GetEntityByComponentID(Guid componentID) protected Entity GetEntityByComponentID<TComponent>(Guid componentID) where TComponent : struct, IComponent
{ {
return GetEntity(GetEntityIDByComponentID(componentID)); return GetEntity(GetEntityIDByComponentID<TComponent>(componentID));
} }
protected TComponent GetComponentByID<TComponent>(Guid componentID) where TComponent : struct, IComponent protected TComponent GetComponentByID<TComponent>(Guid componentID) where TComponent : struct, IComponent
{ {
var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>));
var existingRead = receiveTypes.Contains(typeof(ComponentMessage<TComponent>));
if (!pendingRead && !existingRead)
{
throw new IllegalReadException("Engine {0} tried to read undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
if (componentMessageManager.GetComponentTypeByID(componentID) != typeof(TComponent)) if (componentMessageManager.GetComponentTypeByID(componentID) != typeof(TComponent))
{ {
throw new ComponentTypeMismatchException("Expected Component to be of type {0} but was actually of type {1}", typeof(TComponent).Name, componentMessageManager.GetComponentTypeByID(componentID).Name); throw new ComponentTypeMismatchException("Expected Component to be of type {0} but was actually of type {1}", typeof(TComponent).Name, componentMessageManager.GetComponentTypeByID(componentID).Name);
@ -124,40 +142,6 @@ namespace Encompass
return GetEntity(componentManager.GetEntityIDByComponentID(componentID)); return GetEntity(componentManager.GetEntityIDByComponentID(componentID));
} }
protected Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{
var componentID = componentManager.MarkComponentForAdd(entity, component);
if (sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
SendMessage(newComponentMessage);
SendPendingComponentMessage(newComponentMessage);
}
return componentID;
}
protected Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent
{
var componentID = componentManager.MarkDrawComponentForAdd(entity, component, layer);
if (sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
SendMessage(newComponentMessage);
SendPendingComponentMessage(newComponentMessage);
}
return componentID;
}
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
{ {
var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>)); var pendingRead = receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>));
@ -280,22 +264,57 @@ namespace Encompass
} }
} }
internal void UpdateComponentInWorld<TComponent>(Guid componentID, TComponent newComponent) where TComponent : struct, IComponent protected Guid SetComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{ {
componentManager.AddUpdateComponentOperation(componentID, newComponent); var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0;
var componentID = componentManager.MarkComponentForWrite(entity, component, priority);
if (!sendTypes.Contains(typeof(ComponentWriteMessage<TComponent>)))
{
throw new IllegalWriteException("Engine {0} tried to update undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
} }
protected void UpdateComponent<TComponent>(Guid componentID, TComponent newComponentValue) where TComponent : struct, IComponent if (sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{ {
if (!sendTypes.Contains(typeof(ComponentUpdateMessage<TComponent>))) PendingComponentMessage<TComponent> newComponentMessage;
{ newComponentMessage.entity = entity;
throw new IllegalUpdateException("Engine {0} tried to update undeclared Component {1}", GetType().Name, typeof(TComponent).Name); newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
newComponentMessage.priority = priority;
SendPendingComponentMessage(newComponentMessage);
} }
ComponentUpdateMessage<TComponent> componentUpdateMessage; return componentID;
componentUpdateMessage.componentID = componentID; }
componentUpdateMessage.component = newComponentValue;
SendMessage(componentUpdateMessage); protected Guid SetComponent<TComponent>(Guid componentID, TComponent component) where TComponent : struct, IComponent
{
return SetComponent(GetEntityByComponentID<TComponent>(componentID), component);
}
protected Guid SetDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent
{
var priority = writePriorities.ContainsKey(typeof(TComponent)) ? writePriorities[typeof(TComponent)] : 0;
var componentID = componentManager.MarkDrawComponentForWrite(entity, component, priority, layer);
if (!sendTypes.Contains(typeof(ComponentWriteMessage<TComponent>)))
{
throw new IllegalWriteException("Engine {0} tried to write undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
if (sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
newComponentMessage.priority = priority;
SendPendingComponentMessage(newComponentMessage);
}
return componentID;
} }
protected void SendMessage<TMessage>(TMessage message) where TMessage : struct, IMessage protected void SendMessage<TMessage>(TMessage message) where TMessage : struct, IMessage

View File

@ -1,18 +0,0 @@
namespace Encompass.Engines
{
internal class ComponentUpdater<TComponent> : Engine where TComponent : struct, IComponent
{
public ComponentUpdater() : base()
{
receiveTypes.Add(typeof(ComponentUpdateMessage<TComponent>));
}
public override void Update(double dt)
{
foreach (var componentUpdateMessage in ReadMessages<ComponentUpdateMessage<TComponent>>())
{
UpdateComponentInWorld(componentUpdateMessage.componentID, componentUpdateMessage.component);
}
}
}
}

View File

@ -1,5 +1,7 @@
using System.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Encompass.Exceptions;
namespace Encompass namespace Encompass
{ {
@ -34,9 +36,16 @@ namespace Encompass
} }
public Entity GetEntity(Guid id) public Entity GetEntity(Guid id)
{
if (IDToEntity.ContainsKey(id))
{ {
return IDToEntity[id]; return IDToEntity[id];
} }
else
{
throw new EntityNotFoundException("Entity with ID {0} does not exist.", id);
}
}
public void MarkForDestroy(Guid entityID) public void MarkForDestroy(Guid entityID)
{ {

View File

@ -2,9 +2,9 @@ using System;
namespace Encompass.Exceptions namespace Encompass.Exceptions
{ {
public class IllegalUpdateTypeException : Exception public class ComponentNotFoundException : Exception
{ {
public IllegalUpdateTypeException( public ComponentNotFoundException(
string format, string format,
params object[] args params object[] args
) : base(string.Format(format, args)) { } ) : base(string.Format(format, args)) { }

View File

@ -2,9 +2,9 @@ using System;
namespace Encompass.Exceptions namespace Encompass.Exceptions
{ {
public class EngineUpdateConflictException : Exception public class EngineWriteConflictException : Exception
{ {
public EngineUpdateConflictException( public EngineWriteConflictException(
string format, string format,
params object[] args params object[] args
) : base(string.Format(format, args)) { } ) : base(string.Format(format, args)) { }

View File

@ -2,9 +2,9 @@ using System;
namespace Encompass.Exceptions namespace Encompass.Exceptions
{ {
public class IllegalActivateException : Exception public class EntityNotFoundException : Exception
{ {
public IllegalActivateException( public EntityNotFoundException(
string format, string format,
params object[] args params object[] args
) : base(string.Format(format, args)) { } ) : base(string.Format(format, args)) { }

View File

@ -2,9 +2,9 @@ using System;
namespace Encompass.Exceptions namespace Encompass.Exceptions
{ {
public class IllegalUpdateException : Exception public class IllegalWriteException : Exception
{ {
public IllegalUpdateException( public IllegalWriteException(
string format, string format,
params object[] args params object[] args
) : base(string.Format(format, args)) { } ) : base(string.Format(format, args)) { }

View File

@ -2,9 +2,9 @@ using System;
namespace Encompass.Exceptions namespace Encompass.Exceptions
{ {
public class IllegalActivateTypeException : Exception public class IllegalWritePendingException : Exception
{ {
public IllegalActivateTypeException( public IllegalWritePendingException(
string format, string format,
params object[] args params object[] args
) : base(string.Format(format, args)) { } ) : base(string.Format(format, args)) { }

View File

@ -0,0 +1,12 @@
using System;
namespace Encompass.Exceptions
{
public class IllegalWritePendingTypeException : Exception
{
public IllegalWritePendingTypeException(
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

@ -2,7 +2,7 @@ using System;
namespace Encompass namespace Encompass
{ {
internal struct ComponentUpdateMessage<TComponent> : IMessage where TComponent : struct, IComponent internal struct ComponentWriteMessage<TComponent> : IMessage where TComponent : struct, IComponent
{ {
public Guid componentID; public Guid componentID;
public TComponent component; public TComponent component;

View File

@ -7,5 +7,6 @@ namespace Encompass
public Entity entity; public Entity entity;
public Guid componentID; public Guid componentID;
public TComponent component; public TComponent component;
public int priority;
} }
} }

View File

@ -41,9 +41,8 @@ namespace Encompass
componentMessageManager.ClearMessages(); componentMessageManager.ClearMessages();
entityManager.DestroyMarkedEntities(); entityManager.DestroyMarkedEntities();
componentManager.PerformComponentUpdates();
componentManager.RemoveMarkedComponents(); componentManager.RemoveMarkedComponents();
componentManager.AddMarkedComponents(); componentManager.WriteComponents();
} }
public void Draw() public void Draw()

View File

@ -50,14 +50,14 @@ namespace Encompass
messageManager.AddMessageDelayed(message, time); messageManager.AddMessageDelayed(message, time);
} }
public Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent public Guid SetComponent<TComponent>(Entity entity, TComponent component, int priority = 0) where TComponent : struct, IComponent
{ {
return componentManager.MarkComponentForAdd(entity, component); return componentManager.MarkComponentForWrite(entity, component, priority);
} }
public Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent public Guid SetDrawComponent<TComponent>(Entity entity, TComponent component, int priority = 0, int layer = 0) where TComponent : struct, IComponent
{ {
return componentManager.MarkDrawComponentForAdd(entity, component, layer); return componentManager.MarkDrawComponentForWrite(entity, component, priority, layer);
} }
internal void RegisterComponent(Type componentType) internal void RegisterComponent(Type componentType)
@ -66,11 +66,6 @@ namespace Encompass
AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType))); AddEngine((Engine)Activator.CreateInstance(typeof(ComponentMessageEmitter<>).MakeGenericType(componentType)));
} }
internal void RegisterNewComponentUpdater(Type componentType)
{
AddEngine((Engine)Activator.CreateInstance(typeof(ComponentUpdater<>).MakeGenericType(componentType)));
}
public Engine AddEngine<TEngine>(TEngine engine) where TEngine : Engine public Engine AddEngine<TEngine>(TEngine engine) where TEngine : Engine
{ {
engine.AssignEntityManager(entityManager); engine.AssignEntityManager(entityManager);
@ -128,19 +123,6 @@ namespace Encompass
typeToReaders[receiveType].Add(engine); typeToReaders[receiveType].Add(engine);
} }
foreach (var sendType in engine.sendTypes)
{
if (sendType.IsGenericType)
{
var genericTypeDefinition = sendType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(ComponentUpdateMessage<>))
{
var componentType = sendType.GetGenericArguments().Single();
RegisterNewComponentUpdater(componentType);
}
}
}
return engine; return engine;
} }
@ -201,45 +183,92 @@ namespace Encompass
throw new EngineCycleException(errorString); throw new EngineCycleException(errorString);
} }
var mutatedComponentTypes = new HashSet<Type>(); var writtenComponentTypesWithoutPriority = new HashSet<Type>();
var duplicateMutations = new List<Type>(); var writtenComponentTypesWithPriority = new HashSet<Type>();
var updateMessageToEngines = new Dictionary<Type, List<Engine>>(); var duplicateWritesWithoutPriority = new List<Type>();
var duplicateWritesWithSamePriority = new List<Type>();
var writePriorities = new Dictionary<Type, HashSet<int>>();
var writeMessageToEngines = new Dictionary<Type, List<Engine>>();
foreach (var engine in engines) foreach (var engine in engines)
{ {
var updateTypes = engine.sendTypes.Where((type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ComponentUpdateMessage<>)); var writeTypes = engine.sendTypes.Where((type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ComponentWriteMessage<>));
foreach (var updateType in updateTypes) foreach (var writeType in writeTypes)
{ {
if (mutatedComponentTypes.Contains(updateType)) var componentType = writeType.GetGenericArguments()[0];
if (engine.writePriorities.ContainsKey(componentType))
{ {
duplicateMutations.Add(updateType); var priority = engine.writePriorities[componentType];
writtenComponentTypesWithPriority.Add(componentType);
if (!writePriorities.ContainsKey(componentType))
{
writePriorities[componentType] = new HashSet<int>();
}
if (writePriorities[componentType].Contains(priority))
{
duplicateWritesWithSamePriority.Add(componentType);
}
else if (writtenComponentTypesWithoutPriority.Contains(componentType))
{
duplicateWritesWithoutPriority.Add(componentType);
} }
else else
{ {
mutatedComponentTypes.Add(updateType); writePriorities[componentType].Add(priority);
} }
}
if (!updateMessageToEngines.ContainsKey(updateType)) else
{ {
updateMessageToEngines[updateType] = new List<Engine>(); if (writtenComponentTypesWithoutPriority.Contains(componentType) || writtenComponentTypesWithPriority.Contains(componentType))
}
updateMessageToEngines[updateType].Add(engine);
}
}
if (duplicateMutations.Count > 0)
{ {
var errorString = "Multiple Engines update the same Component: "; duplicateWritesWithoutPriority.Add(componentType);
foreach (var componentType in duplicateMutations) }
else
{
writtenComponentTypesWithoutPriority.Add(componentType);
}
}
if (!writeMessageToEngines.ContainsKey(componentType))
{
writeMessageToEngines[componentType] = new List<Engine>();
}
writeMessageToEngines[componentType].Add(engine);
}
}
if (duplicateWritesWithoutPriority.Count > 0)
{
var errorString = "Multiple Engines write the same Component without declaring priority: ";
foreach (var componentType in duplicateWritesWithoutPriority)
{ {
errorString += "\n" + errorString += "\n" +
componentType.Name + " updated by: " + componentType.Name + " written by: " +
string.Join(", ", updateMessageToEngines[componentType].Select((engine) => engine.GetType().Name)); string.Join(", ", writeMessageToEngines[componentType].Select((engine) => engine.GetType().Name));
}
errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations.";
throw new EngineWriteConflictException(errorString);
} }
throw new EngineUpdateConflictException(errorString); if (duplicateWritesWithSamePriority.Count > 0)
{
var errorString = "Multiple Engines write the same Component with the same priority: ";
foreach (var componentType in duplicateWritesWithSamePriority)
{
errorString += "\n" +
componentType.Name + " written by: " +
string.Join(", ", writeMessageToEngines[componentType].Select(engine => engine.GetType().Name));
}
errorString += "\nTo resolve the conflict, add priority arguments to the Writes declarations.";
throw new EngineWriteConflictException(errorString);
} }
var engineOrder = new List<Engine>(); var engineOrder = new List<Engine>();
@ -257,9 +286,8 @@ namespace Encompass
renderManager renderManager
); );
componentManager.PerformComponentUpdates();
componentManager.RemoveMarkedComponents(); componentManager.RemoveMarkedComponents();
componentManager.AddMarkedComponents(); componentManager.WriteComponents();
return world; return world;
} }

View File

@ -3,7 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Encompass</RootNamespace> <RootNamespace>Encompass</RootNamespace>
<PackageId>EncompassECS.Framework</PackageId> <PackageId>EncompassECS.Framework</PackageId>
<Version>0.13.0-rc2</Version> <Version>0.13.0-rc7</Version>
<Authors>Evan Hemsley</Authors> <Authors>Evan Hemsley</Authors>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Company>Moonside Games</Company> <Company>Moonside Games</Company>

View File

@ -1,3 +1,4 @@
using System.ComponentModel;
using NUnit.Framework; using NUnit.Framework;
using FluentAssertions; using FluentAssertions;
@ -11,7 +12,7 @@ namespace Tests
{ {
public class ComponentTests public class ComponentTests
{ {
struct MockComponent : IComponent struct MockComponent : Encompass.IComponent
{ {
public string myString; public string myString;
public int myInt; public int myInt;
@ -22,7 +23,6 @@ namespace Tests
public Entity entity; public Entity entity;
} }
static IEnumerable<(Guid, MockComponent)> gottenMockComponentIDPairs = Enumerable.Empty<(Guid, MockComponent)>();
static (Guid, MockComponent) gottenMockComponentIDPair; static (Guid, MockComponent) gottenMockComponentIDPair;
[Receives(typeof(EntityMessage))] [Receives(typeof(EntityMessage))]
@ -70,7 +70,7 @@ namespace Tests
mockComponent.myInt = 3; mockComponent.myInt = 3;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
AddComponentTestMessage addComponentTestMessage; AddComponentTestMessage addComponentTestMessage;
addComponentTestMessage.entity = entity; addComponentTestMessage.entity = entity;
@ -83,58 +83,76 @@ namespace Tests
} }
[Test] [Test]
public void AddMultipleComponentOfSameTypeToEntity() public void SetMultipleComponentOfSameTypeOnEntity()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new ReadMockComponentEngine());
MockComponent mockComponent;
mockComponent.myInt = 3;
mockComponent.myString = "hello";
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, new MockComponent { myInt = 20, myString = "what" }, 2);
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, new MockComponent { myInt = 50, myString = "hi" }, 0);
worldBuilder.SetComponent(entity, new MockComponent { myInt = 40, myString = "wassup" }, 1);
Assert.Throws<MultipleComponentOfSameTypeException>(() => worldBuilder.Build()); var world = worldBuilder.Build();
world.Update(0.01);
Assert.That(gottenMockComponentIDPair.Item2.myInt, Is.EqualTo(50));
Assert.That(gottenMockComponentIDPair.Item2.myString, Is.EqualTo("hi"));
} }
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent))]
class MultipleAddEngine : Engine [WritesPending(typeof(MockComponent))]
[Writes(typeof(MockComponent))]
class OverwriteEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>()) foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>())
{ {
var entity = GetEntityByComponentID(mockComponentID); var entity = GetEntityByComponentID<MockComponent>(mockComponentID);
AddComponent(entity, new MockComponent()); SetComponent(entity, new MockComponent { myInt = 420 });
} }
} }
} }
[ReadsPending(typeof(MockComponent))]
[Reads(typeof(MockComponent))]
class ReadMockComponentEngine : Engine
{
public override void Update(double dt)
{
gottenMockComponentIDPair = ReadComponent<MockComponent>();
}
}
[Test] [Test]
public void EngineAddMultipleComponentOfSameTypeToEntity() public void EngineOverwriteComponent()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new MultipleAddEngine()); worldBuilder.AddEngine(new OverwriteEngine());
worldBuilder.AddEngine(new ReadMockComponentEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent {});
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01);
Assert.Throws<MultipleComponentOfSameTypeException>(() => world.Update(0.01)); Assert.That(gottenMockComponentIDPair.Item2.myInt, Is.EqualTo(420));
} }
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent))]
[Writes(typeof(MockComponent))]
class AddAndRemoveComponentEngine : Engine class AddAndRemoveComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>()) foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>())
{ {
var entity = GetEntityByComponentID(mockComponentID); var entity = GetEntityByComponentID<MockComponent>(mockComponentID);
AddComponent(entity, mockComponent); SetComponent(entity, mockComponent);
RemoveComponent(mockComponentID); RemoveComponent(mockComponentID);
} }
} }
@ -147,7 +165,7 @@ namespace Tests
worldBuilder.AddEngine(new AddAndRemoveComponentEngine()); worldBuilder.AddEngine(new AddAndRemoveComponentEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent());
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -183,15 +201,16 @@ namespace Tests
} }
} }
[Activates(typeof(MockComponent))] [WritesPending(typeof(MockComponent))]
[Receives(typeof(AddMockComponentMessage))] [Receives(typeof(AddMockComponentMessage))]
[Writes(typeof(MockComponent))]
class AddMockComponentEngine : Engine class AddMockComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var message in ReadMessages<AddMockComponentMessage>()) foreach (var message in ReadMessages<AddMockComponentMessage>())
{ {
AddComponent(message.entity, message.mockComponent); SetComponent(message.entity, message.mockComponent);
} }
} }
} }
@ -239,7 +258,7 @@ namespace Tests
mockComponent.myInt = 3; mockComponent.myInt = 3;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
var componentID = worldBuilder.AddComponent<MockComponent>(entity, mockComponent); var componentID = worldBuilder.SetComponent<MockComponent>(entity, mockComponent);
EntityMessage entityMessage; EntityMessage entityMessage;
entityMessage.entity = entity; entityMessage.entity = entity;
@ -282,7 +301,7 @@ namespace Tests
mockComponent.myInt = 3; mockComponent.myInt = 3;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
HasComponentTestMessage hasComponentTestMessage; HasComponentTestMessage hasComponentTestMessage;
hasComponentTestMessage.entity = entity; hasComponentTestMessage.entity = entity;
@ -368,7 +387,7 @@ namespace Tests
mockComponent.myInt = 3; mockComponent.myInt = 3;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
var componentID = worldBuilder.AddComponent(entity, mockComponent); var componentID = worldBuilder.SetComponent(entity, mockComponent);
RemoveComponentTestMessage removeComponentMessage; RemoveComponentTestMessage removeComponentMessage;
removeComponentMessage.entity = entity; removeComponentMessage.entity = entity;

View File

@ -59,8 +59,8 @@ namespace Tests
mockComponentB.myInt = 1; mockComponentB.myInt = 1;
mockComponentB.myString = "howdy"; mockComponentB.myString = "howdy";
var componentAID = worldBuilder.AddComponent(entity, mockComponent); var componentAID = worldBuilder.SetComponent(entity, mockComponent);
var componentBID = worldBuilder.AddComponent(entityB, mockComponentB); var componentBID = worldBuilder.SetComponent(entityB, mockComponentB);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -83,7 +83,7 @@ namespace Tests
mockComponent.myInt = 0; mockComponent.myInt = 0;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -109,8 +109,8 @@ namespace Tests
mockComponentB.myInt = 1; mockComponentB.myInt = 1;
mockComponentB.myString = "howdy"; mockComponentB.myString = "howdy";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
worldBuilder.AddComponent(entityB, mockComponentB); worldBuilder.SetComponent(entityB, mockComponentB);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -120,7 +120,7 @@ namespace Tests
} }
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent))]
[Updates(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)
@ -129,7 +129,7 @@ namespace Tests
component.myInt = 420; component.myInt = 420;
component.myString = "blaze it"; component.myString = "blaze it";
UpdateComponent(componentID, component); SetComponent(GetEntityByComponentID<MockComponent>(componentID), component);
} }
} }
@ -149,7 +149,7 @@ namespace Tests
mockComponent.myInt = 0; mockComponent.myInt = 0;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -169,7 +169,7 @@ namespace Tests
component.myInt = 420; component.myInt = 420;
component.myString = "blaze it"; component.myString = "blaze it";
UpdateComponent(componentID, component); SetComponent(componentID, component);
component = ReadComponent<MockComponent>().Item2; component = ReadComponent<MockComponent>().Item2;
} }
@ -187,11 +187,11 @@ namespace Tests
mockComponent.myInt = 0; mockComponent.myInt = 0;
mockComponent.myString = "hello"; mockComponent.myString = "hello";
worldBuilder.AddComponent(entity, mockComponent); worldBuilder.SetComponent(entity, mockComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
var ex = Assert.Throws<IllegalUpdateException>(() => world.Update(0.01f)); var ex = Assert.Throws<IllegalWriteException>(() => world.Update(0.01f));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent")); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent"));
} }
@ -353,7 +353,7 @@ namespace Tests
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent());
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -391,10 +391,10 @@ namespace Tests
componentB.myString = "hello"; componentB.myString = "hello";
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, componentA); worldBuilder.SetComponent(entity, componentA);
var entityB = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entityB, componentB); worldBuilder.SetComponent(entityB, componentB);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01f); world.Update(0.01f);
@ -436,7 +436,7 @@ namespace Tests
foreach (var componentPair in ReadComponents<DestroyerComponent>()) foreach (var componentPair in ReadComponents<DestroyerComponent>())
{ {
var componentID = componentPair.Item1; var componentID = componentPair.Item1;
var entityID = GetEntityIDByComponentID(componentID); var entityID = GetEntityIDByComponentID<DestroyerComponent>(componentID);
Destroy(entityID); Destroy(entityID);
} }
} }
@ -469,13 +469,13 @@ namespace Tests
mockComponent.myInt = 2; mockComponent.myInt = 2;
mockComponent.myString = "blah"; mockComponent.myString = "blah";
worldBuilder.AddComponent(entity, destroyerComponent); worldBuilder.SetComponent(entity, destroyerComponent);
var componentID = worldBuilder.AddComponent(entity, mockComponent); var componentID = worldBuilder.SetComponent(entity, mockComponent);
worldBuilder.AddComponent(entityB, destroyerComponent); worldBuilder.SetComponent(entityB, destroyerComponent);
var componentBID = worldBuilder.AddComponent(entityB, mockComponent); var componentBID = worldBuilder.SetComponent(entityB, mockComponent);
var componentCID = worldBuilder.AddComponent(entityC, mockComponent); var componentCID = worldBuilder.SetComponent(entityC, mockComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -495,7 +495,7 @@ namespace Tests
foreach (var componentPair in ReadComponents<DestroyerComponent>()) foreach (var componentPair in ReadComponents<DestroyerComponent>())
{ {
var componentID = componentPair.Item1; var componentID = componentPair.Item1;
var entity = GetEntityByComponentID(componentID); var entity = GetEntityByComponentID<MockComponent>(componentID);
var (id, _) = GetComponent<MockComponent>(entity); var (id, _) = GetComponent<MockComponent>(entity);
RemoveComponent(id); RemoveComponent(id);
Destroy(entity.ID); Destroy(entity.ID);
@ -512,8 +512,8 @@ namespace Tests
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new DestroyerComponent()); worldBuilder.SetComponent(entity, new DestroyerComponent());
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent());
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -528,7 +528,7 @@ namespace Tests
public override void Update(double dt) public override void Update(double dt)
{ {
var componentID = ReadComponent<MockComponent>().Item1; var componentID = ReadComponent<MockComponent>().Item1;
entityFromComponentIDResult = GetEntityByComponentID(componentID); entityFromComponentIDResult = GetEntityByComponentID<MockComponent>(componentID);
} }
} }
@ -543,7 +543,7 @@ namespace Tests
component.myString = "howdy"; component.myString = "howdy";
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, component); worldBuilder.SetComponent(entity, component);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01); world.Update(0.01);
@ -551,17 +551,18 @@ namespace Tests
Assert.That(entity, Is.EqualTo(entityFromComponentIDResult)); Assert.That(entity, Is.EqualTo(entityFromComponentIDResult));
} }
[Activates(typeof(MockComponent))]
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent))]
[WritesPending(typeof(MockComponent))]
[Writes(typeof(MockComponent))]
class AddAndRemoveMockComponentEngine : Engine class AddAndRemoveMockComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>()) foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>())
{ {
var entity = GetEntityByComponentID(mockComponentID); var entity = GetEntityByComponentID<MockComponent>(mockComponentID);
RemoveComponent(mockComponentID); RemoveComponent(mockComponentID);
AddComponent(entity, new MockComponent()); SetComponent(entity, new MockComponent());
} }
} }
} }
@ -573,7 +574,7 @@ namespace Tests
{ {
foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>()) foreach (var (mockComponentID, mockComponent) in ReadComponents<MockComponent>())
{ {
entityFromComponentIDResult = GetEntityByComponentID(mockComponentID); entityFromComponentIDResult = GetEntityByComponentID<MockComponent>(mockComponentID);
} }
} }
} }
@ -586,7 +587,7 @@ namespace Tests
worldBuilder.AddEngine(new GetEntityFromPendingComponentIDEngine()); worldBuilder.AddEngine(new GetEntityFromPendingComponentIDEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent());
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -613,7 +614,7 @@ namespace Tests
worldBuilder.AddEngine(new GetPendingComponentFromIDEngine()); worldBuilder.AddEngine(new GetPendingComponentFromIDEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent()); worldBuilder.SetComponent(entity, new MockComponent());
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -642,7 +643,7 @@ namespace Tests
component.myString = "howdy"; component.myString = "howdy";
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, component); worldBuilder.SetComponent(entity, component);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01f); world.Update(0.01f);
@ -652,7 +653,7 @@ namespace Tests
struct OtherComponent : IComponent { } struct OtherComponent : IComponent { }
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent), typeof(OtherComponent))]
class GetComponentByIDWithTypeMismatchEngine : Engine class GetComponentByIDWithTypeMismatchEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -673,7 +674,7 @@ namespace Tests
component.myString = "howdy"; component.myString = "howdy";
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, component); worldBuilder.SetComponent(entity, component);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -708,7 +709,7 @@ namespace Tests
EntityIDComponent entityIDComponent; EntityIDComponent entityIDComponent;
entityIDComponent.entityID = entityTwo.ID; entityIDComponent.entityID = entityTwo.ID;
worldBuilder.AddComponent(entity, entityIDComponent); worldBuilder.SetComponent(entity, entityIDComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -727,25 +728,26 @@ namespace Tests
public MockComponent mockComponent; public MockComponent mockComponent;
} }
[Reads(typeof(MockComponent))]
[Receives(typeof(MockComponentUpdateMessage))] [Receives(typeof(MockComponentUpdateMessage))]
[Updates(typeof(MockComponent))] [Writes(typeof(MockComponent))]
class RepeatUpdateEngine : Engine class UpdateByComponentIDEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
foreach (var mockComponentUpdateMessage in ReadMessages<MockComponentUpdateMessage>()) foreach (var mockComponentUpdateMessage in ReadMessages<MockComponentUpdateMessage>())
{ {
UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); SetComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent);
UpdateComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent); SetComponent(mockComponentUpdateMessage.componentID, mockComponentUpdateMessage.mockComponent);
} }
} }
} }
[Test] [Test]
public void EngineUpdatesComponentMultipleTimes() public void EngineUpdateByComponentID()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new RepeatUpdateEngine()); worldBuilder.AddEngine(new UpdateByComponentIDEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
@ -753,7 +755,7 @@ namespace Tests
mockComponent.myInt = 1; mockComponent.myInt = 1;
mockComponent.myString = "5"; mockComponent.myString = "5";
var mockComponentID = worldBuilder.AddComponent(entity, mockComponent); var mockComponentID = worldBuilder.SetComponent(entity, mockComponent);
MockComponentUpdateMessage mockComponentUpdateMessage; MockComponentUpdateMessage mockComponentUpdateMessage;
mockComponentUpdateMessage.componentID = mockComponentID; mockComponentUpdateMessage.componentID = mockComponentID;
@ -761,7 +763,7 @@ namespace Tests
worldBuilder.SendMessage(mockComponentUpdateMessage); worldBuilder.SendMessage(mockComponentUpdateMessage);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
Assert.Throws<RepeatUpdateComponentException>(() => world.Update(0.01)); Assert.DoesNotThrow(() => world.Update(0.01));
} }
[Reads(typeof(MockComponent))] [Reads(typeof(MockComponent))]
@ -787,7 +789,7 @@ namespace Tests
worldBuilder.AddEngine(new MessageReadEngine()); worldBuilder.AddEngine(new MessageReadEngine());
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, new MockComponent {}); worldBuilder.SetComponent(entity, new MockComponent {});
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -806,7 +808,8 @@ namespace Tests
} }
[Receives(typeof(MockMessage))] [Receives(typeof(MockMessage))]
[Activates(typeof(MockComponent))] [WritesPending(typeof(MockComponent))]
[Writes(typeof(MockComponent))]
class ActivateComponentEngine : Engine class ActivateComponentEngine : Engine
{ {
public override void Update(double dt) public override void Update(double dt)
@ -814,7 +817,7 @@ namespace Tests
foreach (var message in ReadMessages<MockMessage>()) foreach (var message in ReadMessages<MockMessage>())
{ {
var entity = CreateEntity(); var entity = CreateEntity();
AddComponent(entity, new MockComponent {}); SetComponent(entity, new MockComponent {});
} }
} }
} }
@ -844,5 +847,49 @@ namespace Tests
Assert.DoesNotThrow(() => world.Update(0.01)); Assert.DoesNotThrow(() => world.Update(0.01));
} }
struct DestroyComponentMessage : IMessage { public Entity entity; }
[Reads(typeof(MockComponent))]
[Writes(typeof(MockComponent))]
class AddComponentEngine : Engine
{
public override void Update(double dt)
{
foreach (var (componentID, component) in ReadComponents<MockComponent>())
{
SetComponent(componentID, new MockComponent {});
}
}
}
[Receives(typeof(DestroyComponentMessage))]
class DestroyEntityEngine : Engine
{
public override void Update(double dt)
{
foreach (var message in ReadMessages<DestroyComponentMessage>())
{
Destroy(message.entity.ID);
}
}
}
[Test]
public void EngineSetComponentAndDestroyEntitySameFrame()
{
var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AddComponentEngine());
worldBuilder.AddEngine(new DestroyEntityEngine());
var entity = worldBuilder.CreateEntity();
worldBuilder.SetComponent(entity, new MockComponent {});
worldBuilder.SendMessage(new DestroyComponentMessage { entity = entity });
var world = worldBuilder.Build();
world.Update(0.01);
Assert.DoesNotThrow(() => world.Update(0.01));
}
} }
} }

View File

@ -32,7 +32,7 @@ namespace Tests
AComponent aComponent; AComponent aComponent;
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
var componentID = worldBuilder.AddComponent(entity, aComponent); var componentID = worldBuilder.SetComponent(entity, aComponent);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -52,10 +52,10 @@ namespace Tests
AComponent aComponentTwo; AComponent aComponentTwo;
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
var componentID = worldBuilder.AddComponent(entity, aComponent); var componentID = worldBuilder.SetComponent(entity, aComponent);
var entityB = worldBuilder.CreateEntity(); var entityB = worldBuilder.CreateEntity();
var componentTwoID = worldBuilder.AddComponent(entityB, aComponentTwo); var componentTwoID = worldBuilder.SetComponent(entityB, aComponentTwo);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01f); world.Update(0.01f);

View File

@ -52,9 +52,9 @@ namespace Tests
TestDrawComponent testDrawComponent; TestDrawComponent testDrawComponent;
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, aComponent); worldBuilder.SetComponent(entity, aComponent);
worldBuilder.AddComponent(entity, cComponent); worldBuilder.SetComponent(entity, cComponent);
var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 2); var testDrawComponentID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 2);
var world = worldBuilder.Build(); var world = worldBuilder.Build();
@ -72,7 +72,7 @@ namespace Tests
{ {
foreach (var (componentID, component) in ReadComponents<TestDrawComponent>()) foreach (var (componentID, component) in ReadComponents<TestDrawComponent>())
{ {
Destroy(GetEntityIDByComponentID(componentID)); Destroy(GetEntityIDByComponentID<TestDrawComponent>(componentID));
} }
} }
} }
@ -88,7 +88,7 @@ namespace Tests
TestDrawComponent testDrawComponent; TestDrawComponent testDrawComponent;
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
var testDrawComponentID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 1); var testDrawComponentID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 1);
var world = worldBuilder.Build(); var world = worldBuilder.Build();

View File

@ -23,13 +23,14 @@ namespace Tests
} }
} }
[Activates(typeof(TestComponent))] [WritesPending(typeof(TestComponent))]
[Writes(typeof(TestComponent))]
class TestSpawner : Spawner<SpawnMessageA> class TestSpawner : Spawner<SpawnMessageA>
{ {
protected override void Spawn(SpawnMessageA message) protected override void Spawn(SpawnMessageA message)
{ {
resultEntity = CreateEntity(); resultEntity = CreateEntity();
AddComponent(resultEntity, new TestComponent()); SetComponent(resultEntity, new TestComponent());
Assert.Pass(); Assert.Pass();
} }
} }

View File

@ -112,30 +112,99 @@ namespace Tests
} }
} }
public class MutationConflict public class MultipleEngineWriteConflict
{ {
struct AComponent : IComponent { } struct AComponent : IComponent { }
[Updates(typeof(AComponent))] [Writes(typeof(AComponent))]
class AEngine : Engine class AEngine : Engine
{ {
public override void Update(double dt) { } public override void Update(double dt) { }
} }
[Updates(typeof(AComponent))] [Writes(typeof(AComponent))]
class BEngine : Engine class BEngine : Engine
{ {
public override void Update(double dt) { } public override void Update(double dt) { }
} }
[Test] [Test]
public void MutationConflictException() public void EngineWriteConflictException()
{ {
var worldBuilder = new WorldBuilder(); var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AEngine()); worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine()); worldBuilder.AddEngine(new BEngine());
Assert.Throws<EngineUpdateConflictException>(() => worldBuilder.Build()); Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
}
}
public class MultipleEngineWriteWithPriority
{
struct SetMessage : IMessage
{
public Entity entity;
}
struct AComponent : IComponent
{
public int myInt;
}
[Receives(typeof(SetMessage))]
[Writes(typeof(AComponent), 0)]
[WritesPending(typeof(AComponent))]
class AEngine : Engine
{
public override void Update(double dt)
{
foreach (var setMessage in ReadMessages<SetMessage>())
{
SetComponent(setMessage.entity, new AComponent { myInt = 0 });
}
}
}
[Receives(typeof(SetMessage))]
[Writes(typeof(AComponent), 1)]
[WritesPending(typeof(AComponent))]
class BEngine : Engine
{
public override void Update(double dt)
{
foreach (var setMessage in ReadMessages<SetMessage>())
{
SetComponent(setMessage.entity, new AComponent { myInt = 1 });
}
}
}
static AComponent resultComponent;
[ReadsPending(typeof(AComponent))]
class ReadComponentEngine : Engine
{
public override void Update(double dt)
{
resultComponent = ReadComponent<AComponent>().Item2;
}
}
[Test]
public void LowerPriorityWrites()
{
var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine());
var entity = worldBuilder.CreateEntity();
worldBuilder.SendMessage(new SetMessage { entity = entity });
var world = worldBuilder.Build();
world.Update(0.01);
Assert.That(resultComponent.myInt, Is.EqualTo(0));
} }
} }
@ -206,6 +275,68 @@ namespace Tests
} }
} }
public class PriorityConflict
{
[Writes(typeof(MockComponent), 2)]
class AEngine : Engine
{
public override void Update(double dt)
{
}
}
[Writes(typeof(MockComponent), 2)]
class BEngine : Engine
{
public override void Update(double dt)
{
}
}
[Test]
public void PriorityConflictTest()
{
var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine());
Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
}
}
public class EngineWriteConflict
{
[Writes(typeof(MockComponent))]
class AEngine : Engine
{
public override void Update(double dt)
{
}
}
[Writes(typeof(MockComponent), 2)]
class BEngine : Engine
{
public override void Update(double dt)
{
}
}
[Test]
public void EngineWriteConflictPriorityAndNoPriorityTest()
{
var worldBuilder = new WorldBuilder();
worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine());
Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
}
}
public class LegalEngines public class LegalEngines
{ {
static List<Engine> order = new List<Engine>(); static List<Engine> order = new List<Engine>();

View File

@ -43,26 +43,26 @@ namespace Tests
TestDrawComponent testDrawComponent = default(TestDrawComponent); TestDrawComponent testDrawComponent = default(TestDrawComponent);
var entity = worldBuilder.CreateEntity(); var entity = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entity, testComponent); worldBuilder.SetComponent(entity, testComponent);
var testDrawComponentOneID = worldBuilder.AddDrawComponent(entity, testDrawComponent, 3); var testDrawComponentOneID = worldBuilder.SetDrawComponent(entity, testDrawComponent, 3);
TestDrawComponent testDrawComponentTwo = default(TestDrawComponent); TestDrawComponent testDrawComponentTwo = default(TestDrawComponent);
var entityTwo = worldBuilder.CreateEntity(); var entityTwo = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entityTwo, testComponent); worldBuilder.SetComponent(entityTwo, testComponent);
var testDrawComponentTwoID = worldBuilder.AddDrawComponent(entityTwo, testDrawComponentTwo, 1); var testDrawComponentTwoID = worldBuilder.SetDrawComponent(entityTwo, testDrawComponentTwo, 1);
TestDrawComponent testDrawComponentThree = default(TestDrawComponent); TestDrawComponent testDrawComponentThree = default(TestDrawComponent);
var entityThree = worldBuilder.CreateEntity(); var entityThree = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entityThree, testComponent); worldBuilder.SetComponent(entityThree, testComponent);
var testDrawComponentThreeID = worldBuilder.AddDrawComponent(entityThree, testDrawComponentThree, 5); var testDrawComponentThreeID = worldBuilder.SetDrawComponent(entityThree, testDrawComponentThree, 5);
TestDrawComponent testDrawComponentFour = default(TestDrawComponent); TestDrawComponent testDrawComponentFour = default(TestDrawComponent);
var entityFour = worldBuilder.CreateEntity(); var entityFour = worldBuilder.CreateEntity();
worldBuilder.AddComponent(entityFour, testComponent); worldBuilder.SetComponent(entityFour, testComponent);
var testDrawComponentFourID = worldBuilder.AddDrawComponent(entityFour, testDrawComponentFour, -5); var testDrawComponentFourID = worldBuilder.SetDrawComponent(entityFour, testDrawComponentFour, -5);
var world = worldBuilder.Build(); var world = worldBuilder.Build();