fix(scene): avoid scene ID collision on load
Do not serialize the SceneID component. Generate a new SceneID when loading a scene file dynamically to prevent ID collisions. Update related tests and scene graph builders.
This commit is contained in:
@@ -99,7 +99,7 @@ Used for the **initial full build** of the scene graph from an ECS `World`. Afte
|
|||||||
**Algorithm:**
|
**Algorithm:**
|
||||||
|
|
||||||
1. Query all entities with `SceneID` component.
|
1. Query all entities with `SceneID` component.
|
||||||
2. Group entities by `SceneID.scene.ID`.
|
2. Group entities by `SceneID.scene.id`.
|
||||||
3. For each scene group:
|
3. For each scene group:
|
||||||
- Create a `SceneNode` (name comes from editor metadata, not from runtime).
|
- Create a `SceneNode` (name comes from editor metadata, not from runtime).
|
||||||
- Walk the `Hierarchy` component linked-list to build the tree:
|
- Walk the `Hierarchy` component linked-list to build the tree:
|
||||||
@@ -238,7 +238,7 @@ public class EditorWorldService : IDisposable
|
|||||||
|
|
||||||
1. Check `EditorWorld.Version` — if unchanged, skip.
|
1. Check `EditorWorld.Version` — if unchanged, skip.
|
||||||
2. Query all entities with `SceneID` component from the editor world.
|
2. Query all entities with `SceneID` component from the editor world.
|
||||||
3. Group by `SceneID.scene.ID`.
|
3. Group by `SceneID.scene.id`.
|
||||||
4. **For each scene group:**
|
4. **For each scene group:**
|
||||||
- Find or create the `SceneNode` in `RootNodes` (match by `Scene.ID`).
|
- Find or create the `SceneNode` in `RootNodes` (match by `Scene.ID`).
|
||||||
- Walk the `Hierarchy` linked-list of roots.
|
- Walk the `Hierarchy` linked-list of roots.
|
||||||
|
|||||||
@@ -151,6 +151,6 @@ public static class SceneGraphBuilder
|
|||||||
|
|
||||||
private static string GetDefaultSceneName(Scene scene)
|
private static string GetDefaultSceneName(Scene scene)
|
||||||
{
|
{
|
||||||
return $"NewScene ({scene.ID})";
|
return $"NewScene ({scene.id})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ public class EditorWorldService : IDisposable
|
|||||||
public EditorWorldService()
|
public EditorWorldService()
|
||||||
{
|
{
|
||||||
EditorWorld = World.Create(entityCapacity: DEFAULT_ENTITY_CAPACITY);
|
EditorWorld = World.Create(entityCapacity: DEFAULT_ENTITY_CAPACITY);
|
||||||
CreateDefaultScene();
|
// CreateDefaultScene();
|
||||||
RebuildSceneGraph();
|
// RebuildSceneGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateDefaultScene()
|
public void CreateDefaultScene()
|
||||||
{
|
{
|
||||||
var scene = SceneManager.CreateScene();
|
var scene = SceneManager.CreateScene();
|
||||||
var entity = EditorWorld.EntityManager.CreateEntity();
|
var entity = EditorWorld.EntityManager.CreateEntity();
|
||||||
EditorWorld.EntityManager.AddComponent(entity, new Ghost.Engine.Components.SceneID
|
EditorWorld.EntityManager.AddComponent(entity, new Engine.Components.SceneID
|
||||||
{
|
{
|
||||||
scene = scene
|
scene = scene
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class SceneGraphSyncService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var sceneName = $"NewScene ({scene.ID})";
|
var sceneName = $"NewScene ({scene.id})";
|
||||||
var newSceneNode = new SceneNode(world, scene, sceneName);
|
var newSceneNode = new SceneNode(world, scene, sceneName);
|
||||||
rootNodes.Add(newSceneNode);
|
rootNodes.Add(newSceneNode);
|
||||||
return newSceneNode;
|
return newSceneNode;
|
||||||
|
|||||||
@@ -136,29 +136,6 @@ public class SceneSerializationService : IDisposable
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] GetEntityFieldOffsetsFromJson(string typeName, string componentJson)
|
|
||||||
{
|
|
||||||
var type = Type.GetType(typeName);
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
return Array.Empty<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var entityFields = GetEntityFields(type);
|
|
||||||
if (entityFields.Length == 0)
|
|
||||||
{
|
|
||||||
return Array.Empty<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var offsets = new int[entityFields.Length];
|
|
||||||
for (var i = 0; i < entityFields.Length; i++)
|
|
||||||
{
|
|
||||||
offsets[i] = (int)Marshal.OffsetOf(type, entityFields[i].Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe void SerializeToBinary(SceneSaveData data, Stream targetStream)
|
public static unsafe void SerializeToBinary(SceneSaveData data, Stream targetStream)
|
||||||
{
|
{
|
||||||
using var writer = new BinaryWriter(targetStream, Encoding.UTF8, true);
|
using var writer = new BinaryWriter(targetStream, Encoding.UTF8, true);
|
||||||
@@ -185,7 +162,11 @@ public class SceneSerializationService : IDisposable
|
|||||||
foreach (var (typeName, componentElement) in entity.Components)
|
foreach (var (typeName, componentElement) in entity.Components)
|
||||||
{
|
{
|
||||||
var typeHash = GetTypeNameHash(typeName);
|
var typeHash = GetTypeNameHash(typeName);
|
||||||
var componentType = Type.GetType(typeName);
|
var componentType = TypeCache.GetTypes(typeName);
|
||||||
|
if (componentType == typeof(SceneID))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (componentType == null)
|
if (componentType == null)
|
||||||
{
|
{
|
||||||
@@ -276,7 +257,7 @@ public class SceneSerializationService : IDisposable
|
|||||||
|
|
||||||
#region Load Scene into Editor World
|
#region Load Scene into Editor World
|
||||||
|
|
||||||
public unsafe Result LoadSceneIntoEditorWorld(SceneSaveData data, SceneLoadingType loadingType = SceneLoadingType.Single)
|
public unsafe Result<Scene> LoadSceneIntoEditorWorld(SceneSaveData data, SceneLoadingType loadingType = SceneLoadingType.Single)
|
||||||
{
|
{
|
||||||
if (loadingType == SceneLoadingType.Single)
|
if (loadingType == SceneLoadingType.Single)
|
||||||
{
|
{
|
||||||
@@ -284,6 +265,7 @@ public class SceneSerializationService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
|
var activeScene = SceneManager.CreateScene();
|
||||||
|
|
||||||
var entityCount = data.Entities.Count;
|
var entityCount = data.Entities.Count;
|
||||||
if (entityCount == 0)
|
if (entityCount == 0)
|
||||||
@@ -307,6 +289,8 @@ public class SceneSerializationService : IDisposable
|
|||||||
var entityData = data.Entities[fileIndex];
|
var entityData = data.Entities[fileIndex];
|
||||||
ref var list = ref typeIds[fileIndex];
|
ref var list = ref typeIds[fileIndex];
|
||||||
|
|
||||||
|
list.Add(ComponentRegistry.GetOrRegisterComponentID<SceneID>());
|
||||||
|
|
||||||
foreach (var (typeName, _) in entityData.Components)
|
foreach (var (typeName, _) in entityData.Components)
|
||||||
{
|
{
|
||||||
var compId = ComponentRegistry.GetComponentIDByName(typeName);
|
var compId = ComponentRegistry.GetComponentIDByName(typeName);
|
||||||
@@ -324,12 +308,7 @@ public class SceneSerializationService : IDisposable
|
|||||||
list.Add(compId);
|
list.Add(compId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count == 0)
|
var componentSet = new ComponentSetView(list);
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var componentSet = new ComponentSet(scope.AllocationHandle, list);
|
|
||||||
var entity = world.EntityManager.CreateEntity(componentSet);
|
var entity = world.EntityManager.CreateEntity(componentSet);
|
||||||
forwardMap[fileIndex] = entity;
|
forwardMap[fileIndex] = entity;
|
||||||
}
|
}
|
||||||
@@ -342,9 +321,11 @@ public class SceneSerializationService : IDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
world.EntityManager.SetComponent(entity, new SceneID { scene = activeScene });
|
||||||
|
|
||||||
var entityData = data.Entities[fileIndex];
|
var entityData = data.Entities[fileIndex];
|
||||||
ref var list = ref typeIds[fileIndex];
|
ref var list = ref typeIds[fileIndex];
|
||||||
var idx = 0;
|
var idx = 1;
|
||||||
|
|
||||||
foreach (var (typeName, componentElement) in entityData.Components)
|
foreach (var (typeName, componentElement) in entityData.Components)
|
||||||
{
|
{
|
||||||
@@ -384,7 +365,7 @@ public class SceneSerializationService : IDisposable
|
|||||||
|
|
||||||
RebuildAndReturn:
|
RebuildAndReturn:
|
||||||
_worldService.RebuildSceneGraph();
|
_worldService.RebuildSceneGraph();
|
||||||
return Result.Success();
|
return activeScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Identifier<IComponent> RegisterComponentByType(Type type)
|
private static Identifier<IComponent> RegisterComponentByType(Type type)
|
||||||
@@ -489,6 +470,11 @@ public class SceneSerializationService : IDisposable
|
|||||||
foreach (var layout in archetype._layouts)
|
foreach (var layout in archetype._layouts)
|
||||||
{
|
{
|
||||||
var type = ComponentRegistry.s_runtimeIDToType[layout.componentID];
|
var type = ComponentRegistry.s_runtimeIDToType[layout.componentID];
|
||||||
|
if (type == typeof(SceneID))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var fullName = type.FullName ?? type.Name;
|
var fullName = type.FullName ?? type.Name;
|
||||||
var compInfo = ComponentRegistry.GetComponentInfo(layout.componentID);
|
var compInfo = ComponentRegistry.GetComponentInfo(layout.componentID);
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ public static class TypeCache
|
|||||||
return s_types;
|
return s_types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TypeInfo? GetTypes(string typeFullName)
|
||||||
|
{
|
||||||
|
return s_types.FirstOrDefault(t => t.FullName == typeFullName);
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<MethodInfo>? GetMethodsWithAttribute<T>()
|
public static IEnumerable<MethodInfo>? GetMethodsWithAttribute<T>()
|
||||||
where T : DiscoverableAttributeBase
|
where T : DiscoverableAttributeBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.6" />
|
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.6" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.24">
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.26">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Utilities;
|
||||||
|
using Ghost.Engine.Components;
|
||||||
|
using Ghost.Engine.Core;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
namespace Ghost.Engine;
|
||||||
@@ -9,19 +13,11 @@ internal partial class AssetEntry
|
|||||||
{
|
{
|
||||||
private static void RegisterSceneCallback()
|
private static void RegisterSceneCallback()
|
||||||
{
|
{
|
||||||
s_onCreation[(int)AssetType.Scene] = static (e) =>
|
s_onCreation[(int)AssetType.Scene] = null;
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
s_onParseRawData[(int)AssetType.Scene] = static (e) => e.ParseSceneData();
|
s_onParseRawData[(int)AssetType.Scene] = static (e) => e.ParseSceneData();
|
||||||
s_onRecordUpload[(int)AssetType.Scene] = static (e, ctx) => Result.Success();
|
s_onRecordUpload[(int)AssetType.Scene] = static (e, ctx) => Result.Success();
|
||||||
s_onUploadComplete[(int)AssetType.Scene] = static (e, ctx) =>
|
s_onUploadComplete[(int)AssetType.Scene] = null;
|
||||||
{
|
s_onReleaseResource[(int)AssetType.Scene] = null;
|
||||||
};
|
|
||||||
|
|
||||||
s_onReleaseResource[(int)AssetType.Scene] = static (e) =>
|
|
||||||
{
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe Result ParseSceneData()
|
private unsafe Result ParseSceneData()
|
||||||
@@ -73,69 +69,102 @@ internal partial class AssetManager
|
|||||||
|
|
||||||
public static class SceneLoader
|
public static class SceneLoader
|
||||||
{
|
{
|
||||||
private struct BinaryEntityInfo
|
private struct BinaryEntityInfo : IDisposable
|
||||||
{
|
{
|
||||||
public int entityIndex;
|
public int entityIndex;
|
||||||
public int componentCount;
|
public int componentCount;
|
||||||
public struct ComponentInfo
|
public struct ComponentInfo
|
||||||
{
|
{
|
||||||
public uint typeHash;
|
public uint typeHash;
|
||||||
public string typeName;
|
|
||||||
public Identifier<IComponent> typeID;
|
public Identifier<IComponent> typeID;
|
||||||
public int dataSize;
|
public int dataSize;
|
||||||
public int dataOffset;
|
public int dataOffset;
|
||||||
public int entityFieldCount;
|
public int entityFieldCount;
|
||||||
public int[] entityFieldOffsets;
|
public UnsafeArray<int> entityFieldOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentInfo[] components;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe Result<int> LoadSceneIntoWorld(World world, void* pRawData, int dataSize)
|
public static unsafe Result<int> LoadSceneIntoWorld(World world, void* pRawData, int dataSize)
|
||||||
{
|
{
|
||||||
RegisterKnownComponentTypes();
|
var reader = new SpanReader(new ReadOnlySpan<byte>(pRawData, dataSize));
|
||||||
|
|
||||||
var pData = (byte*)pRawData;
|
var magic = Encoding.UTF8.GetString(reader.ReadSpan<byte>(4));
|
||||||
var offset = 0;
|
|
||||||
|
|
||||||
var magic = Encoding.UTF8.GetString(pData + offset, 4);
|
|
||||||
offset += 4;
|
|
||||||
if (magic != "GSCN")
|
if (magic != "GSCN")
|
||||||
{
|
{
|
||||||
return Result.Failure("Invalid scene binary magic.");
|
return Result.Failure("Invalid scene binary magic.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = ReadInt32(pData, ref offset);
|
var version = reader.Read<int>();
|
||||||
if (version != 1)
|
if (version != 1)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Unsupported scene binary version: {version}");
|
return Result.Failure($"Unsupported scene binary version: {version}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var entityCount = ReadInt32(pData, ref offset);
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var entityInfos = new BinaryEntityInfo[entityCount];
|
|
||||||
var forwardMap = new Dictionary<int, Entity>(entityCount);
|
var entityCount = reader.Read<int>();
|
||||||
|
using var entityInfos = new BinaryEntityInfoArray(entityCount, scope.AllocationHandle);
|
||||||
|
using var forwardMap = new UnsafeHashMap<int, Entity>(entityCount, scope.AllocationHandle);
|
||||||
|
|
||||||
for (var i = 0; i < entityCount; i++)
|
for (var i = 0; i < entityCount; i++)
|
||||||
{
|
{
|
||||||
var compCount = ReadInt32(pData, ref offset);
|
var compCount = reader.Read<int>();
|
||||||
var comps = new BinaryEntityInfo.ComponentInfo[compCount];
|
if (compCount == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var comps = new UnsafeArray<BinaryEntityInfo.ComponentInfo>(compCount, scope.AllocationHandle);
|
||||||
|
|
||||||
for (var j = 0; j < compCount; j++)
|
for (var j = 0; j < compCount; j++)
|
||||||
{
|
{
|
||||||
var typeHash = (uint)ReadInt32(pData, ref offset);
|
var typeHash = reader.Read<uint>();
|
||||||
var nameLength = ReadInt32(pData, ref offset);
|
var nameLength = reader.Read<int>();
|
||||||
var typeName = Encoding.UTF8.GetString(pData + offset, nameLength);
|
var typeName = Encoding.UTF8.GetString(reader.ReadSpan<byte>(nameLength));
|
||||||
offset += nameLength;
|
|
||||||
|
|
||||||
var dataSz = ReadInt32(pData, ref offset);
|
var dataSz = reader.Read<int>();
|
||||||
var dataOff = offset;
|
var dataOff = reader.Position;
|
||||||
offset += dataSz;
|
reader.Position += dataSz;
|
||||||
|
|
||||||
var fieldCount = ReadInt32(pData, ref offset);
|
var fieldCount = reader.Read<int>();
|
||||||
var fieldOffsets = new int[fieldCount];
|
var fieldOffsets = new UnsafeArray<int>(fieldCount, scope.AllocationHandle);
|
||||||
for (var f = 0; f < fieldCount; f++)
|
for (var f = 0; f < fieldCount; f++)
|
||||||
{
|
{
|
||||||
fieldOffsets[f] = ReadInt32(pData, ref offset);
|
fieldOffsets[f] = reader.Read<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeID = ComponentRegistry.GetComponentIDByName(typeName);
|
var typeID = ComponentRegistry.GetComponentIDByName(typeName);
|
||||||
@@ -143,7 +172,6 @@ public static class SceneLoader
|
|||||||
comps[j] = new BinaryEntityInfo.ComponentInfo
|
comps[j] = new BinaryEntityInfo.ComponentInfo
|
||||||
{
|
{
|
||||||
typeHash = typeHash,
|
typeHash = typeHash,
|
||||||
typeName = typeName,
|
|
||||||
typeID = typeID,
|
typeID = typeID,
|
||||||
dataSize = dataSz,
|
dataSize = dataSz,
|
||||||
dataOffset = dataOff,
|
dataOffset = dataOff,
|
||||||
@@ -160,34 +188,31 @@ public static class SceneLoader
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var pTypeIds = stackalloc Identifier<IComponent>[32];
|
using var typeIds = new UnsafeList<Identifier<IComponent>>(32, scope.AllocationHandle);
|
||||||
|
typeIds.Add(ComponentTypeID<SceneID>.Value);
|
||||||
|
|
||||||
for (var i = 0; i < entityCount; i++)
|
for (var i = 0; i < entityCount; i++)
|
||||||
{
|
{
|
||||||
var info = entityInfos[i];
|
ref var info = ref entityInfos[i];
|
||||||
var validCount = 0;
|
|
||||||
|
|
||||||
for (var j = 0; j < info.componentCount; j++)
|
for (var j = 0; j < info.componentCount; j++)
|
||||||
{
|
{
|
||||||
if (info.components[j].typeID.IsValid)
|
if (info.components[j].typeID.IsValid)
|
||||||
{
|
{
|
||||||
if (validCount < 32)
|
typeIds.Add(info.components[j].typeID);
|
||||||
{
|
|
||||||
pTypeIds[validCount] = info.components[j].typeID;
|
|
||||||
}
|
|
||||||
|
|
||||||
validCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validCount > 0 && validCount <= 32)
|
var set = new ComponentSetView(typeIds);
|
||||||
{
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
using var set = new ComponentSet(scope.AllocationHandle, new ReadOnlySpan<Identifier<IComponent>>(pTypeIds, validCount));
|
|
||||||
var entity = world.EntityManager.CreateEntity(set);
|
var entity = world.EntityManager.CreateEntity(set);
|
||||||
forwardMap[i] = entity;
|
|
||||||
}
|
forwardMap.TryAdd(i, entity);
|
||||||
|
typeIds.RemoveRange(1, typeIds.Count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var activeScene = SceneManager.CreateScene();
|
||||||
|
|
||||||
for (var i = 0; i < entityCount; i++)
|
for (var i = 0; i < entityCount; i++)
|
||||||
{
|
{
|
||||||
if (!forwardMap.TryGetValue(i, out var entity))
|
if (!forwardMap.TryGetValue(i, out var entity))
|
||||||
@@ -195,6 +220,8 @@ public static class SceneLoader
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
world.EntityManager.SetComponent(entity, new SceneID { scene = activeScene });
|
||||||
|
|
||||||
var info = entityInfos[i];
|
var info = entityInfos[i];
|
||||||
for (var j = 0; j < info.componentCount; j++)
|
for (var j = 0; j < info.componentCount; j++)
|
||||||
{
|
{
|
||||||
@@ -205,7 +232,7 @@ public static class SceneLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
var compSize = ComponentRegistry.GetComponentInfo(comp.typeID).size;
|
var compSize = ComponentRegistry.GetComponentInfo(comp.typeID).size;
|
||||||
var pSrc = pData + comp.dataOffset;
|
var pSrc = (byte*)pRawData + comp.dataOffset;
|
||||||
|
|
||||||
world.EntityManager.SetComponent(entity, comp.typeID, pSrc);
|
world.EntityManager.SetComponent(entity, comp.typeID, pSrc);
|
||||||
}
|
}
|
||||||
@@ -250,18 +277,4 @@ public static class SceneLoader
|
|||||||
|
|
||||||
return Result.Success(entityCount);
|
return Result.Success(entityCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterKnownComponentTypes()
|
|
||||||
{
|
|
||||||
_ = ComponentTypeID<Ghost.Engine.Components.Hierarchy>.Value;
|
|
||||||
_ = ComponentTypeID<Ghost.Engine.Components.LocalToWorld>.Value;
|
|
||||||
_ = ComponentTypeID<Ghost.Engine.Components.SceneID>.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe int ReadInt32(byte* pData, ref int offset)
|
|
||||||
{
|
|
||||||
var value = *(int*)(pData + offset);
|
|
||||||
offset += 4;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ public interface IContentProvider
|
|||||||
|
|
||||||
internal partial class AssetEntry
|
internal partial class AssetEntry
|
||||||
{
|
{
|
||||||
private static readonly Action<AssetEntry>[] s_onCreation = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
private static readonly Action<AssetEntry>?[] s_onCreation = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
||||||
private static readonly Func<AssetEntry, Result>[] s_onParseRawData = new Func<AssetEntry, Result>[(int)AssetType.Unknown + 1];
|
private static readonly Func<AssetEntry, Result>?[] s_onParseRawData = new Func<AssetEntry, Result>[(int)AssetType.Unknown + 1];
|
||||||
private static readonly Func<AssetEntry, ResourceStreamingContext, Result>[] s_onRecordUpload = new Func<AssetEntry, ResourceStreamingContext, Result>[(int)AssetType.Unknown + 1];
|
private static readonly Func<AssetEntry, ResourceStreamingContext, Result>?[] s_onRecordUpload = new Func<AssetEntry, ResourceStreamingContext, Result>[(int)AssetType.Unknown + 1];
|
||||||
private static readonly Action<AssetEntry, ResourceStreamingContext>[] s_onUploadComplete = new Action<AssetEntry, ResourceStreamingContext>[(int)AssetType.Unknown + 1];
|
private static readonly Action<AssetEntry, ResourceStreamingContext>?[] s_onUploadComplete = new Action<AssetEntry, ResourceStreamingContext>[(int)AssetType.Unknown + 1];
|
||||||
private static readonly Action<AssetEntry>[] s_onReleaseResource = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
private static readonly Action<AssetEntry>?[] s_onReleaseResource = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
||||||
|
|
||||||
static AssetEntry()
|
static AssetEntry()
|
||||||
{
|
{
|
||||||
@@ -321,7 +321,7 @@ internal partial class AssetManager : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal bool RemoveEntry(Guid guid)
|
internal bool RemoveEntry(Guid guid)
|
||||||
{
|
{
|
||||||
return _entries.TryRemove(guid, out var entry);
|
return _entries.TryRemove(guid, out var _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureScheduled(AssetEntry entry)
|
private void EnsureScheduled(AssetEntry entry)
|
||||||
|
|||||||
@@ -8,35 +8,24 @@ namespace Ghost.Engine.Core;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a runtime scene - a collection of entities with the same SceneID.
|
/// Represents a runtime scene - a collection of entities with the same SceneID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct Scene : IEquatable<Scene>
|
public struct Scene : IEquatable<Scene>
|
||||||
{
|
{
|
||||||
private readonly short _id;
|
public byte id;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the unique identifier of this scene.
|
|
||||||
/// </summary>
|
|
||||||
public short ID => _id;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether this scene is valid.
|
/// Gets whether this scene is valid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsValid => _id >= 0;
|
public readonly bool IsValid => id != 255;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an invalid scene instance.
|
/// Gets an invalid scene instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Scene Invalid => new(-1);
|
public static Scene Invalid => new Scene { id = 255 };
|
||||||
|
|
||||||
[JsonConstructor]
|
public readonly bool Equals(Scene other)
|
||||||
public Scene(short id)
|
|
||||||
{
|
{
|
||||||
_id = id;
|
return id == other.id;
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(Scene other)
|
|
||||||
{
|
|
||||||
return _id == other._id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
@@ -44,9 +33,9 @@ public readonly struct Scene : IEquatable<Scene>
|
|||||||
return obj is Scene other && Equals(other);
|
return obj is Scene other && Equals(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public readonly override int GetHashCode()
|
||||||
{
|
{
|
||||||
return _id.GetHashCode();
|
return id.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(Scene left, Scene right)
|
public static bool operator ==(Scene left, Scene right)
|
||||||
@@ -59,9 +48,9 @@ public readonly struct Scene : IEquatable<Scene>
|
|||||||
return !left.Equals(right);
|
return !left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public readonly override string ToString()
|
||||||
{
|
{
|
||||||
return $"Scene(ID: {_id})";
|
return $"Scene(ID: {id})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +63,8 @@ public readonly struct Scene : IEquatable<Scene>
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static class SceneManager
|
public static class SceneManager
|
||||||
{
|
{
|
||||||
private static short s_nextSceneID;
|
private static byte s_nextSceneID;
|
||||||
private static readonly Queue<short> s_recycledSceneIDs = new();
|
private static readonly Queue<byte> s_recycledSceneIDs = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new scene in the world.
|
/// Creates a new scene in the world.
|
||||||
@@ -88,7 +77,7 @@ public static class SceneManager
|
|||||||
id = s_nextSceneID++;
|
id = s_nextSceneID++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Scene(id);
|
return new Scene { id = id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -112,7 +101,7 @@ public static class SceneManager
|
|||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
for (var i = 0; i < chunk.EntityCount; i++)
|
||||||
{
|
{
|
||||||
if (sceneIDs[i].scene.ID == scene.ID)
|
if (sceneIDs[i].scene.id == scene.id)
|
||||||
{
|
{
|
||||||
entitiesToDestroy.Add(entities[i]);
|
entitiesToDestroy.Add(entities[i]);
|
||||||
}
|
}
|
||||||
@@ -120,7 +109,7 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
||||||
s_recycledSceneIDs.Enqueue(scene.ID);
|
s_recycledSceneIDs.Enqueue(scene.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -145,7 +134,7 @@ public static class SceneManager
|
|||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
for (var i = 0; i < chunk.EntityCount; i++)
|
||||||
{
|
{
|
||||||
if (sceneIDs[i].scene.ID == scene.ID)
|
if (sceneIDs[i].scene.id == scene.id)
|
||||||
{
|
{
|
||||||
entities.Add(chunkEntities[i]);
|
entities.Add(chunkEntities[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -331,6 +331,11 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
return _hashCode;
|
return _hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_components.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public override readonly bool Equals(object? obj)
|
public override readonly bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
return obj is ComponentSet set && Equals(set);
|
return obj is ComponentSet set && Equals(set);
|
||||||
@@ -346,8 +351,53 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
return !(left == right);
|
return !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public static implicit operator ComponentSetView(in ComponentSet set)
|
||||||
{
|
{
|
||||||
_components.Dispose();
|
return new ComponentSetView(set.Components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
||||||
|
{
|
||||||
|
private readonly ReadOnlySpan<Identifier<IComponent>> _components;
|
||||||
|
private int _hashCode;
|
||||||
|
|
||||||
|
public readonly ReadOnlySpan<Identifier<IComponent>> Components => _components;
|
||||||
|
|
||||||
|
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components)
|
||||||
|
{
|
||||||
|
_components = components;
|
||||||
|
_hashCode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(ComponentSetView other)
|
||||||
|
{
|
||||||
|
return _hashCode == other._hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
if (_hashCode == -1)
|
||||||
|
{
|
||||||
|
_hashCode = ComponentRegistry.GetHashCode(_components);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(ComponentSetView left, ComponentSetView right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(ComponentSetView left, ComponentSetView right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
||||||
/// <returns>The created entity.</returns>
|
/// <returns>The created entity.</returns>
|
||||||
public Entity CreateEntity(ComponentSet set)
|
public Entity CreateEntity(ComponentSetView set)
|
||||||
{
|
{
|
||||||
var entities = (Span<Entity>)stackalloc Entity[1];
|
var entities = (Span<Entity>)stackalloc Entity[1];
|
||||||
CreateEntities(entities, set);
|
CreateEntities(entities, set);
|
||||||
@@ -187,7 +187,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <param name="entities">The span to store the created entities.</param>
|
/// <param name="entities">The span to store the created entities.</param>
|
||||||
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
||||||
/// <returns>An array of the created entities.</returns>
|
/// <returns>An array of the created entities.</returns>
|
||||||
public void CreateEntities(Span<Entity> entities, ComponentSet set)
|
public void CreateEntities(Span<Entity> entities, ComponentSetView set)
|
||||||
{
|
{
|
||||||
var hash = set.GetHashCode();
|
var hash = set.GetHashCode();
|
||||||
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
|
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
|
||||||
@@ -222,7 +222,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">The number of entities to create.</param>
|
/// <param name="count">The number of entities to create.</param>
|
||||||
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
||||||
public void CreateEntities(int count, ComponentSet set)
|
public void CreateEntities(int count, ComponentSetView set)
|
||||||
{
|
{
|
||||||
var hash = set.GetHashCode();
|
var hash = set.GetHashCode();
|
||||||
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
|
var arcID = _world.ComponentManager.GetArchetypeIDBySignatureHash(hash);
|
||||||
|
|||||||
@@ -117,16 +117,18 @@ public class SceneSerializationTests
|
|||||||
Assert.AreEqual(3, data.Entities.Count, $"data contained {data.Entities.Count} entities");
|
Assert.AreEqual(3, data.Entities.Count, $"data contained {data.Entities.Count} entities");
|
||||||
foreach (var ent in data.Entities)
|
foreach (var ent in data.Entities)
|
||||||
{
|
{
|
||||||
Assert.IsTrue(ent.Components.Count > 0, "Entity has no components");
|
Assert.IsTrue(ent.Components.Count >= 0, "Entity has no components"); // Can be 0 because we might have entities without components, but should not be negative
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadResult = _serializationService.LoadSceneIntoEditorWorld(data);
|
var loadResult = _serializationService.LoadSceneIntoEditorWorld(data);
|
||||||
Assert.IsTrue(loadResult.IsSuccess, loadResult.Message);
|
Assert.IsTrue(loadResult.IsSuccess, loadResult.Message);
|
||||||
|
|
||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
|
scene = loadResult.Value;
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
using var entities = SceneManager.GetSceneEntities(scene, world, scope.AllocationHandle);
|
using var entities = SceneManager.GetSceneEntities(scene, world, scope.AllocationHandle);
|
||||||
Assert.AreEqual(3, entities.Count, $"Expected 3 entities for scene {scene.ID} but found {entities.Count}");
|
Assert.AreEqual(3, entities.Count, $"Expected 3 entities for scene {scene.id} but found {entities.Count}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -271,8 +273,7 @@ public class SceneSerializationTests
|
|||||||
initialCount += chunk.EntityCount;
|
initialCount += chunk.EntityCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditorWorldService creates 1 default scene entity, plus 4 from this test = 5
|
Assert.AreEqual(4, initialCount, "Expected 4 entities.");
|
||||||
Assert.AreEqual(5, initialCount, "Expected 5 entities (1 default + 4 from test).");
|
|
||||||
|
|
||||||
var filePath = Path.Combine(_projectRoot, "SingleLoad.gscene");
|
var filePath = Path.Combine(_projectRoot, "SingleLoad.gscene");
|
||||||
_serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
_serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
||||||
|
|||||||
Reference in New Issue
Block a user