From e76952cd0d0304d4636ec4344c7dc6970f4c990c Mon Sep 17 00:00:00 2001 From: REDCODE Date: Sun, 14 Jun 2026 21:18:50 +0200 Subject: [PATCH] Asset compiler first iteration: --- SlaveEngine.AssetBuilder/Compiler.cs | 187 +++++++++++++++++ SlaveEngine.AssetBuilder/Models/YamlModels.cs | 35 ++++ SlaveEngine.AssetBuilder/Program.cs | 32 +++ .../SlaveEngine.AssetBuilder.csproj | 20 ++ SlaveEngine.AssetBuilder/SparWriter.cs | 73 +++++++ SlaveEngine.AssetBuilder/SvgParser.cs | 192 ++++++++++++++++++ .../AssetClasses/VectorAsset.cs | 5 - SlaveEngine.Assets/Processors/SVGProcessor.cs | 10 - SlaveEngine.Assets/ResourceManager.cs | 97 +++++---- Solution.sln | 14 ++ 10 files changed, 610 insertions(+), 55 deletions(-) create mode 100644 SlaveEngine.AssetBuilder/Compiler.cs create mode 100644 SlaveEngine.AssetBuilder/Models/YamlModels.cs create mode 100644 SlaveEngine.AssetBuilder/Program.cs create mode 100644 SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj create mode 100644 SlaveEngine.AssetBuilder/SparWriter.cs create mode 100644 SlaveEngine.AssetBuilder/SvgParser.cs delete mode 100644 SlaveEngine.Assets/AssetClasses/VectorAsset.cs delete mode 100644 SlaveEngine.Assets/Processors/SVGProcessor.cs diff --git a/SlaveEngine.AssetBuilder/Compiler.cs b/SlaveEngine.AssetBuilder/Compiler.cs new file mode 100644 index 0000000..2a8c605 --- /dev/null +++ b/SlaveEngine.AssetBuilder/Compiler.cs @@ -0,0 +1,187 @@ +using System.Text.RegularExpressions; +using SlaveEngine.AssetBuilder.Models; +using SlaveEngine.Assets; +using SlaveEngine.Assets.Models; +using SlaveEngine.Assets.Primitives; +using Spectre.Console; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace SlaveEngine.AssetBuilder; + +public class Compiler { + private readonly string _inputDir; + private readonly string _outputDir; + private readonly IDeserializer _yaml; + + public Compiler(string inputDir, string outputDir) { + _inputDir = inputDir; + _outputDir = outputDir; + + _yaml = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + } + + private T LoadYaml(string path) { + var text = File.ReadAllText(path); + text = NormalizeFlowArrays(text); + return _yaml.Deserialize(text); + } + + private static string NormalizeFlowArrays(string yaml) { + return Regex.Replace(yaml, + @"(position:\s*)\[([\s\S]*?)\]", + m => { + var prefix = m.Groups[1].Value.TrimEnd(); + var values = m.Groups[2].Value + .Replace('\n', ' ') + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(v => v.Trim()) + .Where(v => v.Length > 0) + .ToList(); + if (values.Count == 0) return prefix + " []"; + return prefix + "\n" + + string.Join("\n", values.Select(v => $" - {v}")); + }); + } + + public int Run() { + AnsiConsole.Write(new Rule("[yellow]SlaveEngine Asset Builder[/]") { Justification = Justify.Left }); + AnsiConsole.MarkupLine(" [bold]Input:[/] {0}", _inputDir); + AnsiConsole.MarkupLine(" [bold]Output:[/] {0}", _outputDir); + AnsiConsole.WriteLine(); + + Directory.CreateDirectory(_outputDir); + + var parts = DiscoverParts(); + if (parts.Count == 0) { + AnsiConsole.MarkupLine("[red]No parts found.[/]"); + return 1; + } + + var success = 0; + + AnsiConsole.Progress() + .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new ElapsedTimeColumn()) + .Start(ctx => { + var task = ctx.AddTask("Compiling parts"); + task.MaxValue = parts.Count; + + foreach (var (id, partDir) in parts) { + var (ok, msg) = CompilePart(partDir); + if (ok) + AnsiConsole.MarkupLine("[green]{0}[/]", msg); + else + AnsiConsole.MarkupLine("[red]{0}[/]", msg); + if (ok) success++; + task.Increment(1); + } + }); + + if (success == parts.Count) + AnsiConsole.MarkupLine("[green]Done. {0}/{1} compiled.[/]", success, parts.Count); + else if (success > 0) + AnsiConsole.MarkupLine("[yellow]Done. {0}/{1} compiled.[/]", success, parts.Count); + else + AnsiConsole.MarkupLine("[red]Done. {0}/{1} compiled.[/]", success, parts.Count); + + return success == parts.Count ? 0 : 1; + } + + private List<(string id, string dir)> DiscoverParts() { + var parts = new List<(string id, string dir)>(); + + var catalogPath = Path.Combine(_inputDir, "Catalog.yaml"); + if (File.Exists(catalogPath)) { + AnsiConsole.MarkupLine("[dim]Using catalog:[/] {0}", catalogPath); + var catalog = LoadYaml(catalogPath); + if (catalog?.Parts != null) { + foreach (var entry in catalog.Parts) { + var partDir = Path.GetFullPath(Path.Combine(_inputDir, entry.Path)); + if (Directory.Exists(partDir)) + parts.Add((entry.Id, partDir)); + else + AnsiConsole.MarkupLine("[yellow] Warning:[/] directory not found: {0}", partDir); + } + return parts; + } + } + + var scanDir = Path.Combine(_inputDir, "Parts"); + if (!Directory.Exists(scanDir)) return parts; + + AnsiConsole.MarkupLine("[dim]Scanning:[/] {0}", scanDir); + foreach (var dir in Directory.GetDirectories(scanDir)) { + var yamlPath = Path.Combine(dir, "part.yaml"); + if (!File.Exists(yamlPath)) continue; + try { + var partYaml = LoadYaml(yamlPath); + if (partYaml != null && !string.IsNullOrEmpty(partYaml.Id)) + parts.Add((partYaml.Id, dir)); + } catch (Exception ex) { + AnsiConsole.MarkupLine("[yellow] Warning:[/] failed to parse {0}: {1}", yamlPath, ex.Message); + } + } + + return parts; + } + + private (bool success, string message) CompilePart(string partDir) { + var yamlPath = Path.Combine(partDir, "part.yaml"); + if (!File.Exists(yamlPath)) + return (false, $"Skipping (no part.yaml): {partDir}"); + + PartYaml partYaml; + try { + partYaml = LoadYaml(yamlPath); + } catch (Exception ex) { + return (false, $"Failed to parse {yamlPath}: {ex.Message}"); + } + + if (partYaml == null) + return (false, $"Empty YAML: {yamlPath}"); + + try { + var variantList = partYaml.Variants ?? new List(); + var variants = new VariantAsset[variantList.Count]; + for (var i = 0; i < variantList.Count; i++) { + var v = variantList[i]; + var svgPath = Path.Combine(partDir, v.File); + if (!File.Exists(svgPath)) { + variants[i] = new VariantAsset { X = v.X, Y = v.Y, Groups = Array.Empty() }; + continue; + } + var groups = SvgParser.Parse(svgPath); + variants[i] = new VariantAsset { X = v.X, Y = v.Y, Groups = groups }; + } + + var fieldList = partYaml.Fields ?? new List(); + var jointList = partYaml.Joints ?? new List(); + + var asset = new PartAsset { + Guid = Guid.NewGuid(), + FileInfo = new AssetFileInfo { Filename = partYaml.Id, FilePath = "" }, + Id = partYaml.Id, + OriginalKey = partYaml.OriginalKey ?? "", + Resource = partYaml.Resource ?? "", + MorphX = partYaml.MorphX, + MorphY = partYaml.MorphY, + Fields = fieldList.Select(f => f.Name ?? "").ToArray(), + Joints = jointList.Select(j => new Joint { + X = j.Position != null && j.Position.Count > 0 ? j.Position[0] : 0, + Y = j.Position != null && j.Position.Count > 1 ? j.Position[1] : 0 + }).ToArray(), + Variants = variants + }; + + var outputPath = Path.Combine(_outputDir, $"{partYaml.Id}.spart"); + SparWriter.Write(asset, outputPath); + + return (true, $" {partYaml.Id}... OK ({variants.Length} variants)"); + } catch (Exception ex) { + return (false, $" {partYaml.Id}... FAILED: {ex.Message}"); + } + } +} diff --git a/SlaveEngine.AssetBuilder/Models/YamlModels.cs b/SlaveEngine.AssetBuilder/Models/YamlModels.cs new file mode 100644 index 0000000..f5531eb --- /dev/null +++ b/SlaveEngine.AssetBuilder/Models/YamlModels.cs @@ -0,0 +1,35 @@ +namespace SlaveEngine.AssetBuilder.Models; + +public class PartYaml { + public string Id { get; set; } = ""; + public string OriginalKey { get; set; } = ""; + public string Resource { get; set; } = ""; + public int MorphX { get; set; } + public int MorphY { get; set; } + public List? Variants { get; set; } + public List? Fields { get; set; } + public List? Joints { get; set; } +} + +public class VariantYaml { + public int X { get; set; } + public int Y { get; set; } + public string File { get; set; } = ""; +} + +public class FieldYaml { + public string Name { get; set; } = ""; +} + +public class JointYaml { + public List? Position { get; set; } +} + +public class CatalogYaml { + public List? Parts { get; set; } +} + +public class CatalogEntry { + public string Id { get; set; } = ""; + public string Path { get; set; } = ""; +} diff --git a/SlaveEngine.AssetBuilder/Program.cs b/SlaveEngine.AssetBuilder/Program.cs new file mode 100644 index 0000000..8cdb582 --- /dev/null +++ b/SlaveEngine.AssetBuilder/Program.cs @@ -0,0 +1,32 @@ +using System.CommandLine; +using Spectre.Console; + +namespace SlaveEngine.AssetBuilder; + +class Program { + static int Main(string[] args) { + var inputOption = new Option("--input", "Path to the input SlaveMatrix/Assets folder"); + inputOption.SetDefaultValueFactory(() => new DirectoryInfo("./SlaveMatrix/Assets")); + + var outputOption = new Option("--output", "Path to the output directory for compiled .spart files"); + outputOption.SetDefaultValueFactory(() => new DirectoryInfo("./Assets")); + + var rootCommand = new RootCommand( + "SlaveEngine Asset Builder — compiles YAML + SVG assets into .spart binaries"); + rootCommand.AddOption(inputOption); + rootCommand.AddOption(outputOption); + + rootCommand.SetHandler((DirectoryInfo input, DirectoryInfo output) => { + if (!input.Exists) { + AnsiConsole.MarkupLine("[red]Error:[/] input directory not found: {0}", input.FullName); + Environment.Exit(1); + } + + var compiler = new Compiler(input.FullName, output.FullName); + var result = compiler.Run(); + Environment.Exit(result); + }, inputOption, outputOption); + + return rootCommand.Invoke(args); + } +} diff --git a/SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj b/SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj new file mode 100644 index 0000000..1f225be --- /dev/null +++ b/SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + Exe + net8.0 + enable + enable + + + diff --git a/SlaveEngine.AssetBuilder/SparWriter.cs b/SlaveEngine.AssetBuilder/SparWriter.cs new file mode 100644 index 0000000..85395a7 --- /dev/null +++ b/SlaveEngine.AssetBuilder/SparWriter.cs @@ -0,0 +1,73 @@ +using System.Text; +using SlaveEngine.Assets; +using SlaveEngine.Assets.Models; +using SlaveEngine.Assets.Primitives; + +namespace SlaveEngine.AssetBuilder; + +public static class SparWriter { + public static void Write(PartAsset asset, string outputPath) { + using var stream = File.Create(outputPath); + using var writer = new BinaryWriter(stream); + + writer.Write(Encoding.UTF8.GetBytes("SPRT")); + writer.Write(1); // version + + WriteString(writer, asset.Id); + WriteString(writer, asset.OriginalKey); + WriteString(writer, asset.Resource); + writer.Write(asset.MorphX); + writer.Write(asset.MorphY); + + writer.Write(asset.Fields.Length); + foreach (var f in asset.Fields) WriteString(writer, f); + + writer.Write(asset.Joints.Length); + foreach (var j in asset.Joints) { + writer.Write(j.X); + writer.Write(j.Y); + } + + writer.Write(asset.Variants.Length); + foreach (var variant in asset.Variants) { + writer.Write(variant.X); + writer.Write(variant.Y); + + writer.Write(variant.Groups.Length); + foreach (var group in variant.Groups) { + WriteString(writer, group.Name); + writer.Write(group.HasTransform); + if (group.HasTransform) { + writer.Write(group.Tx); + writer.Write(group.Ty); + writer.Write(group.Angle); + writer.Write(group.Sx); + writer.Write(group.Sy); + writer.Write(group.Bx); + writer.Write(group.By); + } + + writer.Write(group.Paths.Length); + foreach (var path in group.Paths) { + WriteString(writer, path.Fill); + WriteString(writer, path.Stroke); + writer.Write(path.StrokeWidth); + writer.Write(path.IsClosed); + + writer.Write(path.Commands.Length); + foreach (var cmd in path.Commands) { + writer.Write((byte)cmd.Type); + writer.Write(cmd.Args.Length); + foreach (var arg in cmd.Args) writer.Write(arg); + } + } + } + } + } + + private static void WriteString(BinaryWriter writer, string s) { + var bytes = Encoding.UTF8.GetBytes(s); + writer.Write(bytes.Length); + writer.Write(bytes); + } +} diff --git a/SlaveEngine.AssetBuilder/SvgParser.cs b/SlaveEngine.AssetBuilder/SvgParser.cs new file mode 100644 index 0000000..4dab41d --- /dev/null +++ b/SlaveEngine.AssetBuilder/SvgParser.cs @@ -0,0 +1,192 @@ +using System.Globalization; +using System.Xml.Linq; +using SlaveEngine.Assets.Models; +using SlaveEngine.Assets.Primitives; + +namespace SlaveEngine.AssetBuilder; + +public static class SvgParser { + private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; + + public static PathGroup[] Parse(string svgPath) { + var doc = XDocument.Load(svgPath); + var svg = doc.Root; + if (svg == null) return Array.Empty(); + + var groups = new List(); + CollectGroups(svg, groups, null); + return groups.ToArray(); + } + + private static void CollectGroups(XElement parent, List groups, string? inheritedName) { + foreach (var child in parent.Elements()) { + var name = child.Name.LocalName; + if (name != "g" && name != "path") continue; + var id = child.Attribute("id")?.Value ?? inheritedName ?? ""; + + if (name == "g") { + var transformAttr = child.Attribute("transform")?.Value; + + if (transformAttr != null) { + ParseTransform(transformAttr, + out var tx, out var ty, out var angle, + out var sx, out var sy, out var bx, out var by); + + var paths = CollectPaths(child); + groups.Add(new PathGroup { + Name = id, + HasTransform = true, + Tx = tx, Ty = ty, Angle = angle, + Sx = sx, Sy = sy, Bx = bx, By = by, + Paths = paths + }); + } else { + CollectGroups(child, groups, id); + } + } + } + } + + private static PathData[] CollectPaths(XElement parent) { + var paths = new List(); + foreach (var elem in parent.Elements()) { + var name = elem.Name.LocalName; + if (name == "path") { + var d = elem.Attribute("d")?.Value ?? ""; + var (commands, isClosed) = ParsePathData(d); + paths.Add(new PathData { + Fill = elem.Attribute("fill")?.Value ?? "none", + Stroke = elem.Attribute("stroke")?.Value ?? "none", + StrokeWidth = float.Parse( + elem.Attribute("stroke-width")?.Value ?? "0", Inv), + IsClosed = isClosed, + Commands = commands + }); + } else if (name == "g") { + paths.AddRange(CollectPaths(elem)); + } + } + return paths.ToArray(); + } + + private static void ParseTransform(string t, + out float tx, out float ty, out float angle, + out float sx, out float sy, out float bx, out float by) { + tx = ty = angle = 0; + sx = sy = 1; + bx = by = 0; + + if (string.IsNullOrWhiteSpace(t)) return; + + var parts = t.Split(')', StringSplitOptions.RemoveEmptyEntries); + foreach (var part in parts) { + var trimmed = part.TrimStart(); + if (trimmed.StartsWith("translate(")) { + var args = trimmed["translate(".Length..].Split(','); + if (args.Length >= 2) { + tx = float.Parse(args[0].Trim(), Inv); + ty = float.Parse(args[1].Trim(), Inv); + } + } else if (trimmed.StartsWith("rotate(")) { + var arg = trimmed["rotate(".Length..].Trim(); + angle = float.Parse(arg, Inv); + } else if (trimmed.StartsWith("scale(")) { + var args = trimmed["scale(".Length..].Split(','); + if (args.Length >= 2) { + sx = float.Parse(args[0].Trim(), Inv); + sy = float.Parse(args[1].Trim(), Inv); + } + } + } + } + + internal static (BezierCommand[] commands, bool isClosed) ParsePathData(string d) { + var commands = new List(); + var tokens = Tokenize(d); + var i = 0; + float cx = 0, cy = 0; + + while (i < tokens.Count) { + var cmd = tokens[i][0]; + i++; + + switch (cmd) { + case 'M': case 'm': { + var x = float.Parse(tokens[i++], Inv); + var y = float.Parse(tokens[i++], Inv); + if (cmd == 'm') { x += cx; y += cy; } + commands.Add(new BezierCommand { Type = CommandType.MoveTo, Args = new[] { x, y } }); + cx = x; cy = y; + break; + } + case 'L': case 'l': { + var x = float.Parse(tokens[i++], Inv); + var y = float.Parse(tokens[i++], Inv); + if (cmd == 'l') { x += cx; y += cy; } + commands.Add(new BezierCommand { Type = CommandType.LineTo, Args = new[] { x, y } }); + cx = x; cy = y; + break; + } + case 'C': case 'c': { + var c1x = float.Parse(tokens[i++], Inv); + var c1y = float.Parse(tokens[i++], Inv); + var c2x = float.Parse(tokens[i++], Inv); + var c2y = float.Parse(tokens[i++], Inv); + var ex = float.Parse(tokens[i++], Inv); + var ey = float.Parse(tokens[i++], Inv); + if (cmd == 'c') { + c1x += cx; c1y += cy; + c2x += cx; c2y += cy; + ex += cx; ey += cy; + } + commands.Add(new BezierCommand { + Type = CommandType.CubicBezierTo, + Args = new[] { c1x, c1y, c2x, c2y, ex, ey } + }); + cx = ex; cy = ey; + break; + } + case 'Z': case 'z': { + commands.Add(new BezierCommand { Type = CommandType.Close, Args = Array.Empty() }); + break; + } + } + } + + var closed = commands.Count > 0 && commands[^1].Type == CommandType.Close; + return (commands.ToArray(), closed); + } + + private static List Tokenize(string d) { + var tokens = new List(); + var i = 0; + while (i < d.Length) { + var c = d[i]; + if (char.IsWhiteSpace(c) || c == ',') { i++; continue; } + if (char.IsLetter(c)) { + var upper = char.ToUpperInvariant(c); + if (upper is 'M' or 'L' or 'C' or 'Z') { + tokens.Add(c.ToString()); + i++; + continue; + } + i++; + continue; + } + if (c is '-' or '+' or '.' || char.IsDigit(c)) { + var start = i; + if (c is '-' or '+') i++; + while (i < d.Length && (char.IsDigit(d[i]) || d[i] == '.')) i++; + if (i < d.Length && (d[i] is 'e' or 'E')) { + i++; + if (i < d.Length && (d[i] is '+' or '-')) i++; + while (i < d.Length && char.IsDigit(d[i])) i++; + } + tokens.Add(d[start..i]); + continue; + } + i++; + } + return tokens; + } +} diff --git a/SlaveEngine.Assets/AssetClasses/VectorAsset.cs b/SlaveEngine.Assets/AssetClasses/VectorAsset.cs deleted file mode 100644 index 2ae5faf..0000000 --- a/SlaveEngine.Assets/AssetClasses/VectorAsset.cs +++ /dev/null @@ -1,5 +0,0 @@ -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/Processors/SVGProcessor.cs b/SlaveEngine.Assets/Processors/SVGProcessor.cs deleted file mode 100644 index dd1c3a4..0000000 --- a/SlaveEngine.Assets/Processors/SVGProcessor.cs +++ /dev/null @@ -1,10 +0,0 @@ -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 index 1970209..e11c546 100644 --- a/SlaveEngine.Assets/ResourceManager.cs +++ b/SlaveEngine.Assets/ResourceManager.cs @@ -1,61 +1,78 @@ -using System; -using System.Collections.Generic; using System.Reflection; +using System.Text; namespace SlaveEngine.Assets; public sealed class ResourceManager { - public ResourceManager(string basePath) - { + 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 readonly List _assetProcessors = new(); + private readonly Dictionary _assetIndex = new(); + private readonly Dictionary _assetCache = new(); + + public void Initialize() { + 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)); + private void ScanDirectory(string directory) { + foreach (var file in Directory.GetFiles(directory, "*.spart")) { + try { + var id = ReadAssetIdFromSpart(file); + _assetIndex[id] = file; + } catch { + // skip corrupt files + } } } - 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); + public T? GetAsset(string id) where T : Asset { + if (_assetCache.TryGetValue(id, out var cached)) + return (T)cached; + + if (!_assetIndex.TryGetValue(id, out var path)) + return null; + + var ext = Path.GetExtension(path); + var processor = GetProcessorForExtension(ext); + if (processor == null) + return null; + + var fileInfo = new AssetFileInfo { + Filename = Path.GetFileNameWithoutExtension(path), + FilePath = path + }; + + var asset = processor.Process(fileInfo, File.ReadAllBytes(path)); + _assetCache[id] = asset; + return (T)asset; } -} \ No newline at end of file + + public IReadOnlyCollection GetAssetIds() => _assetIndex.Keys; + + public void RegisterProcessor(AssetProcessor processor) { + if (_assetProcessors.All(p => p.GetType() != processor.GetType())) + _assetProcessors.Add(processor); + } + + private AssetProcessor? GetProcessorForExtension(string extension) => + _assetProcessors.FirstOrDefault(p => p.GetType() + .GetCustomAttribute()?.ManagedExtensions + .Contains(extension) == true); + + private static string ReadAssetIdFromSpart(string path) { + using var stream = File.OpenRead(path); + using var reader = new BinaryReader(stream); + reader.ReadBytes(8); + var len = reader.ReadInt32(); + return Encoding.UTF8.GetString(reader.ReadBytes(len)); + } +} diff --git a/Solution.sln b/Solution.sln index ec84391..6e71f6b 100644 --- a/Solution.sln +++ b/Solution.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveMatrix.Extract", "Slav EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveEngine.Assets", "SlaveEngine.Assets\SlaveEngine.Assets.csproj", "{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveEngine.AssetBuilder", "SlaveEngine.AssetBuilder\SlaveEngine.AssetBuilder.csproj", "{DE1800EA-9AB1-4558-BB60-40BAA505F475}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -69,6 +71,18 @@ Global {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 + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x64.Build.0 = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x86.Build.0 = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x64.ActiveCfg = Release|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x64.Build.0 = Release|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x86.ActiveCfg = Release|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x86.Build.0 = Release|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE