Scaffolded resource manager
This commit is contained in:
6
SlaveEngine.Assets/Asset.cs
Normal file
6
SlaveEngine.Assets/Asset.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace SlaveEngine.Assets;
|
||||
|
||||
public abstract class Asset {
|
||||
public Guid Guid { get; private set; }
|
||||
public AssetFileInfo FileInfo { get; private set; }
|
||||
}
|
||||
5
SlaveEngine.Assets/AssetClasses/VectorAsset.cs
Normal file
5
SlaveEngine.Assets/AssetClasses/VectorAsset.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace SlaveEngine.Assets;
|
||||
|
||||
public class VectorAsset : Asset {
|
||||
//TODO: insert the data from an SVG here.
|
||||
}
|
||||
6
SlaveEngine.Assets/AssetFileInfo.cs
Normal file
6
SlaveEngine.Assets/AssetFileInfo.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace SlaveEngine.Assets;
|
||||
|
||||
public struct AssetFileInfo {
|
||||
public string Filename;
|
||||
public string FilePath;
|
||||
}
|
||||
5
SlaveEngine.Assets/AssetProcessor.cs
Normal file
5
SlaveEngine.Assets/AssetProcessor.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace SlaveEngine.Assets;
|
||||
|
||||
public abstract class AssetProcessor {
|
||||
public abstract Asset Process(AssetFileInfo fileInfo , byte[] data);
|
||||
}
|
||||
46
SlaveEngine.Assets/AssetProcessorAttribute.cs
Normal file
46
SlaveEngine.Assets/AssetProcessorAttribute.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
10
SlaveEngine.Assets/Processors/SVGProcessor.cs
Normal file
10
SlaveEngine.Assets/Processors/SVGProcessor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
61
SlaveEngine.Assets/ResourceManager.cs
Normal file
61
SlaveEngine.Assets/ResourceManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
9
SlaveEngine.Assets/SlaveEngine.Assets.csproj
Normal file
9
SlaveEngine.Assets/SlaveEngine.Assets.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
14
Solution.sln
14
Solution.sln
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2DGAMELIB", "2DGAMELIB\2DGA
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveMatrix.Extract", "SlaveMatrix.Extract\SlaveMatrix.Extract.csproj", "{DAEAB23A-E099-432E-9C85-39D0853BD78A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveEngine.Assets", "SlaveEngine.Assets\SlaveEngine.Assets.csproj", "{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -55,6 +57,18 @@ Global
|
||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in New Issue
Block a user