feat(asset): asset manager + registration generator
Add runtime AssetManager and AssetHandlerRegistrationGenerator source generator. Update editor asset handler types and services to work with new registration mechanism and asset catalog. Remove legacy contracts ICloneable and IReleasable. Files added: - src/Runtime/Ghost.Engine/AssetManager.cs - src/Runtime/Ghost.Generator/AssetHandlerRegistrationGenerator.cs Major edits: - Editor asset handler classes and services (Asset*, Texture*, Registry) - Runtime Handle.cs and project files - Render graph executor and tests updated accordingly This commit introduces the foundation for the modern asset pipeline including generated registration of asset handlers and a centralized runtime AssetManager that will drive asset lifecycle.
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
namespace Ghost.Core.Contracts;
|
||||
|
||||
public interface ICloneable
|
||||
{
|
||||
object Clone();
|
||||
}
|
||||
|
||||
public interface ICloneable<T>
|
||||
{
|
||||
T Clone();
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Ghost.Core.Contracts;
|
||||
|
||||
internal interface IReleasable
|
||||
{
|
||||
void InternalRelease();
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.5" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="10.0.6" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
public readonly struct Handle<T> : IEquatable<Handle<T>>
|
||||
@@ -245,4 +247,49 @@ public readonly struct Key128<T> : IEquatable<Key128<T>>
|
||||
|
||||
public static implicit operator UInt128(Key128<T> key) => key.Value;
|
||||
public static implicit operator Key128<T>(UInt128 value) => new Key128<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public readonly struct AssetRef<T> : IEquatable<AssetRef<T>>
|
||||
{
|
||||
public readonly Guid guid;
|
||||
|
||||
public static AssetRef<T> Null => default;
|
||||
|
||||
public bool IsValid => guid != Guid.Empty;
|
||||
|
||||
public AssetRef(Guid guid)
|
||||
{
|
||||
this.guid = guid;
|
||||
}
|
||||
|
||||
public bool Equals(AssetRef<T> other)
|
||||
{
|
||||
return guid == other.guid;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return guid.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is AssetRef<T> r && Equals(r);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"AssetRef<{typeof(T).Name}>({guid:N})";
|
||||
}
|
||||
|
||||
public static bool operator ==(AssetRef<T> a, AssetRef<T> b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(AssetRef<T> a, AssetRef<T> b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Runtime/Ghost.Engine/AssetManager.cs
Normal file
44
src/Runtime/Ghost.Engine/AssetManager.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Ghost.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
internal abstract class RuntimeAsset;
|
||||
|
||||
internal interface IRuntimeAssetLoader
|
||||
{
|
||||
ValueTask<Result<RuntimeAsset>> LoadAsync(Stream cookedData, Guid id, CancellationToken token = default);
|
||||
}
|
||||
|
||||
internal sealed class RuntimeLoaderRegistry
|
||||
{
|
||||
private readonly Dictionary<Guid, IRuntimeAssetLoader> _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<Result<RuntimeAsset>> LoadAsync(Stream cookedData, Guid id, CancellationToken token)
|
||||
{
|
||||
// Read the ImageContentHeader you wrote during import
|
||||
var header = new ImageContentHeader();
|
||||
cookedData.ReadExactly(MemoryMarshal.AsBytes(new Span<ImageContentHeader>(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
|
||||
{
|
||||
}
|
||||
@@ -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<INamedTypeSymbol> 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 = $@"// <auto-generated />
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -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 = $@"// <auto-generated/>
|
||||
var code = $@"// <auto-generated/>
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal static partial class {typeName}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.1.0" />
|
||||
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.RenderGraphModule;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user