introduce concept of pending component

pull/5/head
thatcosmonaut 2019-07-19 16:15:48 -07:00
parent 1845d5f766
commit 89154f21d7
15 changed files with 123 additions and 112 deletions

5
TODO
View File

@ -1,6 +1 @@
- Change "Writes" To "Sends" and make it only take Messages
- Add "Updates" for updating components
- so we have four attributes: Activates, Reads, Updates, and Sends
- docs

View File

@ -19,7 +19,7 @@ namespace Encompass
throw new IllegalActivateTypeException("{0} must be a Component", activateType.Name);
}
this.activateTypes.Add(typeof(NewComponentMessage<>).MakeGenericType(activateType));
this.activateTypes.Add(typeof(PendingComponentMessage<>).MakeGenericType(activateType));
}
}
}

View File

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

View File

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

View File

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

View File

@ -42,10 +42,10 @@ namespace Encompass
receiveTypes.UnionWith(readsAttribute.readTypes);
}
var readsNewAttribute = GetType().GetCustomAttribute<ReadsNew>(false);
if (readsNewAttribute != null)
var readsPendingAttribute = GetType().GetCustomAttribute<ReadsPending>(false);
if (readsPendingAttribute != null)
{
receiveTypes.UnionWith(readsNewAttribute.newComponentReadTypes);
receiveTypes.UnionWith(readsPendingAttribute.readPendingTypes);
}
var updatesAttribute = GetType().GetCustomAttribute<Updates>(false);
@ -118,14 +118,14 @@ namespace Encompass
protected Guid AddComponent<TComponent>(Entity entity, TComponent component) where TComponent : struct, IComponent
{
if (!sendTypes.Contains(typeof(NewComponentMessage<TComponent>)))
if (!sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
var componentID = componentManager.AddComponent(entity.ID, component);
NewComponentMessage<TComponent> componentMessage;
PendingComponentMessage<TComponent> componentMessage;
componentMessage.entity = entity;
componentMessage.componentID = componentID;
componentMessage.component = component;
@ -136,14 +136,14 @@ namespace Encompass
protected Guid AddDrawComponent<TComponent>(Entity entity, TComponent component, int layer = 0) where TComponent : struct, IComponent
{
if (!sendTypes.Contains(typeof(NewComponentMessage<TComponent>)))
if (!sendTypes.Contains(typeof(PendingComponentMessage<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);
NewComponentMessage<TComponent> newComponentMessage;
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
@ -154,7 +154,7 @@ namespace Encompass
protected void ActivateComponent<TComponent>(Guid componentID) where TComponent : struct, IComponent
{
if (!sendTypes.Contains(typeof(NewComponentMessage<TComponent>)))
if (!sendTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
throw new IllegalActivateException("Engine {0} tried to activate undeclared Component {1}", GetType().Name, typeof(TComponent).Name);
}
@ -162,7 +162,7 @@ namespace Encompass
var entity = GetEntity(componentManager.GetEntityIDByComponentID(componentID));
var component = GetComponentByID<TComponent>(componentID);
NewComponentMessage<TComponent> newComponentMessage;
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;
@ -186,19 +186,19 @@ namespace Encompass
return ReadMessages<ComponentMessage<TComponent>>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component));
}
private IEnumerable<ValueTuple<Guid, TComponent>> NewComponentsOnEntity<TComponent>(Entity entity) where TComponent : struct, IComponent
private IEnumerable<ValueTuple<Guid, TComponent>> PendingComponentsOnEntity<TComponent>(Entity entity) where TComponent : struct, IComponent
{
if (!receiveTypes.Contains(typeof(NewComponentMessage<TComponent>)))
if (!receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared new Component {1}", GetType().Name, typeof(TComponent).Name);
throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name);
}
return ReadMessages<NewComponentMessage<TComponent>>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component));
return ReadMessages<PendingComponentMessage<TComponent>>().Where((message) => message.entity == entity).Select((message) => (message.componentID, message.component));
}
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponentsIncludingNew<TComponent>(Entity entity) where TComponent : struct, IComponent
protected IEnumerable<ValueTuple<Guid, TComponent>> GetPendingComponents<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return ExistingComponentsOnEntity<TComponent>(entity).Union(NewComponentsOnEntity<TComponent>(entity));
return PendingComponentsOnEntity<TComponent>(entity);
}
protected IEnumerable<ValueTuple<Guid, TComponent>> GetComponents<TComponent>(Entity entity) where TComponent : struct, IComponent
@ -206,11 +206,21 @@ namespace Encompass
return ExistingComponentsOnEntity<TComponent>(entity);
}
protected ValueTuple<Guid, TComponent> GetPendingComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return PendingComponentsOnEntity<TComponent>(entity).First();
}
protected ValueTuple<Guid, TComponent> GetComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return GetComponents<TComponent>(entity).First();
}
protected bool HasPendingComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return GetPendingComponents<TComponent>(entity).Any();
}
protected bool HasComponent<TComponent>(Entity entity) where TComponent : struct, IComponent
{
return GetComponents<TComponent>(entity).Any();
@ -220,7 +230,7 @@ namespace Encompass
{
if (!updateTypes.Contains(typeof(TComponent)))
{
throw new IllegalSendException("Engine {0} tried to write undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
throw new IllegalSendException("Engine {0} tried to update undeclared Component {1}", this.GetType().Name, typeof(TComponent).Name);
}
componentManager.AddUpdateComponentOperation(componentID, newComponent);
@ -235,7 +245,7 @@ namespace Encompass
{
if (!sendTypes.Contains(typeof(TMessage)))
{
throw new IllegalSendException("Engine {0} tried to write undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
throw new IllegalSendException("Engine {0} tried to send undeclared Message {1}", this.GetType().Name, typeof(TMessage).Name);
}
messageManager.AddMessage(message);
@ -256,6 +266,16 @@ namespace Encompass
return ReadMessages<TMessage>().Single();
}
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadPendingComponents<TComponent>() where TComponent : struct, IComponent
{
if (!receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name);
}
return ReadMessages<PendingComponentMessage<TComponent>>().Select((message) => (message.componentID, message.component));
}
protected IEnumerable<ValueTuple<Guid, TComponent>> ReadComponents<TComponent>() where TComponent : struct, IComponent
{
if (!receiveTypes.Contains(typeof(ComponentMessage<TComponent>)))
@ -266,6 +286,11 @@ namespace Encompass
return ReadMessages<ComponentMessage<TComponent>>().Select((message) => (message.componentID, message.component));
}
protected ValueTuple<Guid, TComponent> ReadPendingComponent<TComponent>() where TComponent : struct, IComponent
{
return ReadPendingComponents<TComponent>().Single();
}
protected ValueTuple<Guid, TComponent> ReadComponent<TComponent>() where TComponent : struct, IComponent
{
return ReadComponents<TComponent>().Single();
@ -281,6 +306,16 @@ namespace Encompass
return ReadMessages<TMessage>().Any();
}
protected bool SomePendingComponent<TComponent>() where TComponent : struct, IComponent
{
if (!receiveTypes.Contains(typeof(PendingComponentMessage<TComponent>)))
{
throw new IllegalReadException("Engine {0} tried to read undeclared pending Component {1}", GetType().Name, typeof(TComponent).Name);
}
return ReadMessages<PendingComponentMessage<TComponent>>().Any();
}
protected bool SomeComponent<TComponent>() where TComponent : struct, IComponent
{
if (!receiveTypes.Contains(typeof(ComponentMessage<TComponent>)))

View File

@ -6,14 +6,14 @@ namespace Encompass.Engines
{
public NewComponentMessageEmitter() : base()
{
sendTypes.Add(typeof(NewComponentMessage<TComponent>));
sendTypes.Add(typeof(PendingComponentMessage<TComponent>));
}
public override void Update(double dt)
{
foreach (var (entity, componentID, component) in ReadComponentsFromWorld<TComponent>())
{
NewComponentMessage<TComponent> newComponentMessage;
PendingComponentMessage<TComponent> newComponentMessage;
newComponentMessage.entity = entity;
newComponentMessage.componentID = componentID;
newComponentMessage.component = component;

View File

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

View File

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

View File

@ -2,7 +2,7 @@ using System;
namespace Encompass
{
public struct NewComponentMessage<TComponent> : IMessage where TComponent : struct, IComponent
public struct PendingComponentMessage<TComponent> : IMessage where TComponent : struct, IComponent
{
public Entity entity;
public Guid componentID;

View File

@ -87,13 +87,13 @@ namespace Encompass
foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes))
{
// ComponentMessages can safely self-cycle
// this does introduce a gotcha though: if you AddComponent and then HasComponent or GetComponent you will receive a false negative
// there is no point to doing this but it is a gotcha i suppose
if (!(messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(ComponentMessage<>)))
if ((messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(PendingComponentMessage<>)))
{
throw new EngineMessageSelfCycleException("Engine {0} both reads and writes Message {1}", engine.GetType().Name, messageType.Name);
var componentType = messageType.GetGenericArguments().Single();
throw new EngineSelfCycleException("Engine {0} both activates and reads pending Component {1}", engine.GetType().Name, componentType.Name);
}
throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageType.Name);
}
if (messageSendTypes.Any())
@ -106,7 +106,7 @@ namespace Encompass
if (receiveType.IsGenericType)
{
var genericTypeDefinition = receiveType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(NewComponentMessage<>))
if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(PendingComponentMessage<>))
{
var componentType = receiveType.GetGenericArguments().Single();
if (!registeredComponentTypes.Contains(componentType))
@ -129,7 +129,7 @@ namespace Encompass
if (sendType.IsGenericType)
{
var genericTypeDefinition = sendType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(NewComponentMessage<>))
if (genericTypeDefinition == typeof(ComponentMessage<>) || genericTypeDefinition == typeof(PendingComponentMessage<>))
{
var componentType = sendType.GetGenericArguments().Single();
if (!registeredNewComponentTypes.Contains(componentType))
@ -195,12 +195,13 @@ namespace Encompass
{
var cycles = engineGraph.SimpleCycles();
var errorString = "Cycle(s) found in Engines: ";
foreach (var cycle in cycles.Reverse())
foreach (var cycle in cycles)
{
var reversed = cycle.Reverse();
errorString += "\n" +
string.Join(" -> ", cycle.Select((engine) => engine.GetType().Name)) +
string.Join(" -> ", reversed.Select((engine) => engine.GetType().Name)) +
" -> " +
cycle.First().GetType().Name;
reversed.First().GetType().Name;
}
throw new EngineCycleException(errorString);
}
@ -213,38 +214,35 @@ namespace Encompass
{
foreach (var updateType in engine.updateTypes)
{
if (updateType.GetInterfaces().Contains(typeof(IComponent))) // if our write type is a component
if (mutatedComponentTypes.Contains(updateType))
{
if (mutatedComponentTypes.Contains(updateType))
{
duplicateMutations.Add(updateType);
}
else
{
mutatedComponentTypes.Add(updateType);
}
if (!componentToEngines.ContainsKey(updateType))
{
componentToEngines[updateType] = new List<Engine>();
}
componentToEngines[updateType].Add(engine);
duplicateMutations.Add(updateType);
}
else
{
mutatedComponentTypes.Add(updateType);
}
if (!componentToEngines.ContainsKey(updateType))
{
componentToEngines[updateType] = new List<Engine>();
}
componentToEngines[updateType].Add(engine);
}
}
if (duplicateMutations.Count > 0)
{
var errorString = "Multiple Engines write the same Component: ";
var errorString = "Multiple Engines update the same Component: ";
foreach (var componentType in duplicateMutations)
{
errorString += "\n" +
componentType.Name + " written by: " +
componentType.Name + " updated by: " +
string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name));
}
throw new EngineWriteConflictException(errorString);
throw new EngineUpdateConflictException(errorString);
}
var engineOrder = new List<Engine>();

View File

@ -124,6 +124,7 @@ namespace Tests
}
[Reads(typeof(MockComponent))]
[ReadsPending(typeof(MockComponent))]
class HasMockComponentEngine : Engine
{
private Entity entity;
@ -135,7 +136,7 @@ namespace Tests
public override void Update(double dt)
{
Assert.IsTrue(HasComponent<MockComponent>(entity));
Assert.IsTrue(HasPendingComponent<MockComponent>(entity));
}
}
@ -468,14 +469,14 @@ namespace Tests
}
[Receives(typeof(CheckHasMockComponentMessage))]
[Reads(typeof(MockComponent))]
[ReadsPending(typeof(MockComponent))]
class CheckHasMockComponentEngine : Engine
{
public override void Update(double dt)
{
foreach (var checkHasMockComponentMessage in ReadMessages<CheckHasMockComponentMessage>())
{
Assert.IsTrue(HasComponent<MockComponent>(checkHasMockComponentMessage.entity));
Assert.IsTrue(HasPendingComponent<MockComponent>(checkHasMockComponentMessage.entity));
}
}
}

View File

@ -192,7 +192,7 @@ namespace Tests
var world = worldBuilder.Build();
var ex = Assert.Throws<IllegalSendException>(() => world.Update(0.01f));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to write undeclared Component MockComponent"));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent"));
}
struct MockMessage : IMessage
@ -279,7 +279,7 @@ namespace Tests
var world = worldBuilder.Build();
var ex = Assert.Throws<IllegalSendException>(() => world.Update(0.01f));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to write undeclared Message MockMessage"));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage"));
}
static bool someTest;

View File

@ -48,8 +48,6 @@ namespace Tests
world.Update(0.01f);
Console.WriteLine(renderer.IsTracking(entityNotToTrack.ID));
Assert.IsTrue(renderer.IsTracking(entityToTrack.ID));
Assert.IsFalse(renderer.IsTracking(entityNotToTrack.ID));
Assert.IsFalse(renderer.IsTracking(entityWithoutDrawComponent.ID));

View File

@ -133,7 +133,7 @@ namespace Tests
worldBuilder.AddEngine(new AEngine());
worldBuilder.AddEngine(new BEngine());
Assert.Throws<EngineWriteConflictException>(() => worldBuilder.Build());
Assert.Throws<EngineUpdateConflictException>(() => worldBuilder.Build());
}
}
@ -156,7 +156,7 @@ namespace Tests
{
var worldBuilder = new WorldBuilder();
Assert.Throws<EngineMessageSelfCycleException>(() => worldBuilder.AddEngine(new AEngine()), "Engine both reads and writes Message AMessage");
Assert.Throws<EngineSelfCycleException>(() => worldBuilder.AddEngine(new AEngine()), "Engine both sends and receives Message AMessage");
}
}