From 61057f1f44a74cb087f77497f65397795087d94e Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Fri, 26 Jul 2019 01:17:39 -0700 Subject: [PATCH] structure for running engines in parallel --- encompass-cs/Graph/DirectedGraph.cs | 21 +++++++++++++++--- encompass-cs/World.cs | 26 ++++++++++++++++++----- encompass-cs/WorldBuilder.cs | 30 ++++++++++++++++++-------- test/ComponentTest.cs | 5 ++++- test/DirectedGraphTest.cs | 8 +++---- test/EngineTest.cs | 2 ++ test/SpawnerTest.cs | 27 +++++++++++++---------- test/WorldBuilderTest.cs | 22 +++++++++++++------ test/test.csproj | 33 ++++++++++++++--------------- 9 files changed, 118 insertions(+), 56 deletions(-) diff --git a/encompass-cs/Graph/DirectedGraph.cs b/encompass-cs/Graph/DirectedGraph.cs index c8620f2..125a153 100644 --- a/encompass-cs/Graph/DirectedGraph.cs +++ b/encompass-cs/Graph/DirectedGraph.cs @@ -28,6 +28,7 @@ namespace Encompass protected List _vertices = new List(); protected Dictionary> _neighbors = new Dictionary>(); + protected Dictionary> _reverseNeighbors = new Dictionary>(); public IEnumerable Vertices { get { return _vertices; } } @@ -41,6 +42,7 @@ namespace Encompass { _vertices.Add(vertex); _neighbors.Add(vertex, new HashSet()); + _reverseNeighbors.Add(vertex, new HashSet()); } } @@ -86,6 +88,7 @@ namespace Encompass if (VertexExists(v) && VertexExists(u)) { _neighbors[v].Add(u); + _reverseNeighbors[u].Add(v); } } @@ -110,7 +113,19 @@ namespace Encompass } else { - return Enumerable.Empty(); + return Enumerable.Empty(); // should throw instead + } + } + + public IEnumerable IncomingEdges(T vertex) + { + if (VertexExists(vertex)) + { + return _reverseNeighbors[vertex]; + } + else + { + return Enumerable.Empty(); // should throw instead } } @@ -181,7 +196,7 @@ namespace Encompass var distances = new Dictionary(); foreach (var node in Vertices) { - distances[node] = int.MaxValue; + distances[node] = int.MinValue; } distances[source] = 0; @@ -191,7 +206,7 @@ namespace Encompass { foreach (var neighbor in Neighbors(node)) { - if (distances[neighbor] > distances[node] + 1) + if (distances[neighbor] < distances[node] + 1) { distances[neighbor] = distances[node] + 1; } diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index a17623c..1979a17 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -1,24 +1,26 @@ using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace Encompass { public class World { - private readonly List enginesInOrder; + private readonly ILookup engineGroups; private readonly EntityManager entityManager; private readonly ComponentManager componentManager; private readonly MessageManager messageManager; private readonly RenderManager renderManager; internal World( - List enginesInOrder, + ILookup engineGroups, EntityManager entityManager, ComponentManager componentManager, MessageManager messageManager, RenderManager renderManager ) { - this.enginesInOrder = enginesInOrder; + this.engineGroups = engineGroups; this.entityManager = entityManager; this.componentManager = componentManager; this.messageManager = messageManager; @@ -27,9 +29,23 @@ namespace Encompass 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(); diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 47060f3..8799d8d 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -7,6 +7,11 @@ using Encompass.Engines; namespace Encompass { + class WorldEngine : Engine // used to simulate the World, removed when passed to World + { + public override void Update(double dt) { } + } + public class WorldBuilder { private readonly List engines = new List(); @@ -165,7 +170,7 @@ namespace Encompass return renderer; } - private void BuildEngineGraph() + private void BuildEngineGraph(Engine worldEngine) { 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() { - BuildEngineGraph(); + var worldEngine = new WorldEngine(); + BuildEngineGraph(worldEngine); if (engineGraph.Cyclic()) { @@ -245,14 +263,8 @@ namespace Encompass throw new EngineUpdateConflictException(errorString); } - var engineOrder = new List(); - foreach (var engine in engineGraph.TopologicalSort()) - { - engineOrder.Add(engine); - } - var world = new World( - engineOrder, + engineGraph.LongestPaths(worldEngine).Where(pair => pair.Item2 != 0).ToLookup(pair => pair.Item2, pair => pair.Item1), entityManager, componentManager, messageManager, diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index 4c69056..d80a4a1 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -123,6 +123,7 @@ namespace Tests } } + static bool hasComponentResult = false; [ReadsPending(typeof(MockComponent))] class HasMockComponentEngine : Engine { @@ -135,7 +136,7 @@ namespace Tests public override void Update(double dt) { - Assert.IsTrue(HasComponent(entity)); + hasComponentResult = HasComponent(entity); } } @@ -152,6 +153,8 @@ namespace Tests var world = worldBuilder.Build(); world.Update(0.01); + + Assert.IsTrue(hasComponentResult); } [Receives(typeof(EntityMessage))] diff --git a/test/DirectedGraphTest.cs b/test/DirectedGraphTest.cs index b4eca48..06d7bb8 100644 --- a/test/DirectedGraphTest.cs +++ b/test/DirectedGraphTest.cs @@ -179,10 +179,10 @@ namespace Tests var result = graph.LongestPaths('r'); result.Should().Contain(('r', 0)); result.Should().Contain(('s', 1)); - result.Should().Contain(('t', 1)); - result.Should().Contain(('x', 2)); - result.Should().Contain(('y', 2)); - result.Should().Contain(('z', 3)); + result.Should().Contain(('t', 2)); + result.Should().Contain(('x', 3)); + result.Should().Contain(('y', 4)); + result.Should().Contain(('z', 5)); } [Test] diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 060383a..4b02d14 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -191,6 +191,8 @@ namespace Tests var world = worldBuilder.Build(); + Action updateWorld = () => world.Update(0.01); + var ex = Assert.Throws(() => world.Update(0.01f)); Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent")); } diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index 1ca1a00..3aa171e 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -1,16 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Text; +using NUnit.Framework; + using Encompass; using Encompass.Engines; -using NUnit.Framework; namespace Tests { public class SpawnerTest { - struct TestComponent : IComponent { } - struct SpawnMessageA : IMessage { } + public struct TestComponent : IComponent { } + public struct SpawnMessageA : IMessage { } static Entity resultEntity; @@ -23,17 +21,22 @@ namespace Tests } } + static bool spawnResult = false; + [Activates(typeof(TestComponent))] - class TestSpawner : Spawner + public class TestSpawner : Spawner { - protected override void Spawn(SpawnMessageA message) + protected override void Spawn(SpawnMessageA message) { - resultEntity = CreateEntity(); - AddComponent(resultEntity, new TestComponent()); - Assert.Pass(); + spawnResult = true; } } + interface TestSpawnerProtectedMembers + { + void Spawn(SpawnMessageA message); + } + [Test] public void RunsSpawnMethodOnMessageRead() { @@ -44,6 +47,8 @@ namespace Tests var world = worldBuilder.Build(); world.Update(0.01); + + Assert.IsTrue(spawnResult); } } } diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 589c994..4162d45 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -1,8 +1,9 @@ using NUnit.Framework; using Encompass; -using System.Collections.Generic; +using System.Collections.Concurrent; using Encompass.Exceptions; +using System.Collections.Generic; namespace Tests { @@ -206,7 +207,7 @@ namespace Tests public class LegalEngines { - static List order = new List(); + static ConcurrentQueue queue = new ConcurrentQueue(); struct AComponent : IComponent { } struct BComponent : IComponent { } @@ -221,7 +222,7 @@ namespace Tests { public override void Update(double dt) { - order.Add(this); + queue.Enqueue(this); } } @@ -230,7 +231,7 @@ namespace Tests { public override void Update(double dt) { - order.Add(this); + queue.Enqueue(this); } } @@ -240,7 +241,7 @@ namespace Tests { public override void Update(double dt) { - order.Add(this); + queue.Enqueue(this); } } @@ -249,7 +250,7 @@ namespace Tests { public override void Update(double dt) { - order.Add(this); + queue.Enqueue(this); } } @@ -269,6 +270,15 @@ namespace Tests world.Update(0.01f); + var order = new List(); + + 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(engineB), Is.LessThan(order.IndexOf(engineC))); Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD))); diff --git a/test/test.csproj b/test/test.csproj index f525fa9..2f91937 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -1,18 +1,17 @@ - - - - netcoreapp2.1 - false - Tests - EncompassECS.Framework.Tests - - - - - - - - - - + + + netcoreapp2.1 + false + Tests + EncompassECS.Framework.Tests + + + + + + + + + + \ No newline at end of file