more serialization changes
parent
b30d53e71d
commit
be95e80265
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue