diff --git a/MoonTools.ECS.csproj b/MoonTools.ECS.csproj index 8867478..e7a1568 100644 --- a/MoonTools.ECS.csproj +++ b/MoonTools.ECS.csproj @@ -4,6 +4,7 @@ net6.0 enable x64 + true diff --git a/src/ComponentDepot.cs b/src/ComponentDepot.cs index bd43661..4c063ea 100644 --- a/src/ComponentDepot.cs +++ b/src/ComponentDepot.cs @@ -11,13 +11,13 @@ namespace MoonTools.ECS private Dictionary> typeToFilterSignatures = new Dictionary>(); - private Dictionary> entityComponentMap = new Dictionary>(); - #if DEBUG private Dictionary singleComponentFilters = new Dictionary(); #endif - internal void Register() where TComponent : struct + private HashSet TypesWithDisabledSerialization = new HashSet(); + + internal void Register() where TComponent : unmanaged { if (!storages.ContainsKey(typeof(TComponent))) { @@ -33,19 +33,19 @@ namespace MoonTools.ECS return storages[type]; } - private ComponentStorage Lookup() where TComponent : struct + private ComponentStorage Lookup() where TComponent : unmanaged { // TODO: is it possible to optimize this? Register(); return (ComponentStorage) storages[typeof(TComponent)]; } - public bool Some() where TComponent : struct + public bool Some() where TComponent : unmanaged { return Lookup().Any(); } - public bool Has(int entityID) where TComponent : struct + public bool Has(int entityID) where TComponent : unmanaged { return Lookup().Has(entityID); } @@ -55,29 +55,22 @@ namespace MoonTools.ECS return Lookup(type).Has(entityID); } - public ref readonly TComponent Get(int entityID) where TComponent : struct + public ref readonly TComponent Get(int entityID) where TComponent : unmanaged { return ref Lookup().Get(entityID); } - public ref readonly TComponent Get() where TComponent : struct + public ref readonly TComponent Get() where TComponent : unmanaged { return ref Lookup().Get(); } - public void Set(int entityID, in TComponent component) where TComponent : struct + public void Set(int entityID, in TComponent component) where TComponent : unmanaged { - Lookup().Set(entityID, component); - - if (!entityComponentMap.ContainsKey(entityID)) - { - entityComponentMap.Add(entityID, new HashSet()); - } - - var notFound = entityComponentMap[entityID].Add(typeof(TComponent)); + var existed = Lookup().Set(entityID, component); // update filters - if (notFound) + if (!existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { @@ -89,24 +82,22 @@ namespace MoonTools.ECS } } - public Entity GetSingletonEntity() where TComponent : struct + public Entity GetSingletonEntity() where TComponent : unmanaged { return Lookup().FirstEntity(); } - public ReadOnlySpan ReadComponents() where TComponent : struct + public ReadOnlySpan ReadComponents() where TComponent : unmanaged { return Lookup().AllComponents(); } private void Remove(Type type, int entityID) { - Lookup(type).Remove(entityID); - - var found = entityComponentMap[entityID].Remove(type); + var existed = Lookup(type).Remove(entityID); // update filters - if (found) + if (existed) { if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures)) { @@ -118,14 +109,12 @@ namespace MoonTools.ECS } } - public void Remove(int entityID) where TComponent : struct + public void Remove(int entityID) where TComponent : unmanaged { - Lookup().Remove(entityID); - - var found = entityComponentMap[entityID].Remove(typeof(TComponent)); + var existed = Lookup().Remove(entityID); // update filters - if (found) + if (existed) { if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) { @@ -137,16 +126,12 @@ namespace MoonTools.ECS } } + // TODO: is there some way to optimize this without complicating serialization? public void OnEntityDestroy(int entityID) { - if (entityComponentMap.ContainsKey(entityID)) + foreach (var type in storages.Keys) { - foreach (var type in entityComponentMap[entityID]) - { - Remove(type, entityID); - } - - entityComponentMap.Remove(entityID); + Remove(type, entityID); } } @@ -231,6 +216,11 @@ namespace MoonTools.ECS filterSignatureToEntityIDs[filterSignature].Add(entityID); } + public void DisableSerialization() where TComponent : unmanaged + { + TypesWithDisabledSerialization.Add(typeof(TComponent)); + } + public ComponentDepotState CreateState() { return new ComponentDepotState(); @@ -238,12 +228,30 @@ namespace MoonTools.ECS public void Serialize(ComponentDepotState state) { + // FIXME: this is creating garbage state.StorageStates.Clear(); foreach (var (type, storage) in storages) { - var storageState = storage.CreateState(); - storage.Serialize(storageState); - state.StorageStates.Add(type, storageState); + // FIXME: we could cache this + if (!TypesWithDisabledSerialization.Contains(type)) + { + var storageState = storage.CreateState(); + storage.Serialize(storageState); + state.StorageStates.Add(type, storageState); + } + } + + // FIXME: this is creating garbage + state.FilterStates.Clear(); + foreach (var (signature, set) in filterSignatureToEntityIDs) + { + // FIXME: we could cache this + if (!signature.Included.Overlaps(TypesWithDisabledSerialization) && !signature.Excluded.Overlaps(TypesWithDisabledSerialization)) + { + var setState = new IndexableSetState(set.Count); + set.Save(setState); + state.FilterStates[signature] = setState; + } } } @@ -253,6 +261,11 @@ namespace MoonTools.ECS { storages[type].Deserialize(storageState); } + + foreach (var (signature, setState) in state.FilterStates) + { + filterSignatureToEntityIDs[signature].Load(setState); + } } #if DEBUG diff --git a/src/ComponentDepotState.cs b/src/ComponentDepotState.cs index 78481d1..b137f24 100644 --- a/src/ComponentDepotState.cs +++ b/src/ComponentDepotState.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; namespace MoonTools.ECS { - public class ComponentDepotState - { + public class ComponentDepotState + { public Dictionary StorageStates = new Dictionary(); + public Dictionary> FilterStates = new Dictionary>(); } } diff --git a/src/ComponentStorage.cs b/src/ComponentStorage.cs index 3020bb1..e19af89 100644 --- a/src/ComponentStorage.cs +++ b/src/ComponentStorage.cs @@ -7,14 +7,14 @@ namespace MoonTools.ECS internal abstract class ComponentStorage { public abstract bool Has(int entityID); - public abstract void Remove(int entityID); + public abstract bool Remove(int entityID); public abstract object Debug_Get(int entityID); public abstract ComponentStorageState CreateState(); public abstract void Serialize(ComponentStorageState state); public abstract void Deserialize(ComponentStorageState state); } - internal class ComponentStorage : ComponentStorage where TComponent : struct + internal class ComponentStorage : ComponentStorage where TComponent : unmanaged { private int nextID; private readonly Dictionary entityIDToStorageIndex = new Dictionary(16); @@ -52,8 +52,11 @@ namespace MoonTools.ECS return ref components[0]; } - public void Set(int entityID, in TComponent component) + // Returns true if the entity already had this component. + public bool Set(int entityID, in TComponent component) { + bool result = true; + if (!entityIDToStorageIndex.ContainsKey(entityID)) { var index = nextID; @@ -67,12 +70,17 @@ namespace MoonTools.ECS entityIDToStorageIndex[entityID] = index; entityIDs[index] = entityID; + + result = false; } components[entityIDToStorageIndex[entityID]] = component; + + return result; } - public override void Remove(int entityID) + // Returns true if the entity had this component. + public override bool Remove(int entityID) { if (entityIDToStorageIndex.ContainsKey(entityID)) { @@ -91,7 +99,11 @@ namespace MoonTools.ECS } nextID -= 1; + + return true; } + + return false; } public void Clear() @@ -137,6 +149,8 @@ namespace MoonTools.ECS { serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value; } + + serializedComponentStorage.Count = nextID; } public override void Deserialize(ComponentStorageState serializedComponentStorage) @@ -149,6 +163,8 @@ namespace MoonTools.ECS { entityIDToStorageIndex[kvp.Key] = kvp.Value; } + + nextID = serializedComponentStorage.Count; } } } diff --git a/src/ComponentStorageState.cs b/src/ComponentStorageState.cs index dd98063..dfc3e52 100644 --- a/src/ComponentStorageState.cs +++ b/src/ComponentStorageState.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Runtime.InteropServices; namespace MoonTools.ECS { @@ -11,12 +10,12 @@ namespace MoonTools.ECS public byte[] EntityIDs; public byte[] Components; - public static ComponentStorageState Create(int count) + public unsafe static ComponentStorageState Create(int count) where TComponent : unmanaged { return new ComponentStorageState( count, - count * Marshal.SizeOf(), - count * Marshal.SizeOf() + count * sizeof(int), + count * sizeof(TComponent) ); } diff --git a/src/FilterBuilder.cs b/src/FilterBuilder.cs index 4ba782d..be75edc 100644 --- a/src/FilterBuilder.cs +++ b/src/FilterBuilder.cs @@ -23,14 +23,14 @@ namespace MoonTools.ECS Excluded = excluded; } - public FilterBuilder Include() where TComponent : struct + public FilterBuilder Include() where TComponent : unmanaged { ComponentDepot.Register(); Included.Add(typeof(TComponent)); return new FilterBuilder(ComponentDepot, Included, Excluded); } - public FilterBuilder Exclude() where TComponent : struct + public FilterBuilder Exclude() where TComponent : unmanaged { ComponentDepot.Register(); Excluded.Add(typeof(TComponent)); diff --git a/src/FilterSignature.cs b/src/FilterSignature.cs index 4857119..7508b36 100644 --- a/src/FilterSignature.cs +++ b/src/FilterSignature.cs @@ -7,8 +7,8 @@ namespace MoonTools.ECS { private const int HASH_FACTOR = 97; - public HashSet Included; - public HashSet Excluded; + public readonly HashSet Included; + public readonly HashSet Excluded; public FilterSignature(HashSet included, HashSet excluded) { diff --git a/src/IndexableSet.cs b/src/IndexableSet.cs index cf8f56c..6057cf1 100644 --- a/src/IndexableSet.cs +++ b/src/IndexableSet.cs @@ -1,10 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace MoonTools.ECS { - internal class IndexableSet : IEnumerable where T : notnull + internal class IndexableSet : IEnumerable where T : unmanaged { private Dictionary indices; private T[] array; @@ -78,5 +79,38 @@ namespace MoonTools.ECS yield return array[i]; } } + + public void Save(IndexableSetState state) + { + state.Indices.Clear(); + foreach (var (key, value) in indices) + { + state.Indices[key] = value; + } + + ReadOnlySpan arrayBytes = MemoryMarshal.Cast(array); + + if (arrayBytes.Length > state.Array.Length) + { + Array.Resize(ref state.Array, arrayBytes.Length); + } + + arrayBytes.CopyTo(state.Array); + + state.Count = Count; + } + + public void Load(IndexableSetState state) + { + indices.Clear(); + foreach (var kvp in state.Indices) + { + indices[kvp.Key] = kvp.Value; + } + + state.Array.CopyTo(MemoryMarshal.Cast(array)); + + Count = state.Count; + } } } diff --git a/src/IndexableSetState.cs b/src/IndexableSetState.cs new file mode 100644 index 0000000..43b7b79 --- /dev/null +++ b/src/IndexableSetState.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace MoonTools.ECS +{ + public class IndexableSetState where T : unmanaged + { + public int Count; + public Dictionary Indices; + public byte[] Array; + + public unsafe IndexableSetState(int count) + { + Count = count; + Indices = new Dictionary(count); + Array = new byte[sizeof(T) * count]; + } + } +} diff --git a/src/World.cs b/src/World.cs index 1582846..a5dbe81 100644 --- a/src/World.cs +++ b/src/World.cs @@ -12,12 +12,12 @@ return EntityStorage.Create(); } - public void Set(Entity entity, in TComponent component) where TComponent : struct + public void Set(Entity entity, in TComponent component) where TComponent : unmanaged { ComponentDepot.Set(entity.ID, component); } - public void Send(in TMessage message) where TMessage : struct + public void Send(in TMessage message) where TMessage : unmanaged { MessageDepot.Add(message); } @@ -27,6 +27,11 @@ MessageDepot.Clear(); } + public void DisableSerialization() where TComponent : unmanaged + { + ComponentDepot.DisableSerialization(); + } + public ComponentDepotState Serialize() { var state = ComponentDepot.CreateState();