Compare commits

..

14 Commits

34 changed files with 2005 additions and 2286 deletions

View File

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

View File

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

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

@ -1,121 +0,0 @@
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);
}
}

129
src/ComponentDepot.cs Normal file
View File

@ -0,0 +1,129 @@
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,142 +1,148 @@
using System;
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
internal class ComponentStorage : IDisposable
namespace MoonTools.ECS
{
internal readonly Dictionary<Entity, int> EntityIDToStorageIndex = new Dictionary<Entity, int>(16);
internal readonly NativeArray Components;
internal readonly NativeArray<Entity> EntityIDs;
internal readonly TypeId TypeId;
internal readonly int ElementSize;
private bool IsDisposed;
public ComponentStorage(TypeId typeId, int elementSize)
internal abstract class ComponentStorage
{
ElementSize = elementSize;
Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>();
TypeId = typeId;
}
internal abstract unsafe void Set(int entityID, void* component);
public abstract bool Remove(int entityID);
public abstract void Clear();
public bool Any()
{
return Components.Count > 0;
}
public bool Has(Entity entity)
{
return EntityIDToStorageIndex.ContainsKey(entity);
}
public ref T Get<T>(in Entity entity) where T : unmanaged
{
return ref Components.Get<T>(EntityIDToStorageIndex[entity]);
}
public ref T GetFirst<T>() where T : unmanaged
{
// 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
if (Components.Count == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
internal abstract object Debug_Get(int entityID);
internal abstract IEnumerable<int> Debug_GetEntityIDs();
#endif
return ref Components.Get<T>(0);
}
// Returns true if the entity had this component.
public bool Set<T>(in Entity entity, in T component) where T : unmanaged
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
{
if (EntityIDToStorageIndex.TryGetValue(entity, out var index))
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];
public bool Any()
{
Components.Set(index, component);
return true;
return nextID > 0;
}
else
public ref readonly TComponent Get(int entityID)
{
EntityIDToStorageIndex[entity] = Components.Count;
EntityIDs.Append(entity);
Components.Append(component);
return false;
return ref components[entityIDToStorageIndex[entityID]];
}
}
// Returns true if the entity had this component.
public bool Remove(in Entity entity)
{
if (EntityIDToStorageIndex.TryGetValue(entity, out int index))
internal override unsafe void* UntypedGet(int entityID)
{
var lastElementIndex = Components.Count - 1;
var lastEntity = EntityIDs[lastElementIndex];
// move a component into the hole to maintain contiguous memory
Components.Delete(index);
EntityIDs.Delete(index);
EntityIDToStorageIndex.Remove(entity);
// update the index if it changed
if (lastElementIndex != index)
fixed (void* p = &components[entityIDToStorageIndex[entityID]])
{
EntityIDToStorageIndex[lastEntity] = index;
return p;
}
}
public ref readonly TComponent GetFirst()
{
#if DEBUG
if (nextID == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
#endif
return ref components[0];
}
public void Set(int entityID, in TComponent component)
{
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;
}
return true;
components[entityIDToStorageIndex[entityID]] = component;
}
return false;
}
public void Clear()
{
Components.Clear();
EntityIDs.Clear();
EntityIDToStorageIndex.Clear();
}
public Entity FirstEntity()
{
#if DEBUG
if (EntityIDs.Count == 0)
internal override unsafe void Set(int entityID, void* component)
{
throw new IndexOutOfRangeException("Component storage is empty!");
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;
}
return false;
}
public override void Clear()
{
nextID = 0;
entityIDToStorageIndex.Clear();
}
public ReadOnlySpan<TComponent> AllComponents()
{
return new ReadOnlySpan<TComponent>(components, 0, nextID);
}
public Entity FirstEntity()
{
#if DEBUG
if (nextID == 0)
{
throw new IndexOutOfRangeException("Component storage is empty!");
}
#endif
return EntityIDs[0];
}
return new Entity(entityIDs[0]);
}
public override ComponentStorage<TComponent> CreateStorage()
{
return new ComponentStorage<TComponent>();
}
#if DEBUG
internal IEnumerable<Entity> Debug_GetEntities()
{
return EntityIDToStorageIndex.Keys;
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
internal override object Debug_Get(int entityID)
{
Components.Dispose();
EntityIDs.Dispose();
IsDisposed = true;
return components[entityIDToStorageIndex[entityID]];
}
}
// ~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);
internal override IEnumerable<int> Debug_GetEntityIDs()
{
return entityIDToStorageIndex.Keys;
}
#endif
}
}

View File

@ -1,17 +1,44 @@
// 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;
public abstract class DebugSystem : System
namespace MoonTools.ECS
{
protected DebugSystem(World world) : base(world) { }
public abstract class DebugSystem : System
{
protected DebugSystem(World world) : base(world)
{
}
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);
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;
}
}
}
}
}
#endif

40
src/DynamicArray.cs Normal file
View File

@ -0,0 +1,40 @@
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,3 +1,49 @@
namespace MoonTools.ECS;
using System;
public readonly record struct Entity(uint ID);
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);
}
}
}

View File

@ -1,36 +1,112 @@
namespace MoonTools.ECS;
using System;
using System.Collections.Generic;
public abstract class EntityComponentReader
namespace MoonTools.ECS
{
protected readonly World World;
public FilterBuilder FilterBuilder => World.FilterBuilder;
protected EntityComponentReader(World world)
public abstract class 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);
}
}
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);
}

117
src/EntityStorage.cs Normal file
View File

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

View File

@ -1,123 +1,32 @@
using System;
using MoonTools.ECS.Collections;
using System.Collections.Generic;
namespace MoonTools.ECS;
public class Filter
namespace MoonTools.ECS
{
private World World;
internal FilterSignature Signature;
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)
public class Filter
{
World = world;
Signature = signature;
}
internal FilterSignature Signature;
private FilterStorage FilterStorage;
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;
}
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);
}
foreach (var type in Signature.Excluded)
{
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);
EntitySet.Add(entity);
}
public int Count => FilterStorage.FilterCount(Signature);
public bool Empty => Count == 0;
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);
// WARNING: this WILL crash if the index is out of range!
public Entity NthEntity(int index) => FilterStorage.FilterNthEntity(Signature, index);
}
}

View File

@ -1,43 +1,77 @@
using MoonTools.ECS.Collections;
using System;
using System.Collections.Generic;
namespace MoonTools.ECS
{
public struct FilterBuilder
{
World World;
IndexableSet<TypeId> Included;
IndexableSet<TypeId> Excluded;
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;
internal FilterBuilder(World world)
{
World = world;
Included = new IndexableSet<TypeId>();
Excluded = new IndexableSet<TypeId>();
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>();
}
private FilterBuilder(World world, IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
{
World = world;
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;
Included = included;
Excluded = excluded;
InRelations = inRelations;
OutRelations = outRelations;
}
public FilterBuilder Include<T>() where T : unmanaged
public FilterBuilder Include<TComponent>() where TComponent : unmanaged
{
Included.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
Included.Add(ComponentTypeIndices.GetIndex<TComponent>());
return this;
}
public FilterBuilder Exclude<T>() where T : unmanaged
public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
{
Excluded.Add(World.GetComponentTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
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;
}
public Filter Build()
{
var signature = new FilterSignature(Included, Excluded);
return World.GetFilter(signature);
return FilterStorage.CreateFilter(Included, Excluded, InRelations, OutRelations);
}
}
}

View File

@ -1,69 +1,76 @@
using System;
using MoonTools.ECS.Collections;
using System.Collections.Generic;
namespace MoonTools.ECS;
public struct FilterSignature : IEquatable<FilterSignature>
namespace MoonTools.ECS
{
public readonly IndexableSet<TypeId> Included;
public readonly IndexableSet<TypeId> Excluded;
public FilterSignature(IndexableSet<TypeId> included, IndexableSet<TypeId> excluded)
internal struct FilterSignature : IEquatable<FilterSignature>
{
Included = included;
Excluded = excluded;
}
public readonly HashSet<int> Included;
public readonly HashSet<int> Excluded;
public readonly HashSet<int> InRelations;
public readonly HashSet<int> OutRelations;
public override bool Equals(object? obj)
{
return obj is FilterSignature signature && Equals(signature);
}
public FilterSignature(
HashSet<int> included,
HashSet<int> excluded,
HashSet<int> inRelations,
HashSet<int> outRelations
) {
Included = included;
Excluded = excluded;
InRelations = inRelations;
OutRelations = outRelations;
}
public bool Equals(FilterSignature other)
{
foreach (var included in Included)
public override bool Equals(object? obj)
{
if (!other.Included.Contains(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)
{
return false;
hashcode = HashCode.Combine(hashcode, type);
}
}
foreach (var excluded in Excluded)
{
if (!other.Excluded.Contains(excluded))
foreach (var type in Excluded)
{
return false;
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;
}
return true;
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var type in Included)
public static bool operator ==(FilterSignature left, FilterSignature right)
{
hashcode = HashCode.Combine(hashcode, type);
return left.Equals(right);
}
foreach (var type in Excluded)
public static bool operator !=(FilterSignature left, FilterSignature right)
{
hashcode = HashCode.Combine(hashcode, type);
return !(left == right);
}
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);
}
}

196
src/FilterStorage.cs Normal file
View File

@ -0,0 +1,196 @@
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);
}
}
}
}
}

View File

@ -1,62 +0,0 @@
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);
}
}

110
src/IndexableSet.cs Normal file
View File

@ -0,0 +1,110 @@
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];
}
}
}
}

View File

@ -1,20 +0,0 @@
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);
}

68
src/MessageDepot.cs Normal file
View File

@ -0,0 +1,68 @@
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,63 +1,93 @@
using System;
using MoonTools.ECS.Collections;
using System.Collections.Generic;
namespace MoonTools.ECS;
public class MessageStorage : IDisposable
namespace MoonTools.ECS
{
private NativeArray Messages;
private bool IsDisposed;
public MessageStorage(int elementSize)
internal abstract class MessageStorage
{
Messages = new NativeArray(elementSize);
public abstract void Clear();
}
public void Add<T>(in T message) where T : unmanaged
internal class MessageStorage<TMessage> : MessageStorage where TMessage : unmanaged
{
Messages.Append(message);
}
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>>();
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)
public MessageStorage()
{
Messages.Dispose();
IsDisposed = true;
messages = new TMessage[capacity];
}
public void Add(in TMessage message)
{
if (count == capacity)
{
capacity *= 2;
Array.Resize(ref messages, capacity);
}
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();
}
}
}
// ~MessageStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -1,208 +1,72 @@
using System;
using System;
using System.Runtime.CompilerServices;
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
namespace MoonTools.ECS
{
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()
internal static class RandomGenerator
{
Init((uint) Environment.TickCount);
}
private static Random random = new Random();
/// <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++)
private static int[] Primes =
{
s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0;
State[i] = ~ ~s; //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);
}
Index = 0;
}
/// <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)
/// <summary>
/// A psuedorandom nonrepeating sequence of integers from 0 to n.
/// </summary>
public static LinearCongruentialEnumerator LinearCongruentialGenerator(int n)
{
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)
var x = Primes[random.Next(Primes.Length - 1)];
while (x % n == 0)
{
Unsafe.Write(ptr + offset, State[i]);
offset += 4;
// not coprime, try again
x = Primes[random.Next(Primes.Length - 1)];
}
Unsafe.Write(ptr + offset, Index);
return new LinearCongruentialEnumerator(random.Next(n), x, n);
}
}
/// <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)
public struct LinearCongruentialEnumerator
{
#if DEBUG
if (bytes.Length < STATE_BYTE_COUNT)
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)
{
throw new ArgumentException("Byte span too short!");
current = start;
this.start = start;
this.prime = prime;
this.count = count;
}
#endif
fixed (byte* ptr = bytes)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
var offset = 0;
for (var i = 0; i < 16; i += 1)
current += 1;
if (current < start + count)
{
State[i] = Unsafe.Read<uint>(ptr + offset);
offset += 4;
return true;
}
Index = Unsafe.Read<uint>(ptr + offset);
return false;
}
}
private uint NextInternal()
{
uint a, b, c, d;
a = State[Index];
c = State[(Index+13)&15];
b = a^c^(a<<16)^(c<<15);
c = State[(Index+9)&15];
c ^= (c>>11);
a = State[Index] = b^c;
d = (uint) (a ^((a<<5)&0xDA442D24UL));
Index = (Index + 15)&15;
a = State[Index];
State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
return State[Index];
}
/// <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;
public int Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (current * prime) % count;
}
}
}

View File

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

168
src/RelationDepot.cs Normal file
View File

@ -0,0 +1,168 @@
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,286 +1,278 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
// TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots
internal class RelationStorage
namespace MoonTools.ECS
{
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)
internal abstract class RelationStorage
{
ElementSize = relationDataSize;
Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>());
RelationDatas = new NativeArray(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();
}
public ReverseSpanEnumerator<(Entity, Entity)> All()
// 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
{
return new ReverseSpanEnumerator<(Entity, Entity)>(Relations.ToSpan<(Entity, Entity)>());
}
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>>();
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))
public ReverseSpanEnumerator<(Entity, Entity)> All()
{
RelationDatas.Set(index, relationData);
return;
return new ReverseSpanEnumerator<(Entity, Entity)>(new Span<(Entity, Entity)>(relations, 0, count));
}
if (!OutRelationSets.ContainsKey(entityA))
public void Set(in Entity entityA, in Entity entityB, TRelation relationData)
{
OutRelationSets[entityA] = AcquireHashSetFromPool();
}
OutRelationSets[entityA].Add(entityB);
var relation = (entityA, entityB);
if (!InRelationSets.ContainsKey(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;
}
public TRelation Get(in Entity entityA, in Entity entityB)
{
InRelationSets[entityB] = AcquireHashSetFromPool();
return relationDatas[indices[(entityA, entityB)]];
}
InRelationSets[entityB].Add(entityA);
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))
public bool Has((Entity, Entity) relation)
{
return entityOutRelations.GetEnumerator();
return indices.ContainsKey(relation);
}
else
public override ReverseSpanEnumerator<Entity> OutRelations(int entityID)
{
return ReverseSpanEnumerator<Entity>.Empty;
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
{
return entityOutRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
}
}
public Entity OutFirst(Entity Entity)
{
return OutNth(Entity, 0);
}
public Entity OutNth(Entity Entity, int n)
{
public Entity OutFirst(int entityID)
{
#if DEBUG
if (!OutRelationSets.ContainsKey(Entity) || OutRelationSets[Entity].Count == 0)
{
throw new KeyNotFoundException("No out relations to this entity!");
}
if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0)
{
throw new KeyNotFoundException("No out relations to this entity!");
}
#endif
return OutRelationSets[Entity][n];
}
public bool HasOutRelation(Entity Entity)
{
return OutRelationSets.ContainsKey(Entity) && OutRelationSets[Entity].Count > 0;
}
public int OutRelationCount(Entity Entity)
{
return OutRelationSets.TryGetValue(Entity, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public ReverseSpanEnumerator<Entity> InRelations(Entity Entity)
{
if (InRelationSets.TryGetValue(Entity, out var entityInRelations))
{
return entityInRelations.GetEnumerator();
return outRelations[entityID][0];
}
else
public override bool HasOutRelation(int entityID)
{
return ReverseSpanEnumerator<Entity>.Empty;
return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0;
}
}
public Entity InFirst(Entity Entity)
{
return InNth(Entity, 0);
}
public int OutRelationCount(int entityID)
{
return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public Entity InNth(Entity Entity, int n)
{
public ReverseSpanEnumerator<Entity> InRelations(int entityID)
{
if (inRelations.TryGetValue(entityID, out var entityInRelations))
{
return entityInRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<Entity>.Empty;
}
}
public Entity InFirst(int entityID)
{
#if DEBUG
if (!InRelationSets.ContainsKey(Entity) || InRelationSets[Entity].Count == 0)
{
throw new KeyNotFoundException("No in relations to this entity!");
}
if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0)
{
throw new KeyNotFoundException("No out relations to this entity!");
}
#endif
return InRelationSets[Entity][n];
}
return inRelations[entityID][0];
}
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))
public override bool HasInRelation(int entityID)
{
entityOutRelations.Remove(entityB);
if (OutRelationSets[entityA].Count == 0)
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))
{
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;
}
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)
{
Remove(entity, entityB);
}
ReturnHashSetToPool(entityOutRelations);
OutRelationSets.Remove(entity);
}
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)
entityOutRelations.Remove(entityB.ID);
if (outRelations[entityA.ID].Count == 0)
{
set.Dispose();
aEmpty = true;
}
}
if (inRelations.TryGetValue(entityB.ID, out var entityInRelations))
{
entityInRelations.Remove(entityA.ID);
if (inRelations[entityB.ID].Count == 0)
{
bEmpty = true;
}
}
if (indices.TryGetValue(relation, out var index))
{
var lastElementIndex = count - 1;
// move an element into the hole
if (index != lastElementIndex)
{
var lastRelation = relations[lastElementIndex];
indices[lastRelation] = index;
relationDatas[index] = relationDatas[lastElementIndex];
relations[index] = lastRelation;
}
Relations.Dispose();
RelationDatas.Dispose();
count -= 1;
indices.Remove(relation);
}
IsDisposed = true;
return (aEmpty, bEmpty);
}
private 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);
}
// 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();
}
}
// ~RelationStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -1,6 +1,7 @@
namespace MoonTools.ECS;
public abstract class Renderer : EntityComponentReader
namespace MoonTools.ECS
{
public Renderer(World world) : base(world) { }
public abstract class Renderer : EntityComponentReader
{
public Renderer(World world) : base(world) { }
}
}

View File

@ -1,378 +1,122 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS;
// TODO: we should implement a NativeDictionary that can be memcopied
public class Snapshot : IDisposable
namespace MoonTools.ECS
{
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)
public class Snapshot
{
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
private World World;
private Filter? Filter;
// restore filter states
// this could be sped up if we figured out a direct IndexableSet copy
foreach (var (signature, entityList) in Filters)
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)
{
var filter = world.FilterIndex[signature];
World = world;
SnapshotEntityStorage = new EntityStorage();
SnapshotComponentDepot = new ComponentDepot(World.ComponentTypeIndices);
SnapshotRelationDepot = new RelationDepot(World.RelationTypeIndices);
}
filter.Clear();
public unsafe void Take(Filter filter)
{
Clear();
Filter = filter;
SnapshotComponentDepot.CreateMissingStorages(World.ComponentDepot);
SnapshotRelationDepot.CreateMissingStorages(World.RelationDepot);
foreach (var entity in entityList)
foreach (var worldEntity in filter.Entities)
{
filter.AddEntity(entity);
}
}
var snapshotEntity = SnapshotEntityStorage.Create();
WorldToSnapshotID.Add(worldEntity.ID, snapshotEntity.ID);
// clear all component storages in case any were created after snapshot
// FIXME: this can be eliminated via component discovery
foreach (var componentStorage in world.ComponentIndex)
{
componentStorage.Clear();
}
// clear all relation storages in case any were created after snapshot
// FIXME: this can be eliminated via component discovery
foreach (var relationStorage in world.RelationIndex)
{
relationStorage.Clear();
}
for (var i = 0; i < ComponentSnapshots.Count; i += 1)
{
var componentStorage = world.ComponentIndex[i];
ComponentSnapshots[i].Restore(componentStorage);
}
// restore relation state
for (var i = 0; i < RelationSnapshots.Count; i += 1)
{
var relationStorage = world.RelationIndex[i];
RelationSnapshots[i].Restore(relationStorage);
}
// restore entity relation index state
// FIXME: arghhhh this is so slow
foreach (var relationTypeSet in world.EntityRelationIndex)
{
relationTypeSet.Clear();
}
for (var i = 0; i < EntityRelationIndex.Count; i += 1)
{
var relationTypeSet = EntityRelationIndex[i];
foreach (var typeId in relationTypeSet)
{
world.EntityRelationIndex[i].Add(typeId);
}
}
// restore entity component index state
// FIXME: arrghghhh this is so slow
foreach (var componentTypeSet in world.EntityComponentIndex)
{
componentTypeSet.Clear();
}
for (var i = 0; i < EntityComponentIndex.Count; i += 1)
{
var componentTypeSet = EntityComponentIndex[i];
foreach (var typeId in componentTypeSet)
{
world.EntityComponentIndex[i].Add(typeId);
}
}
// restore entity tags
for (var i = 0; i < EntityTags.Count; i += 1)
{
world.EntityTags[i] = EntityTags[i];
}
}
public void Take(World world)
{
// copy id assigner state
world.EntityIdAssigner.CopyTo(EntityIdAssigner);
// copy filter states
foreach (var (_, filter) in world.FilterIndex)
{
TakeFilterSnapshot(filter);
}
// copy components
for (var i = ComponentSnapshots.Count; i < world.ComponentIndex.Count; i += 1)
{
ComponentSnapshots.Add(new ComponentSnapshot(world.ComponentIndex[i].ElementSize));
}
for (var i = 0; i < world.ComponentIndex.Count; i += 1)
{
ComponentSnapshots[i].Take(world.ComponentIndex[i]);
}
// copy relations
for (var i = RelationSnapshots.Count; i < world.RelationIndex.Count; i += 1)
{
RelationSnapshots.Add(new RelationSnapshot(world.RelationIndex[i].ElementSize));
}
for (var i = 0; i < world.RelationIndex.Count; i += 1)
{
RelationSnapshots[i].Take(world.RelationIndex[i]);
}
// fill in missing index structures
for (var i = EntityComponentIndex.Count; i < world.EntityComponentIndex.Count; i += 1)
{
EntityComponentIndex.Add(new IndexableSet<TypeId>());
}
for (var i = EntityRelationIndex.Count; i < world.EntityRelationIndex.Count; i += 1)
{
EntityRelationIndex.Add(new IndexableSet<TypeId>());
}
// copy entity relation index
// FIXME: arghhhh this is so slow
for (var i = 0; i < world.EntityRelationIndex.Count; i += 1)
{
EntityRelationIndex[i].Clear();
foreach (var typeId in world.EntityRelationIndex[i])
{
EntityRelationIndex[i].Add(typeId);
}
}
// copy entity component index
// FIXME: arghhhh this is so slow
for (var i = 0; i < world.EntityComponentIndex.Count; i += 1)
{
EntityComponentIndex[i].Clear();
foreach (var typeId in world.EntityComponentIndex[i])
{
EntityComponentIndex[i].Add(typeId);
}
}
// copy entity tags
EntityTags.Clear();
foreach (var s in world.EntityTags)
{
EntityTags.Add(s);
}
}
private void TakeFilterSnapshot(Filter filter)
{
if (!Filters.TryGetValue(filter.Signature, out var entities))
{
entities = new List<Entity>();
Filters.Add(filter.Signature, entities);
}
entities.Clear();
foreach (var entity in filter.EntitySet.AsSpan())
{
entities.Add(entity);
}
}
private class ComponentSnapshot : IDisposable
{
private readonly NativeArray Components;
private readonly NativeArray<Entity> EntityIDs;
private bool IsDisposed;
public ComponentSnapshot(int elementSize)
{
Components = new NativeArray(elementSize);
EntityIDs = new NativeArray<Entity>();
}
public void Take(ComponentStorage componentStorage)
{
componentStorage.Components.CopyAllTo(Components);
componentStorage.EntityIDs.CopyTo(EntityIDs);
}
public void Restore(ComponentStorage componentStorage)
{
Components.CopyAllTo(componentStorage.Components);
EntityIDs.CopyTo(componentStorage.EntityIDs);
componentStorage.EntityIDToStorageIndex.Clear();
for (int i = 0; i < EntityIDs.Count; i += 1)
{
var entityID = EntityIDs[i];
componentStorage.EntityIDToStorageIndex[entityID] = i;
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
foreach (var componentTypeIndex in World.EntityStorage.ComponentTypeIndices(worldEntity.ID))
{
Components.Dispose();
EntityIDs.Dispose();
SnapshotEntityStorage.SetComponent(snapshotEntity.ID, componentTypeIndex);
SnapshotComponentDepot.Set(snapshotEntity.ID, componentTypeIndex, World.ComponentDepot.UntypedGet(worldEntity.ID, componentTypeIndex));
}
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)
foreach (var worldEntity in filter.Entities)
{
var relation = Relations.Get<(Entity, Entity)>(index);
relationStorage.Indices[relation] = index;
var snapshotEntityID = WorldToSnapshotID[worldEntity.ID];
relationStorage.Indices[relation] = index;
if (!relationStorage.OutRelationSets.ContainsKey(relation.Item1))
foreach (var relationTypeIndex in World.EntityStorage.RelationTypeIndices(worldEntity.ID))
{
relationStorage.OutRelationSets[relation.Item1] =
relationStorage.AcquireHashSetFromPool();
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));
}
}
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)
public unsafe void Restore()
{
if (!IsDisposed)
if (Filter == null)
{
if (disposing)
{
Relations.Dispose();
RelationDatas.Dispose();
}
IsDisposed = true;
return;
}
}
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 entity in Filter.Entities)
{
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();
World.Destroy(entity);
}
IsDisposed = true;
}
}
for (var i = 0; i < SnapshotEntityStorage.Count; i += 1)
{
var entity = World.CreateEntity();
SnapshotToWorldID.Add(entity.ID);
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
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));
}
}
}
}
private void Clear()
{
SnapshotEntityStorage.Clear();
SnapshotComponentDepot.Clear();
SnapshotRelationDepot.Clear();
SnapshotToWorldID.Clear();
WorldToSnapshotID.Clear();
}
}
}

View File

@ -1,15 +1,102 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS;
public abstract class System : Manipulator
namespace MoonTools.ECS
{
protected System(World world) : base(world) { }
public abstract class System : EntityComponentReader
{
internal MessageDepot MessageDepot => World.MessageDepot;
public abstract void Update(TimeSpan delta);
public System(World world) : base(world) { }
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);
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);
}
}

12
src/Template.cs Normal file
View File

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

34
src/TemplateStorage.cs Normal file
View File

@ -0,0 +1,34 @@
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;
}
}
}

View File

@ -1,82 +0,0 @@
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);
}
}

33
src/TypeIndices.cs Normal file
View File

@ -0,0 +1,33 @@
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,506 +1,108 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
#if DEBUG
using System.Reflection;
#endif
namespace MoonTools.ECS;
public class World : IDisposable
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
// 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>();
// check for use after destroy
if (!EntityStorage.Exists(entity))
{
throw new InvalidOperationException("This entity is not valid!");
}
#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)
if (EntityStorage.SetComponent(entity.ID, ComponentTypeIndices.GetIndex<TComponent>()))
{
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
FilterStorage.Check<TComponent>(entity.ID);
}
foreach (var typeId in signature.Excluded)
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))
{
ComponentTypeToFilter[(int) typeId.Value].Add(filter);
EntityStorage.SetComponent(entity.ID, componentTypeIndex);
FilterStorage.CheckComponentChange(entity.ID, componentTypeIndex);
ComponentDepot.Set(entity.ID, componentTypeIndex, TemplateComponentDepot.UntypedGet(template.ID, componentTypeIndex));
}
FilterIndex.Add(signature, filter);
return entity;
}
return filter;
}
// ENTITIES
public Entity CreateEntity(string tag = "")
{
var entity = new Entity(EntityIdAssigner.Assign());
if (entity.ID == EntityComponentIndex.Count)
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
EntityRelationIndex.Add(new IndexableSet<TypeId>());
EntityComponentIndex.Add(new IndexableSet<TypeId>());
EntityTags.Add(tag);
MessageDepot.Add(message);
}
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)
public void Destroy(in Entity entity)
{
var componentStorage = ComponentIndex[componentTypeIndex];
componentStorage.Remove(entity);
foreach (var filter in ComponentTypeToFilter[componentTypeIndex])
foreach (var componentTypeIndex in EntityStorage.ComponentTypeIndices(entity.ID))
{
filter.RemoveEntity(entity);
}
}
// 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])
{
filter.Check(entity);
}
}
}
public void Remove<T>(in Entity entity) where T : unmanaged
{
var componentStorage = GetComponentStorage<T>();
if (componentStorage.Remove(entity))
{
EntityComponentIndex[(int) entity.ID].Remove(componentStorage.TypeId);
foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId])
{
filter.Check(entity);
}
}
}
// RELATIONS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private RelationStorage GetRelationStorage<T>() where T : unmanaged
{
var typeId = new TypeId(RelationTypeIdAssigner<T>.Id);
if (typeId.Value < RelationIndex.Count)
{
return RelationIndex[typeId];
}
for (var i = RelationIndex.Count; i <= typeId; i += 1)
{
RelationIndex.Add(new RelationStorage(RelationTypeElementSizes[i]));
}
return RelationIndex[typeId];
}
public void Relate<T>(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Set(entityA, entityB, relation);
EntityRelationIndex[(int) entityA.ID].Add(new TypeId(RelationTypeIdAssigner<T>.Id));
EntityRelationIndex[(int) entityB.ID].Add(new TypeId(RelationTypeIdAssigner<T>.Id));
}
public void Unrelate<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Remove(entityA, entityB);
}
public void UnrelateAll<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.RemoveEntity(entity);
}
public bool Related<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Has(entityA, entityB);
}
public T GetRelationData<T>(in Entity entityA, in Entity entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Get<T>(entityA, entityB);
}
public ReverseSpanEnumerator<(Entity, Entity)> Relations<T>() where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.All();
}
public ReverseSpanEnumerator<Entity> OutRelations<T>(Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelations(entity);
}
public Entity OutRelationSingleton<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutFirst(entity);
}
public bool HasOutRelation<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasOutRelation(entity);
}
public int OutRelationCount<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelationCount(entity);
}
public Entity NthOutRelation<T>(in Entity entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutNth(entity, n);
}
public ReverseSpanEnumerator<Entity> InRelations<T>(Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelations(entity);
}
public Entity InRelationSingleton<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InFirst(entity);
}
public bool HasInRelation<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasInRelation(entity);
}
public int InRelationCount<T>(in Entity entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelationCount(entity);
}
public Entity NthInRelation<T>(in Entity entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InNth(entity, n);
}
// MESSAGES
private MessageStorage GetMessageStorage<T>() where T : unmanaged
{
var typeId = new TypeId(MessageTypeIdAssigner<T>.Id);
if (typeId < MessageIndex.Count)
{
return MessageIndex[typeId];
}
for (var i = MessageIndex.Count; i <= typeId; i += 1)
{
MessageIndex.Add(new MessageStorage(MessageTypeElementSizes[i]));
}
return MessageIndex[typeId];
}
public void Send<T>(in T message) where T : unmanaged
{
GetMessageStorage<T>().Add(message);
}
public bool SomeMessage<T>() where T : unmanaged
{
return GetMessageStorage<T>().Some();
}
public ReadOnlySpan<T> ReadMessages<T>() where T : unmanaged
{
return GetMessageStorage<T>().All<T>();
}
public T ReadMessage<T>() where T : unmanaged
{
return GetMessageStorage<T>().First<T>();
}
public void ClearMessages<T>() where T : unmanaged
{
GetMessageStorage<T>().Clear();
}
// TODO: temporary component storage?
public void FinishUpdate()
{
foreach (var messageStorage in MessageIndex)
{
messageStorage.Clear();
}
}
// DEBUG
// NOTE: these methods are very inefficient
// they should only be used in debugging contexts!!
#if DEBUG
public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity)
{
return new ComponentTypeEnumerator(this, EntityComponentIndex[(int) entity.ID]);
}
public IEnumerable<Entity> Debug_GetEntities(Type componentType)
{
var baseGetComponentStorageMethod = typeof(World).GetMethod(nameof(World.GetComponentStorage), BindingFlags.NonPublic | BindingFlags.Instance)!;
var genericGetComponentStorageMethod = baseGetComponentStorageMethod.MakeGenericMethod(componentType);
var storage = genericGetComponentStorageMethod.Invoke(this, null) as ComponentStorage;
return storage!.Debug_GetEntities();
}
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
foreach (var type in ComponentTypeToId.Keys)
{
if (type.ToString().ToLower().Contains(typeString.ToLower()))
{
yield return type;
}
}
}
public ref struct ComponentTypeEnumerator
{
private World World;
private IndexableSet<TypeId> Types;
private int ComponentIndex;
public ComponentTypeEnumerator GetEnumerator() => this;
internal ComponentTypeEnumerator(
World world,
IndexableSet<TypeId> types
)
{
World = world;
Types = types;
ComponentIndex = -1;
}
public bool MoveNext()
{
ComponentIndex += 1;
return ComponentIndex < Types.Count;
}
public Type Current => ComponentTypeIdToType[Types[ComponentIndex]];
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
foreach (var componentStorage in ComponentIndex)
{
componentStorage.Dispose();
}
foreach (var relationStorage in RelationIndex)
{
relationStorage.Dispose();
}
foreach (var messageStorage in MessageIndex)
{
messageStorage.Dispose();
}
foreach (var componentSet in EntityComponentIndex)
{
componentSet.Dispose();
}
foreach (var relationSet in EntityRelationIndex)
{
relationSet.Dispose();
}
foreach (var filter in FilterIndex.Values)
{
filter.Dispose();
}
EntityIdAssigner.Dispose();
ComponentDepot.Remove(entity.ID, componentTypeIndex);
FilterStorage.RemoveEntity(entity.ID, componentTypeIndex);
}
IsDisposed = true;
foreach (var relationTypeIndex in EntityStorage.RelationTypeIndices(entity.ID))
{
RelationDepot.UnrelateAll(entity.ID, relationTypeIndex);
}
EntityStorage.Destroy(entity);
}
}
// // 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);
public void FinishUpdate()
{
MessageDepot.Clear();
}
public Snapshot CreateSnapshot()
{
return new Snapshot(this);
}
}
}