This commit is contained in:
Mikhail 2023-06-22 03:54:20 +08:00
parent b7b2e1dd79
commit 04d4fd2afd
10 changed files with 443 additions and 149 deletions

172
src/Common/IdsBasket.cs Normal file
View File

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

View File

@ -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;
}
}
}

View File

@ -1,10 +0,0 @@
using DCFApixels.DragonECS;
namespace DragonECS.DragonECS
{
internal readonly struct Relation : IEcsComponent
{
public readonly int entity;
public readonly int otherEntity;
}
}

View File

@ -1,89 +1,173 @@
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
using DCFApixels.DragonECS.Relations.Utils; using DCFApixels.DragonECS.Relations.Utils;
using System.Collections;
using System.Runtime.CompilerServices;
namespace DragonECS.DragonECS namespace DragonECS.DragonECS
{ {
internal static class WorldRelationsMatrix //Relation entity
//Relation
//Relation component
public readonly struct Relations
{ {
private static SparseArray64<RelationManager> _matrix; public readonly RelationManager manager;
public Relations(RelationManager manager)
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)
{ {
int worldID = world.id; this.manager = manager;
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) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public ref readonly RelationTargets GetRelationTargets(int relationEntityID) => ref manager.GetRelationTargets(relationEntityID);
int worldID = world.id;
int otherWorldID = otherWorld.id;
//var manager = _matrix[worldID, otherWorldID];
_matrix.Remove(worldID, otherWorldID);
} }
public class RelationManager : IEcsWorldEventListener, IEcsEntityEventListener
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];
}
}
public class RelationManager : IEcsPoolEventListener, IEcsWorldEventListener, IEcsEntityEventListener
{ {
private EcsRelationWorld _relationWorld; private EcsRelationWorld _relationWorld;
private EcsWorld _world; private EcsWorld _world;
private EcsWorld _otherWorld; private EcsWorld _otherWorld;
private int[] _mapping = new int[256]; private SparseArray64<int> _relationsMatrix = new SparseArray64<int>();
private IdsLinkedList _idsBasket = new IdsLinkedList(256);
private EcsPool<Relation> _relationsPool;
public readonly Orientation Forward;
public readonly Orientation Reverse;
private RelationTargets[] _relationTargets;
public EcsWorld World => _world;
public EcsWorld RelationWorld => _relationWorld; public EcsWorld RelationWorld => _relationWorld;
public EcsWorld OtherWorld => _otherWorld;
public bool IsSolo => _world == _otherWorld; public bool IsSolo => _world == _otherWorld;
internal RelationManager(EcsRelationWorld relationWorld, EcsWorld world, EcsWorld otherWorld) internal RelationManager(EcsWorld world, EcsRelationWorld relationWorld, EcsWorld otherWorld)
{ {
_relationWorld = relationWorld; _relationWorld = relationWorld;
_world = world; _world = world;
_otherWorld = otherWorld; _otherWorld = otherWorld;
_relationsPool = relationWorld.GetPool<Relation>(); _relationTargets = new RelationTargets[relationWorld.Capacity];
_relationsPool.AddListener(this);
_relationWorld.AddListener(worldEventListener: this); _relationWorld.AddListener(worldEventListener: this);
_relationWorld.AddListener(entityEventListener: 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<int> buffer) { } void IEcsWorldEventListener.OnReleaseDelEntityBuffer(ReadOnlySpan<int> buffer) { }
void IEcsWorldEventListener.OnWorldDestroy() { } void IEcsWorldEventListener.OnWorldDestroy() { }
void IEcsEntityEventListener.OnNewEntity(int entityID) { } void IEcsEntityEventListener.OnNewEntity(int entityID) { }
void IEcsEntityEventListener.OnDelEntity(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<int> временная заглушка, потому тут будет спан из линкедлиста
public ReadOnlySpan<int> GetRelations(int entityID)
{
throw new NotImplementedException();
}
//ReadOnlySpan<int> временная заглушка, потому тут будет спан из линкедлиста
public ReadOnlySpan<int> GetRelationsWith(EcsSubject subject, int entityID)
{
if (subject.World != _relationWorld)
throw new ArgumentException();
throw new NotImplementedException();
}
#endregion
}
#endregion
}
public static class WorldRelationExtensions public static class WorldRelationExtensions
{ {

14
src/Utils/Exceptions.cs Normal file
View File

@ -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) { }
}
}

View File

@ -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]);
}
}
}

View File

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace DCFApixels.DragonECS.Relations.Utils namespace DCFApixels.DragonECS.Relations.Utils
@ -22,6 +23,7 @@ namespace DCFApixels.DragonECS.Relations.Utils
public int Count => _count; public int Count => _count;
public int Capacity => _nodes.Length; public int Capacity => _nodes.Length;
public int Last => _lastNodeIndex; public int Last => _lastNodeIndex;
public ReadOnlySpan<Node> Nodes => new ReadOnlySpan<Node>(_nodes);
#endregion #endregion
#region Constructors #region Constructors
@ -52,19 +54,34 @@ namespace DCFApixels.DragonECS.Relations.Utils
/// <returns> new node index</returns> /// <returns> new node index</returns>
public int InsertAfter(int nodeIndex, int value) 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; int newNodeIndex = _recycledNodesCount > 0 ? _recycledNodes[--_recycledNodesCount] : _count;
ref Node prevNode = ref _nodes[nodeIndex]; ref Node prevNode = ref _nodes[nodeIndex];
ref Node nextNode = ref _nodes[prevNode.next]; ref Node nextNode = ref _nodes[prevNode.next];
if (prevNode.next == 0)
_lastNodeIndex = newNodeIndex;
_nodes[newNodeIndex].Set(value, nextNode.prev, prevNode.next); _nodes[newNodeIndex].Set(value, nextNode.prev, prevNode.next);
prevNode.next = newNodeIndex; prevNode.next = newNodeIndex;
nextNode.prev = 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; return newNodeIndex;
} }
//public int InsertBefore(int nodeIndex, int value) { }
public void Remove(int nodeIndex) public void Remove(int nodeIndex)
{ {
if(nodeIndex <= 0) if(nodeIndex <= 0)
@ -80,6 +97,37 @@ namespace DCFApixels.DragonECS.Relations.Utils
_count--; _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 int Add(int id) => InsertAfter(_lastNodeIndex, id);
public ref readonly Node GetNode(int nodeIndex) => ref _nodes[nodeIndex]; public ref readonly Node GetNode(int nodeIndex) => ref _nodes[nodeIndex];
@ -108,39 +156,10 @@ namespace DCFApixels.DragonECS.Relations.Utils
this.next = next; this.next = next;
this.prev = prev; this.prev = prev;
} }
public override string ToString() => $"node(<:{prev} >:{next} v:{value})"; public override string ToString() => $"node({prev}<>{next} v:{value})";
} }
#region Enumerator/Span #region Span/Enumerator
public struct Enumerator : IEnumerator<int>
{
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;
}
}
public readonly ref struct Span public readonly ref struct Span
{ {
private readonly IdsLinkedList _source; private readonly IdsLinkedList _source;
@ -187,7 +206,8 @@ namespace DCFApixels.DragonECS.Relations.Utils
#endregion #endregion
public class DebuggerProxy #region Debug
internal class DebuggerProxy
{ {
private IdsLinkedList list; private IdsLinkedList list;
public NodeInfo[] Nodes public NodeInfo[] Nodes
@ -206,15 +226,12 @@ namespace DCFApixels.DragonECS.Relations.Utils
{ {
index = next; index = next;
next = nodes[next].next; next = nodes[next].next;
if (index > 0 && count-- > 0) if (!(index > 0 && count-- > 0))
{ } break;
else break;
ref var node = ref nodes[index]; ref var node = ref nodes[index];
result[i] = new NodeInfo(index, node.prev, node.next, node.value); result[i] = new NodeInfo(index, node.prev, node.next, node.value);
i++; i++;
} }
return result; return result;
} }
} }
@ -239,5 +256,6 @@ namespace DCFApixels.DragonECS.Relations.Utils
public override string ToString() => $"[{index}] {prev}_{next} - {value}"; public override string ToString() => $"[{index}] {prev}_{next} - {value}";
} }
} }
#endregion
} }
} }

View File

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
namespace DCFApixels.DragonECS.Relations.Utils namespace DCFApixels.DragonECS.Relations.Utils
{ {
public class SparseArray64<TValue> internal class SparseArray64<TValue>
{ {
public const int MIN_CAPACITY_BITS_OFFSET = 4; public const int MIN_CAPACITY_BITS_OFFSET = 4;
public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET;
@ -148,6 +148,17 @@ namespace DCFApixels.DragonECS.Relations.Utils
value = _entries[index].value; value = _entries[index].value;
return true; 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 #endregion
#region Contains #region Contains

View File

@ -0,0 +1,41 @@
using DCFApixels.DragonECS;
using DCFApixels.DragonECS.Relations.Utils;
namespace DragonECS.DragonECS
{
internal static class WorldRelationsMatrix
{
private static SparseArray64<RelationManager> _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);
}
}