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();