From 04d4fd2afdccc072f1f7079f4b3d1ed756540880 Mon Sep 17 00:00:00 2001 From: Mikhail <99481254+DCFApixels@users.noreply.github.com> Date: Thu, 22 Jun 2023 03:54:20 +0800 Subject: [PATCH] Update --- src/{ => Common}/EcsRelationWorld.cs | 0 src/Common/IdsBasket.cs | 172 ++++++++++++++++++++++++++ src/Common/RelationTargets.cs | 14 +++ src/Relation.cs | 10 -- src/RelationManager.cs | 178 ++++++++++++++++++++------- src/Utils/Exceptions.cs | 14 +++ src/Utils/IdsBasket.cs | 50 -------- src/Utils/IdsLinkedList.cs | 100 +++++++++------ src/Utils/SparseArray64.cs | 13 +- src/WorldRelationsMatrix.cs | 41 ++++++ 10 files changed, 443 insertions(+), 149 deletions(-) rename src/{ => Common}/EcsRelationWorld.cs (100%) create mode 100644 src/Common/IdsBasket.cs create mode 100644 src/Common/RelationTargets.cs delete mode 100644 src/Relation.cs create mode 100644 src/Utils/Exceptions.cs delete mode 100644 src/Utils/IdsBasket.cs create mode 100644 src/WorldRelationsMatrix.cs diff --git a/src/EcsRelationWorld.cs b/src/Common/EcsRelationWorld.cs similarity index 100% rename from src/EcsRelationWorld.cs rename to src/Common/EcsRelationWorld.cs diff --git a/src/Common/IdsBasket.cs b/src/Common/IdsBasket.cs new file mode 100644 index 0000000..dad77df --- /dev/null +++ b/src/Common/IdsBasket.cs @@ -0,0 +1,172 @@ +using DCFApixels.DragonECS; +using DCFApixels.DragonECS.Relations.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; + +namespace DragonECS.DragonECS +{ + [DebuggerTypeProxy(typeof(DebuggerProxy))] + public class IdsBasket + { + private IdsLinkedList _headList = new IdsLinkedList(4); + private IdsLinkedList _valueList = new IdsLinkedList(4); + private SpanInfo[] _headMapping; + private SpanInfo[] _valueMapping; + private int _size; + + public IdsBasket(int size) + { + _headMapping = new SpanInfo[size]; + _valueMapping = new SpanInfo[size]; + _size = size; + } + + public void Resize(int newSize) + { + Array.Resize(ref _headMapping, newSize); + Array.Resize(ref _valueMapping, newSize); + _size = newSize; + } + public void Clear() + { + _headList.Clear(); + _valueList.Clear(); + ArrayUtility.Fill(_headMapping, SpanInfo.Empty); + ArrayUtility.Fill(_valueMapping, SpanInfo.Empty); + _size = 0; + } + public void AddToHead(int headID, int id) + { + int _savedNode; + ref var headSpan = ref _headMapping[headID]; + if (headSpan.startNodeIndex <= 0) + { + _savedNode = _headList.Add(id); + headSpan.startNodeIndex = _savedNode; + } + else + { + _savedNode = _headList.InsertAfter(headSpan.startNodeIndex, id); + } + headSpan.count++; + + ref var valueSpan = ref _valueMapping[id]; + if (valueSpan.startNodeIndex <= 0) + valueSpan.startNodeIndex = _valueList.Add(_savedNode); + else + _valueList.InsertAfter(valueSpan.startNodeIndex, _savedNode); + valueSpan.count++; + } + public void Del(int id) + { + ref var valueSpan = ref _valueMapping[id]; + ref var headSpan = ref _headMapping[id]; + if (valueSpan.startNodeIndex <= 0) + return; + foreach (var nodeIndex in _valueList.GetSpan(valueSpan.startNodeIndex, valueSpan.count)) + _headList.Remove(nodeIndex); + _valueList.RemoveSpan(valueSpan.startNodeIndex, valueSpan.count); + valueSpan = SpanInfo.Empty; + } + + public void DelHead(int headID) + { + ref var headSpan = ref _headMapping[headID]; + _valueList.RemoveSpan(headSpan.startNodeIndex, headSpan.count); + headSpan = SpanInfo.Empty; + } + + public IdsLinkedList.Span GetSpanFor(int value) + { + ref var head = ref _headMapping[value]; + if (head.startNodeIndex <= 0) + return _headList.EmptySpan(); + else + return _headList.GetSpan(head.startNodeIndex, head.count); + } + + private struct SpanInfo + { + public readonly static SpanInfo Empty = default; + public int startNodeIndex; + public int count; + } + + #region DebuggerProxy + internal class DebuggerProxy + { + private IdsBasket _basket; + + public SpanDebugInfo[] HeadSpans => GetSpans(_basket._headList, _basket._headMapping); + public SpanDebugInfo[] ValueSpans => GetSpans(_basket._valueList, _basket._valueMapping); + + private SpanDebugInfo[] GetSpans(IdsLinkedList list, SpanInfo[] mapping) + { + SpanDebugInfo[] result = new SpanDebugInfo[mapping.Length]; + for (int i = 0; i < mapping.Length; i++) + result[i] = new SpanDebugInfo(list, mapping[i].startNodeIndex, mapping[i].count); + return result; + } + public DebuggerProxy(IdsBasket basket) + { + _basket = basket; + } + + public struct SpanDebugInfo + { + private IdsLinkedList _list; + public int index; + public int count; + public NodeDebugInfo[] Nodes + { + get + { + var result = new NodeDebugInfo[this.count]; + var nodes = _list.Nodes; + int index; + int count = this.count; + int next = this.index; + int i = 0; + while (true) + { + index = next; + next = nodes[next].next; + if (!(index > 0 && count-- > 0)) + break; + var node = nodes[index]; + result[i] = new NodeDebugInfo(index, node.prev, node.next, node.value); + i++; + } + return result; + } + } + public SpanDebugInfo(IdsLinkedList list, int index, int count) + { + _list = list; + this.index = index; + this.count = count; + } + public override string ToString() => $"[{index}] {count}"; + } + public struct NodeDebugInfo + { + public int index; + public int prev; + public int next; + public int value; + public NodeDebugInfo(int index, int prev, int next, int value) + { + this.index = index; + this.prev = prev; + this.next = next; + this.value = value; + } + public override string ToString() => $"[{index}] {prev}_{next} - {value}"; + } + } + #endregion + } +} diff --git a/src/Common/RelationTargets.cs b/src/Common/RelationTargets.cs new file mode 100644 index 0000000..ce3c2ed --- /dev/null +++ b/src/Common/RelationTargets.cs @@ -0,0 +1,14 @@ +namespace DragonECS.DragonECS +{ + public readonly struct RelationTargets + { + public static readonly RelationTargets Empty = new RelationTargets(); + public readonly int entity; + public readonly int otherEntity; + public RelationTargets(int entity, int otherEntity) + { + this.entity = entity; + this.otherEntity = otherEntity; + } + } +} \ No newline at end of file diff --git a/src/Relation.cs b/src/Relation.cs deleted file mode 100644 index d1dc6ca..0000000 --- a/src/Relation.cs +++ /dev/null @@ -1,10 +0,0 @@ -using DCFApixels.DragonECS; - -namespace DragonECS.DragonECS -{ - internal readonly struct Relation : IEcsComponent - { - public readonly int entity; - public readonly int otherEntity; - } -} \ No newline at end of file diff --git a/src/RelationManager.cs b/src/RelationManager.cs index e1d2b0e..d0b8c6b 100644 --- a/src/RelationManager.cs +++ b/src/RelationManager.cs @@ -1,89 +1,173 @@ using DCFApixels.DragonECS; using DCFApixels.DragonECS.Relations.Utils; +using System.Collections; +using System.Runtime.CompilerServices; namespace DragonECS.DragonECS { - internal static class WorldRelationsMatrix + //Relation entity + //Relation + //Relation component + public readonly struct Relations { - private static SparseArray64 _matrix; - - internal static bool HasRelation(EcsWorld world, EcsWorld otherWorld) => HasRelation(world.id, otherWorld.id); - internal static bool HasRelation(int worldID, int otherWorldID) => _matrix.Contains(worldID, otherWorldID); - - internal static RelationManager Register(EcsWorld world, EcsWorld otherWorld, EcsRelationWorld relationWorld) + public readonly RelationManager manager; + public Relations(RelationManager manager) { - int worldID = world.id; - int otherWorldID = otherWorld.id; -#if DEBUG - if (_matrix.Contains(worldID, otherWorldID)) - throw new EcsFrameworkException(); -#endif - RelationManager manager = new RelationManager(relationWorld, world, otherWorld); - _matrix[worldID, otherWorldID] = manager; - return manager; - } - internal static void Unregister(EcsWorld world, EcsWorld otherWorld) - { - int worldID = world.id; - int otherWorldID = otherWorld.id; - //var manager = _matrix[worldID, otherWorldID]; - _matrix.Remove(worldID, otherWorldID); - } - - public static RelationManager Get(EcsWorld world, EcsWorld otherWorld) - { -#if DEBUG - if (_matrix.Contains(world.id, otherWorld.id)) - throw new EcsFrameworkException(); -#endif - return _matrix[world.id, otherWorld.id]; + this.manager = manager; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly RelationTargets GetRelationTargets(int relationEntityID) => ref manager.GetRelationTargets(relationEntityID); } - - public class RelationManager : IEcsPoolEventListener, IEcsWorldEventListener, IEcsEntityEventListener + public class RelationManager : IEcsWorldEventListener, IEcsEntityEventListener { private EcsRelationWorld _relationWorld; private EcsWorld _world; private EcsWorld _otherWorld; - private int[] _mapping = new int[256]; - private IdsLinkedList _idsBasket = new IdsLinkedList(256); - private EcsPool _relationsPool; + private SparseArray64 _relationsMatrix = new SparseArray64(); + public readonly Orientation Forward; + public readonly Orientation Reverse; + + private RelationTargets[] _relationTargets; + + public EcsWorld World => _world; public EcsWorld RelationWorld => _relationWorld; + public EcsWorld OtherWorld => _otherWorld; public bool IsSolo => _world == _otherWorld; - internal RelationManager(EcsRelationWorld relationWorld, EcsWorld world, EcsWorld otherWorld) + internal RelationManager(EcsWorld world, EcsRelationWorld relationWorld, EcsWorld otherWorld) { _relationWorld = relationWorld; _world = world; _otherWorld = otherWorld; - _relationsPool = relationWorld.GetPool(); - _relationsPool.AddListener(this); + _relationTargets = new RelationTargets[relationWorld.Capacity]; + _relationWorld.AddListener(worldEventListener: this); _relationWorld.AddListener(entityEventListener: this); + + IdsBasket basket = new IdsBasket(256); + IdsBasket otherBasket = new IdsBasket(256); + + Forward = new Orientation(this, false, relationWorld, IsSolo, basket, otherBasket); + Reverse = new Orientation(this, true, relationWorld, IsSolo, otherBasket, basket); } - void IEcsPoolEventListener.OnAdd(int entityID) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly RelationTargets GetRelationTargets(int relationEntityID) { - //_idsBasket. + return ref _relationTargets[relationEntityID]; } - void IEcsPoolEventListener.OnGet(int entityID) { } - void IEcsPoolEventListener.OnDel(int entityID) { } - void IEcsWorldEventListener.OnWorldResize(int newSize) { } + #region Callbacks + void IEcsWorldEventListener.OnWorldResize(int newSize) + { + Array.Resize(ref _relationTargets, newSize); + } void IEcsWorldEventListener.OnReleaseDelEntityBuffer(ReadOnlySpan buffer) { } void IEcsWorldEventListener.OnWorldDestroy() { } void IEcsEntityEventListener.OnNewEntity(int entityID) { } void IEcsEntityEventListener.OnDelEntity(int entityID) { - + ref RelationTargets rel = ref _relationTargets[entityID]; + if (_relationsMatrix.Contains(rel.entity, rel.otherEntity)) + Forward.DelRelation(rel.entity, rel.otherEntity); } - } + #endregion + #region Orientation + public readonly struct Orientation + { + private readonly RelationManager _source; + + private readonly EcsWorld _relationWorld; + + private readonly IdsBasket _basket; + private readonly IdsBasket _otherBasket; + + private readonly bool _isSolo; + + private readonly bool _isReverce; + + public Orientation(RelationManager source, bool isReverce, EcsWorld relationWorld, bool isSolo, IdsBasket basket, IdsBasket otherBasket) + { + _source = source; + _isReverce = isReverce; + _relationWorld = relationWorld; + _isSolo = isSolo; + _basket = basket; + _otherBasket = otherBasket; + } + + #region New/Del + public int NewRelation(int entityID, int otherEntityID) + { + if (HasRelation(entityID, otherEntityID)) + throw new EcsRelationException(); + int e = _relationWorld.NewEmptyEntity(); + _basket.AddToHead(entityID, otherEntityID); + _otherBasket.AddToHead(otherEntityID, entityID); + _source._relationTargets[e] = new RelationTargets(entityID, otherEntityID); + return e; + } + public void DelRelation(int entityID, int otherEntityID) + { + if (!_source._relationsMatrix.TryGetValue(entityID, otherEntityID, out int e)) + throw new EcsRelationException(); + _basket.DelHead(entityID); + _otherBasket.Del(entityID); + _relationWorld.DelEntity(e); + _source._relationTargets[e] = RelationTargets.Empty; + } + #endregion + + #region Has + public bool HasRelation(int entityID, int otherEntityID) => _source._relationsMatrix.Contains(entityID, otherEntityID); + public bool HasRelationWith(EcsSubject subject, int entityID, int otherEntityID) + { + if (subject.World != _relationWorld) + throw new ArgumentException(); + return _source._relationsMatrix.TryGetValue(entityID, otherEntityID, out int entity) && subject.IsMatches(entity); + } + #endregion + + #region GetRelation + public int GetRelation(int entityID, int otherEntityID) + { + if (!_source._relationsMatrix.TryGetValue(entityID, otherEntityID, out int e)) + throw new EcsRelationException(); + return e; + } + public bool TryGetRelation(int entityID, int otherEntityID, out int entity) + { + return _source._relationsMatrix.TryGetValue(entityID, otherEntityID, out entity); + } + public bool TryGetRelation(EcsSubject subject, int entityID, int otherEntityID, out int entity) + { + return _source._relationsMatrix.TryGetValue(entityID, otherEntityID, out entity) && subject.IsMatches(entity); + } + #endregion + + #region GetRelations + //ReadOnlySpan временная заглушка, потому тут будет спан из линкедлиста + public ReadOnlySpan GetRelations(int entityID) + { + throw new NotImplementedException(); + } + //ReadOnlySpan временная заглушка, потому тут будет спан из линкедлиста + public ReadOnlySpan GetRelationsWith(EcsSubject subject, int entityID) + { + if (subject.World != _relationWorld) + throw new ArgumentException(); + throw new NotImplementedException(); + } + #endregion + } + #endregion + } public static class WorldRelationExtensions { diff --git a/src/Utils/Exceptions.cs b/src/Utils/Exceptions.cs new file mode 100644 index 0000000..ff38ead --- /dev/null +++ b/src/Utils/Exceptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.Serialization; + +namespace DCFApixels.DragonECS +{ + [Serializable] + public class EcsRelationException : Exception + { + public EcsRelationException() { } + public EcsRelationException(string message) : base(EcsConsts.EXCEPTION_MESSAGE_PREFIX + message) { } + public EcsRelationException(string message, Exception inner) : base(EcsConsts.EXCEPTION_MESSAGE_PREFIX + message, inner) { } + protected EcsRelationException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/src/Utils/IdsBasket.cs b/src/Utils/IdsBasket.cs deleted file mode 100644 index 400e8cb..0000000 --- a/src/Utils/IdsBasket.cs +++ /dev/null @@ -1,50 +0,0 @@ -using DCFApixels.DragonECS.Relations.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace DragonECS.DragonECS -{ - internal class IdsBasket - { - private IdsLinkedList _linketdList = new IdsLinkedList(4); - private int[] _mapping; - private int[] _counts; - - public void Clear() - { - _linketdList.Clear(); - ArrayUtility.Fill(_mapping, 0, 0, _mapping.Length); - ArrayUtility.Fill(_counts, 0, 0, _counts.Length); - } - public void AddToHead(int headEntityID, int addedEntityID) - { - ref var nodeIndex = ref _mapping[headEntityID]; - if (nodeIndex <= 0) - { - nodeIndex = _linketdList.Add(addedEntityID); - } - else - { - _linketdList.InsertAfter(nodeIndex, addedEntityID); - } - _counts[headEntityID]++; - } - - public void DelHead(int headEntityID) - { - - } - - public IdsLinkedList.Span GetEntitiesFor(int entity) - { - ref var nodeIndex = ref _mapping[entity]; - if (nodeIndex <= 0) - return _linketdList.EmptySpan(); - else - return _linketdList.GetSpan(nodeIndex, _counts[entity]); - } - } -} diff --git a/src/Utils/IdsLinkedList.cs b/src/Utils/IdsLinkedList.cs index 49331bc..3861491 100644 --- a/src/Utils/IdsLinkedList.cs +++ b/src/Utils/IdsLinkedList.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Diagnostics; using System.Reflection; +using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; namespace DCFApixels.DragonECS.Relations.Utils @@ -22,6 +23,7 @@ namespace DCFApixels.DragonECS.Relations.Utils public int Count => _count; public int Capacity => _nodes.Length; public int Last => _lastNodeIndex; + public ReadOnlySpan Nodes => new ReadOnlySpan(_nodes); #endregion #region Constructors @@ -52,19 +54,34 @@ namespace DCFApixels.DragonECS.Relations.Utils /// new node index public int InsertAfter(int nodeIndex, int value) { - _count++; + if(++_count >= _nodes.Length) + Array.Resize(ref _nodes, _nodes.Length << 1); int newNodeIndex = _recycledNodesCount > 0 ? _recycledNodes[--_recycledNodesCount] : _count; ref Node prevNode = ref _nodes[nodeIndex]; ref Node nextNode = ref _nodes[prevNode.next]; + if (prevNode.next == 0) + _lastNodeIndex = newNodeIndex; _nodes[newNodeIndex].Set(value, nextNode.prev, prevNode.next); prevNode.next = newNodeIndex; nextNode.prev = newNodeIndex; - if(prevNode.next == 0) - _lastNodeIndex = newNodeIndex; + + return newNodeIndex; + } + public int InsertBefore(int nodeIndex, int value) + { + if (++_count >= _nodes.Length) + Array.Resize(ref _nodes, _nodes.Length << 1); + int newNodeIndex = _recycledNodesCount > 0 ? _recycledNodes[--_recycledNodesCount] : _count; + + ref Node nextNode = ref _nodes[nodeIndex]; + ref Node prevNode = ref _nodes[nextNode.prev]; + _nodes[newNodeIndex].Set(value, nextNode.prev, prevNode.next); + prevNode.next = newNodeIndex; + nextNode.prev = newNodeIndex; + return newNodeIndex; } - //public int InsertBefore(int nodeIndex, int value) { } public void Remove(int nodeIndex) { if(nodeIndex <= 0) @@ -80,6 +97,37 @@ namespace DCFApixels.DragonECS.Relations.Utils _count--; } + public void RemoveSpan(int startNodeIndex, int count) + { + if (count <= 0) + return; + + int endNodeIndex = startNodeIndex; + + if (_recycledNodesCount >= _recycledNodes.Length) + Array.Resize(ref _recycledNodes, _recycledNodes.Length << 1); + _recycledNodes[_recycledNodesCount++] = startNodeIndex; + + for (int i = 1; i < count; i++) + { + endNodeIndex = _nodes[endNodeIndex].next; + if (endNodeIndex == 0) + throw new ArgumentOutOfRangeException(); + + if (_recycledNodesCount >= _recycledNodes.Length) + Array.Resize(ref _recycledNodes, _recycledNodes.Length << 1); + _recycledNodes[_recycledNodesCount++] = endNodeIndex; + } + + ref var startNode = ref _nodes[startNodeIndex]; + ref var endNode = ref _nodes[endNodeIndex]; + + _nodes[endNode.next].prev = startNode.prev; + _nodes[startNode.prev].next = endNode.next; + + _count -= count; + } + public int Add(int id) => InsertAfter(_lastNodeIndex, id); public ref readonly Node GetNode(int nodeIndex) => ref _nodes[nodeIndex]; @@ -108,39 +156,10 @@ namespace DCFApixels.DragonECS.Relations.Utils this.next = next; this.prev = prev; } - public override string ToString() => $"node(<:{prev} >:{next} v:{value})"; + public override string ToString() => $"node({prev}<>{next} v:{value})"; } - #region Enumerator/Span - public struct Enumerator : IEnumerator - { - private readonly Node[] _nodes; - private int _index; - private int _next; - public Enumerator(Node[] nodes) - { - _nodes = nodes; - _index = -1; - _next = _nodes[Head].next; - } - public int Current => _nodes[_index].value; - - - public bool MoveNext() - { - _index = _next; - _next = _nodes[_next].next; - return _index > 0; - } - - object IEnumerator.Current => Current; - void IDisposable.Dispose() { } - void IEnumerator.Reset() - { - _index = -1; - _next = Head; - } - } + #region Span/Enumerator public readonly ref struct Span { private readonly IdsLinkedList _source; @@ -187,7 +206,8 @@ namespace DCFApixels.DragonECS.Relations.Utils #endregion - public class DebuggerProxy + #region Debug + internal class DebuggerProxy { private IdsLinkedList list; public NodeInfo[] Nodes @@ -206,15 +226,12 @@ namespace DCFApixels.DragonECS.Relations.Utils { index = next; next = nodes[next].next; - if (index > 0 && count-- > 0) - { } - else break; - + if (!(index > 0 && count-- > 0)) + break; ref var node = ref nodes[index]; result[i] = new NodeInfo(index, node.prev, node.next, node.value); i++; } - return result; } } @@ -239,5 +256,6 @@ namespace DCFApixels.DragonECS.Relations.Utils public override string ToString() => $"[{index}] {prev}_{next} - {value}"; } } + #endregion } } diff --git a/src/Utils/SparseArray64.cs b/src/Utils/SparseArray64.cs index 55be571..c51e156 100644 --- a/src/Utils/SparseArray64.cs +++ b/src/Utils/SparseArray64.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; namespace DCFApixels.DragonECS.Relations.Utils { - public class SparseArray64 + internal class SparseArray64 { public const int MIN_CAPACITY_BITS_OFFSET = 4; public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; @@ -148,6 +148,17 @@ namespace DCFApixels.DragonECS.Relations.Utils value = _entries[index].value; return true; } + public bool TryGetValue(long keyX, long keyY, out TValue value) + { + int index = FindEntry(keyX + (keyY << 32)); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } #endregion #region Contains diff --git a/src/WorldRelationsMatrix.cs b/src/WorldRelationsMatrix.cs new file mode 100644 index 0000000..85f8063 --- /dev/null +++ b/src/WorldRelationsMatrix.cs @@ -0,0 +1,41 @@ +using DCFApixels.DragonECS; +using DCFApixels.DragonECS.Relations.Utils; + +namespace DragonECS.DragonECS +{ + internal static class WorldRelationsMatrix + { + private static SparseArray64 _matrix; + + internal static RelationManager Register(EcsWorld world, EcsWorld otherWorld, EcsRelationWorld relationWorld) + { + int worldID = world.id; + int otherWorldID = otherWorld.id; +#if DEBUG + if (_matrix.Contains(worldID, otherWorldID)) + throw new EcsFrameworkException(); +#endif + RelationManager manager = new RelationManager(world, relationWorld, otherWorld); + _matrix[worldID, otherWorldID] = manager; + return manager; + } + internal static void Unregister(EcsWorld world, EcsWorld otherWorld) + { + int worldID = world.id; + int otherWorldID = otherWorld.id; + //var manager = _matrix[worldID, otherWorldID]; + _matrix.Remove(worldID, otherWorldID); + } + + internal static RelationManager Get(EcsWorld world, EcsWorld otherWorld) + { +#if DEBUG + if (_matrix.Contains(world.id, otherWorld.id)) + throw new EcsFrameworkException(); +#endif + return _matrix[world.id, otherWorld.id]; + } + internal static bool HasRelation(EcsWorld world, EcsWorld otherWorld) => HasRelation(world.id, otherWorld.id); + internal static bool HasRelation(int worldID, int otherWorldID) => _matrix.Contains(worldID, otherWorldID); + } +}