refactor(core): asset pipeline overhaul & dock removal
- Introduced IAsset interface and refactored asset loading/saving. - Migrated TextureContentHeader to Ghost.Engine; updated usage. - Rewrote AssetRegistry, AssetCatalog, ImportCoordinator for new asset flow. - Added thread-safe ConcurrentHashSet utility. - Improved EditorApplication folder management/init. - Updated TextureAssetHandler/TextureProcessor for new import/export. - Added EditorContentProvider for asset access. - Updated AssetManager to use new AssetType enum; removed GCHandle. - Removed all custom docking controls and templates. - Deleted obsolete ViewModels/Pages (Console, Hierarchy, Inspector, Project). - Renamed ProjectBrowser to ContentBrowser; updated references. - Updated NuGet packages, Result conversions, and commit instructions. - General cleanup: namespaces, dead code, structure.
This commit is contained in:
@@ -1,15 +1,2 @@
|
||||
namespace Ghost.Core;
|
||||
|
||||
public enum AssetType : byte
|
||||
{
|
||||
Texture = 0,
|
||||
Mesh = 1,
|
||||
Material = 2,
|
||||
Shaders = 3,
|
||||
Audio = 4,
|
||||
Scene = 5,
|
||||
Video = 6,
|
||||
Json = 7,
|
||||
|
||||
Unknown = 64,
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.8" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="2.0.0" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.0.0" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.14">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -81,7 +81,7 @@ public readonly struct Result
|
||||
sb.AppendLine(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
return Success();
|
||||
@@ -158,6 +158,7 @@ public readonly struct Result<T>
|
||||
|
||||
public static implicit operator Result<T>(T? data) => data is not null ? Success(data) : Failure(null);
|
||||
public static implicit operator Result<T>(Result result) => result.IsSuccess ? Success(default!) : Failure(result.Message);
|
||||
public static implicit operator Result(Result<T> result) => result.IsSuccess ? Result.Success() : Result.Failure(result.Message);
|
||||
public static implicit operator bool(Result<T> result) => result.IsSuccess;
|
||||
}
|
||||
|
||||
|
||||
162
src/Runtime/Ghost.Core/Utilities/ConcurrentHashSet.cs
Normal file
162
src/Runtime/Ghost.Core/Utilities/ConcurrentHashSet.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
public class ConcurrentHashSet<T> : IDisposable
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly ConcurrentHashSet<T> _set;
|
||||
private readonly HashSet<T>.Enumerator _enumerator;
|
||||
|
||||
public Enumerator(ConcurrentHashSet<T> set)
|
||||
{
|
||||
_set = set;
|
||||
_set._lock.EnterReadLock();
|
||||
_enumerator = _set._hashSet.GetEnumerator();
|
||||
}
|
||||
|
||||
public readonly T Current => _enumerator.Current;
|
||||
readonly object? IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_set._lock.IsReadLockHeld)
|
||||
{
|
||||
_set._lock.ExitReadLock();
|
||||
}
|
||||
|
||||
_enumerator.Dispose();
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
return _enumerator.MoveNext();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
private readonly HashSet<T> _hashSet = new HashSet<T>();
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _hashSet.Count;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsReadLockHeld)
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _hashSet.Count == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsReadLockHeld)
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~ConcurrentHashSet()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
public bool Add(T item)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return _hashSet.Add(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
return _hashSet.Contains(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsReadLockHeld)
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
return _hashSet.Remove(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_hashSet.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lock.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using MemoryHandle = System.Buffers.MemoryHandle;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
@@ -51,3 +53,47 @@ public unsafe class NativeMemoryManager<T> : MemoryManager<T>
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CastMemoryManager<TFrom, TTo> : MemoryManager<TTo>
|
||||
where TFrom : struct
|
||||
where TTo : struct
|
||||
{
|
||||
private readonly Memory<TFrom> _from;
|
||||
private MemoryHandle _innerHandle;
|
||||
|
||||
public CastMemoryManager(Memory<TFrom> from)
|
||||
{
|
||||
_from = from;
|
||||
}
|
||||
|
||||
public override Span<TTo> GetSpan()
|
||||
{
|
||||
return MemoryMarshal.Cast<TFrom, TTo>(_from.Span);
|
||||
}
|
||||
|
||||
public override MemoryHandle Pin(int elementIndex = 0)
|
||||
{
|
||||
_innerHandle = _from.Pin();
|
||||
|
||||
unsafe
|
||||
{
|
||||
int byteOffset = elementIndex * Unsafe.SizeOf<TTo>();
|
||||
void* pointer = (byte*)_innerHandle.Pointer + byteOffset;
|
||||
|
||||
return new MemoryHandle(pointer, default, this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unpin()
|
||||
{
|
||||
_innerHandle.Dispose();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_innerHandle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,7 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine.AssetLoader;
|
||||
|
||||
public abstract class Asset : IResourceReleasable
|
||||
{
|
||||
private bool _disposed;
|
||||
|
||||
public Guid ID
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract AssetType Type
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected Asset(Guid id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
protected virtual void Release(IResourceDatabase resourceDatabase)
|
||||
{
|
||||
}
|
||||
|
||||
public void ReleaseResource(IResourceDatabase database)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Release(database);
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct AssetReference : IEquatable<AssetReference>
|
||||
{
|
||||
private readonly int _value;
|
||||
@@ -84,71 +44,3 @@ public readonly struct AssetReference : IEquatable<AssetReference>
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 64)] // Leave extra space for future expansion without breaking compatibility
|
||||
public struct TextureContentHeader
|
||||
{
|
||||
public uint width;
|
||||
public uint height;
|
||||
public uint depth;
|
||||
public uint mipLevels;
|
||||
public uint dimension; // 1 for 1D, 2 for 2D, 3 for 3D
|
||||
public uint colorComponents;
|
||||
}
|
||||
|
||||
public class TextureAsset : Asset
|
||||
{
|
||||
private MemoryBlock _textureData;
|
||||
private readonly uint _width;
|
||||
private readonly uint _height;
|
||||
private readonly uint _depth;
|
||||
private readonly uint _colorComponents;
|
||||
private readonly uint _mipLevels;
|
||||
private readonly uint _dimension;
|
||||
|
||||
private Handle<GPUTexture> _textureHandle;
|
||||
|
||||
public override AssetType Type => AssetType.Texture;
|
||||
|
||||
public uint Width => _width;
|
||||
public uint Height => _height;
|
||||
public uint Depth => _depth;
|
||||
public uint MipLevels => _mipLevels;
|
||||
public uint Dimension => _dimension;
|
||||
public uint ColorComponents => _colorComponents;
|
||||
|
||||
public Handle<GPUTexture> TextureHandle => _textureHandle;
|
||||
|
||||
internal TextureAsset([OwnershipTransfer] ref MemoryBlock data, TextureContentHeader header, Guid id)
|
||||
: base(id)
|
||||
{
|
||||
_textureData = data;
|
||||
_width = header.width;
|
||||
_height = header.height;
|
||||
_depth = header.depth;
|
||||
_mipLevels = header.mipLevels;
|
||||
_dimension = header.dimension;
|
||||
_colorComponents = header.colorComponents;
|
||||
}
|
||||
|
||||
internal void SetTextureHandle(Handle<GPUTexture> handle, bool disposeCPUData = true)
|
||||
{
|
||||
_textureHandle = handle;
|
||||
if (disposeCPUData)
|
||||
{
|
||||
_textureData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<T> GeData<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return _textureData.AsSpan<T>();
|
||||
}
|
||||
|
||||
protected override void Release(IResourceDatabase resourceDatabase)
|
||||
{
|
||||
_textureData.Dispose();
|
||||
resourceDatabase.ReleaseResource(_textureHandle.AsResource());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Engine.AssetLoader;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using TerraFX.Interop.Windows;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 64)] // Leave extra space for future expansion without breaking compatibility
|
||||
public struct TextureContentHeader
|
||||
{
|
||||
public uint width;
|
||||
public uint height;
|
||||
public uint depth;
|
||||
public uint mipLevels;
|
||||
public uint dimension; // 1 for 1D, 2 for 2D, 3 for 3D
|
||||
public uint colorComponents;
|
||||
}
|
||||
|
||||
internal partial class AssetEntry
|
||||
{
|
||||
private unsafe class TextureData
|
||||
|
||||
@@ -11,11 +11,24 @@ using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
public enum AssetState : byte
|
||||
public enum AssetType
|
||||
{
|
||||
Texture = 0,
|
||||
Mesh = 1,
|
||||
Material = 2,
|
||||
Shaders = 3,
|
||||
Scene = 4,
|
||||
Audio = 5,
|
||||
Video = 6,
|
||||
Json = 7,
|
||||
|
||||
Unknown = 32, // We are unlikely to have more than 32 asset types.
|
||||
}
|
||||
|
||||
public enum AssetState
|
||||
{
|
||||
Unloaded = 0,
|
||||
Scheduled = 1,
|
||||
@@ -192,6 +205,11 @@ internal unsafe partial class AssetEntry
|
||||
public void OnReleaseResource()
|
||||
{
|
||||
s_onReleaseResource[(int)_assetType]?.Invoke(this);
|
||||
|
||||
if (_rawData.IsCreated)
|
||||
{
|
||||
_rawData.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +217,7 @@ internal struct LoadAssetJob : IJob
|
||||
{
|
||||
public Guid assetID;
|
||||
public AssetType assetType;
|
||||
public GCHandle assetManagerHandle;
|
||||
public AssetManager assetManager;
|
||||
|
||||
private static Result LoadRawData(IContentProvider contentProvider, AssetEntry entry)
|
||||
{
|
||||
@@ -232,8 +250,6 @@ internal struct LoadAssetJob : IJob
|
||||
|
||||
public readonly void Execute(ref readonly JobExecutionContext ctx)
|
||||
{
|
||||
var assetManager = assetManagerHandle.Target as AssetManager;
|
||||
|
||||
Logger.DebugAssert(assetManager is not null);
|
||||
|
||||
if (!assetManager.TryGetEntry(assetID, out var entry))
|
||||
@@ -279,8 +295,6 @@ internal partial class AssetManager : IDisposable
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, AssetEntry> _entries;
|
||||
|
||||
private GCHandle _selfHandle;
|
||||
|
||||
// TODO
|
||||
private Handle<GPUTexture> _fallbackTexture;
|
||||
private Handle<GPUTexture> _fallbackNormalMap;
|
||||
@@ -300,7 +314,6 @@ internal partial class AssetManager : IDisposable
|
||||
_jobScheduler = jobScheduler;
|
||||
|
||||
_entries = new ConcurrentDictionary<Guid, AssetEntry>();
|
||||
_selfHandle = GCHandle.Alloc(this, GCHandleType.Normal);
|
||||
}
|
||||
|
||||
internal bool TryGetEntry(Guid guid, [NotNullWhen(true)] out AssetEntry? entry)
|
||||
@@ -378,10 +391,10 @@ internal partial class AssetManager : IDisposable
|
||||
{
|
||||
assetID = entry.AssetId,
|
||||
assetType = entry.AssetType,
|
||||
assetManagerHandle = _selfHandle,
|
||||
assetManager = this,
|
||||
};
|
||||
|
||||
entry.SetLoadJobHandle(_jobScheduler.Schedule(ref job, dependency, JobPriority.Low)); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
|
||||
entry.SetLoadJobHandle(_jobScheduler.Schedule(ref job, JobPriority.Low, dependency)); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -409,18 +422,18 @@ internal partial class AssetManager : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.State is AssetState.Loading or AssetState.Loaded or AssetState.Uploading)
|
||||
if (Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Ready) == (int)AssetState.Ready)
|
||||
{
|
||||
// Entry is in Ready state — the old texture is valid and will remain visible.
|
||||
// Go directly to Scheduled → Loading → Loaded → Uploading → Ready again.
|
||||
// The swap cycle in RecordTextureUpload/OnTextureUploadComplete handles the
|
||||
// v1 → v2 transition exactly like the fallback → v1 transition.
|
||||
EnsureScheduled(entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.SetPendingReimport();
|
||||
return;
|
||||
}
|
||||
|
||||
// Entry is in Ready state — the old texture is valid and will remain visible.
|
||||
// Go directly to Scheduled → Loading → Loaded → Uploading → Ready again.
|
||||
// The swap cycle in RecordTextureUpload/OnTextureUploadComplete handles the
|
||||
// v1 → v2 transition exactly like the fallback → v1 transition.
|
||||
entry.State = AssetState.Scheduled;
|
||||
EnsureScheduled(entry);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -431,6 +444,5 @@ internal partial class AssetManager : IDisposable
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
_selfHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ internal unsafe struct ChunkInfo
|
||||
internal unsafe struct JobChunkBatch<TJob> : IJobParallelFor
|
||||
where TJob : unmanaged, IJobChunk
|
||||
{
|
||||
|
||||
public TJob userJob;
|
||||
public ReadOnlyUnsafeCollection<ChunkInfo> chunkInfos;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user