Compare commits
2 Commits
84c936bb7a
...
7dac1e4437
| Author | SHA1 | Date | |
|---|---|---|---|
| 7dac1e4437 | |||
| e04c7eb6a7 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,7 +13,7 @@
|
|||||||
AGENTS.md
|
AGENTS.md
|
||||||
.opencode/
|
.opencode/
|
||||||
.code-review-graph/
|
.code-review-graph/
|
||||||
.github/instructions/
|
.antigravitycli/
|
||||||
|
|
||||||
ref/
|
ref/
|
||||||
docfx/
|
docfx/
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ public readonly struct ComponentObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ref T GetData<T>()
|
public ref T GetData<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return ref _world.EntityManager.GetComponent<T>(_entity);
|
return ref _world.EntityManager.GetComponent<T>(_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetData<T>(in T data)
|
public void SetData<T>(in T data)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
_world.EntityManager.SetComponent(_entity, data);
|
_world.EntityManager.SetComponent(_entity, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,20 +31,19 @@ public static class SceneGraphBuilder
|
|||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
var entities = chunk.GetEntities();
|
var entities = chunk.GetEntities();
|
||||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
var scene = chunk.GetSharedComponent<SceneID>();
|
||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
for (var i = 0; i < chunk.EntityCount; i++)
|
||||||
{
|
{
|
||||||
var s = sceneIDs[i].value;
|
if (scene.value == Scene.INVALID_ID)
|
||||||
if (s == Scene.INVALID_ID)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sceneMap.TryGetValue(s, out var list))
|
if (!sceneMap.TryGetValue(scene.value, out var list))
|
||||||
{
|
{
|
||||||
list = new List<Entity>();
|
list = new List<Entity>();
|
||||||
sceneMap[s] = list;
|
sceneMap[scene.value] = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(entities[i]);
|
list.Add(entities[i]);
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ using Ghost.Core.Graphics;
|
|||||||
using Ghost.Editor.Core.Assets;
|
using Ghost.Editor.Core.Assets;
|
||||||
using Ghost.Editor.Core.Contracts;
|
using Ghost.Editor.Core.Contracts;
|
||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using Ghost.Engine;
|
|
||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Services;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -19,13 +16,13 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
private readonly IAssetRegistry _assetRegistry;
|
private readonly IAssetRegistry _assetRegistry;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IShaderCompiler _compiler;
|
private readonly IShaderCompiler _compiler;
|
||||||
private EngineCore? _engineCore;
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ulong, Guid> _shaderIdToAssetId = new();
|
private readonly ConcurrentDictionary<ulong, Guid> _shaderIdToAssetId = new();
|
||||||
private readonly ConcurrentDictionary<Guid, Dictionary<int, string>[]> _assetKeywordMappings = new();
|
private readonly ConcurrentDictionary<Guid, Dictionary<int, string>[]> _assetKeywordMappings = new();
|
||||||
private Task? _shaderDictionaryPopulated;
|
private Task? _shaderDictionaryPopulated;
|
||||||
|
|
||||||
public event Action<Key64<ShaderVariant>, ulong>? OnShaderVariantCompiled;
|
public event ShaderVariantCompiledHandler? OnShaderVariantCompiled;
|
||||||
|
public event Action<ulong>? OnShaderInvalidated;
|
||||||
|
|
||||||
public EditorShaderCompilerBridge(IAssetRegistry assetRegistry, IServiceProvider serviceProvider)
|
public EditorShaderCompilerBridge(IAssetRegistry assetRegistry, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
@@ -50,13 +47,7 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
_shaderIdToAssetId[nameHash] = guid;
|
_shaderIdToAssetId[nameHash] = guid;
|
||||||
BuildKeywordMappings(result.Value, guid);
|
BuildKeywordMappings(result.Value, guid);
|
||||||
|
|
||||||
_engineCore ??= _serviceProvider.GetService<EngineCore>();
|
OnShaderInvalidated?.Invoke(nameHash);
|
||||||
if (_engineCore != null)
|
|
||||||
{
|
|
||||||
var shaderLibrary = _engineCore.RenderSystem.ShaderLibrary;
|
|
||||||
var pipelineLibrary = _engineCore.RenderSystem.GraphicsEngine.PipelineLibrary;
|
|
||||||
shaderLibrary.InvalidateShaderCache(nameHash, pipelineLibrary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,11 +260,6 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_engineCore == null)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var compiled = compileResult.Value;
|
using var compiled = compileResult.Value;
|
||||||
|
|
||||||
var stageCount = 0;
|
var stageCount = 0;
|
||||||
@@ -298,11 +284,7 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
byteCodes[idx++] = new ShaderByteCode { pCode = (byte*)compiled.psResult.GetUnsafePtr(), size = (ulong)compiled.psResult.Length };
|
byteCodes[idx++] = new ShaderByteCode { pCode = (byte*)compiled.psResult.GetUnsafePtr(), size = (ulong)compiled.psResult.Length };
|
||||||
}
|
}
|
||||||
|
|
||||||
var shaderLibrary = _engineCore.RenderSystem.ShaderLibrary;
|
OnShaderVariantCompiled?.Invoke(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(byteCodes, stageCount));
|
||||||
shaderLibrary.CacheCompiledResult(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(byteCodes, stageCount));
|
|
||||||
|
|
||||||
var (compiledHash, _) = shaderLibrary.GetCompiledHash(shaderId, passIndex, variantKey);
|
|
||||||
OnShaderVariantCompiled?.Invoke(variantKey, compiledHash);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -331,11 +313,6 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_engineCore == null)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var bytecodeArray = compileResult.Value;
|
using var bytecodeArray = compileResult.Value;
|
||||||
|
|
||||||
var byteCode = new ShaderByteCode
|
var byteCode = new ShaderByteCode
|
||||||
@@ -344,11 +321,7 @@ internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
|||||||
size = (ulong)bytecodeArray.Length
|
size = (ulong)bytecodeArray.Length
|
||||||
};
|
};
|
||||||
|
|
||||||
var shaderLibrary = _engineCore.RenderSystem.ShaderLibrary;
|
OnShaderVariantCompiled?.Invoke(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(ref byteCode));
|
||||||
shaderLibrary.CacheCompiledResult(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(ref byteCode));
|
|
||||||
|
|
||||||
var (compiledHash, _) = shaderLibrary.GetCompiledHash(shaderId, passIndex, variantKey);
|
|
||||||
OnShaderVariantCompiled?.Invoke(variantKey, compiledHash);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class EditorWorldService : IDisposable
|
|||||||
{
|
{
|
||||||
var scene = SceneManager.CreateScene();
|
var scene = SceneManager.CreateScene();
|
||||||
var entity = EditorWorld.EntityManager.CreateEntity();
|
var entity = EditorWorld.EntityManager.CreateEntity();
|
||||||
EditorWorld.EntityManager.AddComponent(entity, new Engine.Components.SceneID
|
EditorWorld.EntityManager.AddSharedComponent(entity, new Engine.Components.SceneID
|
||||||
{
|
{
|
||||||
value = scene.ID
|
value = scene.ID
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -179,20 +179,19 @@ public class SceneGraphSyncService
|
|||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
var entities = chunk.GetEntities();
|
var entities = chunk.GetEntities();
|
||||||
var sceneIDs = chunk.GetComponentData<SceneID>();
|
var scene = chunk.GetSharedComponent<SceneID>();
|
||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
for (var i = 0; i < chunk.EntityCount; i++)
|
||||||
{
|
{
|
||||||
var s = sceneIDs[i].value;
|
if (scene.value == Scene.INVALID_ID)
|
||||||
if (s == Scene.INVALID_ID)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sceneMap.TryGetValue(s, out var list))
|
if (!sceneMap.TryGetValue(scene.value, out var list))
|
||||||
{
|
{
|
||||||
list = new List<Entity>();
|
list = new List<Entity>();
|
||||||
sceneMap[s] = list;
|
sceneMap[scene.value] = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(entities[i]);
|
list.Add(entities[i]);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Editor.Core.Contracts;
|
using Ghost.Editor.Core.Contracts;
|
||||||
using Ghost.Editor.Core.SceneGraph;
|
|
||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
@@ -350,7 +349,7 @@ internal class SceneSerializationService : IDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
world.EntityManager.SetComponent(entity, new SceneID { value = activeScene.ID });
|
world.EntityManager.SetSharedComponent(entity, new SceneID { value = activeScene.ID });
|
||||||
|
|
||||||
var entityData = data.Entities[fileIndex];
|
var entityData = data.Entities[fileIndex];
|
||||||
ref var list = ref typeIds[fileIndex];
|
ref var list = ref typeIds[fileIndex];
|
||||||
@@ -421,17 +420,12 @@ internal class SceneSerializationService : IDisposable
|
|||||||
|
|
||||||
#region Save Scene from Editor World
|
#region Save Scene from Editor World
|
||||||
|
|
||||||
public unsafe Result SaveSceneFromEditorWorld(string filePath, Scene scene)
|
public unsafe void SaveSceneFromEditorWorld(string filePath, Scene scene)
|
||||||
{
|
{
|
||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
using var sceneEntities = SceneManager.GetSceneEntities(scene, world, scope.AllocationHandle);
|
using var sceneEntities = SceneManager.GetSceneEntities(world, scene, scope.AllocationHandle);
|
||||||
|
|
||||||
if (sceneEntities.Count == 0)
|
|
||||||
{
|
|
||||||
return Result.Failure("No entities found for the specified scene.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entities = new List<Entity>(sceneEntities.Count);
|
var entities = new List<Entity>(sceneEntities.Count);
|
||||||
for (var i = 0; i < sceneEntities.Count; i++)
|
for (var i = 0; i < sceneEntities.Count; i++)
|
||||||
@@ -511,8 +505,6 @@ internal class SceneSerializationService : IDisposable
|
|||||||
writer.Flush();
|
writer.Flush();
|
||||||
|
|
||||||
File.WriteAllBytes(filePath, stream.ToArray());
|
File.WriteAllBytes(filePath, stream.ToArray());
|
||||||
|
|
||||||
return Result.Success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Entity> SortEntitiesByHierarchy(World world, List<Entity> entities)
|
private static List<Entity> SortEntitiesByHierarchy(World world, List<Entity> entities)
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ public partial class App : Application
|
|||||||
services.AddSingleton<IContentProvider, EditorContentProvider>();
|
services.AddSingleton<IContentProvider, EditorContentProvider>();
|
||||||
services.AddSingleton<IShaderCompilationBridge, EditorShaderCompilerBridge>();
|
services.AddSingleton<IShaderCompilationBridge, EditorShaderCompilerBridge>();
|
||||||
|
|
||||||
services.AddSingleton<EngineCore>();
|
|
||||||
|
|
||||||
services.AddSingleton<EngineEditorViewModel>();
|
services.AddSingleton<EngineEditorViewModel>();
|
||||||
|
|
||||||
services.AddTransient<ContentBrowserViewModel>();
|
services.AddTransient<ContentBrowserViewModel>();
|
||||||
|
|||||||
@@ -50,4 +50,9 @@ internal partial class ContentBrowser
|
|||||||
// Refresh the view model to show the new folder
|
// Refresh the view model to show the new folder
|
||||||
viewModel.NavigateToDirectory(currentDir);
|
viewModel.NavigateToDirectory(currentDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ContextMenuItem("project-browser", "Create/Asset/Scene")]
|
||||||
|
private static void CreateSceneAsset()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -154,13 +154,10 @@ public interface IBufferReader
|
|||||||
|
|
||||||
T Read<T>()
|
T Read<T>()
|
||||||
where T : unmanaged;
|
where T : unmanaged;
|
||||||
|
|
||||||
void ReadExactly<T>(Span<T> dst)
|
void ReadExactly<T>(Span<T> dst)
|
||||||
where T : unmanaged;
|
where T : unmanaged;
|
||||||
|
|
||||||
ReadOnlySpan<T> ReadSpan<T>(int length)
|
ReadOnlySpan<T> ReadSpan<T>(int length)
|
||||||
where T : unmanaged;
|
where T : unmanaged;
|
||||||
|
|
||||||
ReadOnlySpan<T> ReadToEnd<T>()
|
ReadOnlySpan<T> ReadToEnd<T>()
|
||||||
where T : unmanaged;
|
where T : unmanaged;
|
||||||
}
|
}
|
||||||
@@ -211,7 +208,7 @@ public unsafe struct BufferReader : IBufferReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void ReadExactly<T>(Span<T> dst)
|
public void ReadExactly<T>(Span<T> dst)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
var newAddr = _address + sizeof(T) * dst.Length;
|
var newAddr = _address + sizeof(T) * dst.Length;
|
||||||
@@ -219,9 +216,9 @@ public unsafe struct BufferReader : IBufferReader
|
|||||||
|
|
||||||
var src = new ReadOnlySpan<T>(_address, dst.Length);
|
var src = new ReadOnlySpan<T>(_address, dst.Length);
|
||||||
src.CopyTo(dst);
|
src.CopyTo(dst);
|
||||||
|
_address = newAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<T> ReadSpan<T>(int length)
|
public ReadOnlySpan<T> ReadSpan<T>(int length)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using Misaki.HighPerformance.Mathematics;
|
|||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
public unsafe struct Camera : IComponent
|
public unsafe struct Camera : IComponentData
|
||||||
{
|
{
|
||||||
public float nearClipPlane;
|
public float nearClipPlane;
|
||||||
public float farClipPlane;
|
public float farClipPlane;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Ghost.Graphics.Services;
|
|||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
public struct GPUInstanceRef : IComponent
|
public struct GPUInstanceRef : IComponentData
|
||||||
{
|
{
|
||||||
public uint gpuInstanceIndex;
|
public uint gpuInstanceIndex;
|
||||||
public Identifier<MaterialPalette> materialPalette;
|
public Identifier<MaterialPalette> materialPalette;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
[HideEditor]
|
[HideEditor]
|
||||||
public struct Hierarchy : IComponent
|
public struct Hierarchy : IComponentData
|
||||||
{
|
{
|
||||||
public Entity parent;
|
public Entity parent;
|
||||||
public Entity firstChild;
|
public Entity firstChild;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Misaki.HighPerformance.Mathematics;
|
|||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
public struct LocalToWorld : IComponent
|
public struct LocalToWorld : IComponentData
|
||||||
{
|
{
|
||||||
public float4x4 matrix;
|
public float4x4 matrix;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ using Ghost.Graphics.Services;
|
|||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|
||||||
public struct MeshInstance : IComponent
|
public struct MeshInstance : IComponentData
|
||||||
{
|
{
|
||||||
public Handle<Mesh> mesh;
|
public Handle<Mesh> mesh;
|
||||||
public Identifier<MaterialPalette> materialPalette;
|
public Identifier<MaterialPalette> materialPalette;
|
||||||
|
|||||||
@@ -66,18 +66,9 @@ public struct Scene : IEquatable<Scene>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public class LoadedSceneData : IDisposable
|
||||||
/// Manages scenes within a world.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is a minimal runtime representation. All metadata (like scene names)
|
|
||||||
/// should be stored in editor-only classes (SceneNode).
|
|
||||||
/// </remarks>
|
|
||||||
public static class SceneManager
|
|
||||||
{
|
{
|
||||||
internal struct LoadedSceneData : IDisposable
|
public struct EntityData : IDisposable
|
||||||
{
|
|
||||||
internal struct EntityData : IDisposable
|
|
||||||
{
|
{
|
||||||
public int fileLocalIndex;
|
public int fileLocalIndex;
|
||||||
public UnsafeList<Identifier<IComponent>> componentTypeIDs;
|
public UnsafeList<Identifier<IComponent>> componentTypeIDs;
|
||||||
@@ -108,10 +99,18 @@ public static class SceneManager
|
|||||||
{
|
{
|
||||||
entities[i].Dispose();
|
entities[i].Dispose();
|
||||||
}
|
}
|
||||||
entities.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
entities.Dispose();
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages scenes within a world.
|
||||||
|
/// </summary>
|
||||||
|
public static class SceneManager
|
||||||
|
{
|
||||||
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();
|
||||||
|
|
||||||
@@ -215,7 +214,10 @@ public static class SceneManager
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
compData.Dispose();
|
compData.Dispose();
|
||||||
if (fieldCount > 0) fieldOffsets.Dispose();
|
if (fieldCount > 0)
|
||||||
|
{
|
||||||
|
fieldOffsets.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,23 +244,37 @@ public static class SceneManager
|
|||||||
return ParseSceneData(header, ref reader, allocationHandle);
|
return ParseSceneData(header, ref reader, allocationHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static unsafe Result<int> MaterializeScene(World world, ref readonly LoadedSceneData result, Scene scene)
|
/// <summary>
|
||||||
|
/// Materializes the loaded scene data into actual entities in the world, setting their components and remapping entity references.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method create entities directly into the world. Must ensure it's the safe to perform such strcture changes before calling this method (e.g. not in the middle of a system update that might be iterating over entities).
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="world">The world into which to materialize the scene data.</param>
|
||||||
|
/// <param name="result">The loaded scene data.</param>
|
||||||
|
/// <param name="scene">The scene to which the entities belong.</param>
|
||||||
|
/// <param name="startEntityIndex">The index of the first entity to materialize.</param>
|
||||||
|
/// <param name="length">The number of entities to materialize.</param>
|
||||||
|
public static unsafe void MaterializeScene(World world, ref readonly LoadedSceneData result, Scene scene, int startEntityIndex, int length)
|
||||||
{
|
{
|
||||||
|
if (startEntityIndex < 0 || startEntityIndex + length > result.entities.Length)
|
||||||
|
{
|
||||||
|
Logger.Error($"Invalid entity index range for materialization: start={startEntityIndex}, length={length}, total={result.entities.Length}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
using var forwardMap = new UnsafeHashMap<int, Entity>(result.entities.Length, scope.AllocationHandle);
|
using var forwardMap = new UnsafeHashMap<int, Entity>(result.entities.Length, scope.AllocationHandle);
|
||||||
using var sharedCom = new SharedComponentSet(256, scope.AllocationHandle);
|
using var sharedCom = new SharedComponentSet(256, scope.AllocationHandle);
|
||||||
|
|
||||||
// Create entities and set SceneID
|
// Create entities and set SceneID
|
||||||
for (var i = 0; i < result.entities.Length; i++)
|
for (var i = startEntityIndex; i < startEntityIndex + length; i++)
|
||||||
{
|
{
|
||||||
ref var pending = ref result.entities[i];
|
ref var pending = ref result.entities[i];
|
||||||
|
|
||||||
using var typeIds = new UnsafeList<Identifier<IComponent>>(pending.componentTypeIDs.Count + 1, scope.AllocationHandle);
|
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++)
|
typeIds.AddRange(pending.componentTypeIDs);
|
||||||
{
|
|
||||||
typeIds.Add(pending.componentTypeIDs[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedCom.With(new SceneID { value = scene.ID });
|
sharedCom.With(new SceneID { value = scene.ID });
|
||||||
|
|
||||||
@@ -270,11 +286,13 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set component data
|
// Set component data
|
||||||
for (var i = 0; i < result.entities.Length; i++)
|
for (var i = startEntityIndex; i < startEntityIndex + length; i++)
|
||||||
{
|
{
|
||||||
ref var pending = ref result.entities[i];
|
ref var pending = ref result.entities[i];
|
||||||
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (var j = 0; j < pending.componentData.Count; j++)
|
for (var j = 0; j < pending.componentData.Count; j++)
|
||||||
{
|
{
|
||||||
@@ -284,11 +302,13 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remap entity references
|
// Remap entity references
|
||||||
for (var i = 0; i < result.entities.Length; i++)
|
for (var i = startEntityIndex; i < startEntityIndex + length; i++)
|
||||||
{
|
{
|
||||||
ref var pending = ref result.entities[i];
|
ref var pending = ref result.entities[i];
|
||||||
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
if (!forwardMap.TryGetValue(pending.fileLocalIndex, out var entity))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (var j = 0; j < pending.entityFields.Count; j++)
|
for (var j = 0; j < pending.entityFields.Count; j++)
|
||||||
{
|
{
|
||||||
@@ -297,7 +317,9 @@ public static class SceneManager
|
|||||||
|
|
||||||
var pComponent = world.EntityManager.GetComponent(entity, compTypeID);
|
var pComponent = world.EntityManager.GetComponent(entity, compTypeID);
|
||||||
if (pComponent == null)
|
if (pComponent == null)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (var f = 0; f < fieldOffsets.Length; f++)
|
for (var f = 0; f < fieldOffsets.Length; f++)
|
||||||
{
|
{
|
||||||
@@ -313,8 +335,6 @@ public static class SceneManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success(result.entities.Length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -327,16 +347,13 @@ public static class SceneManager
|
|||||||
var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
|
var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
|
||||||
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
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())
|
||||||
{
|
{
|
||||||
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
ref readonly var sceneID = ref chunk.GetSharedComponent<SceneID>();
|
||||||
if (sceneID.value == scene.ID)
|
if (sceneID.value == scene.ID)
|
||||||
{
|
{
|
||||||
ecb.DestroyEntities(chunk.GetEntities());
|
world.EntityManager.DestroyEntities(chunk.GetEntities());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +367,7 @@ public static class SceneManager
|
|||||||
/// <param name="world">The world containing the entities.</param>
|
/// <param name="world">The world containing the entities.</param>
|
||||||
/// <param name="entities">Span to store the entities.</param>
|
/// <param name="entities">Span to store the entities.</param>
|
||||||
/// <returns>The number of entities written to the span.</returns>
|
/// <returns>The number of entities written to the span.</returns>
|
||||||
public static UnsafeList<Entity> GetSceneEntities(Scene scene, World world, AllocationHandle handle)
|
public static UnsafeList<Entity> GetSceneEntities(World world, Scene scene, AllocationHandle handle)
|
||||||
{
|
{
|
||||||
var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
|
var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
|
||||||
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Ghost.Graphics.RHI;
|
|||||||
using Ghost.Graphics.Services;
|
using Ghost.Graphics.Services;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace Ghost.Engine.Streaming;
|
namespace Ghost.Engine.Streaming;
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ internal static class AssetEntryFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Progress report
|
// TODO: Progress report
|
||||||
internal abstract class AssetEntry
|
internal abstract class AssetEntry : IAssetEntry
|
||||||
{
|
{
|
||||||
private readonly AssetManager _assetManager;
|
private readonly AssetManager _assetManager;
|
||||||
private readonly ResourceManager _resourceManager;
|
private readonly ResourceManager _resourceManager;
|
||||||
@@ -72,19 +73,27 @@ internal abstract class AssetEntry
|
|||||||
protected ResourceManager ResourceManager => _resourceManager;
|
protected ResourceManager ResourceManager => _resourceManager;
|
||||||
protected IResourceDatabase ResourceDatabase => _resourceDatabase;
|
protected IResourceDatabase ResourceDatabase => _resourceDatabase;
|
||||||
|
|
||||||
public AssetManager AssetManager => _assetManager;
|
|
||||||
public Guid AssetId => _assetId;
|
public Guid AssetId => _assetId;
|
||||||
public JobHandle LoadJobHandle => _loadJobHandle;
|
public JobHandle LoadJobHandle => _loadJobHandle;
|
||||||
public AssetType AssetType => _assetType;
|
public AssetType AssetType => _assetType;
|
||||||
public ReadOnlySpan<Guid> Dependencies => _dependencies;
|
public ReadOnlySpan<Guid> Dependencies => _dependencies;
|
||||||
public int RefCount => Volatile.Read(ref _refCount);
|
public int RefCount => Volatile.Read(ref _refCount);
|
||||||
|
|
||||||
public ref bool PendingReimport => ref _pendingReimport;
|
|
||||||
public ref int StateValue => ref _state;
|
public ref int StateValue => ref _state;
|
||||||
public AssetState State
|
public AssetState State
|
||||||
{
|
{
|
||||||
get => (AssetState)Volatile.Read(ref _state);
|
get => (AssetState)Volatile.Read(ref _state);
|
||||||
set => Volatile.Write(ref _state, (int)value);
|
set
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _state, (int)value);
|
||||||
|
if (Volatile.Read(ref _state) == (int)AssetState.Ready)
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _pendingReimport, false, true))
|
||||||
|
{
|
||||||
|
_assetManager.ReimportAsset(_assetId); // re-queue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
protected AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
||||||
@@ -141,27 +150,35 @@ internal abstract class AssetEntry
|
|||||||
return newRefCount;
|
return newRefCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Result OnLoadContent(Stream contentStream);
|
public virtual void OnReleaseResource()
|
||||||
public abstract void OnReleaseResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract class ProcessableAssetEntry : AssetEntry
|
|
||||||
{
|
|
||||||
protected ProcessableAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
|
||||||
: base(manager, resourceDatabase, resourceManager, assetId, assetType, dependencies)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Result<JobHandle> OnProcessing(object? context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class UploadableAssetEntry : AssetEntry
|
interface IAssetEntry
|
||||||
{
|
{
|
||||||
protected UploadableAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
|
Guid AssetId { get; }
|
||||||
: base(manager, resourceDatabase, resourceManager, assetId, assetType, dependencies)
|
AssetType AssetType { get; }
|
||||||
{
|
ReadOnlySpan<Guid> Dependencies { get; }
|
||||||
}
|
int RefCount { get; }
|
||||||
|
AssetState State { get; set; }
|
||||||
|
|
||||||
public abstract Result OnRecordUploadCommands(ResourceStreamingContext context);
|
void AddRef();
|
||||||
public abstract void OnUploadComplete(ResourceStreamingContext context);
|
int Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface ILoadableAssetEntry : IAssetEntry
|
||||||
|
{
|
||||||
|
Result OnLoadContent(Stream contentStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IProcessableAssetEntry : IAssetEntry
|
||||||
|
{
|
||||||
|
Result<JobHandle> OnProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IUploadableAssetEntry : IAssetEntry
|
||||||
|
{
|
||||||
|
Result OnRecordUploadCommands(ResourceStreamingContext context);
|
||||||
|
void OnUploadComplete(ResourceStreamingContext context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ internal struct LoadAssetJob : IJob
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var stream = openResult.Value;
|
using var stream = openResult.Value;
|
||||||
var result = entry.OnLoadContent(stream);
|
|
||||||
|
if (entry is ILoadableAssetEntry loadable)
|
||||||
|
{
|
||||||
|
var result = loadable.OnLoadContent(stream);
|
||||||
|
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
entry.State = AssetState.Failed;
|
entry.State = AssetState.Failed;
|
||||||
@@ -61,12 +65,6 @@ internal struct LoadAssetJob : IJob
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
entry.State = AssetState.Failed;
|
|
||||||
Logger.Error($"Failed to load asset {assetID}: {ex.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.State = AssetState.Loaded;
|
entry.State = AssetState.Loaded;
|
||||||
if (!assetManager.StreamingProcessor.EnqueueForProcess(entry))
|
if (!assetManager.StreamingProcessor.EnqueueForProcess(entry))
|
||||||
@@ -75,6 +73,13 @@ internal struct LoadAssetJob : IJob
|
|||||||
entry.State = AssetState.Ready;
|
entry.State = AssetState.Ready;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
entry.State = AssetState.Failed;
|
||||||
|
Logger.Error($"Failed to load asset {assetID}: {ex.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support DirectStorage.
|
// TODO: Support DirectStorage.
|
||||||
@@ -113,6 +118,24 @@ public partial class AssetManager : IDisposable
|
|||||||
return _entries.TryRemove(guid, out var _);
|
return _entries.TryRemove(guid, out var _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private AssetEntry GetOrCreateEntry(Guid guid)
|
||||||
|
{
|
||||||
|
var entry = _entries.GetOrAdd(guid, static (id, self) =>
|
||||||
|
{
|
||||||
|
var type = self._contentProvider.GetAssetType(id);
|
||||||
|
var deps = self._contentProvider.GetDependencies(id);
|
||||||
|
|
||||||
|
var entry = AssetEntryFactory.CreateNewEntry(self, self._resourceDatabase, self._resourceManager, id, type, deps);
|
||||||
|
|
||||||
|
self.EnsureScheduled(entry);
|
||||||
|
return entry;
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
entry.AddRef();
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureScheduled(AssetEntry entry)
|
private void EnsureScheduled(AssetEntry entry)
|
||||||
{
|
{
|
||||||
var previousState = Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Unloaded);
|
var previousState = Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Unloaded);
|
||||||
@@ -188,24 +211,6 @@ public partial class AssetManager : IDisposable
|
|||||||
entry.SetLoadJobHandle(handle); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
|
entry.SetLoadJobHandle(handle); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private AssetEntry GetOrCreateEntry(Guid guid)
|
|
||||||
{
|
|
||||||
var entry = _entries.GetOrAdd(guid, static (id, self) =>
|
|
||||||
{
|
|
||||||
var type = self._contentProvider.GetAssetType(id);
|
|
||||||
var deps = self._contentProvider.GetDependencies(id);
|
|
||||||
|
|
||||||
var entry = AssetEntryFactory.CreateNewEntry(self, self._resourceDatabase, self._resourceManager, id, type, deps);
|
|
||||||
|
|
||||||
self.EnsureScheduled(entry);
|
|
||||||
return entry;
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
entry.AddRef();
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ReimportAsset(Guid guid)
|
internal void ReimportAsset(Guid guid)
|
||||||
{
|
{
|
||||||
if (!_entries.TryGetValue(guid, out var entry))
|
if (!_entries.TryGetValue(guid, out var entry))
|
||||||
@@ -228,6 +233,19 @@ public partial class AssetManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public IResolveOperation ResolveAsset(Guid assetID)
|
||||||
|
//{
|
||||||
|
// if (assetID == Guid.Empty)
|
||||||
|
// {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var entry = GetOrCreateEntry(assetID);
|
||||||
|
// entry.OnResolve();
|
||||||
|
|
||||||
|
// return new IResolveOperation;
|
||||||
|
//}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var entry in _entries.Values)
|
foreach (var entry in _entries.Values)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public partial class AssetManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
internal unsafe class MeshAssetEntry : AssetEntry, ILoadableAssetEntry, IUploadableAssetEntry
|
||||||
{
|
{
|
||||||
private Handle<Mesh> _actualHandle;
|
private Handle<Mesh> _actualHandle;
|
||||||
private Handle<Mesh> _tempHandle;
|
private Handle<Mesh> _tempHandle;
|
||||||
@@ -121,7 +121,12 @@ internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
|||||||
_actualHandle = resourceManager.RegisterMesh(ref mesh);
|
_actualHandle = resourceManager.RegisterMesh(ref mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Result OnLoadContent(Stream contentStream)
|
public override void OnReleaseResource()
|
||||||
|
{
|
||||||
|
ResourceManager.ReleaseMesh(_tempHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result OnLoadContent(Stream contentStream)
|
||||||
{
|
{
|
||||||
bool ValidateRange(ulong offset, int count, uint stride)
|
bool ValidateRange(ulong offset, int count, uint stride)
|
||||||
{
|
{
|
||||||
@@ -159,7 +164,7 @@ internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
|||||||
return Result.Failure("Mesh content contains an invalid material part range.");
|
return Result.Failure("Mesh content contains an invalid material part range.");
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.Seek(0, SeekOrigin.Begin);
|
contentStream.Position = 0;
|
||||||
|
|
||||||
_rawData = contentStream.ReadMemory(AllocationHandle.Persistent);
|
_rawData = contentStream.ReadMemory(AllocationHandle.Persistent);
|
||||||
var pData = (byte*)_rawData.GetUnsafePtr();
|
var pData = (byte*)_rawData.GetUnsafePtr();
|
||||||
@@ -176,11 +181,6 @@ internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
|||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnReleaseResource()
|
|
||||||
{
|
|
||||||
ResourceManager.ReleaseMesh(_tempHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Handle<GPUBuffer> CreateBuffer(ResourceStreamingContext context, void* pData, int count, uint stride, BufferUsage usage, string name)
|
private static Handle<GPUBuffer> CreateBuffer(ResourceStreamingContext context, void* pData, int count, uint stride, BufferUsage usage, string name)
|
||||||
{
|
{
|
||||||
var desc = new BufferDesc
|
var desc = new BufferDesc
|
||||||
@@ -202,7 +202,7 @@ internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
|||||||
name);
|
name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Result OnRecordUploadCommands(ResourceStreamingContext context)
|
public Result OnRecordUploadCommands(ResourceStreamingContext context)
|
||||||
{
|
{
|
||||||
var vertexBuffer = CreateBuffer(context, _pVertices, _header.vertexCount, (uint)sizeof(Vertex),
|
var vertexBuffer = CreateBuffer(context, _pVertices, _header.vertexCount, (uint)sizeof(Vertex),
|
||||||
BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_VertexBuffer");
|
BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_VertexBuffer");
|
||||||
@@ -298,7 +298,7 @@ internal unsafe class MeshAssetEntry : UploadableAssetEntry
|
|||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUploadComplete(ResourceStreamingContext context)
|
public void OnUploadComplete(ResourceStreamingContext context)
|
||||||
{
|
{
|
||||||
var (dstMeshRef, dstError) = context.ResourceManager.GetMeshReference(_actualHandle);
|
var (dstMeshRef, dstError) = context.ResourceManager.GetMeshReference(_actualHandle);
|
||||||
var (srcMeshRef, srcError) = context.ResourceManager.GetMeshReference(_tempHandle);
|
var (srcMeshRef, srcError) = context.ResourceManager.GetMeshReference(_tempHandle);
|
||||||
|
|||||||
@@ -11,28 +11,28 @@ 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<IProcessableAssetEntry> _pendingProcess;
|
||||||
private readonly ConcurrentQueue<UploadableAssetEntry> _pendingUpload;
|
private readonly ConcurrentQueue<IUploadableAssetEntry> _pendingUpload;
|
||||||
private readonly ConcurrentQueue<UploadableAssetEntry> _pendingFinalize;
|
private readonly ConcurrentQueue<IUploadableAssetEntry> _pendingFinalize;
|
||||||
|
|
||||||
private ulong _pendingCopyFenceValue;
|
private ulong _pendingCopyFenceValue;
|
||||||
|
|
||||||
public ResourceStreamingProcessor()
|
public ResourceStreamingProcessor()
|
||||||
{
|
{
|
||||||
_pendingProcess = new ConcurrentQueue<ProcessableAssetEntry>();
|
_pendingProcess = new ConcurrentQueue<IProcessableAssetEntry>();
|
||||||
_pendingUpload = new ConcurrentQueue<UploadableAssetEntry>();
|
_pendingUpload = new ConcurrentQueue<IUploadableAssetEntry>();
|
||||||
_pendingFinalize = new ConcurrentQueue<UploadableAssetEntry>();
|
_pendingFinalize = new ConcurrentQueue<IUploadableAssetEntry>();
|
||||||
_pendingCopyFenceValue = 0;
|
_pendingCopyFenceValue = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EnqueueForProcess(AssetEntry entry)
|
public bool EnqueueForProcess(AssetEntry entry)
|
||||||
{
|
{
|
||||||
if (entry is UploadableAssetEntry uploadable)
|
if (entry is IUploadableAssetEntry uploadable)
|
||||||
{
|
{
|
||||||
_pendingUpload.Enqueue(uploadable);
|
_pendingUpload.Enqueue(uploadable);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (entry is ProcessableAssetEntry processable)
|
else if (entry is IProcessableAssetEntry processable)
|
||||||
{
|
{
|
||||||
_pendingProcess.Enqueue(processable);
|
_pendingProcess.Enqueue(processable);
|
||||||
return true;
|
return true;
|
||||||
@@ -48,7 +48,7 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
|||||||
|
|
||||||
while (_pendingProcess.TryDequeue(out var entry))
|
while (_pendingProcess.TryDequeue(out var entry))
|
||||||
{
|
{
|
||||||
var result = entry.OnProcessing(context);
|
var result = entry.OnProcessing();
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
Logger.Error(result.Message);
|
Logger.Error(result.Message);
|
||||||
@@ -74,12 +74,7 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
|||||||
{
|
{
|
||||||
while (_pendingFinalize.TryDequeue(out var item))
|
while (_pendingFinalize.TryDequeue(out var item))
|
||||||
{
|
{
|
||||||
Volatile.Write(ref item.StateValue, (int)AssetState.Ready);
|
item.State = AssetState.Ready;
|
||||||
if (Interlocked.CompareExchange(ref item.PendingReimport, false, true))
|
|
||||||
{
|
|
||||||
item.AssetManager.ReimportAsset(item.AssetId); // re-queue
|
|
||||||
}
|
|
||||||
|
|
||||||
item.OnUploadComplete(context);
|
item.OnUploadComplete(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,40 @@ 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)
|
private struct LoadSceneJob : IJob
|
||||||
|
{
|
||||||
|
public SceneContentHeader header;
|
||||||
|
public Stream stream;
|
||||||
|
|
||||||
|
public LoadedSceneData loadedSceneData;
|
||||||
|
|
||||||
|
public readonly void Execute(ref readonly JobExecutionContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent);
|
||||||
|
if (loadResult.IsFailure)
|
||||||
|
{
|
||||||
|
Logger.Error($"Failed to parse scene data: {loadResult.Message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedSceneData.entities = loadResult.Value.entities;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error($"Exception while loading scene: {ex}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe Result<JobHandle> LoadScene(World world, AssetRef<Scene> sceneAsset, SceneLoadingType loadingType, ref LoadedSceneData? loadedSceneData)
|
||||||
{
|
{
|
||||||
if (!sceneAsset.IsValid)
|
if (!sceneAsset.IsValid)
|
||||||
{
|
{
|
||||||
@@ -38,32 +68,50 @@ public partial class AssetManager
|
|||||||
return Result.Failure($"Failed to open scene {sceneAsset.ID}: {openResult.Message}.");
|
return Result.Failure($"Failed to open scene {sceneAsset.ID}: {openResult.Message}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
var stream = openResult.Value;
|
||||||
|
|
||||||
|
if (stream.Length < sizeof(SceneContentHeader))
|
||||||
{
|
{
|
||||||
using var stream = openResult.Value;
|
stream.Dispose();
|
||||||
|
return Result.Failure("Invalid scene file size.");
|
||||||
|
}
|
||||||
|
|
||||||
var header = stream.Read<SceneContentHeader>();
|
var header = stream.Read<SceneContentHeader>();
|
||||||
if (header.magic != SceneContentHeader.MAGIC)
|
if (header.magic != SceneContentHeader.MAGIC)
|
||||||
{
|
{
|
||||||
|
stream.Dispose();
|
||||||
return Result.Failure("Unexpected header format.");
|
return Result.Failure("Unexpected header format.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version != SceneContentHeader.VERSION)
|
if (header.version != SceneContentHeader.VERSION)
|
||||||
{
|
{
|
||||||
|
stream.Dispose();
|
||||||
return Result.Failure($"Not supported scene header version {header.version}.");
|
return Result.Failure($"Not supported scene header version {header.version}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
if (loadingType == SceneLoadingType.Single)
|
if (loadingType == SceneLoadingType.Single)
|
||||||
{
|
{
|
||||||
world.Reset();
|
world.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadResult = SceneManager.ParseSceneData(header, stream, AllocationHandle.Persistent);
|
loadedSceneData ??= new LoadedSceneData();
|
||||||
|
|
||||||
return JobHandle.Invalid;
|
var entry = GetOrCreateEntry(sceneAsset.ID); // Purely to get the dependencies and ensure the asset is tracked, the actual loading is done in the job.
|
||||||
|
|
||||||
|
var job = new LoadSceneJob
|
||||||
|
{
|
||||||
|
header = header,
|
||||||
|
stream = stream,
|
||||||
|
loadedSceneData = loadedSceneData
|
||||||
|
};
|
||||||
|
|
||||||
|
return _jobScheduler.Schedule(in job, entry.LoadJobHandle);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
stream.Dispose();
|
||||||
return Result.Failure(ex.Message);
|
return Result.Failure(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,29 +121,6 @@ internal class SceneAssetEntry : AssetEntry
|
|||||||
{
|
{
|
||||||
public SceneAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, Guid[] dependencies)
|
public SceneAssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, Guid[] dependencies)
|
||||||
: base(manager, resourceDatabase, resourceManager, assetId, AssetType.Scene, dependencies)
|
: base(manager, resourceDatabase, resourceManager, assetId, AssetType.Scene, dependencies)
|
||||||
{
|
|
||||||
// TODO: How can I get this? Ideally the public api will be something like SceneManager.LoadScene(World, Scene, SceneLoadingType).
|
|
||||||
// Should we handle the scene loading explicitly instead of auto loading on the first resolve?
|
|
||||||
// For example if we have a component called SceneStreamer{ SceneID a; SceneID b; }
|
|
||||||
// In save data, we convert the SceneID(ushort) to a asset gui, and convert it back during load. So at ResolveScene stage (before the file even been loaded), we need to call the SceneManager.CreateScene() and return the id immediately.
|
|
||||||
// Currently we store the world and loading type directly inside the asset entry, but actually that should not be bound with the asset itself, because we may load scene A along at the first time, then we load it additively at the second time.
|
|
||||||
// So, maybe the scene asset entry should only create a unique id from SceneManager.CreateScene() then resolve the scene file without loading it into world.
|
|
||||||
// Then we can load the scene into world using our job system, and user can decide to wait it immediatly (sync) or fire-and-forget (async).
|
|
||||||
// The workflow may be this:
|
|
||||||
// 1. Startup scene load, during resolve, see SceneStreamer has two SceneID fields (which still contains guid now), resolve this two scene via AssetManager. Get the id of those two scene immediately.
|
|
||||||
// 2. Background job load the scene into memory, parse the raw memory into the format that runtime understand. (Or maybe we do not load full memory, just check the header to see if it's valid?
|
|
||||||
// If the streamer type has 20 scenes, loading all 20 scenes into memory is very huge.).
|
|
||||||
// 3. The streamer called SceneManager.LoadScene(World, SceneID, SceneLoadingType) (example api, may not be this exactly). (Mybe we load the data into memory here every time when LoadScene is
|
|
||||||
// called? It will be fine right since load scene itself is a heavy opeartion and it's not am opeartion that will be performed per frame)
|
|
||||||
// 4. Background job load the scene into world by creating entities and setup components for those entities.
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Result OnLoadContent(Stream contentStream)
|
|
||||||
{
|
|
||||||
return Result.Success();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnReleaseResource()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ public partial class AssetManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal unsafe class TextureAssetEntry : UploadableAssetEntry
|
internal unsafe class TextureAssetEntry : AssetEntry, ILoadableAssetEntry, IUploadableAssetEntry
|
||||||
{
|
{
|
||||||
private Handle<GPUTexture> _actualHandle;
|
private Handle<GPUTexture> _actualHandle;
|
||||||
private Handle<GPUTexture> _tempHandle;
|
private Handle<GPUTexture> _tempHandle;
|
||||||
@@ -102,7 +102,12 @@ internal unsafe class TextureAssetEntry : UploadableAssetEntry
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Result OnLoadContent(Stream contentStream)
|
public override void OnReleaseResource()
|
||||||
|
{
|
||||||
|
ResourceDatabase.ReleaseResource(_tempHandle.AsResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result OnLoadContent(Stream contentStream)
|
||||||
{
|
{
|
||||||
var header = contentStream.Read<TextureContentHeader>();
|
var header = contentStream.Read<TextureContentHeader>();
|
||||||
|
|
||||||
@@ -133,13 +138,7 @@ internal unsafe class TextureAssetEntry : UploadableAssetEntry
|
|||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnReleaseResource()
|
public Result OnRecordUploadCommands(ResourceStreamingContext context)
|
||||||
{
|
|
||||||
ResourceDatabase.ReleaseResource(_tempHandle.AsResource());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override Result OnRecordUploadCommands(ResourceStreamingContext context)
|
|
||||||
{
|
{
|
||||||
Logger.DebugAssert(_textureData.IsCreated);
|
Logger.DebugAssert(_textureData.IsCreated);
|
||||||
|
|
||||||
@@ -161,7 +160,7 @@ internal unsafe class TextureAssetEntry : UploadableAssetEntry
|
|||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnUploadComplete(ResourceStreamingContext context)
|
public void OnUploadComplete(ResourceStreamingContext context)
|
||||||
{
|
{
|
||||||
var actualHandle = context.ResourceDatabase.Replace(_actualHandle.AsResource(), _tempHandle.AsResource());
|
var actualHandle = context.ResourceDatabase.Replace(_actualHandle.AsResource(), _tempHandle.AsResource());
|
||||||
Logger.DebugAssert(actualHandle.IsValid);
|
Logger.DebugAssert(actualHandle.IsValid);
|
||||||
|
|||||||
@@ -188,11 +188,11 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Edge
|
//private struct Edge
|
||||||
{
|
//{
|
||||||
public int componentID;
|
// public int componentID;
|
||||||
public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
|
// public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
|
||||||
}
|
//}
|
||||||
|
|
||||||
internal UnsafeBitSet _signature;
|
internal UnsafeBitSet _signature;
|
||||||
internal UnsafeList<Chunk> _chunks;
|
internal UnsafeList<Chunk> _chunks;
|
||||||
@@ -202,8 +202,8 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
internal UnsafeArray<SharedComponentLayout> _sharedLayouts;
|
internal UnsafeArray<SharedComponentLayout> _sharedLayouts;
|
||||||
internal UnsafeList<ChunkGroup> _chunkGroups;
|
internal UnsafeList<ChunkGroup> _chunkGroups;
|
||||||
|
|
||||||
private UnsafeList<Edge> _edgesAdd;
|
private UnsafeHashMap<int, int> _edgesAdd;
|
||||||
private UnsafeList<Edge> _edgesRemove;
|
private UnsafeHashMap<int, int> _edgesRemove;
|
||||||
|
|
||||||
// 0 means no cleanup component (since 0 is the empty archetype), -1 means haven't computed yet, positive value means the archetype id of the cleanup edge.
|
// 0 means no cleanup component (since 0 is the empty archetype), -1 means haven't computed yet, positive value means the archetype id of the cleanup edge.
|
||||||
internal int _cleanupEdge;
|
internal int _cleanupEdge;
|
||||||
@@ -230,8 +230,8 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
_worldID = worldID;
|
_worldID = worldID;
|
||||||
|
|
||||||
_chunks = new UnsafeList<Chunk>(4, AllocationHandle.Persistent);
|
_chunks = new UnsafeList<Chunk>(4, AllocationHandle.Persistent);
|
||||||
_edgesAdd = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
_edgesAdd = new UnsafeHashMap<int, int>(4, AllocationHandle.Persistent);
|
||||||
_edgesRemove = new UnsafeList<Edge>(4, AllocationHandle.Persistent);
|
_edgesRemove = new UnsafeHashMap<int, int>(4, AllocationHandle.Persistent);
|
||||||
|
|
||||||
if (componentIds.IsEmpty)
|
if (componentIds.IsEmpty)
|
||||||
{
|
{
|
||||||
@@ -868,51 +868,25 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddEdgeAdd(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
public void AddEdgeAdd(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||||
{
|
{
|
||||||
_edgesAdd.Add(new Edge
|
_edgesAdd.TryAdd(componentID, targetArchetype);
|
||||||
{
|
|
||||||
componentID = componentID,
|
|
||||||
targetArchetype = targetArchetype
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly Identifier<Archetype> GetEdgeAdd(Identifier<IComponent> componentID)
|
public readonly Identifier<Archetype> GetEdgeAdd(Identifier<IComponent> componentID)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < _edgesAdd.Count; i++)
|
return _edgesAdd.GetValueOrDefault(componentID, -1);
|
||||||
{
|
|
||||||
var edge = _edgesAdd[i];
|
|
||||||
if (edge.componentID == componentID)
|
|
||||||
{
|
|
||||||
return edge.targetArchetype;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Identifier<Archetype>.Invalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddEdgeRemove(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
public void AddEdgeRemove(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||||
{
|
{
|
||||||
_edgesRemove.Add(new Edge
|
_edgesRemove.TryAdd(componentID, targetArchetype);
|
||||||
{
|
|
||||||
componentID = componentID,
|
|
||||||
targetArchetype = targetArchetype
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly Identifier<Archetype> GetEdgeRemove(Identifier<IComponent> componentID)
|
public readonly Identifier<Archetype> GetEdgeRemove(Identifier<IComponent> componentID)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < _edgesRemove.Count; i++)
|
return _edgesRemove.GetValueOrDefault(componentID, -1);
|
||||||
{
|
|
||||||
var edge = _edgesRemove[i];
|
|
||||||
if (edge.componentID == componentID)
|
|
||||||
{
|
|
||||||
return edge.targetArchetype;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Identifier<Archetype>.Invalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ using System.Runtime.CompilerServices;
|
|||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
public interface IComponent;
|
public interface IComponent;
|
||||||
public interface IEnableableComponent : IComponent;
|
public interface IComponentData : IComponent;
|
||||||
public interface ICleanupComponent : IComponent;
|
public interface IEnableableComponent : IComponentData;
|
||||||
|
public interface ICleanupComponent : IComponentData;
|
||||||
public interface ISharedComponent : IComponent;
|
public interface ISharedComponent : IComponent;
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)]
|
[AttributeUsage(AttributeTargets.Struct)]
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
|
|
||||||
[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, IComponentData
|
||||||
{
|
{
|
||||||
_writer.Write(ECBOpCode.AddComponent);
|
_writer.Write(ECBOpCode.AddComponent);
|
||||||
_writer.Write(entity);
|
_writer.Write(entity);
|
||||||
@@ -73,7 +73,7 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void RemoveComponent<T>(Entity entity)
|
public void RemoveComponent<T>(Entity entity)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
_writer.Write(ECBOpCode.RemoveComponent);
|
_writer.Write(ECBOpCode.RemoveComponent);
|
||||||
_writer.Write(entity);
|
_writer.Write(entity);
|
||||||
@@ -82,7 +82,7 @@ public unsafe struct EntityCommandBuffer : IDisposable
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SetComponent<T>(Entity entity, T component)
|
public void SetComponent<T>(Entity entity, T component)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
_writer.Write(ECBOpCode.SetComponent);
|
_writer.Write(ECBOpCode.SetComponent);
|
||||||
_writer.Write(entity);
|
_writer.Write(entity);
|
||||||
|
|||||||
@@ -566,7 +566,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <returns>The result status of the operation.</returns>
|
/// <returns>The result status of the operation.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Error CreateSingleton<T>(T component = default)
|
public Error CreateSingleton<T>(T component = default)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return CreateSingleton(ComponentTypeID<T>.Value, &component);
|
return CreateSingleton(ComponentTypeID<T>.Value, &component);
|
||||||
}
|
}
|
||||||
@@ -606,7 +606,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <returns>Reference to the component data. null ref if not found.</returns>
|
/// <returns>Reference to the component data. null ref if not found.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ref T GetSingleton<T>()
|
public ref T GetSingleton<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
var ptr = GetSingleton(ComponentTypeID<T>.Value);
|
var ptr = GetSingleton(ComponentTypeID<T>.Value);
|
||||||
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
||||||
@@ -761,7 +761,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <returns>The result status of the operation.</returns>
|
/// <returns>The result status of the operation.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Error AddComponent<T>(Entity entity, T component = default)
|
public Error AddComponent<T>(Entity entity, T component = default)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return AddComponent(entity, ComponentTypeID<T>.Value, &component);
|
return AddComponent(entity, ComponentTypeID<T>.Value, &component);
|
||||||
}
|
}
|
||||||
@@ -874,7 +874,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <returns>The result status of the operation.</returns>
|
/// <returns>The result status of the operation.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Error RemoveComponent<T>(Entity entity)
|
public Error RemoveComponent<T>(Entity entity)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return RemoveComponent(entity, ComponentTypeID<T>.Value);
|
return RemoveComponent(entity, ComponentTypeID<T>.Value);
|
||||||
}
|
}
|
||||||
@@ -908,7 +908,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <param name="component">The component data.</param>
|
/// <param name="component">The component data.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Error SetComponent<T>(Entity entity, T component)
|
public Error SetComponent<T>(Entity entity, T component)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return SetComponent(entity, ComponentTypeID<T>.Value, &component);
|
return SetComponent(entity, ComponentTypeID<T>.Value, &component);
|
||||||
}
|
}
|
||||||
@@ -939,7 +939,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <returns>Reference to the component data. null ref if not found.</returns>
|
/// <returns>Reference to the component data. null ref if not found.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ref T GetComponent<T>(Entity entity)
|
public ref T GetComponent<T>(Entity entity)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
var ptr = GetComponent(entity, ComponentTypeID<T>.Value);
|
var ptr = GetComponent(entity, ComponentTypeID<T>.Value);
|
||||||
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
return ref *(T*)ptr; // This will return null ref if ptr is null.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Ghost.Entities;
|
|||||||
public interface IManagedComponent;
|
public interface IManagedComponent;
|
||||||
public interface IManagedWrapper;
|
public interface IManagedWrapper;
|
||||||
|
|
||||||
public readonly struct Managed<T> : IComponent, IManagedWrapper
|
public readonly struct Managed<T> : IComponentData, IManagedWrapper
|
||||||
where T : IManagedComponent
|
where T : IManagedComponent
|
||||||
{
|
{
|
||||||
public readonly int id;
|
public readonly int id;
|
||||||
@@ -38,7 +38,7 @@ internal static class ManagedComponentRegistry
|
|||||||
private static readonly List<ManagedComponentInfo> s_registeredComponents = new();
|
private static readonly List<ManagedComponentInfo> s_registeredComponents = new();
|
||||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||||
#if GHOST_SAFETY_CHECKS
|
#if DEBUG || GHOST_EDITOR
|
||||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ internal static class ManagedComponentRegistry
|
|||||||
|
|
||||||
s_typeHandleToID[typeHandle] = newID;
|
s_typeHandleToID[typeHandle] = newID;
|
||||||
s_nameToRuntimeID[stableName] = newID;
|
s_nameToRuntimeID[stableName] = newID;
|
||||||
#if GHOST_SAFETY_CHECKS
|
#if DEBUG || GHOST_EDITOR
|
||||||
s_runtimeIDToType[newID.Value] = typeof(T);
|
s_runtimeIDToType[newID.Value] = typeof(T);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ public abstract class ScriptComponent : IManagedComponent
|
|||||||
public Entity Entity => _entity;
|
public Entity Entity => _entity;
|
||||||
|
|
||||||
protected ref T GetComponent<T>()
|
protected ref T GetComponent<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return ref _world.EntityManager.GetComponent<T>(_entity);
|
return ref _world.EntityManager.GetComponent<T>(_entity);
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ public abstract class ScriptComponent : IManagedComponent
|
|||||||
|
|
||||||
public partial class EntityManager
|
public partial class EntityManager
|
||||||
{
|
{
|
||||||
private IManagedComponentStorage[] _managedStorages;
|
private IManagedComponentStorage[] _managedStorages = new IManagedComponentStorage[64];
|
||||||
|
|
||||||
internal IManagedComponentStorage[] ManagedStorages => _managedStorages;
|
internal IManagedComponentStorage[] ManagedStorages => _managedStorages;
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,40 @@ internal struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
|
|||||||
public override readonly int GetHashCode()
|
public override readonly int GetHashCode()
|
||||||
{
|
{
|
||||||
var hash = 17;
|
var hash = 17;
|
||||||
if (structuralAll.IsCreated) hash = hash * 23 + structuralAll.GetHashCode();
|
if (structuralAll.IsCreated)
|
||||||
if (structuralAbsent.IsCreated) hash = hash * 23 + structuralAbsent.GetHashCode();
|
{
|
||||||
if (structuralAny.IsCreated) hash = hash * 23 + structuralAny.GetHashCode();
|
hash = hash * 23 + structuralAll.GetHashCode();
|
||||||
if (requireEnabled.IsCreated) hash = hash * 23 + requireEnabled.GetHashCode();
|
}
|
||||||
if (requireDisabled.IsCreated) hash = hash * 23 + requireDisabled.GetHashCode();
|
|
||||||
if (rejectIfEnabled.IsCreated) hash = hash * 23 + rejectIfEnabled.GetHashCode();
|
if (structuralAbsent.IsCreated)
|
||||||
if (writeAccess.IsCreated) hash = hash * 23 + writeAccess.GetHashCode();
|
{
|
||||||
|
hash = hash * 23 + structuralAbsent.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (structuralAny.IsCreated)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + structuralAny.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireEnabled.IsCreated)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + requireEnabled.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireDisabled.IsCreated)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + requireDisabled.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rejectIfEnabled.IsCreated)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + rejectIfEnabled.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeAccess.IsCreated)
|
||||||
|
{
|
||||||
|
hash = hash * 23 + writeAccess.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
@@ -153,7 +180,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <returns>true if the component of space T has changed since the specified version; otherwise, false.</returns>
|
/// <returns>true if the component of space T has changed since the specified version; otherwise, false.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly bool HasChanged<T>(uint version)
|
public readonly bool HasChanged<T>(uint version)
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
var layout = GetLayout(ComponentTypeID<T>.Value);
|
var layout = GetLayout(ComponentTypeID<T>.Value);
|
||||||
return version < _pVersion[layout.versionIndex];
|
return version < _pVersion[layout.versionIndex];
|
||||||
@@ -188,7 +215,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <returns>The version number of the component space <typeparamref name="T"/>.</returns>
|
/// <returns>The version number of the component space <typeparamref name="T"/>.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly uint GetComponentVersion<T>()
|
public readonly uint GetComponentVersion<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
return _pVersion[ComponentTypeID<T>.Value];
|
return _pVersion[ComponentTypeID<T>.Value];
|
||||||
}
|
}
|
||||||
@@ -212,7 +239,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ReadOnlySpan<T> GetComponentData<T>()
|
public ReadOnlySpan<T> GetComponentData<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
var layout = GetLayout(ComponentTypeID<T>.Value);
|
var layout = GetLayout(ComponentTypeID<T>.Value);
|
||||||
var pComponentData = _pChunkData + layout.offset;
|
var pComponentData = _pChunkData + layout.offset;
|
||||||
@@ -227,7 +254,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
|
/// <exception cref="InvalidOperationException">Thrown if the specified component space is not present in the archetype.</exception>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Span<T> GetComponentDataRW<T>()
|
public Span<T> GetComponentDataRW<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
var compId = ComponentTypeID<T>.Value;
|
var compId = ComponentTypeID<T>.Value;
|
||||||
var layout = GetLayout(compId);
|
var layout = GetLayout(compId);
|
||||||
@@ -289,7 +316,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
|
|
||||||
var compID = ComponentTypeID<T>.Value;
|
var compID = ComponentTypeID<T>.Value;
|
||||||
var layoutIndex = -1;
|
var layoutIndex = -1;
|
||||||
for (int i = 0; i < _sharedLayouts.Length; i++)
|
for (var i = 0; i < _sharedLayouts.Length; i++)
|
||||||
{
|
{
|
||||||
if (_sharedLayouts[i].componentID == compID.Value)
|
if (_sharedLayouts[i].componentID == compID.Value)
|
||||||
{
|
{
|
||||||
@@ -467,6 +494,43 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool RequiresEnableableFiltering(ref readonly Archetype archetype, ref readonly EntityQueryMask mask)
|
||||||
|
{
|
||||||
|
// If the query asks to filter by enabled/disabled state AND the archetype
|
||||||
|
// actually has that component marked as enableable (enableBitsOffset != -1), we must filter.
|
||||||
|
var it = mask.requireEnabled.GetIterator();
|
||||||
|
while (it.Next(out var id))
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it = mask.requireDisabled.GetIterator();
|
||||||
|
while (it.Next(out var id))
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it = mask.rejectIfEnabled.GetIterator();
|
||||||
|
while (it.Next(out var id))
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static bool CheckBit(byte* maskBase, int index)
|
internal static bool CheckBit(byte* maskBase, int index)
|
||||||
{
|
{
|
||||||
@@ -513,10 +577,25 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
{
|
{
|
||||||
var archetypeID = _matchingArchetypes[i];
|
var archetypeID = _matchingArchetypes[i];
|
||||||
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
|
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
|
||||||
|
|
||||||
|
var requiresFiltering = RequiresEnableableFiltering(in archetype, in _mask);
|
||||||
|
|
||||||
for (var j = 0; j < archetype.ChunkCount; j++)
|
for (var j = 0; j < archetype.ChunkCount; j++)
|
||||||
{
|
{
|
||||||
ref var chunk = ref archetype.GetChunkReference(j);
|
ref var chunk = ref archetype.GetChunkReference(j);
|
||||||
|
if (chunk._count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requiresFiltering)
|
||||||
|
{
|
||||||
|
// Fast path: all entities match
|
||||||
|
total += chunk._count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Slow path: check individual bits
|
||||||
for (var k = 0; k < chunk._count; k++)
|
for (var k = 0; k < chunk._count; k++)
|
||||||
{
|
{
|
||||||
if (IsEntityValid(chunk.GetUnsafePtr(), k, in archetype, in _mask))
|
if (IsEntityValid(chunk.GetUnsafePtr(), k, in archetype, in _mask))
|
||||||
@@ -526,10 +605,54 @@ public unsafe partial struct EntityQuery : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly bool HasMatchingEntity()
|
||||||
|
{
|
||||||
|
var world = World.GetWorld(_worldID);
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _matchingArchetypes.Count; i++)
|
||||||
|
{
|
||||||
|
var archetypeID = _matchingArchetypes[i];
|
||||||
|
ref var archetype = ref world.ComponentManager.GetArchetypeReference(archetypeID);
|
||||||
|
|
||||||
|
// Check ONCE per archetype if we actually need to loop entities
|
||||||
|
var requiresFiltering = RequiresEnableableFiltering(in archetype, in _mask);
|
||||||
|
|
||||||
|
for (var j = 0; j < archetype.ChunkCount; j++)
|
||||||
|
{
|
||||||
|
ref var chunk = ref archetype.GetChunkReference(j);
|
||||||
|
if (chunk._count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requiresFiltering)
|
||||||
|
{
|
||||||
|
// No enablement constraints? Any entity in this chunk is a match!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = 0; k < chunk._count; k++)
|
||||||
|
{
|
||||||
|
if (IsEntityValid(chunk.GetUnsafePtr(), k, in archetype, in _mask))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_mask.Dispose();
|
_mask.Dispose();
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public abstract class SystemBase : ISystem
|
|||||||
{
|
{
|
||||||
var queryID = _requiredQueries[i];
|
var queryID = _requiredQueries[i];
|
||||||
ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
|
ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
|
||||||
if (query.CalculateEntityCount() == 0)
|
if (!query.HasMatchingEntity())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ namespace Ghost.Graphics.D3D12;
|
|||||||
|
|
||||||
internal sealed unsafe partial class D3D12ResourceAllocator
|
internal sealed unsafe partial class D3D12ResourceAllocator
|
||||||
{
|
{
|
||||||
// NOTE: _MAX_BYTES may not be accurate, we need to verify it with feature level checks.
|
// NOTE: MAX_BYTES may not be accurate, we need to verify it with feature level checks.
|
||||||
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
|
private const uint MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
|
||||||
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
|
private const uint MAX_TEXTURE2D_DIMENSION = 16384u;
|
||||||
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
|
private const uint MAX_TEXTURE3D_DIMENSION = 2048u;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CheckBufferSize(ulong sizeInBytes)
|
private static void CheckBufferSize(ulong sizeInBytes)
|
||||||
{
|
{
|
||||||
if (sizeInBytes > _MAX_BYTES)
|
if (sizeInBytes > MAX_BYTES)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void CheckTexture2DSize(uint width, uint height)
|
private static void CheckTexture2DSize(uint width, uint height)
|
||||||
{
|
{
|
||||||
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
|
if (width > MAX_TEXTURE2D_DIMENSION || height > MAX_TEXTURE2D_DIMENSION)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
|
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
|
||||||
}
|
}
|
||||||
@@ -41,8 +41,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator
|
|||||||
|
|
||||||
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||||
{
|
{
|
||||||
private const uint _UPLOAD_BATCH_SIZE = 64 * 1024 * 1024; // 64 MB
|
private const uint UPLOAD_BATCH_SIZE = 64 * 1024 * 1024; // 64 MB
|
||||||
private const uint _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH = 16 * 1024 * 1024; // 16 MB
|
private const uint MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH = 16 * 1024 * 1024; // 16 MB
|
||||||
|
|
||||||
private UniquePtr<D3D12MA_Allocator> _d3d12MA;
|
private UniquePtr<D3D12MA_Allocator> _d3d12MA;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ using Ghost.Core;
|
|||||||
using Ghost.Core.Graphics;
|
using Ghost.Core.Graphics;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Graphics.RHI;
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ using Ghost.Core;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.RHI;
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
public unsafe struct ShaderByteCode
|
||||||
|
{
|
||||||
|
public byte* pCode;
|
||||||
|
public ulong size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe delegate void ShaderVariantCompiledHandler(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, ReadOnlySpan<ShaderByteCode> byteCodes);
|
||||||
|
|
||||||
public interface IShaderCompilationBridge : IDisposable
|
public interface IShaderCompilationBridge : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -11,7 +19,13 @@ public interface IShaderCompilationBridge : IDisposable
|
|||||||
void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask);
|
void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a shader variant has been successfully compiled and updated.
|
/// Event triggered when a shader variant has been successfully compiled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<Key64<ShaderVariant>, ulong> OnShaderVariantCompiled;
|
event ShaderVariantCompiledHandler OnShaderVariantCompiled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a shader source has been imported or modified, requiring cache invalidation.
|
||||||
|
/// </summary>
|
||||||
|
event Action<ulong> OnShaderInvalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ public class RenderSystem : IDisposable
|
|||||||
|
|
||||||
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||||
_swapChainManager = new SwapChainManager(_graphicsEngine);
|
_swapChainManager = new SwapChainManager(_graphicsEngine);
|
||||||
_shaderLibrary = new ShaderLibrary(desc.ShaderCompilationBridge, desc.ShaderCacheDirectory);
|
_shaderLibrary = new ShaderLibrary(desc.ShaderCompilationBridge, _graphicsEngine.PipelineLibrary, desc.ShaderCacheDirectory);
|
||||||
_asyncCopyPipeline = new AsyncCopyPipeline(_graphicsEngine);
|
_asyncCopyPipeline = new AsyncCopyPipeline(_graphicsEngine);
|
||||||
|
|
||||||
_renderThread = new Thread(RenderLoop)
|
_renderThread = new Thread(RenderLoop)
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Ghost.Graphics.Services;
|
namespace Ghost.Graphics.Services;
|
||||||
|
|
||||||
internal unsafe struct ShaderByteCode
|
|
||||||
{
|
|
||||||
public byte* pCode;
|
|
||||||
public ulong size;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct ShaderCache : IDisposable
|
internal struct ShaderCache : IDisposable
|
||||||
{
|
{
|
||||||
public MemoryBlock byteCode;
|
public MemoryBlock byteCode;
|
||||||
@@ -73,24 +67,32 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
|
|
||||||
private readonly string _cacheDirectory;
|
private readonly string _cacheDirectory;
|
||||||
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
|
private readonly IShaderCompilationBridge? _shaderCompilationBridge;
|
||||||
|
private readonly IPipelineLibrary? _pipelineLibrary;
|
||||||
|
|
||||||
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, string cacheDirectory)
|
internal ShaderLibrary(IShaderCompilationBridge? shaderCompilationBridge, IPipelineLibrary? pipelineLibrary, string cacheDirectory)
|
||||||
{
|
{
|
||||||
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, AllocationHandle.Persistent);
|
_inMemoryCache = new UnsafeHashMap<ulong, CacheEntry>(16, AllocationHandle.Persistent);
|
||||||
_variantToCompiledHash = new UnsafeHashMap<ulong, ulong>(16, AllocationHandle.Persistent);
|
_variantToCompiledHash = new UnsafeHashMap<ulong, ulong>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
_cacheDirectory = cacheDirectory;
|
_cacheDirectory = cacheDirectory;
|
||||||
_shaderCompilationBridge = shaderCompilationBridge;
|
_shaderCompilationBridge = shaderCompilationBridge;
|
||||||
|
_pipelineLibrary = pipelineLibrary;
|
||||||
|
|
||||||
if (_shaderCompilationBridge != null)
|
if (_shaderCompilationBridge != null)
|
||||||
{
|
{
|
||||||
_shaderCompilationBridge.OnShaderVariantCompiled += OnVariantCompiled;
|
_shaderCompilationBridge.OnShaderVariantCompiled += OnVariantCompiled;
|
||||||
|
_shaderCompilationBridge.OnShaderInvalidated += OnShaderInvalidated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVariantCompiled(Key64<ShaderVariant> variantKey, ulong newCompiledHash)
|
private void OnVariantCompiled(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, ReadOnlySpan<ShaderByteCode> byteCodes)
|
||||||
{
|
{
|
||||||
_variantToCompiledHash[variantKey] = newCompiledHash;
|
CacheCompiledResult(shaderId, passIndex, variantKey, byteCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShaderInvalidated(ulong shaderId)
|
||||||
|
{
|
||||||
|
InvalidateShaderCache(shaderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetShaderCacheFilePath(ulong hash)
|
private string GetShaderCacheFilePath(ulong hash)
|
||||||
@@ -203,15 +205,20 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
return Error.NotFound;
|
return Error.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateShaderCache(ulong id, IPipelineLibrary pipelineLibrary)
|
public void InvalidateShaderCache(ulong id)
|
||||||
{
|
{
|
||||||
|
if (_pipelineLibrary == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_inMemoryCache.TryGetValue(id, out var entry))
|
if (_inMemoryCache.TryGetValue(id, out var entry))
|
||||||
{
|
{
|
||||||
for (int i = 0; i < entry.cache.Length; i++)
|
for (int i = 0; i < entry.cache.Length; i++)
|
||||||
{
|
{
|
||||||
if (entry.cache[i].compiledHash != 0)
|
if (entry.cache[i].compiledHash != 0)
|
||||||
{
|
{
|
||||||
pipelineLibrary.EvictStalePipelines(entry.cache[i].compiledHash);
|
_pipelineLibrary.EvictStalePipelines(entry.cache[i].compiledHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.Dispose();
|
entry.Dispose();
|
||||||
@@ -237,6 +244,7 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
if (_shaderCompilationBridge != null)
|
if (_shaderCompilationBridge != null)
|
||||||
{
|
{
|
||||||
_shaderCompilationBridge.OnShaderVariantCompiled -= OnVariantCompiled;
|
_shaderCompilationBridge.OnShaderVariantCompiled -= OnVariantCompiled;
|
||||||
|
_shaderCompilationBridge.OnShaderInvalidated -= OnShaderInvalidated;
|
||||||
}
|
}
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Microsoft.UI.Xaml.Controls;
|
|||||||
using Microsoft.UI.Xaml.Media;
|
using Microsoft.UI.Xaml.Media;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using Windows.Devices.Geolocation;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Windows;
|
namespace Ghost.Graphics.Test.Windows;
|
||||||
|
|
||||||
@@ -80,7 +79,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
group.AddSystem<CameraMovingSystem>();
|
group.AddSystem<CameraMovingSystem>();
|
||||||
group.SortSystems();
|
group.SortSystems();
|
||||||
|
|
||||||
_world.SystemManager.InitializeAll(default);
|
_world.SystemManager.InitializeAll();
|
||||||
|
|
||||||
// Create Camera Entity
|
// Create Camera Entity
|
||||||
|
|
||||||
@@ -107,7 +106,7 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
// Create Mesh Entity
|
// Create Mesh Entity
|
||||||
//MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
//MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
||||||
Utilities.MeshUtility.LoadMesh("F:/c/SimpleRayTracer/native/assets/bunny.obj", Allocator.Persistent, out var vertices, out var indices).ThrowIfFailed();
|
Utilities.MeshUtility.LoadMesh("F:/c/SimpleRayTracer/native/assets/bunny.obj", AllocationHandle.Persistent, out var vertices, out var indices).ThrowIfFailed();
|
||||||
|
|
||||||
// TODO: Put this to the beginning of the frame without creating another command buffer?
|
// TODO: Put this to the beginning of the frame without creating another command buffer?
|
||||||
using var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
using var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
||||||
@@ -144,7 +143,6 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
|
|
||||||
private void GraphicsTestWindow_Closed(object sender, WindowEventArgs e)
|
private void GraphicsTestWindow_Closed(object sender, WindowEventArgs e)
|
||||||
{
|
{
|
||||||
#if false
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CompositionTarget.Rendering -= OnRendering;
|
CompositionTarget.Rendering -= OnRendering;
|
||||||
@@ -172,7 +170,6 @@ public sealed partial class GraphicsTestWindow : Window
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace Ghost.UnitTest.ECS;
|
|||||||
[DoNotParallelize]
|
[DoNotParallelize]
|
||||||
public class EntityCommandBufferTests
|
public class EntityCommandBufferTests
|
||||||
{
|
{
|
||||||
private struct CompA : IComponent { public int value; }
|
private struct CompA : IComponentData { public int value; }
|
||||||
private struct CompB : IComponent { public int value; }
|
private struct CompB : IComponentData { public int value; }
|
||||||
|
|
||||||
private World _world = null!;
|
private World _world = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ namespace Ghost.UnitTest.ECS;
|
|||||||
[DoNotParallelize]
|
[DoNotParallelize]
|
||||||
public class EntityQueryTests
|
public class EntityQueryTests
|
||||||
{
|
{
|
||||||
private struct CompA : IComponent { public int value; }
|
private struct CompA : IComponentData { public int value; }
|
||||||
private struct CompB : IComponent { public int value; }
|
private struct CompB : IComponentData { public int value; }
|
||||||
private struct CompC : IComponent { public int value; }
|
private struct CompC : IComponentData { public int value; }
|
||||||
private struct Tag : IComponent { }
|
private struct Tag : IComponentData { }
|
||||||
private struct EnableableComp : IEnableableComponent { public int value; }
|
private struct EnableableComp : IEnableableComponent { public int value; }
|
||||||
|
|
||||||
private World _world = null!;
|
private World _world = null!;
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ public class SharedComponentTests
|
|||||||
{
|
{
|
||||||
// ── Test components ────────────────────────────────────────────────────────
|
// ── Test components ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private struct Tag : IComponent { }
|
private struct Tag : IComponentData { }
|
||||||
|
|
||||||
private struct Tag2 : IComponent { }
|
private struct Tag2 : IComponentData { }
|
||||||
|
|
||||||
private struct ScalarData : IComponent
|
private struct ScalarData : IComponentData
|
||||||
{
|
{
|
||||||
public float value;
|
public float value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace Ghost.UnitTest.ECS;
|
|||||||
[DoNotParallelize]
|
[DoNotParallelize]
|
||||||
public class WorldTests
|
public class WorldTests
|
||||||
{
|
{
|
||||||
private struct CompA : IComponent { public int value; }
|
private struct CompA : IComponentData { public int value; }
|
||||||
private struct CompB : IComponent { public int value; }
|
private struct CompB : IComponentData { public int value; }
|
||||||
|
|
||||||
private World _world = null!;
|
private World _world = null!;
|
||||||
|
|
||||||
|
|||||||
@@ -31,16 +31,22 @@ public class ShaderLibraryTest
|
|||||||
private class MockShaderCompilationBridge : IShaderCompilationBridge
|
private class MockShaderCompilationBridge : IShaderCompilationBridge
|
||||||
{
|
{
|
||||||
public List<(ulong id, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)> Requests { get; } = new();
|
public List<(ulong id, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)> Requests { get; } = new();
|
||||||
public event Action<Key64<ShaderVariant>, ulong>? OnShaderVariantCompiled;
|
public event ShaderVariantCompiledHandler? OnShaderVariantCompiled;
|
||||||
|
public event Action<ulong>? OnShaderInvalidated;
|
||||||
|
|
||||||
public void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)
|
public void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)
|
||||||
{
|
{
|
||||||
Requests.Add((shaderId, passIndex, variantKey, keywordMask));
|
Requests.Add((shaderId, passIndex, variantKey, keywordMask));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerCompiled(Key64<ShaderVariant> variantKey, ulong newHash)
|
public void TriggerCompiled(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, ReadOnlySpan<ShaderByteCode> byteCodes)
|
||||||
{
|
{
|
||||||
OnShaderVariantCompiled?.Invoke(variantKey, newHash);
|
OnShaderVariantCompiled?.Invoke(shaderId, passIndex, variantKey, byteCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerInvalidated(ulong shaderId)
|
||||||
|
{
|
||||||
|
OnShaderInvalidated?.Invoke(shaderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -64,8 +70,8 @@ public class ShaderLibraryTest
|
|||||||
public unsafe void TestInvalidateShaderCache_EvictsPipelinesAndClearsCache()
|
public unsafe void TestInvalidateShaderCache_EvictsPipelinesAndClearsCache()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using var shaderLibrary = new ShaderLibrary(null, "TestShaderCache");
|
|
||||||
var mockPipelineLibrary = new MockPipelineLibrary();
|
var mockPipelineLibrary = new MockPipelineLibrary();
|
||||||
|
using var shaderLibrary = new ShaderLibrary(null, mockPipelineLibrary, "TestShaderCache");
|
||||||
|
|
||||||
ulong testShaderId = 12345;
|
ulong testShaderId = 12345;
|
||||||
var testPassIndex = 0;
|
var testPassIndex = 0;
|
||||||
@@ -99,7 +105,7 @@ public class ShaderLibraryTest
|
|||||||
Assert.AreEqual(expectedHash, cachedResult.Value.compiledHash);
|
Assert.AreEqual(expectedHash, cachedResult.Value.compiledHash);
|
||||||
|
|
||||||
// Act: Invalidate
|
// Act: Invalidate
|
||||||
shaderLibrary.InvalidateShaderCache(testShaderId, mockPipelineLibrary);
|
shaderLibrary.InvalidateShaderCache(testShaderId);
|
||||||
|
|
||||||
// Assert: EvictStalePipelines should be called
|
// Assert: EvictStalePipelines should be called
|
||||||
Assert.HasCount(1, mockPipelineLibrary.EvictedHashes);
|
Assert.HasCount(1, mockPipelineLibrary.EvictedHashes);
|
||||||
@@ -115,7 +121,7 @@ public class ShaderLibraryTest
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mockBridge = new MockShaderCompilationBridge();
|
var mockBridge = new MockShaderCompilationBridge();
|
||||||
using var shaderLibrary = new ShaderLibrary(mockBridge, "TestShaderCache");
|
using var shaderLibrary = new ShaderLibrary(mockBridge, null, "TestShaderCache");
|
||||||
var testShaderId = 555UL;
|
var testShaderId = 555UL;
|
||||||
var passIndex = 1;
|
var passIndex = 1;
|
||||||
var variantKey = new Key64<ShaderVariant>(777);
|
var variantKey = new Key64<ShaderVariant>(777);
|
||||||
@@ -133,28 +139,39 @@ public class ShaderLibraryTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestOnVariantCompiled_UpdatesHashCache()
|
public unsafe void TestOnVariantCompiled_UpdatesHashCache()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mockBridge = new MockShaderCompilationBridge();
|
var mockBridge = new MockShaderCompilationBridge();
|
||||||
using var shaderLibrary = new ShaderLibrary(mockBridge, "TestShaderCache");
|
using var shaderLibrary = new ShaderLibrary(mockBridge, null, "TestShaderCache");
|
||||||
var variantKey = new Key64<ShaderVariant>(123);
|
var variantKey = new Key64<ShaderVariant>(123);
|
||||||
ulong newHash = 0xABCDE;
|
|
||||||
|
var fakeData = new byte[] { 1, 2, 3, 4 };
|
||||||
|
var expectedHash = 0UL;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
mockBridge.TriggerCompiled(variantKey, newHash);
|
fixed (byte* pData = fakeData)
|
||||||
|
{
|
||||||
|
var byteCode = new ShaderByteCode { pCode = pData, size = (ulong)fakeData.Length };
|
||||||
|
|
||||||
|
// Compute expected hash of bytecode
|
||||||
|
var dataSpan = new ReadOnlySpan<byte>(pData, fakeData.Length);
|
||||||
|
expectedHash = System.IO.Hashing.XxHash64.HashToUInt64(dataSpan);
|
||||||
|
|
||||||
|
mockBridge.TriggerCompiled(0, 0, variantKey, new ReadOnlySpan<ShaderByteCode>(ref byteCode));
|
||||||
|
}
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var result = shaderLibrary.GetCompiledHash(0, 0, variantKey);
|
var result = shaderLibrary.GetCompiledHash(0, 0, variantKey);
|
||||||
Assert.IsTrue(result.IsSuccess);
|
Assert.IsTrue(result.IsSuccess);
|
||||||
Assert.AreEqual(newHash, result.Value);
|
Assert.AreEqual(expectedHash, result.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestGetCompiledCache_HandlesIndexOutOfBounds()
|
public void TestGetCompiledCache_HandlesIndexOutOfBounds()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
using var shaderLibrary = new ShaderLibrary(null, "TestShaderCache");
|
using var shaderLibrary = new ShaderLibrary(null, null, "TestShaderCache");
|
||||||
var testShaderId = 111UL;
|
var testShaderId = 111UL;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ public class SceneGraphBuilderTests
|
|||||||
private Entity CreateEntityWithScene(Scene scene)
|
private Entity CreateEntityWithScene(Scene scene)
|
||||||
{
|
{
|
||||||
var entity = _world.EntityManager.CreateEntity();
|
var entity = _world.EntityManager.CreateEntity();
|
||||||
_world.EntityManager.AddComponent(entity, new SceneID { value = scene.ID });
|
_world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entity CreateEntityWithSceneAndHierarchy(Scene scene, Entity parent)
|
private Entity CreateEntityWithSceneAndHierarchy(Scene scene, Entity parent)
|
||||||
{
|
{
|
||||||
var entity = _world.EntityManager.CreateEntity();
|
var entity = _world.EntityManager.CreateEntity();
|
||||||
_world.EntityManager.AddComponent(entity, new SceneID { value = scene.ID });
|
_world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
|
||||||
_world.EntityManager.AddComponent(entity, new Hierarchy
|
_world.EntityManager.AddComponent(entity, new Hierarchy
|
||||||
{
|
{
|
||||||
parent = Entity.Invalid,
|
parent = Entity.Invalid,
|
||||||
@@ -163,7 +163,7 @@ public class SceneGraphBuilderTests
|
|||||||
public void Build_InvalidSceneEntitiesAreExcluded()
|
public void Build_InvalidSceneEntitiesAreExcluded()
|
||||||
{
|
{
|
||||||
var entity = _world.EntityManager.CreateEntity();
|
var entity = _world.EntityManager.CreateEntity();
|
||||||
_world.EntityManager.AddComponent(entity, new SceneID { value = Scene.INVALID_ID });
|
_world.EntityManager.AddSharedComponent(entity, new SceneID { value = Scene.INVALID_ID });
|
||||||
|
|
||||||
var nodes = SceneGraphBuilder.Build(_world);
|
var nodes = SceneGraphBuilder.Build(_world);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#if true
|
|
||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Editor.Core;
|
using Ghost.Editor.Core;
|
||||||
using Ghost.Editor.Core.Assets;
|
using Ghost.Editor.Core.Assets;
|
||||||
@@ -75,7 +74,7 @@ public class SceneSerializationTests
|
|||||||
{
|
{
|
||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
var entity = world.EntityManager.CreateEntity();
|
var entity = world.EntityManager.CreateEntity();
|
||||||
world.EntityManager.AddComponent(entity, new SceneID { value = scene.ID });
|
world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ public class SceneSerializationTests
|
|||||||
{
|
{
|
||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
var entity = world.EntityManager.CreateEntity();
|
var entity = world.EntityManager.CreateEntity();
|
||||||
world.EntityManager.AddComponent(entity, new SceneID { value = scene.ID });
|
world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
|
||||||
world.EntityManager.AddComponent(entity, Hierarchy.Root);
|
world.EntityManager.AddComponent(entity, Hierarchy.Root);
|
||||||
world.EntityManager.AddComponent(entity, new LocalToWorld());
|
world.EntityManager.AddComponent(entity, new LocalToWorld());
|
||||||
|
|
||||||
@@ -104,8 +103,8 @@ public class SceneSerializationTests
|
|||||||
CreateEntityWithHierarchy(scene, Entity.Invalid);
|
CreateEntityWithHierarchy(scene, Entity.Invalid);
|
||||||
|
|
||||||
var filePath = Path.Combine(_projectRoot, "TestScene.gscene");
|
var filePath = Path.Combine(_projectRoot, "TestScene.gscene");
|
||||||
var saveResult = _serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
_serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
||||||
Assert.IsTrue(saveResult.IsSuccess, saveResult.Message);
|
|
||||||
Assert.IsTrue(File.Exists(filePath));
|
Assert.IsTrue(File.Exists(filePath));
|
||||||
|
|
||||||
var json = await File.ReadAllTextAsync(filePath, TestContext.CancellationToken);
|
var json = await File.ReadAllTextAsync(filePath, TestContext.CancellationToken);
|
||||||
@@ -129,7 +128,7 @@ public class SceneSerializationTests
|
|||||||
scene = loadResult.Value;
|
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(world, scene, 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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +144,7 @@ public class SceneSerializationTests
|
|||||||
var world = _worldService.EditorWorld;
|
var world = _worldService.EditorWorld;
|
||||||
|
|
||||||
var filePath = Path.Combine(_projectRoot, "HierarchyScene.gscene");
|
var filePath = Path.Combine(_projectRoot, "HierarchyScene.gscene");
|
||||||
var saveResult = _serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
_serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
||||||
Assert.IsTrue(saveResult.IsSuccess, saveResult.Message);
|
|
||||||
|
|
||||||
var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
|
var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
|
||||||
Assert.IsNotNull(data);
|
Assert.IsNotNull(data);
|
||||||
@@ -288,6 +286,7 @@ public class SceneSerializationTests
|
|||||||
Assert.IsTrue(loadResult.IsSuccess, loadResult.Message);
|
Assert.IsTrue(loadResult.IsSuccess, loadResult.Message);
|
||||||
|
|
||||||
var afterCount = 0;
|
var afterCount = 0;
|
||||||
|
query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
afterCount += chunk.EntityCount;
|
afterCount += chunk.EntityCount;
|
||||||
@@ -302,10 +301,9 @@ public class SceneSerializationTests
|
|||||||
var scene = SceneManager.CreateScene();
|
var scene = SceneManager.CreateScene();
|
||||||
|
|
||||||
var filePath = Path.Combine(_projectRoot, "EmptyScene.gscene");
|
var filePath = Path.Combine(_projectRoot, "EmptyScene.gscene");
|
||||||
var saveResult = _serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
_serializationService.SaveSceneFromEditorWorld(filePath, scene);
|
||||||
Assert.IsTrue(saveResult.IsFailure, "Empty scene should fail to save.");
|
|
||||||
|
|
||||||
Assert.AreEqual("No entities found for the specified scene.", saveResult.Message);
|
Assert.IsTrue(File.Exists(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -396,7 +394,7 @@ public class SceneSerializationTests
|
|||||||
var world = World.Create(entityCapacity: 64);
|
var world = World.Create(entityCapacity: 64);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Result<SceneManager.LoadedSceneData> sceneDataResult;
|
Result<LoadedSceneData> sceneDataResult;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
fixed (byte* pBinary = binary)
|
fixed (byte* pBinary = binary)
|
||||||
@@ -409,7 +407,7 @@ public class SceneSerializationTests
|
|||||||
Assert.AreEqual(3, sceneDataResult.Value.entities.Count);
|
Assert.AreEqual(3, sceneDataResult.Value.entities.Count);
|
||||||
|
|
||||||
using var sceneData = sceneDataResult.Value;
|
using var sceneData = sceneDataResult.Value;
|
||||||
SceneManager.MaterializeScene(world, in sceneData, scene);
|
SceneManager.MaterializeScene(world, in sceneData, scene, 0, sceneData.entities.Count);
|
||||||
|
|
||||||
var queryID = new QueryBuilder().WithAll<Hierarchy>().Build(world);
|
var queryID = new QueryBuilder().WithAll<Hierarchy>().Build(world);
|
||||||
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
@@ -481,4 +479,3 @@ public class SceneSerializationTests
|
|||||||
get; set;
|
get; set;
|
||||||
} = null!;
|
} = null!;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|||||||
Reference in New Issue
Block a user