diff --git a/.drone.yml b/.drone.yml index 7d02a31..21f04c9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,18 +7,18 @@ steps: - name: test - image: mcr.microsoft.com/dotnet/core/sdk:3.1 + image: mcr.microsoft.com/dotnet/sdk:5.0.102-ca-patch-buster-slim commands: - dotnet build -c Release - dotnet test -c Release - name: deploy - image: mcr.microsoft.com/dotnet/core/sdk:3.1 + image: mcr.microsoft.com/dotnet/sdk:5.0.102-ca-patch-buster-slim environment: API_KEY: from_secret: API_KEY commands: - - dotnet nuget push /build/encompass-cs/bin/Release/EncompassECS.Framework.*.nupkg -s https://api.nuget.org/v3/index.json -k $API_KEY + - dotnet nuget push /build/encompass-cs/bin/Release/EncompassECS.Framework.*.nupkg -s https://api.nuget.org/v3/index.json -k $API_KEY when: ref: - refs/tags/*.*.* diff --git a/TODO b/TODO deleted file mode 100644 index d96f6e9..0000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -- immutable component system? - -- method to remove all components of a type without destroying Entities -- method to remove a component of a type without destroying entity - -- look at test coverage -- docs diff --git a/encompass-cs/Collections/BitSet512.cs b/encompass-cs/Collections/BitSet512.cs new file mode 100644 index 0000000..1c67de7 --- /dev/null +++ b/encompass-cs/Collections/BitSet512.cs @@ -0,0 +1,156 @@ +using System; + +namespace Encompass +{ + public static unsafe class MemoryHelper + { + public static void Copy(uint* src, uint* dest, int count) + { + for (; count != 0; count--) *dest++ = *src++; + } + + public static void Fill(uint* p, int count, uint value) + { + for (; count != 0; count--) *p++ = value; + } + + public static void And(uint* p, uint* q, uint* result, int count) + { + for (; count != 0; count--) *result++ = *p++ & *q++; + } + + public static void Or(uint* p, uint* q, uint* result, int count) + { + for (; count != 0; count--) *result++ = *p++ | *q++; + } + + public static void Not(uint* p, uint* result, int count) + { + for (; count != 0; count--) *result++ = ~*p++; + } + + public static bool Equal(uint* p, uint* q, int count) + { + for (; count != 0; count--) if (*p++ != *q++) { return false; } + return true; + } + } + + public unsafe struct BitSet512 : IEquatable + { + public static BitSet512 Zero { get; } = new BitSet512(0); + public static BitSet512 Ones { get; } = new BitSet512(uint.MaxValue); + + private const int _uintLength = 16; + + private fixed uint _buffer[_uintLength]; + + public BitSet512(uint value) + { + fixed (uint* p = _buffer) MemoryHelper.Fill(p, _uintLength, value); + } + + public BitSet512(uint* src) + { + fixed (uint* dest = _buffer) MemoryHelper.Copy(src, dest, _uintLength); + } + + public static BitSet512 operator &(BitSet512 a, BitSet512 b) + { + var tmp = stackalloc uint[_uintLength]; + MemoryHelper.And(a._buffer, b._buffer, tmp, _uintLength); + return new BitSet512(tmp); + } + + public static BitSet512 operator |(BitSet512 a, BitSet512 b) + { + var tmp = stackalloc uint[_uintLength]; + MemoryHelper.Or(a._buffer, b._buffer, tmp, _uintLength); + return new BitSet512(tmp); + } + + public static BitSet512 operator ~(BitSet512 a) + { + var tmp = stackalloc uint[_uintLength]; + MemoryHelper.Not(a._buffer, tmp, _uintLength); + return new BitSet512(tmp); + } + + public static bool operator ==(BitSet512 left, BitSet512 right) + { + return left.Equals(right); + } + + public static bool operator !=(BitSet512 left, BitSet512 right) + { + return !(left == right); + } + + public BitSet512 Set(int index) + { + var tmp = stackalloc uint[_uintLength]; + fixed (uint* p = _buffer) MemoryHelper.Copy(p, tmp, _uintLength); + tmp[index / 32] |= (uint)(1 << index % 32); + return new BitSet512(tmp); + } + + public BitSet512 UnSet(int index) + { + var tmp = stackalloc uint[_uintLength]; + fixed (uint* p = _buffer) MemoryHelper.Copy(p, tmp, _uintLength); + tmp[index / 32] &= ~(uint)(1 << index % 32); + return new BitSet512(tmp); + } + + public bool Get(int bitIndex) + { + var bitInt = (uint)(1 << bitIndex % 32); + return (_buffer[bitIndex / 32] & bitInt) == bitInt; + } + + public bool AllTrue() + { + return this == Ones; + } + + public bool AllFalse() + { + return this == Zero; + } + + public static BitSet512 BitwiseAnd(BitSet512 left, BitSet512 right) + { + return left & right; + } + + public static BitSet512 BitwiseOr(BitSet512 left, BitSet512 right) + { + return left | right; + } + + public static BitSet512 OnesComplement(BitSet512 bitSet) + { + return ~bitSet; + } + + public override bool Equals(object obj) + { + return obj is BitSet512 set && Equals(set); + } + + public bool Equals(BitSet512 other) + { + fixed (uint* p = _buffer) return MemoryHelper.Equal(p, other._buffer, _uintLength); + } + + public override int GetHashCode() + { + var hc = 0; + for (var i = 0; i < _uintLength; i++) + { + hc ^= _buffer[i].GetHashCode(); + } + return hc; + } + } +} diff --git a/encompass-cs/Collections/ComponentBitSet.cs b/encompass-cs/Collections/ComponentBitSet.cs index 2867686..47364fc 100644 --- a/encompass-cs/Collections/ComponentBitSet.cs +++ b/encompass-cs/Collections/ComponentBitSet.cs @@ -1,4 +1,3 @@ -using MoonTools.FastCollections; using System; using System.Collections.Generic; diff --git a/encompass-cs/Collections/ComponentStore.cs b/encompass-cs/Collections/ComponentStore.cs index 99b85d3..3aa9c90 100644 --- a/encompass-cs/Collections/ComponentStore.cs +++ b/encompass-cs/Collections/ComponentStore.cs @@ -1,5 +1,4 @@ using Encompass.Exceptions; -using MoonTools.FastCollections; using System; using System.Collections.Generic; diff --git a/encompass-cs/Engine.cs b/encompass-cs/Engine.cs index 46abe4d..b777a0e 100644 --- a/encompass-cs/Engine.cs +++ b/encompass-cs/Engine.cs @@ -3,7 +3,6 @@ using System.Reflection; using System.Collections.Generic; using System.Linq; using Encompass.Exceptions; -using MoonTools.FastCollections; namespace Encompass { diff --git a/encompass-cs/EntitySetQuery.cs b/encompass-cs/EntitySetQuery.cs index 978a13b..3c20d2a 100644 --- a/encompass-cs/EntitySetQuery.cs +++ b/encompass-cs/EntitySetQuery.cs @@ -1,6 +1,4 @@ -using MoonTools.FastCollections; - -namespace Encompass +namespace Encompass { internal struct EntitySetQuery { diff --git a/encompass-cs/Graph/DirectedGraph.cs b/encompass-cs/Graph/DirectedGraph.cs new file mode 100644 index 0000000..1093278 --- /dev/null +++ b/encompass-cs/Graph/DirectedGraph.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + internal class DirectedGraph where TNode : IEquatable + { + protected HashSet nodes = new HashSet(); + protected Dictionary> neighbors = new Dictionary>(); + + protected HashSet<(TNode, TNode)> edges = new HashSet<(TNode, TNode)>(); + + public IEnumerable Nodes => nodes; + public IEnumerable<(TNode, TNode)> Edges => edges; + + public void AddNode(TNode node) + { + if (!Exists(node)) + { + nodes.Add(node); + neighbors.Add(node, new HashSet()); + } + } + + public void AddNodes(params TNode[] nodes) + { + foreach (var node in nodes) + { + AddNode(node); + } + } + + public void RemoveNode(TNode node) + { + CheckNodes(node); + + var edgesToRemove = new List<(TNode, TNode)>(); + + foreach (var entry in neighbors) + { + if (entry.Value.Contains(node)) + { + edgesToRemove.Add((entry.Key, node)); + } + } + + foreach (var edge in edgesToRemove) + { + RemoveEdge(edge.Item1, edge.Item2); + } + + nodes.Remove(node); + neighbors.Remove(node); + } + + public void RemoveEdge(TNode v, TNode u) + { + CheckEdge(v, u); + neighbors[v].Remove(u); + edges.Remove((v, u)); + } + + public void AddEdge(TNode v, TNode u) + { + CheckNodes(v, u); + if (Exists(v, u)) { throw new ArgumentException($"Edge between {v} and {u} already exists in the graph"); } + + if (v.Equals(u)) { throw new ArgumentException("Self-edges are not allowed in a simple graph. Use a multigraph instead"); } + + neighbors[v].Add(u); + edges.Add((v, u)); + } + + public bool Exists(TNode node) + { + return nodes.Contains(node); + } + + public bool Exists(TNode v, TNode u) + { + CheckNodes(v, u); + return edges.Contains((v, u)); + } + + protected void CheckNodes(params TNode[] givenNodes) + { + foreach (var node in givenNodes) + { + if (!Exists(node)) + { + throw new System.ArgumentException($"Vertex {node} does not exist in the graph"); + } + } + } + + protected void CheckEdge(TNode v, TNode u) + { + CheckNodes(v, u); + if (!Exists(v, u)) { throw new ArgumentException($"Edge between vertex {v} and vertex {u} does not exist in the graph"); } + } + + public IEnumerable Neighbors(TNode node) + { + CheckNodes(node); + return neighbors[node]; + } + + public DirectedGraph Clone() + { + var clone = new DirectedGraph(); + clone.AddNodes(Nodes.ToArray()); + + foreach (var v in Nodes) + { + foreach (var n in Neighbors(v)) + { + clone.AddEdge(v, n); + } + } + + return clone; + } + + public DirectedGraph SubGraph(params TNode[] subVertices) + { + var subGraph = new DirectedGraph(); + subGraph.AddNodes(subVertices.ToArray()); + + foreach (var n in Nodes) + { + if (Nodes.Contains(n)) + { + var neighbors = Neighbors(n); + foreach (var u in neighbors) + { + if (subVertices.Contains(u)) + { + subGraph.AddEdge(n, u); + } + } + } + } + + return subGraph; + } + + private IEnumerable PostorderNodeDFSHelper(HashSet discovered, TNode v) + { + discovered.Add(v); + + foreach (var neighbor in Neighbors(v)) + { + if (!discovered.Contains(neighbor)) + { + foreach (var node in PostorderNodeDFSHelper(discovered, neighbor)) + { + yield return node; + } + } + } + + yield return v; + } + + protected IEnumerable PostorderNodeDFS() + { + var dfsDiscovered = new HashSet(); + + foreach (var node in Nodes) + { + if (!dfsDiscovered.Contains(node)) + { + foreach (var thing in PostorderNodeDFSHelper(dfsDiscovered, node)) + { + yield return thing; + } + } + } + } + + public IEnumerable TopologicalSort() + { + return PostorderNodeDFS().Reverse(); + } + + public bool Cyclic() + { + return StronglyConnectedComponents().Any((scc) => scc.Count() > 1); + } + + public IEnumerable> SimpleCycles() + { + void unblock(TNode thisnode, HashSet blocked, Dictionary> B) //refactor to remove closure + { + var stack = new Stack(); + stack.Push(thisnode); + + while (stack.Count > 0) + { + var node = stack.Pop(); + if (blocked.Contains(thisnode)) + { + blocked.Remove(thisnode); + if (B.ContainsKey(node)) + { + foreach (var n in B[node]) + { + if (!stack.Contains(n)) + { + stack.Push(n); + } + } + B[node].Clear(); + } + } + } + } + + List> result = new List>(); + var subGraph = Clone(); + + var sccs = new Stack>(); + foreach (var scc in StronglyConnectedComponents()) + { + sccs.Push(scc); + } + + while (sccs.Count > 0) + { + var scc = new Stack(sccs.Pop()); + var startNode = scc.Pop(); + var path = new Stack(); + path.Push(startNode); + var blocked = new HashSet + { + startNode + }; + var closed = new HashSet(); + var B = new Dictionary>(); + var stack = new Stack<(TNode, Stack)>(); + stack.Push((startNode, new Stack(subGraph.Neighbors(startNode)))); + + while (stack.Count > 0) + { + var entry = stack.Peek(); + var thisnode = entry.Item1; + var neighbors = entry.Item2; + + if (neighbors.Count > 0) + { + var nextNode = neighbors.Pop(); + + if (nextNode.Equals(startNode)) + { + var resultPath = new List(); + foreach (var v in path) + { + resultPath.Add(v); + } + result.Add(resultPath); + foreach (var v in path) + { + closed.Add(v); + } + } + else if (!blocked.Contains(nextNode)) + { + path.Push(nextNode); + stack.Push((nextNode, new Stack(subGraph.Neighbors(nextNode)))); + closed.Remove(nextNode); + blocked.Add(nextNode); + continue; + } + } + + if (neighbors.Count == 0) + { + if (closed.Contains(thisnode)) + { + unblock(thisnode, blocked, B); + } + else + { + foreach (var neighbor in subGraph.Neighbors(thisnode)) + { + if (!B.ContainsKey(neighbor)) + { + B[neighbor] = new HashSet(); + } + B[neighbor].Add(thisnode); + } + } + + stack.Pop(); + path.Pop(); + } + } + + subGraph.RemoveNode(startNode); + var H = subGraph.SubGraph(scc.ToArray()); + var HSccs = H.StronglyConnectedComponents(); + foreach (var HScc in HSccs) + { + sccs.Push(HScc); + } + } + + return result.Distinct(new SimpleCycleComparer()); + } + + protected IEnumerable> StronglyConnectedComponents() + { + var preorder = new Dictionary(); + var lowlink = new Dictionary(); + var sccFound = new Dictionary(); + var sccQueue = new Stack(); + + uint preorderCounter = 0; + + foreach (var source in Nodes) + { + if (!sccFound.ContainsKey(source)) + { + var queue = new Stack(); + queue.Push(source); + + while (queue.Count > 0) + { + var v = queue.Peek(); + if (!preorder.ContainsKey(v)) + { + preorderCounter++; + preorder[v] = preorderCounter; + } + + var done = true; + var vNeighbors = Neighbors(v); + foreach (var w in vNeighbors) + { + if (!preorder.ContainsKey(w)) + { + queue.Push(w); + done = false; + break; + } + } + + if (done) + { + lowlink[v] = preorder[v]; + foreach (var w in vNeighbors) + { + if (!sccFound.ContainsKey(w)) + { + if (preorder[w] > preorder[v]) + { + lowlink[v] = Math.Min(lowlink[v], lowlink[w]); + } + else + { + lowlink[v] = Math.Min(lowlink[v], preorder[w]); + } + } + } + queue.Pop(); + if (lowlink[v] == preorder[v]) + { + sccFound[v] = true; + var scc = new List() { v }; + while (sccQueue.Count > 0 && preorder[sccQueue.Peek()] > preorder[v]) + { + var k = sccQueue.Pop(); + sccFound[k] = true; + scc.Add(k); + } + + yield return scc; + } + else + { + sccQueue.Push(v); + } + } + } + } + } + } + } +} diff --git a/encompass-cs/Graph/SimpleCycleComparer.cs b/encompass-cs/Graph/SimpleCycleComparer.cs new file mode 100644 index 0000000..f4c1ad0 --- /dev/null +++ b/encompass-cs/Graph/SimpleCycleComparer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Encompass +{ + internal class SimpleCycleComparer : IEqualityComparer> + { + public bool Equals(IEnumerable x, IEnumerable y) + { + return x.SequenceEqual(y); + } + + public int GetHashCode(IEnumerable obj) + { + return obj.Aggregate(0, (current, next) => current.GetHashCode() ^ next.GetHashCode()); + } + } +} diff --git a/encompass-cs/WorldBuilder.cs b/encompass-cs/WorldBuilder.cs index 2fe307e..efaf698 100644 --- a/encompass-cs/WorldBuilder.cs +++ b/encompass-cs/WorldBuilder.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Reflection; using System.Linq; using Encompass.Exceptions; -using MoonTools.Core.Graph; -using MoonTools.Core.Graph.Extensions; namespace Encompass { @@ -20,7 +18,7 @@ namespace Encompass { private readonly int _entityCapacity; private readonly List _engines = new List(); - private readonly DirectedGraph _engineGraph = GraphBuilder.DirectedGraph(); + private readonly DirectedGraph _engineGraph = new DirectedGraph(); private readonly ComponentStore _startingExistingComponentStore; private readonly ComponentStore _startingUpToDateComponentStore; @@ -130,16 +128,29 @@ namespace Encompass var messageReceiveTypes = engine.ReceiveTypes; var messageSendTypes = engine.SendTypes; - RegisterMessageTypes(engine.ReceiveTypes.Union(engine.SendTypes)); + RegisterMessageTypes(engine.ReceiveTypes); + RegisterMessageTypes(engine.SendTypes); - foreach (var writeImmediateType in engine.WriteImmediateTypes.Intersect(engine.ReadImmediateTypes)) + foreach (var writeImmediateType in engine.WriteImmediateTypes) { - throw new EngineSelfCycleException("Engine {0} both writes and reads immediate Component {1}", engine.GetType().Name, writeImmediateType.Name); + foreach (var readImmediateType in engine.ReadImmediateTypes) + { + if (readImmediateType == writeImmediateType) + { + throw new EngineSelfCycleException("Engine {0} both writes and reads immediate Component {1}", engine.GetType().Name, writeImmediateType.Name); + } + } } - foreach (var messageType in messageReceiveTypes.Intersect(messageSendTypes)) + foreach (var messageReceiveType in messageReceiveTypes) { - throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageType.Name); + foreach (var messageSendType in messageSendTypes) + { + if (messageReceiveType == messageSendType) + { + throw new EngineSelfCycleException("Engine {0} both receives and sends Message {1}", engine.GetType().Name, messageReceiveType.Name); + } + } } if (messageSendTypes.Count > 0 || engine.WriteImmediateTypes.Count > 0) @@ -147,7 +158,7 @@ namespace Encompass _senders.Add(engine); } - foreach (var componentType in engine.QueryWithTypes.Union(engine.QueryWithoutTypes)) + foreach (var componentType in engine.QueryWithTypes) { _trackingManager.RegisterComponentTypeToEngine(componentType, engine); if (engine.ReadImmediateTypes.Contains(componentType)) @@ -156,7 +167,26 @@ namespace Encompass } } - foreach (var receiveType in engine.ReceiveTypes.Union(engine.ReadImmediateTypes)) + foreach (var componentType in engine.QueryWithoutTypes) + { + _trackingManager.RegisterComponentTypeToEngine(componentType, engine); + if (engine.ReadImmediateTypes.Contains(componentType)) + { + _trackingManager.RegisterImmediateComponentTypeToEngine(componentType, engine); + } + } + + foreach (var receiveType in engine.ReceiveTypes) + { + if (!_typeToReaders.ContainsKey(receiveType)) + { + _typeToReaders.Add(receiveType, new HashSet()); + } + + _typeToReaders[receiveType].Add(engine); + } + + foreach (var receiveType in engine.ReadImmediateTypes) { if (!_typeToReaders.ContainsKey(receiveType)) { @@ -214,7 +244,24 @@ namespace Encompass { foreach (var senderEngine in _senders) { - foreach (var messageType in senderEngine.SendTypes.Union(senderEngine.WriteImmediateTypes)) + foreach (var messageType in senderEngine.SendTypes) + { + if (_typeToReaders.ContainsKey(messageType)) + { + foreach (var readerEngine in _typeToReaders[messageType]) + { + if (senderEngine != readerEngine) + { + if (!_engineGraph.Exists(senderEngine, readerEngine)) + { + _engineGraph.AddEdge(senderEngine, readerEngine); + } + } + } + } + } + + foreach (var messageType in senderEngine.WriteImmediateTypes) { if (_typeToReaders.ContainsKey(messageType)) { diff --git a/encompass-cs/encompass-cs.csproj b/encompass-cs/encompass-cs.csproj index dc9a6a0..a8b4b4d 100644 --- a/encompass-cs/encompass-cs.csproj +++ b/encompass-cs/encompass-cs.csproj @@ -25,8 +25,6 @@ - - diff --git a/test/test.csproj b/test/test.csproj index 9479dc3..f4c53aa 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -1,7 +1,7 @@  - netcoreapp3.0 + net5.0 false Tests EncompassECS.Framework.Tests