diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs
index 1914cf3..b95a65a 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.FileOps.cs
@@ -2,7 +2,7 @@ using Ghost.Core;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
///
/// Create a new asset at the specified path.
@@ -11,7 +11,7 @@ public static partial class AssetService
/// Path to create the asset at.
/// Content to write to the asset file.
/// Result indicating success or failure.
- public static async ValueTask CreateAssetAsync(string assetPath, ReadOnlyMemory content, CancellationToken token = default)
+ public async ValueTask CreateAssetAsync(string assetPath, ReadOnlyMemory content, CancellationToken token = default)
{
if (AssetsDirectory == null)
{
@@ -57,7 +57,7 @@ public static partial class AssetService
///
/// Path to create the asset at.
/// Result indicating success or failure.
- public static ValueTask CreateAssetAsync(string assetPath, CancellationToken token = default)
+ public ValueTask CreateAssetAsync(string assetPath, CancellationToken token = default)
{
return CreateAssetAsync(assetPath, ReadOnlyMemory.Empty, token);
}
@@ -67,7 +67,7 @@ public static partial class AssetService
///
/// GUID of the asset to delete.
/// Result indicating success or failure.
- public static async ValueTask DeleteAssetAsync(Guid guid, CancellationToken token = default)
+ public async ValueTask DeleteAssetAsync(Guid guid, CancellationToken token = default)
{
var pathResult = GuidToPath(guid);
if (pathResult.IsFailure)
@@ -114,7 +114,7 @@ public static partial class AssetService
///
/// Path to the asset to delete.
/// Result indicating success or failure.
- public static ValueTask DeleteAssetAsync(string assetPath, CancellationToken token = default)
+ public ValueTask DeleteAssetAsync(string assetPath, CancellationToken token = default)
{
var guidResult = PathToGuid(assetPath);
if (guidResult.IsFailure)
@@ -131,7 +131,7 @@ public static partial class AssetService
/// GUID of the asset to move.
/// New path for the asset (relative or absolute).
/// Result indicating success or failure.
- public static async ValueTask MoveAssetAsync(Guid guid, string newPath, CancellationToken token = default)
+ public async ValueTask MoveAssetAsync(Guid guid, string newPath, CancellationToken token = default)
{
var oldPathResult = GuidToPath(guid);
if (oldPathResult.IsFailure)
@@ -211,7 +211,7 @@ public static partial class AssetService
/// CurrentApplication path of the asset.
/// New path for the asset (relative or absolute).
/// Result indicating success or failure.
- public static ValueTask MoveAssetAsync(string oldPath, string newPath, CancellationToken token = default)
+ public ValueTask MoveAssetAsync(string oldPath, string newPath, CancellationToken token = default)
{
var guidResult = PathToGuid(oldPath);
if (guidResult.IsFailure)
@@ -228,7 +228,7 @@ public static partial class AssetService
/// GUID of the asset to copy.
/// New path for the copied asset (relative or absolute).
/// Result containing the new asset's GUID.
- public static async ValueTask> CopyAssetAsync(Guid guid, string newPath, CancellationToken token = default)
+ public async ValueTask> CopyAssetAsync(Guid guid, string newPath, CancellationToken token = default)
{
var oldPathResult = GuidToPath(guid);
if (oldPathResult.IsFailure)
@@ -299,7 +299,7 @@ public static partial class AssetService
/// Path of the asset to copy.
/// New path for the copied asset (relative or absolute).
/// Result containing the new asset's GUID.
- public static ValueTask> CopyAssetAsync(string sourcePath, string destPath, CancellationToken token = default)
+ public ValueTask> CopyAssetAsync(string sourcePath, string destPath, CancellationToken token = default)
{
var guidResult = PathToGuid(sourcePath);
if (guidResult.IsFailure)
@@ -315,7 +315,7 @@ public static partial class AssetService
///
/// GUID of the asset to mark dirty.
/// Result indicating success or failure.
- public static Result MarkDirtyAsync(Guid guid, CancellationToken token = default)
+ public Result MarkDirtyAsync(Guid guid, CancellationToken token = default)
{
MarkDirty(guid);
return Result.Success();
@@ -325,7 +325,7 @@ public static partial class AssetService
/// Import all dirty assets.
///
/// Result indicating success or failure.
- public static async Task ImportDirtyAssetsAsync(CancellationToken token = default)
+ public async Task ImportDirtyAssetsAsync(CancellationToken token = default)
{
var dirtyGuids = GetDirtyAssets();
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Importer.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Importer.cs
index cafe15d..4af1208 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Importer.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Importer.cs
@@ -3,27 +3,27 @@ using System.Reflection;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
- private static readonly Dictionary s_importerInstances = new();
+ private readonly Dictionary _importerInstances = new();
///
/// Import an asset at the specified path.
///
/// Full path to the asset file.
/// Result indicating success or failure.
- private static async ValueTask ImportAssetAsync(string assetPath, CancellationToken token = default)
+ private async ValueTask ImportAssetAsync(string assetPath, CancellationToken token = default)
{
var extension = Path.GetExtension(assetPath);
- if (!s_importerTypeLookup.TryGetValue(extension, out var importerType))
+ if (!_importerTypeLookup.TryGetValue(extension, out var importerType))
{
// No importer registered for this file type
return Result.Success();
}
// Get or create importer instance
- if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
+ if (!_importerInstances.TryGetValue(importerType, out var importerInstance))
{
importerInstance = Activator.CreateInstance(importerType) as AssetImporter;
if (importerInstance is null)
@@ -31,7 +31,7 @@ public static partial class AssetService
return Result.Failure($"Failed to create importer instance for type {importerType.Name}");
}
- s_importerInstances[importerType] = importerInstance;
+ _importerInstances[importerType] = importerInstance;
}
// Read metadata
@@ -41,7 +41,7 @@ public static partial class AssetService
return Result.Failure($"Failed to read asset metadata: {metaResult.Message}");
}
- return await importerInstance.ImportAsync(assetPath, metaResult.Value, token);
+ return await importerInstance.ImportAsync(assetPath, metaResult.Value, this, token);
}
///
@@ -49,9 +49,9 @@ public static partial class AssetService
///
/// File extension (e.g., ".png").
/// The importer type if found, otherwise null.
- public static Type? GetImporterType(string extension)
+ public Type? GetImporterType(string extension)
{
- s_importerTypeLookup.TryGetValue(extension, out var importerType);
+ _importerTypeLookup.TryGetValue(extension, out var importerType);
return importerType;
}
@@ -59,9 +59,9 @@ public static partial class AssetService
/// Get all registered importer types and their supported extensions.
///
/// Dictionary mapping extensions to importer types.
- public static Dictionary GetAllImporters()
+ public Dictionary GetAllImporters()
{
- return new Dictionary(s_importerTypeLookup);
+ return new Dictionary(_importerTypeLookup);
}
///
@@ -72,17 +72,18 @@ public static partial class AssetService
/// Full path where the asset should be saved.
/// In-memory asset data to export.
/// Result with the GUID of the exported asset.
- public static async ValueTask> ExportAssetAsync(string assetPath, T assetData, CancellationToken token = default) where T : class
+ public async ValueTask> ExportAssetAsync(string assetPath, T assetData, CancellationToken token = default)
+ where T : class
{
var extension = Path.GetExtension(assetPath);
- if (!s_importerTypeLookup.TryGetValue(extension, out var importerType))
+ if (!_importerTypeLookup.TryGetValue(extension, out var importerType))
{
return Result.Failure($"No importer registered for extension {extension}");
}
// Get or create importer instance
- if (!s_importerInstances.TryGetValue(importerType, out var importerInstance))
+ if (!_importerInstances.TryGetValue(importerType, out var importerInstance))
{
importerInstance = Activator.CreateInstance(importerType) as AssetImporter;
if (importerInstance is null)
@@ -90,14 +91,7 @@ public static partial class AssetService
return Result.Failure($"Failed to create importer instance for type {importerType.Name}");
}
- s_importerInstances[importerType] = importerInstance;
- }
-
- // Find and invoke the ExportAsync method
- var exportMethod = importerType.GetMethod("ExportAsync", BindingFlags.Public | BindingFlags.Instance);
- if (exportMethod == null)
- {
- return Result.Failure($"ExportAsync method not found on importer {importerType.Name}. This importer does not support exporting.");
+ _importerInstances[importerType] = importerInstance;
}
// Generate metadata for the new asset
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs
index 404c118..30f9280 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Loader.cs
@@ -4,21 +4,21 @@ using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
// Asset cache - stores loaded assets by GUID
- private static readonly ConcurrentDictionary s_assetCache = new();
+ private readonly ConcurrentDictionary _assetCache = new();
// LRU tracking - stores access time for each cached asset
- private static readonly ConcurrentDictionary s_assetAccessTime = new();
+ private readonly ConcurrentDictionary _assetAccessTime = new();
// Maximum number of cached assets before eviction starts
- private const int MAX_CACHED_ASSETS = 1000;
+ private const int _MAX_CACHED_ASSETS = 1000;
// Percentage of cache to evict when limit is reached (evict oldest 20%)
private const float _CACHE_EVICTION_PERCENTAGE = 0.2f;
- private static Result GetImportedAssetsDirectory()
+ private Result GetImportedAssetsDirectory()
{
if (AssetsDirectory == null)
{
@@ -34,7 +34,7 @@ public static partial class AssetService
return cacheDir;
}
- private static Result GetImportedAssetPath(Guid guid)
+ private Result GetImportedAssetPath(Guid guid)
{
var importedDirResult = GetImportedAssetsDirectory();
if (importedDirResult.IsFailure)
@@ -47,13 +47,13 @@ public static partial class AssetService
return assetDataPath;
}
- private static Result LoadAssetInternal(Guid guid) where T : Asset
+ private Result LoadAssetInternal(Guid guid) where T : Asset
{
// Check cache first
- if (s_assetCache.TryGetValue(guid, out var cachedAsset))
+ if (_assetCache.TryGetValue(guid, out var cachedAsset))
{
// Update access time for LRU
- s_assetAccessTime[guid] = DateTime.UtcNow;
+ _assetAccessTime[guid] = DateTime.UtcNow;
if (cachedAsset is T typedAsset)
{
@@ -98,7 +98,7 @@ public static partial class AssetService
}
}
- public static Result LoadAssetAtPath(string assetPath) where T : Asset
+ public Result LoadAssetAtPath(string assetPath) where T : Asset
{
var guidResult = PathToGuid(assetPath);
if (guidResult.IsFailure)
@@ -109,24 +109,24 @@ public static partial class AssetService
return LoadAsset(guidResult.Value);
}
- private static void CacheAsset(Guid guid, Asset asset)
+ private void CacheAsset(Guid guid, Asset asset)
{
// Check if we need to evict old assets
- if (s_assetCache.Count >= MAX_CACHED_ASSETS)
+ if (_assetCache.Count >= _MAX_CACHED_ASSETS)
{
EvictOldestAssets();
}
- s_assetCache[guid] = asset;
- s_assetAccessTime[guid] = DateTime.UtcNow;
+ _assetCache[guid] = asset;
+ _assetAccessTime[guid] = DateTime.UtcNow;
}
- private static void EvictOldestAssets()
+ private void EvictOldestAssets()
{
- var evictionCount = (int)(MAX_CACHED_ASSETS * _CACHE_EVICTION_PERCENTAGE);
+ var evictionCount = (int)(_MAX_CACHED_ASSETS * _CACHE_EVICTION_PERCENTAGE);
// Sort by access time and remove oldest entries
- var oldestAssets = s_assetAccessTime
+ var oldestAssets = _assetAccessTime
.OrderBy(kvp => kvp.Value)
.Take(evictionCount)
.Select(kvp => kvp.Key)
@@ -134,8 +134,8 @@ public static partial class AssetService
foreach (var guid in oldestAssets)
{
- s_assetCache.TryRemove(guid, out _);
- s_assetAccessTime.TryRemove(guid, out _);
+ _assetCache.TryRemove(guid, out _);
+ _assetAccessTime.TryRemove(guid, out _);
}
}
@@ -143,19 +143,19 @@ public static partial class AssetService
/// Unload a specific asset from cache.
///
/// GUID of the asset to unload.
- public static void UnloadAsset(Guid guid)
+ public void UnloadAsset(Guid guid)
{
- s_assetCache.TryRemove(guid, out _);
- s_assetAccessTime.TryRemove(guid, out _);
+ _assetCache.TryRemove(guid, out _);
+ _assetAccessTime.TryRemove(guid, out _);
}
///
/// Unload all assets from cache.
///
- public static void UnloadAllAssets()
+ public void UnloadAllAssets()
{
- s_assetCache.Clear();
- s_assetAccessTime.Clear();
+ _assetCache.Clear();
+ _assetAccessTime.Clear();
}
///
@@ -163,18 +163,18 @@ public static partial class AssetService
///
/// GUID of the asset.
/// True if the asset is in cache.
- public static bool IsAssetLoaded(Guid guid)
+ public bool IsAssetLoaded(Guid guid)
{
- return s_assetCache.ContainsKey(guid);
+ return _assetCache.ContainsKey(guid);
}
///
/// Get cache statistics.
///
/// Tuple of (current cache size, max cache size).
- public static (int currentSize, int maxSize) GetCacheStats()
+ public (int currentSize, int maxSize) GetCacheStats()
{
- return (s_assetCache.Count, MAX_CACHED_ASSETS);
+ return (_assetCache.Count, _MAX_CACHED_ASSETS);
}
///
@@ -185,7 +185,7 @@ public static partial class AssetService
/// GUID of the asset.
/// Processed asset data to save.
/// Result indicating success or failure.
- public static Result SaveImportedAsset(Guid guid, T assetData)
+ public Result SaveImportedAsset(Guid guid, T assetData)
where T : Asset
{
var assetPathResult = GetImportedAssetPath(guid);
@@ -196,7 +196,7 @@ public static partial class AssetService
try
{
- var json = JsonSerializer.Serialize(assetData, s_defaultJsonOptions);
+ var json = JsonSerializer.Serialize(assetData, _defaultJsonOptions);
File.WriteAllText(assetPathResult.Value, json);
// Invalidate cache for this asset so it gets reloaded next time
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Lookup.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Lookup.cs
index 004d2de..1084086 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Lookup.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Lookup.cs
@@ -3,12 +3,12 @@ using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
///
/// Get the relative path from the assets directory.
///
- private static Result GetRelativePath(string fullPath)
+ private Result GetRelativePath(string fullPath)
{
if (AssetsDirectory == null)
{
@@ -26,7 +26,7 @@ public static partial class AssetService
///
/// Get the full path from a relative path.
///
- private static Result GetFullPath(string relativePath)
+ private Result GetFullPath(string relativePath)
{
if (AssetsDirectory == null)
{
@@ -41,7 +41,7 @@ public static partial class AssetService
///
/// Full or relative path to the asset.
/// The GUID of the asset if found.
- public static Result PathToGuid(string assetPath)
+ public Result PathToGuid(string assetPath)
{
var relativePath = assetPath;
@@ -59,9 +59,9 @@ public static partial class AssetService
// Normalize path separators
relativePath = relativePath.Replace('\\', '/');
- lock (s_dbLock)
+ lock (_dbLock)
{
- if (s_pathAssetLookup.TryGetValue(relativePath, out var guid))
+ if (_pathAssetLookup.TryGetValue(relativePath, out var guid))
{
return guid;
}
@@ -75,11 +75,11 @@ public static partial class AssetService
///
/// GUID of the asset.
/// The relative path to the asset if found.
- public static Result GuidToPath(Guid guid)
+ public Result GuidToPath(Guid guid)
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- if (s_assetPathLookup.TryGetValue(guid, out var path))
+ if (_assetPathLookup.TryGetValue(guid, out var path))
{
return path;
}
@@ -94,7 +94,7 @@ public static partial class AssetService
/// Type of asset to load.
/// GUID of the asset.
/// The loaded asset.
- public static Result LoadAsset(Guid guid) where T : Asset
+ public Result LoadAsset(Guid guid) where T : Asset
{
// Implemented in AssetService.Loader.cs
return LoadAssetInternal(guid);
@@ -105,7 +105,7 @@ public static partial class AssetService
///
/// GUID of the asset.
/// List of tags associated with the asset.
- public static async ValueTask>> GetAssetTagsAsync(Guid guid, CancellationToken token = default)
+ public async ValueTask>> GetAssetTagsAsync(Guid guid, CancellationToken token = default)
{
var pathResult = GuidToPath(guid);
if (pathResult.IsFailure)
@@ -134,7 +134,7 @@ public static partial class AssetService
/// GUID of the asset.
/// New tags for the asset.
/// Result indicating success or failure.
- public static async ValueTask SetAssetTagsAsync(Guid guid, List tags, CancellationToken token = default)
+ public async ValueTask SetAssetTagsAsync(Guid guid, List tags, CancellationToken token = default)
{
var pathResult = GuidToPath(guid);
if (pathResult.IsFailure)
@@ -174,7 +174,7 @@ public static partial class AssetService
///
/// Search pattern (e.g., "*.txt", "player?", "test*").
/// List of matching asset GUIDs.
- public static async Task> FindAssetsByNameAsync(string namePattern, CancellationToken token = default)
+ public async Task> FindAssetsByNameAsync(string namePattern, CancellationToken token = default)
{
return await GetAssetsByNameAsync(namePattern, token);
}
@@ -184,7 +184,7 @@ public static partial class AssetService
///
/// Tag to search for.
/// List of asset GUIDs with the specified tag.
- public static async Task> FindAssetsByTagAsync(string tag, CancellationToken token = default)
+ public async Task> FindAssetsByTagAsync(string tag, CancellationToken token = default)
{
return await GetAssetsByTagAsync(tag, token);
}
@@ -193,11 +193,11 @@ public static partial class AssetService
/// Get all assets in the database.
///
/// Dictionary mapping GUIDs to relative paths.
- public static IReadOnlyDictionary GetAllAssets()
+ public IReadOnlyDictionary GetAllAssets()
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- return s_assetPathLookup.AsReadOnly();
+ return _assetPathLookup.AsReadOnly();
}
}
}
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Meta.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Meta.cs
index c5a4679..14ad358 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Meta.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Meta.cs
@@ -7,13 +7,13 @@ using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
- private static readonly Dictionary s_importerTypeLookup = new();
+ private readonly Dictionary _importerTypeLookup = new();
- private static void InitializeMetaData()
+ private void InitializeMetaData()
{
- if (s_watcher == null)
+ if (_watcher == null)
{
throw new InvalidOperationException("AssetDatabase is not initialized. Ensure that Initialize() is called before registering asset importers.");
}
@@ -24,17 +24,17 @@ public static partial class AssetService
var attribute = type.GetCustomAttribute()!;
foreach (var extension in attribute.SupportedExtensions)
{
- s_importerTypeLookup[extension] = type;
+ _importerTypeLookup[extension] = type;
}
}
- s_watcher.Created += OnFSEvent;
- s_watcher.Deleted += OnFSEvent;
- s_watcher.Changed += OnFSEvent;
- s_watcher.Renamed += OnAssetRenamed;
+ _watcher.Created += OnFSEvent;
+ _watcher.Deleted += OnFSEvent;
+ _watcher.Changed += OnFSEvent;
+ _watcher.Renamed += OnAssetRenamed;
}
- private static Result GetMetaFilePath(string assetPath)
+ private Result GetMetaFilePath(string assetPath)
{
if (Directory.Exists(assetPath))
{
@@ -49,11 +49,11 @@ public static partial class AssetService
return assetPath + FileExtensions.META_FILE_EXTENSION;
}
- private static ImporterSettings? GetDefaultSettingsForAsset(string assetPath)
+ private ImporterSettings? GetDefaultSettingsForAsset(string assetPath)
{
var extension = Path.GetExtension(assetPath);
- if (s_importerTypeLookup.TryGetValue(extension, out var importerType))
+ if (_importerTypeLookup.TryGetValue(extension, out var importerType))
{
var settingsType = importerType.BaseType?.GetGenericArguments()[0];
if (settingsType == null || !typeof(ImporterSettings).IsAssignableFrom(settingsType))
@@ -70,7 +70,7 @@ public static partial class AssetService
///
/// Calculate SHA256 hash of a file for change detection.
///
- private static async Task CalculateFileHashAsync(string filePath, CancellationToken token = default)
+ private async Task CalculateFileHashAsync(string filePath, CancellationToken token = default)
{
try
{
@@ -84,12 +84,12 @@ public static partial class AssetService
}
}
- private static async Task WriteMetaFileAsync(string metaFilePath, AssetMeta metaData, CancellationToken token = default)
+ private async Task WriteMetaFileAsync(string metaFilePath, AssetMeta metaData, CancellationToken token = default)
{
try
{
await using var fileStream = File.Create(metaFilePath);
- await JsonSerializer.SerializeAsync(fileStream, metaData, s_defaultJsonOptions, token);
+ await JsonSerializer.SerializeAsync(fileStream, metaData, _defaultJsonOptions, token);
return Result.Success();
}
catch (Exception ex)
@@ -101,7 +101,7 @@ public static partial class AssetService
///
/// Read metadata from a .gmeta file.
///
- private static async ValueTask> ReadMetaFileAsync(string assetPath, CancellationToken token = default)
+ private async ValueTask> ReadMetaFileAsync(string assetPath, CancellationToken token = default)
{
var metaFileResult = GetMetaFilePath(assetPath);
if (metaFileResult.IsFailure)
@@ -117,7 +117,7 @@ public static partial class AssetService
try
{
await using var fileStream = File.OpenRead(metaFileResult.Value);
- var meta = await JsonSerializer.DeserializeAsync(fileStream, s_defaultJsonOptions, token);
+ var meta = await JsonSerializer.DeserializeAsync(fileStream, _defaultJsonOptions, token);
if (meta == null)
{
return Result.Failure("Failed to deserialize metadata");
@@ -131,7 +131,7 @@ public static partial class AssetService
}
}
- internal static async ValueTask GenerateMetaFileAsync(string assetPath, CancellationToken token = default)
+ internal async ValueTask GenerateMetaFileAsync(string assetPath, CancellationToken token = default)
{
Result r;
@@ -147,7 +147,7 @@ public static partial class AssetService
if (existingMetaResult.IsSuccess)
{
var existingMeta = existingMetaResult.Value;
- if (s_assetPathLookup.TryGetValue(existingMeta.Guid, out var path))
+ if (_assetPathLookup.TryGetValue(existingMeta.Guid, out var path))
{
var relResult = GetRelativePath(assetPath);
if (relResult.IsSuccess && assetPath != path)
@@ -196,12 +196,12 @@ public static partial class AssetService
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsMetaFile(string path)
+ private bool IsMetaFile(string path)
{
return Path.GetExtension(path).Equals(FileExtensions.META_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase);
}
- private static async void OnFSEvent(object sender, FileSystemEventArgs e)
+ private async void OnFSEvent(object sender, FileSystemEventArgs e)
{
if (IsMetaFile(e.FullPath))
{
@@ -219,7 +219,7 @@ public static partial class AssetService
await PostCommandAsync(new AssetCommand(type, e.FullPath, Timestamp: DateTime.UtcNow));
}
- private static async void OnAssetRenamed(object sender, RenamedEventArgs e)
+ private async void OnAssetRenamed(object sender, RenamedEventArgs e)
{
if (IsMetaFile(e.FullPath))
{
@@ -232,7 +232,7 @@ public static partial class AssetService
///
/// Mark all assets that depend on the specified asset as dirty.
///
- private static async Task MarkDependentAssetsDirtyAsync(Guid assetGuid)
+ private async Task MarkDependentAssetsDirtyAsync(Guid assetGuid)
{
// TODO: We should have a reverse dependency lookup in the database to avoid scanning all assets.
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Open.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Open.cs
index bc05396..b106251 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.Open.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.Open.cs
@@ -5,11 +5,11 @@ using System.Reflection;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
- private static readonly Dictionary> s_assetOpenHandlers = new(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary> _assetOpenHandlers = new(StringComparer.OrdinalIgnoreCase);
- private static void InitializeAssetHandle()
+ private void InitializeAssetHandle()
{
var methods = TypeCache.GetTypes()
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
@@ -23,20 +23,20 @@ public static partial class AssetService
var del = (Action)Delegate.CreateDelegate(typeof(Action), method);
foreach (var ext in attr.Extensions)
{
- if (s_assetOpenHandlers.ContainsKey(ext))
+ if (_assetOpenHandlers.ContainsKey(ext))
{
Logger.LogError($"Duplicate asset open handler for extension '{ext}' found in method '{method.Name}'. Existing handler will be overwritten.");
}
- s_assetOpenHandlers[ext] = del;
+ _assetOpenHandlers[ext] = del;
}
}
}
- public static void OpenAsset(string path)
+ public void OpenAsset(string path)
{
var extension = Path.GetExtension(path);
- if (s_assetOpenHandlers.TryGetValue(extension, out var handler))
+ if (_assetOpenHandlers.TryGetValue(extension, out var handler))
{
handler(path);
}
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs
index 358c80d..19d57ca 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.SQLite.cs
@@ -4,14 +4,14 @@ using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle;
-public static partial class AssetService
+public partial class AssetService
{
- private static SqliteConnection? s_dbConnection;
+ private SqliteConnection? _dbConnection;
///
- /// Initialize the SQLite database for asset caching.
+ /// Init the SQLite database for asset caching.
///
- private static async Task InitializeDatabaseAsync(CancellationToken token = default)
+ private async Task InitializeDatabaseAsync(CancellationToken token = default)
{
if (AssetsDirectory == null)
{
@@ -32,11 +32,11 @@ public static partial class AssetService
Cache = SqliteCacheMode.Shared
}.ToString();
- s_dbConnection = new SqliteConnection(connectionString);
- await s_dbConnection.OpenAsync(token);
+ _dbConnection = new SqliteConnection(connectionString);
+ await _dbConnection.OpenAsync(token);
// Create tables
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = @"
CREATE TABLE IF NOT EXISTS Assets (
Guid TEXT PRIMARY KEY,
@@ -60,9 +60,9 @@ public static partial class AssetService
/// Asset metadata from .gmeta file.
/// SHA256 hash of the asset file content.
/// List of GUIDs this asset depends on (extracted during import).
- private static async ValueTask UpsertAssetAsync(string assetPath, AssetMeta meta, string fileHash, List? dependencies = null, CancellationToken token = default)
+ private async ValueTask UpsertAssetAsync(string assetPath, AssetMeta meta, string fileHash, List? dependencies = null, CancellationToken token = default)
{
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return Result.Failure("Database not initialized");
}
@@ -75,21 +75,21 @@ public static partial class AssetService
try
{
- lock (s_dbLock)
+ lock (_dbLock)
{
// If this GUID already exists with a different path, remove the old path mapping
- if (s_assetPathLookup.TryGetValue(meta.Guid, out var oldPath) && oldPath != relativePath.Value)
+ if (_assetPathLookup.TryGetValue(meta.Guid, out var oldPath) && oldPath != relativePath.Value)
{
- s_pathAssetLookup.Remove(oldPath);
+ _pathAssetLookup.Remove(oldPath);
}
// Update lookups with new path (normalize path separators for consistency)
var normalizedPath = relativePath.Value.Replace('\\', '/');
- s_assetPathLookup[meta.Guid] = normalizedPath;
- s_pathAssetLookup[normalizedPath] = meta.Guid;
+ _assetPathLookup[meta.Guid] = normalizedPath;
+ _pathAssetLookup[normalizedPath] = meta.Guid;
}
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = @"
INSERT OR REPLACE INTO Assets (Guid, Path, Version, Tags, FileHash, DependencyGuids, LastModified)
VALUES (@guid, @path, @version, @tags, @fileHash, @deps, @modified)
@@ -114,25 +114,25 @@ public static partial class AssetService
///
/// Remove an asset from the database.
///
- private static async Task RemoveAssetFromDatabaseAsync(Guid guid, CancellationToken token = default)
+ private async Task RemoveAssetFromDatabaseAsync(Guid guid, CancellationToken token = default)
{
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return Result.Failure("Database not initialized");
}
try
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- if (s_assetPathLookup.TryGetValue(guid, out var path))
+ if (_assetPathLookup.TryGetValue(guid, out var path))
{
- s_assetPathLookup.Remove(guid);
- s_pathAssetLookup.Remove(path);
+ _assetPathLookup.Remove(guid);
+ _pathAssetLookup.Remove(path);
}
}
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "DELETE FROM Assets WHERE Guid = @guid";
cmd.Parameters.AddWithValue("@guid", guid.ToString());
@@ -150,16 +150,16 @@ public static partial class AssetService
///
/// Load all assets from the database into memory cache.
///
- private static async Task LoadAssetCacheFromDatabaseAsync(CancellationToken token = default)
+ private async Task LoadAssetCacheFromDatabaseAsync(CancellationToken token = default)
{
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return;
}
try
{
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "SELECT Guid, Path FROM Assets";
await using var reader = await cmd.ExecuteReaderAsync(token);
@@ -170,10 +170,10 @@ public static partial class AssetService
if (Guid.TryParse(guidStr, out var guid))
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- s_assetPathLookup[guid] = path;
- s_pathAssetLookup[path] = guid;
+ _assetPathLookup[guid] = path;
+ _pathAssetLookup[path] = guid;
}
}
}
@@ -187,18 +187,18 @@ public static partial class AssetService
///
/// Get assets by tag.
///
- private static async Task> GetAssetsByTagAsync(string tag, CancellationToken token = default)
+ private async Task> GetAssetsByTagAsync(string tag, CancellationToken token = default)
{
var result = new List();
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return result;
}
try
{
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "SELECT Guid, Tags FROM Assets";
await using var reader = await cmd.ExecuteReaderAsync(token);
@@ -228,16 +228,16 @@ public static partial class AssetService
///
/// Get the file hash for an asset from the database.
///
- private static async Task GetFileHashAsync(Guid guid, CancellationToken token = default)
+ private async Task GetFileHashAsync(Guid guid, CancellationToken token = default)
{
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return null;
}
try
{
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "SELECT FileHash FROM Assets WHERE Guid = @guid";
cmd.Parameters.AddWithValue("@guid", guid.ToString());
@@ -253,16 +253,16 @@ public static partial class AssetService
///
/// Get the dependencies for an asset from the database.
///
- private static async Task> GetDependenciesAsync(Guid guid, CancellationToken token = default)
+ private async Task> GetDependenciesAsync(Guid guid, CancellationToken token = default)
{
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return new List();
}
try
{
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "SELECT DependencyGuids FROM Assets WHERE Guid = @guid";
cmd.Parameters.AddWithValue("@guid", guid.ToString());
@@ -285,11 +285,11 @@ public static partial class AssetService
/// Find assets by name pattern using database query with wildcards.
///
/// Pattern supporting * (any chars) and ? (single char).
- private static async Task> GetAssetsByNameAsync(string namePattern, CancellationToken token = default)
+ private async Task> GetAssetsByNameAsync(string namePattern, CancellationToken token = default)
{
var results = new List();
- if (s_dbConnection == null)
+ if (_dbConnection == null)
{
return results;
}
@@ -299,7 +299,7 @@ public static partial class AssetService
// Convert wildcard pattern to SQL LIKE pattern
var sqlPattern = namePattern.Replace('*', '%').Replace('?', '_');
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
// Extract just the filename from the path for matching
// SQLite doesn't have a built-in path manipulation, so we search in the full path
@@ -344,9 +344,9 @@ public static partial class AssetService
///
/// Remove orphaned entries from database (assets that no longer exist on disk).
///
- private static async Task RemoveOrphanedEntriesAsync(CancellationToken token = default)
+ private async Task RemoveOrphanedEntriesAsync(CancellationToken token = default)
{
- if (s_dbConnection == null || AssetsDirectory == null)
+ if (_dbConnection == null || AssetsDirectory == null)
{
return;
}
@@ -355,7 +355,7 @@ public static partial class AssetService
{
var orphanedGuids = new List();
- await using var cmd = s_dbConnection.CreateCommand();
+ await using var cmd = _dbConnection.CreateCommand();
cmd.CommandText = "SELECT Guid, Path FROM Assets";
await using var reader = await cmd.ExecuteReaderAsync(token);
diff --git a/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs b/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs
index 67f496e..08e2aaf 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetDatabase.cs
@@ -1,4 +1,5 @@
using Ghost.Core;
+using Ghost.Editor.Core.Contracts;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -33,31 +34,31 @@ internal readonly record struct AssetCommand(
/// Handles asset registration, lookup, importing, and dependency management.
/// Uses SQLite for persistent storage and efficient querying.
///
-public static partial class AssetService
+public partial class AssetService : IAssetService
{
- private static FileSystemWatcher? s_watcher;
- private static readonly Lock s_dbLock = new();
- private static readonly Dictionary s_assetPathLookup = new();
- private static readonly Dictionary s_pathAssetLookup = new();
+ private FileSystemWatcher? _watcher;
+ private readonly Lock _dbLock = new();
+ private readonly Dictionary _assetPathLookup = new();
+ private readonly Dictionary _pathAssetLookup = new();
// In-memory dirty asset tracking (for runtime modifications only)
// TODO: We do not handle the reimporting of dirty assets yet
- private static readonly HashSet s_dirtyAssets = new();
+ private readonly HashSet _dirtyAssets = new();
// Command buffer pattern - Channel for file system event commands
- private static Channel? s_commandChannel;
- private static Timer? s_commandProcessorTimer;
- private static readonly ConcurrentQueue s_waitingCommands = new(); // Commands waiting for manual refresh
- private static bool s_autoRefreshEnabled = true;
+ private Channel? _commandChannel;
+ private Timer? _commandProcessorTimer;
+ private readonly ConcurrentQueue _waitingCommands = new(); // Commands waiting for manual refresh
+ private bool _autoRefreshEnabled = true;
// Initialization guard
- private static readonly Lock s_initializationLock = new();
- private static bool s_initialized = false;
+ private readonly Lock _initializationLock = new();
+ private bool _initialized = false;
- private static readonly TimeSpan s_debounceDelay = TimeSpan.FromMilliseconds(100);
- private static readonly ManualResetEventSlim s_resetEventSlim = new(false);
+ private readonly TimeSpan _debounceDelay = TimeSpan.FromMilliseconds(100);
+ private readonly ManualResetEventSlim _resetEventSlim = new(false);
- private static readonly JsonSerializerOptions s_defaultJsonOptions = new()
+ private readonly JsonSerializerOptions _defaultJsonOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@@ -67,43 +68,43 @@ public static partial class AssetService
}
};
- public static DirectoryInfo? AssetsDirectory
+ public DirectoryInfo? AssetsDirectory
{
get;
private set;
}
///
- /// Initialize the asset database.
+ /// Init the asset database.
/// Must be called after project is loaded.
///
- internal static async Task Initialize(CancellationToken token = default)
+ internal async Task Init(CancellationToken token = default)
{
- lock (s_initializationLock)
+ lock (_initializationLock)
{
- if (s_initialized)
+ if (_initialized)
{
return;
}
- s_initialized = true;
+ _initialized = true;
}
AssetsDirectory = new DirectoryInfo(Path.Combine(EditorApplication.CurrentProjectPath, EditorApplication.ASSETS_FOLDER_NAME));
- s_commandChannel = Channel.CreateUnbounded(new UnboundedChannelOptions
+ _commandChannel = Channel.CreateUnbounded(new UnboundedChannelOptions
{
SingleReader = false,
SingleWriter = false
});
- // Initialize command processor timer (starts disabled, triggered by events)
- s_commandProcessorTimer = new Timer(ProcessPendingCommands, null, Timeout.Infinite, Timeout.Infinite);
+ // Init command processor timer (starts disabled, triggered by events)
+ _commandProcessorTimer = new Timer(ProcessPendingCommands, null, Timeout.Infinite, Timeout.Infinite);
await InitializeDatabaseAsync(token);
await LoadAssetCacheFromDatabaseAsync(token);
- s_watcher = new FileSystemWatcher
+ _watcher = new FileSystemWatcher
{
Path = AssetsDirectory.FullName,
IncludeSubdirectories = true,
@@ -122,7 +123,7 @@ public static partial class AssetService
/// Validate the asset database and fix any inconsistencies.
/// Checks for missing/corrupted assets and regenerates metadata as needed.
///
- private static async Task ValidateAndFixDatabaseAsync(CancellationToken token = default)
+ private async Task ValidateAndFixDatabaseAsync(CancellationToken token = default)
{
if (AssetsDirectory == null)
{
@@ -175,19 +176,19 @@ public static partial class AssetService
/// Refresh the asset database manually.
/// Scans the project directory for changes and processes any queued file system events.
///
- public static async Task RefreshAsync(CancellationToken token = default)
+ public async Task RefreshAsync(CancellationToken token = default)
{
// Flush waiting commands to channel
- while (s_waitingCommands.TryDequeue(out var cmd))
+ while (_waitingCommands.TryDequeue(out var cmd))
{
- s_commandChannel?.Writer.TryWrite(cmd);
+ _commandChannel?.Writer.TryWrite(cmd);
}
- s_resetEventSlim.Reset();
- s_commandChannel?.Writer.TryWrite(new AssetCommand(AssetCommandType.ManualRefresh, string.Empty));
- s_commandProcessorTimer?.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
+ _resetEventSlim.Reset();
+ _commandChannel?.Writer.TryWrite(new AssetCommand(AssetCommandType.ManualRefresh, string.Empty));
+ _commandProcessorTimer?.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
- await Task.Run(s_resetEventSlim.Wait, token);
+ await Task.Run(_resetEventSlim.Wait, token);
return Result.Success();
}
@@ -195,55 +196,55 @@ public static partial class AssetService
/// Mark an asset as dirty (modified in memory but not yet saved).
/// This state is NOT persisted and will be lost on application restart.
///
- public static void MarkDirty(Guid assetGuid)
+ public void MarkDirty(Guid assetGuid)
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- s_dirtyAssets.Add(assetGuid);
+ _dirtyAssets.Add(assetGuid);
}
}
///
/// Check if an asset is marked as dirty.
///
- public static bool IsDirty(Guid assetGuid)
+ public bool IsDirty(Guid assetGuid)
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- return s_dirtyAssets.Contains(assetGuid);
+ return _dirtyAssets.Contains(assetGuid);
}
}
///
/// Get all dirty assets.
///
- public static Guid[] GetDirtyAssets()
+ public Guid[] GetDirtyAssets()
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- return s_dirtyAssets.ToArray();
+ return _dirtyAssets.ToArray();
}
}
///
/// Clear dirty flag for an asset (typically after saving).
///
- public static void ClearDirty(Guid assetGuid)
+ public void ClearDirty(Guid assetGuid)
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- s_dirtyAssets.Remove(assetGuid);
+ _dirtyAssets.Remove(assetGuid);
}
}
///
/// Clear all dirty flags.
///
- public static void ClearAllDirty()
+ public void ClearAllDirty()
{
- lock (s_dbLock)
+ lock (_dbLock)
{
- s_dirtyAssets.Clear();
+ _dirtyAssets.Clear();
}
}
@@ -251,15 +252,15 @@ public static partial class AssetService
/// Enable or disable automatic asset database refresh.
/// When disabled, file system events are queued and processed only when RefreshAsync() is called.
///
- public static void SetAutoRefresh(bool enabled)
+ public void SetAutoRefresh(bool enabled)
{
- s_autoRefreshEnabled = enabled;
+ _autoRefreshEnabled = enabled;
}
- internal static void FlushPendingCommands()
+ internal void FlushPendingCommands()
{
// Stop timer temporarily
- s_commandProcessorTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ _commandProcessorTimer?.Change(Timeout.Infinite, Timeout.Infinite);
// Give a tiny bit of time for any in-flight file watcher events to post to channel
Thread.Sleep(50);
@@ -268,27 +269,27 @@ public static partial class AssetService
ProcessPendingCommands(null);
}
- private static async ValueTask PostCommandAsync(AssetCommand command, CancellationToken token = default)
+ private async ValueTask PostCommandAsync(AssetCommand command, CancellationToken token = default)
{
- if (s_commandChannel == null)
+ if (_commandChannel == null)
{
return;
}
- if (s_autoRefreshEnabled)
+ if (_autoRefreshEnabled)
{
- await s_commandChannel.Writer.WriteAsync(command, token);
- s_commandProcessorTimer?.Change(s_debounceDelay, Timeout.InfiniteTimeSpan);
+ await _commandChannel.Writer.WriteAsync(command, token);
+ _commandProcessorTimer?.Change(_debounceDelay, Timeout.InfiniteTimeSpan);
}
else
{
- s_waitingCommands.Enqueue(command);
+ _waitingCommands.Enqueue(command);
}
}
- private static async void ProcessPendingCommands(object? state)
+ private async void ProcessPendingCommands(object? state)
{
- if (s_commandChannel == null)
+ if (_commandChannel == null)
{
return;
}
@@ -298,7 +299,7 @@ public static partial class AssetService
// // Collect all pending commands
// var commands = new List();
//
- // while (s_commandChannel.Reader.TryRead(out var cmd))
+ // while (_commandChannel.Reader.TryRead(out var cmd))
// {
// commands.Add(cmd);
// }
@@ -333,12 +334,12 @@ public static partial class AssetService
// Execute commands
// NOTE: We many don't need to collect all commands first, just process as we read.
// Channel in c# is thread-safe for multiple readers/writers.
- //await foreach (var cmd in s_commandChannel.Reader.ReadAllAsync())
+ //await foreach (var cmd in _commandChannel.Reader.ReadAllAsync())
//{
// await ExecuteCommandAsync(cmd);
//}
- while (s_commandChannel.Reader.TryRead(out var cmd))
+ while (_commandChannel.Reader.TryRead(out var cmd))
{
await ExecuteCommandAsync(cmd);
}
@@ -351,11 +352,11 @@ public static partial class AssetService
}
finally
{
- s_resetEventSlim.Set();
+ _resetEventSlim.Set();
}
}
- private static async ValueTask ExecuteCommandAsync(AssetCommand command)
+ private async ValueTask ExecuteCommandAsync(AssetCommand command)
{
switch (command.Type)
{
@@ -384,7 +385,7 @@ public static partial class AssetService
}
}
- private static async ValueTask HandleFileCreatedAsync(string path)
+ private async ValueTask HandleFileCreatedAsync(string path)
{
if (!File.Exists(path))
{
@@ -394,7 +395,7 @@ public static partial class AssetService
await GenerateMetaFileAsync(path, CancellationToken.None);
}
- private static async ValueTask HandleFileModifiedAsync(string path)
+ private async ValueTask HandleFileModifiedAsync(string path)
{
if (!File.Exists(path))
{
@@ -421,7 +422,7 @@ public static partial class AssetService
}
}
- private static async ValueTask HandleFileDeletedAsync(string path)
+ private async ValueTask HandleFileDeletedAsync(string path)
{
var metaFileResult = GetMetaFilePath(path);
if (metaFileResult.IsSuccess && File.Exists(metaFileResult.Value))
@@ -449,7 +450,7 @@ public static partial class AssetService
}
}
- private static async ValueTask HandleFileRenamedAsync(string oldPath, string newPath)
+ private async ValueTask HandleFileRenamedAsync(string oldPath, string newPath)
{
var oldMetaPath = oldPath + Utilities.FileExtensions.META_FILE_EXTENSION;
var newMetaPath = newPath + Utilities.FileExtensions.META_FILE_EXTENSION;
@@ -491,33 +492,33 @@ public static partial class AssetService
}
}
- internal static void Shutdown()
+ internal void Shutdown()
{
- lock (s_initializationLock)
+ lock (_initializationLock)
{
- if (!s_initialized)
+ if (!_initialized)
{
return;
}
- s_watcher?.Dispose();
- s_watcher = null;
+ _watcher?.Dispose();
+ _watcher = null;
- s_commandProcessorTimer?.Dispose();
- s_commandProcessorTimer = null;
+ _commandProcessorTimer?.Dispose();
+ _commandProcessorTimer = null;
- s_dbConnection?.Close();
- s_dbConnection?.Dispose();
- s_dbConnection = null;
+ _dbConnection?.Close();
+ _dbConnection?.Dispose();
+ _dbConnection = null;
- s_assetPathLookup.Clear();
- s_pathAssetLookup.Clear();
- s_dirtyAssets.Clear();
- s_waitingCommands.Clear();
- s_importerInstances.Clear();
- s_importerTypeLookup.Clear();
+ _assetPathLookup.Clear();
+ _pathAssetLookup.Clear();
+ _dirtyAssets.Clear();
+ _waitingCommands.Clear();
+ _importerInstances.Clear();
+ _importerTypeLookup.Clear();
- s_initialized = false;
+ _initialized = false;
}
}
}
diff --git a/Ghost.Editor.Core/AssetHandle/AssetImporter.cs b/Ghost.Editor.Core/AssetHandle/AssetImporter.cs
index 9f04ba4..90954b2 100644
--- a/Ghost.Editor.Core/AssetHandle/AssetImporter.cs
+++ b/Ghost.Editor.Core/AssetHandle/AssetImporter.cs
@@ -1,4 +1,5 @@
using Ghost.Core;
+using Ghost.Editor.Core.Contracts;
namespace Ghost.Editor.Core.AssetHandle;
@@ -11,7 +12,7 @@ public abstract class AssetImporter
/// Metadata for the asset.
/// Cancellation token.
/// Result indicating success or failure.
- public abstract ValueTask ImportAsync(string assetPath, AssetMeta meta, CancellationToken token = default);
+ public abstract ValueTask ImportAsync(string assetPath, AssetMeta meta, IAssetService assetService, CancellationToken token = default);
///
/// Export in-memory asset data to disk.
@@ -34,12 +35,13 @@ public abstract class AssetImporter
/// Dependencies are extracted from asset content during import and stored in the database.
///
/// List of dependency GUIDs extracted from the asset.
+ /// The asset service instance.
/// Result indicating if all dependencies are valid.
- protected virtual ValueTask ValidateDependenciesAsync(List dependencies, CancellationToken token = default)
+ protected virtual ValueTask ValidateDependenciesAsync(List dependencies, IAssetService assetService, CancellationToken token = default)
{
foreach (var dependencyGuid in dependencies)
{
- var path = AssetService.GuidToPath(dependencyGuid);
+ var path = assetService.GuidToPath(dependencyGuid);
if (path.IsFailure)
{
return ValueTask.FromResult(Result.Failure($"Missing dependency: {dependencyGuid}"));
diff --git a/Ghost.Editor.Core/AssetHandle/Importers/TextImporter.cs b/Ghost.Editor.Core/AssetHandle/Importers/TextImporter.cs
index 4c72a47..13404ad 100644
--- a/Ghost.Editor.Core/AssetHandle/Importers/TextImporter.cs
+++ b/Ghost.Editor.Core/AssetHandle/Importers/TextImporter.cs
@@ -1,4 +1,5 @@
using Ghost.Core;
+using Ghost.Editor.Core.Contracts;
namespace Ghost.Editor.Core.AssetHandle.Importers;
@@ -27,7 +28,7 @@ internal class TextImporterSettings : ImporterSettings
[AssetImporter(".txt", ".md")]
internal class TextImporter : AssetImporter
{
- public override async ValueTask ImportAsync(string assetPath, AssetMeta meta, CancellationToken token = default)
+ public override async ValueTask ImportAsync(string assetPath, AssetMeta meta, IAssetService assetService, CancellationToken token = default)
{
var settings = GetSettings(meta);
@@ -36,7 +37,7 @@ internal class TextImporter : AssetImporter
var dependencies = new List();
// Validate dependencies
- var depResult = await ValidateDependenciesAsync(dependencies);
+ var depResult = await ValidateDependenciesAsync(dependencies, assetService, token);
if (depResult.IsFailure)
{
return depResult;
diff --git a/Ghost.Editor.Core/AssetHandle/Importers/TextureImporter.cs b/Ghost.Editor.Core/AssetHandle/Importers/TextureImporter.cs
index 13f0702..168de03 100644
--- a/Ghost.Editor.Core/AssetHandle/Importers/TextureImporter.cs
+++ b/Ghost.Editor.Core/AssetHandle/Importers/TextureImporter.cs
@@ -1,4 +1,5 @@
using Ghost.Core;
+using Ghost.Editor.Core.Contracts;
using System.Text.Json;
namespace Ghost.Editor.Core.AssetHandle.Importers;
@@ -73,20 +74,19 @@ internal class TextureImporterSettings : ImporterSettings
[AssetImporter(".png", ".jpg", ".jpeg", ".dds", ".tga", ".bmp")]
internal class TextureImporter : AssetImporter
{
- public override async ValueTask ImportAsync(string assetPath, AssetMeta meta, CancellationToken token = default)
+ public override async ValueTask ImportAsync(string assetPath, AssetMeta meta, IAssetService assetService, CancellationToken token = default)
{
var settings = GetSettings(meta);
// Textures typically don't reference other assets as dependencies
- // If they did (e.g., normal maps referencing base textures), extract here
- var dependencies = new List();
+ //var dependencies = new List();
- // Validate dependencies
- var depResult = await ValidateDependenciesAsync(dependencies, token);
- if (depResult.IsFailure)
- {
- return depResult;
- }
+ //// Validate dependencies
+ //var depResult = await ValidateDependenciesAsync(dependencies, assetService, token);
+ //if (depResult.IsFailure)
+ //{
+ // return depResult;
+ //}
try
{
@@ -134,7 +134,7 @@ internal class TextureImporter : AssetImporter
};
// Save the imported asset data
- var saveResult = AssetService.SaveImportedAsset(meta.Guid, textureAsset);
+ var saveResult = assetService.SaveImportedAsset(meta.Guid, textureAsset);
if (saveResult.IsFailure)
{
return Result.Failure($"Failed to save texture asset: {saveResult.Message}");
diff --git a/Ghost.Editor.Core/Contracts/IAssetService.cs b/Ghost.Editor.Core/Contracts/IAssetService.cs
index bcc0901..b07138d 100644
--- a/Ghost.Editor.Core/Contracts/IAssetService.cs
+++ b/Ghost.Editor.Core/Contracts/IAssetService.cs
@@ -1,5 +1,62 @@
+using Ghost.Core;
+using Ghost.Editor.Core.AssetHandle;
+
namespace Ghost.Editor.Core.Contracts;
public interface IAssetService
{
+ DirectoryInfo? AssetsDirectory { get; }
+
+ // Lifecycle
+ Task RefreshAsync(CancellationToken token = default);
+
+ // Dirty tracking
+ void MarkDirty(Guid assetGuid);
+ bool IsDirty(Guid assetGuid);
+ Guid[] GetDirtyAssets();
+ void ClearDirty(Guid assetGuid);
+ void ClearAllDirty();
+ void SetAutoRefresh(bool enabled);
+
+ // Path <-> GUID lookup
+ Result PathToGuid(string assetPath);
+ Result GuidToPath(Guid guid);
+
+ // Asset loading
+ Result LoadAsset(Guid guid) where T : Asset;
+ Result LoadAssetAtPath(string assetPath) where T : Asset;
+ void UnloadAsset(Guid guid);
+ void UnloadAllAssets();
+ bool IsAssetLoaded(Guid guid);
+ (int currentSize, int maxSize) GetCacheStats();
+ Result SaveImportedAsset(Guid guid, T assetData) where T : Asset;
+
+ // Asset tags
+ ValueTask>> GetAssetTagsAsync(Guid guid, CancellationToken token = default);
+ ValueTask SetAssetTagsAsync(Guid guid, List tags, CancellationToken token = default);
+
+ // Asset search
+ Task> FindAssetsByNameAsync(string namePattern, CancellationToken token = default);
+ Task> FindAssetsByTagAsync(string tag, CancellationToken token = default);
+ IReadOnlyDictionary GetAllAssets();
+
+ // Asset file operations
+ ValueTask CreateAssetAsync(string assetPath, ReadOnlyMemory content, CancellationToken token = default);
+ ValueTask CreateAssetAsync(string assetPath, CancellationToken token = default);
+ ValueTask DeleteAssetAsync(Guid guid, CancellationToken token = default);
+ ValueTask DeleteAssetAsync(string assetPath, CancellationToken token = default);
+ ValueTask MoveAssetAsync(Guid guid, string newPath, CancellationToken token = default);
+ ValueTask MoveAssetAsync(string oldPath, string newPath, CancellationToken token = default);
+ ValueTask> CopyAssetAsync(Guid guid, string newPath, CancellationToken token = default);
+ ValueTask> CopyAssetAsync(string sourcePath, string destPath, CancellationToken token = default);
+ Result MarkDirtyAsync(Guid guid, CancellationToken token = default);
+ Task ImportDirtyAssetsAsync(CancellationToken token = default);
+
+ // Importer management
+ Type? GetImporterType(string extension);
+ Dictionary GetAllImporters();
+ ValueTask> ExportAssetAsync(string assetPath, T assetData, CancellationToken token = default) where T : class;
+
+ // Asset opening
+ void OpenAsset(string path);
}
diff --git a/Ghost.Editor.Core/EditorApplication.cs b/Ghost.Editor.Core/EditorApplication.cs
index 4bc20ff..67cad2a 100644
--- a/Ghost.Editor.Core/EditorApplication.cs
+++ b/Ghost.Editor.Core/EditorApplication.cs
@@ -1,3 +1,4 @@
+using Ghost.Editor.Core.Contracts;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
@@ -56,5 +57,9 @@ public static class EditorApplication
internal static void Shutdown()
{
+ if (s_serviceProvider?.GetService(typeof(IAssetService)) is AssetHandle.AssetService assetService)
+ {
+ assetService.Shutdown();
+ }
}
}
\ No newline at end of file
diff --git a/Ghost.Editor/ActivationHandler.cs b/Ghost.Editor/ActivationHandler.cs
index a7296c6..94a7045 100644
--- a/Ghost.Editor/ActivationHandler.cs
+++ b/Ghost.Editor/ActivationHandler.cs
@@ -1,4 +1,5 @@
using Ghost.Editor.Core;
+using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models;
using Ghost.Engine;
@@ -61,7 +62,9 @@ internal static class ActivationHandler
((EngineCore)App.GetService()).Init();
});
- // TODO: Initialize other subsystems here.
+ await ((Core.AssetHandle.AssetService)App.GetService()).Init();
+
+ // TODO: Init other subsystems here.
// await Task.Delay(10000); // Wait 10 seconds to simulate work.
}
}
\ No newline at end of file
diff --git a/Ghost.Editor/App.xaml.cs b/Ghost.Editor/App.xaml.cs
index f76e075..f14ff4c 100644
--- a/Ghost.Editor/App.xaml.cs
+++ b/Ghost.Editor/App.xaml.cs
@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Editor.Core;
+using Ghost.Editor.Core.AssetHandle;
using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Services;
using Ghost.Editor.View.Pages.EngineEditor;
diff --git a/Ghost.Editor/ViewModels/Controls/ProjectBrowserViewModel.cs b/Ghost.Editor/ViewModels/Controls/ProjectBrowserViewModel.cs
index 9a7da24..b0d6a81 100644
--- a/Ghost.Editor/ViewModels/Controls/ProjectBrowserViewModel.cs
+++ b/Ghost.Editor/ViewModels/Controls/ProjectBrowserViewModel.cs
@@ -11,6 +11,7 @@ namespace Ghost.Editor.ViewModels.Controls;
internal partial class ProjectBrowserViewModel : ObservableObject
{
private readonly IInspectorService _inspectorService;
+ private readonly IAssetService _assetService;
private readonly Dictionary _pathToDirectoryItemMap = new();
private ExplorerItem? _selectedItem;
@@ -40,9 +41,10 @@ internal partial class ProjectBrowserViewModel : ObservableObject
get; set;
} = string.Empty;
- public ProjectBrowserViewModel(IInspectorService inspectorService)
+ public ProjectBrowserViewModel(IInspectorService inspectorService, IAssetService assetService)
{
_inspectorService = inspectorService;
+ _assetService = assetService;
var assetsRootItem = new ExplorerItem(EditorApplication.ASSETS_FOLDER_NAME, Path.Combine(EditorApplication.CurrentProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
LoadSubFolderRecursive(assetsRootItem);
@@ -108,7 +110,7 @@ internal partial class ProjectBrowserViewModel : ObservableObject
}
else
{
- AssetService.OpenAsset(SelectedItem.FullName);
+ _assetService.OpenAsset(SelectedItem.FullName);
return (null, 1);
}
}
diff --git a/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs b/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs
index 2b56b03..6a199e8 100644
--- a/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs
+++ b/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs
@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Ghost.Editor.Core;
using Ghost.Editor.Core.AssetHandle;
+using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Models;
using System.Collections.ObjectModel;
@@ -8,6 +9,8 @@ namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
internal partial class ProjectViewModel : ObservableObject
{
+ private readonly IAssetService _assetService;
+
public ObservableCollection SubDirectories
{
get;
@@ -34,8 +37,10 @@ internal partial class ProjectViewModel : ObservableObject
set;
}
- public ProjectViewModel()
+ public ProjectViewModel(IAssetService assetService)
{
+ _assetService = assetService;
+
var assetsRootItem = new ExplorerItem("Assets", Path.Combine(EditorApplication.CurrentProjectPath, EditorApplication.ASSETS_FOLDER_NAME), true);
LoadSubFolderRecursive(ref assetsRootItem);
@@ -124,7 +129,7 @@ internal partial class ProjectViewModel : ObservableObject
}
else
{
- AssetService.OpenAsset(SelectedAsset.FullName);
+ _assetService.OpenAsset(SelectedAsset.FullName);
}
}
diff --git a/Ghost.UnitTest/AssetDatabaseIntegrationTest.cs b/Ghost.UnitTest/AssetDatabaseIntegrationTest.cs
index f77a161..e1c30e6 100644
--- a/Ghost.UnitTest/AssetDatabaseIntegrationTest.cs
+++ b/Ghost.UnitTest/AssetDatabaseIntegrationTest.cs
@@ -49,7 +49,7 @@ public class AssetDatabaseIntegrationTest
var projectMetadataInfo = new Data.Models.ProjectMetadataInfo(projectPath, metadata);
ProjectService.CurrentProject = projectMetadataInfo;
- // Initialize AssetService
+ // Init AssetService
await AssetService.Initialize(TestContext.CancellationToken);
// Give the file system watcher time to start