Refactor asset import API and mesh streaming pipeline

- Standardize IImportableAssetHandler.ImportAsync to return sub-asset results
- Remove ISubAssetImportableAssetHandler, merge into main interface
- Update FBX/Texture handlers for new import contract
- Add StreamUtility for efficient (async) binary writes
- Refactor meshlet/LOD building to use ref structs and safe memory
- Use new streaming utilities in mesh import/export and tests
- AssetCatalog.Remove now recursively deletes sub-assets
- Improve asset registry file watcher for better change detection
- Log unhandled exceptions in App instead of breaking
- Add interpolated collection support to NativeMemoryManager
- Update project references and fix minor bugs
This commit is contained in:
2026-05-05 17:19:24 +09:00
parent 8d3e1c91d7
commit 5de480e231
15 changed files with 214 additions and 180 deletions

View File

@@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.9" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="3.1.6" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.18">
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.20">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -20,7 +20,7 @@ public unsafe class NativeMemoryManager<T> : MemoryManager<T>
}
public static NativeMemoryManager<T> FromUnsafeCollection<C>(ref readonly C collection)
where C : unmanaged, IUnsafeCollection<T>
where C : IUnsafeCollection<T>
{
if (!collection.IsCreated)
{
@@ -30,6 +30,19 @@ public unsafe class NativeMemoryManager<T> : MemoryManager<T>
return new NativeMemoryManager<T>((T*)collection.GetUnsafePtr(), collection.Count);
}
public static NativeMemoryManager<T> FromUnsafeCollectionInterpolated<C, U>(ref readonly C collection)
where U : unmanaged
where C : IUnsafeCollection<U>
{
if (!collection.IsCreated)
{
throw new InvalidOperationException("The collection is not created.");
}
var length = collection.Count * Unsafe.SizeOf<U>() / Unsafe.SizeOf<T>();
return new NativeMemoryManager<T>((T*)collection.GetUnsafePtr(), length);
}
public static NativeMemoryManager<T> FromMemoryBlock(MemoryBlock memoryBlock, int start, int length)
{
return new NativeMemoryManager<T>((T*)memoryBlock.GetUnsafePtr() + start, length);

View File

@@ -0,0 +1,55 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Core.Utilities;
public static class StreamUtility
{
public static void Write<T>(this Stream stream, in T value)
where T : struct
{
stream.Write(MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in value)));
}
public static void Write<T>(this Stream stream, ReadOnlySpan<T> values)
where T : struct
{
if (values.IsEmpty)
{
return;
}
stream.Write(MemoryMarshal.AsBytes(values));
}
public static async ValueTask WriteAsync<T, C>(this Stream stream, C collection, CancellationToken cancellationToken = default)
where T : unmanaged
where C : IUnsafeCollection<T>
{
if (!collection.IsCreated || collection.Count == 0)
{
return;
}
using var manager = NativeMemoryManager<byte>.FromUnsafeCollectionInterpolated<C, T>(in collection);
await stream.WriteAsync(manager.Memory, cancellationToken).ConfigureAwait(false);
}
public static async ValueTask WriteAsync<T>(this Stream stream, T value, CancellationToken cancellationToken = default)
where T : unmanaged
{
var size = Unsafe.SizeOf<T>();
var buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
Unsafe.WriteUnaligned(ref buffer[0], value);
await stream.WriteAsync(buffer.AsMemory(0, size), cancellationToken);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
}

View File

@@ -188,6 +188,9 @@ internal unsafe class ShaderLibrary : IDisposable
kvp.Value.Dispose();
}
_inMemoryCache.Dispose();
_variantToCompiledHash.Dispose();
GC.SuppressFinalize(this);
}
}