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 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; } }
@ -41,6 +42,7 @@ namespace Encompass
{
_vertices.Add(vertex);
_neighbors.Add(vertex, new HashSet<T>());
_reverseNeighbors.Add(vertex, new HashSet<T>());
}
}
@ -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<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>();
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;
}

View File

@ -1,24 +1,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Encompass
{
public class World
{
private readonly List<Engine> enginesInOrder;
private readonly ILookup<int, Engine> engineGroups;
private readonly EntityManager entityManager;
private readonly ComponentManager componentManager;
private readonly MessageManager messageManager;
private readonly RenderManager renderManager;
internal World(
List<Engine> enginesInOrder,
ILookup<int, Engine> 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();

View File

@ -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<Engine> engines = new List<Engine>();
@ -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<Engine>();
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,

View File

@ -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<MockComponent>(entity));
hasComponentResult = HasComponent<MockComponent>(entity);
}
}
@ -152,6 +153,8 @@ namespace Tests
var world = worldBuilder.Build();
world.Update(0.01);
Assert.IsTrue(hasComponentResult);
}
[Receives(typeof(EntityMessage))]

View File

@ -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]

View File

@ -191,6 +191,8 @@ namespace Tests
var world = worldBuilder.Build();
Action updateWorld = () => world.Update(0.01);
var ex = Assert.Throws<IllegalUpdateException>(() => world.Update(0.01f));
Assert.That(ex.Message, Is.EqualTo("Engine UndeclaredUpdateComponentTestEngine tried to update undeclared Component MockComponent"));
}

View File

@ -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<SpawnMessageA>
public class TestSpawner : Spawner<SpawnMessageA>
{
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);
}
}
}

View File

@ -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<Engine> order = new List<Engine>();
static ConcurrentQueue<Engine> queue = new ConcurrentQueue<Engine>();
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<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(engineB), Is.LessThan(order.IndexOf(engineC)));
Assert.That(order.IndexOf(engineC), Is.LessThan(order.IndexOf(engineD)));

View File

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