forked from Misaki/GhostEngine
Improve AssetDatabase performance.
This commit is contained in:
@@ -5,7 +5,7 @@ namespace Ghost.Editor.Core.AssetHandle;
|
|||||||
|
|
||||||
public static partial class AssetDatabase
|
public static partial class AssetDatabase
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<Type, object> s_importerInstances = new();
|
private static readonly Dictionary<Type, AssetImporter> s_importerInstances = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import an asset at the specified path.
|
/// Import an asset at the specified path.
|
||||||
@@ -25,8 +25,8 @@ public static partial class AssetDatabase
|
|||||||
// Get or create importer instance
|
// Get or create importer instance
|
||||||
if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
|
if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
|
||||||
{
|
{
|
||||||
importerInstance = Activator.CreateInstance(importerType);
|
importerInstance = Activator.CreateInstance(importerType) as AssetImporter;
|
||||||
if (importerInstance == null)
|
if (importerInstance is null)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Failed to create importer instance for type {importerType.Name}");
|
return Result.Failure($"Failed to create importer instance for type {importerType.Name}");
|
||||||
}
|
}
|
||||||
@@ -41,45 +41,7 @@ public static partial class AssetDatabase
|
|||||||
return Result.Failure($"Failed to read asset metadata: {metaResult.Message}");
|
return Result.Failure($"Failed to read asset metadata: {metaResult.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Avoid reflection.
|
return await importerInstance.ImportAsync(assetPath, metaResult.Value, token);
|
||||||
// Find and invoke the ImportAsync method. Support importers that accept (string, AssetMeta)
|
|
||||||
// or (string, AssetMeta, CancellationToken).
|
|
||||||
var importMethod = importerType.GetMethod("ImportAsync", BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (importMethod == null)
|
|
||||||
{
|
|
||||||
return Result.Failure($"ImportAsync method not found on importer {importerType.Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var parameters = importMethod.GetParameters();
|
|
||||||
object? invokeResult;
|
|
||||||
|
|
||||||
if (parameters.Length == 2)
|
|
||||||
{
|
|
||||||
invokeResult = importMethod.Invoke(importerInstance, new object[] { assetPath, metaResult.Value });
|
|
||||||
}
|
|
||||||
else if (parameters.Length == 3 && parameters[2].ParameterType == typeof(CancellationToken))
|
|
||||||
{
|
|
||||||
invokeResult = importMethod.Invoke(importerInstance, new object[] { assetPath, metaResult.Value, token });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Result.Failure($"Unsupported ImportAsync signature on importer {importerType.Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invokeResult is not Task<Result> task)
|
|
||||||
{
|
|
||||||
return Result.Failure("Importer did not return a valid Task<Result>");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await task;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Failure($"Asset import failed: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -122,8 +84,8 @@ public static partial class AssetDatabase
|
|||||||
// Get or create importer instance
|
// Get or create importer instance
|
||||||
if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
|
if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
|
||||||
{
|
{
|
||||||
importerInstance = Activator.CreateInstance(importerType);
|
importerInstance = Activator.CreateInstance(importerType) as AssetImporter;
|
||||||
if (importerInstance == null)
|
if (importerInstance is null)
|
||||||
{
|
{
|
||||||
return Result<Guid>.Failure($"Failed to create importer instance for type {importerType.Name}");
|
return Result<Guid>.Failure($"Failed to create importer instance for type {importerType.Name}");
|
||||||
}
|
}
|
||||||
@@ -138,39 +100,20 @@ public static partial class AssetDatabase
|
|||||||
return Result<Guid>.Failure($"ExportAsync method not found on importer {importerType.Name}. This importer does not support exporting.");
|
return Result<Guid>.Failure($"ExportAsync method not found on importer {importerType.Name}. This importer does not support exporting.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Generate metadata for the new asset
|
// Generate metadata for the new asset
|
||||||
await GenerateMetaFileAsync(assetPath, token);
|
var result = await GenerateMetaFileAsync(assetPath, token);
|
||||||
|
if (result.IsFailure)
|
||||||
|
{
|
||||||
|
return Result<Guid>.Failure($"Failed to generate metadata: {result.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
var metaResult = await ReadMetaFileAsync(assetPath, token);
|
var metaResult = await ReadMetaFileAsync(assetPath, token);
|
||||||
if (metaResult.IsFailure)
|
if (metaResult.IsFailure)
|
||||||
{
|
{
|
||||||
return Result<Guid>.Failure($"Failed to generate metadata: {metaResult.Message}");
|
return Result<Guid>.Failure($"Failed to read metadata: {metaResult.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameters = exportMethod.GetParameters();
|
result = await importerInstance.ExportAsync(assetPath, assetData, metaResult.Value, token);
|
||||||
object? invokeResult;
|
|
||||||
|
|
||||||
if (parameters.Length == 3)
|
|
||||||
{
|
|
||||||
invokeResult = exportMethod.Invoke(importerInstance, new object[] { assetPath, assetData, metaResult.Value });
|
|
||||||
}
|
|
||||||
else if (parameters.Length == 4 && parameters[3].ParameterType == typeof(CancellationToken))
|
|
||||||
{
|
|
||||||
invokeResult = exportMethod.Invoke(importerInstance, new object[] { assetPath, assetData, metaResult.Value, token });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Result<Guid>.Failure($"Unsupported ExportAsync signature on importer {importerType.Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invokeResult is not Task<Result> task)
|
|
||||||
{
|
|
||||||
return Result<Guid>.Failure("Exporter did not return a valid Task<Result>");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await task;
|
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return Result<Guid>.Failure(result.Message);
|
return Result<Guid>.Failure(result.Message);
|
||||||
@@ -182,9 +125,4 @@ public static partial class AssetDatabase
|
|||||||
|
|
||||||
return metaResult.Value.Guid;
|
return metaResult.Value.Guid;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result<Guid>.Failure($"Asset export failed: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ghost.Core;
|
|||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.AssetHandle;
|
namespace Ghost.Editor.Core.AssetHandle;
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ public static partial class AssetDatabase
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Failed to load asset cache: {ex.Message}");
|
Logger.LogError($"Failed to load asset cache: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ public static partial class AssetDatabase
|
|||||||
private static bool s_autoRefreshEnabled = true;
|
private static bool s_autoRefreshEnabled = true;
|
||||||
private static readonly Queue<AssetCommand> s_waitingCommands = new(); // Commands waiting for manual refresh
|
private static readonly Queue<AssetCommand> s_waitingCommands = new(); // Commands waiting for manual refresh
|
||||||
|
|
||||||
|
private static TaskCompletionSource<bool>? s_refreshTcs;
|
||||||
|
|
||||||
// Initialization guard
|
// Initialization guard
|
||||||
private static readonly Lock s_initializationLock = new();
|
private static readonly Lock s_initializationLock = new();
|
||||||
private static bool s_initialized = false;
|
private static bool s_initialized = false;
|
||||||
@@ -77,7 +79,7 @@ public static partial class AssetDatabase
|
|||||||
/// Initialize the asset database.
|
/// Initialize the asset database.
|
||||||
/// Must be called after project is loaded.
|
/// Must be called after project is loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static async void Initialize(CancellationToken token = default)
|
internal static async Task Initialize(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
lock (s_initializationLock)
|
lock (s_initializationLock)
|
||||||
{
|
{
|
||||||
@@ -95,7 +97,6 @@ public static partial class AssetDatabase
|
|||||||
|
|
||||||
AssetsDirectory = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(ProjectService.CurrentProject.Path)!, ProjectService.ASSETS_FOLDER));
|
AssetsDirectory = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(ProjectService.CurrentProject.Path)!, ProjectService.ASSETS_FOLDER));
|
||||||
|
|
||||||
// Initialize command channel (unbounded for simplicity)
|
|
||||||
s_commandChannel = Channel.CreateUnbounded<AssetCommand>(new UnboundedChannelOptions
|
s_commandChannel = Channel.CreateUnbounded<AssetCommand>(new UnboundedChannelOptions
|
||||||
{
|
{
|
||||||
SingleReader = false, // Timer callback reads
|
SingleReader = false, // Timer callback reads
|
||||||
@@ -105,13 +106,9 @@ public static partial class AssetDatabase
|
|||||||
// Initialize command processor timer (starts disabled, triggered by events)
|
// Initialize command processor timer (starts disabled, triggered by events)
|
||||||
s_commandProcessorTimer = new Timer(ProcessPendingCommands, null, Timeout.Infinite, Timeout.Infinite);
|
s_commandProcessorTimer = new Timer(ProcessPendingCommands, null, Timeout.Infinite, Timeout.Infinite);
|
||||||
|
|
||||||
// Initialize database
|
|
||||||
await InitializeDatabaseAsync(token);
|
await InitializeDatabaseAsync(token);
|
||||||
|
|
||||||
// Load asset cache from database
|
|
||||||
await LoadAssetCacheFromDatabaseAsync(token);
|
await LoadAssetCacheFromDatabaseAsync(token);
|
||||||
|
|
||||||
// Initialize file system watcher
|
|
||||||
s_watcher = new FileSystemWatcher
|
s_watcher = new FileSystemWatcher
|
||||||
{
|
{
|
||||||
Path = AssetsDirectory.FullName,
|
Path = AssetsDirectory.FullName,
|
||||||
@@ -123,7 +120,6 @@ public static partial class AssetDatabase
|
|||||||
InitializeAssetHandle();
|
InitializeAssetHandle();
|
||||||
InitializeMetaData();
|
InitializeMetaData();
|
||||||
|
|
||||||
// Validate and fix database on startup
|
|
||||||
await ValidateAndFixDatabaseAsync(token);
|
await ValidateAndFixDatabaseAsync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,16 +189,17 @@ public static partial class AssetDatabase
|
|||||||
{
|
{
|
||||||
s_commandChannel?.Writer.TryWrite(cmd);
|
s_commandChannel?.Writer.TryWrite(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s_refreshTcs = new TaskCompletionSource<bool>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post manual refresh command
|
|
||||||
s_commandChannel?.Writer.TryWrite(new AssetCommand(AssetCommandType.ManualRefresh, string.Empty));
|
s_commandChannel?.Writer.TryWrite(new AssetCommand(AssetCommandType.ManualRefresh, string.Empty));
|
||||||
|
|
||||||
// Trigger timer immediately
|
|
||||||
s_commandProcessorTimer?.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
s_commandProcessorTimer?.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
|
||||||
|
|
||||||
// Wait a bit for processing to complete (this is best-effort)
|
if (!await s_refreshTcs.Task.WaitAsync(token))
|
||||||
await Task.Delay(200, token);
|
{
|
||||||
|
return Result.Failure("Asset database refresh failed");
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
@@ -328,7 +325,7 @@ public static partial class AssetDatabase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timer callback to process pending commands.
|
/// Timer callback to process pending commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ProcessPendingCommands(object? state)
|
private static async void ProcessPendingCommands(object? state)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -347,32 +344,38 @@ public static partial class AssetDatabase
|
|||||||
commandsByPath[cmd.Path] = cmd;
|
commandsByPath[cmd.Path] = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out temp files (files that were created then deleted)
|
// NOTE: We handle the temp file filtering in each command handler now
|
||||||
lock (s_commandLock)
|
// We should able to remove this allocation heavy code
|
||||||
{
|
|
||||||
var pathsToProcess = commandsByPath.Keys.ToList();
|
|
||||||
foreach (var path in pathsToProcess)
|
|
||||||
{
|
|
||||||
// If file was created/modified but doesn't exist anymore, skip
|
|
||||||
if (!File.Exists(path) && commandsByPath[path].Type != AssetCommandType.FileDeleted)
|
|
||||||
{
|
|
||||||
commandsByPath.Remove(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear pending paths
|
// Filter out temp files (files that were created then deleted)
|
||||||
s_pendingCommandPaths.Clear();
|
// lock (s_commandLock)
|
||||||
}
|
// {
|
||||||
|
// var pathsToProcess = commandsByPath.Keys.ToList();
|
||||||
|
// foreach (var path in pathsToProcess)
|
||||||
|
// {
|
||||||
|
// // If file was created/modified but doesn't exist anymore, skip
|
||||||
|
// if (!File.Exists(path) && commandsByPath[path].Type != AssetCommandType.FileDeleted)
|
||||||
|
// {
|
||||||
|
// commandsByPath.Remove(path);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Clear pending paths
|
||||||
|
// s_pendingCommandPaths.Clear();
|
||||||
|
// }
|
||||||
|
|
||||||
// Execute commands
|
// Execute commands
|
||||||
foreach (var cmd in commandsByPath.Values)
|
foreach (var cmd in commandsByPath.Values)
|
||||||
{
|
{
|
||||||
ExecuteCommandAsync(cmd).GetAwaiter().GetResult();
|
await ExecuteCommandAsync(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s_refreshTcs?.SetResult(true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error processing commands: {ex.Message}");
|
Logger.LogError($"Error processing commands: {ex.Message}");
|
||||||
|
s_refreshTcs?.SetResult(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +416,11 @@ public static partial class AssetDatabase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task HandleFileCreatedAsync(string path)
|
private static async Task HandleFileCreatedAsync(string path)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await GenerateMetaFileAsync(path, CancellationToken.None);
|
await GenerateMetaFileAsync(path, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +429,11 @@ public static partial class AssetDatabase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task HandleFileModifiedAsync(string path)
|
private static async Task HandleFileModifiedAsync(string path)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if file hash changed
|
// Check if file hash changed
|
||||||
var metaResult = await ReadMetaFileAsync(path, CancellationToken.None);
|
var metaResult = await ReadMetaFileAsync(path, CancellationToken.None);
|
||||||
if (metaResult.IsFailure)
|
if (metaResult.IsFailure)
|
||||||
@@ -447,6 +460,11 @@ public static partial class AssetDatabase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task HandleFileDeletedAsync(string path)
|
private static async Task HandleFileDeletedAsync(string path)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var metaFileResult = GetMetaFilePath(path);
|
var metaFileResult = GetMetaFilePath(path);
|
||||||
if (metaFileResult.IsSuccess && File.Exists(metaFileResult.Value))
|
if (metaFileResult.IsSuccess && File.Exists(metaFileResult.Value))
|
||||||
{
|
{
|
||||||
@@ -468,7 +486,7 @@ public static partial class AssetDatabase
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error deleting asset metadata: {ex.Message}");
|
Logger.LogError($"Error deleting asset metadata: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -478,6 +496,17 @@ public static partial class AssetDatabase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task HandleFileRenamedAsync(string oldPath, string newPath)
|
private static async Task HandleFileRenamedAsync(string oldPath, string newPath)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(oldPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(newPath))
|
||||||
|
{
|
||||||
|
Logger.LogWarning($"Cannot rename asset from '{oldPath}' to '{newPath}': target file already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var oldMetaPath = oldPath + Utilities.FileExtensions.META_FILE_EXTENSION;
|
var oldMetaPath = oldPath + Utilities.FileExtensions.META_FILE_EXTENSION;
|
||||||
var newMetaPath = newPath + Utilities.FileExtensions.META_FILE_EXTENSION;
|
var newMetaPath = newPath + Utilities.FileExtensions.META_FILE_EXTENSION;
|
||||||
|
|
||||||
|
|||||||
@@ -2,21 +2,16 @@ using Ghost.Core;
|
|||||||
|
|
||||||
namespace Ghost.Editor.Core.AssetHandle;
|
namespace Ghost.Editor.Core.AssetHandle;
|
||||||
|
|
||||||
/// <summary>
|
public abstract class AssetImporter
|
||||||
/// Base class for all asset importers.
|
|
||||||
/// Asset importers process source files and convert them into engine-ready formats.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TSettings">The type of importer settings this importer uses.</typeparam>
|
|
||||||
internal abstract class AssetImporter<TSettings>
|
|
||||||
where TSettings : ImporterSettings, new()
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import the asset at the specified path with the given settings.
|
/// Import the asset at the specified path with the given settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assetPath">Full path to the source asset file.</param>
|
/// <param name="assetPath">Full path to the source asset file.</param>
|
||||||
/// <param name="meta">Metadata for the asset.</param>
|
/// <param name="meta">Metadata for the asset.</param>
|
||||||
|
/// <param name="token">Cancellation token.</param>
|
||||||
/// <returns>Result indicating success or failure.</returns>
|
/// <returns>Result indicating success or failure.</returns>
|
||||||
public abstract Task<Result> ImportAsync(string assetPath, AssetMeta meta);
|
public abstract ValueTask<Result> ImportAsync(string assetPath, AssetMeta meta, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Export in-memory asset data to disk.
|
/// Export in-memory asset data to disk.
|
||||||
@@ -26,31 +21,12 @@ internal abstract class AssetImporter<TSettings>
|
|||||||
/// <param name="assetPath">Full path where the asset should be saved.</param>
|
/// <param name="assetPath">Full path where the asset should be saved.</param>
|
||||||
/// <param name="assetData">In-memory asset data to serialize.</param>
|
/// <param name="assetData">In-memory asset data to serialize.</param>
|
||||||
/// <param name="meta">Metadata for the asset.</param>
|
/// <param name="meta">Metadata for the asset.</param>
|
||||||
|
/// <param name="token">Cancellation token.</param>
|
||||||
/// <returns>Result indicating success or failure.</returns>
|
/// <returns>Result indicating success or failure.</returns>
|
||||||
public virtual Task<Result> ExportAsync<T>(string assetPath, T assetData, AssetMeta meta) where T : class
|
public virtual ValueTask<Result> ExportAsync<T>(string assetPath, T assetData, AssetMeta meta, CancellationToken token = default)
|
||||||
|
where T : class
|
||||||
{
|
{
|
||||||
return Task.FromResult(Result.Failure("This importer does not support exporting assets."));
|
return ValueTask.FromResult(Result.Failure("This importer does not support exporting assets."));
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the settings for this importer from the metadata.
|
|
||||||
/// Creates default settings if none exist.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="meta">Asset metadata.</param>
|
|
||||||
/// <returns>The importer settings.</returns>
|
|
||||||
protected TSettings GetSettings(AssetMeta meta)
|
|
||||||
{
|
|
||||||
var typeName = GetType().Name;
|
|
||||||
var settings = meta.GetImporterSettings<TSettings>(typeName);
|
|
||||||
|
|
||||||
if (settings != null)
|
|
||||||
{
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultSettings = new TSettings();
|
|
||||||
meta.SetImporterSettings(typeName, defaultSettings);
|
|
||||||
return defaultSettings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -78,3 +54,28 @@ internal abstract class AssetImporter<TSettings>
|
|||||||
return ValueTask.FromResult(Result.Success());
|
return ValueTask.FromResult(Result.Success());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class AssetImporter<TSettings> : AssetImporter
|
||||||
|
where TSettings : ImporterSettings, new()
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the settings for this importer from the metadata.
|
||||||
|
/// Creates default settings if none exist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="meta">Asset metadata.</param>
|
||||||
|
/// <returns>The importer settings.</returns>
|
||||||
|
protected TSettings GetSettings(AssetMeta meta)
|
||||||
|
{
|
||||||
|
var typeName = GetType().Name;
|
||||||
|
var settings = meta.GetImporterSettings<TSettings>(typeName);
|
||||||
|
|
||||||
|
if (settings != null)
|
||||||
|
{
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultSettings = new TSettings();
|
||||||
|
meta.SetImporterSettings(typeName, defaultSettings);
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Ghost.Editor.Core.AssetHandle;
|
|||||||
/// Contains GUID, version, tags, and importer settings.
|
/// Contains GUID, version, tags, and importer settings.
|
||||||
/// FileHash and Dependencies are stored in the database only, not in .gmeta files.
|
/// FileHash and Dependencies are stored in the database only, not in .gmeta files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class AssetMeta
|
public class AssetMeta
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique identifier for the asset.
|
/// Unique identifier for the asset.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Ghost.Editor.Core.AssetHandle;
|
namespace Ghost.Editor.Core.AssetHandle;
|
||||||
|
|
||||||
internal abstract class ImporterSettings
|
public abstract class ImporterSettings
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -58,7 +59,7 @@ public readonly struct Scene : IEquatable<Scene>
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Scene {{ ID: {_id} }}";
|
return $"Scene(ID: {_id})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,24 +70,17 @@ public readonly struct Scene : IEquatable<Scene>
|
|||||||
/// This is a minimal runtime representation. All metadata (like scene names)
|
/// This is a minimal runtime representation. All metadata (like scene names)
|
||||||
/// should be stored in editor-only classes (SceneNode).
|
/// should be stored in editor-only classes (SceneNode).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class SceneManager
|
public static class SceneManager
|
||||||
{
|
{
|
||||||
private readonly World _world;
|
private static short s_nextSceneID;
|
||||||
private short _nextSceneID;
|
|
||||||
|
|
||||||
internal SceneManager(World world)
|
|
||||||
{
|
|
||||||
_world = world;
|
|
||||||
_nextSceneID = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new scene in the world.
|
/// Creates a new scene in the world.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The created scene.</returns>
|
/// <returns>The created scene.</returns>
|
||||||
public Scene CreateScene()
|
public static Scene CreateScene()
|
||||||
{
|
{
|
||||||
var scene = new Scene(_nextSceneID++);
|
var scene = new Scene(s_nextSceneID++);
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +88,11 @@ public class SceneManager
|
|||||||
/// Destroys all entities belonging to the specified scene.
|
/// Destroys all entities belonging to the specified scene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scene">The scene to unload.</param>
|
/// <param name="scene">The scene to unload.</param>
|
||||||
public void UnloadScene(Scene scene)
|
/// <param name="world">The world containing the entities.</param>
|
||||||
|
public static void UnloadScene(Scene scene, World world)
|
||||||
{
|
{
|
||||||
// Build query for entities with SceneID
|
var queryID = new QueryBuilder().WithAll<Components.SceneID>().Build(world);
|
||||||
var builder = new QueryBuilder();
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
builder.WithAll([ComponentTypeID<Components.SceneID>.Value]);
|
|
||||||
var queryID = builder.Build(_world);
|
|
||||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var entitiesToDestroy = new UnsafeList<Entity>(128, scope.AllocationHandle);
|
var entitiesToDestroy = new UnsafeList<Entity>(128, scope.AllocationHandle);
|
||||||
@@ -120,22 +112,20 @@ public class SceneManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
world.EntityManager.DestroyEntities(entitiesToDestroy.AsSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all entities belonging to the specified scene.
|
/// Gets all entities belonging to the specified scene.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scene">The scene to query.</param>
|
/// <param name="scene">The scene to query.</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 int GetSceneEntities(Scene scene, Span<Entity> entities)
|
public static int GetSceneEntities(Scene scene, World world, Span<Entity> entities)
|
||||||
{
|
{
|
||||||
// Build query for entities with SceneID
|
var queryID = new QueryBuilder().WithAll<Components.SceneID>().Build(world);
|
||||||
var builder = new QueryBuilder();
|
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
|
||||||
builder.WithAll([ComponentTypeID<Components.SceneID>.Value]);
|
|
||||||
var queryID = builder.Build(_world);
|
|
||||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -164,8 +164,8 @@ public struct Material : IResourceReleasable
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataSpan.CopyTo(cacheSpan);
|
dataSpan.CopyTo(cacheSpan);
|
||||||
|
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
|
|
||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +184,8 @@ public struct Material : IResourceReleasable
|
|||||||
}
|
}
|
||||||
|
|
||||||
data.CopyTo(cacheSpan);
|
data.CopyTo(cacheSpan);
|
||||||
|
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
|
|
||||||
return Error.None;
|
return Error.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class AssetDatabaseIntegrationTest
|
|||||||
ProjectService.CurrentProject = projectMetadataInfo;
|
ProjectService.CurrentProject = projectMetadataInfo;
|
||||||
|
|
||||||
// Initialize AssetDatabase
|
// Initialize AssetDatabase
|
||||||
AssetDatabase.Initialize(TestContext.CancellationToken);
|
await AssetDatabase.Initialize(TestContext.CancellationToken);
|
||||||
|
|
||||||
// Give the file system watcher time to start
|
// Give the file system watcher time to start
|
||||||
await Task.Delay(100, TestContext.CancellationToken);
|
await Task.Delay(100, TestContext.CancellationToken);
|
||||||
|
|||||||
Reference in New Issue
Block a user