structure for running engines in parallel

pull/5/head
Evan Hemsley 2019-07-26 01:17:39 -07:00
parent fd43036b2b
commit 61057f1f44
9 changed files with 118 additions and 56 deletions

View File

@ -28,6 +28,7 @@ namespace Encompass
protected List<T> _vertices = new List<T>(); protected List<T> _vertices = new List<T>();
protected Dictionary<T, HashSet<T>> _neighbors = new Dictionary<T, HashSet<T>>(); protected Dictionary<T, HashSet<T>> _neighbors = new Dictionary<T, HashSet<T>>();
protected Dictionary<T, HashSet<T>> _reverseNeighbors = new Dictionary<T, HashSet<T>>();
public IEnumerable<T> Vertices { get { return _vertices; } } public IEnumerable<T> Vertices { get { return _vertices; } }
@ -41,6 +42,7 @@ namespace Encompass
{ {
_vertices.Add(vertex); _vertices.Add(vertex);
_neighbors.Add(vertex, new HashSet<T>()); _neighbors.Add(vertex, new HashSet<T>());
_reverseNeighbors.Add(vertex, new HashSet<T>());
} }
} }
@ -86,6 +88,7 @@ namespace Encompass
if (VertexExists(v) && VertexExists(u)) if (VertexExists(v) && VertexExists(u))
{ {
_neighbors[v].Add(u); _neighbors[v].Add(u);
_reverseNeighbors[u].Add(v);
} }
} }
@ -110,7 +113,19 @@ namespace Encompass
} }
else else
{ {
return Enumerable.Empty<T>(); return Enumerable.Empty<T>(); // should throw instead
}
}
public IEnumerable<T> IncomingEdges(T vertex)
{
if (VertexExists(vertex))
{
return _reverseNeighbors[vertex];
}
else
{
return Enumerable.Empty<T>(); // should throw instead
} }
} }
@ -181,7 +196,7 @@ namespace Encompass
var distances = new Dictionary<T, int>(); var distances = new Dictionary<T, int>();
foreach (var node in Vertices) foreach (var node in Vertices)
{ {
distances[node] = int.MaxValue; distances[node] = int.MinValue;
} }
distances[source] = 0; distances[source] = 0;
@ -191,7 +206,7 @@ namespace Encompass
{ {
foreach (var neighbor in Neighbors(node)) foreach (var neighbor in Neighbors(node))
{ {
if (distances[neighbor] > distances[node] + 1) if (distances[neighbor] < distances[node] + 1)
{ {
distances[neighbor] = distances[node] + 1; distances[neighbor] = distances[node] + 1;
} }

View File

@ -1,24 +1,26 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Encompass namespace Encompass
{ {
public class World public class World
{ {
private readonly List<Engine> enginesInOrder; private readonly ILookup<int, Engine> engineGroups;
private readonly EntityManager entityManager; private readonly EntityManager entityManager;
private readonly ComponentManager componentManager; private readonly ComponentManager componentManager;
private readonly MessageManager messageManager; private readonly MessageManager messageManager;
private readonly RenderManager renderManager; private readonly RenderManager renderManager;
internal World( internal World(
List<Engine> enginesInOrder, ILookup<int, Engine> engineGroups,
EntityManager entityManager, EntityManager entityManager,
ComponentManager componentManager, ComponentManager componentManager,
MessageManager messageManager, MessageManager messageManager,
RenderManager renderManager RenderManager renderManager
) )
{ {
this.enginesInOrder = enginesInOrder; this.engineGroups = engineGroups;
this.entityManager = entityManager; this.entityManager = entityManager;
this.componentManager = componentManager; this.componentManager = componentManager;
this.messageManager = messageManager; this.messageManager = messageManager;
@ -27,9 +29,23 @@ namespace Encompass
public void Update(double dt) public void Update(double dt)
{ {
foreach (var engine in enginesInOrder) foreach (var engineGroup in engineGroups.OrderBy(lookup => lookup.Key))
{ {
engine.Update(dt); try
{
Parallel.ForEach(engineGroup, engine => engine.Update(dt));
}
catch (System.AggregateException e)
{
if (e.InnerException != null)
{
throw e.InnerException;
}
else
{
throw e;
}
}
} }
messageManager.ClearMessages(); messageManager.ClearMessages();

View File

@ -7,6 +7,11 @@ using Encompass.Engines;
namespace Encompass namespace Encompass
{ {
class WorldEngine : Engine // used to simulate the World, removed when passed to World
{
public override void Update(double dt) { }
}
public class WorldBuilder public class WorldBuilder
{ {
private readonly List<Engine> engines = new List<Engine>(); private readonly List<Engine> engines = new List<Engine>();
@ -165,7 +170,7 @@ namespace Encompass
return renderer; return renderer;
} }
private void BuildEngineGraph() private void BuildEngineGraph(Engine worldEngine)
{ {
foreach (var senderEngine in senders) foreach (var senderEngine in senders)
{ {
@ -183,11 +188,24 @@ namespace Encompass
} }
} }
} }
engineGraph.AddVertex(worldEngine);
foreach (var engine in engines.Where(engine => engine.GetType().IsGenericType && engine.GetType().GetGenericTypeDefinition() == typeof(ComponentMessageEmitter<>)))
{
engineGraph.AddEdge(worldEngine, engine);
}
foreach (var engine in engines.Where(engine => !engineGraph.IncomingEdges(engine).Any()))
{
engineGraph.AddEdge(worldEngine, engine);
}
} }
public World Build() public World Build()
{ {
BuildEngineGraph(); var worldEngine = new WorldEngine();
BuildEngineGraph(worldEngine);
if (engineGraph.Cyclic()) if (engineGraph.Cyclic())
{ {
@ -245,14 +263,8 @@ namespace Encompass
throw new EngineUpdateConflictException(errorString); throw new EngineUpdateConflictException(errorString);
} }
var engineOrder = new List<Engine>();
foreach (var engine in engineGraph.TopologicalSort())
{
engineOrder.Add(engine);
}
var world = new World( var world = new World(
engineOrder, engineGraph.LongestPaths(worldEngine).Where(pair => pair.Item2 != 0).ToLookup(pair => pair.Item2, pair => pair.Item1),
entityManager, entityManager,
componentManager, componentManager,
messageManager, messageManager,

View File

@ -123,6 +123,7 @@ namespace Tests
} }
} }
static bool hasComponentResult = false;
[ReadsPending(typeof(MockComponent))] [ReadsPending(typeof(MockComponent))]
class HasMockComponentEngine : Engine class HasMockComponentEngine : Engine
{ {
@ -135,7 +136,7 @@ namespace Tests
public override void Update(double dt) public override void Update(double dt)
{ {
Assert.IsTrue(HasComponent<MockComponent>(entity)); hasComponentResult = HasComponent<MockComponent>(entity);
} }
} }
@ -152,6 +153,8 @@ namespace Tests
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01); world.Update(0.01);
Assert.IsTrue(hasComponentResult);
} }
[Receives(typeof(EntityMessage))] [Receives(typeof(EntityMessage))]

View File

@ -179,10 +179,10 @@ namespace Tests
var result = graph.LongestPaths('r'); var result = graph.LongestPaths('r');
result.Should().Contain(('r', 0)); result.Should().Contain(('r', 0));
result.Should().Contain(('s', 1)); result.Should().Contain(('s', 1));
result.Should().Contain(('t', 1)); result.Should().Contain(('t', 2));
result.Should().Contain(('x', 2)); result.Should().Contain(('x', 3));
result.Should().Contain(('y', 2)); result.Should().Contain(('y', 4));
result.Should().Contain(('z', 3)); result.Should().Contain(('z', 5));
} }
[Test] [Test]

View File

@ -191,6 +191,8 @@ namespace Tests
var world = worldBuilder.Build(); var world = worldBuilder.Build();
Action updateWorld = () => world.Update(0.01);
var ex = Assert.Throws<IllegalUpdateException>(() => world.Update(0.01f)); var ex = Assert.Throws<IllegalUpdateException>(() => 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"));
} }

View File

@ -1,16 +1,14 @@
using System; using NUnit.Framework;
using System.Collections.Generic;
using System.Text;
using Encompass; using Encompass;
using Encompass.Engines; using Encompass.Engines;
using NUnit.Framework;
namespace Tests namespace Tests
{ {
public class SpawnerTest public class SpawnerTest
{ {
struct TestComponent : IComponent { } public struct TestComponent : IComponent { }
struct SpawnMessageA : IMessage { } public struct SpawnMessageA : IMessage { }
static Entity resultEntity; static Entity resultEntity;
@ -23,17 +21,22 @@ namespace Tests
} }
} }
static bool spawnResult = false;
[Activates(typeof(TestComponent))] [Activates(typeof(TestComponent))]
class TestSpawner : Spawner<SpawnMessageA> public class TestSpawner : Spawner<SpawnMessageA>
{ {
protected override void Spawn(SpawnMessageA message) protected override void Spawn(SpawnMessageA message)
{ {
resultEntity = CreateEntity(); spawnResult = true;
AddComponent(resultEntity, new TestComponent());
Assert.Pass();
} }
} }
interface TestSpawnerProtectedMembers
{
void Spawn(SpawnMessageA message);
}
[Test] [Test]
public void RunsSpawnMethodOnMessageRead() public void RunsSpawnMethodOnMessageRead()
{ {
@ -44,6 +47,8 @@ namespace Tests
var world = worldBuilder.Build(); var world = worldBuilder.Build();
world.Update(0.01); world.Update(0.01);
Assert.IsTrue(spawnResult);
} }
} }
} }

View File

@ -1,8 +1,9 @@
using NUnit.Framework; using NUnit.Framework;
using Encompass; using Encompass;
using System.Collections.Generic; using System.Collections.Concurrent;
using Encompass.Exceptions; using Encompass.Exceptions;
using System.Collections.Generic;
namespace Tests namespace Tests
{ {
@ -206,7 +207,7 @@ namespace Tests
public class LegalEngines public class LegalEngines
{ {
static List<Engine> order = new List<Engine>(); static ConcurrentQueue<Engine> queue = new ConcurrentQueue<Engine>();
struct AComponent : IComponent { } struct AComponent : IComponent { }
struct BComponent : IComponent { } struct BComponent : IComponent { }
@ -221,7 +222,7 @@ namespace Tests
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
order.Add(this); queue.Enqueue(this);
} }
} }
@ -230,7 +231,7 @@ namespace Tests
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
order.Add(this); queue.Enqueue(this);
} }
} }
@ -240,7 +241,7 @@ namespace Tests
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
order.Add(this); queue.Enqueue(this);
} }
} }
@ -249,7 +250,7 @@ namespace Tests
{ {
public override void Update(double dt) public override void Update(double dt)
{ {
order.Add(this); queue.Enqueue(this);
} }
} }
@ -269,6 +270,15 @@ namespace Tests
world.Update(0.01f); world.Update(0.01f);
var order = new List<Engine>();
while(!queue.IsEmpty)
{
Engine engine;
queue.TryDequeue(out engine);
order.Add(engine);
}
Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineC))); Assert.That(order.IndexOf(engineA), Is.LessThan(order.IndexOf(engineC)));
Assert.That(order.IndexOf(engineB), 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))); Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD)));

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
@ -7,12 +6,12 @@
<AssemblyName>EncompassECS.Framework.Tests</AssemblyName> <AssemblyName>EncompassECS.Framework.Tests</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.7.0" /> <PackageReference Include="FluentAssertions" Version="5.7.0"/>
<PackageReference Include="nunit" Version="3.11.0" /> <PackageReference Include="nunit" Version="3.11.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.11.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\encompass-cs\encompass-cs.csproj" /> <ProjectReference Include="..\encompass-cs\encompass-cs.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>