diff --git a/src/Editor/Ghost.DSL/Ghost.DSL.csproj b/src/Editor/Ghost.DSL/Ghost.DSL.csproj
index 89bdeed..f06620a 100644
--- a/src/Editor/Ghost.DSL/Ghost.DSL.csproj
+++ b/src/Editor/Ghost.DSL/Ghost.DSL.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/Asset.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/Asset.cs
index 623ff61..f5d4917 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/Asset.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/Asset.cs
@@ -1,6 +1,4 @@
using Ghost.Editor.Core.Contracts;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Ghost.Editor.Core.AssetHandler;
@@ -16,21 +14,9 @@ public abstract class Asset
get;
}
- public Guid[] Dependencies
- {
- get;
- }
-
- public IAssetSettings? Settings
- {
- get;
- }
-
- protected Asset(Guid id, Guid[] dependencies, IAssetSettings? settings)
+ protected Asset(Guid id)
{
ID = id;
- Dependencies = dependencies;
- Settings = settings;
}
public virtual ValueTask RefreshAsync(IAssetRegistry db, CancellationToken token = default)
@@ -39,100 +25,6 @@ public abstract class Asset
}
}
-// Do not change the order of the fields in this struct, as it is used for binary serialization/deserialization.
-[StructLayout(LayoutKind.Sequential, Size = SIZE)]
-internal struct AssetMetadata
-{
- public const int CURRENT_FORMAT_VERSION = 1;
- public const int SIZE = 128; // Fixed size for metadata header. We choose 128 bytes to allow future expansion without breaking compatibility.
-
- public AssetMetadata(Guid id, Guid typeID)
- {
- FormatVersion = CURRENT_FORMAT_VERSION;
- ID = id;
- TypeID = typeID;
- }
-
- public int FormatVersion
- {
- get;
- }
-
- public Guid ID
- {
- get;
- }
-
- public Guid TypeID
- {
- get;
- }
-
- public int HandlerVersion
- {
- get; set;
- }
-
- public int DependencyCount
- {
- get; set;
- }
-
- public long DependenciesOffset
- {
- get; set;
- }
-
- public long SettingsOffset
- {
- get; set;
- }
-
- public long SettingsSize
- {
- get; set;
- }
-
- public long ContentOffset
- {
- get; set;
- }
-
- public long ContentSize
- {
- get; set;
- }
-
- public static void WriteToStream(Stream stream, scoped ref readonly AssetMetadata metadata)
- {
- var buffer = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in metadata, 1));
- stream.Write(buffer);
- }
-
- public static AssetMetadata ReadFromStream(Stream stream)
- {
- Span buffer = stackalloc byte[SIZE];
- stream.ReadExactly(buffer);
- return Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(buffer));
- }
-}
-
-[StructLayout(LayoutKind.Sequential, Size = SIZE)]
-public readonly struct DependencyInfo
-{
- public const int SIZE = 16;
-
- public Guid ID
- {
- get; init;
- }
-
- public readonly ReadOnlySpan AsBytes()
- {
- return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in this, 1));
- }
-}
-
public readonly struct AssetReference : IEquatable
{
private readonly int _value;
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandler.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandler.cs
index 4885429..4344af6 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandler.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandler.cs
@@ -6,34 +6,27 @@ namespace Ghost.Editor.Core.AssetHandler;
[AttributeUsage(AttributeTargets.Class)]
public sealed class CustomAssetHandlerAttribute : Attribute
{
- public required string ID
+ public CustomAssetHandlerAttribute(string id, string[] supportedExtensions, int version = 1)
{
- get; init;
}
-
- public required string[] SupportedExtensions
- {
- get; init;
- }
-
- public bool AllowCaching
- {
- get; init;
- } = true;
}
public interface IAssetExportOptions;
public interface IAssetHandler
{
- ValueTask> LoadAsync(Stream sourceStream, IAssetRegistry assetRegistry, CancellationToken token = default);
+ ValueTask> LoadAsync(Stream sourceStream, Guid id, IAssetRegistry assetRegistry, CancellationToken token = default);
ValueTask SaveAsync(Asset asset, Stream targetStream, IAssetRegistry assetRegistry, CancellationToken token = default);
}
public interface IImportableAssetHandler : IAssetHandler
{
- IAssetSettings? CreateDefaultSettings() => null;
+ IAssetSettings? CreateDefaultSettings();
ValueTask ImportAsync(Stream sourceStream, Stream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default);
+}
+
+public interface IExportableAssetHandler : IAssetHandler
+{
ValueTask ExportAsync(Stream assetStream, Stream targetStream, IAssetExportOptions? options, CancellationToken token = default);
}
@@ -46,16 +39,16 @@ public static class AssetHandlerExtensions
return await handler.ImportAsync(sourceStream, targetStream, id, settings, token);
}
- public static async ValueTask ExportAsync(this IImportableAssetHandler handler, string assetFilePath, string targetFilePath, IAssetExportOptions? options, CancellationToken token = default)
+ public static async ValueTask ExportAsync(this IExportableAssetHandler handler, string assetFilePath, string targetFilePath, IAssetExportOptions? options, CancellationToken token = default)
{
await using var assetStream = new FileStream(assetFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
await using var targetStream = new FileStream(targetFilePath, FileMode.Create, FileAccess.Write, FileShare.None);
return await handler.ExportAsync(assetStream, targetStream, options, token);
}
- public static async ValueTask> LoadAsync(this IAssetHandler handler, string assetFilePath, IAssetRegistry assetDatabase, CancellationToken token = default)
+ public static async ValueTask> LoadAsync(this IAssetHandler handler, string assetFilePath, Guid id, IAssetRegistry assetDatabase, CancellationToken token = default)
{
await using var sourceStream = new FileStream(assetFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
- return await handler.LoadAsync(sourceStream, assetDatabase, token);
+ return await handler.LoadAsync(sourceStream, id, assetDatabase, token);
}
}
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandlerRegistry.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandlerRegistry.cs
index ef62488..c347295 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandlerRegistry.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetHandlerRegistry.cs
@@ -1,13 +1,10 @@
-using System.Reflection;
-using Ghost.Editor.Core.Utilities;
-
namespace Ghost.Editor.Core.AssetHandler;
///
/// One-time scan at editor startup → two dictionaries.
/// All lookups are O(1) after construction.
///
-internal sealed class AssetHandlerRegistry
+public sealed class AssetHandlerRegistry
{
private readonly Dictionary _byExtension;
private readonly Dictionary _byTypeId;
@@ -18,49 +15,17 @@ internal sealed class AssetHandlerRegistry
_byExtension = new Dictionary(StringComparer.OrdinalIgnoreCase);
_byTypeId = new Dictionary();
_versionByTypeId = new Dictionary();
+ }
- foreach (var typeInfo in TypeCache.GetTypes())
+ public void RegisterHandler(IAssetHandler handler, ReadOnlySpan extensions, Guid typeId, int version)
+ {
+ _byTypeId[typeId] = handler;
+ _versionByTypeId[typeId] = version;
+
+ foreach (var ext in extensions)
{
- if (typeInfo.IsAbstract || typeInfo.IsInterface)
- {
- continue;
- }
-
- if (!typeof(IAssetHandler).IsAssignableFrom(typeInfo))
- {
- continue;
- }
-
- var attr = typeInfo.GetCustomAttribute();
- if (attr == null)
- {
- continue;
- }
-
- if (!Guid.TryParse(attr.ID, out var typeId))
- {
- continue;
- }
-
- try
- {
- if (Activator.CreateInstance(typeInfo) is IAssetHandler handler)
- {
- _byTypeId[typeId] = handler;
- // Note: Versioning could be expanded, but for now we assume version 1 or look for a constant
- _versionByTypeId[typeId] = 1;
-
- foreach (var ext in attr.SupportedExtensions)
- {
- var normalizedExt = ext.StartsWith('.') ? ext : "." + ext;
- _byExtension[normalizedExt] = handler;
- }
- }
- }
- catch
- {
- // Log failure to instantiate handler in real app
- }
+ var normalizedExt = ext.StartsWith('.') ? ext : "." + ext;
+ _byExtension[normalizedExt] = handler;
}
}
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetMeta.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetMeta.cs
index ad8107a..ae2f003 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/AssetMeta.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/AssetMeta.cs
@@ -67,6 +67,8 @@ public sealed class AssetMeta
internal static class AssetMetaIO
{
+ private const string _META_EXTENSION = ".gmeta";
+
private static readonly JsonSerializerOptions s_options = new()
{
WriteIndented = true,
@@ -108,7 +110,7 @@ internal static class AssetMetaIO
File.Move(tempPath, metaPath);
}
- public static string GetMetaPath(string sourceFilePath) => sourceFilePath + ".gmeta";
+ public static string GetMetaPath(string sourceFilePath) => sourceFilePath + _META_EXTENSION;
- public static string GetSourcePath(string metaPath) => metaPath[..^".gmeta".Length];
+ public static string GetSourcePath(string metaPath) => metaPath[..^_META_EXTENSION.Length];
}
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/TextureAsset.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/TextureAsset.cs
index e7aefd6..d461289 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/TextureAsset.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/TextureAsset.cs
@@ -2,10 +2,7 @@ using Ghost.Core;
using Ghost.Editor.Core.Contracts;
using Ghost.Graphics.RHI;
using ImageMagick;
-using System.Buffers;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using static Ghost.Editor.Core.AssetHandler.TextureAssetSettings;
namespace Ghost.Editor.Core.AssetHandler;
@@ -54,15 +51,47 @@ public class TextureAsset : Asset
internal const string _TYPE_ID = "0906F4EB-C3F0-431B-BCEA-132C88AB0C3F";
internal static readonly Guid s_typeGuid = Guid.Parse(_TYPE_ID);
- private readonly Handle _texture;
+ private readonly byte[] _textureData;
+ private readonly uint _width;
+ private readonly uint _height;
+ private readonly uint _depth;
+ private readonly uint _colorComponents;
public override Guid TypeID => s_typeGuid;
- public Handle Texture => _texture;
- public TextureAsset(Guid id, Guid[] dependencies, IAssetSettings? settings, Handle texture)
- : base(id, dependencies, settings)
+ ///
+ /// Gets the raw texture data in a compressed format.
+ ///
+ public ReadOnlyMemory TextureData => _textureData;
+
+ ///
+ /// Gets the width of the texture in pixels.
+ ///
+ public uint Width => _width;
+
+ ///
+ /// Gets the height of the texture in pixels.
+ ///
+ public uint Height => _height;
+
+ ///
+ /// Gets the bit depth of the texture.
+ ///
+ public uint Depth => _depth;
+
+ ///
+ /// Gets the number of color components in the texture.
+ ///
+ public uint ColorComponents => _colorComponents;
+
+ internal TextureAsset(byte[] data, ImageContentHeader header, Guid id)
+ : base(id)
{
- _texture = texture;
+ _textureData = data;
+ _width = header.width;
+ _height = header.height;
+ _depth = header.depth;
+ _colorComponents = header.colorComponents;
}
}
@@ -213,148 +242,96 @@ public class TextureAssetSettings : IAssetSettings
} = new SamplerSettings();
}
-[CustomAssetHandler(ID = TextureAsset._TYPE_ID, SupportedExtensions = new[] { ".png", ".jpg", ".jpeg", ".tga", ".bmp", ".hdr" })]
+[StructLayout(LayoutKind.Sequential, Size = 64)] // Leave extra space for future expansion without breaking compatibility
+internal struct ImageContentHeader
+{
+ public uint width;
+ public uint height;
+ public uint depth;
+ public uint colorComponents;
+}
+
+[CustomAssetHandler(TextureAsset._TYPE_ID, [ ".png", ".jpg", ".jpeg", ".tga", ".bmp", ".hdr" ], 1)]
internal class TextureAssetHandler : IImportableAssetHandler
{
- private const int _CURRENT_VERSION = 1;
-
- private struct ImageContentHeader
+ public IAssetSettings? CreateDefaultSettings()
{
- public uint width;
- public uint height;
- public uint depth;
- public uint colorComponents;
+ return new TextureAssetSettings();
}
- private static async ValueTask> WriteSettingsToStreamAsync(TextureAssetSettings settings, Stream stream, CancellationToken token = default)
+ public async ValueTask> LoadAsync(Stream sourceStream, Guid id, IAssetRegistry assetRegistry, CancellationToken token = default)
{
- var size = Unsafe.SizeOf() + Unsafe.SizeOf() + Unsafe.SizeOf();
- var tempArray = ArrayPool.Shared.Rent(size);
-
try
{
- ref var address = ref MemoryMarshal.GetReference(tempArray);
- Unsafe.WriteUnaligned(ref address, settings.Basic);
- Unsafe.WriteUnaligned(ref Unsafe.Add(ref address, Unsafe.SizeOf()), settings.Advanced);
- Unsafe.WriteUnaligned(ref Unsafe.Add(ref address, Unsafe.SizeOf() + Unsafe.SizeOf()), settings.Sampler);
+ // FIX: Should the sourceStream be the stream of the imported file or the raw asset file?
+ // Or should we change our paramemters to inlcude more information and let each handler decide how to load the asset?
+ // The problem of a single sourceStream is, for example, for texture assets, we don't even need to read the ".png" file at all,
+ // but for some other asset types, we may don't even have imported intermediate files at all.
- await stream.WriteAsync(tempArray.AsMemory(0, size), token).ConfigureAwait(false);
+ // var path = assetRegistry.GetAssetPath(id);
+ // if (string.IsNullOrEmpty(path))
+ // {
+ // return Result.Failure("Asset path not found in registry.");
+ // }
+ //
+ // var metadataPath = AssetMetaIO.GetMetaPath(path);
+ // var meta = await AssetMetaIO.ReadAsync(metadataPath, token).ConfigureAwait(false);
+ // Logger.DebugAssert(meta != null, $"Missing or invalid metadata for asset at {path}");
- return Result.Success(size);
+
+
+ var header = new ImageContentHeader();
+ sourceStream.ReadExactly(MemoryMarshal.AsBytes(new Span(ref header)));
+
+ var imageDataSize = (int)(sourceStream.Length - sourceStream.Position);
+ var imageData = new byte[imageDataSize];
+ await sourceStream.ReadExactlyAsync(imageData, token).ConfigureAwait(false);
+
+ var asset = new TextureAsset(imageData, header, id);
+ return asset;
}
catch (Exception ex)
{
- return Result.Failure($"Failed to write texture asset settings to stream: {ex.Message}");
+ return Result.Failure($"Failed to load texture asset: {ex.Message}");
}
- finally
- {
- ArrayPool.Shared.Return(tempArray);
- }
- }
-
- private static async ValueTask> ReadSettingsFromStreamAsync(Stream stream, CancellationToken token = default)
- {
- var size = Unsafe.SizeOf() + Unsafe.SizeOf() + Unsafe.SizeOf();
- var tempArray = ArrayPool.Shared.Rent(size);
-
- try
- {
- await stream.ReadAsync(tempArray.AsMemory(0, size), token).ConfigureAwait(false);
-
- // Use index-based reads after the await to avoid 'ref across await' errors.
- var basic = Unsafe.ReadUnaligned(ref tempArray[0]);
- var advanced = Unsafe.ReadUnaligned(ref tempArray[Unsafe.SizeOf()]);
- var sampler = Unsafe.ReadUnaligned(ref tempArray[Unsafe.SizeOf() + Unsafe.SizeOf()]);
-
- var settings = new TextureAssetSettings
- {
- Basic = basic,
- Advanced = advanced,
- Sampler = sampler
- };
-
- return Result.Success(settings);
- }
- catch (Exception ex)
- {
- return Result.Failure($"Failed to read texture asset settings from stream: {ex.Message}");
- }
- finally
- {
- ArrayPool.Shared.Return(tempArray);
- }
- }
-
- public ValueTask ExportAsync(Stream assetStream, Stream targetStream, IAssetExportOptions? options, CancellationToken token = default)
- {
- throw new NotImplementedException();
- }
-
- public async ValueTask ImportAsync(Stream sourceStream, Stream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
- {
- var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
- using var image = new MagickImage(sourceStream);
- var bytes = image.ToByteArray();
-
- await TextureProcessor.CompressToCacheAsync(EditorApplication.LibraryFolderPath, id, bytes, image.Width, image.Height, image.Depth, textureSettings, token).ConfigureAwait(false);
-
- var header = new AssetMetadata(id, TextureAsset.s_typeGuid)
- {
- HandlerVersion = _CURRENT_VERSION,
- SettingsOffset = AssetMetadata.SIZE,
- };
-
- targetStream.Seek(header.SettingsOffset, SeekOrigin.Begin);
- var sizeResult = await WriteSettingsToStreamAsync(textureSettings, targetStream, token).ConfigureAwait(false);
- if (sizeResult.IsFailure)
- {
- return Result.Failure($"Failed to write texture asset settings: {sizeResult.Message}");
- }
-
- // Content layout (all little-endian):
- // uint32 width
- // uint32 height
- // uint32 depth
- // uint32 colorComponents
- // byte[] pixelBytes
-
- header.SettingsSize = sizeResult.Value;
- header.ContentOffset = header.SettingsOffset + sizeResult.Value;
- unsafe
- {
- header.ContentSize = sizeof(ImageContentHeader) + image.Width * image.Height * (image.Depth / 8) * image.ChannelCount;
- }
-
- // Write raw image content
- targetStream.Seek(header.ContentOffset, SeekOrigin.Begin);
-
- var contentHeader = new ImageContentHeader
- {
- width = image.Width,
- height = image.Height,
- depth = image.Depth,
- colorComponents = image.ChannelCount
- };
-
- targetStream.Write(MemoryMarshal.AsBytes(new Span(ref contentHeader)));
-
- await targetStream.WriteAsync(bytes, token).ConfigureAwait(false);
- await targetStream.FlushAsync(token).ConfigureAwait(false);
-
- // Patch header now that all sizes are known
- targetStream.Seek(0, SeekOrigin.Begin);
- AssetMetadata.WriteToStream(targetStream, ref header);
-
- return Result.Success();
- }
-
- public ValueTask> LoadAsync(Stream sourceStream, IAssetRegistry assetRegistry, CancellationToken token = default)
- {
- throw new NotImplementedException();
}
public ValueTask SaveAsync(Asset asset, Stream targetStream, IAssetRegistry assetRegistry, CancellationToken token = default)
{
throw new NotImplementedException();
}
+
+ public async ValueTask ImportAsync(Stream sourceStream, Stream targetStream, Guid id, IAssetSettings? settings, CancellationToken token = default)
+ {
+ try
+ {
+ var textureSettings = settings as TextureAssetSettings ?? new TextureAssetSettings();
+ using var image = new MagickImage(sourceStream);
+ var bytes = image.ToByteArray();
+
+ await TextureProcessor.CompressToCacheAsync(EditorApplication.LibraryFolderPath, id, bytes, image.Width, image.Height, image.Depth, textureSettings, token)
+ .ConfigureAwait(false);
+
+ targetStream.Seek(0, SeekOrigin.Begin);
+
+ var contentHeader = new ImageContentHeader
+ {
+ width = image.Width,
+ height = image.Height,
+ depth = image.Depth,
+ colorComponents = image.ChannelCount
+ };
+
+ targetStream.Write(MemoryMarshal.AsBytes(new Span(ref contentHeader)));
+
+ await targetStream.WriteAsync(bytes, token).ConfigureAwait(false);
+ await targetStream.FlushAsync(token).ConfigureAwait(false);
+
+ return Result.Success();
+ }
+ catch (Exception ex)
+ {
+ return Result.Failure($"Failed to import texture asset: {ex.Message}");
+ }
+ }
}
diff --git a/src/Editor/Ghost.Editor.Core/AssetHandler/TextureProcessor.cs b/src/Editor/Ghost.Editor.Core/AssetHandler/TextureProcessor.cs
index 721be18..8e336dd 100644
--- a/src/Editor/Ghost.Editor.Core/AssetHandler/TextureProcessor.cs
+++ b/src/Editor/Ghost.Editor.Core/AssetHandler/TextureProcessor.cs
@@ -186,7 +186,7 @@ internal static class TextureProcessor
var workItem = new NvttPipelineTask(cachePath, image, width, height, depth, settings);
ThreadPool.UnsafeQueueUserWorkItem(workItem, true);
await workItem.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
-
+
return cachePath;
}
diff --git a/src/Editor/Ghost.Editor.Core/Attributes.cs b/src/Editor/Ghost.Editor.Core/Attributes.cs
index 34b940b..17fdaff 100644
--- a/src/Editor/Ghost.Editor.Core/Attributes.cs
+++ b/src/Editor/Ghost.Editor.Core/Attributes.cs
@@ -57,8 +57,20 @@ public class EditorInjectionAttribute : DiscoverableAttributeBase
Transient,
}
+ public ServiceLifetime Lifetime
+ {
+ get;
+ }
+
+ public Type ImplementationType
+ {
+ get;
+ }
+
public EditorInjectionAttribute(ServiceLifetime lifetime, Type implementationType)
{
+ Lifetime = lifetime;
+ ImplementationType = implementationType;
}
}
@@ -86,4 +98,4 @@ public sealed class ContextMenuItemAttribute : DiscoverableAttributeBase
Name = name;
Group = group;
}
-}
\ No newline at end of file
+}
diff --git a/src/Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj b/src/Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj
index 9183680..addfc6e 100644
--- a/src/Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj
+++ b/src/Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj
@@ -15,7 +15,7 @@
-
+
@@ -25,6 +25,7 @@
+
@@ -40,4 +41,4 @@
Designer
-
+
\ No newline at end of file
diff --git a/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs b/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs
index a89baee..88b0db3 100644
--- a/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs
+++ b/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs
@@ -224,6 +224,19 @@ internal sealed class AssetCatalog : IDisposable
return list;
}
+ public List GetDependencies(Guid guid)
+ {
+ _cmdGetDependencies.Parameters.Clear();
+ _cmdGetDependencies.Parameters.AddWithValue("@guid", guid.ToByteArray());
+ using var reader = _cmdGetDependencies.ExecuteReader();
+ var list = new List();
+ while (reader.Read())
+ {
+ list.Add(new Guid((byte[])reader[0]));
+ }
+ return list;
+ }
+
public List<(Guid guid, string sourcePath)> GetDirtyAssets()
{
using var reader = _cmdGetDirty.ExecuteReader();
diff --git a/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs b/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs
index 0248dec..55d8902 100644
--- a/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs
+++ b/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs
@@ -165,26 +165,32 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
return;
}
- var handlerTypeId = handler?.GetType().GetCustomAttribute()?.ID;
+ var handlerTypeId = handler?.GetType().GetCustomAttributesData().FirstOrDefault(d => d.AttributeType == typeof(CustomAssetHandlerAttribute))?.ConstructorArguments[0].Value;
var meta = new AssetMeta
{
Guid = Guid.NewGuid(),
- HandlerTypeId = handlerTypeId == null ? null : Guid.Parse(handlerTypeId),
+ HandlerTypeId = handlerTypeId is string str? Guid.Parse(str) : null,
HandlerVersion = 1,
Settings = importable?.CreateDefaultSettings()
};
_ignoreMetaWrites[metaPath] = true;
await AssetMetaIO.WriteAsync(metaPath, meta);
-
+
_catalog.Upsert(meta, relativePath);
await _importCoordinator.EnqueueAsync(new ImportJob(meta.Guid, relativePath, metaPath, ImportReason.NewAsset));
}
- public string? GetAssetPath(Guid id) => _catalog.GetSourcePath(id);
+ public string? GetAssetPath(Guid id)
+ {
+ return _catalog.GetSourcePath(id);
+ }
- public Guid GetAssetGuid(string path) => _catalog.GetGuid(path.Replace('\\', '/'));
+ public Guid GetAssetGuid(string path)
+ {
+ return _catalog.GetGuid(path.Replace(Path.DirectorySeparatorChar, '/'));
+ }
public async ValueTask> ImportAssetAsync(string sourceFilePath, string targetAssetPath, CancellationToken token = default)
{
@@ -192,7 +198,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
// Current requirement: "returns the new GUID immediately (import happens in background)"
var ext = Path.GetExtension(sourceFilePath);
- var relativePath = targetAssetPath.Replace('\\', '/');
+ var relativePath = targetAssetPath.Replace(Path.DirectorySeparatorChar, '/');
var fullPath = Path.Combine(_assetsRoot, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
@@ -224,7 +230,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
{
if (_loadedAssets.TryGetValue(id, out var weakRef) && weakRef.TryGetTarget(out var asset))
{
- return Result.Success(asset);
+ return asset;
}
await _loadLock.WaitAsync(token);
@@ -232,7 +238,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
{
if (_loadedAssets.TryGetValue(id, out weakRef) && weakRef.TryGetTarget(out asset))
{
- return Result.Success(asset);
+ return asset;
}
var importedPath = Path.Combine(_libraryRoot, "Imports", $"{id:N}.imported");
@@ -257,7 +263,10 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable
}
}
- public ValueTask SaveAssetAsync(Asset asset, CancellationToken token = default) => throw new NotImplementedException();
+ public ValueTask SaveAssetAsync(Asset asset, CancellationToken token = default)
+ {
+ throw new NotImplementedException();
+ }
public void Dispose()
{
diff --git a/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs b/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs
index 67ce375..8e4e832 100644
--- a/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs
+++ b/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs
@@ -97,7 +97,7 @@ internal sealed class ImportCoordinator : IDisposable
return;
}
- var handler = (meta.HandlerTypeId.HasValue)
+ var handler = meta.HandlerTypeId.HasValue
? _handlers.GetByTypeId(meta.HandlerTypeId.Value)
: _handlers.GetByExtension(Path.GetExtension(job.SourcePath));
diff --git a/src/Editor/Ghost.Editor.Core/Utilities/TypeCache.cs b/src/Editor/Ghost.Editor.Core/Utilities/TypeCache.cs
index d0df615..38bbbf5 100644
--- a/src/Editor/Ghost.Editor.Core/Utilities/TypeCache.cs
+++ b/src/Editor/Ghost.Editor.Core/Utilities/TypeCache.cs
@@ -6,13 +6,13 @@ namespace Ghost.Editor.Core.Utilities;
public static class TypeCache
{
- private static TypeInfo[] s_types;
- private static Dictionary> s_attributeMethodCache;
+ private static TypeInfo[] s_types = null!;
+ private static Dictionary> s_attributeMethodCache = null!;
+ private static Dictionary> s_attributeTypeCache = null!;
static TypeCache()
{
- s_types = LoadTypes();
- s_attributeMethodCache = FindMethodWithAttribute();
+ Reload();
}
private static TypeInfo[] LoadTypes()
@@ -62,6 +62,29 @@ public static class TypeCache
return dict;
}
+ private static Dictionary> FindTypesWithAttribute()
+ {
+ var dict = new Dictionary>();
+ for (int i = 0; i < s_types.Length; i++)
+ {
+ TypeInfo? type = s_types[i];
+ var attrs = type.GetCustomAttributes(false);
+ foreach (var attr in attrs)
+ {
+ var key = attr.GetType().TypeHandle.Value;
+ ref var typeList = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out var exist);
+ if (!exist)
+ {
+ typeList = new List();
+ }
+
+ typeList!.Add(i);
+ }
+ }
+
+ return dict;
+ }
+
internal static void Initialize()
{
// Intentionally left blank.
@@ -72,6 +95,7 @@ public static class TypeCache
{
s_types = LoadTypes();
s_attributeMethodCache = FindMethodWithAttribute();
+ s_attributeTypeCache = FindTypesWithAttribute();
}
public static IReadOnlyCollection GetTypes()
@@ -79,7 +103,7 @@ public static class TypeCache
return s_types;
}
- public static IReadOnlyCollection? GetMethodsWithAttribute()
+ public static IEnumerable? GetMethodsWithAttribute()
where T : DiscoverableAttributeBase
{
var key = typeof(T).TypeHandle.Value;
@@ -90,4 +114,16 @@ public static class TypeCache
return null;
}
-}
\ No newline at end of file
+
+ public static IEnumerable? GetTypesWithAttribute()
+ where T : DiscoverableAttributeBase
+ {
+ var key = typeof(T).TypeHandle.Value;
+ if (s_attributeTypeCache.TryGetValue(key, out var typeIndices))
+ {
+ return typeIndices.Select(i => s_types[i]);
+ }
+
+ return null;
+ }
+}
diff --git a/src/Editor/Ghost.Editor/App.xaml.cs b/src/Editor/Ghost.Editor/App.xaml.cs
index 52ca88c..c377cb3 100644
--- a/src/Editor/Ghost.Editor/App.xaml.cs
+++ b/src/Editor/Ghost.Editor/App.xaml.cs
@@ -72,6 +72,7 @@ public partial class App : Application
services.AddTransient();
+ // TODO: Use source generators to generate this code at compile time instead of using reflection at runtime.
foreach (var type in TypeCache.GetTypes())
{
var data = type.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType == typeof(EditorInjectionAttribute));
diff --git a/src/Editor/Ghost.Editor/Ghost.Editor.csproj b/src/Editor/Ghost.Editor/Ghost.Editor.csproj
index 5b50c39..b81341c 100644
--- a/src/Editor/Ghost.Editor/Ghost.Editor.csproj
+++ b/src/Editor/Ghost.Editor/Ghost.Editor.csproj
@@ -39,7 +39,7 @@
-
+
diff --git a/src/Runtime/Ghost.Core/Contracts/ICloneable.cs b/src/Runtime/Ghost.Core/Contracts/ICloneable.cs
deleted file mode 100644
index 4e1740e..0000000
--- a/src/Runtime/Ghost.Core/Contracts/ICloneable.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Ghost.Core.Contracts;
-
-public interface ICloneable
-{
- object Clone();
-}
-
-public interface ICloneable
-{
- T Clone();
-}
\ No newline at end of file
diff --git a/src/Runtime/Ghost.Core/Contracts/IReleasable.cs b/src/Runtime/Ghost.Core/Contracts/IReleasable.cs
deleted file mode 100644
index 61a5f53..0000000
--- a/src/Runtime/Ghost.Core/Contracts/IReleasable.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Ghost.Core.Contracts;
-
-internal interface IReleasable
-{
- void InternalRelease();
-}
\ No newline at end of file
diff --git a/src/Runtime/Ghost.Core/Ghost.Core.csproj b/src/Runtime/Ghost.Core/Ghost.Core.csproj
index 0d8d178..fc32708 100644
--- a/src/Runtime/Ghost.Core/Ghost.Core.csproj
+++ b/src/Runtime/Ghost.Core/Ghost.Core.csproj
@@ -26,7 +26,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/Runtime/Ghost.Core/Handle.cs b/src/Runtime/Ghost.Core/Handle.cs
index cc9a6f3..c6b2027 100644
--- a/src/Runtime/Ghost.Core/Handle.cs
+++ b/src/Runtime/Ghost.Core/Handle.cs
@@ -1,3 +1,5 @@
+using System.Runtime.InteropServices;
+
namespace Ghost.Core;
public readonly struct Handle : IEquatable>
@@ -245,4 +247,49 @@ public readonly struct Key128 : IEquatable>
public static implicit operator UInt128(Key128 key) => key.Value;
public static implicit operator Key128(UInt128 value) => new Key128(value);
-}
\ No newline at end of file
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public readonly struct AssetRef : IEquatable>
+{
+ public readonly Guid guid;
+
+ public static AssetRef Null => default;
+
+ public bool IsValid => guid != Guid.Empty;
+
+ public AssetRef(Guid guid)
+ {
+ this.guid = guid;
+ }
+
+ public bool Equals(AssetRef other)
+ {
+ return guid == other.guid;
+ }
+
+ public override int GetHashCode()
+ {
+ return guid.GetHashCode();
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is AssetRef r && Equals(r);
+ }
+
+ public override string ToString()
+ {
+ return $"AssetRef<{typeof(T).Name}>({guid:N})";
+ }
+
+ public static bool operator ==(AssetRef a, AssetRef b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(AssetRef a, AssetRef b)
+ {
+ return !a.Equals(b);
+ }
+}
diff --git a/src/Runtime/Ghost.Engine/AssetManager.cs b/src/Runtime/Ghost.Engine/AssetManager.cs
new file mode 100644
index 0000000..f3e476e
--- /dev/null
+++ b/src/Runtime/Ghost.Engine/AssetManager.cs
@@ -0,0 +1,44 @@
+using Ghost.Core;
+using System.Runtime.InteropServices;
+
+namespace Ghost.Engine;
+
+internal abstract class RuntimeAsset;
+
+internal interface IRuntimeAssetLoader
+{
+ ValueTask> LoadAsync(Stream cookedData, Guid id, CancellationToken token = default);
+}
+
+internal sealed class RuntimeLoaderRegistry
+{
+ private readonly Dictionary _loaders = new();
+ public void Register(Guid cookedTypeId, IRuntimeAssetLoader loader)
+ {
+ _loaders[cookedTypeId] = loader;
+ }
+ public IRuntimeAssetLoader? GetLoader(Guid cookedTypeId)
+ {
+ _loaders.TryGetValue(cookedTypeId, out var loader);
+ return loader;
+ }
+}
+
+internal sealed class CookedTextureLoader : IRuntimeAssetLoader
+{
+ public static readonly Guid TYPE_ID = TextureAsset.s_typeGuid;
+ public async ValueTask> LoadAsync(Stream cookedData, Guid id, CancellationToken token)
+ {
+ // Read the ImageContentHeader you wrote during import
+ var header = new ImageContentHeader();
+ cookedData.ReadExactly(MemoryMarshal.AsBytes(new Span(ref header)));
+ // Read the rest as raw GPU data (DDS/BC compressed bytes)
+ var data = new byte[cookedData.Length - cookedData.Position];
+ await cookedData.ReadExactlyAsync(data, token);
+ return new TextureAsset(data, header, id);
+ }
+}
+
+public class AssetManager
+{
+}
diff --git a/src/Runtime/Ghost.Generator/AssetHandlerRegistrationGenerator.cs b/src/Runtime/Ghost.Generator/AssetHandlerRegistrationGenerator.cs
new file mode 100644
index 0000000..e0c301b
--- /dev/null
+++ b/src/Runtime/Ghost.Generator/AssetHandlerRegistrationGenerator.cs
@@ -0,0 +1,90 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+
+namespace Ghost.Generator;
+
+[Generator]
+internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var handerCandidates = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ predicate: (s, _) => s is ClassDeclarationSyntax,
+ transform: GetAssetHandlerSymbol)
+ .Where(symbol => symbol != null)
+ .Collect();
+
+ context.RegisterSourceOutput(handerCandidates, GenerateRegistrationCode);
+ }
+
+ private void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray array)
+ {
+ if (array.IsDefaultOrEmpty)
+ {
+ return;
+ }
+
+ var sb = new System.Text.StringBuilder();
+
+ foreach (var symbol in array)
+ {
+ var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.AssetHandler.CustomAssetHandlerAttribute");
+ if (attribute == null)
+ {
+ continue;
+ }
+
+ var id = attribute.ConstructorArguments[0].Value as string;
+ var extensionsTypesConstants = attribute.ConstructorArguments[1].Values;
+ var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
+ var version = (int)attribute.ConstructorArguments[2].Value;
+
+ sb.Append($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
+ }
+
+ var registerTypeName = "g_assethandler_registeration";
+ var code = $@"//
+
+[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
+internal static partial class {registerTypeName}
+{{
+ [global::System.Runtime.CompilerServices.ModuleInitializer]
+ internal static void RegisterAssetHandlers()
+ {{
+{sb}
+ }}
+}}";
+
+ context.AddSource($"{registerTypeName}.gen.cs", code);
+ }
+
+ private INamedTypeSymbol GetAssetHandlerSymbol(GeneratorSyntaxContext context, CancellationToken token)
+ {
+ var classSyntax = (ClassDeclarationSyntax)context.Node;
+ if (context.SemanticModel.GetDeclaredSymbol(classSyntax) is not INamedTypeSymbol symbol)
+ {
+ return null;
+ }
+
+ var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetHandler");
+ if (iHandlerSymbol == null)
+ {
+ return null;
+ }
+
+ foreach (var iface in symbol.AllInterfaces)
+ {
+ if (SymbolEqualityComparer.Default.Equals(iface, iHandlerSymbol))
+ {
+ return symbol;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs b/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs
index f651849..288cda0 100644
--- a/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs
+++ b/src/Runtime/Ghost.Generator/ComponentRegistrationGenerator.cs
@@ -1,6 +1,5 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
@@ -14,7 +13,6 @@ namespace Ghost.Generator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
- // 1. Pipeline: Find all structs implementing IComponent
var componentCandidates = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (s, _) => s is StructDeclarationSyntax,
@@ -22,7 +20,6 @@ namespace Ghost.Generator
.Where(symbol => symbol != null)
.Collect();
- // 4. Output: Generate the source using both pieces of data at once
context.RegisterSourceOutput(componentCandidates, GenerateRegistrationCode);
}
@@ -30,7 +27,7 @@ namespace Ghost.Generator
private static INamedTypeSymbol GetComponentSymbol(GeneratorSyntaxContext ctx, CancellationToken _)
{
var structSyntax = (StructDeclarationSyntax)ctx.Node;
- if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
+ if (ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is not INamedTypeSymbol symbol)
{
return null;
}
@@ -73,7 +70,7 @@ namespace Ghost.Generator
}
var typeName = $"g_component_registration";
- var code = $@"//
+ var code = $@"//
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal static partial class {typeName}
diff --git a/src/Runtime/Ghost.Generator/Ghost.Generator.csproj b/src/Runtime/Ghost.Generator/Ghost.Generator.csproj
index 16c9f5f..1813c5d 100644
--- a/src/Runtime/Ghost.Generator/Ghost.Generator.csproj
+++ b/src/Runtime/Ghost.Generator/Ghost.Generator.csproj
@@ -13,11 +13,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/Runtime/Ghost.Graphics.D3D12/Ghost.Graphics.D3D12.csproj b/src/Runtime/Ghost.Graphics.D3D12/Ghost.Graphics.D3D12.csproj
index 6706756..9d7316f 100644
--- a/src/Runtime/Ghost.Graphics.D3D12/Ghost.Graphics.D3D12.csproj
+++ b/src/Runtime/Ghost.Graphics.D3D12/Ghost.Graphics.D3D12.csproj
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs
index d8bec56..143df69 100644
--- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs
+++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphExecutor.cs
@@ -1,7 +1,6 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Services;
-using System.Diagnostics;
namespace Ghost.Graphics.RenderGraphModule;
diff --git a/src/Test/Ghost.Graphics.Test/Ghost.Graphics.Test.csproj b/src/Test/Ghost.Graphics.Test/Ghost.Graphics.Test.csproj
index c5b7443..eefd10e 100644
--- a/src/Test/Ghost.Graphics.Test/Ghost.Graphics.Test.csproj
+++ b/src/Test/Ghost.Graphics.Test/Ghost.Graphics.Test.csproj
@@ -42,11 +42,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/Test/Ghost.Graphics.Test/Systems/RenderExtractionSystem.cs b/src/Test/Ghost.Graphics.Test/Systems/RenderExtractionSystem.cs
index bd02440..56ae0d7 100644
--- a/src/Test/Ghost.Graphics.Test/Systems/RenderExtractionSystem.cs
+++ b/src/Test/Ghost.Graphics.Test/Systems/RenderExtractionSystem.cs
@@ -5,13 +5,12 @@ using Ghost.Engine.Components;
using Ghost.Engine.Systems;
using Ghost.Entities;
using Ghost.Graphics.Core;
-using Ghost.Graphics.Test.RenderPipeline;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.Test.Systems;
-[RenderPipelineSystem]
+//[RenderPipelineSystem]
[UpdateAfter]
public class RenderExtractionSystem : ISystem
{
diff --git a/src/Test/Ghost.UnitTest/Ghost.UnitTest.csproj b/src/Test/Ghost.UnitTest/Ghost.UnitTest.csproj
index 9b2af54..bf5f2ed 100644
--- a/src/Test/Ghost.UnitTest/Ghost.UnitTest.csproj
+++ b/src/Test/Ghost.UnitTest/Ghost.UnitTest.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj b/src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj
index 5da3cf6..3c5b9b6 100644
--- a/src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj
+++ b/src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj
@@ -8,7 +8,7 @@
-
+