diff --git a/SlaveEngine.Assets/Asset.cs b/SlaveEngine.Assets/Asset.cs new file mode 100644 index 0000000..93baf03 --- /dev/null +++ b/SlaveEngine.Assets/Asset.cs @@ -0,0 +1,6 @@ +namespace SlaveEngine.Assets; + +public abstract class Asset { + public Guid Guid { get; private set; } + public AssetFileInfo FileInfo { get; private set; } +} \ No newline at end of file diff --git a/SlaveEngine.Assets/AssetClasses/VectorAsset.cs b/SlaveEngine.Assets/AssetClasses/VectorAsset.cs new file mode 100644 index 0000000..2ae5faf --- /dev/null +++ b/SlaveEngine.Assets/AssetClasses/VectorAsset.cs @@ -0,0 +1,5 @@ +namespace SlaveEngine.Assets; + +public class VectorAsset : Asset { + //TODO: insert the data from an SVG here. +} \ No newline at end of file diff --git a/SlaveEngine.Assets/AssetFileInfo.cs b/SlaveEngine.Assets/AssetFileInfo.cs new file mode 100644 index 0000000..dec3527 --- /dev/null +++ b/SlaveEngine.Assets/AssetFileInfo.cs @@ -0,0 +1,6 @@ +namespace SlaveEngine.Assets; + +public struct AssetFileInfo { + public string Filename; + public string FilePath; +} \ No newline at end of file diff --git a/SlaveEngine.Assets/AssetProcessor.cs b/SlaveEngine.Assets/AssetProcessor.cs new file mode 100644 index 0000000..7aeeb01 --- /dev/null +++ b/SlaveEngine.Assets/AssetProcessor.cs @@ -0,0 +1,5 @@ +namespace SlaveEngine.Assets; + +public abstract class AssetProcessor { + public abstract Asset Process(AssetFileInfo fileInfo , byte[] data); +} \ No newline at end of file diff --git a/SlaveEngine.Assets/AssetProcessorAttribute.cs b/SlaveEngine.Assets/AssetProcessorAttribute.cs new file mode 100644 index 0000000..cff89c9 --- /dev/null +++ b/SlaveEngine.Assets/AssetProcessorAttribute.cs @@ -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(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(); + } +} \ No newline at end of file diff --git a/SlaveEngine.Assets/Processors/SVGProcessor.cs b/SlaveEngine.Assets/Processors/SVGProcessor.cs new file mode 100644 index 0000000..dd1c3a4 --- /dev/null +++ b/SlaveEngine.Assets/Processors/SVGProcessor.cs @@ -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(); + } +} \ No newline at end of file diff --git a/SlaveEngine.Assets/ResourceManager.cs b/SlaveEngine.Assets/ResourceManager.cs new file mode 100644 index 0000000..1970209 --- /dev/null +++ b/SlaveEngine.Assets/ResourceManager.cs @@ -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()?.ManagedExtensions + .Contains(extension) + ); + + public static ResourceManager? Instance { get; private set; } + + private List _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); + } +} \ No newline at end of file diff --git a/SlaveEngine.Assets/SlaveEngine.Assets.csproj b/SlaveEngine.Assets/SlaveEngine.Assets.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/SlaveEngine.Assets/SlaveEngine.Assets.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Solution.sln b/Solution.sln index e298a31..ec84391 100644 --- a/Solution.sln +++ b/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