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>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@ -11,13 +11,13 @@ namespace MoonTools.ECS
private Dictionary<Type, HashSet<FilterSignature>> typeToFilterSignatures = new Dictionary<Type, HashSet<FilterSignature>>();
private Dictionary<int, HashSet<Type>> entityComponentMap = new Dictionary<int, HashSet<Type>>();
#if DEBUG
private Dictionary<Type, Filter> singleComponentFilters = new Dictionary<Type, Filter>();
#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)))
{
@ -33,19 +33,19 @@ namespace MoonTools.ECS
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?
Register<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();
}
public bool Has<TComponent>(int entityID) where TComponent : struct
public bool Has<TComponent>(int entityID) where TComponent : unmanaged
{
return Lookup<TComponent>().Has(entityID);
}
@ -55,29 +55,22 @@ namespace MoonTools.ECS
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);
}
public ref readonly TComponent Get<TComponent>() where TComponent : struct
public ref readonly TComponent Get<TComponent>() where TComponent : unmanaged
{
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);
if (!entityComponentMap.ContainsKey(entityID))
{
entityComponentMap.Add(entityID, new HashSet<Type>());
}
var notFound = entityComponentMap[entityID].Add(typeof(TComponent));
var existed = Lookup<TComponent>().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<TComponent>() where TComponent : struct
public Entity GetSingletonEntity<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().FirstEntity();
}
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : struct
public ReadOnlySpan<TComponent> ReadComponents<TComponent>() where TComponent : unmanaged
{
return Lookup<TComponent>().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<TComponent>(int entityID) where TComponent : struct
public void Remove<TComponent>(int entityID) where TComponent : unmanaged
{
Lookup<TComponent>().Remove(entityID);
var found = entityComponentMap[entityID].Remove(typeof(TComponent));
var existed = Lookup<TComponent>().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<TComponent>() 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<int>(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

View File

@ -3,8 +3,9 @@ using System.Collections.Generic;
namespace MoonTools.ECS
{
public class ComponentDepotState
{
public class ComponentDepotState
{
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
{
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<TComponent> : ComponentStorage where TComponent : struct
internal class ComponentStorage<TComponent> : ComponentStorage where TComponent : unmanaged
{
private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(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;
}
}
}

View File

@ -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<TComponent>(int count)
public unsafe static ComponentStorageState Create<TComponent>(int count) where TComponent : unmanaged
{
return new ComponentStorageState(
count,
count * Marshal.SizeOf<int>(),
count * Marshal.SizeOf<TComponent>()
count * sizeof(int),
count * sizeof(TComponent)
);
}

View File

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

View File

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

View File

@ -1,10 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
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 T[] array;
@ -78,5 +79,38 @@ namespace MoonTools.ECS
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();
}
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);
}
public void Send<TMessage>(in TMessage message) where TMessage : struct
public void Send<TMessage>(in TMessage message) where TMessage : unmanaged
{
MessageDepot.Add(message);
}
@ -27,6 +27,11 @@
MessageDepot.Clear();
}
public void DisableSerialization<TComponent>() where TComponent : unmanaged
{
ComponentDepot.DisableSerialization<TComponent>();
}
public ComponentDepotState Serialize()
{
var state = ComponentDepot.CreateState();