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
34 changed files with 2288 additions and 2007 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,129 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
internal class ComponentDepot
{
private TypeIndices ComponentTypeIndices;
private ComponentStorage[] storages = new ComponentStorage[256];
public ComponentDepot(TypeIndices componentTypeIndices)
{
ComponentTypeIndices = componentTypeIndices;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Register<TComponent>(int index) where TComponent : unmanaged
{
if (index >= storages.Length)
{
Array.Resize(ref storages, storages.Length * 2);
}
storages[index] = new ComponentStorage<TComponent>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
{
var storageIndex = ComponentTypeIndices.GetIndex<TComponent>();
// TODO: is there some way to avoid this null check?
if (storages[storageIndex] == null)
{
Register<TComponent>(storageIndex);
}
return (ComponentStorage<TComponent>) storages[storageIndex];
}
public bool Some<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().Any();
}
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get(entityID);
}
public ref readonly TComponent GetFirst<TComponent>() where TComponent : unmanaged
{
return ref Lookup<TComponent>().GetFirst();
}
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
{
Lookup<TComponent>().Set(entityID, component);
}
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().FirstEntity();
}
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().AllComponents();
}
public void Remove(int entityID, int storageIndex)
{
storages[storageIndex].Remove(entityID);
}
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
{
Lookup<TComponent>().Remove(entityID);
}
public void Clear()
{
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
{
if (storages[i] != null)
{
storages[i].Clear();
}
}
}
// these methods used to implement snapshots, templates, and debugging
internal unsafe void* UntypedGet(int entityID, int componentTypeIndex)
{
return storages[componentTypeIndex].UntypedGet(entityID);
}
internal unsafe void Set(int entityID, int componentTypeIndex, void* component)
{
storages[componentTypeIndex].Set(entityID, component);
}
public void CreateMissingStorages(ComponentDepot other)
{
for (var i = 0; i < ComponentTypeIndices.Count; i += 1)
{
if (storages[i] == null && other.storages[i] != null)
{
storages[i] = other.storages[i].CreateStorage();
}
}
}
// this method is used to iterate components of an entity, only for use with a debug inspector
#if DEBUG
public object Debug_Get(int entityID, int componentTypeIndex)
{
return storages[componentTypeIndex].Debug_Get(entityID);
}
public IEnumerable<int> Debug_GetEntityIDs(int componentTypeIndex)
{
return storages[componentTypeIndex].Debug_GetEntityIDs();
}
#endif
}
}

View File

@ -1,148 +1,142 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
internal class ComponentStorage : IDisposable
{
internal abstract class ComponentStorage
{
internal abstract unsafe void Set(int entityID, void* component);
public abstract bool Remove(int entityID);
public abstract void Clear();
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;
// used for debugging and template instantiation
internal abstract unsafe void* UntypedGet(int entityID);
// used to create correctly typed storage on snapshot
public abstract ComponentStorage CreateStorage();
#if DEBUG
internal abstract object Debug_Get(int entityID);
internal abstract IEnumerable<int> Debug_GetEntityIDs();
#endif
private bool IsDisposed;
public ComponentStorage(TypeId typeId, int elementSize)
{
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 ref readonly TComponent Get(int entityID)
{
return ref components[entityIDToStorageIndex[entityID]];
}
public ref T Get<T>(in Entity entity) where T : unmanaged
{
return ref Components.Get<T>(EntityIDToStorageIndex[entity]);
}
internal override unsafe void* UntypedGet(int entityID)
{
fixed (void* p = &components[entityIDToStorageIndex[entityID]])
{
return p;
}
}
public ref readonly TComponent GetFirst()
{
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);
}
public void 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))
{
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;
}
components[entityIDToStorageIndex[entityID]] = component;
Components.Set(index, component);
return true;
}
internal override unsafe void Set(int entityID, void* component)
else
{
Set(entityID, *((TComponent*) component));
}
// Returns true if the entity had this component.
public override bool Remove(int entityID)
{
if (entityIDToStorageIndex.TryGetValue(entityID, out int storageIndex))
{
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 override 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;
public ReadOnlySpan<TComponent> AllComponents()
{
return new ReadOnlySpan<TComponent>(components, 0, nextID);
}
var lastEntity = EntityIDs[lastElementIndex];
public Entity FirstEntity()
{
#if DEBUG
if (nextID == 0)
// 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)
{
throw new IndexOutOfRangeException("Component storage is empty!");
EntityIDToStorageIndex[lastEntity] = index;
}
#endif
return new Entity(entityIDs[0]);
return true;
}
public override ComponentStorage<TComponent> CreateStorage()
return false;
}
public void Clear()
{
Components.Clear();
EntityIDs.Clear();
EntityIDToStorageIndex.Clear();
}
public Entity FirstEntity()
{
#if DEBUG
if (EntityIDs.Count == 0)
{
return new ComponentStorage<TComponent>();
throw new IndexOutOfRangeException("Component storage is empty!");
}
#endif
return EntityIDs[0];
}
#if DEBUG
internal override object Debug_Get(int entityID)
{
return components[entityIDToStorageIndex[entityID]];
}
internal override IEnumerable<int> Debug_GetEntityIDs()
{
return entityIDToStorageIndex.Keys;
}
internal IEnumerable<Entity> Debug_GetEntities()
{
return EntityIDToStorageIndex.Keys;
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Components.Dispose();
EntityIDs.Dispose();
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,44 +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<dynamic> Debug_GetAllComponents(Entity entity)
{
foreach (var typeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
{
yield return ComponentDepot.Debug_Get(entity.ID, typeIndex);
}
}
protected IEnumerable<Entity> Debug_GetEntities(Type componentType)
{
foreach (var entityID in ComponentDepot.Debug_GetEntityIDs(ComponentTypeIndices.GetIndex(componentType)))
{
yield return new Entity(entityID);
}
}
protected IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
foreach (var type in ComponentTypeIndices.Types)
{
if (type.ToString().ToLower().Contains(typeString.ToLower()))
{
yield return type;
}
}
}
}
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,40 +0,0 @@
using System;
namespace MoonTools.ECS
{
public class DynamicArray<T> where T : unmanaged
{
private T[] Array;
public int Count { get; private set; }
public Span<T> ToSpan() => new Span<T>(Array, 0, Count);
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, 0, Count));
public DynamicArray(int capacity = 16)
{
Array = new T[capacity];
Count = 0;
}
public ref T this[int i]
{
get { return ref Array[i]; }
}
public void Add(T item)
{
if (Count >= Array.Length)
{
global::System.Array.Resize(ref Array, Array.Length * 2);
}
Array[Count] = item;
Count += 1;
}
public void Clear()
{
Count = 0;
}
}
}

View File

@ -1,49 +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 static implicit operator int(Entity e)
{
return e.ID;
}
public static implicit operator Entity(int i)
{
return new Entity(i);
}
}
}
public readonly record struct Entity(uint ID);

View File

@ -1,112 +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(FilterStorage, ComponentTypeIndices, RelationTypeIndices);
internal FilterStorage FilterStorage => World.FilterStorage;
internal TypeIndices ComponentTypeIndices => World.ComponentTypeIndices;
internal TypeIndices RelationTypeIndices => World.RelationTypeIndices;
internal TemplateStorage TemplateStorage => World.TemplateStorage;
internal ComponentDepot TemplateComponentDepot => World.TemplateComponentDepot;
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
{
var storageIndex = ComponentTypeIndices.GetIndex<TComponent>();
return EntityStorage.HasComponent(entity.ID, storageIndex);
}
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.GetFirst<TComponent>();
}
protected Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return ComponentDepot.GetSingletonEntity<TComponent>();
}
protected ReverseSpanEnumerator<(Entity, Entity)> 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);
}
protected TRelationKind GetRelationData<TRelationKind>(in Entity a, in Entity b) where TRelationKind : unmanaged
{
return RelationDepot.Get<TRelationKind>(a, b);
}
// relations go A->B, so given A, will give all entities in outgoing relations of this kind.
protected ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.OutRelations<TRelationKind>(entity.ID);
}
protected Entity 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 entities in incoming A relations of this kind.
protected ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
return RelationDepot.InRelations<TRelationKind>(entity.ID);
}
protected Entity 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,117 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class EntityStorage
{
private int nextID = 0;
// FIXME: why is this duplicated?
private readonly Stack<int> availableIDs = new Stack<int>();
// FIXME: this is only needed in debug mode
private readonly HashSet<int> availableIDHash = new HashSet<int>();
private Dictionary<int, HashSet<int>> EntityToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
private Dictionary<int, HashSet<int>> EntityToRelationTypeIndices = new Dictionary<int, HashSet<int>>();
public int Count => nextID - availableIDs.Count;
public Entity Create()
{
var entity = new Entity(NextID());
EntityToComponentTypeIndices.TryAdd(entity.ID, new HashSet<int>());
EntityToRelationTypeIndices.TryAdd(entity.ID, new HashSet<int>());
return entity;
}
public bool Exists(in Entity entity)
{
return Taken(entity.ID);
}
public void Destroy(in Entity entity)
{
EntityToComponentTypeIndices[entity.ID].Clear();
EntityToRelationTypeIndices[entity.ID].Clear();
Release(entity.ID);
}
// Returns true if the component is new.
public bool SetComponent(int entityID, int componentTypeIndex)
{
return EntityToComponentTypeIndices[entityID].Add(componentTypeIndex);
}
public bool HasComponent(int entityID, int componentTypeIndex)
{
return EntityToComponentTypeIndices[entityID].Contains(componentTypeIndex);
}
// Returns true if the component existed.
public bool RemoveComponent(int entityID, int componentTypeIndex)
{
return EntityToComponentTypeIndices[entityID].Remove(componentTypeIndex);
}
public void AddRelationKind(int entityID, int relationIndex)
{
EntityToRelationTypeIndices[entityID].Add(relationIndex);
}
public void RemoveRelation(int entityId, int relationIndex)
{
EntityToRelationTypeIndices[entityId].Remove(relationIndex);
}
public HashSet<int> ComponentTypeIndices(int entityID)
{
return EntityToComponentTypeIndices[entityID];
}
public HashSet<int> RelationTypeIndices(int entityID)
{
return EntityToRelationTypeIndices[entityID];
}
public void Clear()
{
nextID = 0;
foreach (var componentSet in EntityToComponentTypeIndices.Values)
{
componentSet.Clear();
}
foreach (var relationSet in EntityToRelationTypeIndices.Values)
{
relationSet.Clear();
}
availableIDs.Clear();
availableIDHash.Clear();
}
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

@ -1,36 +1,35 @@
using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public ref struct ReverseSpanEnumerator<T>
{
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()
{
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)
{
if (index > 0)
{
index -= 1;
return true;
}
return false;
Index -= 1;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReverseSpanEnumerator(Span<T> span)
{
Span = span;
index = span.Length;
}
public static ReverseSpanEnumerator<T> Empty => new ReverseSpanEnumerator<T>();
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,32 +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 FilterStorage FilterStorage;
private World World;
internal FilterSignature Signature;
internal Filter(
FilterStorage filterStorage,
HashSet<int> included,
HashSet<int> excluded,
HashSet<int> inRelations,
HashSet<int> outRelations
) {
FilterStorage = filterStorage;
Signature = new FilterSignature(included, excluded, inRelations, outRelations);
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)
{
World.Destroy(entity);
}
}
internal void Check(Entity entity)
{
foreach (var type in Signature.Included)
{
if (!World.Has(entity, type))
{
EntitySet.Remove(entity);
return;
}
}
public ReverseSpanEnumerator<Entity> Entities => FilterStorage.FilterEntities(Signature);
public LinearCongruentialEnumerator EntitiesInRandomOrder => FilterStorage.FilterEntitiesRandom(Signature);
public Entity RandomEntity => FilterStorage.FilterRandomEntity(Signature);
foreach (var type in Signature.Excluded)
{
if (World.Has(entity, type))
{
EntitySet.Remove(entity);
return;
}
}
public int Count => FilterStorage.FilterCount(Signature);
public bool Empty => Count == 0;
EntitySet.Add(entity);
}
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, 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,77 +1,43 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public struct FilterBuilder
{
private TypeIndices ComponentTypeIndices;
private TypeIndices RelationTypeIndices;
private FilterStorage FilterStorage;
private HashSet<int> Included;
private HashSet<int> Excluded;
private HashSet<int> InRelations;
private HashSet<int> OutRelations;
World World;
IndexableSet<TypeId> Included;
IndexableSet<TypeId> Excluded;
internal FilterBuilder(
FilterStorage filterStorage,
TypeIndices componentTypeIndices,
TypeIndices relationTypeIndices
) {
FilterStorage = filterStorage;
ComponentTypeIndices = componentTypeIndices;
RelationTypeIndices = relationTypeIndices;
Included = new HashSet<int>();
Excluded = new HashSet<int>();
InRelations = new HashSet<int>();
OutRelations = new HashSet<int>();
internal FilterBuilder(World world)
{
World = world;
Included = new IndexableSet<TypeId>();
Excluded = new IndexableSet<TypeId>();
}
private FilterBuilder(
FilterStorage filterStorage,
TypeIndices componentTypeIndices,
TypeIndices relationTypeIndices,
HashSet<int> included,
HashSet<int> excluded,
HashSet<int> inRelations,
HashSet<int> outRelations
) {
FilterStorage = filterStorage;
ComponentTypeIndices = componentTypeIndices;
RelationTypeIndices = relationTypeIndices;
private FilterBuilder(World world, IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
World = world;
Included = included;
Excluded = excluded;
InRelations = inRelations;
OutRelations = outRelations;
}
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
public FilterBuilder Include<T>() where T : unmanaged
{
Included.Add(ComponentTypeIndices.GetIndex<TComponent>());
return this;
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
{
Excluded.Add(ComponentTypeIndices.GetIndex<TComponent>());
return this;
}
public FilterBuilder WithInRelation<TRelation>() where TRelation : unmanaged
{
InRelations.Add(RelationTypeIndices.GetIndex<TRelation>());
return this;
}
public FilterBuilder WithOutRelation<TRelation>() where TRelation : unmanaged
{
OutRelations.Add(RelationTypeIndices.GetIndex<TRelation>());
return this;
Excluded.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public Filter Build()
{
return FilterStorage.CreateFilter(Included, Excluded, InRelations, OutRelations);
var signature = new FilterSignature(Included, Excluded);
return World.GetFilter(signature);
}
}
}

View File

@ -1,76 +1,69 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
public struct FilterSignature : IEquatable<FilterSignature>
{
internal struct FilterSignature : IEquatable<FilterSignature>
public readonly IndexableSet<TypeId> Included;
public readonly IndexableSet<TypeId> Excluded;
public FilterSignature(IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
public readonly HashSet<int> Included;
public readonly HashSet<int> Excluded;
public readonly HashSet<int> InRelations;
public readonly HashSet<int> OutRelations;
Included = included;
Excluded = excluded;
}
public FilterSignature(
HashSet<int> included,
HashSet<int> excluded,
HashSet<int> inRelations,
HashSet<int> outRelations
) {
Included = included;
Excluded = excluded;
InRelations = inRelations;
OutRelations = outRelations;
}
public override bool Equals(object? obj)
{
return obj is FilterSignature signature && Equals(signature);
}
public override bool Equals(object? obj)
public bool Equals(FilterSignature other)
{
foreach (var included in Included)
{
return obj is FilterSignature signature && Equals(signature);
}
public bool Equals(FilterSignature other)
{
return
Included.SetEquals(other.Included) &&
Excluded.SetEquals(other.Excluded) &&
InRelations.SetEquals(other.InRelations) &&
OutRelations.SetEquals(other.OutRelations);
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var type in Included)
if (!other.Included.Contains(included))
{
hashcode = HashCode.Combine(hashcode, type);
return false;
}
foreach (var type in Excluded)
{
hashcode = HashCode.Combine(hashcode, type);
}
foreach (var type in InRelations)
{
hashcode = HashCode.Combine(hashcode, type);
}
foreach (var type in OutRelations)
{
hashcode = HashCode.Combine(hashcode, type);
}
return hashcode;
}
public static bool operator ==(FilterSignature left, FilterSignature right)
foreach (var excluded in Excluded)
{
return left.Equals(right);
if (!other.Excluded.Contains(excluded))
{
return false;
}
}
public static bool operator !=(FilterSignature left, FilterSignature right)
return true;
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var type in Included)
{
return !(left == right);
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,196 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
internal class FilterStorage
{
private EntityStorage EntityStorage;
private RelationDepot RelationDepot;
private TypeIndices ComponentTypeIndices;
private TypeIndices RelationTypeIndices;
private Dictionary<FilterSignature, IndexableSet<Entity>> filterSignatureToEntityIDs = new Dictionary<FilterSignature, IndexableSet<Entity>>();
private Dictionary<int, HashSet<FilterSignature>> componentTypeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>();
private Dictionary<int, HashSet<FilterSignature>> relationTypeToFilterSignatures = new Dictionary<int, HashSet<FilterSignature>>();
public FilterStorage(
EntityStorage entityStorage,
RelationDepot relationDepot,
TypeIndices componentTypeIndices,
TypeIndices relationTypeIndices
) {
EntityStorage = entityStorage;
RelationDepot = relationDepot;
ComponentTypeIndices = componentTypeIndices;
RelationTypeIndices = relationTypeIndices;
}
public Filter CreateFilter(
HashSet<int> included,
HashSet<int> excluded,
HashSet<int> inRelations,
HashSet<int> outRelations
) {
var filterSignature = new FilterSignature(included, excluded, inRelations, outRelations);
if (!filterSignatureToEntityIDs.ContainsKey(filterSignature))
{
filterSignatureToEntityIDs.Add(filterSignature, new IndexableSet<Entity>());
foreach (var type in included)
{
if (!componentTypeToFilterSignatures.ContainsKey(type))
{
componentTypeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
componentTypeToFilterSignatures[type].Add(filterSignature);
}
foreach (var type in excluded)
{
if (!componentTypeToFilterSignatures.ContainsKey(type))
{
componentTypeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
componentTypeToFilterSignatures[type].Add(filterSignature);
}
foreach (var type in inRelations)
{
if (!relationTypeToFilterSignatures.ContainsKey(type))
{
relationTypeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
relationTypeToFilterSignatures[type].Add(filterSignature);
}
foreach (var type in outRelations)
{
if (!relationTypeToFilterSignatures.ContainsKey(type))
{
relationTypeToFilterSignatures.Add(type, new HashSet<FilterSignature>());
}
relationTypeToFilterSignatures[type].Add(filterSignature);
}
}
return new Filter(this, included, excluded, inRelations, outRelations);
}
public ReverseSpanEnumerator<Entity> FilterEntities(FilterSignature filterSignature)
{
return filterSignatureToEntityIDs[filterSignature].GetEnumerator();
}
public LinearCongruentialEnumerator FilterEntitiesRandom(FilterSignature filterSignature)
{
return RandomGenerator.LinearCongruentialGenerator(FilterCount(filterSignature));
}
public Entity FilterNthEntity(FilterSignature filterSignature, int index)
{
return new Entity(filterSignatureToEntityIDs[filterSignature][index]);
}
public Entity FilterRandomEntity(FilterSignature filterSignature)
{
var randomIndex = RandomGenerator.Next(FilterCount(filterSignature));
return new Entity(filterSignatureToEntityIDs[filterSignature][randomIndex]);
}
public int FilterCount(FilterSignature filterSignature)
{
return filterSignatureToEntityIDs[filterSignature].Count;
}
public void CheckComponentChange(int entityID, int componentTypeIndex)
{
if (componentTypeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
CheckFilter(entityID, filterSignature);
}
}
}
public void CheckRelationChange(int entityID, int relationTypeIndex)
{
if (relationTypeToFilterSignatures.TryGetValue(relationTypeIndex, out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
CheckFilter(entityID, filterSignature);
}
}
}
public void Check<TComponent>(int entityID) where TComponent : unmanaged
{
CheckComponentChange(entityID, ComponentTypeIndices.GetIndex<TComponent>());
}
public bool CheckSatisfied(int entityID, FilterSignature filterSignature)
{
foreach (var type in filterSignature.Included)
{
if (!EntityStorage.HasComponent(entityID, type))
{
return false;
}
}
foreach (var type in filterSignature.Excluded)
{
if (EntityStorage.HasComponent(entityID, type))
{
return false;
}
}
foreach (var type in filterSignature.InRelations)
{
if (!RelationDepot.HasInRelation(entityID, type))
{
return false;
}
}
foreach (var type in filterSignature.OutRelations)
{
if (!RelationDepot.HasOutRelation(entityID, type))
{
return false;
}
}
return true;
}
private void CheckFilter(int entityID, FilterSignature filterSignature)
{
if (CheckSatisfied(entityID, filterSignature))
{
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
}
else
{
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
}
}
public void RemoveEntity(int entityID, int componentTypeIndex)
{
if (componentTypeToFilterSignatures.TryGetValue(componentTypeIndex, out var filterSignatures))
{
foreach (var filterSignature in filterSignatures)
{
filterSignatureToEntityIDs[filterSignature].Remove(entityID);
}
}
}
}
}

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,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
internal class IndexableSet<T> where T : unmanaged
{
private Dictionary<T, int> indices;
private T[] array;
public int Count { get; private set; }
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(array, 0, Count));
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 void Clear()
{
Count = 0;
}
public struct Enumerator
{
/// <summary>The set being enumerated.</summary>
private readonly IndexableSet<T> _set;
/// <summary>The next index to yield.</summary>
private int _index;
/// <summary>Initialize the enumerator.</summary>
/// <param name="set">The set to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(IndexableSet<T> set)
{
_set = set;
_index = _set.Count;
}
/// <summary>Advances the enumerator to the next element of the span.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int index = _index - 1;
if (index >= 0)
{
_index = index;
return true;
}
return false;
}
/// <summary>Gets the element at the current position of the enumerator.</summary>
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _set[_index];
}
}
}
}

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,68 +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 : unmanaged
{
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 : unmanaged
{
Lookup<TMessage>().Add(message);
}
public void Add<TMessage>(int entityID, in TMessage message) where TMessage : unmanaged
{
Lookup<TMessage>().Add(entityID, message);
}
public bool Some<TMessage>() where TMessage : unmanaged
{
return Lookup<TMessage>().Some();
}
public ReadOnlySpan<TMessage> All<TMessage>() where TMessage : unmanaged
{
return Lookup<TMessage>().All();
}
public TMessage First<TMessage>() where TMessage : unmanaged
{
return Lookup<TMessage>().First();
}
public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged
{
return Lookup<TMessage>().WithEntity(entityID);
}
public ref readonly TMessage FirstWithEntity<TMessage>(int entityID) where TMessage : unmanaged
{
return ref Lookup<TMessage>().FirstWithEntity(entityID);
}
public bool SomeWithEntity<TMessage>(int entityID) where TMessage : unmanaged
{
return Lookup<TMessage>().SomeWithEntity(entityID);
}
public void Clear()
{
foreach (var storage in storages.Values)
{
storage.Clear();
}
}
}
}

View File

@ -1,93 +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 : unmanaged
public void Add<T>(in T message) where T : unmanaged
{
private int count = 0;
private int capacity = 128;
private TMessage[] messages;
// duplicating storage here for fast iteration
private Dictionary<int, DynamicArray<TMessage>> entityToMessages = new Dictionary<int, DynamicArray<TMessage>>();
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;
count += 1;
}
public void Add(int entityID, in TMessage message)
{
if (!entityToMessages.ContainsKey(entityID))
{
entityToMessages.Add(entityID, new DynamicArray<TMessage>());
}
entityToMessages[entityID].Add(message);
Add(message);
}
public bool Some()
{
return count > 0;
}
public ReadOnlySpan<TMessage> All()
{
return new ReadOnlySpan<TMessage>(messages, 0, count);
}
public TMessage First()
{
return messages[0];
}
public ReverseSpanEnumerator<TMessage> WithEntity(int entityID)
{
if (entityToMessages.TryGetValue(entityID, out var messages))
{
return messages.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<TMessage>.Empty;
}
}
public ref readonly TMessage FirstWithEntity(int entityID)
{
return ref entityToMessages[entityID][0];
}
public bool SomeWithEntity(int entityID)
{
return entityToMessages.ContainsKey(entityID) && entityToMessages[entityID].Count > 0;
}
public override void Clear()
{
count = 0;
foreach (var set in entityToMessages.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,72 +1,208 @@
using System;
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 LinearCongruentialEnumerator 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;
}
return new LinearCongruentialEnumerator(random.Next(n), x, n);
Unsafe.Write(ptr + offset, Index);
}
}
public struct LinearCongruentialEnumerator
/// <summary>
/// Loads the entire state of the RNG from a Span.
/// </summary>
/// <param name="bytes">Must be a span of at least STATE_BYTE_COUNT bytes.</param>
/// <exception cref="ArgumentException">Thrown if the byte span is too short.</exception>
public unsafe void LoadState(Span<byte> bytes)
{
private readonly int start;
private readonly int count;
private readonly int prime;
private int current;
public LinearCongruentialEnumerator GetEnumerator() => this;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal LinearCongruentialEnumerator(int start, int prime, int count)
#if DEBUG
if (bytes.Length < STATE_BYTE_COUNT)
{
current = start;
this.start = start;
this.prime = prime;
this.count = count;
throw new ArgumentException("Byte span too short!");
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
fixed (byte* ptr = bytes)
{
current += 1;
if (current < start + count)
var offset = 0;
for (var i = 0; i < 16; i += 1)
{
return true;
State[i] = Unsafe.Read<uint>(ptr + offset);
offset += 4;
}
return false;
Index = Unsafe.Read<uint>(ptr + offset);
}
}
public int Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (current * prime) % count;
}
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,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS
{
internal class RelationDepot
{
private TypeIndices RelationTypeIndices;
private RelationStorage[] storages = new RelationStorage[256];
public RelationDepot(TypeIndices relationTypeIndices)
{
RelationTypeIndices = relationTypeIndices;
}
private void Register<TRelationKind>(int index) where TRelationKind : unmanaged
{
if (index >= storages.Length)
{
Array.Resize(ref storages, storages.Length * 2);
}
storages[index] = new RelationStorage<TRelationKind>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private RelationStorage<TRelationKind> Lookup<TRelationKind>() where TRelationKind : unmanaged
{
var storageIndex = RelationTypeIndices.GetIndex<TRelationKind>();
// TODO: is there some way to avoid this null check?
if (storages[storageIndex] == null)
{
Register<TRelationKind>(storageIndex);
}
return (RelationStorage<TRelationKind>) storages[storageIndex];
}
public void Set<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().Set(entityA, entityB, relationData);
}
public TRelationKind Get<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().Get(entityA, entityB);
}
public (bool, bool) Remove<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().Remove(entityA, entityB);
}
public void UnrelateAll<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
Lookup<TRelationKind>().UnrelateAll(entityID);
}
public ReverseSpanEnumerator<(Entity, Entity)> Relations<TRelationKind>() where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().All();
}
public bool Related<TRelationKind>(int idA, int idB) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().Has((idA, idB));
}
public ReverseSpanEnumerator<Entity> OutRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().OutRelations(entityID);
}
public Entity 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 bool HasOutRelation(int entityID, int typeIndex)
{
return storages[typeIndex].HasOutRelation(entityID);
}
public ReverseSpanEnumerator<Entity> InRelations<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().InRelations(entityID);
}
public Entity 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 bool HasInRelation(int entityID, int typeIndex)
{
return storages[typeIndex].HasInRelation(entityID);
}
public int InRelationCount<TRelationKind>(int entityID) where TRelationKind : unmanaged
{
return Lookup<TRelationKind>().InRelationCount(entityID);
}
// untyped methods used for destroying and snapshots
public unsafe void Set(int entityA, int entityB, int relationTypeIndex, void* relationData)
{
storages[relationTypeIndex].Set(entityA, entityB, relationData);
}
public int GetStorageIndex(int relationTypeIndex, int entityA, int entityB)
{
return storages[relationTypeIndex].GetStorageIndex(entityA, entityB);
}
public unsafe void* Get(int relationTypeIndex, int relationStorageIndex)
{
return storages[relationTypeIndex].Get(relationStorageIndex);
}
public void UnrelateAll(int entityID, int relationTypeIndex)
{
storages[relationTypeIndex].UnrelateAll(entityID);
}
public ReverseSpanEnumerator<Entity> OutRelations(int entityID, int relationTypeIndex)
{
return storages[relationTypeIndex].OutRelations(entityID);
}
public void Clear()
{
for (var i = 0; i < RelationTypeIndices.Count; i += 1)
{
if (storages[i] != null)
{
storages[i].Clear();
}
}
}
public void CreateMissingStorages(RelationDepot other)
{
for (var i = 0; i < RelationTypeIndices.Count; i += 1)
{
if (storages[i] == null && other.storages[i] != null)
{
storages[i] = other.storages[i].CreateStorage();
}
}
}
}
}

View File

@ -1,278 +1,286 @@
using System;
using System.Collections.Generic;
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 bool HasInRelation(int entityID);
public abstract bool HasOutRelation(int entityID);
public abstract unsafe void Set(int entityA, int entityB, void* relationData);
public abstract int GetStorageIndex(int entityA, int entityB);
public abstract unsafe void* Get(int relationStorageIndex);
public abstract void UnrelateAll(int entityID);
public abstract ReverseSpanEnumerator<Entity> OutRelations(int entityID);
public abstract RelationStorage CreateStorage();
public abstract void Clear();
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<(Entity, Entity), int> indices = new Dictionary<(Entity, Entity), int>(16);
private (Entity, Entity)[] relations = new (Entity, Entity)[16];
private TRelation[] relationDatas = new TRelation[16];
private Dictionary<int, IndexableSet<Entity>> outRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Dictionary<int, IndexableSet<Entity>> inRelations = new Dictionary<int, IndexableSet<Entity>>(16);
private Stack<IndexableSet<Entity>> listPool = new Stack<IndexableSet<Entity>>();
return new ReverseSpanEnumerator<(Entity, Entity)>(Relations.ToSpan<(Entity, Entity)>());
}
public ReverseSpanEnumerator<(Entity, Entity)> 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))
{
return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count));
RelationDatas.Set(index, relationData);
return;
}
public void Set(in Entity entityA, in Entity entityB, TRelation relationData)
if (!OutRelationSets.ContainsKey(entityA))
{
var relation = (entityA, entityB);
if (indices.TryGetValue(relation, out var index))
{
relationDatas[index] = relationData;
return;
}
var idA = entityA.ID;
var idB = entityB.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 TRelation Get(in Entity entityA, in Entity entityB)
if (!InRelationSets.ContainsKey(entityB))
{
return relationDatas[indices[(entityA, entityB)]];
InRelationSets[entityB] = AcquireHashSetFromPool();
}
InRelationSets[entityB].Add(entityA);
public bool Has((Entity, Entity) relation)
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))
{
return indices.ContainsKey(relation);
return entityOutRelations.GetEnumerator();
}
public override ReverseSpanEnumerator<Entity> OutRelations(int entityID)
else
{
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
{
return entityOutRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
return ReverseSpanEnumerator<Entity>.Empty;
}
}
public Entity OutFirst(int entityID)
{
public Entity OutFirst(Entity Entity)
{
return OutNth(Entity, 0);
}
public Entity OutNth(Entity Entity, int n)
{
#if DEBUG
if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0)
{
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
return outRelations[entityID][0];
}
return OutRelationSets[Entity][n];
}
public override 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.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public int OutRelationCount(Entity Entity)
{
return OutRelationSets.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public ReverseSpanEnumerator<Entity> InRelations(int entityID)
public ReverseSpanEnumerator<Entity> InRelations(Entity Entity)
{
if (InRelationSets.TryGetValue(Entity, out var entityInRelations))
{
if (inRelations.TryGetValue(entityID, out var entityInRelations))
{
return entityInRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
return entityInRelations.GetEnumerator();
}
public Entity 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) || inRelations[entityID].Count == 0)
{
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
return inRelations[entityID][0];
}
return InRelationSets[Entity][n];
}
public override bool HasInRelation(int entityID)
public bool HasInRelation(Entity Entity)
{
return InRelationSets.ContainsKey(Entity) && InRelationSets[Entity].Count > 0;
}
public int InRelationCount(Entity Entity)
{
return InRelationSets.TryGetValue(Entity, out var entityInRelations) ? entityInRelations.Count : 0;
}
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))
{
return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0;
}
public int InRelationCount(int entityID)
{
return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0;
}
public (bool, bool) Remove(in Entity entityA, in Entity entityB)
{
var aEmpty = false;
var bEmpty = false;
var relation = (entityA, entityB);
if (outRelations.TryGetValue(entityA.ID, out var entityOutRelations))
entityOutRelations.Remove(entityB);
if (OutRelationSets[entityA].Count == 0)
{
entityOutRelations.Remove(entityB.ID);
if (outRelations[entityA.ID].Count == 0)
{
aEmpty = true;
}
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.TryGetValue(entityB.ID, out var entityInRelations))
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)
{
entityInRelations.Remove(entityA.ID);
if (inRelations[entityB.ID].Count == 0)
{
bEmpty = true;
}
Remove(entity, entityB);
}
if (indices.TryGetValue(relation, out var index))
{
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);
Relations.Dispose();
RelationDatas.Dispose();
}
return (aEmpty, bEmpty);
IsDisposed = true;
}
}
private IndexableSet<Entity> AcquireHashSetFromPool()
{
if (listPool.Count == 0)
{
listPool.Push(new IndexableSet<Entity>());
}
// ~RelationStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
return listPool.Pop();
}
private void ReturnHashSetToPool(IndexableSet<Entity> hashSet)
{
hashSet.Clear();
listPool.Push(hashSet);
}
// untyped methods used for internal implementation
public override unsafe void Set(int entityA, int entityB, void* relationData)
{
Set(entityA, entityB, *((TRelation*) relationData));
}
public override int GetStorageIndex(int entityA, int entityB)
{
return indices[(entityA, entityB)];
}
public override unsafe void* Get(int relationStorageIndex)
{
fixed (void* p = &relations[relationStorageIndex])
{
return p;
}
}
public override void UnrelateAll(int entityID)
{
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
{
foreach (var entityB in entityOutRelations)
{
Remove(entityID, entityB);
}
ReturnHashSetToPool(entityOutRelations);
outRelations.Remove(entityID);
}
if (inRelations.TryGetValue(entityID, out var entityInRelations))
{
foreach (var entityA in entityInRelations)
{
Remove(entityA, entityID);
}
ReturnHashSetToPool(entityInRelations);
inRelations.Remove(entityID);
}
}
public override RelationStorage<TRelation> CreateStorage()
{
return new RelationStorage<TRelation>();
}
public override void Clear()
{
count = 0;
indices.Clear();
foreach (var set in inRelations.Values)
{
ReturnHashSetToPool(set);
}
inRelations.Clear();
foreach (var set in outRelations.Values)
{
ReturnHashSetToPool(set);
}
outRelations.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,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) { }
}

View File

@ -1,122 +1,378 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
namespace MoonTools.ECS;
// TODO: we should implement a NativeDictionary that can be memcopied
public class Snapshot : IDisposable
{
public class Snapshot
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)
{
private World World;
private Filter? Filter;
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
private EntityStorage SnapshotEntityStorage;
private ComponentDepot SnapshotComponentDepot;
private RelationDepot SnapshotRelationDepot;
private List<int> SnapshotToWorldID = new List<int>();
private Dictionary<int, int> WorldToSnapshotID = new Dictionary<int, int>();
internal Snapshot(World world)
// restore filter states
// this could be sped up if we figured out a direct IndexableSet copy
foreach (var (signature, entityList) in Filters)
{
World = world;
SnapshotEntityStorage = new EntityStorage();
SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices);
SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices);
}
var filter = world.FilterIndex[signature];
public unsafe void Take(Filter filter)
{
Clear();
Filter = filter;
SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot);
SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot);
filter.Clear();
foreach (var worldEntity in filter.Entities)
foreach (var entity in entityList)
{
var snapshotEntity = SnapshotEntityStorage.Create();
WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID);
foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID))
{
SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex);
SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex));
}
}
foreach (var worldEntity in filter.Entities)
{
var snapshotEntityID = WorldToSnapshotID[worldEntity.ID];
foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID))
{
SnapshotEntityStorage.AddRelationKind(snapshotEntityID, relationTypeIndex);
foreach (var otherEntityID in World.RelationDepot.OutRelations(worldEntity.ID, relationTypeIndex))
{
#if DEBUG
if (!World.FilterStorage.CheckSatisfied(otherEntityID, Filter.Signature))
{
throw new InvalidOperationException($"Snapshot entity {worldEntity.ID} is related to non-snapshot entity {otherEntityID}!");
}
#endif
var relationStorageIndex = World.RelationDepot.GetStorageIndex(relationTypeIndex, worldEntity, otherEntityID);
var otherSnapshotID = WorldToSnapshotID[otherEntityID];
SnapshotEntityStorage.AddRelationKind(otherSnapshotID, relationTypeIndex);
SnapshotRelationDepot.Set(snapshotEntityID, otherSnapshotID, relationTypeIndex, World.RelationDepot.Get(relationTypeIndex, relationStorageIndex));
}
}
filter.AddEntity(entity);
}
}
public unsafe void Restore()
// 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)
{
if (Filter == null)
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)
{
return;
}
foreach (var entity in Filter.Entities)
{
World.Destroy(entity);
}
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
{
var entity = World.CreateEntity();
SnapshotToWorldID.Add(entity.ID);
foreach (var componentTypeIndex in SnapshotEntityStorage.ComponentTypeIndices(i))
{
World.EntityStorage.SetComponent(entity.ID, componentTypeIndex);
World.FilterStorage.CheckComponentChange(entity.ID, componentTypeIndex);
World.ComponentDepot.Set(entity.ID, componentTypeIndex, SnapshotComponentDepot.UntypedGet(i, componentTypeIndex));
}
}
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
{
var worldID = SnapshotToWorldID[i];
foreach (var relationTypeIndex in SnapshotEntityStorage.RelationTypeIndices(i))
{
World.EntityStorage.AddRelationKind(worldID, relationTypeIndex);
foreach (var otherEntityID in SnapshotRelationDepot.OutRelations(i, relationTypeIndex))
{
var relationStorageIndex = SnapshotRelationDepot.GetStorageIndex(relationTypeIndex, i, otherEntityID);
var otherEntityWorldID = SnapshotToWorldID[otherEntityID];
World.RelationDepot.Set(worldID, otherEntityWorldID, relationTypeIndex, SnapshotRelationDepot.Get(relationTypeIndex, relationStorageIndex));
}
}
world.EntityRelationIndex[i].Add(typeId);
}
}
private void Clear()
// restore entity component index state
// FIXME: arrghghhh this is so slow
foreach (var componentTypeSet in world.EntityComponentIndex)
{
SnapshotEntityStorage.Clear();
SnapshotComponentDepot.Clear();
SnapshotRelationDepot.Clear();
SnapshotToWorldID.Clear();
WorldToSnapshotID.Clear();
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,102 +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() => World.CreateEntity();
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
{
if (EntityStorage.RemoveComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
{
ComponentDepot.Remove<TComponent>(entity.ID);
FilterStorage.Check<TComponent>(entity.ID);
}
}
protected void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(template, component);
protected Entity Instantiate(in Template template) => World.Instantiate(template);
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 ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
{
return MessageDepot.WithEntity<TMessage>(entity.ID);
}
protected ref readonly TMessage ReadMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
{
return ref MessageDepot.FirstWithEntity<TMessage>(entity.ID);
}
protected bool SomeMessageWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
{
return MessageDepot.SomeWithEntity<TMessage>(entity.ID);
}
protected void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(message);
}
protected void Send<TMessage>(in Entity entity, in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(entity.ID, message);
}
protected void Relate<TRelationKind>(in Entity entityA, in Entity entityB, TRelationKind relationData) where TRelationKind : unmanaged
{
RelationDepot.Set(entityA, entityB, relationData);
var relationTypeIndex = RelationTypeIndices.GetIndex<TRelationKind>();
EntityStorage.AddRelationKind(entityA.ID, relationTypeIndex);
EntityStorage.AddRelationKind(entityB.ID, relationTypeIndex);
}
protected void Unrelate<TRelationKind>(in Entity entityA, in Entity entityB) where TRelationKind : unmanaged
{
var (aEmpty, bEmpty) = RelationDepot.Remove<TRelationKind>(entityA, entityB);
if (aEmpty)
{
EntityStorage.RemoveRelation(entityA.ID, RelationTypeIndices.GetIndex<TRelationKind>());
}
if (bEmpty)
{
EntityStorage.RemoveRelation(entityB.ID, RelationTypeIndices.GetIndex<TRelationKind>());
}
}
protected void UnrelateAll<TRelationKind>(in Entity entity) where TRelationKind : unmanaged
{
RelationDepot.UnrelateAll<TRelationKind>(entity.ID);
EntityStorage.RemoveRelation(entity.ID, RelationTypeIndices.GetIndex<TRelationKind>());
}
protected void Destroy(in Entity entity) => World.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);
}

View File

@ -1,12 +0,0 @@
namespace MoonTools.ECS
{
public struct Template
{
public int ID { get; }
internal Template(int id)
{
ID = id;
}
}
}

View File

@ -1,34 +0,0 @@
using System.Collections.Generic;
namespace MoonTools.ECS
{
public class TemplateStorage
{
private int nextID = 0;
private Dictionary<int, HashSet<int>> TemplateToComponentTypeIndices = new Dictionary<int, HashSet<int>>();
public Template Create()
{
TemplateToComponentTypeIndices.Add(nextID, new HashSet<int>());
return new Template(NextID());
}
public bool SetComponent(int templateID, int componentTypeIndex)
{
return TemplateToComponentTypeIndices[templateID].Add(componentTypeIndex);
}
public HashSet<int> ComponentTypeIndices(int templateID)
{
return TemplateToComponentTypeIndices[templateID];
}
private int NextID()
{
var id = nextID;
nextID += 1;
return id;
}
}
}

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,33 +0,0 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
public class TypeIndices
{
Dictionary<Type, int> TypeToIndex = new Dictionary<Type, int>();
int nextID = 0;
public int Count => TypeToIndex.Count;
public int GetIndex<T>() where T : unmanaged
{
if (!TypeToIndex.ContainsKey(typeof(T)))
{
TypeToIndex.Add(typeof(T), nextID);
nextID += 1;
}
return TypeToIndex[typeof(T)];
}
public int GetIndex(Type type)
{
return TypeToIndex[type];
}
#if DEBUG
public Dictionary<Type, int>.KeyCollection Types => TypeToIndex.Keys;
#endif
}
}

View File

@ -1,108 +1,506 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS
{
public class World
{
internal readonly TypeIndices ComponentTypeIndices = new TypeIndices();
internal readonly TypeIndices RelationTypeIndices = new TypeIndices();
internal readonly EntityStorage EntityStorage = new EntityStorage();
internal readonly ComponentDepot ComponentDepot;
internal readonly MessageDepot MessageDepot = new MessageDepot();
internal readonly RelationDepot RelationDepot;
internal readonly FilterStorage FilterStorage;
public FilterBuilder FilterBuilder => new FilterBuilder(FilterStorage, ComponentTypeIndices, RelationTypeIndices);
internal readonly TemplateStorage TemplateStorage = new TemplateStorage();
internal readonly ComponentDepot TemplateComponentDepot;
public World()
{
ComponentDepot = new ComponentDepot(ComponentTypeIndices);
RelationDepot = new RelationDepot(RelationTypeIndices);
FilterStorage = new FilterStorage(EntityStorage, RelationDepot, ComponentTypeIndices, RelationTypeIndices);
TemplateComponentDepot = new ComponentDepot(ComponentTypeIndices);
}
public Entity CreateEntity()
{
return EntityStorage.Create();
}
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG
// check for use after destroy
if (!EntityStorage.Exists(entity))
{
throw new InvalidOperationException("This entity is not valid!");
}
using System.Reflection;
#endif
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
namespace MoonTools.ECS;
public class World : IDisposable
{
#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
{
var typeId = new TypeId(ComponentTypeIdAssigner<T>.Id);
if (typeId < ComponentIndex.Count)
{
return typeId;
}
// add missing storages, it's possible for there to be multiples in multi-world scenarios
for (var i = ComponentIndex.Count; i <= typeId; i += 1)
{
var missingTypeId = new TypeId((uint) i);
var componentStorage = new ComponentStorage(missingTypeId, ComponentTypeElementSizes[i]);
ComponentIndex.Add(componentStorage);
ComponentTypeToFilter.Add(new List<Filter>());
}
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))
{
filter = new Filter(this, signature);
foreach (var typeId in signature.Included)
{
FilterStorage.Check<TComponent>(entity.ID);
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
}
ComponentDepot.Set<TComponent>(entity.ID, component);
}
public Template CreateTemplate()
{
return TemplateStorage.Create();
}
public void Set<TComponent>(in Template template, in TComponent component) where TComponent : unmanaged
{
var componentTypeIndex = ComponentTypeIndices.GetIndex<TComponent>();
TemplateStorage.SetComponent(template.ID, componentTypeIndex);
TemplateComponentDepot.Set(template.ID, component);
ComponentDepot.Register<TComponent>(componentTypeIndex);
}
public unsafe Entity Instantiate(in Template template)
{
var entity = EntityStorage.Create();
foreach (var componentTypeIndex in TemplateStorage.ComponentTypeIndices(template.ID))
foreach (var typeId in signature.Excluded)
{
EntityStorage.SetComponent(entity.ID, componentTypeIndex);
FilterStorage.CheckComponentChange(entity.ID, componentTypeIndex);
ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex));
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
}
return entity;
FilterIndex.Add(signature, filter);
}
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
return filter;
}
// ENTITIES
public Entity CreateEntity(string tag = "")
{
var entity = new Entity(EntityIdAssigner.Assign());
if (entity.ID == EntityComponentIndex.Count)
{
MessageDepot.Add(message);
EntityRelationIndex.Add(new IndexableSet<TypeId>());
EntityComponentIndex.Add(new IndexableSet<TypeId>());
EntityTags.Add(tag);
}
public void Destroy(in Entity entity)
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)
{
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
var componentStorage = ComponentIndex[componentTypeIndex];
componentStorage.Remove(entity);
foreach (var filter in ComponentTypeToFilter[componentTypeIndex])
{
ComponentDepot.Remove(entity.ID, componentTypeIndex);
FilterStorage.RemoveEntity(entity.ID, componentTypeIndex);
filter.RemoveEntity(entity);
}
}
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
// remove all relations from storage
foreach (var relationTypeIndex in relationSet)
{
var relationStorage = RelationIndex[relationTypeIndex];
relationStorage.RemoveEntity(entity);
}
componentSet.Clear();
relationSet.Clear();
// 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))
{
EntityComponentIndex[(int) entity.ID].Add(componentStorage.TypeId);
foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId])
{
RelationDepot.UnrelateAll(entity.ID, relationTypeIndex);
filter.Check(entity);
}
EntityStorage.Destroy(entity);
}
public void FinishUpdate()
{
MessageDepot.Clear();
}
public Snapshot CreateSnapshot()
{
return new Snapshot(this);
}
}
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);
}
}