world builder structure
parent
c1aa6a07c9
commit
cfc5ca178f
|
@ -6,9 +6,9 @@ namespace Encompass
|
|||
{
|
||||
public abstract class Engine
|
||||
{
|
||||
public readonly List<Type> mutateComponentTypes = new List<Type>();
|
||||
public readonly List<Type> emitMessageTypes = new List<Type>();
|
||||
public readonly List<Type> readMessageTypes = new List<Type>();
|
||||
private readonly List<Type> mutateComponentTypes = new List<Type>();
|
||||
private readonly List<Type> emitMessageTypes = new List<Type>();
|
||||
private readonly List<Type> readMessageTypes = new List<Type>();
|
||||
|
||||
private EntityManager entityManager;
|
||||
private ComponentManager componentManager;
|
||||
|
|
|
@ -4,19 +4,19 @@ namespace Encompass
|
|||
{
|
||||
public class World
|
||||
{
|
||||
private List<Engine> engines;
|
||||
private List<Engine> enginesInOrder;
|
||||
private EntityManager entityManager;
|
||||
private ComponentManager componentManager;
|
||||
private MessageManager messageManager;
|
||||
|
||||
internal World(
|
||||
List<Engine> engines,
|
||||
List<Engine> enginesInOrder,
|
||||
EntityManager entityManager,
|
||||
ComponentManager componentManager,
|
||||
MessageManager messageManager
|
||||
)
|
||||
{
|
||||
this.engines = engines;
|
||||
this.enginesInOrder = enginesInOrder;
|
||||
this.entityManager = entityManager;
|
||||
this.componentManager = componentManager;
|
||||
this.messageManager = messageManager;
|
||||
|
@ -24,7 +24,7 @@ namespace Encompass
|
|||
|
||||
public void Update(float dt)
|
||||
{
|
||||
foreach (var engine in engines)
|
||||
foreach (var engine in enginesInOrder)
|
||||
{
|
||||
engine.Update(dt);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
public class WorldBuilder
|
||||
{
|
||||
private List<Engine> engines = new List<Engine>();
|
||||
private DirectedGraph<Engine> engineGraph = new DirectedGraph<Engine>();
|
||||
|
||||
private ComponentManager componentManager;
|
||||
private EntityManager entityManager;
|
||||
private MessageManager messageManager;
|
||||
|
||||
private Dictionary<Type, HashSet<Engine>> messageTypeToEmitters = new Dictionary<Type, HashSet<Engine>>();
|
||||
private Dictionary<Type, HashSet<Engine>> messageTypeToReaders = new Dictionary<Type, HashSet<Engine>>();
|
||||
|
||||
public WorldBuilder()
|
||||
{
|
||||
componentManager = new ComponentManager();
|
||||
|
@ -32,13 +39,122 @@ namespace Encompass
|
|||
|
||||
engines.Add(engine);
|
||||
|
||||
engineGraph.AddVertex(engine);
|
||||
|
||||
var emitMessageAttribute = engine.GetType().GetCustomAttribute<Emits>(false);
|
||||
if (emitMessageAttribute != null)
|
||||
{
|
||||
foreach (var emitMessageType in engine.GetType().GetCustomAttribute<Emits>(false).emitMessageTypes)
|
||||
{
|
||||
if (!messageTypeToEmitters.ContainsKey(emitMessageType))
|
||||
{
|
||||
messageTypeToEmitters.Add(emitMessageType, new HashSet<Engine>());
|
||||
}
|
||||
|
||||
messageTypeToEmitters[emitMessageType].Add(engine);
|
||||
|
||||
if (messageTypeToReaders.ContainsKey(emitMessageType))
|
||||
{
|
||||
foreach (var reader in messageTypeToReaders[emitMessageType])
|
||||
{
|
||||
engineGraph.AddEdge(engine, reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readMessageAttribute = engine.GetType().GetCustomAttribute<Reads>(false);
|
||||
if (readMessageAttribute != null)
|
||||
{
|
||||
foreach (var readMessageType in engine.GetType().GetCustomAttribute<Reads>(false).readMessageTypes)
|
||||
{
|
||||
if (!messageTypeToReaders.ContainsKey(readMessageType))
|
||||
{
|
||||
messageTypeToReaders.Add(readMessageType, new HashSet<Engine>());
|
||||
}
|
||||
|
||||
messageTypeToReaders[readMessageType].Add(engine);
|
||||
|
||||
if (messageTypeToEmitters.ContainsKey(readMessageType))
|
||||
{
|
||||
foreach (var emitter in messageTypeToEmitters[readMessageType])
|
||||
{
|
||||
engineGraph.AddEdge(emitter, engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
public World Build()
|
||||
{
|
||||
if (engineGraph.Cyclic())
|
||||
{
|
||||
var cycles = engineGraph.SimpleCycles();
|
||||
var errorString = "Cycle(s) found in Engines: ";
|
||||
foreach (var cycle in cycles)
|
||||
{
|
||||
errorString += "\n" +
|
||||
string.Join(" - > ", cycle.Select((engine) => engine.GetType().Name)) +
|
||||
" -> " +
|
||||
cycle.First().GetType().Name;
|
||||
}
|
||||
throw new EngineCycleException(errorString);
|
||||
}
|
||||
|
||||
var mutatedComponentTypes = new HashSet<Type>();
|
||||
var duplicateMutations = new List<Type>();
|
||||
var componentToEngines = new Dictionary<Type, List<Engine>>();
|
||||
|
||||
foreach (var engine in engines)
|
||||
{
|
||||
var mutateAttribute = engine.GetType().GetCustomAttribute<Mutates>(false);
|
||||
if (mutateAttribute != null)
|
||||
{
|
||||
foreach (var mutateComponentType in engine.GetType().GetCustomAttribute<Mutates>(false).mutateComponentTypes)
|
||||
{
|
||||
if (mutatedComponentTypes.Contains(mutateComponentType))
|
||||
{
|
||||
duplicateMutations.Add(mutateComponentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
mutatedComponentTypes.Add(mutateComponentType);
|
||||
}
|
||||
|
||||
if (!componentToEngines.ContainsKey(mutateComponentType))
|
||||
{
|
||||
componentToEngines[mutateComponentType] = new List<Engine>();
|
||||
}
|
||||
|
||||
componentToEngines[mutateComponentType].Add(engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateMutations.Count > 0)
|
||||
{
|
||||
var errorString = "Multiple Engines mutate the same Component: ";
|
||||
foreach (var componentType in duplicateMutations)
|
||||
{
|
||||
errorString += "\n" +
|
||||
componentType.Name + " mutated by: " +
|
||||
string.Join(", ", componentToEngines[componentType].Select((engine) => engine.GetType().Name));
|
||||
}
|
||||
|
||||
throw new EngineMutationConflictException(errorString);
|
||||
}
|
||||
|
||||
var engineOrder = new List<Engine>();
|
||||
foreach (var engine in engineGraph.TopologicalSort())
|
||||
{
|
||||
engineOrder.Add(engine);
|
||||
}
|
||||
|
||||
var world = new World(
|
||||
this.engines,
|
||||
engineOrder,
|
||||
this.entityManager,
|
||||
this.componentManager,
|
||||
this.messageManager
|
||||
|
|
|
@ -21,5 +21,8 @@
|
|||
<Content Include="attributes\Emits.cs" />
|
||||
<Content Include="exceptions\IllegalMessageEmitException.cs" />
|
||||
<Content Include="exceptions\IllegalMessageReadException.cs" />
|
||||
<Content Include="exceptions\EngineCycleException.cs" />
|
||||
<Content Include="exceptions\EngineMutationConflictException.cs" />
|
||||
<Content Include="attributes\Reads.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
public class EngineCycleException : Exception
|
||||
{
|
||||
public EngineCycleException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Encompass
|
||||
{
|
||||
public class EngineMutationConflictException : Exception
|
||||
{
|
||||
public EngineMutationConflictException(
|
||||
string format,
|
||||
params object[] args
|
||||
) : base(string.Format(format, args)) { }
|
||||
}
|
||||
}
|
|
@ -159,6 +159,11 @@ namespace Encompass
|
|||
return output;
|
||||
}
|
||||
|
||||
public bool Cyclic()
|
||||
{
|
||||
return StronglyConnectedComponents().Any((scc) => scc.Count() > 1);
|
||||
}
|
||||
|
||||
public IEnumerable<T> TopologicalSort()
|
||||
{
|
||||
var dfs = NodeDFS();
|
||||
|
|
|
@ -341,5 +341,34 @@ namespace Tests
|
|||
result.Should().ContainEquivalentOf(cycleE);
|
||||
result.Should().HaveCount(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cyclic()
|
||||
{
|
||||
var myGraph = new DirectedGraph<int>();
|
||||
myGraph.AddVertices(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
Tuple.Create(1, 2),
|
||||
Tuple.Create(2, 3),
|
||||
Tuple.Create(3, 1),
|
||||
Tuple.Create(3, 4)
|
||||
);
|
||||
|
||||
Assert.That(myGraph.Cyclic(), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Acyclic()
|
||||
{
|
||||
var myGraph = new DirectedGraph<int>();
|
||||
myGraph.AddVertices(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
Tuple.Create(1, 2),
|
||||
Tuple.Create(2, 3),
|
||||
Tuple.Create(3, 4)
|
||||
);
|
||||
|
||||
Assert.That(myGraph.Cyclic(), Is.False);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
using NUnit.Framework;
|
||||
|
||||
using Encompass;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public class WorldBuilderTest
|
||||
{
|
||||
public class EngineCycleSimple
|
||||
{
|
||||
struct AMessage : IMessage { }
|
||||
struct BMessage : IMessage { }
|
||||
|
||||
[Reads(typeof(AMessage))]
|
||||
[Emits(typeof(BMessage))]
|
||||
class AEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
BMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(BMessage))]
|
||||
[Emits(typeof(AMessage))]
|
||||
class BEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
AMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EngineCycle()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine<AEngine>();
|
||||
worldBuilder.AddEngine<BEngine>();
|
||||
|
||||
Assert.Throws<EngineCycleException>(() => worldBuilder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
public class EngineCycleComplex
|
||||
{
|
||||
struct AMessage : IMessage { }
|
||||
struct BMessage : IMessage { }
|
||||
struct CMessage : IMessage { }
|
||||
struct DMessage : IMessage { }
|
||||
|
||||
[Reads(typeof(AMessage))]
|
||||
[Emits(typeof(BMessage))]
|
||||
class AEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
BMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(BMessage))]
|
||||
[Emits(typeof(CMessage))]
|
||||
class BEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
CMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(CMessage))]
|
||||
[Emits(typeof(DMessage))]
|
||||
class CEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
DMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(DMessage))]
|
||||
[Emits(typeof(AMessage))]
|
||||
class DEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
AMessage message;
|
||||
this.EmitMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EngineCycle()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine<AEngine>();
|
||||
worldBuilder.AddEngine<BEngine>();
|
||||
worldBuilder.AddEngine<CEngine>();
|
||||
worldBuilder.AddEngine<DEngine>();
|
||||
|
||||
Assert.Throws<EngineCycleException>(() => worldBuilder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
public class MutationConflict
|
||||
{
|
||||
struct AComponent : IComponent { }
|
||||
|
||||
[Mutates(typeof(AComponent))]
|
||||
class AEngine : Engine
|
||||
{
|
||||
public override void Update(float dt) { }
|
||||
}
|
||||
|
||||
[Mutates(typeof(AComponent))]
|
||||
class BEngine : Engine
|
||||
{
|
||||
public override void Update(float dt) { }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MutationConflictException()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
worldBuilder.AddEngine<AEngine>();
|
||||
worldBuilder.AddEngine<BEngine>();
|
||||
|
||||
Assert.Throws<EngineMutationConflictException>(() => worldBuilder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
public class LegalEngines
|
||||
{
|
||||
static List<Engine> order = new List<Engine>();
|
||||
|
||||
struct AComponent : IComponent { }
|
||||
struct BComponent : IComponent { }
|
||||
|
||||
struct AMessage : IMessage { }
|
||||
struct BMessage : IMessage { }
|
||||
struct CMessage : IMessage { }
|
||||
struct DMessage : IMessage { }
|
||||
|
||||
[Mutates(typeof(AComponent))]
|
||||
[Emits(typeof(AMessage))]
|
||||
class AEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
order.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Mutates(typeof(BComponent))]
|
||||
[Emits(typeof(BMessage))]
|
||||
class BEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
order.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(AMessage), typeof(BMessage))]
|
||||
[Emits(typeof(DMessage))]
|
||||
class CEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
order.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Reads(typeof(DMessage))]
|
||||
class DEngine : Engine
|
||||
{
|
||||
public override void Update(float dt)
|
||||
{
|
||||
order.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EngineOrder()
|
||||
{
|
||||
var worldBuilder = new WorldBuilder();
|
||||
|
||||
var engineA = worldBuilder.AddEngine<AEngine>();
|
||||
var engineB = worldBuilder.AddEngine<BEngine>();
|
||||
var engineC = worldBuilder.AddEngine<CEngine>();
|
||||
var engineD = worldBuilder.AddEngine<DEngine>();
|
||||
|
||||
Assert.DoesNotThrow(() => worldBuilder.Build());
|
||||
|
||||
var world = worldBuilder.Build();
|
||||
|
||||
world.Update(0.01f);
|
||||
|
||||
Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineC)));
|
||||
Assert.That(order.IndexOf(engineB), Is.LessThan(order.IndexOf(engineC)));
|
||||
Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,5 +15,6 @@
|
|||
<ProjectReference Include="..\src\encompass-cs.csproj" />
|
||||
<Content Include="EngineTest.cs" />
|
||||
<Content Include="DirectedGraphTest.cs" />
|
||||
<Content Include="WorldBuilderTest.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue