Refactor scene loading, shared components, and cleanup
- Split scene loading into parsing and materialization steps - Make SceneID an ISharedComponent; add SharedComponentSet - Centralize archetype/cleanup logic for entity destruction - Add batch DestroyEntities to EntityCommandBuffer - Use shared component filtering for SceneID queries - Move AssetType/AssetState enums to AssetEntry.cs - Remove ManagedEntity/ScriptComponent logic - Misc: Write<T> signature, AsSpan, code style, GC fixes
This commit is contained in:
@@ -32,7 +32,7 @@ public unsafe struct BufferWriter : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write<T>(T value)
|
public void Write<T>(scoped in T value)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
EnsureCapacity(sizeof(T));
|
EnsureCapacity(sizeof(T));
|
||||||
@@ -72,7 +72,7 @@ public unsafe struct BufferWriter : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly Span<byte> AsSpan()
|
public readonly Span<byte> AsSpan()
|
||||||
{
|
{
|
||||||
return _buffer.AsSpan();
|
return _buffer.AsSpan(0, Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -114,7 +114,7 @@ public unsafe ref struct SpanWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Write<T>(T value)
|
public void Write<T>(scoped in T value)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Ghost.Entities;
|
|||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
public struct SceneID : IComponent // TODO: ISharedComponent
|
public struct SceneID : ISharedComponent
|
||||||
{
|
{
|
||||||
public ushort value;
|
public ushort value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Ghost.Core.Utilities;
|
|||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Engine.Streaming;
|
using Ghost.Engine.Streaming;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Misaki.HighPerformance.Jobs;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -29,7 +28,7 @@ public struct Scene : IEquatable<Scene>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an invalid scene instance.
|
/// Gets an invalid scene instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Scene Invalid => new Scene { _id = INVALID_ID };
|
public static Scene Invalid => new() { _id = INVALID_ID };
|
||||||
|
|
||||||
internal Scene(ushort id)
|
internal Scene(ushort id)
|
||||||
{
|
{
|
||||||
@@ -76,105 +75,43 @@ public struct Scene : IEquatable<Scene>
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class SceneManager
|
public static class SceneManager
|
||||||
{
|
{
|
||||||
private struct BinaryEntityInfo : IDisposable
|
|
||||||
{
|
|
||||||
public int entityIndex;
|
|
||||||
public int componentCount;
|
|
||||||
public struct ComponentInfo
|
|
||||||
{
|
|
||||||
public UnsafeArray<int> entityFieldOffsets;
|
|
||||||
public long dataOffset;
|
|
||||||
public uint typeHash;
|
|
||||||
public Identifier<IComponent> typeID;
|
|
||||||
public int dataSize;
|
|
||||||
public int entityFieldCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnsafeArray<ComponentInfo> components;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < components.Length; i++)
|
|
||||||
{
|
|
||||||
components[i].entityFieldOffsets.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct BinaryEntityInfoArray : IDisposable
|
|
||||||
{
|
|
||||||
public UnsafeArray<BinaryEntityInfo> data;
|
|
||||||
|
|
||||||
public readonly ref BinaryEntityInfo this[int index] => ref data[index];
|
|
||||||
|
|
||||||
public BinaryEntityInfoArray(int count, AllocationHandle handle)
|
|
||||||
{
|
|
||||||
data = new UnsafeArray<BinaryEntityInfo>(count, handle, AllocationOption.Clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < data.Length; i++)
|
|
||||||
{
|
|
||||||
data[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct SceneLoadResult : IDisposable
|
internal struct SceneLoadResult : IDisposable
|
||||||
{
|
{
|
||||||
internal struct PendingEntity : IDisposable
|
internal struct PendingEntity : IDisposable
|
||||||
{
|
{
|
||||||
public int fileLocalIndex;
|
public int fileLocalIndex;
|
||||||
public ComponentSet componentSet;
|
public UnsafeList<Identifier<IComponent>> componentTypeIDs;
|
||||||
public UnsafeList<(Identifier<IComponent> typeID, UnsafeArray<byte> data)> componentData;
|
public UnsafeList<(Identifier<IComponent> typeID, UnsafeArray<byte> data)> componentData;
|
||||||
public UnsafeList<(int componentIndex, UnsafeArray<int> fieldOffsets)> entityFields;
|
public UnsafeList<(int componentDataIndex, UnsafeArray<int> fieldOffsets)> entityFields;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
componentTypeIDs.Dispose();
|
||||||
for (int i = 0; i < componentData.Count; i++)
|
for (int i = 0; i < componentData.Count; i++)
|
||||||
{
|
{
|
||||||
componentData[i].data.Dispose();
|
componentData[i].data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentSet.Dispose();
|
|
||||||
componentData.Dispose();
|
componentData.Dispose();
|
||||||
|
for (int i = 0; i < entityFields.Count; i++)
|
||||||
|
{
|
||||||
|
entityFields[i].fieldOffsets.Dispose();
|
||||||
|
}
|
||||||
entityFields.Dispose();
|
entityFields.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnsafeList<PendingEntity> entities;
|
public UnsafeArray<PendingEntity> entities;
|
||||||
public Scene scene;
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < entities.Count; i++)
|
for (int i = 0; i < entities.Length; i++)
|
||||||
{
|
{
|
||||||
entities[i].Dispose();
|
entities[i].Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
entities.Dispose();
|
entities.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe struct LoadSceneJob : IJob
|
|
||||||
{
|
|
||||||
public SceneContentHeader header;
|
|
||||||
public Stream stream;
|
|
||||||
|
|
||||||
public SceneLoadResult* result;
|
|
||||||
public AllocationHandle allocationHandle;
|
|
||||||
|
|
||||||
public void Execute(ref readonly JobExecutionContext ctx)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ushort s_nextSceneID;
|
private static ushort s_nextSceneID;
|
||||||
private static readonly Queue<ushort> s_recycledSceneIDs = new();
|
private static readonly Queue<ushort> s_recycledSceneIDs = new();
|
||||||
|
|
||||||
@@ -197,23 +134,39 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static unsafe Result<JobHandle> LoadSceneIntoWorld(World world, SceneContentHeader header, Stream stream)
|
internal static SceneLoadResult ParseSceneData(SceneContentHeader header, Stream stream, AllocationHandle allocationHandle)
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
var result = new SceneLoadResult
|
||||||
|
{
|
||||||
|
entities = new UnsafeArray<SceneLoadResult.PendingEntity>(header.entityCount, allocationHandle)
|
||||||
|
};
|
||||||
|
|
||||||
using var entityInfos = new BinaryEntityInfoArray(header.entityCount, scope.AllocationHandle);
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
using var forwardMap = new UnsafeHashMap<int, Entity>(header.entityCount, scope.AllocationHandle);
|
|
||||||
using var str = new UnsafeArray<byte>(128, scope.AllocationHandle);
|
using var str = new UnsafeArray<byte>(128, scope.AllocationHandle);
|
||||||
|
|
||||||
for (var i = 0; i < header.entityCount; i++)
|
for (var i = 0; i < header.entityCount; i++)
|
||||||
{
|
{
|
||||||
var compCount = stream.Read<int>();
|
var compCount = stream.Read<int>();
|
||||||
|
|
||||||
if (compCount == 0)
|
if (compCount == 0)
|
||||||
{
|
{
|
||||||
|
result.entities[i] = new SceneLoadResult.PendingEntity
|
||||||
|
{
|
||||||
|
fileLocalIndex = i,
|
||||||
|
componentTypeIDs = new UnsafeList<Identifier<IComponent>>(0, allocationHandle),
|
||||||
|
componentData = new UnsafeList<(Identifier<IComponent> typeID, UnsafeArray<byte> data)>(0, allocationHandle),
|
||||||
|
entityFields = new UnsafeList<(int componentDataIndex, UnsafeArray<int> fieldOffsets)>(0, allocationHandle)
|
||||||
|
};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var comps = new UnsafeArray<BinaryEntityInfo.ComponentInfo>(compCount, scope.AllocationHandle);
|
var pending = new SceneLoadResult.PendingEntity
|
||||||
|
{
|
||||||
|
fileLocalIndex = i,
|
||||||
|
componentTypeIDs = new UnsafeList<Identifier<IComponent>>(compCount, allocationHandle),
|
||||||
|
componentData = new UnsafeList<(Identifier<IComponent> typeID, UnsafeArray<byte> data)>(compCount, allocationHandle),
|
||||||
|
entityFields = new UnsafeList<(int componentDataIndex, UnsafeArray<int> fieldOffsets)>(compCount, allocationHandle)
|
||||||
|
};
|
||||||
|
|
||||||
for (var j = 0; j < compCount; j++)
|
for (var j = 0; j < compCount; j++)
|
||||||
{
|
{
|
||||||
@@ -226,119 +179,109 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
var strSpan = str.AsSpan(0, nameLength);
|
var strSpan = str.AsSpan(0, nameLength);
|
||||||
stream.ReadExactly(strSpan);
|
stream.ReadExactly(strSpan.Slice(0, nameLength));
|
||||||
|
|
||||||
var typeName = Encoding.UTF8.GetString(strSpan);
|
var typeName = Encoding.UTF8.GetString(strSpan);
|
||||||
|
|
||||||
var dataSz = stream.Read<int>();
|
var dataSz = stream.Read<int>();
|
||||||
var dataOff = stream.Position;
|
var compData = new UnsafeArray<byte>(dataSz, allocationHandle);
|
||||||
stream.Position += dataSz;
|
stream.ReadExactly(compData);
|
||||||
|
|
||||||
var fieldCount = stream.Read<int>();
|
var fieldCount = stream.Read<int>();
|
||||||
var fieldOffsets = new UnsafeArray<int>(fieldCount, scope.AllocationHandle);
|
|
||||||
|
UnsafeArray<int> fieldOffsets = default;
|
||||||
|
if (fieldCount > 0)
|
||||||
|
{
|
||||||
|
fieldOffsets = new UnsafeArray<int>(fieldCount, allocationHandle);
|
||||||
for (var f = 0; f < fieldCount; f++)
|
for (var f = 0; f < fieldCount; f++)
|
||||||
{
|
{
|
||||||
fieldOffsets[f] = stream.Read<int>();
|
fieldOffsets[f] = stream.Read<int>();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var typeID = ComponentRegistry.GetComponentIDByName(typeName);
|
var typeID = ComponentRegistry.GetComponentIDByName(typeName);
|
||||||
|
if (typeID.IsValid)
|
||||||
comps[j] = new BinaryEntityInfo.ComponentInfo
|
|
||||||
{
|
{
|
||||||
dataOffset = dataOff,
|
pending.componentTypeIDs.Add(typeID);
|
||||||
entityFieldOffsets = fieldOffsets,
|
pending.componentData.Add((typeID, compData));
|
||||||
typeHash = typeHash,
|
if (fieldCount > 0)
|
||||||
typeID = typeID,
|
{
|
||||||
dataSize = dataSz,
|
pending.entityFields.Add((pending.componentData.Count - 1, fieldOffsets));
|
||||||
entityFieldCount = fieldCount,
|
}
|
||||||
};
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compData.Dispose();
|
||||||
|
if (fieldCount > 0) fieldOffsets.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entityInfos[i] = new BinaryEntityInfo
|
result.entities[i] = pending;
|
||||||
{
|
|
||||||
entityIndex = i,
|
|
||||||
componentCount = compCount,
|
|
||||||
components = comps,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using var typeIds = new UnsafeList<Identifier<IComponent>>(32, scope.AllocationHandle);
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static unsafe Result<int> MaterializeScene(World world, ref SceneLoadResult result, Scene scene)
|
||||||
|
{
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
using var forwardMap = new UnsafeHashMap<int, Entity>(result.entities.Length, scope.AllocationHandle);
|
||||||
|
using var sharedCom = new SharedComponentSet(256, scope.AllocationHandle);
|
||||||
|
|
||||||
|
// Create entities and set SceneID
|
||||||
|
for (var i = 0; i < result.entities.Length; i++)
|
||||||
|
{
|
||||||
|
ref var pending = ref result.entities[i];
|
||||||
|
|
||||||
|
using var typeIds = new UnsafeList<Identifier<IComponent>>(pending.componentTypeIDs.Count + 1, scope.AllocationHandle);
|
||||||
typeIds.Add(ComponentTypeID<SceneID>.Value);
|
typeIds.Add(ComponentTypeID<SceneID>.Value);
|
||||||
|
for (int j = 0; j < pending.componentTypeIDs.Count; j++)
|
||||||
for (var i = 0; i < header.entityCount; i++)
|
|
||||||
{
|
{
|
||||||
ref var info = ref entityInfos[i];
|
typeIds.Add(pending.componentTypeIDs[j]);
|
||||||
|
|
||||||
for (var j = 0; j < info.componentCount; j++)
|
|
||||||
{
|
|
||||||
if (info.components[j].typeID.IsValid)
|
|
||||||
{
|
|
||||||
typeIds.Add(info.components[j].typeID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var set = new ComponentSetView(typeIds);
|
sharedCom.With(new SceneID { value = scene.ID });
|
||||||
|
|
||||||
|
var set = new ComponentSetView(typeIds, sharedCom);
|
||||||
var entity = world.EntityManager.CreateEntity(set);
|
var entity = world.EntityManager.CreateEntity(set);
|
||||||
|
forwardMap.TryAdd(pending.fileLocalIndex, entity);
|
||||||
|
|
||||||
forwardMap.TryAdd(i, entity);
|
sharedCom.Reset();
|
||||||
typeIds.RemoveRange(1, typeIds.Count - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeScene = CreateScene();
|
// Set component data
|
||||||
|
for (var i = 0; i < result.entities.Length; i++)
|
||||||
for (var i = 0; i < header.entityCount; i++)
|
|
||||||
{
|
|
||||||
if (!forwardMap.TryGetValue(i, out var entity))
|
|
||||||
{
|
{
|
||||||
|
ref var pending = ref result.entities[i];
|
||||||
|
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
for (var j = 0; j < pending.componentData.Count; j++)
|
||||||
|
{
|
||||||
|
var (typeID, data) = pending.componentData[j];
|
||||||
|
world.EntityManager.SetComponent(entity, typeID, data.GetUnsafePtr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
world.EntityManager.SetComponent(entity, new SceneID { value = activeScene.ID });
|
// Remap entity references
|
||||||
|
for (var i = 0; i < result.entities.Length; i++)
|
||||||
using var compScope = AllocationManager.CreateStackScope();
|
|
||||||
var info = entityInfos[i];
|
|
||||||
|
|
||||||
for (var j = 0; j < info.componentCount; j++)
|
|
||||||
{
|
|
||||||
var comp = info.components[j];
|
|
||||||
if (!comp.typeID.IsValid)
|
|
||||||
{
|
{
|
||||||
|
ref var pending = ref result.entities[i];
|
||||||
|
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
var compSize = ComponentRegistry.GetComponentInfo(comp.typeID).size;
|
for (var j = 0; j < pending.entityFields.Count; j++)
|
||||||
|
|
||||||
stream.Position = comp.dataOffset;
|
|
||||||
|
|
||||||
using var src = stream.ReadMemory(compSize, compScope.AllocationHandle);
|
|
||||||
world.EntityManager.SetComponent(entity, comp.typeID, src.GetUnsafePtr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < header.entityCount; i++)
|
|
||||||
{
|
{
|
||||||
if (!forwardMap.TryGetValue(i, out var entity))
|
var (componentDataIndex, fieldOffsets) = pending.entityFields[j];
|
||||||
{
|
var compTypeID = pending.componentData[componentDataIndex].typeID;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = entityInfos[i];
|
var pComponent = world.EntityManager.GetComponent(entity, compTypeID);
|
||||||
for (var j = 0; j < info.componentCount; j++)
|
|
||||||
{
|
|
||||||
var comp = info.components[j];
|
|
||||||
if (!comp.typeID.IsValid || comp.entityFieldCount == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var pComponent = world.EntityManager.GetComponent(entity, comp.typeID);
|
|
||||||
if (pComponent == null)
|
if (pComponent == null)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
for (var f = 0; f < comp.entityFieldCount; f++)
|
for (var f = 0; f < fieldOffsets.Length; f++)
|
||||||
{
|
{
|
||||||
var fieldOffset = comp.entityFieldOffsets[f];
|
var fieldOffset = fieldOffsets[f];
|
||||||
var pField = (byte*)pComponent + fieldOffset;
|
var pField = (byte*)pComponent + fieldOffset;
|
||||||
var fileLocalIndex = *(int*)pField;
|
var fileLocalIndex = *(int*)pField;
|
||||||
if (!forwardMap.TryGetValue(fileLocalIndex, out var remappedEntity))
|
if (!forwardMap.TryGetValue(fileLocalIndex, out var remappedEntity))
|
||||||
@@ -351,7 +294,7 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success(result.entities.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -365,29 +308,18 @@ public static class SceneManager
|
|||||||
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var entitiesToDestroy = new UnsafeList<Entity>(128, scope.AllocationHandle);
|
using var ecb = new EntityCommandBuffer(512, scope.AllocationHandle);
|
||||||
|
|
||||||
// Iterate through all matching entities
|
// Iterate through all matching entities
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
var entities = chunk.GetEntities();
|
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
||||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
if (sceneID.value == scene.ID)
|
||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
|
||||||
{
|
{
|
||||||
if (sceneIDs[i].value == scene.ID)
|
ecb.DestroyEntities(chunk.GetEntities());
|
||||||
{
|
|
||||||
entitiesToDestroy.Add(entities[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
|
||||||
s_recycledSceneIDs.Enqueue(scene.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReleaseScene(Scene scene)
|
|
||||||
{
|
|
||||||
s_recycledSceneIDs.Enqueue(scene.ID);
|
s_recycledSceneIDs.Enqueue(scene.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,15 +340,10 @@ public static class SceneManager
|
|||||||
// Iterate through all matching entities
|
// Iterate through all matching entities
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
var chunkEntities = chunk.GetEntities();
|
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
||||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
if (sceneID.value == scene.ID)
|
||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
|
||||||
{
|
{
|
||||||
if (sceneIDs[i].value == scene.ID)
|
entities.AddRange(chunk.GetEntities());
|
||||||
{
|
|
||||||
entities.Add(chunkEntities[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,35 @@ using Ghost.Graphics;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Services;
|
using Ghost.Graphics.Services;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Engine.Streaming;
|
namespace Ghost.Engine.Streaming;
|
||||||
|
|
||||||
|
public enum AssetType
|
||||||
|
{
|
||||||
|
Texture = 0,
|
||||||
|
Mesh = 1,
|
||||||
|
Material = 2,
|
||||||
|
Shader = 3,
|
||||||
|
Scene = 4,
|
||||||
|
Audio = 5,
|
||||||
|
Video = 6,
|
||||||
|
Json = 7,
|
||||||
|
|
||||||
|
Unknown = 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AssetState
|
||||||
|
{
|
||||||
|
Unloaded = 0,
|
||||||
|
Scheduled = 1,
|
||||||
|
Loading = 2,
|
||||||
|
Loaded = 3,
|
||||||
|
Processing = 4,
|
||||||
|
Ready = 5,
|
||||||
|
Failed = 6,
|
||||||
|
}
|
||||||
|
|
||||||
internal static class AssetEntryFactory
|
internal static class AssetEntryFactory
|
||||||
{
|
{
|
||||||
public static AssetEntry CreateNewEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
public static AssetEntry CreateNewEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Core.Utilities;
|
|
||||||
using Ghost.Graphics.Services;
|
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Services;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Engine.Streaming;
|
namespace Ghost.Engine.Streaming;
|
||||||
|
|
||||||
public enum AssetType
|
|
||||||
{
|
|
||||||
Texture = 0,
|
|
||||||
Mesh = 1,
|
|
||||||
Material = 2,
|
|
||||||
Shader = 3,
|
|
||||||
Scene = 4,
|
|
||||||
Audio = 5,
|
|
||||||
Video = 6,
|
|
||||||
Json = 7,
|
|
||||||
|
|
||||||
Unknown = 64,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AssetState
|
|
||||||
{
|
|
||||||
Unloaded = 0,
|
|
||||||
Scheduled = 1,
|
|
||||||
Loading = 2,
|
|
||||||
Loaded = 3,
|
|
||||||
Processing = 4,
|
|
||||||
Ready = 5,
|
|
||||||
Failed = 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IContentProvider
|
public interface IContentProvider
|
||||||
{
|
{
|
||||||
bool HasAsset(Guid guid);
|
bool HasAsset(Guid guid);
|
||||||
@@ -263,5 +236,7 @@ public partial class AssetManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_entries.Clear();
|
_entries.Clear();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Ghost.Engine.Streaming;
|
|||||||
|
|
||||||
internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
||||||
{
|
{
|
||||||
private const int _MAX_UPLOADS_PER_FRAME = 8;
|
private const int MAX_UPLOADS_PER_FRAME = 8;
|
||||||
|
|
||||||
private readonly ConcurrentQueue<ProcessableAssetEntry> _pendingProcess;
|
private readonly ConcurrentQueue<ProcessableAssetEntry> _pendingProcess;
|
||||||
private readonly ConcurrentQueue<UploadableAssetEntry> _pendingUpload;
|
private readonly ConcurrentQueue<UploadableAssetEntry> _pendingUpload;
|
||||||
@@ -101,7 +101,7 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
|||||||
context.CopyPipeline.Begin();
|
context.CopyPipeline.Begin();
|
||||||
|
|
||||||
var uploadCount = 0;
|
var uploadCount = 0;
|
||||||
while (uploadCount < _MAX_UPLOADS_PER_FRAME && _pendingUpload.TryDequeue(out var entry))
|
while (uploadCount < MAX_UPLOADS_PER_FRAME && _pendingUpload.TryDequeue(out var entry))
|
||||||
{
|
{
|
||||||
if (entry.State != AssetState.Loaded)
|
if (entry.State != AssetState.Loaded)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Ghost.Entities;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Services;
|
using Ghost.Graphics.Services;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Engine.Streaming;
|
namespace Ghost.Engine.Streaming;
|
||||||
@@ -21,6 +22,7 @@ internal struct SceneContentHeader
|
|||||||
public int entityCount;
|
public int entityCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We should have a dedicated scene loading service. Maybe we should make our SceneManager as a service.
|
||||||
public partial class AssetManager
|
public partial class AssetManager
|
||||||
{
|
{
|
||||||
public Result<JobHandle> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType)
|
public Result<JobHandle> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType)
|
||||||
@@ -56,11 +58,7 @@ public partial class AssetManager
|
|||||||
world.Reset();
|
world.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadResult = SceneManager.LoadSceneIntoWorld(world, header, stream);
|
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent);
|
||||||
if (loadResult.IsFailure)
|
|
||||||
{
|
|
||||||
return Result.Failure(loadResult.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JobHandle.Invalid;
|
return JobHandle.Invalid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Utilities;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -52,11 +53,6 @@ internal static class ComponentRegistry
|
|||||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static ComponentRegistry()
|
|
||||||
{
|
|
||||||
GetOrRegisterComponentID<ManagedEntityRef>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
|
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponent
|
||||||
{
|
{
|
||||||
@@ -278,12 +274,12 @@ public partial class ComponentManager : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void Clear()
|
internal void Clear()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _archetypes.Count; i++)
|
for (var i = 0; i < _archetypes.Count; i++)
|
||||||
{
|
{
|
||||||
_archetypes[i].Dispose();
|
_archetypes[i].Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _entityQueries.Count; i++)
|
for (var i = 0; i < _entityQueries.Count; i++)
|
||||||
{
|
{
|
||||||
_entityQueries[i].Dispose();
|
_entityQueries[i].Dispose();
|
||||||
}
|
}
|
||||||
@@ -300,7 +296,7 @@ public partial class ComponentManager : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void Collect()
|
internal void Collect()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _archetypes.Count; i++)
|
for (var i = 0; i < _archetypes.Count; i++)
|
||||||
{
|
{
|
||||||
_archetypes[i].Collect();
|
_archetypes[i].Collect();
|
||||||
}
|
}
|
||||||
@@ -342,6 +338,37 @@ public partial class ComponentManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SharedComponentSet : IDisposable
|
||||||
|
{
|
||||||
|
private BufferWriter _writer;
|
||||||
|
|
||||||
|
public SharedComponentSet(int capacity, AllocationHandle allocationHandle)
|
||||||
|
{
|
||||||
|
_writer = new BufferWriter(capacity, allocationHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void With<T>(scoped in T data)
|
||||||
|
where T : unmanaged, ISharedComponent
|
||||||
|
{
|
||||||
|
_writer.Write(in data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ReadOnlySpan<byte> AsSpan()
|
||||||
|
{
|
||||||
|
return _writer.AsSpan();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_writer.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_writer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
|
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -404,6 +431,11 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
_sharedHashCode = -1;
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentSet(AllocationHandle allocationHandle, ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||||
|
: this(allocationHandle, components, sharedComponentSet.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly ComponentSetView AsView()
|
public readonly ComponentSetView AsView()
|
||||||
{
|
{
|
||||||
@@ -502,6 +534,11 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
|||||||
_sharedHashCode = -1;
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||||
|
: this(components, sharedComponentSet.AsSpan())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public readonly bool Equals(ComponentSetView other)
|
public readonly bool Equals(ComponentSetView other)
|
||||||
{
|
{
|
||||||
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode;
|
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
CreateEntity,
|
CreateEntity,
|
||||||
CreateEntityWithComponents,
|
CreateEntityWithComponents,
|
||||||
DestroyEntity,
|
DestroyEntity,
|
||||||
|
DestroyEntities,
|
||||||
AddComponent,
|
AddComponent,
|
||||||
RemoveComponent,
|
RemoveComponent,
|
||||||
SetComponent,
|
SetComponent,
|
||||||
@@ -52,6 +53,14 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
_writer.Write(entity);
|
_writer.Write(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void DestroyEntities(params ReadOnlySpan<Entity> entities)
|
||||||
|
{
|
||||||
|
_writer.Write(ECBOpCode.DestroyEntities);
|
||||||
|
_writer.Write(entities.Length);
|
||||||
|
_writer.WriteSpan(entities);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddComponent<T>(Entity entity, T component = default)
|
public void AddComponent<T>(Entity entity, T component = default)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponent
|
||||||
@@ -151,6 +160,12 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
entityManager.DestroyEntity(entityToDestroy);
|
entityManager.DestroyEntity(entityToDestroy);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ECBOpCode.DestroyEntities:
|
||||||
|
var removeCount = reader.Read<int>();
|
||||||
|
var entitiesToRemove = reader.ReadSpan<Entity>(removeCount);
|
||||||
|
entityManager.DestroyEntities(entitiesToRemove);
|
||||||
|
break;
|
||||||
|
|
||||||
case ECBOpCode.AddComponent:
|
case ECBOpCode.AddComponent:
|
||||||
var entityToAdd = reader.Read<Entity>();
|
var entityToAdd = reader.Read<Entity>();
|
||||||
var addCompTypeID = reader.Read<Identifier<IComponent>>();
|
var addCompTypeID = reader.Read<Identifier<IComponent>>();
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
using Misaki.HighPerformance.Collections;
|
|
||||||
using Misaki.HighPerformance.Utilities;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
public partial class EntityManager
|
|
||||||
{
|
|
||||||
private readonly SlotMap<List<ScriptComponent>> _scriptComponents;
|
|
||||||
|
|
||||||
internal SlotMap<List<ScriptComponent>> ScriptComponents => _scriptComponents;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ManagedEntity and associates it with the given Entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The Entity to associate with the ManagedEntity.</param>
|
|
||||||
/// <returns>The created ManagedEntity.</returns>
|
|
||||||
public ManagedEntity CreateManagedEntity(Entity entity)
|
|
||||||
{
|
|
||||||
var managedEntity = CreateManagedEntity();
|
|
||||||
AddComponent(entity, new ManagedEntityRef
|
|
||||||
{
|
|
||||||
entity = managedEntity
|
|
||||||
});
|
|
||||||
|
|
||||||
return managedEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new ManagedEntity.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// You must call this if you add <see cref="ManagedEntityRef"/> manually to an entity.
|
|
||||||
/// Otherwise, use <see cref="CreateManagedEntity(Entity)"/>.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns>The created ManagedEntity.</returns>
|
|
||||||
public ManagedEntity CreateManagedEntity()
|
|
||||||
{
|
|
||||||
var id = _scriptComponents.Add(new(8), out var generation);
|
|
||||||
var managedEntity = new ManagedEntity
|
|
||||||
{
|
|
||||||
id = id,
|
|
||||||
generation = generation
|
|
||||||
};
|
|
||||||
|
|
||||||
return managedEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Destroys the given ManagedEntity and calls OnDestroy on all associated ScriptComponents.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity to destroy.</param>
|
|
||||||
public void DestroyManagedEntity(ManagedEntity managedEntity)
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
foreach (var script in scripts)
|
|
||||||
{
|
|
||||||
script.OnDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
_scriptComponents.Remove(managedEntity.id, managedEntity.generation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given ManagedEntity exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity to check.</param>
|
|
||||||
/// <returns>True if the ManagedEntity exists, false otherwise.</returns>
|
|
||||||
public bool Exists(ManagedEntity managedEntity)
|
|
||||||
{
|
|
||||||
return _scriptComponents.Contains(managedEntity.id, managedEntity.generation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a ScriptComponent of space T to the given ManagedEntity and Entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to add.</typeparam>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity to add the ScriptComponent to.</
|
|
||||||
/// <param name="entity">The Entity associated with the ManagedEntity.</param>
|
|
||||||
public void AddScriptComponent<T>(ManagedEntity managedEntity, Entity entity)
|
|
||||||
where T : ScriptComponent, new()
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
var script = new T
|
|
||||||
{
|
|
||||||
_world = _world,
|
|
||||||
_entity = entity,
|
|
||||||
_managedEntity = managedEntity
|
|
||||||
};
|
|
||||||
|
|
||||||
scripts.Add(script);
|
|
||||||
script.OnCreate();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a ScriptComponent of space T to the given Entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to add.</typeparam>
|
|
||||||
/// <param name="entity">The Entity to add the ScriptComponent to.</param>
|
|
||||||
public unsafe void AddScriptComponent<T>(Entity entity)
|
|
||||||
where T : ScriptComponent, new()
|
|
||||||
{
|
|
||||||
var location = _entityLocations.GetElementAt(entity.ID, entity.Generation);
|
|
||||||
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
|
|
||||||
|
|
||||||
var pManagedEntityRef = (ManagedEntityRef*)archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.Value);
|
|
||||||
if (pManagedEntityRef == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Entity {entity} does not have ManagedEntityRef component.");
|
|
||||||
}
|
|
||||||
|
|
||||||
AddScriptComponent<T>(pManagedEntityRef->entity, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Destroys the ScriptComponent of space T associated with the given ManagedEntity.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to destroy.</typeparam>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be destroyed </param>
|
|
||||||
/// <returns>True if the ScriptComponent was found and destroyed, false otherwise.</returns
|
|
||||||
public bool DestroyScriptComponent<T>(ManagedEntity managedEntity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < scripts.Count; i++)
|
|
||||||
{
|
|
||||||
if (scripts[i] is T script)
|
|
||||||
{
|
|
||||||
script.OnDestroy();
|
|
||||||
scripts.RemoveAndSwapBack(i);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given ManagedEntity has a ScriptComponent of space T.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to check for.</typeparam>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity to check.</param>
|
|
||||||
/// <returns>True if the ManagedEntity has a ScriptComponent of space T, false </returns>
|
|
||||||
public bool HasScriptComponent<T>(ManagedEntity managedEntity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
foreach (var script in scripts)
|
|
||||||
{
|
|
||||||
if (script is T)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the ScriptComponent of space T associated with the given ManagedEntity.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to get.</typeparam>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponent is to be retrieved
|
|
||||||
/// <returns>The ScriptComponent of space T.</returns>
|
|
||||||
public T GetScriptComponent<T>(ManagedEntity managedEntity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
foreach (var script in scripts)
|
|
||||||
{
|
|
||||||
if (script is T typedScript)
|
|
||||||
{
|
|
||||||
return typedScript;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not have script component of type {typeof(T)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all ScriptComponents of space T associated with the given ManagedEntity.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The space of ScriptComponent to get.</typeparam>
|
|
||||||
/// <param name="managedEntity">The ManagedEntity whose ScriptComponents are to be retrieved
|
|
||||||
/// <returns>The list of ScriptComponents of space T.</returns>
|
|
||||||
public List<T> GetScriptComponents<T>(ManagedEntity managedEntity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
|
||||||
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
|
||||||
{
|
|
||||||
var result = new List<T>();
|
|
||||||
foreach (var script in scripts)
|
|
||||||
{
|
|
||||||
if (script is T typedScript)
|
|
||||||
{
|
|
||||||
result.Add(typedScript);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,7 +52,6 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, AllocationHandle.Persistent, AllocationOption.Clear);
|
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||||
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~EntityManager()
|
~EntityManager()
|
||||||
@@ -93,6 +92,78 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
_entityLocations.Clear();
|
_entityLocations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or compute the cleanup archetype for <paramref name="archetype"/>.
|
||||||
|
/// The cleanup archetype contains only <see cref="ICleanupComponent"/> components,
|
||||||
|
/// so they can get a final tick before the entity is fully destroyed.
|
||||||
|
/// </summary>
|
||||||
|
private Identifier<Archetype> GetOrCreateCleanupArchetype(ref Archetype archetype)
|
||||||
|
{
|
||||||
|
if (archetype._cleanupEdge >= 0)
|
||||||
|
{
|
||||||
|
return archetype._cleanupEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var signature = ref archetype._signature;
|
||||||
|
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
using var newSignature = new UnsafeBitSet(signature.Count, scope.AllocationHandle);
|
||||||
|
|
||||||
|
var compCount = 0;
|
||||||
|
var it = signature.GetIterator();
|
||||||
|
while (it.Next(out var componentID))
|
||||||
|
{
|
||||||
|
if (ComponentRegistry.GetComponentInfo(componentID).isCleanup)
|
||||||
|
{
|
||||||
|
newSignature.SetBit(componentID);
|
||||||
|
compCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSignatureHash = newSignature.GetHashCode();
|
||||||
|
var newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
||||||
|
if (newArcID.IsInvalid)
|
||||||
|
{
|
||||||
|
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
||||||
|
|
||||||
|
var newIt = newSignature.GetIterator();
|
||||||
|
var i = 0;
|
||||||
|
while (newIt.Next(out var cid))
|
||||||
|
{
|
||||||
|
componentTypeIDs[i++] = cid;
|
||||||
|
}
|
||||||
|
|
||||||
|
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
archetype._cleanupEdge = newArcID;
|
||||||
|
return newArcID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Look up or create an archetype from a <see cref="SpanBitSet"/> signature.
|
||||||
|
/// </summary>
|
||||||
|
private Identifier<Archetype> FindOrCreateArchetype(ref readonly SpanBitSet signature, int componentCount)
|
||||||
|
{
|
||||||
|
var hash = signature.GetHashCode();
|
||||||
|
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
|
||||||
|
if (arcID.IsInvalid)
|
||||||
|
{
|
||||||
|
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[componentCount];
|
||||||
|
|
||||||
|
var it = signature.GetIterator();
|
||||||
|
var i = 0;
|
||||||
|
while (it.Next(out var cid))
|
||||||
|
{
|
||||||
|
componentTypeIDs[i++] = cid;
|
||||||
|
}
|
||||||
|
|
||||||
|
arcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arcID;
|
||||||
|
}
|
||||||
|
|
||||||
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
|
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
|
||||||
ref Archetype newArch, int newChunk, int newRow)
|
ref Archetype newArch, int newChunk, int newRow)
|
||||||
{
|
{
|
||||||
@@ -303,48 +374,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Identifier<Archetype> newArcID = default;
|
var newArcID = GetOrCreateCleanupArchetype(ref archetype);
|
||||||
if (archetype._cleanupEdge < 0)
|
|
||||||
{
|
|
||||||
ref var signature = ref archetype._signature;
|
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
using var newSignature = new UnsafeBitSet(signature.Count, scope.AllocationHandle);
|
|
||||||
|
|
||||||
var compCount = 0;
|
|
||||||
var it = signature.GetIterator();
|
|
||||||
while (it.Next(out var componentID))
|
|
||||||
{
|
|
||||||
if (ComponentRegistry.GetComponentInfo(componentID).isCleanup)
|
|
||||||
{
|
|
||||||
newSignature.SetBit(componentID);
|
|
||||||
compCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
// Create new archetype
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var i = 0;
|
|
||||||
while (newIt.Next(out var index))
|
|
||||||
{
|
|
||||||
componentTypeIDs[i++] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
archetype._cleanupEdge = newArcID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newArcID = archetype._cleanupEdge;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
||||||
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||||
@@ -361,7 +391,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// Destroy the specified entities.
|
/// Destroy the specified entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entities">The entities to destroy.</param>
|
/// <param name="entities">The entities to destroy.</param>
|
||||||
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
public void DestroyEntities(params ReadOnlySpan<Entity> entities)
|
||||||
{
|
{
|
||||||
if (entities.Length == 0)
|
if (entities.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -394,48 +424,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Archetype has ICleanupComponent — move entity to cleanup archetype.
|
// Archetype has ICleanupComponent — move entity to cleanup archetype.
|
||||||
Identifier<Archetype> newArcID;
|
var newArcID = GetOrCreateCleanupArchetype(ref archetype);
|
||||||
if (archetype._cleanupEdge < 0)
|
|
||||||
{
|
|
||||||
// Compute cleanup edge: build a signature containing only cleanup components.
|
|
||||||
ref var signature = ref archetype._signature;
|
|
||||||
|
|
||||||
using var inner = AllocationManager.CreateStackScope();
|
|
||||||
using var newSignature = new UnsafeBitSet(signature.Count, inner.AllocationHandle);
|
|
||||||
|
|
||||||
var compCount = 0;
|
|
||||||
var it = signature.GetIterator();
|
|
||||||
while (it.Next(out var componentID))
|
|
||||||
{
|
|
||||||
if (ComponentRegistry.GetComponentInfo(componentID).isCleanup)
|
|
||||||
{
|
|
||||||
newSignature.SetBit(componentID);
|
|
||||||
compCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var idx = 0;
|
|
||||||
while (newIt.Next(out var cid))
|
|
||||||
{
|
|
||||||
componentTypeIDs[idx++] = cid;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
archetype._cleanupEdge = newArcID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newArcID = archetype._cleanupEdge;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
||||||
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||||
@@ -717,22 +706,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
newSignature.SetBit(componentID);
|
newSignature.SetBit(componentID);
|
||||||
|
|
||||||
// Find or create new archetype
|
// Find or create new archetype
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
// Create new archetype
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var i = 0;
|
|
||||||
while (newIt.Next(out var index))
|
|
||||||
{
|
|
||||||
componentTypeIDs[i++] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
||||||
}
|
}
|
||||||
@@ -847,22 +821,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find or create new archetype
|
// Find or create new archetype
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
// Create new archetype
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var i = 0;
|
|
||||||
while (newIt.Next(out var index))
|
|
||||||
{
|
|
||||||
componentTypeIDs[i++] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldArchetype.AddEdgeRemove(componentID, newArcID);
|
oldArchetype.AddEdgeRemove(componentID, newArcID);
|
||||||
}
|
}
|
||||||
@@ -1121,20 +1080,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
compCount++;
|
compCount++;
|
||||||
newSignature.SetBit(componentID);
|
newSignature.SetBit(componentID);
|
||||||
|
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var i = 0;
|
|
||||||
while (newIt.Next(out var index))
|
|
||||||
{
|
|
||||||
componentTypeIDs[i++] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
||||||
}
|
}
|
||||||
@@ -1227,20 +1173,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
return DestroyEntity_Internal(entity, location);
|
return DestroyEntity_Internal(entity, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newSignatureHash = newSignature.GetHashCode();
|
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||||
newArcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(newSignatureHash);
|
|
||||||
if (newArcID.IsInvalid)
|
|
||||||
{
|
|
||||||
Span<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[compCount];
|
|
||||||
var newIt = newSignature.GetIterator();
|
|
||||||
var i = 0;
|
|
||||||
while (newIt.Next(out var index))
|
|
||||||
{
|
|
||||||
componentTypeIDs[i++] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
oldArchetype.AddEdgeRemove(componentID, newArcID);
|
oldArchetype.AddEdgeRemove(componentID, newArcID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
public record struct ManagedEntity
|
|
||||||
{
|
|
||||||
public int id;
|
|
||||||
public int generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ManagedEntityRef : IComponent
|
|
||||||
{
|
|
||||||
public ManagedEntity entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class ScriptComponent
|
|
||||||
{
|
|
||||||
internal World _world = null!;
|
|
||||||
internal Entity _entity;
|
|
||||||
internal ManagedEntity _managedEntity;
|
|
||||||
|
|
||||||
public World World => _world;
|
|
||||||
public Entity Entity => _entity;
|
|
||||||
public ManagedEntity ManagedEntity => _managedEntity;
|
|
||||||
|
|
||||||
protected ref T GetComponent<T>()
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
ref var value = ref _world.EntityManager.GetComponent<T>(_entity);
|
|
||||||
if (Unsafe.IsNullRef(ref value))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Entity {_entity} does not have component of type {typeof(T)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ref value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnCreate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnDestroy()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnEnable()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnDisable()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Start()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Update()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void FixedUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void LateUpdate()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -101,6 +101,9 @@ public class WorldTests
|
|||||||
var eB = worldB.EntityManager.CreateEntity();
|
var eB = worldB.EntityManager.CreateEntity();
|
||||||
|
|
||||||
Assert.AreEqual(eA, eB);
|
Assert.AreEqual(eA, eB);
|
||||||
|
// Entity does not store world reference, so we can't directly check if world a has eb or world b has ea, but we can check existence in each world.
|
||||||
|
Assert.IsTrue(_world.EntityManager.Exists(eA));
|
||||||
|
Assert.IsTrue(worldB.EntityManager.Exists(eB));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user