more serialization changes

pull/2/head
cosmonaut 2022-04-27 14:13:56 -07:00
parent b30d53e71d
commit be95e80265
10 changed files with 144 additions and 56 deletions

View File

@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -11,13 +11,13 @@ namespace MoonTools.ECS
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>(); private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
#if DEBUG #if DEBUG
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>(); private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
#endif #endif
internal void Register<TComponent>() where TComponent : struct private HashSet<Type> TypesWithDisabledSerialization = new HashSet<Type>();
internal void Register<TComponent>() where TComponent : unmanaged
{ {
if (!storages.ContainsKey(typeof(TComponent))) if (!storages.ContainsKey(typeof(TComponent)))
{ {
@ -33,19 +33,19 @@ namespace MoonTools.ECS
return storages[type]; return storages[type];
} }
private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : struct private ComponentStorage<TComponent> Lookup<TComponent>() where TComponent : unmanaged
{ {
// TODO: is it possible to optimize this? // TODO: is it possible to optimize this?
Register<TComponent>(); Register<TComponent>();
return (ComponentStorage<TComponent>) storages[typeof(TComponent)]; return (ComponentStorage<TComponent>) storages[typeof(TComponent)];
} }
public bool Some<TComponent>() where TComponent : struct public bool Some<TComponent>() where TComponent : unmanaged
{ {
return Lookup<TComponent>().Any(); return Lookup<TComponent>().Any();
} }
public bool Has<TComponent>(int entityID) where TComponent : struct public bool Has<TComponent>(int entityID) where TComponent : unmanaged
{ {
return Lookup<TComponent>().Has(entityID); return Lookup<TComponent>().Has(entityID);
} }
@ -55,29 +55,22 @@ namespace MoonTools.ECS
return Lookup(type).Has(entityID); return Lookup(type).Has(entityID);
} }
public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : struct public ref readonly TComponent Get<TComponent>(int entityID) where TComponent : unmanaged
{ {
return ref Lookup<TComponent>().Get(entityID); return ref Lookup<TComponent>().Get(entityID);
} }
public ref readonly TComponent Get<TComponent>() where TComponent : struct public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged
{ {
return ref Lookup<TComponent>().Get(); return ref Lookup<TComponent>().Get();
} }
public void Set<TComponent>(int entityID, in TComponent component) where TComponent : struct public void Set<TComponent>(int entityID, in TComponent component) where TComponent : unmanaged
{ {
Lookup<TComponent>().Set(entityID, component); var existed = Lookup<TComponent>().Set(entityID, component);
if (!entityComponentMap.ContainsKey(entityID))
{
entityComponentMap.Add(entityID, new HashSet<Type>());
}
var notFound = entityComponentMap[entityID].Add(typeof(TComponent));
// update filters // update filters
if (notFound) if (!existed)
{ {
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures))
{ {
@ -89,24 +82,22 @@ namespace MoonTools.ECS
} }
} }
public Entity GetSingletonEntity<TComponent>() where TComponent : struct public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{ {
return Lookup<TComponent>().FirstEntity(); return Lookup<TComponent>().FirstEntity();
} }
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{ {
return Lookup<TComponent>().AllComponents(); return Lookup<TComponent>().AllComponents();
} }
private void Remove(Type type, int entityID) private void Remove(Type type, int entityID)
{ {
Lookup(type).Remove(entityID); var existed = Lookup(type).Remove(entityID);
var found = entityComponentMap[entityID].Remove(type);
// update filters // update filters
if (found) if (existed)
{ {
if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures)) if (typeToFilterSignatures.TryGetValue(type, out var filterSignatures))
{ {
@ -118,14 +109,12 @@ namespace MoonTools.ECS
} }
} }
public void Remove<TComponent>(int entityID) where TComponent : struct public void Remove<TComponent>(int entityID) where TComponent : unmanaged
{ {
Lookup<TComponent>().Remove(entityID); var existed = Lookup<TComponent>().Remove(entityID);
var found = entityComponentMap[entityID].Remove(typeof(TComponent));
// update filters // update filters
if (found) if (existed)
{ {
if (typeToFilterSignatures.TryGetValue(typeof(TComponent), out var filterSignatures)) 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) public void OnEntityDestroy(int entityID)
{ {
if (entityComponentMap.ContainsKey(entityID)) foreach (var type in storages.Keys)
{ {
foreach (var type in entityComponentMap[entityID]) Remove(type, entityID);
{
Remove(type, entityID);
}
entityComponentMap.Remove(entityID);
} }
} }
@ -231,6 +216,11 @@ namespace MoonTools.ECS
filterSignatureToEntityIDs[filterSignature].Add(entityID); filterSignatureToEntityIDs[filterSignature].Add(entityID);
} }
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
TypesWithDisabledSerialization.Add(typeof(TComponent));
}
public ComponentDepotState CreateState() public ComponentDepotState CreateState()
{ {
return new ComponentDepotState(); return new ComponentDepotState();
@ -238,12 +228,30 @@ namespace MoonTools.ECS
public void Serialize(ComponentDepotState state) public void Serialize(ComponentDepotState state)
{ {
// FIXME: this is creating garbage
state.StorageStates.Clear(); state.StorageStates.Clear();
foreach (var (type, storage) in storages) foreach (var (type, storage) in storages)
{ {
var storageState = storage.CreateState(); // FIXME: we could cache this
storage.Serialize(storageState); if (!TypesWithDisabledSerialization.Contains(type))
state.StorageStates.Add(type, storageState); {
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<int>(set.Count);
set.Save(setState);
state.FilterStates[signature] = setState;
}
} }
} }
@ -253,6 +261,11 @@ namespace MoonTools.ECS
{ {
storages[type].Deserialize(storageState); storages[type].Deserialize(storageState);
} }
foreach (var (signature, setState) in state.FilterStates)
{
filterSignatureToEntityIDs[signature].Load(setState);
}
} }
#if DEBUG #if DEBUG

View File

@ -3,8 +3,9 @@ using System.Collections.Generic;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
public class ComponentDepotState public class ComponentDepotState
{ {
public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>(); public Dictionary<Type, ComponentStorageState> StorageStates = new Dictionary<Type, ComponentStorageState>();
public Dictionary<FilterSignature, IndexableSetState<int>> FilterStates = new Dictionary<FilterSignature, IndexableSetState<int>>();
} }
} }

View File

@ -7,14 +7,14 @@ namespace MoonTools.ECS
internal abstract class ComponentStorage internal abstract class ComponentStorage
{ {
public abstract bool Has(int entityID); 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 object Debug_Get(int entityID);
public abstract ComponentStorageState CreateState(); public abstract ComponentStorageState CreateState();
public abstract void Serialize(ComponentStorageState state); public abstract void Serialize(ComponentStorageState state);
public abstract void Deserialize(ComponentStorageState state); public abstract void Deserialize(ComponentStorageState state);
} }
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : struct internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
{ {
private int nextID; private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16); private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16);
@ -52,8 +52,11 @@ namespace MoonTools.ECS
return ref components[0]; 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)) if (!entityIDToStorageIndex.ContainsKey(entityID))
{ {
var index = nextID; var index = nextID;
@ -67,12 +70,17 @@ namespace MoonTools.ECS
entityIDToStorageIndex[entityID] = index; entityIDToStorageIndex[entityID] = index;
entityIDs[index] = entityID; entityIDs[index] = entityID;
result = false;
} }
components[entityIDToStorageIndex[entityID]] = component; 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)) if (entityIDToStorageIndex.ContainsKey(entityID))
{ {
@ -91,7 +99,11 @@ namespace MoonTools.ECS
} }
nextID -= 1; nextID -= 1;
return true;
} }
return false;
} }
public void Clear() public void Clear()
@ -137,6 +149,8 @@ namespace MoonTools.ECS
{ {
serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value; serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value;
} }
serializedComponentStorage.Count = nextID;
} }
public override void Deserialize(ComponentStorageState serializedComponentStorage) public override void Deserialize(ComponentStorageState serializedComponentStorage)
@ -149,6 +163,8 @@ namespace MoonTools.ECS
{ {
entityIDToStorageIndex[kvp.Key] = kvp.Value; entityIDToStorageIndex[kvp.Key] = kvp.Value;
} }
nextID = serializedComponentStorage.Count;
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
@ -11,12 +10,12 @@ namespace MoonTools.ECS
public byte[] EntityIDs; public byte[] EntityIDs;
public byte[] Components; public byte[] Components;
public static ComponentStorageState Create<TComponent>(int count) public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged
{ {
return new ComponentStorageState( return new ComponentStorageState(
count, count,
count * Marshal.SizeOf<int>(), count * sizeof(int),
count * Marshal.SizeOf<TComponent>() count * sizeof(TComponent)
); );
} }

View File

@ -23,14 +23,14 @@ namespace MoonTools.ECS
Excluded = excluded; Excluded = excluded;
} }
public FilterBuilder Include<TComponent>() where TComponent : struct public FilterBuilder Include<TComponent>() where TComponent : unmanaged
{ {
ComponentDepot.Register<TComponent>(); ComponentDepot.Register<TComponent>();
Included.Add(typeof(TComponent)); Included.Add(typeof(TComponent));
return new FilterBuilder(ComponentDepot, Included, Excluded); return new FilterBuilder(ComponentDepot, Included, Excluded);
} }
public FilterBuilder Exclude<TComponent>() where TComponent : struct public FilterBuilder Exclude<TComponent>() where TComponent : unmanaged
{ {
ComponentDepot.Register<TComponent>(); ComponentDepot.Register<TComponent>();
Excluded.Add(typeof(TComponent)); Excluded.Add(typeof(TComponent));

View File

@ -7,8 +7,8 @@ namespace MoonTools.ECS
{ {
private const int HASH_FACTOR = 97; private const int HASH_FACTOR = 97;
public HashSet<Type> Included; public readonly HashSet<Type> Included;
public HashSet<Type> Excluded; public readonly HashSet<Type> Excluded;
public FilterSignature(HashSet<Type> included, HashSet<Type> excluded) public FilterSignature(HashSet<Type> included, HashSet<Type> excluded)
{ {

View File

@ -1,10 +1,11 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS namespace MoonTools.ECS
{ {
internal class IndexableSet<T> : IEnumerable<T> where T : notnull internal class IndexableSet<T> : IEnumerable<T> where T : unmanaged
{ {
private Dictionary<T, int> indices; private Dictionary<T, int> indices;
private T[] array; private T[] array;
@ -78,5 +79,38 @@ namespace MoonTools.ECS
yield return array[i]; yield return array[i];
} }
} }
public void Save(IndexableSetState<T> state)
{
state.Indices.Clear();
foreach (var (key, value) in indices)
{
state.Indices[key] = value;
}
ReadOnlySpan<byte> arrayBytes = MemoryMarshal.Cast<T, byte>(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<T> state)
{
indices.Clear();
foreach (var kvp in state.Indices)
{
indices[kvp.Key] = kvp.Value;
}
state.Array.CopyTo(MemoryMarshal.Cast<T, byte>(array));
Count = state.Count;
}
} }
} }

19
src/IndexableSetState.cs Normal file
View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
public class IndexableSetState<T> where T : unmanaged
{
public int Count;
public Dictionary<T, int> Indices;
public byte[] Array;
public unsafe IndexableSetState(int count)
{
Count = count;
Indices = new Dictionary<T, int>(count);
Array = new byte[sizeof(T) * count];
}
}
}

View File

@ -12,12 +12,12 @@
return EntityStorage.Create(); return EntityStorage.Create();
} }
public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : struct public void Set<TComponent>(Entity entity, in TComponent component) where TComponent : unmanaged
{ {
ComponentDepot.Set(entity.ID, component); ComponentDepot.Set(entity.ID, component);
} }
public void Send<TMessage>(in TMessage message) where TMessage : struct public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{ {
MessageDepot.Add(message); MessageDepot.Add(message);
} }
@ -27,6 +27,11 @@
MessageDepot.Clear(); MessageDepot.Clear();
} }
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
ComponentDepot.DisableSerialization<TComponent>();
}
public ComponentDepotState Serialize() public ComponentDepotState Serialize()
{ {
var state = ComponentDepot.CreateState(); var state = ComponentDepot.CreateState();