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..9090852 100644 --- a/src/Snapshot.cs +++ b/src/Snapshot.cs @@ -8,12 +8,12 @@ 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>(); @@ -21,7 +21,7 @@ public class Snapshot : IDisposable private Dictionary> EntityComponentIndex = new Dictionary>(); - private Dictionary EntityTags = new Dictionary(); + private List EntityTags = new List(); private IdAssigner EntityIdAssigner = new IdAssigner(); @@ -48,30 +48,29 @@ 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 < world.ComponentIndex.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 @@ -107,9 +106,10 @@ public class Snapshot : IDisposable } // restore entity tags - foreach (var (id, s) in EntityTags) + world.EntityTags.Clear(); + foreach (var s in EntityTags) { - world.EntityTags[id] = s; + world.EntityTags.Add(s); } } @@ -125,15 +125,25 @@ 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]); } // copy entity relation index @@ -171,9 +181,10 @@ public class Snapshot : IDisposable } // 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 +204,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,12 +337,12 @@ 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(); } diff --git a/src/TypeId.cs b/src/TypeId.cs index 62bc5d6..92f3598 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,73 @@ 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); + #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..9ef9ff7 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 List RelationIndex = new List(); internal Dictionary> EntityRelationIndex = new Dictionary>(); // 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 readonly List ComponentIndex = new List(); internal Dictionary> EntityComponentIndex = new Dictionary>(); 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); @@ -122,21 +101,20 @@ public class World : IDisposable { EntityRelationIndex.Add(entity, new IndexableSet()); EntityComponentIndex.Add(entity, 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) @@ -147,7 +125,7 @@ public class World : IDisposable var componentStorage = ComponentIndex[componentTypeIndex]; componentStorage.Remove(entity); - foreach (var filter in TypeToFilter[componentTypeIndex]) + foreach (var filter in ComponentTypeToFilter[componentTypeIndex]) { filter.RemoveEntity(entity); } @@ -212,7 +190,7 @@ public class World : IDisposable { EntityComponentIndex[entity].Add(componentStorage.TypeId); - foreach (var filter in TypeToFilter[componentStorage.TypeId]) + foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } @@ -227,7 +205,7 @@ public class World : IDisposable { EntityComponentIndex[entity].Remove(componentStorage.TypeId); - foreach (var filter in TypeToFilter[componentStorage.TypeId]) + foreach (var filter in ComponentTypeToFilter[componentStorage.TypeId]) { filter.Check(entity); } @@ -236,31 +214,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[entityA].Add(new TypeId(RelationTypeIdAssigner.Id)); + EntityRelationIndex[entityB].Add(new TypeId(RelationTypeIdAssigner.Id)); } public void Unrelate(in Entity entityA, in Entity entityB) where T : unmanaged @@ -355,52 +331,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(); } @@ -417,13 +393,13 @@ public class World : IDisposable 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 +432,7 @@ public class World : IDisposable return ComponentIndex < Types.Count; } - public Type Current => World.IdToType[Types[ComponentIndex]]; + public Type Current => ComponentTypeIdToType[Types[ComponentIndex]]; } #endif @@ -466,17 +442,17 @@ 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(); } @@ -497,7 +473,6 @@ public class World : IDisposable } EntityIdAssigner.Dispose(); - TypeIdAssigner.Dispose(); } IsDisposed = true;