diff --git a/src/Rev2/Archetype.cs b/src/Rev2/Archetype.cs index 4e69e7e..e5f8a16 100644 --- a/src/Rev2/Archetype.cs +++ b/src/Rev2/Archetype.cs @@ -6,8 +6,8 @@ namespace MoonTools.ECS.Rev2 { public ArchetypeSignature Signature; public ArchetypeId Id { get; private set; } - /* FIXME: make this native memory too */ public List Components = new List(); + public List RowToEntity = new List(); public Dictionary Edges = new Dictionary(); public int Count; diff --git a/src/Rev2/ArchetypeSignature.cs b/src/Rev2/ArchetypeSignature.cs index 747fe9d..b904dd3 100644 --- a/src/Rev2/ArchetypeSignature.cs +++ b/src/Rev2/ArchetypeSignature.cs @@ -34,6 +34,16 @@ namespace MoonTools.ECS.Rev2 } } + public void Remove(ComponentId componentId) + { + var index = Ids.BinarySearch(componentId.Id); + + if (index >= 0) + { + Ids.RemoveAt(index); + } + } + public void CopyTo(ArchetypeSignature other) { other.Ids.AddRange(Ids); diff --git a/src/Rev2/Column.cs b/src/Rev2/Column.cs index 6675313..532ac73 100644 --- a/src/Rev2/Column.cs +++ b/src/Rev2/Column.cs @@ -1,21 +1,18 @@ -using System.Runtime.CompilerServices; +using System; using System.Runtime.InteropServices; namespace MoonTools.ECS.Rev2 { - public unsafe class Column + public unsafe class Column : IDisposable { public nint Elements; public int ElementSize; public int Count; public int Capacity; - public static Column Create() where T : unmanaged - { - return new Column(Unsafe.SizeOf()); - } + private bool IsDisposed; - private Column(int elementSize) + public Column(int elementSize) { Capacity = 16; Count = 0; @@ -44,6 +41,17 @@ namespace MoonTools.ECS.Rev2 Count -= 1; } + public void Append(T component) where T : unmanaged + { + if (Count >= Capacity) + { + Resize(); + } + + ((T*) Elements)[Count] = component; + Count += 1; + } + public void CopyToEnd(int index, Column other) { if (other.Count >= other.Capacity) @@ -59,5 +67,27 @@ namespace MoonTools.ECS.Rev2 other.Count += 1; } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + NativeMemory.Free((void*) Elements); + IsDisposed = true; + } + } + + ~Column() + { + // 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); + } } } diff --git a/src/Rev2/World.cs b/src/Rev2/World.cs index c9f98cf..2fccc9a 100644 --- a/src/Rev2/World.cs +++ b/src/Rev2/World.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace MoonTools.ECS.Rev2 { - public class World + public class World : IDisposable { // Lookup from ArchetypeSignature to Archetype Dictionary ArchetypeIndex = new Dictionary(); @@ -11,17 +12,22 @@ namespace MoonTools.ECS.Rev2 // Going from EntityId to Archetype and storage row Dictionary EntityIndex = new Dictionary(); - // Going from ComponentId to an Archetype storage column + // Going from ComponentId to an Archetype storage column index Dictionary> ComponentIndex = new Dictionary>(); // Get ComponentId from a Type Dictionary TypeToComponentId = new Dictionary(); + // Get element size from a ComponentId + Dictionary ElementSizes = new Dictionary(); + // ID Management IdAssigner ArchetypeIdAssigner = new IdAssigner(); IdAssigner EntityIdAssigner = new IdAssigner(); IdAssigner ComponentIdAssigner = new IdAssigner(); + private bool IsDisposed; + public World() { // Create the Empty Archetype @@ -31,13 +37,18 @@ namespace MoonTools.ECS.Rev2 private Archetype CreateArchetype(ArchetypeSignature signature) { var archetypeId = ArchetypeIdAssigner.Assign(); - var archetype = new Archetype(archetypeId, signature); + var archetype = new Archetype(archetypeId, signature) + { + Components = new List(signature.Count) + }; + ArchetypeIndex.Add(signature, archetype); for (int i = 0; i < signature.Count; i += 1) { var componentId = signature[i]; ComponentIndex[componentId].Add(archetypeId, new ArchetypeRecord(i)); + archetype.Components.Add(new Column(ElementSizes[componentId])); } return archetype; @@ -48,20 +59,25 @@ namespace MoonTools.ECS.Rev2 var entityId = EntityIdAssigner.Assign(); var emptyArchetype = ArchetypeIndex[ArchetypeSignature.Empty]; EntityIndex.Add(entityId, new Record(emptyArchetype, 0)); + emptyArchetype.RowToEntity.Add(entityId); return entityId; } // FIXME: would be much more efficient to do all this at load time somehow - private void RegisterComponentId(ComponentId componentId) + private ComponentId RegisterComponent() where T : unmanaged { + var componentId = ComponentIdAssigner.Assign(); + TypeToComponentId.Add(typeof(T), componentId); ComponentIndex.Add(componentId, new Dictionary()); + ElementSizes.Add(componentId, Unsafe.SizeOf()); + return componentId; } public ComponentId GetComponentId() where T : unmanaged { if (!TypeToComponentId.TryGetValue(typeof(T), out var componentId)) { - componentId = ComponentIdAssigner.Assign(); + return RegisterComponent(); } return componentId; @@ -101,6 +117,7 @@ namespace MoonTools.ECS.Rev2 var componentId = GetComponentId(); + // move the entity to the new archetype var record = EntityIndex[entityId]; var archetype = record.Archetype; @@ -120,29 +137,150 @@ namespace MoonTools.ECS.Rev2 nextArchetype = CreateArchetype(nextSignature); } - var newEdge = new ArchetypeEdge(archetype, nextArchetype); + var newEdge = new ArchetypeEdge(nextArchetype, archetype); archetype.Edges.Add(componentId, newEdge); nextArchetype.Edges.Add(componentId, newEdge); } - MoveEntity(entityId, record.Row, archetype, nextArchetype); + MoveEntityToHigherArchetype(entityId, record.Row, archetype, nextArchetype); + + // add the new component to the new archetype + var archetypes = ComponentIndex[componentId]; + var archetypeRecord = archetypes[nextArchetype.Id]; + var column = nextArchetype.Components[archetypeRecord.ColumnIndex]; + column.Append(component); } - private void MoveEntity(EntityId entityId, int row, Archetype from, Archetype to) + public void RemoveComponent(EntityId entityId) where T : unmanaged + { + Archetype? nextArchetype; + + var componentId = GetComponentId(); + + var record = EntityIndex[entityId]; + var archetype = record.Archetype; + + 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, record.Row, archetype, nextArchetype, componentId); + } + + private void MoveEntityToHigherArchetype(EntityId entityId, int row, Archetype from, Archetype to) { for (int i = 0; i < from.Components.Count; i += 1) { var componentId = from.Signature[i]; var destinationColumnIndex = ComponentIndex[componentId][to.Id].ColumnIndex; + // copy all components to higher archetype from.Components[i].CopyToEnd(row, to.Components[destinationColumnIndex]); + + // delete row on from archetype from.Components[i].Delete(row); + + if (from.Count > 1) + { + // update row to entity lookup on from archetype + from.RowToEntity[row] = from.RowToEntity[from.Count - 1]; + from.RowToEntity.RemoveAt(from.Count - 1); + EntityIndex[from.RowToEntity[row]] = new Record(from, row); + } } + // update row to entity lookup on to archetype EntityIndex[entityId] = new Record(to, to.Count); + to.RowToEntity.Add(entityId); to.Count += 1; from.Count -= 1; } + + private void MoveEntityToLowerArchetype(EntityId entityId, int row, Archetype from, Archetype to, ComponentId removed) + { + for (int i = 0; i < from.Components.Count; i += 1) + { + var componentId = from.Signature[i]; + + // delete the row + from.Components[i].Delete(row); + + // if this isn't the removed component, copy to the lower archetype + if (componentId != removed) + { + var destinationColumnIndex = ComponentIndex[componentId][to.Id].ColumnIndex; + from.Components[i].CopyToEnd(row, to.Components[destinationColumnIndex]); + + if (from.Count > 0) + { + // update row to entity lookup on from archetype + from.RowToEntity[row] = from.RowToEntity[from.Count - 1]; + from.RowToEntity.RemoveAt(from.Count - 1); + EntityIndex[from.RowToEntity[row]] = new Record(from, row); + } + } + } + + // update row to entity lookup on to archetype + EntityIndex[entityId] = new Record(to, to.Count); + to.RowToEntity.Add(entityId); + + to.Count += 1; + from.Count -= 1; + } + + 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.Components[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); + } } }