diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index f53a8a0..6edc876 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -10,11 +10,13 @@ internal class ComponentStorage : IDisposable internal readonly NativeArray Components; internal readonly NativeArray EntityIDs; internal readonly TypeId TypeId; + internal readonly int ElementSize; private bool IsDisposed; public ComponentStorage(TypeId typeId, int elementSize) { + ElementSize = elementSize; Components = new NativeArray(elementSize); EntityIDs = new NativeArray(); TypeId = typeId; diff --git a/src/RelationStorage.cs b/src/RelationStorage.cs index 40b64ef..d46d645 100644 --- a/src/RelationStorage.cs +++ b/src/RelationStorage.cs @@ -10,6 +10,7 @@ internal class RelationStorage { internal NativeArray Relations; internal NativeArray RelationDatas; + internal int ElementSize; internal Dictionary<(Entity, Entity), int> Indices = new Dictionary<(Entity, Entity), int>(16); internal Dictionary> OutRelationSets = new Dictionary>(16); internal Dictionary> InRelationSets = new Dictionary>(16); @@ -19,6 +20,7 @@ internal class RelationStorage public RelationStorage(int relationDataSize) { + ElementSize = relationDataSize; Relations = new NativeArray(Unsafe.SizeOf<(Entity, Entity)>()); RelationDatas = new NativeArray(relationDataSize); } diff --git a/src/Snapshot.cs b/src/Snapshot.cs index 0421406..25f9bad 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -8,20 +8,20 @@ namespace MoonTools.ECS; // TODO: we should implement a NativeDictionary that can be memcopied public class Snapshot : IDisposable { - private Dictionary ComponentSnapshots = new Dictionary(); + private List ComponentSnapshots = new List(); + // FIXME: we could just have a filter ID private Dictionary> Filters = new Dictionary>(); - private Dictionary RelationSnapshots = - new Dictionary(); + private List RelationSnapshots = new List(); - private Dictionary> EntityRelationIndex = - new Dictionary>(); + private List> EntityRelationIndex = + new List>(); - private Dictionary> EntityComponentIndex = - new Dictionary>(); + private List> EntityComponentIndex = + new List>(); - private Dictionary EntityTags = new Dictionary(); + private List EntityTags = new List(); private IdAssigner EntityIdAssigner = new IdAssigner(); @@ -48,68 +48,71 @@ public class Snapshot : IDisposable // clear all component storages in case any were created after snapshot // FIXME: this can be eliminated via component discovery - foreach (var (typeId, componentStorage) in world.ComponentIndex) + 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 (typeId, relationStorage) in world.RelationIndex) + foreach (var relationStorage in world.RelationIndex) { relationStorage.Clear(); } - // restore components - foreach (var (typeId, componentSnapshot) in ComponentSnapshots) + for (var i = 0; i < ComponentSnapshots.Count; i += 1) { - var componentStorage = world.ComponentIndex[typeId]; - componentSnapshot.Restore(componentStorage); + var componentStorage = world.ComponentIndex[i]; + ComponentSnapshots[i].Restore(componentStorage); } // restore relation state - foreach (var (typeId, relationSnapshot) in RelationSnapshots) + for (var i = 0; i < RelationSnapshots.Count; i += 1) { - var relationStorage = world.RelationIndex[typeId]; - relationSnapshot.Restore(relationStorage); + var relationStorage = world.RelationIndex[i]; + RelationSnapshots[i].Restore(relationStorage); } // restore entity relation index state // FIXME: arghhhh this is so slow - foreach (var (id, relationTypeSet) in world.EntityRelationIndex) + foreach (var relationTypeSet in world.EntityRelationIndex) { relationTypeSet.Clear(); } - foreach (var (id, relationTypeSet) in EntityRelationIndex) + for (var i = 0; i < EntityRelationIndex.Count; i += 1) { + var relationTypeSet = EntityRelationIndex[i]; + foreach (var typeId in relationTypeSet) { - world.EntityRelationIndex[id].Add(typeId); + world.EntityRelationIndex[i].Add(typeId); } } // restore entity component index state // FIXME: arrghghhh this is so slow - foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + foreach (var componentTypeSet in world.EntityComponentIndex) { componentTypeSet.Clear(); } - foreach (var (id, componentTypeSet) in EntityComponentIndex) + for (var i = 0; i < EntityComponentIndex.Count; i += 1) { + var componentTypeSet = EntityComponentIndex[i]; + foreach (var typeId in componentTypeSet) { - world.EntityComponentIndex[id].Add(typeId); + world.EntityComponentIndex[i].Add(typeId); } } // restore entity tags - foreach (var (id, s) in EntityTags) + for (var i = 0; i < EntityTags.Count; i += 1) { - world.EntityTags[id] = s; + world.EntityTags[i] = EntityTags[i]; } } @@ -125,55 +128,68 @@ public class Snapshot : IDisposable } // copy components - foreach (var (typeId, componentStorage) in world.ComponentIndex) + for (var i = ComponentSnapshots.Count; i < world.ComponentIndex.Count; i += 1) { - TakeComponentSnapshot(typeId, componentStorage); + 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 - foreach (var (typeId, relationStorage) in world.RelationIndex) + for (var i = RelationSnapshots.Count; i < world.RelationIndex.Count; i += 1) { - TakeRelationSnapshot(typeId, relationStorage); + 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()); + } + + for (var i = EntityRelationIndex.Count; i < world.EntityRelationIndex.Count; i += 1) + { + EntityRelationIndex.Add(new IndexableSet()); } // copy entity relation index // FIXME: arghhhh this is so slow - foreach (var (id, relationTypeSet) in world.EntityRelationIndex) + for (var i = 0; i < world.EntityRelationIndex.Count; i += 1) { - if (!EntityRelationIndex.ContainsKey(id)) - { - EntityRelationIndex.Add(id, new IndexableSet()); - } + EntityRelationIndex[i].Clear(); - EntityRelationIndex[id].Clear(); - - foreach (var typeId in relationTypeSet) + foreach (var typeId in world.EntityRelationIndex[i]) { - EntityRelationIndex[id].Add(typeId); + EntityRelationIndex[i].Add(typeId); } } // copy entity component index // FIXME: arghhhh this is so slow - foreach (var (id, componentTypeSet) in world.EntityComponentIndex) + for (var i = 0; i < world.EntityComponentIndex.Count; i += 1) { - if (!EntityComponentIndex.ContainsKey(id)) - { - EntityComponentIndex.Add(id, new IndexableSet()); - } + EntityComponentIndex[i].Clear(); - EntityComponentIndex[id].Clear(); - - foreach (var typeId in componentTypeSet) + foreach (var typeId in world.EntityComponentIndex[i]) { - EntityComponentIndex[id].Add(typeId); + EntityComponentIndex[i].Add(typeId); } } // copy entity tags - foreach (var (id, s) in world.EntityTags) + EntityTags.Clear(); + foreach (var s in world.EntityTags) { - EntityTags[id] = s; + EntityTags.Add(s); } } @@ -193,28 +209,6 @@ public class Snapshot : IDisposable } } - private void TakeComponentSnapshot(TypeId typeId, ComponentStorage componentStorage) - { - if (!ComponentSnapshots.TryGetValue(typeId, out var componentSnapshot)) - { - componentSnapshot = new ComponentSnapshot(componentStorage.Components.ElementSize); - ComponentSnapshots.Add(typeId, componentSnapshot); - } - - componentSnapshot.Take(componentStorage); - } - - 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 ComponentSnapshot : IDisposable { private readonly NativeArray Components; @@ -348,22 +342,22 @@ public class Snapshot : IDisposable { if (disposing) { - foreach (var componentSnapshot in ComponentSnapshots.Values) + foreach (var componentSnapshot in ComponentSnapshots) { componentSnapshot.Dispose(); } - foreach (var relationSnapshot in RelationSnapshots.Values) + foreach (var relationSnapshot in RelationSnapshots) { relationSnapshot.Dispose(); } - foreach (var componentSet in EntityComponentIndex.Values) + foreach (var componentSet in EntityComponentIndex) { componentSet.Dispose(); } - foreach (var relationSet in EntityRelationIndex.Values) + foreach (var relationSet in EntityRelationIndex) { relationSet.Dispose(); } diff --git a/src/TypeId.cs b/src/TypeId.cs index 62bc5d6..53c785f 100644 --- a/src/TypeId.cs +++ b/src/TypeId.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace MoonTools.ECS; @@ -8,4 +9,74 @@ public readonly record struct TypeId(uint Value) : IComparable { return Value.CompareTo(other.Value); } + + public static implicit operator int(TypeId typeId) + { + return (int) typeId.Value; + } +} + +public class ComponentTypeIdAssigner +{ + protected static ushort Counter; +} + +public class ComponentTypeIdAssigner : ComponentTypeIdAssigner +{ + public static readonly ushort Id; + public static readonly int Size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static ComponentTypeIdAssigner() + { + Id = Counter++; + Size = Unsafe.SizeOf(); + + World.ComponentTypeElementSizes.Add(Size); + + #if DEBUG + World.ComponentTypeToId[typeof(T)] = new TypeId(Id); + World.ComponentTypeIdToType.Add(typeof(T)); + #endif + } +} + +public class RelationTypeIdAssigner +{ + protected static ushort Counter; +} + +public class RelationTypeIdAssigner : RelationTypeIdAssigner +{ + public static readonly ushort Id; + public static readonly int Size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static RelationTypeIdAssigner() + { + Id = Counter++; + Size = Unsafe.SizeOf(); + + World.RelationTypeElementSizes.Add(Size); + } +} + +public class MessageTypeIdAssigner +{ + protected static ushort Counter; +} + +public class MessageTypeIdAssigner : MessageTypeIdAssigner +{ + public static readonly ushort Id; + public static readonly int Size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static MessageTypeIdAssigner() + { + Id = Counter++; + Size = Unsafe.SizeOf(); + + World.MessageTypeElementSizes.Add(Size); + } } diff --git a/src/World.cs b/src/World.cs index 70aad7d..753d0b7 100644 --- a/src/World.cs +++ b/src/World.cs @@ -7,85 +7,64 @@ namespace MoonTools.ECS; public class World : IDisposable { - // Get TypeId from a Type - private readonly Dictionary TypeToId = new Dictionary(); - #if DEBUG - private Dictionary IdToType = new Dictionary(); + // TODO: is there a smarter way to do this? + internal static Dictionary ComponentTypeToId = new Dictionary(); + internal static List ComponentTypeIdToType = new List(); #endif - // Get element size from a TypeId - private readonly Dictionary ElementSizes = new Dictionary(); + internal static List ComponentTypeElementSizes = new List(); + internal static List RelationTypeElementSizes = new List(); + internal static List MessageTypeElementSizes = new List(); // Filters internal readonly Dictionary FilterIndex = new Dictionary(); - private readonly Dictionary> TypeToFilter = new Dictionary>(); + private readonly List> ComponentTypeToFilter = new List>(); // TODO: can we make the tag an native array of chars at some point? - internal Dictionary EntityTags = new Dictionary(); + internal List EntityTags = new List(); // Relation Storages - internal Dictionary RelationIndex = new Dictionary(); - internal Dictionary> EntityRelationIndex = new Dictionary>(); + internal List RelationIndex = new List(); + internal List> EntityRelationIndex = new List>(); // Message Storages - private Dictionary MessageIndex = new Dictionary(); + private List MessageIndex = new List(); public FilterBuilder FilterBuilder => new FilterBuilder(this); - internal readonly Dictionary ComponentIndex = new Dictionary(); - internal Dictionary> EntityComponentIndex = new Dictionary>(); + internal readonly List ComponentIndex = new List(); + internal List> EntityComponentIndex = new List>(); internal IdAssigner EntityIdAssigner = new IdAssigner(); - private IdAssigner TypeIdAssigner = new IdAssigner(); private bool IsDisposed; - internal TypeId GetTypeId() 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()); - -#if DEBUG - IdToType.Add(typeId, typeof(T)); -#endif - - return typeId; - } - internal TypeId GetComponentTypeId() where T : unmanaged { - var typeId = GetTypeId(); - if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) + var typeId = new TypeId(ComponentTypeIdAssigner.Id); + if (typeId < ComponentIndex.Count) { return typeId; } - componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); - ComponentIndex.Add(typeId, componentStorage); - TypeToFilter.Add(typeId, new List()); + // 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()); + } + return typeId; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ComponentStorage GetComponentStorage() where T : unmanaged { - var typeId = GetTypeId(); - if (ComponentIndex.TryGetValue(typeId, out var componentStorage)) - { - return componentStorage; - } - - componentStorage = new ComponentStorage(typeId, ElementSizes[typeId]); - ComponentIndex.Add(typeId, componentStorage); - TypeToFilter.Add(typeId, new List()); - return componentStorage; + var typeId = GetComponentTypeId(); + return ComponentIndex[typeId]; } // FILTERS @@ -98,12 +77,12 @@ public class World : IDisposable foreach (var typeId in signature.Included) { - TypeToFilter[typeId].Add(filter); + ComponentTypeToFilter[(int) typeId.Value].Add(filter); } foreach (var typeId in signature.Excluded) { - TypeToFilter[typeId].Add(filter); + ComponentTypeToFilter[(int) typeId.Value].Add(filter); } FilterIndex.Add(signature, filter); @@ -118,50 +97,52 @@ public class World : IDisposable { var entity = new Entity(EntityIdAssigner.Assign()); - if (!EntityComponentIndex.ContainsKey(entity)) + if (entity.ID == EntityComponentIndex.Count) { - EntityRelationIndex.Add(entity, new IndexableSet()); - EntityComponentIndex.Add(entity, new IndexableSet()); + EntityRelationIndex.Add(new IndexableSet()); + EntityComponentIndex.Add(new IndexableSet()); + EntityTags.Add(tag); } - EntityTags[entity] = tag; - return entity; } public void Tag(Entity entity, string tag) { - EntityTags[entity] = tag; + EntityTags[(int) entity.ID] = tag; } public string GetTag(Entity entity) { - return EntityTags[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 EntityComponentIndex[entity]) + foreach (var componentTypeIndex in componentSet) { var componentStorage = ComponentIndex[componentTypeIndex]; componentStorage.Remove(entity); - foreach (var filter in TypeToFilter[componentTypeIndex]) + foreach (var filter in ComponentTypeToFilter[componentTypeIndex]) { filter.RemoveEntity(entity); } } // remove all relations from storage - foreach (var relationTypeIndex in EntityRelationIndex[entity]) + foreach (var relationTypeIndex in relationSet) { var relationStorage = RelationIndex[relationTypeIndex]; relationStorage.RemoveEntity(entity); } - EntityComponentIndex[entity].Clear(); - EntityRelationIndex[entity].Clear(); + componentSet.Clear(); + relationSet.Clear(); // recycle ID EntityIdAssigner.Unassign(entity.ID); @@ -177,7 +158,7 @@ public class World : IDisposable internal bool Has(in Entity entity, in TypeId typeId) { - return EntityComponentIndex[entity].Contains(typeId); + return EntityComponentIndex[(int) entity.ID].Contains(typeId); } public bool Some() where T : unmanaged @@ -210,9 +191,9 @@ public class World : IDisposable if (!componentStorage.Set(entity, component)) { - EntityComponentIndex[entity].Add(componentStorage.TypeId); + EntityComponentIndex[(int) entity.ID].Add(componentStorage.TypeId); - foreach (var filter in TypeToFilter[componentStorage.TypeId]) + foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } @@ -225,9 +206,9 @@ public class World : IDisposable if (componentStorage.Remove(entity)) { - EntityComponentIndex[entity].Remove(componentStorage.TypeId); + EntityComponentIndex[(int) entity.ID].Remove(componentStorage.TypeId); - foreach (var filter in TypeToFilter[componentStorage.TypeId]) + foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } @@ -236,31 +217,29 @@ public class World : IDisposable // RELATIONS - private RelationStorage RegisterRelationType(TypeId typeId) - { - var relationStorage = new RelationStorage(ElementSizes[typeId]); - RelationIndex.Add(typeId, relationStorage); - return relationStorage; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private RelationStorage GetRelationStorage() where T : unmanaged { - var typeId = GetTypeId(); - if (RelationIndex.TryGetValue(typeId, out var relationStorage)) + var typeId = new TypeId(RelationTypeIdAssigner.Id); + if (typeId.Value < RelationIndex.Count) { - return relationStorage; + return RelationIndex[typeId]; } - return RegisterRelationType(typeId); + for (var i = RelationIndex.Count; i <= typeId; i += 1) + { + RelationIndex.Add(new RelationStorage(RelationTypeElementSizes[i])); + } + + return RelationIndex[typeId]; } public void Relate(in Entity entityA, in Entity entityB, in T relation) where T : unmanaged { var relationStorage = GetRelationStorage(); relationStorage.Set(entityA, entityB, relation); - EntityRelationIndex[entityA].Add(TypeToId[typeof(T)]); - EntityRelationIndex[entityB].Add(TypeToId[typeof(T)]); + EntityRelationIndex[(int) entityA.ID].Add(new TypeId(RelationTypeIdAssigner.Id)); + EntityRelationIndex[(int) entityB.ID].Add(new TypeId(RelationTypeIdAssigner.Id)); } public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged @@ -355,52 +334,52 @@ public class World : IDisposable // MESSAGES - private TypeId GetMessageTypeId() where T : unmanaged + private MessageStorage GetMessageStorage() where T : unmanaged { - var typeId = GetTypeId(); + var typeId = new TypeId(MessageTypeIdAssigner.Id); - if (!MessageIndex.ContainsKey(typeId)) + if (typeId < MessageIndex.Count) { - MessageIndex.Add(typeId, new MessageStorage(Unsafe.SizeOf())); + return MessageIndex[typeId]; } - return typeId; + for (var i = MessageIndex.Count; i <= typeId; i += 1) + { + MessageIndex.Add(new MessageStorage(MessageTypeElementSizes[i])); + } + + return MessageIndex[typeId]; } public void Send(in T message) where T : unmanaged { - var typeId = GetMessageTypeId(); - MessageIndex[typeId].Add(message); + GetMessageStorage().Add(message); } public bool SomeMessage() where T : unmanaged { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].Some(); + return GetMessageStorage().Some(); } public ReadOnlySpan ReadMessages() where T : unmanaged { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].All(); + return GetMessageStorage().All(); } public T ReadMessage() where T : unmanaged { - var typeId = GetMessageTypeId(); - return MessageIndex[typeId].First(); + return GetMessageStorage().First(); } public void ClearMessages() where T : unmanaged { - var typeId = GetMessageTypeId(); - MessageIndex[typeId].Clear(); + GetMessageStorage().Clear(); } // TODO: temporary component storage? public void FinishUpdate() { - foreach (var (_, messageStorage) in MessageIndex) + foreach (var messageStorage in MessageIndex) { messageStorage.Clear(); } @@ -412,18 +391,18 @@ public class World : IDisposable #if DEBUG public ComponentTypeEnumerator Debug_GetAllComponentTypes(Entity entity) { - return new ComponentTypeEnumerator(this, EntityComponentIndex[entity]); + return new ComponentTypeEnumerator(this, EntityComponentIndex[(int) entity.ID]); } public IEnumerable Debug_GetEntities(Type componentType) { - var storage = ComponentIndex[TypeToId[componentType]]; + var storage = ComponentIndex[ComponentTypeToId[componentType]]; return storage.Debug_GetEntities(); } public IEnumerable Debug_SearchComponentType(string typeString) { - foreach (var type in TypeToId.Keys) + foreach (var type in ComponentTypeToId.Keys) { if (type.ToString().ToLower().Contains(typeString.ToLower())) { @@ -456,7 +435,7 @@ public class World : IDisposable return ComponentIndex < Types.Count; } - public Type Current => World.IdToType[Types[ComponentIndex]]; + public Type Current => ComponentTypeIdToType[Types[ComponentIndex]]; } #endif @@ -466,29 +445,29 @@ public class World : IDisposable { if (disposing) { - foreach (var componentStorage in ComponentIndex.Values) + foreach (var componentStorage in ComponentIndex) { componentStorage.Dispose(); } - foreach (var relationStorage in RelationIndex.Values) + foreach (var relationStorage in RelationIndex) { relationStorage.Dispose(); } - foreach (var messageStorage in MessageIndex.Values) + foreach (var messageStorage in MessageIndex) { messageStorage.Dispose(); } - foreach (var typeSet in EntityComponentIndex.Values) + foreach (var componentSet in EntityComponentIndex) { - typeSet.Dispose(); + componentSet.Dispose(); } - foreach (var typeSet in EntityRelationIndex.Values) + foreach (var relationSet in EntityRelationIndex) { - typeSet.Dispose(); + relationSet.Dispose(); } foreach (var filter in FilterIndex.Values) @@ -497,7 +476,6 @@ public class World : IDisposable } EntityIdAssigner.Dispose(); - TypeIdAssigner.Dispose(); } IsDisposed = true;