MoonTools.ECS/src/ComponentStorage.cs

155 lines
4.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonTools.ECS
{
internal abstract class ComponentStorage
{
public abstract bool Has(int entityID);
public abstract void 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
{
private int nextID;
private readonly Dictionary<int, int> entityIDToStorageIndex = new Dictionary<int, int>(16);
private int[] entityIDs = new int[16];
private TComponent[] components = new TComponent[16];
public bool Any()
{
return nextID > 0;
}
public override bool Has(int entityID)
{
return entityIDToStorageIndex.ContainsKey(entityID);
}
public ref readonly TComponent Get(int entityID)
{
return ref components[entityIDToStorageIndex[entityID]];
}
public override object Debug_Get(int entityID)
{
return components[entityIDToStorageIndex[entityID]];
}
public ref readonly TComponent Get()
{
#if DEBUG
if (nextID == 0)
{
throw new ArgumentOutOfRangeException("Component storage is empty!");
}
#endif
return ref components[0];
}
public void Set(int entityID, in TComponent component)
{
if (!entityIDToStorageIndex.ContainsKey(entityID))
{
var index = nextID;
nextID += 1;
if (index >= components.Length)
{
Array.Resize(ref components, components.Length * 2);
Array.Resize(ref entityIDs, entityIDs.Length * 2);
}
entityIDToStorageIndex[entityID] = index;
entityIDs[index] = entityID;
}
components[entityIDToStorageIndex[entityID]] = component;
}
public override void Remove(int entityID)
{
if (entityIDToStorageIndex.ContainsKey(entityID))
{
var storageIndex = entityIDToStorageIndex[entityID];
entityIDToStorageIndex.Remove(entityID);
var lastElementIndex = nextID - 1;
// move a component into the hole to maintain contiguous memory
if (lastElementIndex != storageIndex)
{
var lastEntityID = entityIDs[lastElementIndex];
entityIDToStorageIndex[lastEntityID] = storageIndex;
components[storageIndex] = components[lastElementIndex];
entityIDs[storageIndex] = lastEntityID;
}
nextID -= 1;
}
}
public void Clear()
{
nextID = 0;
entityIDToStorageIndex.Clear();
}
public ReadOnlySpan<TComponent> AllComponents()
{
return new ReadOnlySpan<TComponent>(components, 0, nextID);
}
public Entity FirstEntity()
{
return new Entity(entityIDs[0]);
}
public override ComponentStorageState CreateState()
{
return ComponentStorageState.Create<TComponent>(nextID);
}
public override void Serialize(ComponentStorageState serializedComponentStorage)
{
ReadOnlySpan<byte> entityIDBytes = MemoryMarshal.Cast<int, byte>(new ReadOnlySpan<int>(entityIDs, 0, nextID));
if (entityIDBytes.Length > serializedComponentStorage.EntityIDs.Length)
{
Array.Resize(ref serializedComponentStorage.EntityIDs, entityIDBytes.Length);
}
entityIDBytes.CopyTo(serializedComponentStorage.EntityIDs);
ReadOnlySpan<byte> componentBytes = MemoryMarshal.Cast<TComponent, byte>(AllComponents());
if (componentBytes.Length > serializedComponentStorage.Components.Length)
{
Array.Resize(ref serializedComponentStorage.Components, componentBytes.Length);
}
componentBytes.CopyTo(serializedComponentStorage.Components);
serializedComponentStorage.EntityIdToStorageIndex.Clear();
foreach (var kvp in entityIDToStorageIndex)
{
serializedComponentStorage.EntityIdToStorageIndex[kvp.Key] = kvp.Value;
}
}
public override void Deserialize(ComponentStorageState serializedComponentStorage)
{
serializedComponentStorage.EntityIDs.CopyTo(MemoryMarshal.Cast<int, byte>(entityIDs));
serializedComponentStorage.Components.CopyTo(MemoryMarshal.Cast<TComponent, byte>(components));
entityIDToStorageIndex.Clear();
foreach (var kvp in serializedComponentStorage.EntityIdToStorageIndex)
{
entityIDToStorageIndex[kvp.Key] = kvp.Value;
}
}
}
}