world builder structure
parent
c1aa6a07c9
commit
cfc5ca178f
|
@ -6,9 +6,9 @@ namespace Encompass
|
||||||
{
|
{
|
||||||
public abstract class Engine
|
public abstract class Engine
|
||||||
{
|
{
|
||||||
public readonly List<Type> mutateComponentTypes = new List<Type>();
|
private readonly List<Type> mutateComponentTypes = new List<Type>();
|
||||||
public readonly List<Type> emitMessageTypes = new List<Type>();
|
private readonly List<Type> emitMessageTypes = new List<Type>();
|
||||||
public readonly List<Type> readMessageTypes = new List<Type>();
|
private readonly List<Type> readMessageTypes = new List<Type>();
|
||||||
|
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
private ComponentManager componentManager;
|
private ComponentManager componentManager;
|
||||||
|
|
|
@ -4,19 +4,19 @@ namespace Encompass
|
||||||
{
|
{
|
||||||
public class World
|
public class World
|
||||||
{
|
{
|
||||||
private List<Engine> engines;
|
private List<Engine> enginesInOrder;
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
private ComponentManager componentManager;
|
private ComponentManager componentManager;
|
||||||
private MessageManager messageManager;
|
private MessageManager messageManager;
|
||||||
|
|
||||||
internal World(
|
internal World(
|
||||||
List<Engine> engines,
|
List<Engine> enginesInOrder,
|
||||||
EntityManager entityManager,
|
EntityManager entityManager,
|
||||||
ComponentManager componentManager,
|
ComponentManager componentManager,
|
||||||
MessageManager messageManager
|
MessageManager messageManager
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.engines = engines;
|
this.enginesInOrder = enginesInOrder;
|
||||||
this.entityManager = entityManager;
|
this.entityManager = entityManager;
|
||||||
this.componentManager = componentManager;
|
this.componentManager = componentManager;
|
||||||
this.messageManager = messageManager;
|
this.messageManager = messageManager;
|
||||||
|
@ -24,7 +24,7 @@ namespace Encompass
|
||||||
|
|
||||||
public void Update(float dt)
|
public void Update(float dt)
|
||||||
{
|
{
|
||||||
foreach (var engine in engines)
|
foreach (var engine in enginesInOrder)
|
||||||
{
|
{
|
||||||
engine.Update(dt);
|
engine.Update(dt);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Encompass
|
namespace Encompass
|
||||||
{
|
{
|
||||||
public class WorldBuilder
|
public class WorldBuilder
|
||||||
{
|
{
|
||||||
private List<Engine> engines = new List<Engine>();
|
private List<Engine> engines = new List<Engine>();
|
||||||
|
private DirectedGraph<Engine> engineGraph = new DirectedGraph<Engine>();
|
||||||
|
|
||||||
private ComponentManager componentManager;
|
private ComponentManager componentManager;
|
||||||
private EntityManager entityManager;
|
private EntityManager entityManager;
|
||||||
private MessageManager messageManager;
|
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()
|
public WorldBuilder()
|
||||||
{
|
{
|
||||||
componentManager = new ComponentManager();
|
componentManager = new ComponentManager();
|
||||||
|
@ -32,13 +39,122 @@ namespace Encompass
|
||||||
|
|
||||||
engines.Add(engine);
|
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;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
public World Build()
|
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(
|
var world = new World(
|
||||||
this.engines,
|
engineOrder,
|
||||||
this.entityManager,
|
this.entityManager,
|
||||||
this.componentManager,
|
this.componentManager,
|
||||||
this.messageManager
|
this.messageManager
|
||||||
|
|
|
@ -21,5 +21,8 @@
|
||||||
<Content Include="attributes\Emits.cs" />
|
<Content Include="attributes\Emits.cs" />
|
||||||
<Content Include="exceptions\IllegalMessageEmitException.cs" />
|
<Content Include="exceptions\IllegalMessageEmitException.cs" />
|
||||||
<Content Include="exceptions\IllegalMessageReadException.cs" />
|
<Content Include="exceptions\IllegalMessageReadException.cs" />
|
||||||
|
<Content Include="exceptions\EngineCycleException.cs" />
|
||||||
|
<Content Include="exceptions\EngineMutationConflictException.cs" />
|
||||||
|
<Content Include="attributes\Reads.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Cyclic()
|
||||||
|
{
|
||||||
|
return StronglyConnectedComponents().Any((scc) => scc.Count() > 1);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<T> TopologicalSort()
|
public IEnumerable<T> TopologicalSort()
|
||||||
{
|
{
|
||||||
var dfs = NodeDFS();
|
var dfs = NodeDFS();
|
||||||
|
|
|
@ -341,5 +341,34 @@ namespace Tests
|
||||||
result.Should().ContainEquivalentOf(cycleE);
|
result.Should().ContainEquivalentOf(cycleE);
|
||||||
result.Should().HaveCount(5);
|
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" />
|
<ProjectReference Include="..\src\encompass-cs.csproj" />
|
||||||
<Content Include="EngineTest.cs" />
|
<Content Include="EngineTest.cs" />
|
||||||
<Content Include="DirectedGraphTest.cs" />
|
<Content Include="DirectedGraphTest.cs" />
|
||||||
|
<Content Include="WorldBuilderTest.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
Loading…
Reference in New Issue