Compare commits

...

22 Commits
main ... rev2

Author SHA1 Message Date
cosmonaut 07b53b0082 fix storage retrieval + enumerate component types 2023-11-02 15:00:56 -07:00
cosmonaut 6f50e78188 add relation count methods 2023-11-02 12:15:21 -07:00
cosmonaut d3c4c12605 entity tag system 2023-11-02 11:03:51 -07:00
cosmonaut 2a2d8efc5f Debug methods on World 2023-11-01 16:34:24 -07:00
cosmonaut 4f7307d29e add Update to System 2023-10-31 15:09:56 -07:00
cosmonaut 1ec9b9966c add Relate to Manipulator 2023-10-31 15:05:10 -07:00
cosmonaut 63a5643763 add CreateEntity to Manipulator 2023-10-31 15:02:56 -07:00
cosmonaut 5ba5c2f5cd split Id to EntityId and TypeId 2023-10-31 15:00:42 -07:00
cosmonaut d4c84868d8 add FilterBuilder to compatibility system 2023-10-31 14:42:35 -07:00
cosmonaut c7f7d8c362 add compatibility structures 2023-10-31 14:28:12 -07:00
cosmonaut 3d2261f739 messages + rearranging 2023-10-31 11:10:42 -07:00
cosmonaut 9032eff699 snapshot and restore relations 2023-10-30 16:25:33 -07:00
cosmonaut 4d40103a6f id-preserving snapshots 2023-10-30 12:11:50 -07:00
cosmonaut b118cffb83 preparing for relation snapshots 2023-10-27 17:47:30 -07:00
cosmonaut 545637aaf3 unifying Ids and adding Relations 2023-10-26 16:16:28 -07:00
cosmonaut 344a0082b4 fast snapshot restore 2023-10-26 11:02:45 -07:00
cosmonaut 5419fbd72d snapshot system 2023-10-24 18:44:41 -07:00
cosmonaut b3ff7e3f1c implement Filter 2023-10-24 13:13:14 -07:00
cosmonaut 272fd6b492 more rev2 implementation 2023-10-20 17:24:35 -07:00
cosmonaut 5a670fa36c entity iteration pattern 2023-10-20 01:17:03 -07:00
cosmonaut 6173b05ff6 World.Remove and World.Dispose 2023-10-19 17:41:45 -07:00
cosmonaut 40d2bf87ba initial archetype storage structure 2023-10-19 15:41:49 -07:00
29 changed files with 2075 additions and 76 deletions

View File

@ -60,12 +60,27 @@ namespace MoonTools.ECS.Collections
return false;
}
/*
var lastElement = array[Count - 1];
var index = indices[element];
array[index] = lastElement;
indices[lastElement] = index;
count -= 1;
indices.Remove(element);
*/
// FIXME: we can probably undo this change
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;
}

View File

@ -0,0 +1,110 @@
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* Array;
private int count;
private int capacity;
private int elementSize;
public int Count => count;
public Span<T>.Enumerator GetEnumerator() => new Span<T>(Array, count).GetEnumerator();
private bool disposed;
public NativeArray(int capacity = 16)
{
this.capacity = capacity;
elementSize = Unsafe.SizeOf<T>();
Array = (T*) NativeMemory.Alloc((nuint) (capacity * elementSize));
count = 0;
}
public ref T this[int i] => ref Array[i];
public void Add(T item)
{
if (count >= capacity)
{
capacity *= 2;
Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf<T>()));
}
Array[count] = item;
count += 1;
}
public void RemoveLastElement()
{
count -= 1;
}
public bool TryPop(out T element)
{
if (count > 0)
{
element = Array[count - 1];
count -= 1;
return true;
}
element = default;
return false;
}
public void Clear()
{
count = 0;
}
private void ResizeTo(int size)
{
capacity = size;
Array = (T*) NativeMemory.Realloc((void*) Array, (nuint) (elementSize * capacity));
}
public void CopyTo(NativeArray<T> other)
{
if (count >= other.capacity)
{
other.ResizeTo(Count);
}
NativeMemory.Copy(
(void*) Array,
(void*) other.Array,
(nuint) (elementSize * Count)
);
other.count = count;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
NativeMemory.Free(Array);
Array = null;
disposed = true;
}
}
~NativeArray()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,133 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace MoonTools.ECS.Collections;
internal unsafe class NativeArray : IDisposable
{
public nint Elements;
public int Count;
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* Get(int i)
{
return (void*)(Elements + ElementSize * i);
}
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 (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 CopyElementToEnd(int index, NativeArray other)
{
if (other.Count >= other.Capacity)
{
other.Resize();
}
NativeMemory.Copy(
(void*) (Elements + (index * ElementSize)),
(void*) (other.Elements + (other.Count * ElementSize)),
(nuint) ElementSize
);
other.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;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
NativeMemory.Free((void*) Elements);
IsDisposed = true;
}
}
~NativeArray()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -43,7 +43,7 @@ namespace MoonTools.ECS
return Lookup<TComponent>().Any();
}
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
public ref TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
{
return ref Lookup<TComponent>().Get(entityID);
}

View File

@ -41,7 +41,7 @@ namespace MoonTools.ECS
return count > 0;
}
public ref readonly TComponent Get(int entityID)
public ref TComponent Get(int entityID)
{
return ref components[entityIDToStorageIndex[entityID]];
}
@ -151,6 +151,7 @@ namespace MoonTools.ECS
{
return entityIDToStorageIndex.Keys;
}
#endif
protected virtual void Dispose(bool disposing)
{
@ -177,6 +178,5 @@ namespace MoonTools.ECS
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endif
}
}

View File

@ -1,69 +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* Array;
private int count;
private int capacity;
public int Count => count;
public ReverseSpanEnumerator<T> GetEnumerator() => new ReverseSpanEnumerator<T>(new Span<T>(Array, Count));
private bool disposed;
public NativeArray(int capacity = 16)
{
this.capacity = capacity;
Array = (T*) NativeMemory.Alloc((nuint) (capacity * Unsafe.SizeOf<T>()));
count = 0;
}
public ref T this[int i] => ref Array[i];
public void Add(T item)
{
if (count >= capacity)
{
capacity *= 2;
Array = (T*) NativeMemory.Realloc(Array, (nuint) (capacity * Unsafe.SizeOf<T>()));
}
Array[count] = item;
count += 1;
}
public void Clear()
{
count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
NativeMemory.Free(Array);
Array = null;
disposed = true;
}
}
~NativeArray()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -42,7 +42,7 @@ namespace MoonTools.ECS
return Lookup<TMessage>().First();
}
public ReverseSpanEnumerator<TMessage> WithEntity<TMessage>(int entityID) where TMessage : unmanaged
public Span<TMessage>.Enumerator WithEntity<TMessage>(int entityID) where TMessage : unmanaged
{
return Lookup<TMessage>().WithEntity(entityID);
}

View File

@ -63,7 +63,7 @@ namespace MoonTools.ECS
return messages[0];
}
public ReverseSpanEnumerator<TMessage> WithEntity(int entityID)
public Span<TMessage>.Enumerator WithEntity(int entityID)
{
if (entityToMessages.TryGetValue(entityID, out var messages))
{
@ -71,7 +71,7 @@ namespace MoonTools.ECS
}
else
{
return ReverseSpanEnumerator<TMessage>.Empty;
return Span<TMessage>.Empty.GetEnumerator();
}
}

40
src/Rev2/Archetype.cs Normal file
View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
internal class Archetype
{
public World World;
public ArchetypeSignature Signature;
public NativeArray[] ComponentColumns;
public NativeArray<EntityId> RowToEntity = new NativeArray<EntityId>();
public Dictionary<TypeId, int> ComponentToColumnIndex =
new Dictionary<TypeId, int>();
public SortedDictionary<TypeId, ArchetypeEdge> Edges = new SortedDictionary<TypeId, ArchetypeEdge>();
public int Count => RowToEntity.Count;
public Archetype(World world, ArchetypeSignature signature)
{
World = world;
Signature = signature;
ComponentColumns = new NativeArray[signature.Count];
}
public void ClearAll()
{
for (int i = 0; i < ComponentColumns.Length; i += 1)
{
ComponentColumns[i].Count = 0;
}
foreach (var entityId in RowToEntity)
{
World.FreeEntity(entityId);
}
RowToEntity.Clear();
}
}

View File

@ -0,0 +1,3 @@
namespace MoonTools.ECS.Rev2;
internal readonly record struct ArchetypeEdge(Archetype Add, Archetype Remove);

View File

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS.Rev2;
internal class ArchetypeSignature : IEquatable<ArchetypeSignature>
{
public static ArchetypeSignature Empty = new ArchetypeSignature(0);
List<uint> Ids;
public int Count => Ids.Count;
public TypeId this[int i] => new TypeId(Ids[i]);
public ArchetypeSignature()
{
Ids = new List<uint>();
}
public ArchetypeSignature(int capacity)
{
Ids = new List<uint>(capacity);
}
// Maintains sorted order
public void Insert(TypeId componentId)
{
var index = Ids.BinarySearch(componentId.Value);
if (index < 0)
{
Ids.Insert(~index, componentId.Value);
}
}
public void Remove(TypeId componentId)
{
var index = Ids.BinarySearch(componentId.Value);
if (index >= 0)
{
Ids.RemoveAt(index);
}
}
public void CopyTo(ArchetypeSignature other)
{
other.Ids.AddRange(Ids);
}
public override bool Equals(object? obj)
{
return obj is ArchetypeSignature signature && Equals(signature);
}
public bool Equals(ArchetypeSignature? other)
{
if (other == null)
{
return false;
}
if (Ids.Count != other.Ids.Count)
{
return false;
}
for (int i = 0; i < Ids.Count; i += 1)
{
if (Ids[i] != other.Ids[i])
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
var hashcode = 1;
foreach (var id in Ids)
{
hashcode = HashCode.Combine(hashcode, id);
}
return hashcode;
}
}

View File

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

View File

@ -0,0 +1,36 @@
namespace MoonTools.ECS.Rev2.Compatibility;
public abstract class EntityComponentReader
{
protected readonly World World;
public FilterBuilder FilterBuilder => World.FilterBuilder;
protected EntityComponentReader(World world)
{
World = world;
}
protected string GetTag(in EntityId entity) => World.GetTag(entity);
protected bool Has<T>(in EntityId entityId) where T : unmanaged => World.Has<T>(entityId);
protected bool Some<T>() where T : unmanaged => World.Some<T>();
protected ref T Get<T>(in EntityId entityId) where T : unmanaged => ref World.Get<T>(entityId);
protected ref T GetSingleton<T>() where T : unmanaged => ref World.GetSingleton<T>();
protected EntityId GetSingletonEntity<T>() where T : unmanaged => World.GetSingletonEntity<T>();
protected ReverseSpanEnumerator<(EntityId, EntityId)> Relations<T>() where T : unmanaged => World.Relations<T>();
protected bool Related<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged => World.Related<T>(entityA, entityB);
protected T GetRelationData<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged => World.GetRelationData<T>(entityA, entityB);
protected ReverseSpanEnumerator<EntityId> OutRelations<T>(in EntityId entity) where T : unmanaged => World.OutRelations<T>(entity);
protected EntityId OutRelationSingleton<T>(in EntityId entity) where T : unmanaged => World.OutRelationSingleton<T>(entity);
protected bool HasOutRelation<T>(in EntityId entity) where T : unmanaged => World.HasOutRelation<T>(entity);
protected int OutRelationCount<T>(in EntityId entity) where T : unmanaged => World.OutRelationCount<T>(entity);
protected EntityId NthOutRelation<T>(in EntityId entity, int n) where T : unmanaged => World.NthOutRelation<T>(entity, n);
protected ReverseSpanEnumerator<EntityId> InRelations<T>(in EntityId entity) where T : unmanaged => World.InRelations<T>(entity);
protected EntityId InRelationSingleton<T>(in EntityId entity) where T : unmanaged => World.InRelationSingleton<T>(entity);
protected bool HasInRelation<T>(in EntityId entity) where T : unmanaged => World.HasInRelation<T>(entity);
protected int InRelationCount<T>(in EntityId entity) where T : unmanaged => World.InRelationCount<T>(entity);
protected EntityId NthInRelation<T>(in EntityId entity, int n) where T : unmanaged => World.NthInRelation<T>(entity, n);
}

View File

@ -0,0 +1,16 @@
namespace MoonTools.ECS.Rev2.Compatibility;
public class Manipulator : EntityComponentReader
{
public Manipulator(World world) : base(world) { }
protected EntityId CreateEntity(string tag = "") => World.CreateEntity(tag);
protected void Tag(in EntityId entity, string tag) => World.Tag(entity, tag);
protected void Set<TComponent>(in EntityId entity, in TComponent component) where TComponent : unmanaged => World.Set<TComponent>(entity, component);
protected void Remove<TComponent>(in EntityId entity) where TComponent : unmanaged => World.Remove<TComponent>(entity);
protected void Relate<T>(in EntityId entityA, in EntityId entityB, in T relation) where T : unmanaged => World.Relate(entityA, entityB, relation);
protected void Unrelate<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged => World.Unrelate<T>(entityA, entityB);
protected void UnrelateAll<T>(in EntityId entity) where T : unmanaged => World.UnrelateAll<T>(entity);
protected void Destroy(in EntityId entity) => World.Destroy(entity);
}

View File

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

View File

@ -0,0 +1,15 @@
using System;
namespace MoonTools.ECS.Rev2.Compatibility;
public abstract class System : Manipulator
{
protected System(World world) : base(world) { }
public abstract void Update();
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);
}

11
src/Rev2/EntityId.cs Normal file
View File

@ -0,0 +1,11 @@
using System;
namespace MoonTools.ECS.Rev2;
public readonly record struct EntityId(uint Value) : IComparable<EntityId>
{
public int CompareTo(EntityId other)
{
return Value.CompareTo(other.Value);
}
}

203
src/Rev2/Filter.cs Normal file
View File

@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
namespace MoonTools.ECS.Rev2;
// TODO: do we want to get fancy with queries beyond Include and Exclude?
public class Filter
{
private Archetype EmptyArchetype;
private HashSet<TypeId> Included;
private HashSet<TypeId> Excluded;
public EntityEnumerator Entities => new EntityEnumerator(this);
internal ArchetypeEnumerator Archetypes => new ArchetypeEnumerator(this);
public RandomEntityEnumerator EntitiesInRandomOrder => new RandomEntityEnumerator(this);
public bool Empty
{
get
{
var empty = true;
foreach (var archetype in Archetypes)
{
if (archetype.Count > 0)
{
return false;
}
}
return empty;
}
}
public int Count
{
get
{
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
}
return count;
}
}
public EntityId RandomEntity
{
get
{
var randomIndex = RandomManager.Next(Count);
return NthEntity(randomIndex);
}
}
// WARNING: this WILL crash if the index is out of range!
public EntityId NthEntity(int index)
{
var count = 0;
foreach (var archetype in Archetypes)
{
count += archetype.Count;
if (index < count)
{
return archetype.RowToEntity[index];
}
index -= count;
}
throw new InvalidOperationException("Filter index out of range!");
}
public void DestroyAllEntities()
{
foreach (var archetype in Archetypes)
{
archetype.ClearAll();
}
}
internal Filter(Archetype emptyArchetype, HashSet<TypeId> included, HashSet<TypeId> excluded)
{
EmptyArchetype = emptyArchetype;
Included = included;
Excluded = excluded;
}
internal ref struct ArchetypeEnumerator
{
private Archetype CurrentArchetype;
// TODO: pool these
private Queue<Archetype> ArchetypeQueue = new Queue<Archetype>();
private Queue<Archetype> ArchetypeSearchQueue = new Queue<Archetype>();
private HashSet<Archetype> Explored = new HashSet<Archetype>();
public ArchetypeEnumerator GetEnumerator() => this;
public ArchetypeEnumerator(Filter filter)
{
var empty = filter.EmptyArchetype;
ArchetypeSearchQueue.Enqueue(empty);
while (ArchetypeSearchQueue.TryDequeue(out var current))
{
// exclude the empty archetype
var satisfiesFilter = filter.Included.Count != 0;
foreach (var componentId in filter.Included)
{
if (!current.ComponentToColumnIndex.ContainsKey(componentId))
{
satisfiesFilter = false;
}
}
foreach (var componentId in filter.Excluded)
{
if (current.ComponentToColumnIndex.ContainsKey(componentId))
{
satisfiesFilter = false;
}
}
if (satisfiesFilter)
{
ArchetypeQueue.Enqueue(current);
}
// breadth-first search
// ignore excluded component edges
foreach (var (componentId, edge) in current.Edges)
{
if (!Explored.Contains(edge.Add) && !filter.Excluded.Contains(componentId))
{
Explored.Add(edge.Add);
ArchetypeSearchQueue.Enqueue(edge.Add);
}
}
}
}
public bool MoveNext()
{
return ArchetypeQueue.TryDequeue(out CurrentArchetype!);
}
public Archetype Current => CurrentArchetype;
}
public ref struct EntityEnumerator
{
private EntityId CurrentEntity;
public EntityEnumerator GetEnumerator() => this;
// TODO: pool this
Queue<EntityId> EntityQueue = new Queue<EntityId>();
internal EntityEnumerator(Filter filter)
{
var archetypeEnumerator = new ArchetypeEnumerator(filter);
foreach (var archetype in archetypeEnumerator)
{
foreach (var entity in archetype.RowToEntity)
{
EntityQueue.Enqueue(entity);
}
}
}
public bool MoveNext()
{
return EntityQueue.TryDequeue(out CurrentEntity);
}
public EntityId Current => CurrentEntity;
}
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 EntityId Current => Filter.NthEntity(LinearCongruentialEnumerator.Current);
}
}

41
src/Rev2/FilterBuilder.cs Normal file
View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
namespace MoonTools.ECS.Rev2;
public ref struct FilterBuilder
{
World World;
HashSet<TypeId> Included;
HashSet<TypeId> Excluded;
internal FilterBuilder(World world)
{
World = world;
Included = new HashSet<TypeId>();
Excluded = new HashSet<TypeId>();
}
private FilterBuilder(World world, HashSet<TypeId> included, HashSet<TypeId> excluded)
{
World = world;
Included = included;
Excluded = excluded;
}
public FilterBuilder Include<T>() where T : unmanaged
{
Included.Add(World.GetTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public FilterBuilder Exclude<T>() where T : unmanaged
{
Excluded.Add(World.GetTypeId<T>());
return new FilterBuilder(World, Included, Excluded);
}
public Filter Build()
{
return new Filter(World.EmptyArchetype, Included, Excluded);
}
}

6
src/Rev2/IForEach.cs Normal file
View File

@ -0,0 +1,6 @@
namespace MoonTools.ECS.Rev2;
public interface IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged
{
public void Update(ref T1 t1, ref T2 t2);
}

31
src/Rev2/IdAssigner.cs Normal file
View File

@ -0,0 +1,31 @@
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
internal class IdAssigner
{
uint Next;
NativeArray<uint> AvailableIds = new NativeArray<uint>();
public uint Assign()
{
if (!AvailableIds.TryPop(out var id))
{
id = Next;
Next += 1;
}
return id;
}
public void Unassign(uint id)
{
AvailableIds.Add(id);
}
public void CopyTo(IdAssigner other)
{
AvailableIds.CopyTo(other.AvailableIds);
other.Next = Next;
}
}

View File

@ -0,0 +1,63 @@
using System;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
public class MessageStorage : IDisposable
{
private NativeArray Messages;
private bool IsDisposed;
public MessageStorage(int elementSize)
{
Messages = new NativeArray(elementSize);
}
public void Add<T>(in T message) where T : unmanaged
{
Messages.Append(message);
}
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.Count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Messages.Dispose();
IsDisposed = true;
}
}
// ~MessageStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

3
src/Rev2/Record.cs Normal file
View File

@ -0,0 +1,3 @@
namespace MoonTools.ECS.Rev2;
internal readonly record struct Record(Archetype Archetype, int Row);

287
src/Rev2/RelationStorage.cs Normal file
View File

@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
// TODO: implement this entire class with NativeMemory equivalents, can just memcpy for snapshots
internal class RelationStorage
{
internal NativeArray relations;
internal NativeArray relationDatas;
internal Dictionary<(EntityId, EntityId), int> indices = new Dictionary<(EntityId, EntityId), int>(16);
internal Dictionary<EntityId, IndexableSet<EntityId>> outRelations = new Dictionary<EntityId, IndexableSet<EntityId>>(16);
internal Dictionary<EntityId, IndexableSet<EntityId>> inRelations = new Dictionary<EntityId, IndexableSet<EntityId>>(16);
private Stack<IndexableSet<EntityId>> listPool = new Stack<IndexableSet<EntityId>>();
private bool disposed;
public RelationStorage(int relationDataSize)
{
relations = new NativeArray(Unsafe.SizeOf<(EntityId, EntityId)>());
relationDatas = new NativeArray(relationDataSize);
}
public ReverseSpanEnumerator<(EntityId, EntityId)> All()
{
return new ReverseSpanEnumerator<(EntityId, EntityId)>(relations.ToSpan<(EntityId, EntityId)>());
}
public unsafe void Set<T>(in EntityId entityA, in EntityId entityB, in T relationData) where T : unmanaged
{
var relation = (entityA, entityB);
if (indices.TryGetValue(relation, out var index))
{
((T*) relationDatas.Elements)[index] = relationData;
return;
}
if (!outRelations.ContainsKey(entityA))
{
outRelations[entityA] = AcquireHashSetFromPool();
}
outRelations[entityA].Add(entityB);
if (!inRelations.ContainsKey(entityB))
{
inRelations[entityB] = AcquireHashSetFromPool();
}
inRelations[entityB].Add(entityA);
relations.Append(relation);
relationDatas.Append(relationData);
indices.Add(relation, relations.Count - 1);
}
public ref T Get<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged
{
var relationIndex = indices[(entityA, entityB)];
return ref relationDatas.Get<T>(relationIndex);
}
public bool Has(EntityId entityA, EntityId entityB)
{
return indices.ContainsKey((entityA, entityB));
}
public ReverseSpanEnumerator<EntityId> OutRelations(EntityId entityID)
{
if (outRelations.TryGetValue(entityID, out var entityOutRelations))
{
return entityOutRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<EntityId>.Empty;
}
}
public EntityId OutFirst(EntityId entityID)
{
return OutNth(entityID, 0);
}
public EntityId OutNth(EntityId entityID, int n)
{
#if DEBUG
if (!outRelations.ContainsKey(entityID) || outRelations[entityID].Count == 0)
{
throw new KeyNotFoundException("No out relations to this entity!");
}
#endif
return outRelations[entityID][n];
}
public bool HasOutRelation(EntityId entityID)
{
return outRelations.ContainsKey(entityID) && outRelations[entityID].Count > 0;
}
public int OutRelationCount(EntityId entityID)
{
return outRelations.TryGetValue(entityID, out var entityOutRelations) ? entityOutRelations.Count : 0;
}
public ReverseSpanEnumerator<EntityId> InRelations(EntityId entityID)
{
if (inRelations.TryGetValue(entityID, out var entityInRelations))
{
return entityInRelations.GetEnumerator();
}
else
{
return ReverseSpanEnumerator<EntityId>.Empty;
}
}
public EntityId InFirst(EntityId entityID)
{
return InNth(entityID, 0);
}
public EntityId InNth(EntityId entityID, int n)
{
#if DEBUG
if (!inRelations.ContainsKey(entityID) || inRelations[entityID].Count == 0)
{
throw new KeyNotFoundException("No in relations to this entity!");
}
#endif
return inRelations[entityID][n];
}
public bool HasInRelation(EntityId entityID)
{
return inRelations.ContainsKey(entityID) && inRelations[entityID].Count > 0;
}
public int InRelationCount(EntityId entityID)
{
return inRelations.TryGetValue(entityID, out var entityInRelations) ? entityInRelations.Count : 0;
}
public (bool, bool) Remove(in EntityId entityA, in EntityId entityB)
{
var aEmpty = false;
var bEmpty = false;
var relation = (entityA, entityB);
if (outRelations.TryGetValue(entityA, out var entityOutRelations))
{
entityOutRelations.Remove(entityB);
if (outRelations[entityA].Count == 0)
{
aEmpty = true;
}
}
if (inRelations.TryGetValue(entityB, out var entityInRelations))
{
entityInRelations.Remove(entityA);
if (inRelations[entityB].Count == 0)
{
bEmpty = true;
}
}
if (indices.TryGetValue(relation, out var index))
{
var lastElementIndex = relations.Count - 1;
relationDatas.Delete(index);
relations.Delete(index);
// move an element into the hole
if (index != lastElementIndex)
{
var lastRelation = relations.Get<(EntityId, EntityId)>(lastElementIndex);
indices[lastRelation] = index;
}
indices.Remove(relation);
}
return (aEmpty, bEmpty);
}
public void RemoveEntity(in EntityId entity)
{
if (outRelations.TryGetValue(entity, out var entityOutRelations))
{
foreach (var entityB in entityOutRelations)
{
Remove(entity, entityB);
}
ReturnHashSetToPool(entityOutRelations);
outRelations.Remove(entity);
}
if (inRelations.TryGetValue(entity, out var entityInRelations))
{
foreach (var entityA in entityInRelations)
{
Remove(entityA, entity);
}
ReturnHashSetToPool(entityInRelations);
inRelations.Remove(entity);
}
}
internal IndexableSet<EntityId> AcquireHashSetFromPool()
{
if (listPool.Count == 0)
{
listPool.Push(new IndexableSet<EntityId>());
}
return listPool.Pop();
}
private void ReturnHashSetToPool(IndexableSet<EntityId> hashSet)
{
hashSet.Clear();
listPool.Push(hashSet);
}
public void Clear()
{
indices.Clear();
foreach (var set in inRelations.Values)
{
ReturnHashSetToPool(set);
}
inRelations.Clear();
foreach (var set in outRelations.Values)
{
ReturnHashSetToPool(set);
}
outRelations.Clear();
relations.Count = 0;
relationDatas.Count = 0;
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
Clear();
if (disposing)
{
foreach (var set in listPool)
{
set.Dispose();
}
relations.Dispose();
relationDatas.Dispose();
relations = null;
relationDatas = null;
}
disposed = true;
}
}
// ~RelationStorage()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

242
src/Rev2/Snapshot.cs Normal file
View File

@ -0,0 +1,242 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
// TODO: we should implement a NativeDictionary that can be memcopied
public class Snapshot
{
private Dictionary<ArchetypeSignature, ArchetypeSnapshot> ArchetypeSnapshots =
new Dictionary<ArchetypeSignature, ArchetypeSnapshot>();
private Dictionary<TypeId, RelationSnapshot> RelationSnapshots =
new Dictionary<TypeId, RelationSnapshot>();
private Dictionary<EntityId, Record> EntityIndex = new Dictionary<EntityId, Record>();
private Dictionary<EntityId, IndexableSet<TypeId>> EntityRelationIndex =
new Dictionary<EntityId, IndexableSet<TypeId>>();
private Dictionary<EntityId, string> EntityTags = new Dictionary<EntityId, string>();
private IdAssigner EntityIdAssigner = new IdAssigner();
public int Count
{
get
{
var count = 0;
foreach (var snapshot in ArchetypeSnapshots.Values)
{
count += snapshot.Count;
}
return count;
}
}
public void Restore(World world)
{
// restore archetype storage
foreach (var (archetypeSignature, archetypeSnapshot) in ArchetypeSnapshots)
{
var archetype = world.ArchetypeIndex[archetypeSignature];
archetypeSnapshot.Restore(archetype);
}
// restore entity index
world.EntityIndex.Clear();
foreach (var (id, record) in EntityIndex)
{
world.EntityIndex[id] = record;
}
// restore id assigner state
EntityIdAssigner.CopyTo(world.EntityIdAssigner);
// restore relation state
foreach (var (typeId, relationSnapshot) in RelationSnapshots)
{
var relationStorage = world.RelationIndex[typeId];
relationSnapshot.Restore(relationStorage);
}
// restore entity relation index state
// FIXME: arghhhh this is so slow
foreach (var (id, relationTypeSet) in EntityRelationIndex)
{
world.EntityRelationIndex[id].Clear();
foreach (var typeId in relationTypeSet)
{
world.EntityRelationIndex[id].Add(typeId);
}
}
foreach (var (id, s) in EntityTags)
{
world.EntityTags[id] = s;
}
}
public void Take(World world)
{
// copy id assigner state
world.EntityIdAssigner.CopyTo(EntityIdAssigner);
// copy entity index
EntityIndex.Clear();
foreach (var (id, record) in world.EntityIndex)
{
EntityIndex[id] = record;
}
// copy archetypes
foreach (var archetype in world.ArchetypeIndex.Values)
{
TakeArchetypeSnapshot(archetype);
}
// copy relations
foreach (var (typeId, relationStorage) in world.RelationIndex)
{
TakeRelationSnapshot(typeId, relationStorage);
}
// copy entity relation index
// FIXME: arghhhh this is so slow
foreach (var (id, relationTypeSet) in world.EntityRelationIndex)
{
if (!EntityRelationIndex.ContainsKey(id))
{
EntityRelationIndex.Add(id, new IndexableSet<TypeId>());
}
EntityRelationIndex[id].Clear();
foreach (var typeId in relationTypeSet)
{
EntityRelationIndex[id].Add(typeId);
}
}
foreach (var (id, s) in world.EntityTags)
{
EntityTags[id] = s;
}
}
private void TakeArchetypeSnapshot(Archetype archetype)
{
if (!ArchetypeSnapshots.TryGetValue(archetype.Signature, out var archetypeSnapshot))
{
archetypeSnapshot = new ArchetypeSnapshot(archetype);
ArchetypeSnapshots.Add(archetype.Signature, archetypeSnapshot);
}
archetypeSnapshot.Take(archetype);
}
private void TakeRelationSnapshot(TypeId typeId, RelationStorage relationStorage)
{
if (!RelationSnapshots.TryGetValue(typeId, out var snapshot))
{
snapshot = new RelationSnapshot(relationStorage.relationDatas.ElementSize);
RelationSnapshots.Add(typeId, snapshot);
}
snapshot.Take(relationStorage);
}
private class ArchetypeSnapshot
{
private readonly NativeArray[] ComponentColumns;
private readonly NativeArray<EntityId> RowToEntity;
public int Count => RowToEntity.Count;
public ArchetypeSnapshot(Archetype archetype)
{
ComponentColumns = new NativeArray[archetype.ComponentColumns.Length];
RowToEntity = new NativeArray<EntityId>();
for (int i = 0; i < archetype.ComponentColumns.Length; i += 1)
{
ComponentColumns[i] = new NativeArray(archetype.ComponentColumns[i].ElementSize);
}
}
public void Take(Archetype archetype)
{
for (int i = 0; i < ComponentColumns.Length; i += 1)
{
archetype.ComponentColumns[i].CopyAllTo(ComponentColumns[i]);
}
archetype.RowToEntity.CopyTo(RowToEntity);
}
public void Restore(Archetype archetype)
{
// Copy all component data
for (int i = 0; i < ComponentColumns.Length; i += 1)
{
ComponentColumns[i].CopyAllTo(archetype.ComponentColumns[i]);
}
RowToEntity.CopyTo(archetype.RowToEntity);
}
}
private class RelationSnapshot
{
private NativeArray Relations;
private NativeArray RelationDatas;
public RelationSnapshot(int elementSize)
{
Relations = new NativeArray(Unsafe.SizeOf<(EntityId, EntityId)>());
RelationDatas = new NativeArray(elementSize);
}
public void Take(RelationStorage relationStorage)
{
relationStorage.relations.CopyAllTo(Relations);
relationStorage.relationDatas.CopyAllTo(RelationDatas);
}
public void Restore(RelationStorage relationStorage)
{
relationStorage.Clear();
Relations.CopyAllTo(relationStorage.relations);
RelationDatas.CopyAllTo(relationStorage.relationDatas);
for (int index = 0; index < Relations.Count; index += 1)
{
var relation = Relations.Get<(EntityId, EntityId)>(index);
relationStorage.indices[relation] = index;
relationStorage.indices[relation] = index;
if (!relationStorage.outRelations.ContainsKey(relation.Item1))
{
relationStorage.outRelations[relation.Item1] =
relationStorage.AcquireHashSetFromPool();
}
relationStorage.outRelations[relation.Item1].Add(relation.Item2);
if (!relationStorage.inRelations.ContainsKey(relation.Item2))
{
relationStorage.inRelations[relation.Item2] =
relationStorage.AcquireHashSetFromPool();
}
relationStorage.inRelations[relation.Item2].Add(relation.Item1);
}
}
}
}

11
src/Rev2/TypeId.cs Normal file
View File

@ -0,0 +1,11 @@
using System;
namespace MoonTools.ECS.Rev2;
public readonly record struct TypeId(uint Value) : IComparable<TypeId>
{
public int CompareTo(TypeId other)
{
return Value.CompareTo(other.Value);
}
}

683
src/Rev2/World.cs Normal file
View File

@ -0,0 +1,683 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using MoonTools.ECS.Collections;
namespace MoonTools.ECS.Rev2;
public class World : IDisposable
{
// Get TypeId from a Type
internal Dictionary<Type, TypeId> TypeToId = new Dictionary<Type, TypeId>();
#if DEBUG
private Dictionary<TypeId, Type> IdToType = new Dictionary<TypeId, Type>();
#endif
// Get element size from a TypeId
private Dictionary<TypeId, int> ElementSizes = new Dictionary<TypeId, int>();
// Lookup from ArchetypeSignature to Archetype
internal Dictionary<ArchetypeSignature, Archetype> ArchetypeIndex = new Dictionary<ArchetypeSignature, Archetype>();
// Going from EntityId to Archetype and storage row
internal Dictionary<EntityId, Record> EntityIndex = new Dictionary<EntityId, Record>();
// TODO: can we make the tag an inline array of chars at some point?
internal Dictionary<EntityId, string> EntityTags = new Dictionary<EntityId, string>();
// Relation Storages
internal Dictionary<TypeId, RelationStorage> RelationIndex =
new Dictionary<TypeId, RelationStorage>();
// Entity Relation Tracking
internal Dictionary<EntityId, IndexableSet<TypeId>> EntityRelationIndex =
new Dictionary<EntityId, IndexableSet<TypeId>>();
// Message Storages
private Dictionary<TypeId, MessageStorage> MessageIndex =
new Dictionary<TypeId, MessageStorage>();
// Filters with a single Include type for Singleton/Some implementation
internal Dictionary<TypeId, Filter> SingleTypeFilters = new Dictionary<TypeId, Filter>();
// Entity ID Management
internal IdAssigner EntityIdAssigner = new IdAssigner();
private IdAssigner TypeIdAssigner = new IdAssigner();
internal readonly Archetype EmptyArchetype;
public FilterBuilder FilterBuilder => new FilterBuilder(this);
public delegate void RefAction<T1, T2>(ref T1 arg1, ref T2 arg2);
private bool IsDisposed;
public World()
{
// Create the Empty Archetype
EmptyArchetype = CreateArchetype(ArchetypeSignature.Empty);
}
internal Archetype CreateArchetype(ArchetypeSignature signature)
{
var archetype = new Archetype(this, signature);
ArchetypeIndex.Add(signature, archetype);
for (int i = 0; i < signature.Count; i += 1)
{
var componentId = signature[i];
archetype.ComponentToColumnIndex.Add(componentId, i);
archetype.ComponentColumns[i] = new NativeArray(ElementSizes[componentId]);
}
return archetype;
}
public EntityId CreateEntity(string tag = "")
{
var entityId = new EntityId(EntityIdAssigner.Assign());
EntityIndex.Add(entityId, new Record(EmptyArchetype, EmptyArchetype.Count));
EmptyArchetype.RowToEntity.Add(entityId);
if (!EntityRelationIndex.ContainsKey(entityId))
{
EntityRelationIndex.Add(entityId, new IndexableSet<TypeId>());
}
EntityTags[entityId] = tag;
return entityId;
}
public void Tag(in EntityId entityId, string tag)
{
EntityTags[entityId] = tag;
}
public string GetTag(in EntityId entityId)
{
return EntityTags[entityId];
}
internal TypeId GetTypeId<T>() where T : unmanaged
{
if (TypeToId.ContainsKey(typeof(T)))
{
return TypeToId[typeof(T)];
}
var typeId = new TypeId(TypeIdAssigner.Assign());
TypeToId.Add(typeof(T), typeId);
ElementSizes.Add(typeId, Unsafe.SizeOf<T>());
#if DEBUG
IdToType.Add(typeId, typeof(T));
#endif
return typeId;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TypeId GetComponentId<T>() where T : unmanaged
{
var typeId = GetTypeId<T>();
if (!SingleTypeFilters.ContainsKey(typeId))
{
SingleTypeFilters.Add(typeId, FilterBuilder.Include<T>().Build());
}
return typeId;
}
private RelationStorage RegisterRelationType(TypeId typeId)
{
var relationStorage = new RelationStorage(ElementSizes[typeId]);
RelationIndex.Add(typeId, relationStorage);
return relationStorage;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private RelationStorage GetRelationStorage<T>() where T : unmanaged
{
var typeId = GetTypeId<T>();
if (RelationIndex.TryGetValue(typeId, out var relationStorage))
{
return relationStorage;
}
return RegisterRelationType(typeId);
}
// Messages
private TypeId GetMessageTypeId<T>() where T : unmanaged
{
var typeId = GetTypeId<T>();
if (!MessageIndex.ContainsKey(typeId))
{
MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf<T>()));
}
return typeId;
}
public void Send<T>(in T message) where T : unmanaged
{
var typeId = GetMessageTypeId<T>();
MessageIndex[typeId].Add(message);
}
public bool SomeMessage<T>() where T : unmanaged
{
var typeId = GetMessageTypeId<T>();
return MessageIndex[typeId].Some();
}
public ReadOnlySpan<T> ReadMessages<T>() where T : unmanaged
{
var typeId = GetMessageTypeId<T>();
return MessageIndex[typeId].All<T>();
}
public T ReadMessage<T>() where T : unmanaged
{
var typeId = GetMessageTypeId<T>();
return MessageIndex[typeId].First<T>();
}
public void ClearMessages()
{
foreach (var (_, messageStorage) in MessageIndex)
{
messageStorage.Clear();
}
}
// Components
public bool Has<T>(EntityId entityId) where T : unmanaged
{
var componentId = GetComponentId<T>();
var record = EntityIndex[entityId];
return record.Archetype.ComponentToColumnIndex.ContainsKey(componentId);
}
public bool Some<T>() where T : unmanaged
{
var componentTypeId = GetComponentId<T>();
return SingleTypeFilters[componentTypeId].Count > 0;
}
// will throw if non-existent
public unsafe ref T Get<T>(EntityId entityId) where T : unmanaged
{
var componentId = GetComponentId<T>();
var record = EntityIndex[entityId];
var columnIndex = record.Archetype.ComponentToColumnIndex[componentId];
var column = record.Archetype.ComponentColumns[columnIndex];
return ref ((T*) column.Elements)[record.Row];
}
public ref T GetSingleton<T>() where T : unmanaged
{
var componentId = GetComponentId<T>();
foreach (var archetype in SingleTypeFilters[componentId].Archetypes)
{
if (archetype.Count > 0)
{
var columnIndex = archetype.ComponentToColumnIndex[componentId];
return ref archetype.ComponentColumns[columnIndex].Get<T>(0);
}
}
throw new InvalidOperationException("No component of this type exists!");
}
public EntityId GetSingletonEntity<T>() where T : unmanaged
{
var componentId = GetComponentId<T>();
foreach (var archetype in SingleTypeFilters[componentId].Archetypes)
{
if (archetype.Count > 0)
{
return archetype.RowToEntity[0];
}
}
throw new InvalidOperationException("No entity with this component type exists!");
}
public unsafe void Set<T>(in EntityId entityId, in T component) where T : unmanaged
{
var componentId = GetComponentId<T>();
if (Has<T>(entityId))
{
var record = EntityIndex[entityId];
var columnIndex = record.Archetype.ComponentToColumnIndex[componentId];
var column = record.Archetype.ComponentColumns[columnIndex];
((T*) column.Elements)[record.Row] = component;
}
else
{
Add(entityId, component);
}
}
private void Add<T>(EntityId entityId, in T component) where T : unmanaged
{
Archetype? nextArchetype;
var componentId = GetComponentId<T>();
// move the entity to the new archetype
var record = EntityIndex[entityId];
var archetype = record.Archetype;
if (archetype.Edges.TryGetValue(componentId, out var edge))
{
nextArchetype = edge.Add;
}
else
{
// FIXME: pool the signatures
var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
archetype.Signature.CopyTo(nextSignature);
nextSignature.Insert(componentId);
if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
{
nextArchetype = CreateArchetype(nextSignature);
}
var newEdge = new ArchetypeEdge(nextArchetype, archetype);
archetype.Edges.Add(componentId, newEdge);
nextArchetype.Edges.Add(componentId, newEdge);
}
MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype);
// add the new component to the new archetype
var columnIndex = nextArchetype.ComponentToColumnIndex[componentId];
var column = nextArchetype.ComponentColumns[columnIndex];
column.Append(component);
}
public void Remove<T>(EntityId entityId) where T : unmanaged
{
Archetype? nextArchetype;
if (!Has<T>(entityId))
{
return;
}
var componentId = GetComponentId<T>();
var (archetype, row) = EntityIndex[entityId];
if (archetype.Edges.TryGetValue(componentId, out var edge))
{
nextArchetype = edge.Remove;
}
else
{
// FIXME: pool the signatures
var nextSignature = new ArchetypeSignature(archetype.Signature.Count + 1);
archetype.Signature.CopyTo(nextSignature);
nextSignature.Remove(componentId);
if (!ArchetypeIndex.TryGetValue(nextSignature, out nextArchetype))
{
nextArchetype = CreateArchetype(nextSignature);
}
var newEdge = new ArchetypeEdge(nextArchetype, archetype);
archetype.Edges.Add(componentId, newEdge);
nextArchetype.Edges.Add(componentId, newEdge);
}
MoveEntityToLowerArchetype(entityId, row, archetype, nextArchetype, componentId);
}
public void Relate<T>(in EntityId entityA, in EntityId entityB, in T relation) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Set(entityA, entityB, relation);
EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]);
EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]);
}
public void Unrelate<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.Remove(entityA, entityB);
}
public void UnrelateAll<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
relationStorage.RemoveEntity(entity);
}
public bool Related<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Has(entityA, entityB);
}
public T GetRelationData<T>(in EntityId entityA, in EntityId entityB) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.Get<T>(entityA, entityB);
}
public ReverseSpanEnumerator<(EntityId, EntityId)> Relations<T>() where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.All();
}
public ReverseSpanEnumerator<EntityId> OutRelations<T>(EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelations(entity);
}
public EntityId OutRelationSingleton<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutFirst(entity);
}
public bool HasOutRelation<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasOutRelation(entity);
}
public int OutRelationCount<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutRelationCount(entity);
}
public EntityId NthOutRelation<T>(in EntityId entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.OutNth(entity, n);
}
public ReverseSpanEnumerator<EntityId> InRelations<T>(EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelations(entity);
}
public EntityId InRelationSingleton<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InFirst(entity);
}
public bool HasInRelation<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.HasInRelation(entity);
}
public int InRelationCount<T>(in EntityId entity) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InRelationCount(entity);
}
public EntityId NthInRelation<T>(in EntityId entity, int n) where T : unmanaged
{
var relationStorage = GetRelationStorage<T>();
return relationStorage.InNth(entity, n);
}
// used as a fast path by Archetype.ClearAll and snapshot restore
internal void FreeEntity(EntityId entityId)
{
EntityIndex.Remove(entityId);
EntityIdAssigner.Unassign(entityId.Value);
foreach (var relationTypeIndex in EntityRelationIndex[entityId])
{
var relationStorage = RelationIndex[relationTypeIndex];
relationStorage.RemoveEntity(entityId);
}
EntityRelationIndex[entityId].Clear();
}
public void Destroy(EntityId entityId)
{
var record = EntityIndex[entityId];
var archetype = record.Archetype;
var row = record.Row;
for (int i = 0; i < archetype.Signature.Count; i += 1)
{
archetype.ComponentColumns[i].Delete(row);
}
if (row != archetype.Count - 1)
{
// move last row entity to open spot
var lastRowEntity = archetype.RowToEntity[archetype.Count - 1];
archetype.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(archetype, row);
}
archetype.RowToEntity.RemoveLastElement();
EntityIndex.Remove(entityId);
EntityIdAssigner.Unassign(entityId.Value);
foreach (var relationTypeIndex in EntityRelationIndex[entityId])
{
var relationStorage = RelationIndex[relationTypeIndex];
relationStorage.RemoveEntity(entityId);
}
EntityRelationIndex[entityId].Clear();
}
private void MoveEntityToHigherArchetype(EntityId entityId, int row, Archetype from, Archetype to)
{
for (int i = 0; i < from.Signature.Count; i += 1)
{
var componentId = from.Signature[i];
var destinationColumnIndex = to.ComponentToColumnIndex[componentId];
// copy all components to higher archetype
from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]);
// delete row on from archetype
from.ComponentColumns[i].Delete(row);
}
if (row != from.Count - 1)
{
// move last row entity to open spot
var lastRowEntity = from.RowToEntity[from.Count - 1];
from.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(from, row);
}
from.RowToEntity.RemoveLastElement();
// update row to entity lookup on to archetype
EntityIndex[entityId] = new Record(to, to.Count);
to.RowToEntity.Add(entityId);
}
private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, TypeId removed)
{
for (int i = 0; i < from.Signature.Count; i += 1)
{
var componentId = from.Signature[i];
// delete the row
from.ComponentColumns[i].Delete(row);
// if this isn't the removed component, copy to the lower archetype
if (componentId != removed)
{
var destinationColumnIndex = to.ComponentToColumnIndex[componentId];
from.ComponentColumns[i].CopyElementToEnd(row, to.ComponentColumns[destinationColumnIndex]);
}
}
if (row != from.Count - 1)
{
// update row to entity lookup on from archetype
var lastRowEntity = from.RowToEntity[from.Count - 1];
from.RowToEntity[row] = lastRowEntity;
EntityIndex[lastRowEntity] = new Record(from, row);
}
from.RowToEntity.RemoveLastElement();
// update row to entity lookup on to archetype
EntityIndex[entityId] = new Record(to, to.Count);
to.RowToEntity.Add(entityId);
}
public unsafe void ForEachEntity<T, T1, T2>(Filter filter,
T rowForEachContainer) where T : IForEach<T1, T2> where T1 : unmanaged where T2 : unmanaged
{
foreach (var archetype in filter.Archetypes)
{
var componentIdOne = archetype.Signature[0];
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements;
var componentIdTwo = archetype.Signature[1];
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements;
for (int i = archetype.Count - 1; i >= 0; i -= 1)
{
rowForEachContainer.Update(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
}
}
}
public unsafe void ForEachEntity<T1, T2>(Filter filter, RefAction<T1, T2> rowAction) where T1 : unmanaged where T2 : unmanaged
{
foreach (var archetype in filter.Archetypes)
{
var componentIdOne = archetype.Signature[0];
var columnIndexOne = archetype.ComponentToColumnIndex[componentIdOne];
var columnOneElements = archetype.ComponentColumns[columnIndexOne].Elements;
var componentIdTwo = archetype.Signature[1];
var columnIndexTwo = archetype.ComponentToColumnIndex[componentIdTwo];
var columnTwoElements = archetype.ComponentColumns[columnIndexTwo].Elements;
for (int i = archetype.Count - 1; i >= 0; i -= 1)
{
rowAction(ref ((T1*) columnOneElements)[i], ref ((T2*) columnTwoElements)[i]);
}
}
}
// DEBUG
// NOTE: these methods are very inefficient
// they should only be used in debugging contexts!!
#if DEBUG
public ComponentTypeEnumerator Debug_GetAllComponentTypes(EntityId entity)
{
return new ComponentTypeEnumerator(this, EntityIndex[entity]);
}
public Filter.EntityEnumerator Debug_GetEntities(Type componentType)
{
var typeId = TypeToId[componentType];
return SingleTypeFilters[typeId].Entities;
}
public IEnumerable<Type> Debug_SearchComponentType(string typeString)
{
foreach (var type in TypeToId.Keys)
{
if (type.ToString().ToLower().Contains(typeString.ToLower()))
{
yield return type;
}
}
}
public ref struct ComponentTypeEnumerator
{
private World World;
private Record Record;
private int ComponentIndex;
public ComponentTypeEnumerator GetEnumerator() => this;
internal ComponentTypeEnumerator(
World world,
Record record
)
{
World = world;
Record = record;
ComponentIndex = -1;
}
public bool MoveNext()
{
ComponentIndex += 1;
return ComponentIndex < Record.Archetype.ComponentColumns.Length;
}
public unsafe Type Current => World.IdToType[Record.Archetype.Signature[ComponentIndex]];
}
#endif
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
foreach (var archetype in ArchetypeIndex.Values)
{
for (var i = 0; i < archetype.Signature.Count; i += 1)
{
archetype.ComponentColumns[i].Dispose();
}
}
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
IsDisposed = true;
}
}
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~World()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -25,7 +25,7 @@ namespace MoonTools.ECS
return MessageDepot.Some<TMessage>();
}
protected ReverseSpanEnumerator<TMessage> ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
protected Span<TMessage>.Enumerator ReadMessagesWithEntity<TMessage>(in Entity entity) where TMessage : unmanaged
{
return MessageDepot.WithEntity<TMessage>(entity.ID);
}

View File

@ -36,6 +36,11 @@ namespace MoonTools.ECS
return EntityStorage.Tag(entity);
}
public ref TComponent Get<TComponent>(in Entity entity) where TComponent : unmanaged
{
return ref ComponentDepot.Get<TComponent>(entity.ID);
}
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
{
#if DEBUG