Compare commits

...

31 Commits

Author SHA1 Message Date
cosmonaut 3e60d2dff2 reset entity tag on Destroy 2024-02-27 15:48:00 -08:00
cosmonaut 3b460cf326 fix crash in Debug_GetEntities 2024-02-27 15:47:26 -08:00
cosmonaut 079765d009 fix off by one error on LinearCongruentialEnumerator 2024-01-30 17:10:55 -08:00
cosmonaut 0d567f1854 allow Manipulator to send messages 2024-01-18 15:58:28 -08:00
cosmonaut 9b90d47d01 remove Platforms directive from csproj 2024-01-18 15:58:17 -08:00
cosmonaut 842398b237 mark type assigners as internal 2023-12-20 15:16:49 -08:00
cosmonaut b31120310c Eliminating dictionary lookups (#7)
Did some storage restructuring to avoid dictionary lookups. This should be a fairly significant speedup, particularly on the type lookups which now use a clever static generic lookup.

Reviewed-on: #7
2023-12-20 23:12:05 +00:00
cosmonaut 2a1f265f84 .NET 8 2023-11-21 15:23:59 -08:00
cosmonaut a5c15305f1 re-add TimeSpan to System for compatibility 2023-11-21 11:56:04 -08:00
cosmonaut fc2120cca9 Storage Rewrite (#6)
- add Snapshot with Take and Restore methods
- remove World.Transfer
- components can now be accessed via `ref`
- clients should call World.Dispose when they are done with a World
- rewrite ECS storage to use fewer classes
- all ECS storage methods now exposed in World
- removed filter callbacks
- removed MessageWithEntity methods
- various data structure optimizations

Reviewed-on: #6
2023-11-21 19:50:17 +00:00
cosmonaut 4ef7cb4302 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
2023-10-13 20:42:22 +00:00
cosmonaut 839ad015ba add RandomGenerator.SetSeed method 2023-07-27 14:53:50 -07:00
cosmonaut c9643ff0be fix crash in World.Clear 2023-07-27 14:53:33 -07:00
cosmonaut 2bf2128d07 remove unused reference to TemplateComponentDepot 2023-07-21 14:43:54 -07:00
cosmonaut b5e28ae69f add World.Clear 2023-07-21 14:41:53 -07:00
cosmonaut 4d45d05618 World Transfer + Entity Tags (#4)
- removed Snapshot system
- Entities can now be transferred between Worlds along with their Components and Relations using a Filter
- Entities can now be given a string tag on creation
- Manipulators can update an Entity's string tag
2023-07-10 22:36:34 +00:00
cosmonaut f7d4fcdee7 add Manipulator, remove Spawner, remove Template 2023-05-24 12:46:27 -07:00
cosmonaut cc158e460b add license 2023-05-22 11:22:39 -07:00
cosmonaut c68e16456e run Filter callbacks before updating component storage 2023-05-19 13:24:26 -07:00
cosmonaut 4268762a0c fix EntitiesInRandomOrder 2023-04-06 14:54:20 -07:00
cosmonaut 5f0d694eb4 fix garbage in FilterSignature.Equals 2023-04-05 12:08:24 -07:00
cosmonaut ca912a3b5a fix crash when storageIndex > 256 2023-03-21 15:31:59 -07:00
cosmonaut 3817a9e809 get relation by index 2023-03-20 14:26:27 -07:00
cosmonaut 1a6d015fff allow Spawners to read world state 2023-02-10 11:59:40 -08:00
cosmonaut cf75824d67 Fix entity relation inconsistency on destroy 2023-02-09 15:15:55 -08:00
cosmonaut f534ff145e Fix InRelationSingleton error message 2023-02-09 15:15:41 -08:00
cosmonaut 02ccc3d1a5 add World.Relate + Spawner object 2023-02-09 10:50:25 -08:00
cosmonaut 4ff546538b fix false positive on filter remove callback 2023-01-27 09:48:27 -08:00
cosmonaut 1438188dca add filter callback system 2023-01-26 16:34:15 -08:00
cosmonaut e4131d58f5 fix new hashsets being allocated on each entity create 2023-01-10 12:41:16 -08:00
cosmonaut f69d132a5e Storage refactor, snapshot system, experimental template system (#3)
- Major storage refactor to improve performance and reduce garbage collection
- Snapshot system to facilitate rollback implementation
- Experimental template system to instantiate entities based on a template. Use at your own risk, this may change significantly or be removed!

Reviewed-on: #3
2023-01-10 00:41:00 +00:00
38 changed files with 2336 additions and 1668 deletions

23
LICENSE.txt Normal file
View File

@ -0,0 +1,23 @@
MoonTools.ECS - A very simple ECS system for C#
Copyright (c) 2022-2023 Evan Hemsley
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Evan "cosmonaut" Hemsley <evan@moonside.games>

View File

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS.Collections;
public unsafe class IndexableSet<T> : IDisposable where T : unmanaged
{
private Dictionary<T, int> Indices;
private T* Array;
public int Count { get; private set; }
private int Capacity;
private bool IsDisposed;
public Span<T> AsSpan() => new Span<T>(Array, Count);
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count));
public IndexableSet(int capacity = 32)
{
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] => Array[i];
public bool Contains(T element)
{
return Indices.ContainsKey(element);
}
public bool Add(T element)
{
if (!Contains(element))
{
Indices.Add(element, Count);
if (Count >= Capacity)
{
Capacity *= 2;
Array = (T*) NativeMemory.Realloc(Array, (nuint) (Capacity * Unsafe.SizeOf<T>()));
}
Array[Count] = element;
Count += 1;
return true;
}
return false;
}
public bool Remove(T element)
{
if (!Contains(element))
{
return false;
}
var index = Indices[element];
for (var i = index; i < Count - 1; i += 1)
{
Array[i] = Array[i + 1];
Indices[Array[i]] = i;
}
Indices.Remove(element);
Count -= 1;
return true;
}
public void Clear()
{
Indices.Clear();
Count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
NativeMemory.Free(Array);
Array = null;
IsDisposed = true;
}
}
~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

@ -0,0 +1,120 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS.Collections;
public unsafe class NativeArray<T> : IDisposable where T : unmanaged
{
private T* Elements;
public int Count { get; private set;}
private int Capacity;
private int ElementSize;
public Span<T> ToSpan() => new Span<T>(Elements, Count);
public Span<T>.Enumerator GetEnumerator() => new Span<T>(Elements, Count).GetEnumerator();
private bool IsDisposed;
public NativeArray(int capacity = 16)
{
this.Capacity = capacity;
ElementSize = Unsafe.SizeOf<T>();
Elements = (T*) NativeMemory.Alloc((nuint) (capacity * ElementSize));
Count = 0;
}
public ref T this[int i] => ref Elements[i];
public void Append(T item)
{
if (Count >= Capacity)
{
Capacity *= 2;
Elements = (T*) NativeMemory.Realloc(Elements, (nuint) (Capacity * Unsafe.SizeOf<T>()));
}
Elements[Count] = item;
Count += 1;
}
public void RemoveLastElement()
{
Count -= 1;
}
public bool TryPop(out T element)
{
if (Count > 0)
{
element = Elements[Count - 1];
Count -= 1;
return true;
}
element = default;
return false;
}
public void Clear()
{
Count = 0;
}
private void ResizeTo(int size)
{
Capacity = size;
Elements = (T*) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity));
}
// Fills gap by copying final element to the deleted index
public void Delete(int index)
{
if (index != Count - 1)
{
this[index] = this[Count - 1];
}
Count -= 1;
}
public void CopyTo(NativeArray<T> other)
{
if (Count >= other.Capacity)
{
other.ResizeTo(Count);
}
NativeMemory.Copy(
(void*) Elements,
(void*) other.Elements,
(nuint) (ElementSize * Count)
);
other.Count = Count;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
NativeMemory.Free(Elements);
Elements = null;
IsDisposed = 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

@ -0,0 +1,121 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS.Collections;
internal unsafe class NativeArray : IDisposable
{
private nint Elements;
public int Count { get; private set;}
private int Capacity;
public readonly int ElementSize;
private bool IsDisposed;
public NativeArray(int elementSize)
{
Capacity = 16;
Count = 0;
ElementSize = elementSize;
Elements = (nint) NativeMemory.Alloc((nuint) (ElementSize * Capacity));
}
public Span<T> ToSpan<T>()
{
return new Span<T>((void*) Elements, Count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Get<T>(int i) where T : unmanaged
{
return ref ((T*) Elements)[i];
}
public void Set<T>(int i, in T element) where T : unmanaged
{
Unsafe.Write((void*) (Elements + ElementSize * i), element);
}
private void Resize()
{
Capacity *= 2;
Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity));
}
private void ResizeTo(int capacity)
{
Capacity = capacity;
Elements = (nint) NativeMemory.Realloc((void*) Elements, (nuint) (ElementSize * Capacity));
}
// Fills gap by copying final element to the deleted index
public void Delete(int index)
{
if (index != Count - 1)
{
NativeMemory.Copy(
(void*) (Elements + ((Count - 1) * ElementSize)),
(void*) (Elements + (index * ElementSize)),
(nuint) ElementSize
);
}
Count -= 1;
}
public void Append<T>(T component) where T : unmanaged
{
if (Count >= Capacity)
{
Resize();
}
((T*) Elements)[Count] = component;
Count += 1;
}
public void CopyAllTo(NativeArray other)
{
if (Count >= other.Capacity)
{
other.ResizeTo(Count);
}
NativeMemory.Copy(
(void*) Elements,
(void*) other.Elements,
(nuint) (ElementSize * Count)
);
other.Count = Count;
}
public void Clear()
{
Count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
NativeMemory.Free((void*) Elements);
IsDisposed = 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,303 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
internal class ComponentDepot
{
private Dictionary<Type, ComponentStorage> storages = new Dictionary<Type, ComponentStorage>();
private Dictionary<FilterSignature, IndexableSet<int>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<int>>();
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
#if DEBUG
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
#endif
private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Register<TComponent>() where TComponent : unmanaged
{
if (!storages.ContainsKey(typeof(TComponent)))
{
storages.Add(typeof(TComponent), new ComponentStorage<TComponent>());
#if DEBUG
singleComponentFilters.Add(typeof(TComponent), CreateFilter(new HashSet<Type>() { typeof(TComponent) }, new HashSet<Type>()));
#endif
}
}
private ComponentStorage Lookup(Type type)
{
return storages[type];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
{
// TODO: is it possible to optimize this?
Register<TComponent>();
return (ComponentStorage<TComponent>) storages[typeof(TComponent)];
}
public bool Some<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().Any();
}
public bool Has<TComponent>(int entityID) where TComponent : unmanaged
{
return Lookup<TComponent>().Has(entityID);
}
private bool Has(Type type, int entityID)
{
return Lookup(type).Has(entityID);
}
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get(entityID);
}
public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get();
}
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
{
var existed = Lookup<TComponent>().Set(entityID, component);
// update filters
if (!existed)
{
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
CheckFilter(filterSignature, entityID);
}
}
}
}
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().FirstEntity();
}
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().AllComponents();
}
private void Remove(Type type, int entityID)
{
var existed = Lookup(type).Remove(entityID);
// update filters
if (existed)
{
if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
CheckFilter(filterSignature, entityID);
}
}
}
}
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
{
var existed = Lookup<TComponent>().Remove(entityID);
// update filters
if (existed)
{
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
CheckFilter(filterSignature, entityID);
}
}
}
}
// TODO: is there some way to optimize this without complicating serialization?
public void OnEntityDestroy(int entityID)
{
foreach (var type in storages.Keys)
{
Remove(type, entityID);
}
}
public Filter CreateFilter(HashSet<Type> included, HashSet<Type> excluded)
{
var filterSignature = new FilterSignature(included, excluded);
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
{
filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<int>());
foreach (var type in included)
{
if (!typeToFilterSignatures.ContainsKey(type))
{
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
typeToFilterSignatures[type].Add(filterSignature);
}
foreach (var type in excluded)
{
if (!typeToFilterSignatures.ContainsKey(type))
{
typeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
typeToFilterSignatures[type].Add(filterSignature);
}
}
return new Filter(this, included, excluded);
}
// FIXME: this dictionary should probably just store entities
public IEnumerable<Entity> FilterEntities(Filter filter)
{
foreach (var id in filterSignatureToEntityIDs[filter.Signature])
{
yield return new Entity(id);
}
}
public IEnumerable<Entity> FilterEntitiesRandom(Filter filter)
{
foreach (var index in RandomGenerator.LinearCongruentialGenerator(FilterCount(filter)))
{
yield return new Entity(filterSignatureToEntityIDs[filter.Signature][index]);
}
}
public Entity FilterNthEntity(Filter filter, int index)
{
return new Entity(filterSignatureToEntityIDs[filter.Signature][index]);
}
public Entity FilterRandomEntity(Filter filter)
{
var randomIndex = RandomGenerator.Next(FilterCount(filter));
return new Entity(filterSignatureToEntityIDs[filter.Signature][randomIndex]);
}
public int FilterCount(Filter filter)
{
return filterSignatureToEntityIDs[filter.Signature].Count;
}
private void CheckFilter(FilterSignature filterSignature, int entityID)
{
foreach (var type in filterSignature.Included)
{
if (!Has(type, entityID))
{
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
return;
}
}
foreach (var type in filterSignature.Excluded)
{
if (Has(type, entityID))
{
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
return;
}
}
filterSignatureToEntityIDs[filterSignature].Add(entityID);
}
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
TypesWithDisabledSerialization.Add(typeof(TComponent));
}
public void Save(ComponentDepotState state)
{
foreach (var (type, storage) in storages)
{
if (!TypesWithDisabledSerialization.Contains(type))
{
if (!state.StorageStates.ContainsKey(type))
{
state.StorageStates.Add(type, storage.CreateState());
}
storage.Save(state.StorageStates[type]);
}
}
foreach (var (signature, set) in filterSignatureToEntityIDs)
{
// FIXME: we could cache this
if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization))
{
if (!state.FilterStates.ContainsKey(signature))
{
state.FilterStates[signature] = new IndexableSetState<int>(set.Count);
}
set.Save(state.FilterStates[signature]);
}
}
}
public void Load(ComponentDepotState state)
{
foreach (var (type, storageState) in state.StorageStates)
{
storages[type].Load(storageState);
}
foreach (var (signature, setState) in state.FilterStates)
{
filterSignatureToEntityIDs[signature].Load(setState);
}
}
#if DEBUG
public IEnumerable<object> Debug_GetAllComponents(int entityID)
{
foreach (var (type, storage) in storages)
{
if (storage.Has(entityID))
{
yield return storage.Debug_Get(entityID);
}
}
}
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
{
return singleComponentFilters[componentType].Entities;
}
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
foreach (var type in storages.Keys)
{
if (type.ToString().ToLower().Contains(typeString.ToLower()))
{
yield return type;
}
}
}
#endif
}
}

View File

@ -1,170 +1,142 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
internal class ComponentStorage : IDisposable
{
internal abstract class ComponentStorage
internal readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(16);
internal readonly NativeArray Components;
internal readonly NativeArray<Entity> EntityIDs;
internal readonly TypeId TypeId;
internal readonly int ElementSize;
private bool IsDisposed;
public ComponentStorage(TypeId typeId, int elementSize)
{
public abstract bool Has(int entityID);
public abstract bool Remove(int entityID);
public abstract object Debug_Get(int entityID);
public abstract ComponentStorageState CreateState();
public abstract void Save(ComponentStorageState state);
public abstract void Load(ComponentStorageState state);
ElementSize = elementSize;
Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>();
TypeId = typeId;
}
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
public bool Any()
{
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];
return Components.Count > 0;
}
public bool Any()
{
return nextID > 0;
}
public bool Has(Entity entity)
{
return EntityIDToStorageIndex.ContainsKey(entity);
}
public override bool Has(int entityID)
{
return entityIDToStorageIndex.ContainsKey(entityID);
}
public ref T Get<T>(in Entity entity) where T : unmanaged
{
return ref Components.Get<T>(EntityIDToStorageIndex[entity]);
}
public ref readonly TComponent Get(int entityID)
{
return ref components[entityIDToStorageIndex[entityID]];
}
public override object Debug_Get(int entityID)
{
return components[entityIDToStorageIndex[entityID]];
}
public ref readonly TComponent Get()
{
public ref T GetFirst<T>() where T : unmanaged
{
#if DEBUG
if (nextID == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
if (Components.Count == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
#endif
return ref components[0];
}
return ref Components.Get<T>(0);
}
// Returns true if the entity already had this component.
public bool Set(int entityID, in TComponent component)
// Returns true if the entity had this component.
public bool Set<T>(in Entity entity, in T component) where T : unmanaged
{
if (EntityIDToStorageIndex.TryGetValue(entity, out var index))
{
bool result = true;
if (!entityIDToStorageIndex.ContainsKey(entityID))
{
var index = nextID;
nextID += 1;
if (index >= components.Length)
{
Array.Resize(ref components, components.Length * 2);
Array.Resize(ref entityIDs, entityIDs.Length * 2);
}
entityIDToStorageIndex[entityID] = index;
entityIDs[index] = entityID;
result = false;
}
components[entityIDToStorageIndex[entityID]] = component;
return result;
Components.Set(index, component);
return true;
}
// Returns true if the entity had this component.
public override bool Remove(int entityID)
else
{
if (entityIDToStorageIndex.ContainsKey(entityID))
{
var storageIndex = entityIDToStorageIndex[entityID];
entityIDToStorageIndex.Remove(entityID);
var lastElementIndex = nextID - 1;
// move a component into the hole to maintain contiguous memory
if (lastElementIndex != storageIndex)
{
var lastEntityID = entityIDs[lastElementIndex];
entityIDToStorageIndex[lastEntityID] = storageIndex;
components[storageIndex] = components[lastElementIndex];
entityIDs[storageIndex] = lastEntityID;
}
nextID -= 1;
return true;
}
EntityIDToStorageIndex[entity] = Components.Count;
EntityIDs.Append(entity);
Components.Append(component);
return false;
}
}
public void Clear()
// Returns true if the entity had this component.
public bool Remove(in Entity entity)
{
if (EntityIDToStorageIndex.TryGetValue(entity, out int index))
{
nextID = 0;
entityIDToStorageIndex.Clear();
var lastElementIndex = Components.Count - 1;
var lastEntity = EntityIDs[lastElementIndex];
// move a component into the hole to maintain contiguous memory
Components.Delete(index);
EntityIDs.Delete(index);
EntityIDToStorageIndex.Remove(entity);
// update the index if it changed
if (lastElementIndex != index)
{
EntityIDToStorageIndex[lastEntity] = index;
}
return true;
}
public ReadOnlySpan<TComponent> AllComponents()
{
return new ReadOnlySpan<TComponent>(components, 0, nextID);
}
return false;
}
public Entity FirstEntity()
{
public void Clear()
{
Components.Clear();
EntityIDs.Clear();
EntityIDToStorageIndex.Clear();
}
public Entity FirstEntity()
{
#if DEBUG
if (nextID == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
if (EntityIDs.Count == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
#endif
return new Entity(entityIDs[0]);
}
return EntityIDs[0];
}
public override ComponentStorageState CreateState()
#if DEBUG
internal IEnumerable<Entity> Debug_GetEntities()
{
return EntityIDToStorageIndex.Keys;
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
return ComponentStorageState.Create<TComponent>(nextID);
}
Components.Dispose();
EntityIDs.Dispose();
public override void Save(ComponentStorageState state)
{
ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID));
if (entityIDBytes.Length > state.EntityIDs.Length)
{
Array.Resize(ref state.EntityIDs, entityIDBytes.Length);
}
entityIDBytes.CopyTo(state.EntityIDs);
ReadOnlySpan<byte> componentBytes = MemoryMarshal.Cast<TComponent, byte>(AllComponents());
if (componentBytes.Length > state.Components.Length)
{
Array.Resize(ref state.Components, componentBytes.Length);
}
componentBytes.CopyTo(state.Components);
state.Count = nextID;
}
public override void Load(ComponentStorageState state)
{
state.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs));
state.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components));
entityIDToStorageIndex.Clear();
for (var i = 0; i < state.Count; i += 1)
{
entityIDToStorageIndex[entityIDs[i]] = i;
}
nextID = state.Count;
IsDisposed = 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);
}
}

View File

@ -1,32 +1,17 @@
// NOTE: these methods are very inefficient
// this class should only be used in debugging contexts!!
#if DEBUG
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public abstract class DebugSystem : System
{
public abstract class DebugSystem : System
{
protected DebugSystem(World world) : base(world)
{
}
protected DebugSystem(World world) : base(world) { }
protected IEnumerable<object> Debug_GetAllComponents(Entity entity)
{
return ComponentDepot.Debug_GetAllComponents(entity.ID);
}
protected IEnumerable<Entity> Debug_GetEntities(Type componentType)
{
return ComponentDepot.Debug_GetEntities(componentType);
}
protected IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
return ComponentDepot.Debug_SearchComponentType(typeString);
}
}
protected World.ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) => World.Debug_GetAllComponentTypes(entity);
protected IEnumerable<Entity> Debug_GetEntities(Type componentType) => World.Debug_GetEntities(componentType);
protected IEnumerable<Type> Debug_SearchComponentType(string typeString) => World.Debug_SearchComponentType(typeString);
}
#endif

View File

@ -1,39 +1,3 @@
using System;
namespace MoonTools.ECS;
namespace MoonTools.ECS
{
public struct Entity : IEquatable<Entity>
{
public int ID { get; }
internal Entity(int id)
{
ID = id;
}
public override bool Equals(object? obj)
{
return obj is Entity entity && Equals(entity);
}
public override int GetHashCode()
{
return HashCode.Combine(ID);
}
public bool Equals(Entity other)
{
return ID == other.ID;
}
public static bool operator ==(Entity a, Entity b)
{
return a.Equals(b);
}
public static bool operator !=(Entity a, Entity b)
{
return !a.Equals(b);
}
}
}
public readonly record struct Entity(uint ID);

View File

@ -1,106 +1,36 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS;
namespace MoonTools.ECS
public abstract class EntityComponentReader
{
public abstract class EntityComponentReader
protected readonly World World;
public FilterBuilder FilterBuilder => World.FilterBuilder;
protected EntityComponentReader(World world)
{
internal readonly World World;
internal EntityStorage EntityStorage => World.EntityStorage;
internal ComponentDepot ComponentDepot => World.ComponentDepot;
internal RelationDepot RelationDepot => World.RelationDepot;
protected FilterBuilder FilterBuilder => new FilterBuilder(ComponentDepot);
public EntityComponentReader(World world)
{
World = world;
}
protected ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.ReadComponents<TComponent>();
}
protected bool Has<TComponent>(in Entity entity) where TComponent : unmanaged
{
return ComponentDepot.Has<TComponent>(entity.ID);
}
protected bool Some<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.Some<TComponent>();
}
protected ref readonly TComponent Get<TComponent>(in Entity entity) where TComponent : unmanaged
{
return ref ComponentDepot.Get<TComponent>(entity.ID);
}
protected ref readonly TComponent GetSingleton<TComponent>() where TComponent : unmanaged
{
return ref ComponentDepot.Get<TComponent>();
}
protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.GetSingletonEntity<TComponent>();
}
protected bool Exists(in Entity entity)
{
return EntityStorage.Exists(entity);
}
protected IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return RelationDepot.Relations<TRelationKind>();
}
protected bool Related<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged
{
return RelationDepot.Related<TRelationKind>(a.ID, b.ID);
}
// relations go A->B, so given A, will give all outgoing B relations.
protected IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.OutRelations<TRelationKind>(entity.ID);
}
protected (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.OutRelationSingleton<TRelationKind>(entity.ID);
}
protected bool HasOutRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.HasOutRelation<TRelationKind>(entity.ID);
}
protected int OutRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.OutRelationCount<TRelationKind>(entity.ID);
}
// Relations go A->B, so given B, will give all incoming A relations.
protected IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.InRelations<TRelationKind>(entity.ID);
}
protected (Entity, TRelationKind) InRelationSingleton<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.InRelationSingleton<TRelationKind>(entity.ID);
}
protected bool HasInRelation<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.HasInRelation<TRelationKind>(entity.ID);
}
protected int InRelationCount<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.InRelationCount<TRelationKind>(entity.ID);
}
World = world;
}
protected string GetTag(in Entity entity) => World.GetTag(entity);
protected bool Has<T>(in Entity Entity) where T : unmanaged => World.Has<T>(Entity);
protected bool Some<T>() where T : unmanaged => World.Some<T>();
protected ref T Get<T>(in Entity Entity) where T : unmanaged => ref World.Get<T>(Entity);
protected ref T GetSingleton<T>() where T : unmanaged => ref World.GetSingleton<T>();
protected Entity GetSingletonEntity<T>() where T : unmanaged => World.GetSingletonEntity<T>();
protected ReverseSpanEnumerator<(Entity, Entity)> Relations<T>() where T : unmanaged => World.Relations<T>();
protected bool Related<T>(in Entity entityA, in Entity entityB) where T : unmanaged => World.Related<T>(entityA, entityB);
protected T GetRelationData<T>(in Entity entityA, in Entity entityB) where T : unmanaged => World.GetRelationData<T>(entityA, entityB);
protected ReverseSpanEnumerator<Entity> OutRelations<T>(in Entity entity) where T : unmanaged => World.OutRelations<T>(entity);
protected Entity OutRelationSingleton<T>(in Entity entity) where T : unmanaged => World.OutRelationSingleton<T>(entity);
protected bool HasOutRelation<T>(in Entity entity) where T : unmanaged => World.HasOutRelation<T>(entity);
protected int OutRelationCount<T>(in Entity entity) where T : unmanaged => World.OutRelationCount<T>(entity);
protected Entity NthOutRelation<T>(in Entity entity, int n) where T : unmanaged => World.NthOutRelation<T>(entity, n);
protected ReverseSpanEnumerator<Entity> InRelations<T>(in Entity entity) where T : unmanaged => World.InRelations<T>(entity);
protected Entity InRelationSingleton<T>(in Entity entity) where T : unmanaged => World.InRelationSingleton<T>(entity);
protected bool HasInRelation<T>(in Entity entity) where T : unmanaged => World.HasInRelation<T>(entity);
protected int InRelationCount<T>(in Entity entity) where T : unmanaged => World.InRelationCount<T>(entity);
protected Entity NthInRelation<T>(in Entity entity, int n) where T : unmanaged => World.NthInRelation<T>(entity, n);
}

View File

@ -1,75 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class EntityStorage
{
private int nextID = 0;
private readonly Stack<int> availableIDs = new Stack<int>();
private readonly HashSet<int> availableIDHash = new HashSet<int>();
public Entity Create()
{
return new Entity(NextID());
}
public bool Exists(in Entity entity)
{
return Taken(entity.ID);
}
public void Destroy(in Entity entity)
{
Release(entity.ID);
}
public void Save(EntityStorageState state)
{
state.NextID = nextID;
state.availableIDs.Clear();
foreach (var id in availableIDs)
{
state.availableIDs.Add(id);
}
}
public void Load(EntityStorageState state)
{
nextID = state.NextID;
availableIDs.Clear();
availableIDHash.Clear();
foreach (var id in state.availableIDs)
{
availableIDs.Push(id);
availableIDHash.Add(id);
}
}
private int NextID()
{
if (availableIDs.Count > 0)
{
var id = availableIDs.Pop();
availableIDHash.Remove(id);
return id;
}
else
{
var id = nextID;
nextID += 1;
return id;
}
}
private bool Taken(int id)
{
return !availableIDHash.Contains(id) && id < nextID;
}
private void Release(int id)
{
availableIDs.Push(id);
availableIDHash.Add(id);
}
}
}

View File

@ -0,0 +1,35 @@
using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS;
public ref struct ReverseSpanEnumerator<T>
{
private ReadOnlySpan<T> Span;
private int Index;
public ReverseSpanEnumerator<T> GetEnumerator() => this;
public T Current => Span[Index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
if (Index > 0)
{
Index -= 1;
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReverseSpanEnumerator(Span<T> span)
{
Span = span;
Index = span.Length;
}
public static ReverseSpanEnumerator<T> Empty => new ReverseSpanEnumerator<T>();
}

View File

@ -1,27 +1,123 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public class Filter
{
public class Filter
{
internal FilterSignature Signature;
private ComponentDepot ComponentDepot;
private World World;
internal FilterSignature Signature;
internal Filter(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
internal IndexableSet<Entity> EntitySet = new IndexableSet<Entity>();
private bool IsDisposed;
public ReverseSpanEnumerator<Entity> Entities => EntitySet.GetEnumerator();
public bool Empty => EntitySet.Count == 0;
public int Count => EntitySet.Count;
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index) => EntitySet[index];
// WARNING: this WILL crash if the filter is empty!
public Entity RandomEntity => EntitySet[RandomManager.Next(EntitySet.Count)];
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
internal Filter(World world, FilterSignature signature)
{
World = world;
Signature = signature;
}
public void DestroyAllEntities()
{
foreach (var entity in EntitySet)
{
ComponentDepot = componentDepot;
Signature = new FilterSignature(included, excluded);
World.Destroy(entity);
}
}
internal void Check(Entity entity)
{
foreach (var type in Signature.Included)
{
if (!World.Has(entity, type))
{
EntitySet.Remove(entity);
return;
}
}
public IEnumerable<Entity> Entities => ComponentDepot.FilterEntities(this);
public IEnumerable<Entity> EntitiesInRandomOrder => ComponentDepot.FilterEntitiesRandom(this);
public Entity RandomEntity => ComponentDepot.FilterRandomEntity(this);
foreach (var type in Signature.Excluded)
{
if (World.Has(entity, type))
{
EntitySet.Remove(entity);
return;
}
}
public int Count => ComponentDepot.FilterCount(this);
public bool Empty => Count == 0;
EntitySet.Add(entity);
}
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index) => ComponentDepot.FilterNthEntity(this, index);
internal void AddEntity(in Entity entity)
{
EntitySet.Add(entity);
}
internal void RemoveEntity(in Entity entity)
{
EntitySet.Remove(entity);
}
internal void Clear()
{
EntitySet.Clear();
}
public ref struct RandomEntityEnumerator
{
private Filter Filter;
private LinearCongruentialEnumerator LinearCongruentialEnumerator;
public RandomEntityEnumerator GetEnumerator() => this;
internal RandomEntityEnumerator(Filter filter)
{
Filter = filter;
LinearCongruentialEnumerator =
RandomManager.LinearCongruentialSequence(filter.Count);
}
public bool MoveNext() => LinearCongruentialEnumerator.MoveNext();
public Entity Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
EntitySet.Dispose();
}
IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~Filter()
// {
// // 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,45 +1,43 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public struct FilterBuilder
{
private ComponentDepot ComponentDepot;
private HashSet<Type> Included;
private HashSet<Type> Excluded;
World World;
IndexableSet<TypeId> Included;
IndexableSet<TypeId> Excluded;
internal FilterBuilder(ComponentDepot componentDepot)
internal FilterBuilder(World world)
{
ComponentDepot = componentDepot;
Included = new HashSet<Type>();
Excluded = new HashSet<Type>();
World = world;
Included = new IndexableSet<TypeId>();
Excluded = new IndexableSet<TypeId>();
}
private FilterBuilder(ComponentDepot componentDepot, HashSet<Type> included, HashSet<Type> excluded)
private FilterBuilder(World world, IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
ComponentDepot = componentDepot;
World = world;
Included = included;
Excluded = excluded;
}
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
public FilterBuilder Include<T>() where T : unmanaged
{
ComponentDepot.Register<TComponent>();
Included.Add(typeof(TComponent));
return new FilterBuilder(ComponentDepot, Included, Excluded);
Included.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
public FilterBuilder Exclude<T>() where T : unmanaged
{
ComponentDepot.Register<TComponent>();
Excluded.Add(typeof(TComponent));
return new FilterBuilder(ComponentDepot, Included, Excluded);
Excluded.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public Filter Build()
{
return ComponentDepot.CreateFilter(Included, Excluded);
var signature = new FilterSignature(Included, Excluded);
return World.GetFilter(signature);
}
}
}

View File

@ -1,51 +1,69 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public struct FilterSignature : IEquatable<FilterSignature>
{
public struct FilterSignature
public readonly IndexableSet<TypeId> Included;
public readonly IndexableSet<TypeId> Excluded;
public FilterSignature(IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
private const int HASH_FACTOR = 97;
Included = included;
Excluded = excluded;
}
public readonly HashSet<Type> Included;
public readonly HashSet<Type> Excluded;
public override bool Equals(object? obj)
{
return obj is FilterSignature signature && Equals(signature);
}
public FilterSignature(HashSet<Type> included, HashSet<Type> excluded)
public bool Equals(FilterSignature other)
{
foreach (var included in Included)
{
Included = included;
Excluded = excluded;
}
public override bool Equals(object? obj)
{
return obj is FilterSignature signature && Equals(signature);
}
public bool Equals(FilterSignature other)
{
return Included.SetEquals(other.Included) && Excluded.SetEquals(other.Excluded);
}
private int GuidToInt(Guid guid)
{
return BitConverter.ToInt32(guid.ToByteArray());
}
public override int GetHashCode()
{
int result = 1;
foreach (var type in Included)
if (!other.Included.Contains(included))
{
result *= HASH_FACTOR + GuidToInt(type.GUID);
return false;
}
// FIXME: Is there a way to avoid collisions when this is the same set as included?
foreach (var type in Excluded)
{
result *= HASH_FACTOR + GuidToInt(type.GUID);
}
return result;
}
foreach (var excluded in Excluded)
{
if (!other.Excluded.Contains(excluded))
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var type in Included)
{
hashcode = HashCode.Combine(hashcode, type);
}
foreach (var type in Excluded)
{
hashcode = HashCode.Combine(hashcode, type);
}
return hashcode;
}
public static bool operator ==(FilterSignature left, FilterSignature right)
{
return left.Equals(right);
}
public static bool operator !=(FilterSignature left, FilterSignature right)
{
return !(left == right);
}
}

View File

@ -1,7 +0,0 @@
namespace MoonTools.ECS
{
public interface IHasEntity
{
Entity Entity { get; }
}
}

62
src/IdAssigner.cs Normal file
View File

@ -0,0 +1,62 @@
using System;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
internal class IdAssigner : IDisposable
{
uint Next;
NativeArray<uint> AvailableIds = new NativeArray<uint>();
private bool IsDisposed;
public uint Assign()
{
if (AvailableIds.TryPop(out var id))
{
return id;
}
id = Next;
Next += 1;
return id;
}
public void Unassign(uint id)
{
AvailableIds.Append(id);
}
public void CopyTo(IdAssigner other)
{
AvailableIds.CopyTo(other.AvailableIds);
other.Next = Next;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
AvailableIds.Dispose();
}
IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~IdAssigner()
// {
// // 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,115 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
internal class IndexableSet<T> : IEnumerable<T> where T : unmanaged
{
private Dictionary<T, int> indices;
private T[] array;
public int Count { get; private set; }
public IndexableSet(int size = 32)
{
indices = new Dictionary<T, int>(size);
array = new T[size];
}
public T this[int i]
{
get { return array[i]; }
}
public bool Contains(T element)
{
return indices.ContainsKey(element);
}
public bool Add(T element)
{
if (!Contains(element))
{
indices.Add(element, Count);
if (Count >= array.Length)
{
Array.Resize(ref array, array.Length * 2);
}
array[Count] = element;
Count += 1;
return true;
}
return false;
}
public bool Remove(T element)
{
if (!Contains(element))
{
return false;
}
var lastElement = array[Count - 1];
var index = indices[element];
array[index] = lastElement;
indices[lastElement] = index;
Count -= 1;
indices.Remove(element);
return true;
}
public IEnumerator<T> GetEnumerator()
{
for (var i = Count - 1; i >= 0; i -= 1)
{
yield return array[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
for (var i = Count - 1; i >= 0; i -= 1)
{
yield return array[i];
}
}
public void Clear()
{
Count = 0;
}
public void Save(IndexableSetState<T> state)
{
ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(array);
if (arrayBytes.Length > state.Array.Length)
{
Array.Resize(ref state.Array, arrayBytes.Length);
}
arrayBytes.CopyTo(state.Array);
state.Count = Count;
}
public void Load(IndexableSetState<T> state)
{
state.Array.CopyTo(MemoryMarshal.Cast<T, byte>(array));
indices.Clear();
for (var i = 0; i < state.Count; i += 1)
{
indices[array[i]] = i;
}
Count = state.Count;
}
}
}

20
src/Manipulator.cs Normal file
View File

@ -0,0 +1,20 @@
namespace MoonTools.ECS;
public abstract class Manipulator : EntityComponentReader
{
public Manipulator(World world) : base(world)
{
}
protected Entity CreateEntity(string tag = "") => World.CreateEntity(tag);
protected void Tag(Entity entity, string tag) => World.Tag(entity, tag);
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component);
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged => World.Remove<TComponent>(entity);
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged => World.Relate(entityA, entityB, relationData);
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged => World.Unrelate<TRelationKind>(entityA, entityB);
protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged => World.UnrelateAll<TRelationKind>(entity);
protected void Destroy(in Entity entity) => World.Destroy(entity);
protected void Send<TMessage>(in TMessage message) where TMessage : unmanaged => World.Send(message);
}

View File

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class MessageDepot
{
private Dictionary<Type, MessageStorage> storages = new Dictionary<Type, MessageStorage>();
private MessageStorage<TMessage> Lookup<TMessage>() where TMessage : struct
{
if (!storages.ContainsKey(typeof(TMessage)))
{
storages.Add(typeof(TMessage), new MessageStorage<TMessage>());
}
return storages[typeof(TMessage)] as MessageStorage<TMessage>;
}
public void Add<TMessage>(in TMessage message) where TMessage : struct
{
Lookup<TMessage>().Add(message);
}
public bool Some<TMessage>() where TMessage : struct
{
return Lookup<TMessage>().Some();
}
public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : struct
{
return Lookup<TMessage>().All();
}
public TMessage First<TMessage>() where TMessage : struct
{
return Lookup<TMessage>().First();
}
public IEnumerable<TMessage> WithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
{
return Lookup<TMessage>().WithEntity(entityID);
}
public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
{
return ref Lookup<TMessage>().FirstWithEntity(entityID);
}
public bool SomeWithEntity<TMessage>(int entityID) where TMessage : struct, IHasEntity
{
return Lookup<TMessage>().SomeWithEntity(entityID);
}
public void Clear()
{
foreach (var storage in storages.Values)
{
storage.Clear();
}
}
}
}

View File

@ -1,91 +1,63 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public class MessageStorage : IDisposable
{
internal abstract class MessageStorage
private NativeArray Messages;
private bool IsDisposed;
public MessageStorage(int elementSize)
{
public abstract void Clear();
Messages = new NativeArray(elementSize);
}
internal class MessageStorage<TMessage> : MessageStorage where TMessage : struct
public void Add<T>(in T message) where T : unmanaged
{
private int count = 0;
private int capacity = 128;
private TMessage[] messages;
private Dictionary<int, List<int>> entityToIndices = new Dictionary<int, List<int>>();
Messages.Append(message);
}
public MessageStorage()
public bool Some()
{
return Messages.Count > 0;
}
public ReadOnlySpan<T> All<T>() where T : unmanaged
{
return Messages.ToSpan<T>();
}
public T First<T>() where T : unmanaged
{
return Messages.Get<T>(0);
}
public void Clear()
{
Messages.Clear();
}
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
messages = new TMessage[capacity];
Messages.Dispose();
IsDisposed = true;
}
}
public void Add(in TMessage message)
{
if (count == capacity)
{
capacity *= 2;
Array.Resize(ref messages, capacity);
}
// ~MessageStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
messages[count] = message;
if (message is IHasEntity entityMessage)
{
if (!entityToIndices.ContainsKey(entityMessage.Entity.ID))
{
entityToIndices.Add(entityMessage.Entity.ID, new List<int>());
}
entityToIndices[entityMessage.Entity.ID].Add(count);
}
count += 1;
}
public bool Some()
{
return count > 0;
}
public ReadOnlySpan<TMessage> All()
{
return new ReadOnlySpan<TMessage>(messages, 0, count);
}
public TMessage First()
{
return messages[0];
}
public IEnumerable<TMessage> WithEntity(int entityID)
{
if (entityToIndices.ContainsKey(entityID))
{
foreach (var index in entityToIndices[entityID])
{
yield return messages[index];
}
}
}
public ref readonly TMessage FirstWithEntity(int entityID)
{
return ref messages[entityToIndices[entityID][0]];
}
public bool SomeWithEntity(int entityID)
{
return entityToIndices.ContainsKey(entityID) && entityToIndices[entityID].Count > 0;
}
public override void Clear()
{
count = 0;
foreach (var set in entityToIndices.Values)
{
set.Clear();
}
}
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,40 +1,208 @@
using System;
using System.Collections.Generic;
using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
namespace MoonTools.ECS;
/// <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
{
internal static class RandomGenerator
public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int
uint[] State = new uint[16];
uint Index = 0;
uint Seed;
/// <summary>
/// Initializes the RNG with an arbitrary seed.
/// </summary>
public Random()
{
private static Random random = new Random();
Init((uint) Environment.TickCount);
}
private static int[] Primes =
/// <summary>
/// Initializes the RNG with a given seed.
/// </summary>
public void Init(uint seed)
{
Seed = seed;
uint s = seed;
for (int i = 0; i < 16; i++)
{
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 int Next(int maxValue)
{
return random.Next(maxValue);
s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0;
State[i] = ~ ~s; //i ;
}
Index = 0;
}
/// <summary>
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
/// </summary>
public static IEnumerable<int> LinearCongruentialGenerator(int 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)
{
var x = Primes[random.Next(Primes.Length - 1)];
while (x % n == 0)
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)
{
// not coprime, try again
x = Primes[random.Next(Primes.Length - 1)];
Unsafe.Write(ptr + offset, State[i]);
offset += 4;
}
var start = random.Next(n);
for (var i = start; i < start + n; i++)
{
yield return (i * x) % n;
}
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;
}
}

81
src/RandomManager.cs Normal file
View File

@ -0,0 +1,81 @@
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 + 1), 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

@ -1,37 +0,0 @@
using System;
namespace MoonTools.ECS
{
internal struct Relation : IEquatable<Relation>
{
public Entity A { get; }
public Entity B { get; }
internal Relation(Entity entityA, Entity entityB)
{
A = entityA;
B = entityB;
}
internal Relation(int idA, int idB)
{
A = new Entity(idA);
B = new Entity(idB);
}
public override bool Equals(object? obj)
{
return obj is Relation relation && Equals(relation);
}
public bool Equals(Relation other)
{
return A.ID == other.A.ID && B.ID == other.B.ID;
}
public override int GetHashCode()
{
return HashCode.Combine(A.ID, B.ID);
}
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
internal class RelationDepot
{
private Dictionary<Type, RelationStorage> storages = new Dictionary<Type, RelationStorage>();
private void Register<TRelationKind>() where TRelationKind : unmanaged
{
if (!storages.ContainsKey(typeof(TRelationKind)))
{
storages.Add(typeof(TRelationKind), new RelationStorage<TRelationKind>());
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
{
Register<TRelationKind>();
return (RelationStorage<TRelationKind>) storages[typeof(TRelationKind)];
}
public void Set<TRelationKind>(Relation relation, TRelationKind relationData) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().Set(relation, relationData);
}
public void Remove<TRelationKind>(Relation relation) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().Remove(relation);
}
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().UnrelateAll(entityID);
}
// FIXME: optimize this
public void OnEntityDestroy(int entityID)
{
foreach (var storage in storages.Values)
{
storage.OnEntityDestroy(entityID);
}
}
public IEnumerable<(Entity, Entity, TRelationKind)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().All();
}
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().Has(new Relation(idA, idB));
}
public IEnumerable<(Entity, TRelationKind)> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().OutRelations(entityID);
}
public (Entity, TRelationKind) OutRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().OutFirst(entityID);
}
public int OutRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().OutRelationCount(entityID);
}
public bool HasOutRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().HasOutRelation(entityID);
}
public IEnumerable<(Entity, TRelationKind)> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().InRelations(entityID);
}
public (Entity, TRelationKind) InRelationSingleton<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().InFirst(entityID);
}
public bool HasInRelation<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().HasInRelation(entityID);
}
public int InRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().InRelationCount(entityID);
}
public void Save(RelationDepotState state)
{
foreach (var (type, storage) in storages)
{
if (!state.StorageStates.ContainsKey(type))
{
state.StorageStates.Add(type, storage.CreateState());
}
storage.Save(state.StorageStates[type]);
}
}
public void Load(RelationDepotState state)
{
foreach (var (type, storageState) in state.StorageStates)
{
storages[type].Load(storageState);
}
}
}
}

View File

@ -1,282 +1,286 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
// TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots
internal class RelationStorage
{
internal abstract class RelationStorage
internal NativeArray Relations;
internal NativeArray RelationDatas;
internal int ElementSize;
internal Dictionary<(Entity, Entity), int> Indices = new Dictionary<(Entity, Entity), int>(16);
internal Dictionary<Entity, IndexableSet<Entity>> OutRelationSets = new Dictionary<Entity, IndexableSet<Entity>>(16);
internal Dictionary<Entity, IndexableSet<Entity>> InRelationSets = new Dictionary<Entity, IndexableSet<Entity>>(16);
private Stack<IndexableSet<Entity>> ListPool = new Stack<IndexableSet<Entity>>();
private bool IsDisposed;
public RelationStorage(int relationDataSize)
{
public abstract RelationStorageState CreateState();
public abstract void Save(RelationStorageState state);
public abstract void Load(RelationStorageState state);
public abstract void OnEntityDestroy(int entityID);
ElementSize = relationDataSize;
Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>());
RelationDatas = new NativeArray(relationDataSize);
}
// 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
public ReverseSpanEnumerator<(Entity, Entity)> All()
{
private int count = 0;
private Dictionary<Relation, int> indices = new Dictionary<Relation, int>(16);
private Relation[] relations = new Relation[16];
private TRelation[] relationDatas = new TRelation[16];
private Dictionary<int, IndexableSet<int>> outRelations = new Dictionary<int, IndexableSet<int>>(16);
private Dictionary<int, IndexableSet<int>> inRelations = new Dictionary<int, IndexableSet<int>>(16);
private Stack<IndexableSet<int>> listPool = new Stack<IndexableSet<int>>();
return new ReverseSpanEnumerator<(Entity, Entity)>(Relations.ToSpan<(Entity, Entity)>());
}
public IEnumerable<(Entity, Entity, TRelation)> All()
public unsafe void Set<T>(in Entity entityA, in Entity entityB, in T relationData) where T : unmanaged
{
var relation = (entityA, entityB);
if (Indices.TryGetValue(relation, out var index))
{
for (var i = 0; i < count; i += 1)
{
var relation = relations[i];
yield return (relation.A, relation.B, relationDatas[i]);
}
RelationDatas.Set(index, relationData);
return;
}
public void Set(Relation relation, TRelation relationData)
if (!OutRelationSets.ContainsKey(entityA))
{
if (indices.ContainsKey(relation))
{
var index = indices[relation];
relationDatas[index] = relationData;
return;
}
var idA = relation.A.ID;
var idB = relation.B.ID;
if (!outRelations.ContainsKey(idA))
{
outRelations[idA] = AcquireHashSetFromPool();
}
outRelations[idA].Add(idB);
if (!inRelations.ContainsKey(idB))
{
inRelations[idB] = AcquireHashSetFromPool();
}
inRelations[idB].Add(idA);
if (count >= relationDatas.Length)
{
Array.Resize(ref relations, relations.Length * 2);
Array.Resize(ref relationDatas, relationDatas.Length * 2);
}
relations[count] = relation;
relationDatas[count] = relationData;
indices.Add(relation, count);
count += 1;
OutRelationSets[entityA] = AcquireHashSetFromPool();
}
OutRelationSets[entityA].Add(entityB);
public bool Has(Relation relation)
if (!InRelationSets.ContainsKey(entityB))
{
return indices.ContainsKey(relation);
InRelationSets[entityB] = AcquireHashSetFromPool();
}
InRelationSets[entityB].Add(entityA);
// FIXME: creating the new Relation in here is slightly deranged
public IEnumerable<(Entity, TRelation)> OutRelations(int entityID)
Relations.Append(relation);
RelationDatas.Append(relationData);
Indices.Add(relation, Relations.Count - 1);
}
public ref T Get<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationIndex = Indices[(entityA, entityB)];
return ref RelationDatas.Get<T>(relationIndex);
}
public bool Has(Entity entityA, Entity entityB)
{
return Indices.ContainsKey((entityA, entityB));
}
public ReverseSpanEnumerator<Entity> OutRelations(Entity Entity)
{
if (OutRelationSets.TryGetValue(Entity, out var entityOutRelations))
{
if (outRelations.ContainsKey(entityID))
{
foreach (var id in outRelations[entityID])
{
var relation = new Relation(entityID, id);
yield return (relation.B, relationDatas[indices[relation]]);
}
}
return entityOutRelations.GetEnumerator();
}
public (Entity, TRelation) OutFirst(int entityID)
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
}
public Entity OutFirst(Entity Entity)
{
return OutNth(Entity, 0);
}
public Entity OutNth(Entity Entity, int n)
{
#if DEBUG
if (!outRelations.ContainsKey(entityID))
{
throw new KeyNotFoundException("No out relations to this entity!");
}
if (!OutRelationSets.ContainsKey(Entity) || OutRelationSets[Entity].Count == 0)
{
throw new KeyNotFoundException("No out relations to this entity!");
}
#endif
var relation = new Relation(entityID, outRelations[entityID][0]);
return (relation.B, relationDatas[indices[relation]]);
}
return OutRelationSets[Entity][n];
}
public bool HasOutRelation(int entityID)
{
return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0;
}
public bool HasOutRelation(Entity Entity)
{
return OutRelationSets.ContainsKey(Entity) && OutRelationSets[Entity].Count > 0;
}
public int OutRelationCount(int entityID)
{
return outRelations.ContainsKey(entityID) ? outRelations[entityID].Count : 0;
}
public int OutRelationCount(Entity Entity)
{
return OutRelationSets.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public IEnumerable<(Entity, TRelation)> InRelations(int entityID)
public ReverseSpanEnumerator<Entity> InRelations(Entity Entity)
{
if (InRelationSets.TryGetValue(Entity, out var entityInRelations))
{
if (inRelations.ContainsKey(entityID))
{
foreach (var id in inRelations[entityID])
{
var relation = new Relation(id, entityID);
yield return (relation.A, relationDatas[indices[relation]]);
}
}
return entityInRelations.GetEnumerator();
}
public (Entity, TRelation) InFirst(int entityID)
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
}
public Entity InFirst(Entity Entity)
{
return InNth(Entity, 0);
}
public Entity InNth(Entity Entity, int n)
{
#if DEBUG
if (!inRelations.ContainsKey(entityID))
{
throw new KeyNotFoundException("No out relations to this entity!");
}
if (!InRelationSets.ContainsKey(Entity) || InRelationSets[Entity].Count == 0)
{
throw new KeyNotFoundException("No in relations to this entity!");
}
#endif
var relation = new Relation(inRelations[entityID][0], entityID);
return (relation.A, relationDatas[indices[relation]]);
}
return InRelationSets[Entity][n];
}
public bool HasInRelation(int entityID)
{
return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0;
}
public bool HasInRelation(Entity Entity)
{
return InRelationSets.ContainsKey(Entity) && InRelationSets[Entity].Count > 0;
}
public int InRelationCount(int entityID)
{
return inRelations.ContainsKey(entityID) ? inRelations[entityID].Count : 0;
}
public int InRelationCount(Entity Entity)
{
return InRelationSets.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0;
}
public bool Remove(Relation relation)
public (bool, bool) Remove(in Entity entityA, in Entity entityB)
{
var aEmpty = false;
var bEmpty = false;
var relation = (entityA, entityB);
if (OutRelationSets.TryGetValue(entityA, out var entityOutRelations))
{
if (outRelations.ContainsKey(relation.A.ID))
entityOutRelations.Remove(entityB);
if (OutRelationSets[entityA].Count == 0)
{
outRelations[relation.A.ID].Remove(relation.B.ID);
aEmpty = true;
}
}
if (InRelationSets.TryGetValue(entityB, out var entityInRelations))
{
entityInRelations.Remove(entityA);
if (InRelationSets[entityB].Count == 0)
{
bEmpty = true;
}
}
if (Indices.TryGetValue(relation, out var index))
{
var lastElementIndex = Relations.Count - 1;
// move an element into the hole
if (index != lastElementIndex)
{
var lastRelation = Relations.Get<(Entity, Entity)>(lastElementIndex);
Indices[lastRelation] = index;
}
if (inRelations.ContainsKey(relation.B.ID))
RelationDatas.Delete(index);
Relations.Delete(index);
Indices.Remove(relation);
}
return (aEmpty, bEmpty);
}
public void RemoveEntity(in Entity entity)
{
if (OutRelationSets.TryGetValue(entity, out var entityOutRelations))
{
foreach (var entityB in entityOutRelations)
{
inRelations[relation.B.ID].Remove(relation.A.ID);
Remove(entity, entityB);
}
if (indices.ContainsKey(relation))
{
var index = indices[relation];
var lastElementIndex = count - 1;
ReturnHashSetToPool(entityOutRelations);
OutRelationSets.Remove(entity);
}
// move an element into the hole
if (index != lastElementIndex)
if (InRelationSets.TryGetValue(entity, out var entityInRelations))
{
foreach (var entityA in entityInRelations)
{
Remove(entityA, entity);
}
ReturnHashSetToPool(entityInRelations);
InRelationSets.Remove(entity);
}
}
internal IndexableSet<Entity> AcquireHashSetFromPool()
{
if (ListPool.Count == 0)
{
ListPool.Push(new IndexableSet<Entity>());
}
return ListPool.Pop();
}
private void ReturnHashSetToPool(IndexableSet<Entity> hashSet)
{
hashSet.Clear();
ListPool.Push(hashSet);
}
public void Clear()
{
Indices.Clear();
foreach (var set in InRelationSets.Values)
{
ReturnHashSetToPool(set);
}
InRelationSets.Clear();
foreach (var set in OutRelationSets.Values)
{
ReturnHashSetToPool(set);
}
OutRelationSets.Clear();
Relations.Clear();
RelationDatas.Clear();
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Clear();
if (disposing)
{
foreach (var set in ListPool)
{
var lastRelation = relations[lastElementIndex];
indices[lastRelation] = index;
relationDatas[index] = relationDatas[lastElementIndex];
relations[index] = lastRelation;
set.Dispose();
}
count -= 1;
indices.Remove(relation);
return true;
Relations.Dispose();
RelationDatas.Dispose();
}
return false;
IsDisposed = true;
}
}
public void UnrelateAll(int entityID)
{
if (outRelations.ContainsKey(entityID))
{
foreach (var entityB in outRelations[entityID])
{
Remove(new Relation(entityID, entityB));
}
// ~RelationStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
ReturnHashSetToPool(outRelations[entityID]);
outRelations.Remove(entityID);
}
if (inRelations.ContainsKey(entityID))
{
foreach (var entityA in inRelations[entityID])
{
Remove(new Relation(entityA, entityID));
}
ReturnHashSetToPool(inRelations[entityID]);
inRelations.Remove(entityID);
}
}
public override void OnEntityDestroy(int entityID)
{
UnrelateAll(entityID);
}
private IndexableSet<int> AcquireHashSetFromPool()
{
if (listPool.Count == 0)
{
listPool.Push(new IndexableSet<int>());
}
return listPool.Pop();
}
private void ReturnHashSetToPool(IndexableSet<int> hashSet)
{
hashSet.Clear();
listPool.Push(hashSet);
}
public override RelationStorageState CreateState()
{
return RelationStorageState.Create<TRelation>(count);
}
public override void Save(RelationStorageState state)
{
ReadOnlySpan<byte> relationBytes = MemoryMarshal.Cast<Relation, byte>(relations);
if (relationBytes.Length > state.Relations.Length)
{
Array.Resize(ref state.Relations, relationBytes.Length);
}
relationBytes.CopyTo(state.Relations);
ReadOnlySpan<byte> relationDataBytes = MemoryMarshal.Cast<TRelation, byte>(relationDatas);
if (relationDataBytes.Length > state.RelationDatas.Length)
{
Array.Resize(ref state.RelationDatas, relationDataBytes.Length);
}
relationDataBytes.CopyTo(state.RelationDatas);
state.Count = count;
}
public override void Load(RelationStorageState state)
{
state.Relations.CopyTo(MemoryMarshal.Cast<Relation, byte>(relations));
state.RelationDatas.CopyTo(MemoryMarshal.Cast<TRelation, byte>(relationDatas));
indices.Clear();
outRelations.Clear();
inRelations.Clear();
for (var i = 0; i < state.Count; i += 1)
{
var relation = relations[i];
indices[relation] = i;
if (!outRelations.ContainsKey(relation.A.ID))
{
outRelations[relation.A.ID] = AcquireHashSetFromPool();
}
outRelations[relation.A.ID].Add(relation.B.ID);
if (!inRelations.ContainsKey(relation.B.ID))
{
inRelations[relation.B.ID] = AcquireHashSetFromPool();
}
inRelations[relation.B.ID].Add(relation.A.ID);
}
count = state.Count;
}
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,7 +1,6 @@
namespace MoonTools.ECS
namespace MoonTools.ECS;
public abstract class Renderer : EntityComponentReader
{
public abstract class Renderer : EntityComponentReader
{
public Renderer(World world) : base(world) { }
}
public Renderer(World world) : base(world) { }
}

378
src/Snapshot.cs Normal file
View File

@ -0,0 +1,378 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
// TODO: we should implement a NativeDictionary that can be memcopied
public class Snapshot : IDisposable
{
private List<ComponentSnapshot> ComponentSnapshots = new List<ComponentSnapshot>();
// FIXME: we could just have a filter ID
private Dictionary<FilterSignature, List<Entity>> Filters = new Dictionary<FilterSignature, List<Entity>>();
private List<RelationSnapshot> RelationSnapshots = new List<RelationSnapshot>();
private List<IndexableSet<TypeId>> EntityRelationIndex =
new List<IndexableSet<TypeId>>();
private List<IndexableSet<TypeId>> EntityComponentIndex =
new List<IndexableSet<TypeId>>();
private List<string> EntityTags = new List<string>();
private IdAssigner EntityIdAssigner = new IdAssigner();
private bool IsDisposed;
public void Restore(World world)
{
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
// restore filter states
// this could be sped up if we figured out a direct IndexableSet copy
foreach (var (signature, entityList) in Filters)
{
var filter = world.FilterIndex[signature];
filter.Clear();
foreach (var entity in entityList)
{
filter.AddEntity(entity);
}
}
// clear all component storages in case any were created after snapshot
// FIXME: this can be eliminated via component discovery
foreach (var componentStorage in world.ComponentIndex)
{
componentStorage.Clear();
}
// clear all relation storages in case any were created after snapshot
// FIXME: this can be eliminated via component discovery
foreach (var relationStorage in world.RelationIndex)
{
relationStorage.Clear();
}
for (var i = 0; i < ComponentSnapshots.Count; i += 1)
{
var componentStorage = world.ComponentIndex[i];
ComponentSnapshots[i].Restore(componentStorage);
}
// restore relation state
for (var i = 0; i < RelationSnapshots.Count; i += 1)
{
var relationStorage = world.RelationIndex[i];
RelationSnapshots[i].Restore(relationStorage);
}
// restore entity relation index state
// FIXME: arghhhh this is so slow
foreach (var relationTypeSet in world.EntityRelationIndex)
{
relationTypeSet.Clear();
}
for (var i = 0; i < EntityRelationIndex.Count; i += 1)
{
var relationTypeSet = EntityRelationIndex[i];
foreach (var typeId in relationTypeSet)
{
world.EntityRelationIndex[i].Add(typeId);
}
}
// restore entity component index state
// FIXME: arrghghhh this is so slow
foreach (var componentTypeSet in world.EntityComponentIndex)
{
componentTypeSet.Clear();
}
for (var i = 0; i < EntityComponentIndex.Count; i += 1)
{
var componentTypeSet = EntityComponentIndex[i];
foreach (var typeId in componentTypeSet)
{
world.EntityComponentIndex[i].Add(typeId);
}
}
// restore entity tags
for (var i = 0; i < EntityTags.Count; i += 1)
{
world.EntityTags[i] = EntityTags[i];
}
}
public void Take(World world)
{
// copy id assigner state
world.EntityIdAssigner.CopyTo(EntityIdAssigner);
// copy filter states
foreach (var (_, filter) in world.FilterIndex)
{
TakeFilterSnapshot(filter);
}
// copy components
for (var i = ComponentSnapshots.Count; i < world.ComponentIndex.Count; i += 1)
{
ComponentSnapshots.Add(new ComponentSnapshot(world.ComponentIndex[i].ElementSize));
}
for (var i = 0; i < world.ComponentIndex.Count; i += 1)
{
ComponentSnapshots[i].Take(world.ComponentIndex[i]);
}
// copy relations
for (var i = RelationSnapshots.Count; i < world.RelationIndex.Count; i += 1)
{
RelationSnapshots.Add(new RelationSnapshot(world.RelationIndex[i].ElementSize));
}
for (var i = 0; i < world.RelationIndex.Count; i += 1)
{
RelationSnapshots[i].Take(world.RelationIndex[i]);
}
// fill in missing index structures
for (var i = EntityComponentIndex.Count; i < world.EntityComponentIndex.Count; i += 1)
{
EntityComponentIndex.Add(new IndexableSet<TypeId>());
}
for (var i = EntityRelationIndex.Count; i < world.EntityRelationIndex.Count; i += 1)
{
EntityRelationIndex.Add(new IndexableSet<TypeId>());
}
// copy entity relation index
// FIXME: arghhhh this is so slow
for (var i = 0; i < world.EntityRelationIndex.Count; i += 1)
{
EntityRelationIndex[i].Clear();
foreach (var typeId in world.EntityRelationIndex[i])
{
EntityRelationIndex[i].Add(typeId);
}
}
// copy entity component index
// FIXME: arghhhh this is so slow
for (var i = 0; i < world.EntityComponentIndex.Count; i += 1)
{
EntityComponentIndex[i].Clear();
foreach (var typeId in world.EntityComponentIndex[i])
{
EntityComponentIndex[i].Add(typeId);
}
}
// copy entity tags
EntityTags.Clear();
foreach (var s in world.EntityTags)
{
EntityTags.Add(s);
}
}
private void TakeFilterSnapshot(Filter filter)
{
if (!Filters.TryGetValue(filter.Signature, out var entities))
{
entities = new List<Entity>();
Filters.Add(filter.Signature, entities);
}
entities.Clear();
foreach (var entity in filter.EntitySet.AsSpan())
{
entities.Add(entity);
}
}
private class ComponentSnapshot : IDisposable
{
private readonly NativeArray Components;
private readonly NativeArray<Entity> EntityIDs;
private bool IsDisposed;
public ComponentSnapshot(int elementSize)
{
Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>();
}
public void Take(ComponentStorage componentStorage)
{
componentStorage.Components.CopyAllTo(Components);
componentStorage.EntityIDs.CopyTo(EntityIDs);
}
public void Restore(ComponentStorage componentStorage)
{
Components.CopyAllTo(componentStorage.Components);
EntityIDs.CopyTo(componentStorage.EntityIDs);
componentStorage.EntityIDToStorageIndex.Clear();
for (int i = 0; i < EntityIDs.Count; i += 1)
{
var entityID = EntityIDs[i];
componentStorage.EntityIDToStorageIndex[entityID] = i;
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Components.Dispose();
EntityIDs.Dispose();
}
IsDisposed = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
private class RelationSnapshot : IDisposable
{
private NativeArray Relations;
private NativeArray RelationDatas;
private bool IsDisposed;
public RelationSnapshot(int elementSize)
{
Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>());
RelationDatas = new NativeArray(elementSize);
}
public void Take(RelationStorage relationStorage)
{
relationStorage.Relations.CopyAllTo(Relations);
relationStorage.RelationDatas.CopyAllTo(RelationDatas);
}
public void Restore(RelationStorage relationStorage)
{
relationStorage.Clear();
Relations.CopyAllTo(relationStorage.Relations);
RelationDatas.CopyAllTo(relationStorage.RelationDatas);
for (int index = 0; index < Relations.Count; index += 1)
{
var relation = Relations.Get<(Entity, Entity)>(index);
relationStorage.Indices[relation] = index;
relationStorage.Indices[relation] = index;
if (!relationStorage.OutRelationSets.ContainsKey(relation.Item1))
{
relationStorage.OutRelationSets[relation.Item1] =
relationStorage.AcquireHashSetFromPool();
}
relationStorage.OutRelationSets[relation.Item1].Add(relation.Item2);
if (!relationStorage.InRelationSets.ContainsKey(relation.Item2))
{
relationStorage.InRelationSets[relation.Item2] =
relationStorage.AcquireHashSetFromPool();
}
relationStorage.InRelationSets[relation.Item2].Add(relation.Item1);
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Relations.Dispose();
RelationDatas.Dispose();
}
IsDisposed = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
foreach (var componentSnapshot in ComponentSnapshots)
{
componentSnapshot.Dispose();
}
foreach (var relationSnapshot in RelationSnapshots)
{
relationSnapshot.Dispose();
}
foreach (var componentSet in EntityComponentIndex)
{
componentSet.Dispose();
}
foreach (var relationSet in EntityRelationIndex)
{
relationSet.Dispose();
}
EntityIdAssigner.Dispose();
}
IsDisposed = true;
}
}
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,11 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class ComponentDepotState
{
public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>();
public Dictionary<FilterSignature, IndexableSetState<int>> FilterStates = new Dictionary<FilterSignature, IndexableSetState<int>>();
}
}

View File

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class ComponentStorageState
{
public int Count;
public byte[] EntityIDs;
public byte[] Components;
public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged
{
return new ComponentStorageState(
count,
count * sizeof(int),
count * sizeof(TComponent)
);
}
private ComponentStorageState(int count, int entityIDSize, int componentSize)
{
Count = count;
EntityIDs = new byte[entityIDSize];
Components = new byte[componentSize];
}
}
}

View File

@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class EntityStorageState
{
public int NextID;
public List<int> availableIDs = new List<int>();
}
}

View File

@ -1,16 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class IndexableSetState<T> where T : unmanaged
{
public int Count;
public byte[] Array;
public unsafe IndexableSetState(int count)
{
Count = count;
Array = new byte[sizeof(T) * count];
}
}
}

View File

@ -1,10 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class RelationDepotState
{
public Dictionary<Type, RelationStorageState> StorageStates = new Dictionary<Type, RelationStorageState>();
}
}

View File

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class RelationStorageState
{
public int Count;
public byte[] Relations;
public byte[] RelationDatas;
public unsafe static RelationStorageState Create<TRelation>(int count) where TRelation : unmanaged
{
return new RelationStorageState(
count,
count * sizeof(Relation),
count * sizeof(TRelation)
);
}
private RelationStorageState(int count, int relationSize, int relationDataSize)
{
Count = count;
Relations = new byte[relationSize];
RelationDatas = new byte[relationDataSize];
}
}
}

View File

@ -1,16 +0,0 @@
namespace MoonTools.ECS
{
public class WorldState
{
internal readonly ComponentDepotState ComponentDepotState;
internal readonly EntityStorageState EntityStorageState;
internal readonly RelationDepotState RelationDepotState;
public WorldState()
{
ComponentDepotState = new ComponentDepotState();
EntityStorageState = new EntityStorageState();
RelationDepotState = new RelationDepotState();
}
}
}

View File

@ -1,94 +1,15 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public abstract class System : Manipulator
{
public abstract class System : EntityComponentReader
{
internal MessageDepot MessageDepot => World.MessageDepot;
protected System(World world) : base(world) { }
public System(World world) : base(world) { }
public abstract void Update(TimeSpan delta);
public abstract void Update(TimeSpan delta);
protected Entity CreateEntity()
{
return EntityStorage.Create();
}
protected void Set<TComponent>(in Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG
// check for use after destroy
if (!Exists(entity))
{
throw new ArgumentException("This entity is not valid!");
}
#endif
ComponentDepot.Set<TComponent>(entity.ID, component);
}
protected void Remove<TComponent>(in Entity entity) where TComponent : unmanaged
{
ComponentDepot.Remove<TComponent>(entity.ID);
}
protected ReadOnlySpan<TMessage> ReadMessages<TMessage>() where TMessage : unmanaged
{
return MessageDepot.All<TMessage>();
}
protected TMessage ReadMessage<TMessage>() where TMessage : unmanaged
{
return MessageDepot.First<TMessage>();
}
protected bool SomeMessage<TMessage>() where TMessage : unmanaged
{
return MessageDepot.Some<TMessage>();
}
protected IEnumerable<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return MessageDepot.WithEntity<TMessage>(entity.ID);
}
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID);
}
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged, IHasEntity
{
return MessageDepot.SomeWithEntity<TMessage>(entity.ID);
}
protected void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(message);
}
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
{
RelationDepot.Set<TRelationKind>(new Relation(entityA, entityB), relationData);
}
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
RelationDepot.Remove<TRelationKind>(new Relation(entityA, entityB));
}
protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
RelationDepot.UnrelateAll<TRelationKind>(entity.ID);
}
// FIXME: this is insanely inefficient
protected void Destroy(in Entity entity)
{
ComponentDepot.OnEntityDestroy(entity.ID);
RelationDepot.OnEntityDestroy(entity.ID);
EntityStorage.Destroy(entity);
}
}
protected ReadOnlySpan<T> ReadMessages<T>() where T : unmanaged => World.ReadMessages<T>();
protected T ReadMessage<T>() where T : unmanaged => World.ReadMessage<T>();
protected bool SomeMessage<T>() where T : unmanaged => World.SomeMessage<T>();
protected void Send<T>(T message) where T : unmanaged => World.Send(message);
}

82
src/TypeId.cs Normal file
View File

@ -0,0 +1,82 @@
using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS;
public readonly record struct TypeId(uint Value) : IComparable<TypeId>
{
public int CompareTo(TypeId other)
{
return Value.CompareTo(other.Value);
}
public static implicit operator int(TypeId typeId)
{
return (int) typeId.Value;
}
}
internal class ComponentTypeIdAssigner
{
protected static ushort Counter;
}
internal class ComponentTypeIdAssigner<T> : ComponentTypeIdAssigner
{
public static readonly ushort Id;
public static readonly int Size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static ComponentTypeIdAssigner()
{
Id = Counter++;
Size = Unsafe.SizeOf<T>();
World.ComponentTypeElementSizes.Add(Size);
#if DEBUG
World.ComponentTypeToId[typeof(T)] = new TypeId(Id);
World.ComponentTypeIdToType.Add(typeof(T));
#endif
}
}
internal class RelationTypeIdAssigner
{
protected static ushort Counter;
}
internal class RelationTypeIdAssigner<T> : RelationTypeIdAssigner
{
public static readonly ushort Id;
public static readonly int Size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static RelationTypeIdAssigner()
{
Id = Counter++;
Size = Unsafe.SizeOf<T>();
World.RelationTypeElementSizes.Add(Size);
}
}
internal class MessageTypeIdAssigner
{
protected static ushort Counter;
}
internal class MessageTypeIdAssigner<T> : MessageTypeIdAssigner
{
public static readonly ushort Id;
public static readonly int Size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static MessageTypeIdAssigner()
{
Id = Counter++;
Size = Unsafe.SizeOf<T>();
World.MessageTypeElementSizes.Add(Size);
}
}

View File

@ -1,54 +1,506 @@
namespace MoonTools.ECS
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
#if DEBUG
using System.Reflection;
#endif
namespace MoonTools.ECS;
public class World : IDisposable
{
public class World
#if DEBUG
// TODO: is there a smarter way to do this?
internal static Dictionary<Type, TypeId> ComponentTypeToId = new Dictionary<Type, TypeId>();
internal static List<Type> ComponentTypeIdToType = new List<Type>();
#endif
internal static List<int> ComponentTypeElementSizes = new List<int>();
internal static List<int> RelationTypeElementSizes = new List<int>();
internal static List<int> MessageTypeElementSizes = new List<int>();
// Filters
internal readonly Dictionary<FilterSignature, Filter> FilterIndex = new Dictionary<FilterSignature, Filter>();
private readonly List<List<Filter>> ComponentTypeToFilter = new List<List<Filter>>();
// TODO: can we make the tag an native array of chars at some point?
internal List<string> EntityTags = new List<string>();
// Relation Storages
internal List<RelationStorage> RelationIndex = new List<RelationStorage>();
internal List<IndexableSet<TypeId>> EntityRelationIndex = new List<IndexableSet<TypeId>>();
// Message Storages
private List<MessageStorage> MessageIndex = new List<MessageStorage>();
public FilterBuilder FilterBuilder => new FilterBuilder(this);
internal readonly List<ComponentStorage> ComponentIndex = new List<ComponentStorage>();
internal List<IndexableSet<TypeId>> EntityComponentIndex = new List<IndexableSet<TypeId>>();
internal IdAssigner EntityIdAssigner = new IdAssigner();
private bool IsDisposed;
internal TypeId GetComponentTypeId<T>() where T : unmanaged
{
internal readonly EntityStorage EntityStorage = new EntityStorage();
internal readonly ComponentDepot ComponentDepot = new ComponentDepot();
internal readonly MessageDepot MessageDepot = new MessageDepot();
internal readonly RelationDepot RelationDepot = new RelationDepot();
public Entity CreateEntity()
var typeId = new TypeId(ComponentTypeIdAssigner<T>.Id);
if (typeId < ComponentIndex.Count)
{
return EntityStorage.Create();
return typeId;
}
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
// add missing storages, it's possible for there to be multiples in multi-world scenarios
for (var i = ComponentIndex.Count; i <= typeId; i += 1)
{
ComponentDepot.Set(entity.ID, component);
var missingTypeId = new TypeId((uint) i);
var componentStorage = new ComponentStorage(missingTypeId, ComponentTypeElementSizes[i]);
ComponentIndex.Add(componentStorage);
ComponentTypeToFilter.Add(new List<Filter>());
}
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
return typeId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentStorage GetComponentStorage<T>() where T : unmanaged
{
var typeId = GetComponentTypeId<T>();
return ComponentIndex[typeId];
}
// FILTERS
internal Filter GetFilter(FilterSignature signature)
{
if (!FilterIndex.TryGetValue(signature, out var filter))
{
MessageDepot.Add(message);
filter = new Filter(this, signature);
foreach (var typeId in signature.Included)
{
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
}
foreach (var typeId in signature.Excluded)
{
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
}
FilterIndex.Add(signature, filter);
}
public void FinishUpdate()
return filter;
}
// ENTITIES
public Entity CreateEntity(string tag = "")
{
var entity = new Entity(EntityIdAssigner.Assign());
if (entity.ID == EntityComponentIndex.Count)
{
MessageDepot.Clear();
EntityRelationIndex.Add(new IndexableSet<TypeId>());
EntityComponentIndex.Add(new IndexableSet<TypeId>());
EntityTags.Add(tag);
}
public void DisableSerialization<TComponent>() where TComponent : unmanaged
return entity;
}
public void Tag(Entity entity, string tag)
{
EntityTags[(int) entity.ID] = tag;
}
public string GetTag(Entity entity)
{
return EntityTags[(int) entity.ID];
}
public void Destroy(in Entity entity)
{
var componentSet = EntityComponentIndex[(int) entity.ID];
var relationSet = EntityRelationIndex[(int) entity.ID];
// remove all components from storages
foreach (var componentTypeIndex in componentSet)
{
ComponentDepot.DisableSerialization<TComponent>();
var componentStorage = ComponentIndex[componentTypeIndex];
componentStorage.Remove(entity);
foreach (var filter in ComponentTypeToFilter[componentTypeIndex])
{
filter.RemoveEntity(entity);
}
}
public WorldState CreateState()
// remove all relations from storage
foreach (var relationTypeIndex in relationSet)
{
return new WorldState();
var relationStorage = RelationIndex[relationTypeIndex];
relationStorage.RemoveEntity(entity);
}
public void Save(WorldState state)
{
ComponentDepot.Save(state.ComponentDepotState);
EntityStorage.Save(state.EntityStorageState);
RelationDepot.Save(state.RelationDepotState);
}
componentSet.Clear();
relationSet.Clear();
public void Load(WorldState state)
// recycle ID
EntityIdAssigner.Unassign(entity.ID);
EntityTags[(int) entity.ID] = "";
}
// COMPONENTS
public bool Has<T>(in Entity entity) where T : unmanaged
{
var storage = GetComponentStorage<T>();
return storage.Has(entity);
}
internal bool Has(in Entity entity, in TypeId typeId)
{
return EntityComponentIndex[(int) entity.ID].Contains(typeId);
}
public bool Some<T>() where T : unmanaged
{
var storage = GetComponentStorage<T>();
return storage.Any();
}
public ref T Get<T>(in Entity entity) where T : unmanaged
{
var storage = GetComponentStorage<T>();
return ref storage.Get<T>(entity);
}
public ref T GetSingleton<T>() where T : unmanaged
{
var storage = GetComponentStorage<T>();
return ref storage.GetFirst<T>();
}
public Entity GetSingletonEntity<T>() where T : unmanaged
{
var storage = GetComponentStorage<T>();
return storage.FirstEntity();
}
public void Set<T>(in Entity entity, in T component) where T : unmanaged
{
var componentStorage = GetComponentStorage<T>();
if (!componentStorage.Set(entity, component))
{
ComponentDepot.Load(state.ComponentDepotState);
EntityStorage.Load(state.EntityStorageState);
RelationDepot.Load(state.RelationDepotState);
EntityComponentIndex[(int) entity.ID].Add(componentStorage.TypeId);
foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId])
{
filter.Check(entity);
}
}
}
public void Remove<T>(in Entity entity) where T : unmanaged
{
var componentStorage = GetComponentStorage<T>();
if (componentStorage.Remove(entity))
{
EntityComponentIndex[(int) entity.ID].Remove(componentStorage.TypeId);
foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId])
{
filter.Check(entity);
}
}
}
// RELATIONS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private RelationStorage GetRelationStorage<T>() where T : unmanaged
{
var typeId = new TypeId(RelationTypeIdAssigner<T>.Id);
if (typeId.Value < RelationIndex.Count)
{
return RelationIndex[typeId];
}
for (var i = RelationIndex.Count; i <= typeId; i += 1)
{
RelationIndex.Add(new RelationStorage(RelationTypeElementSizes[i]));
}
return RelationIndex[typeId];
}
public void Relate<T>(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Set(entityA, entityB, relation);
EntityRelationIndex[(int) entityA.ID].Add(new TypeId(RelationTypeIdAssigner<T>.Id));
EntityRelationIndex[(int) entityB.ID].Add(new TypeId(RelationTypeIdAssigner<T>.Id));
}
public void Unrelate<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Remove(entityA, entityB);
}
public void UnrelateAll<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.RemoveEntity(entity);
}
public bool Related<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Has(entityA, entityB);
}
public T GetRelationData<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Get<T>(entityA, entityB);
}
public ReverseSpanEnumerator<(Entity, Entity)> Relations<T>() where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.All();
}
public ReverseSpanEnumerator<Entity> OutRelations<T>(Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelations(entity);
}
public Entity OutRelationSingleton<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutFirst(entity);
}
public bool HasOutRelation<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasOutRelation(entity);
}
public int OutRelationCount<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelationCount(entity);
}
public Entity NthOutRelation<T>(in Entity entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutNth(entity, n);
}
public ReverseSpanEnumerator<Entity> InRelations<T>(Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelations(entity);
}
public Entity InRelationSingleton<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InFirst(entity);
}
public bool HasInRelation<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasInRelation(entity);
}
public int InRelationCount<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelationCount(entity);
}
public Entity NthInRelation<T>(in Entity entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InNth(entity, n);
}
// MESSAGES
private MessageStorage GetMessageStorage<T>() where T : unmanaged
{
var typeId = new TypeId(MessageTypeIdAssigner<T>.Id);
if (typeId < MessageIndex.Count)
{
return MessageIndex[typeId];
}
for (var i = MessageIndex.Count; i <= typeId; i += 1)
{
MessageIndex.Add(new MessageStorage(MessageTypeElementSizes[i]));
}
return MessageIndex[typeId];
}
public void Send<T>(in T message) where T : unmanaged
{
GetMessageStorage<T>().Add(message);
}
public bool SomeMessage<T>() where T : unmanaged
{
return GetMessageStorage<T>().Some();
}
public ReadOnlySpan<T> ReadMessages<T>() where T : unmanaged
{
return GetMessageStorage<T>().All<T>();
}
public T ReadMessage<T>() where T : unmanaged
{
return GetMessageStorage<T>().First<T>();
}
public void ClearMessages<T>() where T : unmanaged
{
GetMessageStorage<T>().Clear();
}
// TODO: temporary component storage?
public void FinishUpdate()
{
foreach (var messageStorage in MessageIndex)
{
messageStorage.Clear();
}
}
// DEBUG
// NOTE: these methods are very inefficient
// they should only be used in debugging contexts!!
#if DEBUG
public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity)
{
return new ComponentTypeEnumerator(this, EntityComponentIndex[(int) entity.ID]);
}
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
{
var baseGetComponentStorageMethod = typeof(World).GetMethod(nameof(World.GetComponentStorage), BindingFlags.NonPublic | BindingFlags.Instance)!;
var genericGetComponentStorageMethod = baseGetComponentStorageMethod.MakeGenericMethod(componentType);
var storage = genericGetComponentStorageMethod.Invoke(this, null) as ComponentStorage;
return storage!.Debug_GetEntities();
}
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
foreach (var type in ComponentTypeToId.Keys)
{
if (type.ToString().ToLower().Contains(typeString.ToLower()))
{
yield return type;
}
}
}
public ref struct ComponentTypeEnumerator
{
private World World;
private IndexableSet<TypeId> Types;
private int ComponentIndex;
public ComponentTypeEnumerator GetEnumerator() => this;
internal ComponentTypeEnumerator(
World world,
IndexableSet<TypeId> types
)
{
World = world;
Types = types;
ComponentIndex = -1;
}
public bool MoveNext()
{
ComponentIndex += 1;
return ComponentIndex < Types.Count;
}
public Type Current => ComponentTypeIdToType[Types[ComponentIndex]];
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
foreach (var componentStorage in ComponentIndex)
{
componentStorage.Dispose();
}
foreach (var relationStorage in RelationIndex)
{
relationStorage.Dispose();
}
foreach (var messageStorage in MessageIndex)
{
messageStorage.Dispose();
}
foreach (var componentSet in EntityComponentIndex)
{
componentSet.Dispose();
}
foreach (var relationSet in EntityRelationIndex)
{
relationSet.Dispose();
}
foreach (var filter in FilterIndex.Values)
{
filter.Dispose();
}
EntityIdAssigner.Dispose();
}
IsDisposed = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~World()
// {
// // 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);
}
}