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)]
|
||||
public void Write<T>(T value)
|
||||
public void Write<T>(scoped in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
EnsureCapacity(sizeof(T));
|
||||
@@ -72,7 +72,7 @@ public unsafe struct BufferWriter : IDisposable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Span<byte> AsSpan()
|
||||
{
|
||||
return _buffer.AsSpan();
|
||||
return _buffer.AsSpan(0, Position);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -114,7 +114,7 @@ public unsafe ref struct SpanWriter
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write<T>(T value)
|
||||
public void Write<T>(scoped in T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref _buffer[_position], value);
|
||||
|
||||
@@ -2,7 +2,7 @@ using Ghost.Entities;
|
||||
|
||||
namespace Ghost.Engine.Components;
|
||||
|
||||
public struct SceneID : IComponent // TODO: ISharedComponent
|
||||
public struct SceneID : ISharedComponent
|
||||
{
|
||||
public ushort value;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Ghost.Core.Utilities;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Engine.Streaming;
|
||||
using Ghost.Entities;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Text;
|
||||
@@ -29,7 +28,7 @@ public struct Scene : IEquatable<Scene>
|
||||
/// <summary>
|
||||
/// Gets an invalid scene instance.
|
||||
/// </summary>
|
||||
public static Scene Invalid => new Scene { _id = INVALID_ID };
|
||||
public static Scene Invalid => new() { _id = INVALID_ID };
|
||||
|
||||
internal Scene(ushort id)
|
||||
{
|
||||
@@ -76,105 +75,43 @@ public struct Scene : IEquatable<Scene>
|
||||
/// </remarks>
|
||||
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 PendingEntity : IDisposable
|
||||
{
|
||||
public int fileLocalIndex;
|
||||
public ComponentSet componentSet;
|
||||
public UnsafeList<Identifier<IComponent>> componentTypeIDs;
|
||||
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()
|
||||
{
|
||||
componentTypeIDs.Dispose();
|
||||
for (int i = 0; i < componentData.Count; i++)
|
||||
{
|
||||
componentData[i].data.Dispose();
|
||||
}
|
||||
|
||||
componentSet.Dispose();
|
||||
componentData.Dispose();
|
||||
for (int i = 0; i < entityFields.Count; i++)
|
||||
{
|
||||
entityFields[i].fieldOffsets.Dispose();
|
||||
}
|
||||
entityFields.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public UnsafeList<PendingEntity> entities;
|
||||
public Scene scene;
|
||||
public UnsafeArray<PendingEntity> entities;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
entities[i].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 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 forwardMap = new UnsafeHashMap<int, Entity>(header.entityCount, scope.AllocationHandle);
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
using var str = new UnsafeArray<byte>(128, scope.AllocationHandle);
|
||||
|
||||
for (var i = 0; i < header.entityCount; i++)
|
||||
{
|
||||
var compCount = stream.Read<int>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
@@ -226,119 +179,109 @@ public static class SceneManager
|
||||
}
|
||||
|
||||
var strSpan = str.AsSpan(0, nameLength);
|
||||
stream.ReadExactly(strSpan);
|
||||
stream.ReadExactly(strSpan.Slice(0, nameLength));
|
||||
|
||||
var typeName = Encoding.UTF8.GetString(strSpan);
|
||||
|
||||
var dataSz = stream.Read<int>();
|
||||
var dataOff = stream.Position;
|
||||
stream.Position += dataSz;
|
||||
var compData = new UnsafeArray<byte>(dataSz, allocationHandle);
|
||||
stream.ReadExactly(compData);
|
||||
|
||||
var fieldCount = stream.Read<int>();
|
||||
var fieldOffsets = new UnsafeArray<int>(fieldCount, scope.AllocationHandle);
|
||||
for (var f = 0; f < fieldCount; f++)
|
||||
|
||||
UnsafeArray<int> fieldOffsets = default;
|
||||
if (fieldCount > 0)
|
||||
{
|
||||
fieldOffsets[f] = stream.Read<int>();
|
||||
fieldOffsets = new UnsafeArray<int>(fieldCount, allocationHandle);
|
||||
for (var f = 0; f < fieldCount; f++)
|
||||
{
|
||||
fieldOffsets[f] = stream.Read<int>();
|
||||
}
|
||||
}
|
||||
|
||||
var typeID = ComponentRegistry.GetComponentIDByName(typeName);
|
||||
|
||||
comps[j] = new BinaryEntityInfo.ComponentInfo
|
||||
if (typeID.IsValid)
|
||||
{
|
||||
dataOffset = dataOff,
|
||||
entityFieldOffsets = fieldOffsets,
|
||||
typeHash = typeHash,
|
||||
typeID = typeID,
|
||||
dataSize = dataSz,
|
||||
entityFieldCount = fieldCount,
|
||||
};
|
||||
}
|
||||
|
||||
entityInfos[i] = new BinaryEntityInfo
|
||||
{
|
||||
entityIndex = i,
|
||||
componentCount = compCount,
|
||||
components = comps,
|
||||
};
|
||||
}
|
||||
|
||||
using var typeIds = new UnsafeList<Identifier<IComponent>>(32, scope.AllocationHandle);
|
||||
typeIds.Add(ComponentTypeID<SceneID>.Value);
|
||||
|
||||
for (var i = 0; i < header.entityCount; i++)
|
||||
{
|
||||
ref var info = ref entityInfos[i];
|
||||
|
||||
for (var j = 0; j < info.componentCount; j++)
|
||||
{
|
||||
if (info.components[j].typeID.IsValid)
|
||||
pending.componentTypeIDs.Add(typeID);
|
||||
pending.componentData.Add((typeID, compData));
|
||||
if (fieldCount > 0)
|
||||
{
|
||||
pending.entityFields.Add((pending.componentData.Count - 1, fieldOffsets));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
typeIds.Add(info.components[j].typeID);
|
||||
compData.Dispose();
|
||||
if (fieldCount > 0) fieldOffsets.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
var set = new ComponentSetView(typeIds);
|
||||
result.entities[i] = pending;
|
||||
}
|
||||
|
||||
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);
|
||||
for (int j = 0; j < pending.componentTypeIDs.Count; j++)
|
||||
{
|
||||
typeIds.Add(pending.componentTypeIDs[j]);
|
||||
}
|
||||
|
||||
sharedCom.With(new SceneID { value = scene.ID });
|
||||
|
||||
var set = new ComponentSetView(typeIds, sharedCom);
|
||||
var entity = world.EntityManager.CreateEntity(set);
|
||||
forwardMap.TryAdd(pending.fileLocalIndex, entity);
|
||||
|
||||
forwardMap.TryAdd(i, entity);
|
||||
typeIds.RemoveRange(1, typeIds.Count - 1);
|
||||
sharedCom.Reset();
|
||||
}
|
||||
|
||||
var activeScene = CreateScene();
|
||||
|
||||
for (var i = 0; i < header.entityCount; i++)
|
||||
// Set component data
|
||||
for (var i = 0; i < result.entities.Length; i++)
|
||||
{
|
||||
if (!forwardMap.TryGetValue(i, out var entity))
|
||||
{
|
||||
ref var pending = ref result.entities[i];
|
||||
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||
continue;
|
||||
}
|
||||
|
||||
world.EntityManager.SetComponent(entity, new SceneID { value = activeScene.ID });
|
||||
|
||||
using var compScope = AllocationManager.CreateStackScope();
|
||||
var info = entityInfos[i];
|
||||
|
||||
for (var j = 0; j < info.componentCount; j++)
|
||||
for (var j = 0; j < pending.componentData.Count; j++)
|
||||
{
|
||||
var comp = info.components[j];
|
||||
if (!comp.typeID.IsValid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var compSize = ComponentRegistry.GetComponentInfo(comp.typeID).size;
|
||||
|
||||
stream.Position = comp.dataOffset;
|
||||
|
||||
using var src = stream.ReadMemory(compSize, compScope.AllocationHandle);
|
||||
world.EntityManager.SetComponent(entity, comp.typeID, src.GetUnsafePtr());
|
||||
var (typeID, data) = pending.componentData[j];
|
||||
world.EntityManager.SetComponent(entity, typeID, data.GetUnsafePtr());
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < header.entityCount; i++)
|
||||
// Remap entity references
|
||||
for (var i = 0; i < result.entities.Length; i++)
|
||||
{
|
||||
if (!forwardMap.TryGetValue(i, out var entity))
|
||||
{
|
||||
ref var pending = ref result.entities[i];
|
||||
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||
continue;
|
||||
}
|
||||
|
||||
var info = entityInfos[i];
|
||||
for (var j = 0; j < info.componentCount; j++)
|
||||
for (var j = 0; j < pending.entityFields.Count; j++)
|
||||
{
|
||||
var comp = info.components[j];
|
||||
if (!comp.typeID.IsValid || comp.entityFieldCount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var (componentDataIndex, fieldOffsets) = pending.entityFields[j];
|
||||
var compTypeID = pending.componentData[componentDataIndex].typeID;
|
||||
|
||||
var pComponent = world.EntityManager.GetComponent(entity, comp.typeID);
|
||||
var pComponent = world.EntityManager.GetComponent(entity, compTypeID);
|
||||
if (pComponent == null)
|
||||
{
|
||||
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 fileLocalIndex = *(int*)pField;
|
||||
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>
|
||||
@@ -365,29 +308,18 @@ public static class SceneManager
|
||||
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||
|
||||
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
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var entities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
||||
if (sceneID.value == scene.ID)
|
||||
{
|
||||
if (sceneIDs[i].value == scene.ID)
|
||||
{
|
||||
entitiesToDestroy.Add(entities[i]);
|
||||
}
|
||||
ecb.DestroyEntities(chunk.GetEntities());
|
||||
}
|
||||
}
|
||||
|
||||
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
||||
s_recycledSceneIDs.Enqueue(scene.ID);
|
||||
}
|
||||
|
||||
public static void ReleaseScene(Scene scene)
|
||||
{
|
||||
s_recycledSceneIDs.Enqueue(scene.ID);
|
||||
}
|
||||
|
||||
@@ -408,15 +340,10 @@ public static class SceneManager
|
||||
// Iterate through all matching entities
|
||||
foreach (var chunk in query.GetChunkIterator())
|
||||
{
|
||||
var chunkEntities = chunk.GetEntities();
|
||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
||||
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
||||
if (sceneID.value == scene.ID)
|
||||
{
|
||||
if (sceneIDs[i].value == scene.ID)
|
||||
{
|
||||
entities.Add(chunkEntities[i]);
|
||||
}
|
||||
entities.AddRange(chunk.GetEntities());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,35 @@ using Ghost.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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
|
||||
{
|
||||
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.Utilities;
|
||||
using Ghost.Graphics.Services;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
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
|
||||
{
|
||||
bool HasAsset(Guid guid);
|
||||
@@ -263,5 +236,7 @@ public partial class AssetManager : IDisposable
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Ghost.Engine.Streaming;
|
||||
|
||||
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<UploadableAssetEntry> _pendingUpload;
|
||||
@@ -101,7 +101,7 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
||||
context.CopyPipeline.Begin();
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ghost.Entities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine.Streaming;
|
||||
@@ -21,6 +22,7 @@ internal struct SceneContentHeader
|
||||
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 Result<JobHandle> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType)
|
||||
@@ -56,11 +58,7 @@ public partial class AssetManager
|
||||
world.Reset();
|
||||
}
|
||||
|
||||
var loadResult = SceneManager.LoadSceneIntoWorld(world, header, stream);
|
||||
if (loadResult.IsFailure)
|
||||
{
|
||||
return Result.Failure(loadResult.Message);
|
||||
}
|
||||
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent);
|
||||
|
||||
return JobHandle.Invalid;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
@@ -52,11 +53,6 @@ internal static class ComponentRegistry
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
static ComponentRegistry()
|
||||
{
|
||||
GetOrRegisterComponentID<ManagedEntityRef>();
|
||||
}
|
||||
|
||||
public static unsafe Identifier<IComponent> GetOrRegisterComponentID<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
@@ -278,12 +274,12 @@ public partial class ComponentManager : IDisposable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Clear()
|
||||
{
|
||||
for (int i = 0; i < _archetypes.Count; i++)
|
||||
for (var i = 0; i < _archetypes.Count; i++)
|
||||
{
|
||||
_archetypes[i].Dispose();
|
||||
}
|
||||
|
||||
for (int i = 0; i < _entityQueries.Count; i++)
|
||||
for (var i = 0; i < _entityQueries.Count; i++)
|
||||
{
|
||||
_entityQueries[i].Dispose();
|
||||
}
|
||||
@@ -300,7 +296,7 @@ public partial class ComponentManager : IDisposable
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Collect()
|
||||
{
|
||||
for (int i = 0; i < _archetypes.Count; i++)
|
||||
for (var i = 0; i < _archetypes.Count; i++)
|
||||
{
|
||||
_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>
|
||||
/// Represents an immutable set of component identifiers used to define a group of components within an entity or system.
|
||||
/// </summary>
|
||||
@@ -404,6 +431,11 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
||||
_sharedHashCode = -1;
|
||||
}
|
||||
|
||||
public ComponentSet(AllocationHandle allocationHandle, ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||
: this(allocationHandle, components, sharedComponentSet.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ComponentSetView AsView()
|
||||
{
|
||||
@@ -502,6 +534,11 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
||||
_sharedHashCode = -1;
|
||||
}
|
||||
|
||||
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||
: this(components, sharedComponentSet.AsSpan())
|
||||
{
|
||||
}
|
||||
|
||||
public readonly bool Equals(ComponentSetView other)
|
||||
{
|
||||
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode;
|
||||
|
||||
@@ -12,6 +12,7 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
||||
CreateEntity,
|
||||
CreateEntityWithComponents,
|
||||
DestroyEntity,
|
||||
DestroyEntities,
|
||||
AddComponent,
|
||||
RemoveComponent,
|
||||
SetComponent,
|
||||
@@ -52,6 +53,14 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
||||
_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)]
|
||||
public void AddComponent<T>(Entity entity, T component = default)
|
||||
where T : unmanaged, IComponent
|
||||
@@ -151,6 +160,12 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
||||
entityManager.DestroyEntity(entityToDestroy);
|
||||
break;
|
||||
|
||||
case ECBOpCode.DestroyEntities:
|
||||
var removeCount = reader.Read<int>();
|
||||
var entitiesToRemove = reader.ReadSpan<Entity>(removeCount);
|
||||
entityManager.DestroyEntities(entitiesToRemove);
|
||||
break;
|
||||
|
||||
case ECBOpCode.AddComponent:
|
||||
var entityToAdd = reader.Read<Entity>();
|
||||
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;
|
||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
||||
}
|
||||
|
||||
~EntityManager()
|
||||
@@ -93,6 +92,78 @@ public unsafe partial class EntityManager : IDisposable
|
||||
_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,
|
||||
ref Archetype newArch, int newChunk, int newRow)
|
||||
{
|
||||
@@ -303,48 +374,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
Identifier<Archetype> newArcID = default;
|
||||
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;
|
||||
}
|
||||
var newArcID = GetOrCreateCleanupArchetype(ref archetype);
|
||||
|
||||
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
||||
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||
@@ -361,7 +391,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
/// Destroy the specified entities.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
@@ -394,48 +424,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
else
|
||||
{
|
||||
// Archetype has ICleanupComponent — move entity to cleanup archetype.
|
||||
Identifier<Archetype> newArcID;
|
||||
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;
|
||||
}
|
||||
var newArcID = GetOrCreateCleanupArchetype(ref archetype);
|
||||
|
||||
ref var newArchetype = ref _world.ComponentManager.GetArchetypeReference(newArcID);
|
||||
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||
@@ -717,22 +706,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
newSignature.SetBit(componentID);
|
||||
|
||||
// Find or create new archetype
|
||||
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);
|
||||
}
|
||||
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||
|
||||
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
||||
}
|
||||
@@ -847,22 +821,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
}
|
||||
|
||||
// Find or create new archetype
|
||||
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);
|
||||
}
|
||||
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||
|
||||
oldArchetype.AddEdgeRemove(componentID, newArcID);
|
||||
}
|
||||
@@ -1121,20 +1080,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
compCount++;
|
||||
newSignature.SetBit(componentID);
|
||||
|
||||
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 i = 0;
|
||||
while (newIt.Next(out var index))
|
||||
{
|
||||
componentTypeIDs[i++] = index;
|
||||
}
|
||||
|
||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
||||
}
|
||||
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||
|
||||
oldArchetype.AddEdgeAdd(componentID, newArcID);
|
||||
}
|
||||
@@ -1227,20 +1173,7 @@ public unsafe partial class EntityManager : IDisposable
|
||||
return DestroyEntity_Internal(entity, location);
|
||||
}
|
||||
|
||||
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 i = 0;
|
||||
while (newIt.Next(out var index))
|
||||
{
|
||||
componentTypeIDs[i++] = index;
|
||||
}
|
||||
|
||||
newArcID = _world.ComponentManager.CreateArchetype(componentTypeIDs, newSignatureHash);
|
||||
}
|
||||
newArcID = FindOrCreateArchetype(ref newSignature, compCount);
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user