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: #5set_order
parent
839ad015ba
commit
4ef7cb4302
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,12 +177,14 @@ namespace MoonTools.ECS
|
|||
}
|
||||
}
|
||||
|
||||
filterSignatureToEntityIDs[filterSignature].Add(entityID);
|
||||
if (filterSignatureToEntityIDs[filterSignature].Add(entityID))
|
||||
{
|
||||
if (addCallbacks.TryGetValue(filterSignature, out var addCallback))
|
||||
{
|
||||
addCallback(entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveEntity(int entityID, int componentTypeIndex)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
if (!disposed)
|
||||
{
|
||||
NativeMemory.Free(array);
|
||||
array = null;
|
||||
|
||||
/// <summary>Initialize the enumerator.</summary>
|
||||
/// <param name="set">The set to enumerate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Enumerator(IndexableSet<T> set)
|
||||
{
|
||||
_set = set;
|
||||
_index = _set.Count;
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the next element of the span.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
~IndexableSet()
|
||||
{
|
||||
int index = _index - 1;
|
||||
if (index >= 0)
|
||||
{
|
||||
_index = index;
|
||||
return true;
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Gets the element at the current position of the enumerator.</summary>
|
||||
public T Current
|
||||
public void Dispose()
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _set[_index];
|
||||
}
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
224
src/Random.cs
224
src/Random.cs
|
@ -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 ;
|
||||
}
|
||||
Index = 0;
|
||||
}
|
||||
|
||||
return new LinearCongruentialEnumerator(random.Next(n), x, n);
|
||||
/// <summary>
|
||||
/// Returns the seed that was used to initialize the RNG.
|
||||
/// </summary>
|
||||
public uint GetSeed()
|
||||
{
|
||||
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)
|
||||
{
|
||||
s += State[i];
|
||||
}
|
||||
s += Index;
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
public struct LinearCongruentialEnumerator
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
#if DEBUG
|
||||
if (bytes.Length < STATE_BYTE_COUNT)
|
||||
{
|
||||
current = start;
|
||||
this.start = start;
|
||||
this.prime = prime;
|
||||
this.count = 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;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MoveNext()
|
||||
{
|
||||
current += 1;
|
||||
if (current < start + count)
|
||||
{
|
||||
return true;
|
||||
Index = Unsafe.Read<uint>(ptr + offset);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
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];
|
||||
}
|
||||
|
||||
public int Current
|
||||
/// <summary>
|
||||
/// Returns a non-negative signed integer.
|
||||
/// </summary>
|
||||
public int Next()
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (current * prime) % count;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
54
src/World.cs
54
src/World.cs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue