From fd43036b2baa661bd6c00e7ef60f512e3c340aad Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 25 Jul 2019 20:01:21 -0700 Subject: [PATCH 01/11] single source longest path --- encompass-cs/Graph/DirectedGraph.cs | 30 ++++- test/DirectedGraphTest.cs | 199 ++++++++++++++++------------ 2 files changed, 141 insertions(+), 88 deletions(-) diff --git a/encompass-cs/Graph/DirectedGraph.cs b/encompass-cs/Graph/DirectedGraph.cs index b60db0d..c8620f2 100644 --- a/encompass-cs/Graph/DirectedGraph.cs +++ b/encompass-cs/Graph/DirectedGraph.cs @@ -89,7 +89,7 @@ namespace Encompass } } - public void AddEdges(params Tuple[] edges) + public void AddEdges(params ValueTuple[] edges) { foreach (var edge in edges) { @@ -174,6 +174,34 @@ namespace Encompass return priority.Values.Reverse(); } + public IEnumerable> LongestPaths(T source) + { + var topoSort = TopologicalSort(); + + var distances = new Dictionary(); + foreach (var node in Vertices) + { + distances[node] = int.MaxValue; + } + distances[source] = 0; + + foreach (var node in topoSort) + { + if (distances[node] != int.MaxValue) + { + foreach (var neighbor in Neighbors(node)) + { + if (distances[neighbor] > distances[node] + 1) + { + distances[neighbor] = distances[node] + 1; + } + } + } + } + + return distances.Select((pair) => (pair.Key, pair.Value)); + } + public IEnumerable> StronglyConnectedComponents() { var preorder = new Dictionary(); diff --git a/test/DirectedGraphTest.cs b/test/DirectedGraphTest.cs index 2a3731e..b4eca48 100644 --- a/test/DirectedGraphTest.cs +++ b/test/DirectedGraphTest.cs @@ -47,10 +47,10 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 4), - Tuple.Create(3, 4) + (1, 2), + (2, 3), + (2, 4), + (3, 4) ); Assert.That(myGraph.Neighbors(1), Does.Contain(2)); @@ -66,10 +66,10 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 4), - Tuple.Create(3, 4) + (1, 2), + (2, 3), + (2, 4), + (3, 4) ); myGraph.RemoveEdge(2, 3); @@ -84,10 +84,10 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 4), - Tuple.Create(3, 4) + (1, 2), + (2, 3), + (2, 4), + (3, 4) ); myGraph.RemoveVertex(2); @@ -103,9 +103,9 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices('a', 'b', 'c', 'd'); myGraph.AddEdges( - Tuple.Create('a', 'b'), - Tuple.Create('a', 'c'), - Tuple.Create('b', 'd') + ('a', 'b'), + ('a', 'c'), + ('b', 'd') ); var result = myGraph.NodeDFS(); @@ -129,10 +129,10 @@ namespace Tests var simpleGraph = new DirectedGraph(); simpleGraph.AddVertices('a', 'b', 'c', 'd'); simpleGraph.AddEdges( - Tuple.Create('a', 'b'), - Tuple.Create('a', 'c'), - Tuple.Create('b', 'a'), - Tuple.Create('b', 'd') + ('a', 'b'), + ('a', 'c'), + ('b', 'a'), + ('b', 'd') ); Assert.That(simpleGraph.TopologicalSort(), Is.EqualTo(new char[] { 'a', 'c', 'b', 'd' })); @@ -144,14 +144,14 @@ namespace Tests var complexGraph = new DirectedGraph(); complexGraph.AddVertices('a', 'b', 'c', 'd', 'e', 'f', 'g', 't', 'm'); complexGraph.AddEdges( - Tuple.Create('a', 'b'), - Tuple.Create('a', 'c'), - Tuple.Create('a', 'd'), - Tuple.Create('b', 'f'), - Tuple.Create('b', 'g'), - Tuple.Create('c', 'g'), - Tuple.Create('e', 't'), - Tuple.Create('t', 'm') + ('a', 'b'), + ('a', 'c'), + ('a', 'd'), + ('b', 'f'), + ('b', 'g'), + ('c', 'g'), + ('e', 't'), + ('t', 'm') ); Assert.That( @@ -160,16 +160,41 @@ namespace Tests ); } + [Test] + public void LongestPaths() + { + var graph = new DirectedGraph(); + graph.AddVertices('r', 's', 't', 'x', 'y', 'z'); + graph.AddEdges( + ('r', 's'), + ('r', 't'), + ('s', 'x'), + ('s', 't'), + ('t', 'y'), + ('t', 'x'), + ('x', 'y'), + ('y', 'z') + ); + + 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)); + } + [Test] public void StronglyConnectedComponentsSimple() { var simpleGraph = new DirectedGraph(); simpleGraph.AddVertices(1, 2, 3); simpleGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(3, 2), - Tuple.Create(2, 1) + (1, 2), + (2, 3), + (3, 2), + (2, 1) ); var result = simpleGraph.StronglyConnectedComponents(); @@ -185,12 +210,12 @@ namespace Tests var mediumGraph = new DirectedGraph(); mediumGraph.AddVertices(1, 2, 3, 4); mediumGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(1, 3), - Tuple.Create(1, 4), - Tuple.Create(4, 2), - Tuple.Create(3, 4), - Tuple.Create(2, 3) + (1, 2), + (1, 3), + (1, 4), + (4, 2), + (3, 4), + (2, 3) ); var result = mediumGraph.StronglyConnectedComponents(); @@ -208,18 +233,18 @@ namespace Tests var complexGraph = new DirectedGraph(); complexGraph.AddVertices(1, 2, 3, 4, 5, 6, 7, 8); complexGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 8), - Tuple.Create(3, 4), - Tuple.Create(3, 7), - Tuple.Create(4, 5), - Tuple.Create(5, 3), - Tuple.Create(5, 6), - Tuple.Create(7, 4), - Tuple.Create(7, 6), - Tuple.Create(8, 1), - Tuple.Create(8, 7) + (1, 2), + (2, 3), + (2, 8), + (3, 4), + (3, 7), + (4, 5), + (5, 3), + (5, 6), + (7, 4), + (7, 6), + (8, 1), + (8, 7) ); var result = complexGraph.StronglyConnectedComponents(); @@ -239,11 +264,11 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 1), - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 1), - Tuple.Create(3, 4) + (1, 1), + (1, 2), + (2, 3), + (2, 1), + (3, 4) ); var clone = myGraph.Clone(); @@ -260,11 +285,11 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 1), - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(2, 1), - Tuple.Create(3, 4) + (1, 1), + (1, 2), + (2, 3), + (2, 1), + (3, 4) ); var subGraph = myGraph.SubGraph(1, 2, 3); @@ -280,13 +305,13 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(0, 1, 2); myGraph.AddEdges( - Tuple.Create(0, 0), - Tuple.Create(0, 1), - Tuple.Create(0, 2), - Tuple.Create(1, 2), - Tuple.Create(2, 0), - Tuple.Create(2, 1), - Tuple.Create(2, 2) + (0, 0), + (0, 1), + (0, 2), + (1, 2), + (2, 0), + (2, 1), + (2, 2) ); var result = myGraph.SimpleCycles(); @@ -311,20 +336,20 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); myGraph.AddEdges( - Tuple.Create(0, 1), - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(3, 0), - Tuple.Create(0, 3), - Tuple.Create(3, 4), - Tuple.Create(4, 5), - Tuple.Create(5, 0), - Tuple.Create(0, 1), - Tuple.Create(1, 6), - Tuple.Create(6, 7), - Tuple.Create(7, 8), - Tuple.Create(8, 0), - Tuple.Create(8, 9) + (0, 1), + (1, 2), + (2, 3), + (3, 0), + (0, 3), + (3, 4), + (4, 5), + (5, 0), + (0, 1), + (1, 6), + (6, 7), + (7, 8), + (8, 0), + (8, 9) ); var result = myGraph.SimpleCycles(); @@ -348,10 +373,10 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(3, 1), - Tuple.Create(3, 4) + (1, 2), + (2, 3), + (3, 1), + (3, 4) ); Assert.That(myGraph.Cyclic(), Is.True); @@ -363,9 +388,9 @@ namespace Tests var myGraph = new DirectedGraph(); myGraph.AddVertices(1, 2, 3, 4); myGraph.AddEdges( - Tuple.Create(1, 2), - Tuple.Create(2, 3), - Tuple.Create(3, 4) + (1, 2), + (2, 3), + (3, 4) ); Assert.That(myGraph.Cyclic(), Is.False); From 61057f1f44a74cb087f77497f65397795087d94e Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Fri, 26 Jul 2019 01:17:39 -0700 Subject: [PATCH 02/11] 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 From 9f6352a8f6067c57ae2f015c8d36c121e075ccfd Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 12:42:12 -0700 Subject: [PATCH 03/11] todo note --- TODO | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 6a8a4e6..c5af319 100644 --- a/TODO +++ b/TODO @@ -1,2 +1,4 @@ - look at test coverage -- docs \ No newline at end of file +- docs + +- emit two packages: one for dev, which includes expensive runtime validation checks, and one for release, which disables them \ No newline at end of file From 09dbf073059a6bb4c4054d2740c574a9d6c4c45c Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 13:46:26 -0700 Subject: [PATCH 04/11] unparameterized sendmessage + ParallelUpdate + tests --- encompass-cs/Engine.cs | 13 ++ encompass-cs/MessageManager.cs | 12 ++ encompass-cs/World.cs | 24 ++- encompass-cs/WorldBuilder.cs | 2 +- test/ComponentTest.cs | 126 +++++++++++--- test/EngineTest.cs | 304 ++++++++++++++++++++++++++------- test/EntityRendererTest.cs | 43 ++++- test/GeneralRendererTest.cs | 29 +++- test/SpawnerTest.cs | 14 +- test/WorldBuilderTest.cs | 14 +- test/WorldTest.cs | 17 +- 11 files changed, 478 insertions(+), 120 deletions(-) diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 869b7cc..e624017 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -259,6 +259,19 @@ namespace Encompass SendMessage(componentUpdateMessage); } + // unparameterized version to enable dynamic dispatch + protected void SendMessage(IMessage message) + { + var type = message.GetType(); + + if (!sendTypes.Contains(type) || !type.IsValueType) + { + throw new IllegalSendException("Engine {0} tried to send undeclared Message {1}", GetType().Name, type.Name); + } + + messageManager.AddMessage(message); + } + protected void SendMessage(TMessage message) where TMessage : struct, IMessage { if (!sendTypes.Contains(typeof(TMessage))) diff --git a/encompass-cs/MessageManager.cs b/encompass-cs/MessageManager.cs index b6a758d..e157229 100644 --- a/encompass-cs/MessageManager.cs +++ b/encompass-cs/MessageManager.cs @@ -8,6 +8,18 @@ namespace Encompass { private readonly Dictionary> messageTypeToMessages = new Dictionary>(); + internal void AddMessage(IMessage message) + { + var type = message.GetType(); + + if (!messageTypeToMessages.ContainsKey(type)) + { + messageTypeToMessages.Add(type, new List()); + } + + messageTypeToMessages[type].Add(message); + } + internal void AddMessage(TMessage message) where TMessage : struct, IMessage { if (!messageTypeToMessages.ContainsKey(typeof(TMessage))) diff --git a/encompass-cs/World.cs b/encompass-cs/World.cs index 1979a17..08f95be 100644 --- a/encompass-cs/World.cs +++ b/encompass-cs/World.cs @@ -6,14 +6,14 @@ namespace Encompass { public class World { - private readonly ILookup engineGroups; + private readonly IOrderedEnumerable> engineGroups; private readonly EntityManager entityManager; private readonly ComponentManager componentManager; private readonly MessageManager messageManager; private readonly RenderManager renderManager; internal World( - ILookup engineGroups, + IOrderedEnumerable> engineGroups, EntityManager entityManager, ComponentManager componentManager, MessageManager messageManager, @@ -29,7 +29,20 @@ namespace Encompass public void Update(double dt) { - foreach (var engineGroup in engineGroups.OrderBy(lookup => lookup.Key)) + foreach (var engineGroup in engineGroups) + { + foreach (var engine in engineGroup) + { + engine.Update(dt); + } + } + + PostUpdate(); + } + + public void ParallelUpdate(double dt) + { + foreach (var engineGroup in engineGroups) { try { @@ -48,6 +61,11 @@ namespace Encompass } } + PostUpdate(); + } + + private void PostUpdate() + { messageManager.ClearMessages(); entityManager.DestroyMarkedEntities(); diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 8799d8d..b3daa01 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -264,7 +264,7 @@ namespace Encompass } var world = new World( - engineGraph.LongestPaths(worldEngine).Where(pair => pair.Item2 != 0).ToLookup(pair => pair.Item2, pair => pair.Item1), + engineGraph.LongestPaths(worldEngine).Where(pair => pair.Item2 != 0).ToLookup(pair => pair.Item2, pair => pair.Item1).OrderBy(lookup => lookup.Key), entityManager, componentManager, messageManager, diff --git a/test/ComponentTest.cs b/test/ComponentTest.cs index d80a4a1..1e0c8ff 100644 --- a/test/ComponentTest.cs +++ b/test/ComponentTest.cs @@ -57,8 +57,9 @@ namespace Tests } } - [Test] - public void AddComponent() + [TestCase(true)] + [TestCase(false)] + public void AddComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new AddComponentTestEngine()); @@ -78,7 +79,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } struct AddMockComponentMessage : IMessage @@ -140,8 +148,9 @@ namespace Tests } } - [Test] - public void AddComponentAndReadSameFrame() + [TestCase(true)] + [TestCase(false)] + public void AddComponentAndReadSameFrame(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); @@ -152,7 +161,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsTrue(hasComponentResult); } @@ -199,8 +215,9 @@ namespace Tests } } - [Test] - public void GetComponents() + [TestCase(true)] + [TestCase(false)] + public void GetComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); @@ -237,11 +254,19 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } - [Test] - public void GetComponent() + [TestCase(true)] + [TestCase(false)] + public void GetComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetMockComponentEngine()); @@ -260,7 +285,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.AreEqual((componentID, mockComponent), gottenMockComponentIDPair); } @@ -283,8 +315,9 @@ namespace Tests } } - [Test] - public void HasComponent() + [TestCase(true)] + [TestCase(false)] + public void HasComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new HasComponentTestEngine()); @@ -303,7 +336,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } struct HasComponentWhenInactiveTestMessage : IMessage @@ -324,8 +364,9 @@ namespace Tests } } - [Test] - public void HasComponentWhenInactive() + [TestCase(true)] + [TestCase(false)] + public void HasComponentWhenInactive(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new HasComponentWhenInactiveTestEngine()); @@ -345,7 +386,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } struct RemoveComponentTestMessage : IMessage @@ -409,8 +457,9 @@ namespace Tests } } - [Test] - public void RemoveComponent() + [TestCase(true)] + [TestCase(false)] + public void RemoveComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); @@ -432,7 +481,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } struct ActivateComponentMessage : IMessage @@ -496,8 +552,9 @@ namespace Tests } } - [Test] - public void ActivateComponent() + [TestCase(true)] + [TestCase(false)] + public void ActivateComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); @@ -521,7 +578,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } struct DeactivateComponentMessage : IMessage @@ -572,8 +636,9 @@ namespace Tests } } - [Test] - public void DeactivateComponent() + [TestCase(true)] + [TestCase(false)] + public void DeactivateComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var entity = worldBuilder.CreateEntity(); @@ -595,7 +660,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } } } diff --git a/test/EngineTest.cs b/test/EngineTest.cs index 4b02d14..fc96616 100644 --- a/test/EngineTest.cs +++ b/test/EngineTest.cs @@ -42,8 +42,9 @@ namespace Tests } } - [Test] - public void ReadComponents() + [TestCase(true)] + [TestCase(false)] + public void ReadComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentsTestEngine()); @@ -66,16 +67,23 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); - + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } var resultComponentValues = resultComponents.Select((kv) => kv.Item2); resultComponentValues.Should().Contain(mockComponent); resultComponentValues.Should().Contain(mockComponentB); resultComponents.Should().NotContain((inactiveComponentAID, mockComponent)); } - [Test] - public void ReadComponent() + [TestCase(true)] + [TestCase(false)] + public void ReadComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); @@ -90,13 +98,21 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01f); + } Assert.AreEqual(mockComponent, resultComponent); } - [Test] - public void ReadComponentWhenMultipleComponents() + [TestCase(true)] + [TestCase(false)] + public void ReadComponentWhenMultipleComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadComponentTestEngine()); @@ -115,8 +131,15 @@ namespace Tests worldBuilder.AddComponent(entity, mockComponentB); var world = worldBuilder.Build(); - - Assert.Throws(() => world.Update(0.01f)); + + if (parallelUpdate) + { + Assert.Throws(() => world.ParallelUpdate(0.01)); + } + else + { + Assert.Throws(() => world.Update(0.01f)); + } } [Reads(typeof(MockComponent))] @@ -135,8 +158,9 @@ namespace Tests // this test needs to be improved... - [Test] - public void UpdateComponent() + [TestCase(true)] + [TestCase(false)] + public void UpdateComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); @@ -153,8 +177,16 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + world.Update(0.01); + } Assert.AreEqual(420, resultComponent.myInt); Assert.AreEqual("blaze it", resultComponent.myString); @@ -175,8 +207,9 @@ namespace Tests } } - [Test] - public void UpdateUndeclaredComponent() + [TestCase(true)] + [TestCase(false)] + public void UpdateUndeclaredComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredUpdateComponentTestEngine()); @@ -191,7 +224,14 @@ namespace Tests var world = worldBuilder.Build(); - Action updateWorld = () => world.Update(0.01); + if (parallelUpdate) + { + Action updateWorld = () => world.ParallelUpdate(0.01); + } + else + { + 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")); @@ -223,8 +263,9 @@ namespace Tests } } - [Test] - public void EmitAndReadMessage() + [TestCase(true)] + [TestCase(false)] + public void EmitAndReadMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new MessageEmitEngine()); @@ -232,7 +273,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01f); + } + else + { + world.Update(0.01f); + } Assert.AreEqual(resultMessages.First().myString, "howdy"); } @@ -259,29 +307,46 @@ namespace Tests } } - [Test] - public void ReadMessagesWhenNoneHaveBeenEmitted() + [TestCase(true)] + [TestCase(false)] + public void ReadMessagesWhenNoneHaveBeenEmitted(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadMessagesWhenNoneExistEngine()); var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } emptyReadMessagesResult.Should().BeEmpty(); } - [Test] - public void EmitUndeclaredMessage() + [TestCase(true)] + [TestCase(false)] + public void EmitUndeclaredMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new UndeclaredMessageEmitEngine()); var world = worldBuilder.Build(); - var ex = Assert.Throws(() => world.Update(0.01f)); - Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); + if (parallelUpdate) + { + var ex = Assert.Throws(() => world.ParallelUpdate(0.01)); + Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); + } + else + { + var ex = Assert.Throws(() => world.Update(0.01f)); + Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredMessageEmitEngine tried to send undeclared Message MockMessage")); + } } static bool someTest; @@ -307,8 +372,9 @@ namespace Tests } } - [Test] - public void SomeMessage() + [TestCase(true)] + [TestCase(false)] + public void SomeMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); @@ -316,7 +382,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01f); + } + else + { + world.Update(0.01f); + } Assert.That(someTest, Is.True); } @@ -329,8 +402,9 @@ namespace Tests } } - [Test] - public void UndeclaredSomeMessage() + [TestCase(true)] + [TestCase(false)] + public void UndeclaredSomeMessage(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new EmitMockMessageEngine()); @@ -338,7 +412,14 @@ namespace Tests var world = worldBuilder.Build(); - Assert.Throws(() => world.Update(0.01f)); + if (parallelUpdate) + { + Assert.Throws(() => world.ParallelUpdate(0.01f)); + } + else + { + Assert.Throws(() => world.Update(0.01f)); + } } class SomeComponentTestEngine : Engine @@ -349,8 +430,9 @@ namespace Tests } } - [Test] - public void SomeComponent() + [TestCase(true)] + [TestCase(false)] + public void SomeComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); @@ -359,7 +441,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } } static ValueTuple pairA; @@ -378,8 +467,9 @@ namespace Tests } // Tests that components with identical values should be distinguishable by ID - [Test] - public void SameValueComponents() + [TestCase(true)] + [TestCase(false)] + public void SameValueComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new SameValueComponentReadEngine()); @@ -397,7 +487,15 @@ namespace Tests worldBuilder.AddComponent(entity, componentB); var world = worldBuilder.Build(); - world.Update(0.01f); + + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.That(pairA, Is.Not.EqualTo(pairB)); Assert.That(pairA.Item2, Is.EqualTo(pairB.Item2)); @@ -414,14 +512,23 @@ namespace Tests } } - [Test] - public void ReadComponentsOfTypeWhereNoneExist() + [TestCase(true)] + [TestCase(false)] + public void ReadComponentsOfTypeWhereNoneExist(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new ReadEmptyMockComponentsEngine()); var world = worldBuilder.Build(); - world.Update(0.01f); + + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.That(emptyComponentReadResult, Is.Empty); } @@ -453,8 +560,9 @@ namespace Tests } } - [Test] - public void DestroyEntity() + [TestCase(true)] + [TestCase(false)] + public void DestroyEntity(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyerEngine()); @@ -476,7 +584,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.That(results, Does.Not.Contain((componentID, mockComponent))); Assert.That(results, Does.Not.Contain((componentBID, mockComponent))); @@ -498,8 +613,9 @@ namespace Tests } } - [Test] - public void DestroyEntityWhileRemovingComponent() + [TestCase(true)] + [TestCase(false)] + public void DestroyEntityWhileRemovingComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new DestroyAndAddComponentEngine()); @@ -512,7 +628,14 @@ namespace Tests var world = worldBuilder.Build(); - Assert.DoesNotThrow(() => world.Update(0.01)); + if (parallelUpdate) + { + Assert.DoesNotThrow(() => world.ParallelUpdate(0.01)); + } + else + { + Assert.DoesNotThrow(() => world.Update(0.01)); + } } static Entity entityFromComponentIDResult; @@ -527,8 +650,9 @@ namespace Tests } } - [Test] - public void GetEntityFromComponentID() + [TestCase(true)] + [TestCase(false)] + public void GetEntityFromComponentID(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetEntityFromComponentIDEngine()); @@ -541,7 +665,15 @@ namespace Tests worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); - world.Update(0.01f); + + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.That(entity, Is.EqualTo(entityFromComponentIDResult)); } @@ -557,8 +689,10 @@ namespace Tests mockComponentByIDResult = GetComponentByID(componentID); } } - [Test] - public void GetComponentByID() + + [TestCase(true)] + [TestCase(false)] + public void GetComponentByID(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetComponentByIDEngine()); @@ -571,7 +705,15 @@ namespace Tests worldBuilder.AddComponent(entity, component); var world = worldBuilder.Build(); - world.Update(0.01f); + + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.That(component, Is.EqualTo(mockComponentByIDResult)); } @@ -588,8 +730,9 @@ namespace Tests } } - [Test] - public void GetComponentByIDWithTypeMismatch() + [TestCase(true)] + [TestCase(false)] + public void GetComponentByIDWithTypeMismatch(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new GetComponentByIDWithTypeMismatchEngine()); @@ -603,7 +746,14 @@ namespace Tests var world = worldBuilder.Build(); - Assert.Throws(() => world.Update(0.01f)); + if (parallelUpdate) + { + Assert.Throws(() => world.ParallelUpdate(0.01)); + } + else + { + Assert.Throws(() => world.Update(0.01)); + } } struct EntityIDComponent : IComponent { public Guid entityID; } @@ -622,8 +772,9 @@ namespace Tests } } - [Test] - public void EntityExists() + [TestCase(true)] + [TestCase(false)] + public void EntityExists(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new HasEntityTestEngine()); @@ -638,11 +789,25 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsTrue(hasEntity); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsFalse(hasEntity); } @@ -667,8 +832,9 @@ namespace Tests } } - [Test] - public void EngineUpdatesComponentMultipleTimes() + [TestCase(true)] + [TestCase(false)] + public void EngineUpdatesComponentMultipleTimes(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new RepeatUpdateEngine()); @@ -687,7 +853,15 @@ namespace Tests worldBuilder.SendMessage(mockComponentUpdateMessage); var world = worldBuilder.Build(); - Assert.Throws(() => world.Update(0.01)); + + if (parallelUpdate) + { + Assert.Throws(() => world.ParallelUpdate(0.01)); + } + else + { + Assert.Throws(() => world.Update(0.01)); + } } } } diff --git a/test/EntityRendererTest.cs b/test/EntityRendererTest.cs index b85f09f..53985b4 100644 --- a/test/EntityRendererTest.cs +++ b/test/EntityRendererTest.cs @@ -21,8 +21,9 @@ namespace Tests public override void Render(Entity entity) { } } - [Test] - public void CheckAndTrackEntities() + [TestCase(true)] + [TestCase(false)] + public void CheckAndTrackEntities(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var renderer = worldBuilder.AddEntityRenderer(new TestRenderer()); @@ -46,7 +47,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsTrue(renderer.IsTracking(entityToTrack.ID)); Assert.IsFalse(renderer.IsTracking(entityNotToTrack.ID)); @@ -62,8 +70,9 @@ namespace Tests } } - [Test] - public void InactiveDrawComponent() + [TestCase(true)] + [TestCase(false)] + public void InactiveDrawComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var renderer = worldBuilder.AddEntityRenderer(new TestRenderer()); @@ -81,7 +90,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsFalse(renderer.IsTracking(entity.ID)); @@ -102,8 +118,9 @@ namespace Tests } } - [Test] - public void RenderMethodCalledOnWorldDraw() + [TestCase(true)] + [TestCase(false)] + public void RenderMethodCalledOnWorldDraw(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); var renderer = worldBuilder.AddEntityRenderer(new CalledRenderer()); @@ -119,7 +136,15 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } + world.Draw(); Assert.IsTrue(renderer.IsTracking(entity.ID)); diff --git a/test/GeneralRendererTest.cs b/test/GeneralRendererTest.cs index 497464d..5f80e1c 100644 --- a/test/GeneralRendererTest.cs +++ b/test/GeneralRendererTest.cs @@ -23,8 +23,9 @@ namespace Tests } } - [Test] - public void SingletonComponent() + [TestCase(true)] + [TestCase(false)] + public void SingletonComponent(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddGeneralRenderer(new TestRenderer(), 1); @@ -36,14 +37,23 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } + world.Draw(); Assert.That(result, Is.EqualTo(new ValueTuple(componentID, aComponent))); } - [Test] - public void MultipleComponents() + [TestCase(true)] + [TestCase(false)] + public void MultipleComponents(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddGeneralRenderer(new TestRenderer(), 1); @@ -56,7 +66,14 @@ namespace Tests var componentTwoID = worldBuilder.AddComponent(entity, aComponentTwo); var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.Throws(() => world.Draw()); } diff --git a/test/SpawnerTest.cs b/test/SpawnerTest.cs index 3aa171e..fafaa6e 100644 --- a/test/SpawnerTest.cs +++ b/test/SpawnerTest.cs @@ -37,8 +37,9 @@ namespace Tests void Spawn(SpawnMessageA message); } - [Test] - public void RunsSpawnMethodOnMessageRead() + [TestCase(true)] + [TestCase(false)] + public void RunsSpawnMethodOnMessageRead(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); worldBuilder.AddEngine(new MessageEmitter()); @@ -46,7 +47,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } Assert.IsTrue(spawnResult); } diff --git a/test/WorldBuilderTest.cs b/test/WorldBuilderTest.cs index 4162d45..9d49d3a 100644 --- a/test/WorldBuilderTest.cs +++ b/test/WorldBuilderTest.cs @@ -254,8 +254,9 @@ namespace Tests } } - [Test] - public void EngineOrder() + [TestCase(true)] + [TestCase(false)] + public void EngineOrder(bool parallelUpdate) { var worldBuilder = new WorldBuilder(); @@ -268,7 +269,14 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } var order = new List(); diff --git a/test/WorldTest.cs b/test/WorldTest.cs index fc1b8ff..2aa70ee 100644 --- a/test/WorldTest.cs +++ b/test/WorldTest.cs @@ -33,9 +33,12 @@ namespace Tests } } - [Test] - public void DrawOrder() + [TestCase(true)] + [TestCase(false)] + public void DrawOrder(bool parallelUpdate) { + drawOrder.Clear(); + var worldBuilder = new WorldBuilder(); worldBuilder.AddEntityRenderer(new TestEntityRenderer()); var testGeneralRenderer = worldBuilder.AddGeneralRenderer(new TestGeneralRenderer(), 7); @@ -67,7 +70,15 @@ namespace Tests var world = worldBuilder.Build(); - world.Update(0.01f); + if (parallelUpdate) + { + world.ParallelUpdate(0.01); + } + else + { + world.Update(0.01); + } + world.Draw(); drawOrder.Should().BeEquivalentTo(entityFour, entityTwo, entity, entityThree, testGeneralRenderer); From 36621c2fe787a6d477a0f68f09b4c88892524bbf Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 13:48:36 -0700 Subject: [PATCH 05/11] 0.12.0-rc1 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index 87fe92a..25f61d1 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -4,7 +4,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.11.0 + 0.12.0-rc1 Evan Hemsley true Moonside Games From 539d6de247bcdd513478adbd446ea517f7a50ce1 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 19:17:00 -0700 Subject: [PATCH 06/11] register message types at init time instead of runtime --- TODO | 4 +++- encompass-cs/MessageManager.cs | 17 +++++++---------- encompass-cs/WorldBuilder.cs | 5 +++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/TODO b/TODO index c5af319..e24e2c0 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,6 @@ - look at test coverage - docs -- emit two packages: one for dev, which includes expensive runtime validation checks, and one for release, which disables them \ No newline at end of file +- emit two packages: one for dev, which includes expensive runtime validation checks, and one for release, which disables them + +- thread safety diff --git a/encompass-cs/MessageManager.cs b/encompass-cs/MessageManager.cs index e157229..5cadf61 100644 --- a/encompass-cs/MessageManager.cs +++ b/encompass-cs/MessageManager.cs @@ -8,25 +8,22 @@ namespace Encompass { private readonly Dictionary> messageTypeToMessages = new Dictionary>(); + internal void RegisterMessageType(Type messageType) + { + if (!messageTypeToMessages.ContainsKey(messageType)) { + messageTypeToMessages.Add(messageType, new List()); + } + } + internal void AddMessage(IMessage message) { var type = message.GetType(); - if (!messageTypeToMessages.ContainsKey(type)) - { - messageTypeToMessages.Add(type, new List()); - } - messageTypeToMessages[type].Add(message); } internal void AddMessage(TMessage message) where TMessage : struct, IMessage { - if (!messageTypeToMessages.ContainsKey(typeof(TMessage))) - { - messageTypeToMessages.Add(typeof(TMessage), new List()); - } - messageTypeToMessages[typeof(TMessage)].Add(message); } diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index b3daa01..eea0982 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -91,6 +91,11 @@ namespace Encompass var messageReceiveTypes = engine.receiveTypes; var messageSendTypes = engine.sendTypes; + foreach (var messageType in messageReceiveTypes.Union(messageSendTypes)) + { + messageManager.RegisterMessageType(messageType); + } + foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes)) { if ((messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(PendingComponentMessage<>))) From f3a7331ae90891ce1097ce5dcd761833a1944342 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 19:28:47 -0700 Subject: [PATCH 07/11] thread safe message manager --- encompass-cs/MessageManager.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/encompass-cs/MessageManager.cs b/encompass-cs/MessageManager.cs index 5cadf61..f143946 100644 --- a/encompass-cs/MessageManager.cs +++ b/encompass-cs/MessageManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System; using System.Collections.Generic; using System.Linq; @@ -6,12 +7,12 @@ namespace Encompass { internal class MessageManager { - private readonly Dictionary> messageTypeToMessages = new Dictionary>(); + private readonly Dictionary> messageTypeToMessages = new Dictionary>(); internal void RegisterMessageType(Type messageType) { if (!messageTypeToMessages.ContainsKey(messageType)) { - messageTypeToMessages.Add(messageType, new List()); + messageTypeToMessages.Add(messageType, new ConcurrentStack()); } } @@ -19,12 +20,12 @@ namespace Encompass { var type = message.GetType(); - messageTypeToMessages[type].Add(message); + messageTypeToMessages[type].Push(message); } internal void AddMessage(TMessage message) where TMessage : struct, IMessage { - messageTypeToMessages[typeof(TMessage)].Add(message); + messageTypeToMessages[typeof(TMessage)].Push(message); } internal void ClearMessages() From 6aca66861959ca00034311508741732dcc75f3ad Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 19:54:15 -0700 Subject: [PATCH 08/11] thread safe data structures --- TODO | 1 + encompass-cs/ComponentManager.cs | 92 ++++++++++--------- .../Graph/DirectedGraph.cs | 0 encompass-cs/EntityManager.cs | 21 +++-- encompass-cs/WorldBuilder.cs | 5 +- 5 files changed, 62 insertions(+), 57 deletions(-) rename encompass-cs/{ => DataStructures}/Graph/DirectedGraph.cs (100%) diff --git a/TODO b/TODO index e24e2c0..30fab52 100644 --- a/TODO +++ b/TODO @@ -4,3 +4,4 @@ - emit two packages: one for dev, which includes expensive runtime validation checks, and one for release, which disables them - thread safety +- create ConcurrentHashSet based on ConcurrentDictionary \ No newline at end of file diff --git a/encompass-cs/ComponentManager.cs b/encompass-cs/ComponentManager.cs index e137355..90576ae 100644 --- a/encompass-cs/ComponentManager.cs +++ b/encompass-cs/ComponentManager.cs @@ -1,38 +1,40 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; +// we use byte as a dummy argument to ConcurrentDictionary so we can treat it like a HashSet + namespace Encompass { internal class ComponentManager { private readonly DrawLayerManager drawLayerManager; - private readonly Dictionary componentIDToType = new Dictionary(); - private readonly Dictionary IDToComponent = new Dictionary(); - private readonly Dictionary> entityIDToComponentIDs = new Dictionary>(); - private readonly Dictionary componentIDToEntityID = new Dictionary(); + private readonly ConcurrentDictionary componentIDToType = new ConcurrentDictionary(); + private readonly ConcurrentDictionary IDToComponent = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> entityIDToComponentIDs = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary componentIDToEntityID = new ConcurrentDictionary(); - private readonly Dictionary> typeToComponentIDs = new Dictionary>(); + private readonly ConcurrentDictionary> typeToComponentIDs = new ConcurrentDictionary>(); - private readonly List activeComponents = new List(); - private readonly List inactiveComponents = new List(); + private readonly ConcurrentDictionary activeComponents = new ConcurrentDictionary(); + private readonly ConcurrentDictionary inactiveComponents = new ConcurrentDictionary(); - private readonly HashSet componentsMarkedForActivation = new HashSet(); - private readonly HashSet componentsMarkedForDeactivation = new HashSet(); - private readonly HashSet componentsMarkedForRemoval = new HashSet(); + private readonly ConcurrentDictionary componentsMarkedForDeactivation = new ConcurrentDictionary(); + private readonly ConcurrentDictionary componentsMarkedForRemoval = new ConcurrentDictionary(); - private readonly Dictionary pendingUpdates = new Dictionary(); + private readonly ConcurrentDictionary pendingUpdates = new ConcurrentDictionary(); //shared references with EntityManager - private readonly HashSet entitiesWithAddedComponents; - private readonly HashSet entitiesWithRemovedComponents; + private readonly ConcurrentDictionary entitiesWithAddedComponents; + private readonly ConcurrentDictionary entitiesWithRemovedComponents; public ComponentManager( DrawLayerManager drawLayerManager, - HashSet entitiesWithAddedComponents, - HashSet entitiesWithRemovedComponents + ConcurrentDictionary entitiesWithAddedComponents, + ConcurrentDictionary entitiesWithRemovedComponents ) { this.drawLayerManager = drawLayerManager; @@ -42,7 +44,7 @@ namespace Encompass internal void RegisterEntity(Guid entityID) { - entityIDToComponentIDs.Add(entityID, new HashSet()); + entityIDToComponentIDs.TryAdd(entityID, new ConcurrentDictionary()); } internal Guid NextID() @@ -57,17 +59,17 @@ namespace Encompass if (!typeToComponentIDs.ContainsKey(typeof(TComponent))) { - typeToComponentIDs.Add(typeof(TComponent), new HashSet()); + typeToComponentIDs.TryAdd(typeof(TComponent), new ConcurrentDictionary()); } - typeToComponentIDs[typeof(TComponent)].Add(componentID); + typeToComponentIDs[typeof(TComponent)].TryAdd(componentID, 0); - entityIDToComponentIDs[entity.ID].Add(componentID); + entityIDToComponentIDs[entity.ID].TryAdd(componentID, 0); componentIDToEntityID[componentID] = entity.ID; - activeComponents.Add(componentID); + activeComponents.TryAdd(componentID, 0); - entitiesWithAddedComponents.Add(entity.ID); + entitiesWithAddedComponents.TryAdd(entity.ID, 0); return componentID; } @@ -82,26 +84,26 @@ namespace Encompass internal IEnumerable GetComponentIDsByEntityID(Guid entityID) { return entityIDToComponentIDs.ContainsKey(entityID) ? - entityIDToComponentIDs[entityID] : + entityIDToComponentIDs[entityID].Keys : Enumerable.Empty(); } internal IEnumerable> GetComponentsByEntity(Guid entityID) { - return GetComponentIDsByEntityID(entityID).Intersect(activeComponents).Select((id) => new ValueTuple(id, IDToComponent[id])); + return GetComponentIDsByEntityID(entityID).Intersect(activeComponents.Keys).Select((id) => new ValueTuple(id, IDToComponent[id])); } internal IEnumerable> GetActiveComponentsByType() where TComponent : struct, IComponent { return typeToComponentIDs.ContainsKey(typeof(TComponent)) ? - typeToComponentIDs[typeof(TComponent)].Intersect(activeComponents).Select((id) => new ValueTuple(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) : + typeToComponentIDs[typeof(TComponent)].Keys.Intersect(activeComponents.Keys).Select((id) => new ValueTuple(GetEntityIDByComponentID(id), id, (TComponent)IDToComponent[id])) : Enumerable.Empty>(); } internal IEnumerable> GetActiveComponentsByType(Type type) { return typeToComponentIDs.ContainsKey(type) ? - typeToComponentIDs[type].Intersect(activeComponents).Select((id) => new ValueTuple(id, IDToComponent[id])) : + typeToComponentIDs[type].Keys.Intersect(activeComponents.Keys).Select((id) => new ValueTuple(id, IDToComponent[id])) : Enumerable.Empty>(); } @@ -156,7 +158,7 @@ namespace Encompass throw new RepeatUpdateComponentException("Component {0} with ID {1} was updated multiple times this frame", typeof(TComponent).Name, componentID); } - pendingUpdates.Add(componentID, newComponentValue); + pendingUpdates.TryAdd(componentID, newComponentValue); } internal void PerformComponentUpdates() @@ -179,23 +181,23 @@ namespace Encompass internal void Activate(Guid componentID) { - if (inactiveComponents.Remove(componentID)) + if (inactiveComponents.TryRemove(componentID, out _)) { - activeComponents.Add(componentID); + activeComponents.TryAdd(componentID, 0); } var entityID = GetEntityIDByComponentID(componentID); - entitiesWithAddedComponents.Add(entityID); + entitiesWithAddedComponents.TryAdd(entityID, 0); } internal void MarkForDeactivation(Guid componentID) { - componentsMarkedForDeactivation.Add(componentID); + componentsMarkedForDeactivation.TryAdd(componentID, 0); } internal void DeactivateMarkedComponents() { - foreach (var componentID in componentsMarkedForDeactivation) + foreach (var componentID in componentsMarkedForDeactivation.Keys) { Deactivate(componentID); } @@ -205,23 +207,23 @@ namespace Encompass private void Deactivate(Guid componentID) { - if (activeComponents.Remove(componentID)) + if (activeComponents.TryRemove(componentID, out _)) { - inactiveComponents.Add(componentID); + inactiveComponents.TryAdd(componentID, 0); } var entityID = GetEntityIDByComponentID(componentID); - entitiesWithRemovedComponents.Add(entityID); + entitiesWithRemovedComponents.TryAdd(entityID, 0); } internal void MarkForRemoval(Guid componentID) { - componentsMarkedForRemoval.Add(componentID); + componentsMarkedForRemoval.TryAdd(componentID, 0); } internal void RemoveMarkedComponents() { - foreach (var componentID in componentsMarkedForRemoval) + foreach (var componentID in componentsMarkedForRemoval.Keys) { Remove(componentID); } @@ -234,28 +236,28 @@ namespace Encompass var component = IDToComponent[componentID]; var type = componentIDToType[componentID]; - activeComponents.Remove(componentID); - inactiveComponents.Remove(componentID); + activeComponents.TryRemove(componentID, out _); + inactiveComponents.TryRemove(componentID, out _); var entityID = componentIDToEntityID[componentID]; if (entityIDToComponentIDs.ContainsKey(entityID)) { - entityIDToComponentIDs[entityID].Remove(componentID); + entityIDToComponentIDs[entityID].TryRemove(componentID, out _); } - IDToComponent.Remove(componentID); - componentIDToType.Remove(componentID); - componentIDToEntityID.Remove(componentID); - typeToComponentIDs[type].Remove(componentID); + IDToComponent.TryRemove(componentID, out _); + componentIDToType.TryRemove(componentID, out _); + componentIDToEntityID.TryRemove(componentID, out _); + typeToComponentIDs[type].TryRemove(componentID, out _); drawLayerManager.UnRegisterComponentWithLayer(componentID); - entitiesWithRemovedComponents.Add(entityID); + entitiesWithRemovedComponents.TryAdd(entityID, 0); } public void RegisterDestroyedEntity(Guid entityID) { - entityIDToComponentIDs.Remove(entityID); + entityIDToComponentIDs.TryRemove(entityID, out _); } } } diff --git a/encompass-cs/Graph/DirectedGraph.cs b/encompass-cs/DataStructures/Graph/DirectedGraph.cs similarity index 100% rename from encompass-cs/Graph/DirectedGraph.cs rename to encompass-cs/DataStructures/Graph/DirectedGraph.cs diff --git a/encompass-cs/EntityManager.cs b/encompass-cs/EntityManager.cs index f319b13..e0385a9 100644 --- a/encompass-cs/EntityManager.cs +++ b/encompass-cs/EntityManager.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System; using System.Collections.Generic; @@ -5,22 +6,22 @@ namespace Encompass { internal class EntityManager { - private readonly Dictionary IDToEntity = new Dictionary(); + private readonly ConcurrentDictionary IDToEntity = new ConcurrentDictionary(); private readonly HashSet entitiesMarkedForDestroy = new HashSet(); private readonly Dictionary> componentTypeToEntityTrackers = new Dictionary>(); private readonly Dictionary> entityToEntityTrackers = new Dictionary>(); - private readonly HashSet entitiesWithAddedComponents; - private readonly HashSet entitiesWithRemovedComponents; + private readonly ConcurrentDictionary entitiesWithAddedComponents; + private readonly ConcurrentDictionary entitiesWithRemovedComponents; private readonly ComponentManager componentManager; public EntityManager( ComponentManager componentManager, - HashSet entitiesWithAddedComponents, - HashSet entitiesWithRemovedComponents + ConcurrentDictionary entitiesWithAddedComponents, + ConcurrentDictionary entitiesWithRemovedComponents ) { this.componentManager = componentManager; @@ -57,7 +58,7 @@ namespace Encompass foreach (var entityID in entitiesMarkedForDestroy) { componentManager.MarkAllComponentsOnEntityForRemoval(entityID); - IDToEntity.Remove(entityID); + IDToEntity.TryRemove(entityID, out _); entityToEntityTrackers.Remove(entityID); componentManager.RegisterDestroyedEntity(entityID); } @@ -96,17 +97,17 @@ namespace Encompass public void RegisterDirtyEntityWithAddedComponents(Guid entityID) { - entitiesWithAddedComponents.Add(entityID); + entitiesWithAddedComponents.TryAdd(entityID, 0); } public void RegisterDirtyEntityWithRemovedComponents(Guid entityID) { - entitiesWithRemovedComponents.Add(entityID); + entitiesWithRemovedComponents.TryAdd(entityID, 0); } public void CheckEntitiesWithAddedComponents() { - foreach (var entityID in entitiesWithAddedComponents) + foreach (var entityID in entitiesWithAddedComponents.Keys) { CheckAndRegisterEntity(entityID); } @@ -116,7 +117,7 @@ namespace Encompass public void CheckEntitiesWithRemovedComponents() { - foreach (var entityID in entitiesWithRemovedComponents) + foreach (var entityID in entitiesWithRemovedComponents.Keys) { if (entityToEntityTrackers.ContainsKey(entityID)) { diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index eea0982..7afe52a 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System; using System.Collections.Generic; using System.Reflection; @@ -32,8 +33,8 @@ namespace Encompass public WorldBuilder() { - var entitiesWithAddedComponents = new HashSet(); - var entitiesWithRemovedComponents = new HashSet(); + var entitiesWithAddedComponents = new ConcurrentDictionary(); + var entitiesWithRemovedComponents = new ConcurrentDictionary(); drawLayerManager = new DrawLayerManager(); componentManager = new ComponentManager(drawLayerManager, entitiesWithAddedComponents, entitiesWithRemovedComponents); entityManager = new EntityManager(componentManager, entitiesWithAddedComponents, entitiesWithRemovedComponents); From 0abbd47da38faf7cb8b37076aba2f3ebabfda795 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 19:54:31 -0700 Subject: [PATCH 09/11] 0.12.0-rc2 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index 25f61d1..8746b08 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -4,7 +4,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.12.0-rc1 + 0.12.0-rc2 Evan Hemsley true Moonside Games From df6bd0e394d6d46e8d8cb31cfb3a6d4409ab1eb9 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 20:25:52 -0700 Subject: [PATCH 10/11] thread safe drawlayermanager --- encompass-cs/DrawLayerManager.cs | 49 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/encompass-cs/DrawLayerManager.cs b/encompass-cs/DrawLayerManager.cs index d6082dc..e60fe4e 100644 --- a/encompass-cs/DrawLayerManager.cs +++ b/encompass-cs/DrawLayerManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -6,32 +7,37 @@ namespace Encompass { internal class DrawLayerManager { + private readonly object layerOrderLock = new object(); + private readonly SortedList layerOrder = new SortedList(); - private readonly Dictionary> layerIndexToComponentIDs = new Dictionary>(); - private readonly Dictionary> layerIndexToGeneralRenderers = new Dictionary>(); + private readonly ConcurrentDictionary> layerIndexToComponentIDs = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> layerIndexToGeneralRenderers = new ConcurrentDictionary>(); - private readonly Dictionary componentIDToLayerIndex = new Dictionary(); + private readonly ConcurrentDictionary componentIDToLayerIndex = new ConcurrentDictionary(); - public IEnumerable LayerOrder { get { return layerOrder.Values; } } + public IEnumerable LayerOrder { get { lock(layerOrderLock) { return layerOrder.Values; } } } public void RegisterGeneralRendererWithLayer(GeneralRenderer renderer, int layer) { if (layerIndexToGeneralRenderers.ContainsKey(layer)) { var set = layerIndexToGeneralRenderers[layer]; - set.Add(renderer); + set[renderer] = 0; } else { - var set = new HashSet(); - layerIndexToGeneralRenderers.Add(layer, set); - set.Add(renderer); + var set = new ConcurrentDictionary(); + layerIndexToGeneralRenderers[layer] = set; + set[renderer] = 0; } - if (!layerOrder.ContainsKey(layer)) + lock (layerOrderLock) { - layerOrder.Add(layer, layer); + if (!layerOrder.ContainsKey(layer)) + { + layerOrder.Add(layer, layer); + } } } @@ -39,7 +45,7 @@ namespace Encompass { if (layerIndexToGeneralRenderers.ContainsKey(layer)) { - layerIndexToGeneralRenderers[layer].Remove(renderer); + layerIndexToGeneralRenderers[layer].TryRemove(renderer, out _); } } @@ -54,20 +60,23 @@ namespace Encompass if (layerIndexToComponentIDs.ContainsKey(layer)) { var set = layerIndexToComponentIDs[layer]; - set.Add(id); + set[id] = 0; } else { - var set = new HashSet(); - layerIndexToComponentIDs.Add(layer, set); - set.Add(id); + var set = new ConcurrentDictionary(); + layerIndexToComponentIDs[layer] = set; + set[id] = 0; } componentIDToLayerIndex[id] = layer; - if (!layerOrder.ContainsKey(layer)) + lock (layerOrderLock) { - layerOrder.Add(layer, layer); + if (!layerOrder.ContainsKey(layer)) + { + layerOrder.Add(layer, layer); + } } } @@ -76,7 +85,7 @@ namespace Encompass if (componentIDToLayerIndex.ContainsKey(id)) { var layer = componentIDToLayerIndex[id]; - layerIndexToComponentIDs[layer].Remove(id); + layerIndexToComponentIDs[layer].TryRemove(id, out _); } } @@ -89,14 +98,14 @@ namespace Encompass public IEnumerable ComponentIDsByLayer(int layer) { return layerIndexToComponentIDs.ContainsKey(layer) ? - layerIndexToComponentIDs[layer] : + layerIndexToComponentIDs[layer].Keys : Enumerable.Empty(); } public IEnumerable GeneralRenderersByLayer(int layer) { return layerIndexToGeneralRenderers.ContainsKey(layer) ? - layerIndexToGeneralRenderers[layer] : + layerIndexToGeneralRenderers[layer].Keys : Enumerable.Empty(); } } From 185f35e71c8aa2f2ea277f06a4187fa466ebeee8 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Sun, 28 Jul 2019 20:27:30 -0700 Subject: [PATCH 11/11] 0.12.0-rc3 --- encompass-cs/encompass-cs.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index 8746b08..40f8447 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -4,7 +4,7 @@ netstandard2.0 Encompass EncompassECS.Framework - 0.12.0-rc2 + 0.12.0-rc3 Evan Hemsley true Moonside Games