Determinism fixes (#5)

- Making sure that transferring from one world to another and back preserves original order of filters and relations
- Provides a MoonTools.ECS.Random class
- MoonTools.ECS.RandomManager class can be used to synchronize your Random instance with the ECS randomness
- Refactors some collections to use NativeMemory for performance and to ensure memory safety on world transfer
- Exposes some internal collections to public

Reviewed-on: #5
set_order
cosmonaut 2023-10-13 20:42:22 +00:00
parent 839ad015ba
commit 4ef7cb4302
16 changed files with 674 additions and 250 deletions

View File

@ -89,7 +89,7 @@ namespace MoonTools.ECS
}
}
// these methods used to implement snapshots, templates, and debugging
// these methods used to implement transfers and debugging
internal unsafe void* UntypedGet(int entityID, int componentTypeIndex)
{

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
@ -19,16 +21,24 @@ namespace MoonTools.ECS
#endif
}
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
internal unsafe class ComponentStorage<TComponent> : ComponentStorage, IDisposable where TComponent : unmanaged
{
private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16);
private int[] entityIDs = new int[16];
private TComponent[] components = new TComponent[16];
private TComponent* components;
private int* entityIDs;
private int count = 0;
private int capacity = 16;
private bool disposed;
public ComponentStorage()
{
components = (TComponent*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TComponent>()));
entityIDs = (int*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<int>()));
}
public bool Any()
{
return nextID > 0;
return count > 0;
}
public ref readonly TComponent Get(int entityID)
@ -38,16 +48,13 @@ namespace MoonTools.ECS
internal override unsafe void* UntypedGet(int entityID)
{
fixed (void* p = &components[entityIDToStorageIndex[entityID]])
{
return p;
}
return &components[entityIDToStorageIndex[entityID]];
}
public ref readonly TComponent GetFirst()
{
#if DEBUG
if (nextID == 0)
if (count == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
@ -59,13 +66,14 @@ namespace MoonTools.ECS
{
if (!entityIDToStorageIndex.ContainsKey(entityID))
{
var index = nextID;
nextID += 1;
var index = count;
count += 1;
if (index >= components.Length)
if (index >= capacity)
{
Array.Resize(ref components, components.Length * 2);
Array.Resize(ref entityIDs, entityIDs.Length * 2);
capacity *= 2;
components = (TComponent*) NativeMemory.Realloc(components, (nuint) (capacity * Unsafe.SizeOf<TComponent>()));
entityIDs = (int*) NativeMemory.Realloc(entityIDs, (nuint) (capacity * Unsafe.SizeOf<int>()));
}
entityIDToStorageIndex[entityID] = index;
@ -77,7 +85,7 @@ namespace MoonTools.ECS
internal override unsafe void Set(int entityID, void* component)
{
Set(entityID, *((TComponent*) component));
Set(entityID, *(TComponent*) component);
}
// Returns true if the entity had this component.
@ -87,7 +95,7 @@ namespace MoonTools.ECS
{
entityIDToStorageIndex.Remove(entityID);
var lastElementIndex = nextID - 1;
var lastElementIndex = count - 1;
// move a component into the hole to maintain contiguous memory
if (lastElementIndex != storageIndex)
@ -98,7 +106,7 @@ namespace MoonTools.ECS
entityIDs[storageIndex] = lastEntityID;
}
nextID -= 1;
count -= 1;
return true;
}
@ -108,19 +116,19 @@ namespace MoonTools.ECS
public override void Clear()
{
nextID = 0;
count = 0;
entityIDToStorageIndex.Clear();
}
public ReadOnlySpan<TComponent> AllComponents()
{
return new ReadOnlySpan<TComponent>(components, 0, nextID);
return new ReadOnlySpan<TComponent>(components, count);
}
public Entity FirstEntity()
{
#if DEBUG
if (nextID == 0)
if (count == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
@ -143,6 +151,32 @@ namespace MoonTools.ECS
{
return entityIDToStorageIndex.Keys;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
NativeMemory.Free(components);
NativeMemory.Free(entityIDs);
components = null;
entityIDs = null;
disposed = true;
}
}
~ComponentStorage()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endif
}
}

View File

@ -13,12 +13,9 @@ namespace MoonTools.ECS
{
}
protected IEnumerable<dynamic> Debug_GetAllComponents(Entity entity)
protected ComponentEnumerator Debug_GetAllComponents(Entity entity)
{
foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
{
yield return ComponentDepot.Debug_Get(entity.ID, typeIndex);
}
return new ComponentEnumerator(ComponentDepot, entity, EntityStorage.ComponentTypeIndices(entity.ID));
}
protected IEnumerable<Entity> Debug_GetEntities(Type componentType)
@ -39,6 +36,28 @@ namespace MoonTools.ECS
}
}
}
public ref struct ComponentEnumerator
{
private ComponentDepot ComponentDepot;
private Entity Entity;
private ReverseSpanEnumerator<int> ComponentTypeIndices;
public ComponentEnumerator GetEnumerator() => this;
internal ComponentEnumerator(
ComponentDepot componentDepot,
Entity entity,
Collections.IndexableSet<int> componentTypeIndices
) {
ComponentDepot = componentDepot;
Entity = entity;
ComponentTypeIndices = componentTypeIndices.GetEnumerator();
}
public bool MoveNext() => ComponentTypeIndices.MoveNext();
public object Current => ComponentDepot.Debug_Get(Entity.ID, ComponentTypeIndices.Current);
}
}
}
#endif

View File

@ -1,40 +1,69 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
namespace MoonTools.ECS.Collections
{
public class DynamicArray<T> where T : unmanaged
public unsafe class NativeArray<T> : IDisposable where T : unmanaged
{
private T[] Array;
public int Count { get; private set; }
private T* Array;
private int count;
private int capacity;
public Span<T> ToSpan() => new Span<T>(Array, 0, Count);
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count));
public int Count => count;
public DynamicArray(int capacity = 16)
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count));
private bool disposed;
public NativeArray(int capacity = 16)
{
Array = new T[capacity];
Count = 0;
this.capacity = capacity;
Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>()));
count = 0;
}
public ref T this[int i]
{
get { return ref Array[i]; }
}
public ref T this[int i] => ref Array[i];
public void Add(T item)
{
if (Count >= Array.Length)
if (count >= capacity)
{
global::System.Array.Resize(ref Array, Array.Length * 2);
capacity *= 2;
Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf<T>()));
}
Array[Count] = item;
Count += 1;
Array[count] = item;
count += 1;
}
public void Clear()
{
Count = 0;
count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
NativeMemory.Free(Array);
Array = null;
disposed = true;
}
}
~NativeArray()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -10,8 +11,8 @@ namespace MoonTools.ECS
// FIXME: this is only needed in debug mode
private readonly HashSet<int> availableIDHash = new HashSet<int>();
private Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
private Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>();
private Dictionary<int, IndexableSet<int>> EntityToComponentTypeIndices = new Dictionary<int, IndexableSet<int>>();
private Dictionary<int, IndexableSet<int>> EntityToRelationTypeIndices = new Dictionary<int, IndexableSet<int>>();
public int Count => nextID - availableIDs.Count;
@ -23,12 +24,12 @@ namespace MoonTools.ECS
if (!EntityToComponentTypeIndices.ContainsKey(entity.ID))
{
EntityToComponentTypeIndices.Add(entity.ID, new HashSet<int>());
EntityToComponentTypeIndices.Add(entity.ID, new IndexableSet<int>());
}
if (!EntityToRelationTypeIndices.ContainsKey(entity.ID))
{
EntityToRelationTypeIndices.Add(entity.ID, new HashSet<int>());
EntityToRelationTypeIndices.Add(entity.ID, new IndexableSet<int>());
}
Tags[entity.ID] = tag;
@ -86,12 +87,12 @@ namespace MoonTools.ECS
return Tags[entityID];
}
public HashSet<int> ComponentTypeIndices(int entityID)
public IndexableSet<int> ComponentTypeIndices(int entityID)
{
return EntityToComponentTypeIndices[entityID];
}
public HashSet<int> RelationTypeIndices(int entityID)
public IndexableSet<int> RelationTypeIndices(int entityID)
{
return EntityToRelationTypeIndices[entityID];
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -8,7 +9,7 @@ namespace MoonTools.ECS
internal FilterSignature Signature;
private FilterStorage FilterStorage;
internal Filter(FilterStorage filterStorage, HashSet<int> included, HashSet<int> excluded)
internal Filter(FilterStorage filterStorage, IndexableSet<int> included, IndexableSet<int> excluded)
{
FilterStorage = filterStorage;
Signature = new FilterSignature(included, excluded);

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -7,18 +6,18 @@ namespace MoonTools.ECS
{
private TypeIndices ComponentTypeIndices;
private FilterStorage FilterStorage;
private HashSet<int> Included;
private HashSet<int> Excluded;
private IndexableSet<int> Included;
private IndexableSet<int> Excluded;
internal FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices)
{
FilterStorage = filterStorage;
ComponentTypeIndices = componentTypeIndices;
Included = new HashSet<int>();
Excluded = new HashSet<int>();
Included = new IndexableSet<int>();
Excluded = new IndexableSet<int>();
}
private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, HashSet<int> included, HashSet<int> excluded)
private FilterBuilder(FilterStorage filterStorage, TypeIndices componentTypeIndices, IndexableSet<int> included, IndexableSet<int> excluded)
{
FilterStorage = filterStorage;
ComponentTypeIndices = componentTypeIndices;

View File

@ -1,14 +1,15 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public struct FilterSignature : IEquatable<FilterSignature>
{
public readonly HashSet<int> Included;
public readonly HashSet<int> Excluded;
public readonly IndexableSet<int> Included;
public readonly IndexableSet<int> Excluded;
public FilterSignature(HashSet<int> included, HashSet<int> excluded)
public FilterSignature(IndexableSet<int> included, IndexableSet<int> excluded)
{
Included = included;
Excluded = excluded;
@ -21,8 +22,6 @@ namespace MoonTools.ECS
public bool Equals(FilterSignature other)
{
// workaround for HashSet<T>.SetEquals generating garbage
// maybe fixed in .NET 8?
foreach (var included in Included)
{
if (!other.Included.Contains(included))

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -8,7 +9,7 @@ namespace MoonTools.ECS
private EntityStorage EntityStorage;
private TypeIndices ComponentTypeIndices;
private Dictionary<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>();
private Dictionary<int, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>();
private Dictionary<int, List<FilterSignature>> typeToFilterSignatures = new Dictionary<int, List<FilterSignature>>();
private Dictionary<FilterSignature, Action<Entity>> addCallbacks = new Dictionary<FilterSignature, Action<Entity>>();
private Dictionary<FilterSignature, Action<Entity>> removeCallbacks = new Dictionary<FilterSignature, Action<Entity>>();
@ -19,7 +20,36 @@ namespace MoonTools.ECS
ComponentTypeIndices = componentTypeIndices;
}
public Filter CreateFilter(HashSet<int> included, HashSet<int> excluded)
private void CopyTypeCache(Dictionary<int, List<FilterSignature>> typeCache)
{
foreach (var type in typeCache.Keys)
{
if (!typeToFilterSignatures.ContainsKey(type))
{
typeToFilterSignatures.Add(type, new List<FilterSignature>());
foreach (var signature in typeCache[type])
{
typeToFilterSignatures[type].Add(signature);
}
}
}
}
public void CreateMissingStorages(FilterStorage other)
{
foreach (var filterSignature in other.filterSignatureToEntityIDs.Keys)
{
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
{
filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<Entity>());
}
}
CopyTypeCache(other.typeToFilterSignatures);
}
public Filter CreateFilter(IndexableSet<int> included, IndexableSet<int> excluded)
{
var filterSignature = new FilterSignature(included, excluded);
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
@ -30,7 +60,7 @@ namespace MoonTools.ECS
{
if (!typeToFilterSignatures.ContainsKey(type))
{
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
typeToFilterSignatures.Add(type, new List<FilterSignature>());
}
typeToFilterSignatures[type].Add(filterSignature);
@ -40,7 +70,7 @@ namespace MoonTools.ECS
{
if (!typeToFilterSignatures.ContainsKey(type))
{
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
typeToFilterSignatures.Add(type, new List<FilterSignature>());
}
typeToFilterSignatures[type].Add(filterSignature);
@ -59,7 +89,7 @@ namespace MoonTools.ECS
return new RandomEntityEnumerator(
this,
filterSignature,
RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature)));
RandomManager.LinearCongruentialSequence(FilterCount(filterSignature)));
}
public Entity FilterNthEntity(FilterSignature filterSignature, int index)
@ -69,7 +99,7 @@ namespace MoonTools.ECS
public Entity FilterRandomEntity(FilterSignature filterSignature)
{
var randomIndex = RandomGenerator.Next(FilterCount(filterSignature));
var randomIndex = RandomManager.Next(FilterCount(filterSignature));
return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]);
}
@ -147,10 +177,12 @@ namespace MoonTools.ECS
}
}
filterSignatureToEntityIDs[filterSignature].Add(entityID);
if (addCallbacks.TryGetValue(filterSignature, out var addCallback))
if (filterSignatureToEntityIDs[filterSignature].Add(entityID))
{
addCallback(entityID);
if (addCallbacks.TryGetValue(filterSignature, out var addCallback))
{
addCallback(entityID);
}
}
}
@ -171,6 +203,32 @@ namespace MoonTools.ECS
}
}
// Used by TransferEntity
public void AddEntity(FilterSignature signature, int entityID)
{
filterSignatureToEntityIDs[signature].Add(entityID);
}
public void TransferStorage(Dictionary<int, int> worldToTransferID, FilterStorage other)
{
foreach (var (filterSignature, entityIDs) in filterSignatureToEntityIDs)
{
foreach (var entity in entityIDs)
{
if (worldToTransferID.ContainsKey(entity))
{
var otherEntityID = worldToTransferID[entity];
other.AddEntity(filterSignature, otherEntityID);
if (other.addCallbacks.TryGetValue(filterSignature, out var addCallback))
{
addCallback(otherEntityID);
}
}
}
}
}
// used by World.Clear, ignores callbacks
public void Clear()
{

View File

@ -1,26 +1,31 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
namespace MoonTools.ECS.Collections
{
internal class IndexableSet<T> where T : unmanaged
public unsafe class IndexableSet<T> : IDisposable where T : unmanaged
{
private Dictionary<T, int> indices;
private T[] array;
public int Count { get; private set; }
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count));
private T* array;
private int count;
private int capacity;
private bool disposed;
public IndexableSet(int size = 32)
public int Count => count;
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, count));
public IndexableSet(int capacity = 32)
{
indices = new Dictionary<T, int>(size);
array = new T[size];
this.capacity = capacity;
count = 0;
indices = new Dictionary<T, int>(capacity);
array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>()));
}
public T this[int i]
{
get { return array[i]; }
}
public T this[int i] => array[i];
public bool Contains(T element)
{
@ -31,15 +36,16 @@ namespace MoonTools.ECS
{
if (!Contains(element))
{
indices.Add(element, Count);
indices.Add(element, count);
if (Count >= array.Length)
if (count >= capacity)
{
Array.Resize(ref array, array.Length * 2);
capacity *= 2;
array = (T*) NativeMemory.Realloc(array, (nuint) (capacity * Unsafe.SizeOf<T>()));
}
array[Count] = element;
Count += 1;
array[count] = element;
count += 1;
return true;
}
@ -58,7 +64,7 @@ namespace MoonTools.ECS
var index = indices[element];
array[index] = lastElement;
indices[lastElement] = index;
Count -= 1;
count -= 1;
indices.Remove(element);
return true;
@ -66,45 +72,32 @@ namespace MoonTools.ECS
public void Clear()
{
Count = 0;
indices.Clear();
count = 0;
}
public struct Enumerator
protected virtual void Dispose(bool disposing)
{
/// <summary>The set being enumerated.</summary>
private readonly IndexableSet<T> _set;
/// <summary>The next index to yield.</summary>
private int _index;
/// <summary>Initialize the enumerator.</summary>
/// <param name="set">The set to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(IndexableSet<T> set)
if (!disposed)
{
_set = set;
_index = _set.Count;
}
NativeMemory.Free(array);
array = null;
/// <summary>Advances the enumerator to the next element of the span.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int index = _index - 1;
if (index >= 0)
{
_index = index;
return true;
}
return false;
disposed = true;
}
}
/// <summary>Gets the element at the current position of the enumerator.</summary>
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _set[_index];
}
~IndexableSet()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -8,17 +11,18 @@ namespace MoonTools.ECS
public abstract void Clear();
}
internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged
internal unsafe class MessageStorage<TMessage> : MessageStorage, IDisposable where TMessage : unmanaged
{
private int count = 0;
private int capacity = 128;
private TMessage[] messages;
private TMessage* messages;
// duplicating storage here for fast iteration
private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>();
private Dictionary<int, NativeArray<TMessage>> entityToMessages = new Dictionary<int, NativeArray<TMessage>>();
private bool disposed;
public MessageStorage()
{
messages = new TMessage[capacity];
messages = (TMessage*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TMessage>()));
}
public void Add(in TMessage message)
@ -26,7 +30,7 @@ namespace MoonTools.ECS
if (count == capacity)
{
capacity *= 2;
Array.Resize(ref messages, capacity);
messages = (TMessage*) NativeMemory.Realloc(messages, (nuint) (capacity * Unsafe.SizeOf<TMessage>()));
}
messages[count] = message;
@ -37,7 +41,7 @@ namespace MoonTools.ECS
{
if (!entityToMessages.ContainsKey(entityID))
{
entityToMessages.Add(entityID, new DynamicArray<TMessage>());
entityToMessages.Add(entityID, new NativeArray<TMessage>());
}
entityToMessages[entityID].Add(message);
@ -51,7 +55,7 @@ namespace MoonTools.ECS
public ReadOnlySpan<TMessage> All()
{
return new ReadOnlySpan<TMessage>(messages, 0, count);
return new ReadOnlySpan<TMessage>(messages, count);
}
public TMessage First()
@ -89,5 +93,39 @@ namespace MoonTools.ECS
set.Clear();
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
Clear();
if (disposing)
{
foreach (var array in entityToMessages.Values)
{
array.Dispose();
}
}
NativeMemory.Free(messages);
messages = null;
disposed = true;
}
}
~MessageStorage()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,77 +1,209 @@
using System;
using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
public static class RandomGenerator
/// <summary>
/// This class implements the well equidistributed long-period linear pseudorandom number generator.
/// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf
/// </summary>
public class Random
{
private static Random random = new Random();
public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int
private static int[] Primes =
{
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919
};
uint[] State = new uint[16];
uint Index = 0;
uint Seed;
public static void SetSeed(int seed)
/// <summary>
/// Initializes the RNG with an arbitrary seed.
/// </summary>
public Random()
{
random = new Random(seed);
}
internal static int Next(int maxValue)
{
return random.Next(maxValue);
Init((uint) Environment.TickCount);
}
/// <summary>
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
/// Initializes the RNG with a given seed.
/// </summary>
internal static LinearCongruentialEnumerator LinearCongruentialGenerator(int n)
public void Init(uint seed)
{
var x = Primes[random.Next(Primes.Length - 1)];
while (x % n == 0)
Seed = seed;
uint s = seed;
for (int i = 0; i < 16; i++)
{
// not coprime, try again
x = Primes[random.Next(Primes.Length - 1)];
s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0;
State[i] = ~ ~s; //i ;
}
return new LinearCongruentialEnumerator(random.Next(n), x, n);
}
}
public struct LinearCongruentialEnumerator
{
private readonly int start;
private readonly int count;
private readonly int prime;
private int current;
public LinearCongruentialEnumerator GetEnumerator() => this;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal LinearCongruentialEnumerator(int start, int prime, int count)
{
current = start;
this.start = start;
this.prime = prime;
this.count = count;
Index = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
/// <summary>
/// Returns the seed that was used to initialize the RNG.
/// </summary>
public uint GetSeed()
{
current += 1;
if (current < start + count)
return Seed;
}
/// <summary>
/// Returns the entire state of the RNG as a string.
/// </summary>
public string PrintState()
{
var s = "";
for (var i = 0; i < 16; i += 1)
{
return true;
s += State[i];
}
return false;
s += Index;
return s;
}
public int Current
/// <summary>
/// Saves the entire state of the RNG to a Span.
/// </summary>
/// <param name="bytes">Must be a span of at least STATE_BYTE_COUNT bytes.</param>
/// <exception cref="ArgumentException">Thrown if the byte span is too short.</exception>
public unsafe void SaveState(Span<byte> bytes)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (current * prime) % count;
#if DEBUG
if (bytes.Length < STATE_BYTE_COUNT)
{
throw new ArgumentException("Byte span too short!");
}
#endif
fixed (byte* ptr = bytes)
{
var offset = 0;
for (var i = 0; i < 16; i += 1)
{
Unsafe.Write(ptr + offset, State[i]);
offset += 4;
}
Unsafe.Write(ptr + offset, Index);
}
}
/// <summary>
/// Loads the entire state of the RNG from a Span.
/// </summary>
/// <param name="bytes">Must be a span of at least STATE_BYTE_COUNT bytes.</param>
/// <exception cref="ArgumentException">Thrown if the byte span is too short.</exception>
public unsafe void LoadState(Span<byte> bytes)
{
#if DEBUG
if (bytes.Length < STATE_BYTE_COUNT)
{
throw new ArgumentException("Byte span too short!");
}
#endif
fixed (byte* ptr = bytes)
{
var offset = 0;
for (var i = 0; i < 16; i += 1)
{
State[i] = Unsafe.Read<uint>(ptr + offset);
offset += 4;
}
Index = Unsafe.Read<uint>(ptr + offset);
}
}
private uint NextInternal()
{
uint a, b, c, d;
a = State[Index];
c = State[(Index+13)&15];
b = a^c^(a<<16)^(c<<15);
c = State[(Index+9)&15];
c ^= (c>>11);
a = State[Index] = b^c;
d = (uint) (a ^((a<<5)&0xDA442D24UL));
Index = (Index + 15)&15;
a = State[Index];
State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
return State[Index];
}
/// <summary>
/// Returns a non-negative signed integer.
/// </summary>
public int Next()
{
return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit
}
/// <summary>
/// Returns a non-negative signed integer less than max.
/// </summary>
public int Next(int max)
{
return (int) (((double) Next()) * max / int.MaxValue);
}
/// <summary>
/// Returns a signed integer greater than or equal to min and less than max.
/// </summary>
public int Next(int min, int max)
{
var diff = max - min;
var next = Next(diff);
return min + next;
}
/// <summary>
/// Returns a non-negative signed 64 bit integer.
/// </summary>
public long NextInt64()
{
long next = NextInternal();
next <<= 32;
next |= NextInternal();
next >>>= 1;
return next;
}
/// <summary>
/// Returns a non-negative signed 64 bit integer less than max.
/// </summary>
public long NextInt64(long max)
{
var next = NextInt64();
return (long) (((double) next) * max / long.MaxValue);
}
/// <summary>
/// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max.
/// </summary>
public long NextInt64(long min, long max)
{
var diff = max - min;
var next = NextInt64(diff);
return min + next;
}
/// <summary>
/// Returns a single-precision floating point value between 0 and 1.
/// </summary>
public float NextSingle()
{
var n = NextInternal();
return ((float) n) / uint.MaxValue;
}
/// <summary>
/// Returns a double-precision floating point value between 0 and 1.
/// </summary>
public double NextDouble()
{
var n = NextInternal();
return ((double) n) / uint.MaxValue;
}
}
}

82
src/RandomManager.cs Normal file
View File

@ -0,0 +1,82 @@
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
public static class RandomManager
{
private static Random Random = new Random();
private static int[] Primes =
{
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999,3001,3011,3019,3023,3037,3041,3049,3061,3067,3079,3083,3089,3109,3119,3121,3137,3163,3167,3169,3181,3187,3191,3203,3209,3217,3221,3229,3251,3253,3257,3259,3271,3299,3301,3307,3313,3319,3323,3329,3331,3343,3347,3359,3361,3371,3373,3389,3391,3407,3413,3433,3449,3457,3461,3463,3467,3469,3491,3499,3511,3517,3527,3529,3533,3539,3541,3547,3557,3559,3571,3581,3583,3593,3607,3613,3617,3623,3631,3637,3643,3659,3671,3673,3677,3691,3697,3701,3709,3719,3727,3733,3739,3761,3767,3769,3779,3793,3797,3803,3821,3823,3833,3847,3851,3853,3863,3877,3881,3889,3907,3911,3917,3919,3923,3929,3931,3943,3947,3967,3989,4001,4003,4007,4013,4019,4021,4027,4049,4051,4057,4073,4079,4091,4093,4099,4111,4127,4129,4133,4139,4153,4157,4159,4177,4201,4211,4217,4219,4229,4231,4241,4243,4253,4259,4261,4271,4273,4283,4289,4297,4327,4337,4339,4349,4357,4363,4373,4391,4397,4409,4421,4423,4441,4447,4451,4457,4463,4481,4483,4493,4507,4513,4517,4519,4523,4547,4549,4561,4567,4583,4591,4597,4603,4621,4637,4639,4643,4649,4651,4657,4663,4673,4679,4691,4703,4721,4723,4729,4733,4751,4759,4783,4787,4789,4793,4799,4801,4813,4817,4831,4861,4871,4877,4889,4903,4909,4919,4931,4933,4937,4943,4951,4957,4967,4969,4973,4987,4993,4999,5003,5009,5011,5021,5023,5039,5051,5059,5077,5081,5087,5099,5101,5107,5113,5119,5147,5153,5167,5171,5179,5189,5197,5209,5227,5231,5233,5237,5261,5273,5279,5281,5297,5303,5309,5323,5333,5347,5351,5381,5387,5393,5399,5407,5413,5417,5419,5431,5437,5441,5443,5449,5471,5477,5479,5483,5501,5503,5507,5519,5521,5527,5531,5557,5563,5569,5573,5581,5591,5623,5639,5641,5647,5651,5653,5657,5659,5669,5683,5689,5693,5701,5711,5717,5737,5741,5743,5749,5779,5783,5791,5801,5807,5813,5821,5827,5839,5843,5849,5851,5857,5861,5867,5869,5879,5881,5897,5903,5923,5927,5939,5953,5981,5987,6007,6011,6029,6037,6043,6047,6053,6067,6073,6079,6089,6091,6101,6113,6121,6131,6133,6143,6151,6163,6173,6197,6199,6203,6211,6217,6221,6229,6247,6257,6263,6269,6271,6277,6287,6299,6301,6311,6317,6323,6329,6337,6343,6353,6359,6361,6367,6373,6379,6389,6397,6421,6427,6449,6451,6469,6473,6481,6491,6521,6529,6547,6551,6553,6563,6569,6571,6577,6581,6599,6607,6619,6637,6653,6659,6661,6673,6679,6689,6691,6701,6703,6709,6719,6733,6737,6761,6763,6779,6781,6791,6793,6803,6823,6827,6829,6833,6841,6857,6863,6869,6871,6883,6899,6907,6911,6917,6947,6949,6959,6961,6967,6971,6977,6983,6991,6997,7001,7013,7019,7027,7039,7043,7057,7069,7079,7103,7109,7121,7127,7129,7151,7159,7177,7187,7193,7207,7211,7213,7219,7229,7237,7243,7247,7253,7283,7297,7307,7309,7321,7331,7333,7349,7351,7369,7393,7411,7417,7433,7451,7457,7459,7477,7481,7487,7489,7499,7507,7517,7523,7529,7537,7541,7547,7549,7559,7561,7573,7577,7583,7589,7591,7603,7607,7621,7639,7643,7649,7669,7673,7681,7687,7691,7699,7703,7717,7723,7727,7741,7753,7757,7759,7789,7793,7817,7823,7829,7841,7853,7867,7873,7877,7879,7883,7901,7907,7919
};
public static void SetRandom(Random random)
{
Random = random;
}
internal static int Next(int maxValue)
{
return Random.Next(maxValue);
}
/// <summary>
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
/// </summary>
internal static LinearCongruentialEnumerator LinearCongruentialSequence(int n)
{
if (n == 0)
{
// bail out, empty enumerator
return new LinearCongruentialEnumerator(0, 0, 0);
}
var x = Primes[Random.Next(Primes.Length - 1)];
while (x % n == 0)
{
// not coprime, try again
x = Primes[Random.Next(Primes.Length - 1)];
}
return new LinearCongruentialEnumerator(Random.Next(n), x, n);
}
}
public struct LinearCongruentialEnumerator
{
private readonly int start;
private readonly int count;
private readonly int prime;
private int current;
public LinearCongruentialEnumerator GetEnumerator() => this;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal LinearCongruentialEnumerator(int start, int prime, int count)
{
current = start;
this.start = start;
this.prime = prime;
this.count = count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
current += 1;
if (current < start + count)
{
return true;
}
return false;
}
public int Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (current * prime) % count;
}
}
}

View File

@ -6,11 +6,13 @@ namespace MoonTools.ECS
{
internal class RelationDepot
{
private EntityStorage EntityStorage;
private TypeIndices RelationTypeIndices;
private RelationStorage[] storages = new RelationStorage[256];
public RelationDepot(TypeIndices relationTypeIndices)
public RelationDepot(EntityStorage entityStorage, TypeIndices relationTypeIndices)
{
EntityStorage = entityStorage;
RelationTypeIndices = relationTypeIndices;
}
@ -123,26 +125,11 @@ namespace MoonTools.ECS
storages[relationTypeIndex].Set(entityA, entityB, relationData);
}
public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB)
{
return storages[relationTypeIndex].GetStorageIndex(entityA, entityB);
}
public unsafe void* Get(int relationTypeIndex, int relationStorageIndex)
{
return storages[relationTypeIndex].Get(relationStorageIndex);
}
public void UnrelateAll(int entityID, int relationTypeIndex)
{
storages[relationTypeIndex].UnrelateAll(entityID);
}
public ReverseSpanEnumerator<Entity> OutRelations(int entityID, int relationTypeIndex)
{
return storages[relationTypeIndex].OutRelations(entityID);
}
public void Clear()
{
for (var i = 0; i < storages.Length; i += 1)
@ -174,5 +161,33 @@ namespace MoonTools.ECS
}
}
}
public unsafe void TransferStorage(Dictionary<int, int> worldToTransferID, RelationDepot other)
{
for (var i = 0; i < storages.Length; i += 1)
{
if (storages[i] != null)
{
foreach (var (a, b) in storages[i].All())
{
if (worldToTransferID.TryGetValue(a, out var otherA))
{
if (worldToTransferID.TryGetValue(b, out var otherB))
{
var storageIndex = storages[i].GetStorageIndex(a, b);
var relationData = storages[i].Get(storageIndex);
other.Set(otherA, otherB, i, relationData);
other.EntityStorage.AddRelationKind(otherA, i);
other.EntityStorage.AddRelationKind(otherB, i);
}
else
{
throw new InvalidOperationException($"Missing transfer entity! {EntityStorage.Tag(a.ID)} related to {EntityStorage.Tag(b.ID)}");
}
}
}
}
}
}
}
}

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
@ -9,26 +12,35 @@ namespace MoonTools.ECS
public abstract int GetStorageIndex(int entityA, int entityB);
public abstract unsafe void* Get(int relationStorageIndex);
public abstract void UnrelateAll(int entityID);
public abstract ReverseSpanEnumerator<Entity> OutRelations(int entityID);
public abstract ReverseSpanEnumerator<(Entity, Entity)> All();
public abstract RelationStorage CreateStorage();
public abstract void Clear();
}
// Relation is the two entities, A related to B.
// TRelation is the data attached to the relation.
internal class RelationStorage<TRelation> : RelationStorage where TRelation : unmanaged
internal unsafe class RelationStorage<TRelation> : RelationStorage, IDisposable where TRelation : unmanaged
{
private int count = 0;
private int capacity = 16;
private (Entity, Entity)* relations;
private TRelation* relationDatas;
private Dictionary<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16);
private (Entity, Entity)[] relations = new (Entity, Entity)[16];
private TRelation[] relationDatas = new TRelation[16];
private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>();
public ReverseSpanEnumerator<(Entity, Entity)> All()
private bool disposed;
public RelationStorage()
{
return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count));
relations = ((Entity, Entity)*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>()));
relationDatas = (TRelation*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<TRelation>()));
}
public override ReverseSpanEnumerator<(Entity, Entity)> All()
{
return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, count));
}
public void Set(in Entity entityA, in Entity entityB, TRelation relationData)
@ -56,10 +68,11 @@ namespace MoonTools.ECS
}
inRelations[idB].Add(idA);
if (count >= relationDatas.Length)
if (count >= capacity)
{
Array.Resize(ref relations, relations.Length * 2);
Array.Resize(ref relationDatas, relationDatas.Length * 2);
capacity *= 2;
relations = ((Entity, Entity)*) NativeMemory.Realloc(relations, (nuint) (capacity * Unsafe.SizeOf<(Entity, Entity)>()));
relationDatas = (TRelation*) NativeMemory.Realloc(relationDatas, (nuint) (capacity * Unsafe.SizeOf<TRelation>()));
}
relations[count] = relation;
@ -78,7 +91,7 @@ namespace MoonTools.ECS
return indices.ContainsKey(relation);
}
public override ReverseSpanEnumerator<Entity> OutRelations(int entityID)
public ReverseSpanEnumerator<Entity> OutRelations(int entityID)
{
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
{
@ -219,7 +232,7 @@ namespace MoonTools.ECS
public override unsafe void Set(int entityA, int entityB, void* relationData)
{
Set(entityA, entityB, *((TRelation*) relationData));
Set(entityA, entityB, *(TRelation*) relationData);
}
public override int GetStorageIndex(int entityA, int entityB)
@ -229,10 +242,7 @@ namespace MoonTools.ECS
public override unsafe void* Get(int relationStorageIndex)
{
fixed (void* p = &relations[relationStorageIndex])
{
return p;
}
return &relationDatas[relationStorageIndex];
}
public override void UnrelateAll(int entityID)
@ -282,5 +292,41 @@ namespace MoonTools.ECS
}
outRelations.Clear();
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
Clear();
if (disposing)
{
foreach (var set in listPool)
{
set.Dispose();
}
}
NativeMemory.Free(relations);
NativeMemory.Free(relationDatas);
relations = null;
relationDatas = null;
disposed = true;
}
}
~RelationStorage()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -17,7 +17,7 @@ namespace MoonTools.ECS
public World()
{
ComponentDepot = new ComponentDepot(ComponentTypeIndices);
RelationDepot = new RelationDepot(RelationTypeIndices);
RelationDepot = new RelationDepot(EntityStorage, RelationTypeIndices);
FilterStorage = new FilterStorage(EntityStorage, ComponentTypeIndices);
}
@ -54,14 +54,11 @@ namespace MoonTools.ECS
}
// untyped version for Transfer
// no filter check because filter state is copied directly
internal unsafe void Set(Entity entity, int componentTypeIndex, void* component)
{
ComponentDepot.Set(entity.ID, componentTypeIndex, component);
if (EntityStorage.SetComponent(entity.ID, componentTypeIndex))
{
FilterStorage.Check(entity.ID, componentTypeIndex);
}
EntityStorage.SetComponent(entity.ID, componentTypeIndex);
}
public void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
@ -82,14 +79,6 @@ namespace MoonTools.ECS
EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex);
}
// untyped version for Transfer
internal unsafe void Relate(Entity entityA, Entity entityB, int relationTypeIndex, void* relationData)
{
RelationDepot.Set(entityA, entityB, relationTypeIndex, relationData);
EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex);
EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex);
}
public void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(entityA, entityB);
@ -156,6 +145,14 @@ namespace MoonTools.ECS
private Dictionary<int, int> WorldToTransferID = new Dictionary<int, int>();
/// <summary>
/// If you are using the World Transfer feature, call this once after your systems/filters have all been initialized.
/// </summary>
public void PrepareTransferTo(World other)
{
other.FilterStorage.CreateMissingStorages(FilterStorage);
}
// FIXME: there's probably a better way to handle Filters so they are not world-bound
public unsafe void Transfer(World other, Filter filter, Filter otherFilter)
{
@ -176,30 +173,8 @@ namespace MoonTools.ECS
WorldToTransferID.Add(entity.ID, otherWorldEntity.ID);
}
// set relations before components so filters don't freak out
foreach (var entity in filter.Entities)
{
var otherWorldEntityA = WorldToTransferID[entity.ID];
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
{
foreach (var entityB in RelationDepot.OutRelations(entity.ID, relationTypeIndex))
{
var storageIndex = RelationDepot.GetStorageIndex(relationTypeIndex, entity.ID, entityB);
int otherWorldEntityB;
if (WorldToTransferID.TryGetValue(entityB, out otherWorldEntityB))
{
other.Relate(otherWorldEntityA, otherWorldEntityB, relationTypeIndex, RelationDepot.Get(relationTypeIndex, storageIndex));
}
else
{
// related entity is not in the filter
throw new Exception($"Missing transfer entity! {EntityStorage.Tag(entity.ID)} related to {EntityStorage.Tag(entityB.ID)}");
}
}
}
}
// transfer relations
RelationDepot.TransferStorage(WorldToTransferID, other.RelationDepot);
// set components
foreach (var entity in filter.Entities)
@ -211,6 +186,9 @@ namespace MoonTools.ECS
other.Set(otherWorldEntity, componentTypeIndex, ComponentDepot.UntypedGet(entity.ID, componentTypeIndex));
}
}
// transfer filters last so callbacks trigger correctly
FilterStorage.TransferStorage(WorldToTransferID, other.FilterStorage);
}
}
}