Scaffolded resource manager

This commit is contained in:
2026-06-14 20:39:39 +02:00
parent e3f9c1cc52
commit 201b13f80f
9 changed files with 162 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
namespace SlaveEngine.Assets;
public abstract class Asset {
public Guid Guid { get; private set; }
public AssetFileInfo FileInfo { get; private set; }
}

View File

@@ -0,0 +1,5 @@
namespace SlaveEngine.Assets;
public class VectorAsset : Asset {
//TODO: insert the data from an SVG here.
}

View File

@@ -0,0 +1,6 @@
namespace SlaveEngine.Assets;
public struct AssetFileInfo {
public string Filename;
public string FilePath;
}

View File

@@ -0,0 +1,5 @@
namespace SlaveEngine.Assets;
public abstract class AssetProcessor {
public abstract Asset Process(AssetFileInfo fileInfo , byte[] data);
}

View File

@@ -0,0 +1,46 @@
namespace SlaveEngine.Assets;
public class AssetProcessorAttribute : Attribute {
public Type ManagedType { get; init; }
public string[] ManagedExtensions { get; init; }
public AssetProcessorAttribute(Type managedType, string[] managedExtensions)
{
ManagedType = managedType ?? throw new ArgumentNullException(nameof(managedType));
// Validate that the provided type is Asset or a subclass of Asset
if (!typeof(Asset).IsSubclassOf(managedType))
throw new ArgumentException($"The type '{managedType.FullName}' must derive from '{typeof(Asset).FullName}'.", nameof(managedType));
// Validate extensions: non-null, non-empty, normalized, start with '.', no whitespace, unique
if (managedExtensions == null)
throw new ArgumentNullException(nameof(managedExtensions));
if (managedExtensions.Length == 0)
throw new ArgumentException("At least one file extension must be provided.", nameof(managedExtensions));
var normalized = new List<string>(managedExtensions.Length);
foreach (var ext in managedExtensions)
{
if (string.IsNullOrWhiteSpace(ext))
throw new ArgumentException("Extensions must be non-empty strings.", nameof(managedExtensions));
var e = ext.Trim();
// allow either "png" or ".png" as input, normalize to lowercase with leading dot
if (!e.StartsWith('.'))
e = "." + e;
if (e.Length < 2)
throw new ArgumentException($"Invalid extension '{ext}'.", nameof(managedExtensions));
if (e.Any(char.IsWhiteSpace))
throw new ArgumentException($"Extension '{ext}' contains whitespace.", nameof(managedExtensions));
normalized.Add(e.ToLowerInvariant());
}
// ensure uniqueness
ManagedExtensions = normalized.Distinct().ToArray();
}
}

View File

@@ -0,0 +1,10 @@
namespace SlaveEngine.Assets.Processors;
[AssetProcessor(typeof(VectorAsset), managedExtensions: [".svg"])]
public sealed class SVGProcessor : AssetProcessor{
public override Asset Process(AssetFileInfo fileInfo, byte[] data)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace SlaveEngine.Assets;
public sealed class ResourceManager {
public ResourceManager(string basePath)
{
if (!Path.Exists(basePath))
throw new DirectoryNotFoundException($"The base path '{basePath}' does not exist.");
BasePath = basePath;
Instance = this;
}
private AssetProcessor? GetProcessorForExtension(string extension) =>
_assetProcessors.FirstOrDefault(p => (bool)p.GetType()
.GetCustomAttribute<AssetProcessorAttribute>()?.ManagedExtensions
.Contains(extension)
);
public static ResourceManager? Instance { get; private set; }
private List<AssetProcessor> _assetProcessors = new();
public string BasePath { get; private set; }
public void Initialize(string basePath)
{
ScanDirectory(basePath);
}
private void ScanDirectory(string directory)
{
var files = Directory.GetFiles(directory);
foreach (var file in files)
{
var ext = Path.GetExtension(file);
// check if we can process this extension
var processor = GetProcessorForExtension(ext);
if (processor == null) continue;
var name = Path.GetFileNameWithoutExtension(file);
AssetFileInfo fileInfo = new()
{
Filename = name,
FilePath = file
};
processor.Process(fileInfo, File.ReadAllBytes(file));
}
}
public void RegisterProcessor(AssetProcessor processor)
{
//make sure the processor for this type isn't already in:
var ap = _assetProcessors.FirstOrDefault(p => p.GetType() == processor.GetType());
if (ap == null) _assetProcessors.Add(processor);
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>