diff --git a/.idea/.idea.Nerfed/.idea/vcs.xml b/.idea/.idea.Nerfed/.idea/vcs.xml index 35eb1dd..3886124 100644 --- a/.idea/.idea.Nerfed/.idea/vcs.xml +++ b/.idea/.idea.Nerfed/.idea/vcs.xml @@ -2,5 +2,10 @@ + + + + + \ No newline at end of file diff --git a/Nerfed.Builder/ArgsParser.cs b/Nerfed.Builder/ArgsParser.cs new file mode 100644 index 0000000..ff846ff --- /dev/null +++ b/Nerfed.Builder/ArgsParser.cs @@ -0,0 +1,129 @@ +using System.Collections; +using System.ComponentModel; +using System.Reflection; + +namespace Nerfed.Builder; + + +public class ArgsParser where TArgs : new() +{ + private enum ArgType + { + None, + Key, + Value + } + + public TArgs Arguments { get; } + + private readonly string[] args; + private readonly Dictionary argKeyPropertyMap = new Dictionary(); + + public ArgsParser(string[] args) + { + this.args = args; + Arguments = new TArgs(); + PropertyInfo[] properties = Arguments.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + foreach (PropertyInfo property in properties) + { + ArgumentAttribute argAttribute = property.GetCustomAttribute(); + if (argAttribute == null || string.IsNullOrEmpty(argAttribute.ArgKey)) + { + continue; + } + + argKeyPropertyMap.Add(argAttribute.ArgKey, property); + } + } + + public bool Parse() + { + PropertyInfo property = null; + ArgType lastArgType = ArgType.None; + + for (int i = 0; i < args.Length; i++) + { + string arg = args[i]; + if (arg[0] == '-') + { + if (!argKeyPropertyMap.TryGetValue(arg, out property)) + { + Console.Error.WriteLine($"Invalid argument: {arg}, no such argument key exists"); + return false; + } + + // Boolean arguments require no value, set to true immidiately. + if (property.PropertyType == typeof(bool)) + { + property.SetValue(Arguments, true); + lastArgType = ArgType.Value; + } + else + { + lastArgType = ArgType.Key; + } + } + else + { + if (lastArgType == ArgType.None) + { + Console.Error.WriteLine($"Invalid argument: {arg}, no argument key was provided"); + return false; + } + + Type propertyType = property.PropertyType; + if (propertyType.IsArray) + { + throw new InvalidOperationException("Arrays are not supported, use List instead"); + } + + bool propertyTypeIsList = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>); + if (propertyTypeIsList) + { + propertyType = propertyType.GenericTypeArguments[0]; + } + + TypeConverter typeConverter = TypeDescriptor.GetConverter(propertyType); + object value = typeConverter.ConvertFromString(arg); + + if (value is string stringValue) + { + if (!string.IsNullOrEmpty(stringValue)) + { + if (stringValue[0] == '"') + { + stringValue = stringValue.Substring(1, stringValue.Length - 1); + } + + if (stringValue[^1] == '"') + { + stringValue = stringValue.Substring(0, stringValue.Length - 1); + } + + value = stringValue; + } + } + + if (propertyTypeIsList) + { + IList list = (IList)property.GetValue(Arguments); + if (list == null) + { + list = (IList)Activator.CreateInstance(property.PropertyType); + property.SetValue(Arguments, list); + } + + list.Add(value); + } + else + { + property.SetValue(Arguments, value); + } + + lastArgType = ArgType.Value; + } + } + + return true; + } +} diff --git a/Nerfed.Builder/ArgumentAttribute.cs b/Nerfed.Builder/ArgumentAttribute.cs new file mode 100644 index 0000000..ef57cae --- /dev/null +++ b/Nerfed.Builder/ArgumentAttribute.cs @@ -0,0 +1,11 @@ +namespace Nerfed.Builder; + +public class ArgumentAttribute : Attribute +{ + public string ArgKey { get; } + + public ArgumentAttribute(string argKey) + { + ArgKey = argKey; + } +} diff --git a/Nerfed.Builder/Builder/BuildArgs.cs b/Nerfed.Builder/Builder/BuildArgs.cs new file mode 100644 index 0000000..f9a8580 --- /dev/null +++ b/Nerfed.Builder/Builder/BuildArgs.cs @@ -0,0 +1,16 @@ +namespace Nerfed.Builder; + +public class BuildArgs +{ + [Argument("-build")] + public bool Build { get; set; } + + [Argument("-projectPath")] + public string ProjectPath { get; set; } + + [Argument("-platform")] + public string Platform { get; set; } + + [Argument("-content")] + public List ContentFiles { get; set; } +} diff --git a/Nerfed.Builder/Builder/Builder.cs b/Nerfed.Builder/Builder/Builder.cs new file mode 100644 index 0000000..bd536c2 --- /dev/null +++ b/Nerfed.Builder/Builder/Builder.cs @@ -0,0 +1,119 @@ +using System.Diagnostics; + +namespace Nerfed.Builder; + +public class Builder : IDisposable +{ + private readonly Dictionary importers = new Dictionary(); + private readonly RawFileImporter rawFileImporter; + + public Builder() + { + rawFileImporter = new RawFileImporter(); + + ShaderImporter shaderImporter = new ShaderImporter(); + importers.Add(".vert", shaderImporter); // Vertex shader + importers.Add(".frag", shaderImporter); // Fragment shader + importers.Add(".tesc", shaderImporter); // Tessellation control shader + importers.Add(".tese", shaderImporter); // Tessellation evaluation shader + importers.Add(".geom", shaderImporter); // Geometry shader + importers.Add(".comp", shaderImporter); // Compute shader + } + + public void Run(BuildArgs args) + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + List contentFiles = args.ContentFiles; + string absContentPath = $"{args.ProjectPath}/{PathUtil.ContentFolderName}"; + + // If no files are provided, build all content. + if (args.ContentFiles == null) + { + contentFiles = []; + CollectAssetFiles(absContentPath, absContentPath, ref contentFiles); + } + + string importPath = $"{args.ProjectPath}/{PathUtil.ImportFolderName}"; + + ParallelOptions parallelOptions = new ParallelOptions + { + MaxDegreeOfParallelism = contentFiles.Count + }; + Parallel.ForEach(contentFiles, parallelOptions, relativeFile => + { + try + { + string inFile = $"{args.ProjectPath}/{PathUtil.ContentFolderName}/{relativeFile}"; + + if (!File.Exists(inFile)) + { + Console.Error.WriteLine($"Asset file '{relativeFile}' not found"); + return; + } + + string outFile = $"{importPath}/{relativeFile}{PathUtil.ImportedFileExtension}"; + + FileInfo inFileInfo = new FileInfo(inFile); + FileInfo outFileInfo = new FileInfo(outFile); + + if (!FileUtil.IsNewer(inFileInfo, outFileInfo)) + { + // File has not changed since last build, no need to build this one. + return; + } + + string outDir = Path.GetDirectoryName(outFile); + if (!Directory.Exists(outDir)) + { + Directory.CreateDirectory(outDir); + } + + string ext = Path.GetExtension(inFile).ToLower(); + if (importers.TryGetValue(ext, out IImporter importer)) + { + importer.Import(inFile, outFile); + } + else + { + rawFileImporter.Import(inFile, outFile); + } + + Console.WriteLine(relativeFile); + } + catch (Exception e) + { + Console.Error.WriteLine($"Import error on asset '{relativeFile}': {e.Message}"); + } + }); + + Console.WriteLine($"Build content completed in {stopwatch.Elapsed.TotalSeconds:F2} seconds"); + } + + private void CollectAssetFiles(string assetDir, string dir, ref List files) + { + foreach (string file in Directory.EnumerateFiles(dir)) + { + if (Path.GetExtension(file).Equals(PathUtil.ImportFileExtension, StringComparison.CurrentCultureIgnoreCase)) + { + continue; + } + + string relativeFile = file.Substring(assetDir.Length, file.Length - assetDir.Length); + if (relativeFile[0] == Path.DirectorySeparatorChar || relativeFile[0] == Path.AltDirectorySeparatorChar) + { + relativeFile = relativeFile.Substring(1, relativeFile.Length - 1); + } + + files.Add(relativeFile); + } + + foreach (string subDir in Directory.EnumerateDirectories(dir)) + { + CollectAssetFiles(assetDir, subDir, ref files); + } + } + + public void Dispose() { } +} diff --git a/Nerfed.Builder/Builder/FileUtil.cs b/Nerfed.Builder/Builder/FileUtil.cs new file mode 100644 index 0000000..ebec6eb --- /dev/null +++ b/Nerfed.Builder/Builder/FileUtil.cs @@ -0,0 +1,39 @@ +namespace Nerfed.Builder; + +public static class FileUtil +{ + public static void Copy(string srcFile, string dstFile) { + string dstDir = Path.GetDirectoryName(dstFile); + if(!Directory.Exists(dstDir)) { + Directory.CreateDirectory(dstDir); + } + + File.Copy(srcFile, dstFile, true); + UpdateFileTimeAttributes(dstFile); + } + + public static void WriteBytes(string dstFile, byte[] bytes) { + File.WriteAllBytes(dstFile, bytes); + UpdateFileTimeAttributes(dstFile); + } + + public static void UpdateFileTimeAttributes(string file) { + // Copy over date time attributes so we can check if the file changed. + FileInfo dstFileInfo = new FileInfo(file); + DateTime now = DateTime.Now; + DateTime utcNow = DateTime.UtcNow; + dstFileInfo.CreationTime = now; + dstFileInfo.CreationTimeUtc = utcNow; + dstFileInfo.LastWriteTime = now; + dstFileInfo.LastWriteTimeUtc = utcNow; + dstFileInfo.LastAccessTime = now; + dstFileInfo.LastAccessTimeUtc = utcNow; + } + + /// + /// True if the inFileInfo is newer than the outFileInfo. + /// + public static bool IsNewer(FileInfo inFileInfo, FileInfo outFileInfo) { + return !outFileInfo.Exists || outFileInfo.LastWriteTime <= inFileInfo.LastWriteTime; + } +} diff --git a/Nerfed.Builder/Builder/IImporter.cs b/Nerfed.Builder/Builder/IImporter.cs new file mode 100644 index 0000000..8d6f7f9 --- /dev/null +++ b/Nerfed.Builder/Builder/IImporter.cs @@ -0,0 +1,6 @@ +namespace Nerfed.Builder; + +public interface IImporter +{ + void Import(string inFile, string outFile); +} diff --git a/Nerfed.Builder/Builder/Importers/RawFileImporter.cs b/Nerfed.Builder/Builder/Importers/RawFileImporter.cs new file mode 100644 index 0000000..a750bf0 --- /dev/null +++ b/Nerfed.Builder/Builder/Importers/RawFileImporter.cs @@ -0,0 +1,9 @@ +namespace Nerfed.Builder; + +public class RawFileImporter : IImporter +{ + public void Import(string inFile, string outFile) + { + FileUtil.Copy(inFile, outFile); + } +} diff --git a/Nerfed.Builder/Builder/Importers/ShaderImporter.cs b/Nerfed.Builder/Builder/Importers/ShaderImporter.cs new file mode 100644 index 0000000..fbaa887 --- /dev/null +++ b/Nerfed.Builder/Builder/Importers/ShaderImporter.cs @@ -0,0 +1,33 @@ +using System.Diagnostics; + +namespace Nerfed.Builder; + +public class ShaderImporter : IImporter +{ + public void Import(string inFile, string outFile) + { + using (Process proc = new Process()) + { + string glslc; + if (OperatingSystem.IsWindows()) + { + glslc = "Win64/glslc.exe"; + } + else if (OperatingSystem.IsLinux()) + { + glslc = "Linux/glslc"; + } + else + { + throw new PlatformNotSupportedException("No shader compiler found for current platform"); + } + + proc.StartInfo.FileName = glslc; + proc.StartInfo.Arguments = @$"""{inFile}"" -o ""{outFile}"" -c"; + proc.StartInfo.CreateNoWindow = true; + proc.StartInfo.UseShellExecute = false; + proc.Start(); + proc.WaitForExit(); + } + } +} diff --git a/Nerfed.Builder/Nerfed.Builder.csproj b/Nerfed.Builder/Nerfed.Builder.csproj new file mode 100644 index 0000000..c7ea4d0 --- /dev/null +++ b/Nerfed.Builder/Nerfed.Builder.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + disable + false + + + + ../Tools + + + + ../Tools + + + diff --git a/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings b/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings new file mode 100644 index 0000000..799e870 --- /dev/null +++ b/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/Nerfed.Builder/Packager/PackageArgs.cs b/Nerfed.Builder/Packager/PackageArgs.cs new file mode 100644 index 0000000..9af4b54 --- /dev/null +++ b/Nerfed.Builder/Packager/PackageArgs.cs @@ -0,0 +1,6 @@ +namespace Nerfed.Builder; + +public class PackageArgs +{ + +} diff --git a/Nerfed.Builder/Packager/Packager.cs b/Nerfed.Builder/Packager/Packager.cs new file mode 100644 index 0000000..1bfdcae --- /dev/null +++ b/Nerfed.Builder/Packager/Packager.cs @@ -0,0 +1,12 @@ +namespace Nerfed.Builder; + +public class Packager : IDisposable +{ + public void Run(PackageArgs args) + { + } + + public void Dispose() + { + } +} diff --git a/Nerfed.Builder/PathUtil.cs b/Nerfed.Builder/PathUtil.cs new file mode 100644 index 0000000..10937b7 --- /dev/null +++ b/Nerfed.Builder/PathUtil.cs @@ -0,0 +1,9 @@ +namespace Nerfed.Builder; + +public static class PathUtil +{ + public const string ImportedFileExtension = ".bin"; + public const string ImportFileExtension = ".import"; + public const string ImportFolderName = "Import"; + public const string ContentFolderName = "Content"; +} diff --git a/Nerfed.Builder/Program.cs b/Nerfed.Builder/Program.cs new file mode 100644 index 0000000..bb45c88 --- /dev/null +++ b/Nerfed.Builder/Program.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; + +namespace Nerfed.Builder; + +internal class Program +{ + private static int Main(string[] args) + { + if (Debugger.IsAttached) + { + return Run(args); + } + else + { + try + { + return Run(args); + } + catch (Exception e) + { + Console.Error.WriteLine(e.Message); + return -1; + } + } + } + + private static int Run(string[] rawArgs) + { + if (rawArgs.Length == 0) + { + Console.Error.WriteLine($"Invalid build type '{rawArgs[0]}' expected '-build' or '-package'"); + return -1; + } + + string buildType = rawArgs[0].ToLower(); + if (buildType == "-build") + { + ArgsParser parser = new ArgsParser(rawArgs); + if (!parser.Parse()) + { + Console.Error.Write("Failed to parse build arguments"); + return -1; + } + + using (Builder builder = new Builder()) + { + builder.Run(parser.Arguments); + } + } + else if (buildType == "-package") + { + ArgsParser parser = new ArgsParser(rawArgs); + if (!parser.Parse()) + { + Console.Error.Write("Failed to parse package arguments"); + return -1; + } + + using (Packager packager = new Packager()) + { + packager.Run(parser.Arguments); + } + + Console.Error.WriteLine("Packaging not yet implemented"); + return -1; + } + + return 0; + } +} diff --git a/Nerfed.Runtime/Engine.cs b/Nerfed.Runtime/Engine.cs index b714d2c..876396d 100644 --- a/Nerfed.Runtime/Engine.cs +++ b/Nerfed.Runtime/Engine.cs @@ -54,7 +54,7 @@ internal static void Run(string[] args) { throw new Exception("Failed to init SDL"); } - + GraphicsDevice = new GraphicsDevice(BackendFlags.All); MainWindow = new Window(GraphicsDevice, new WindowCreateInfo(WindowTitle, WindowWidth, WindowHeight, ScreenMode.Windowed)); diff --git a/Nerfed.sln b/Nerfed.sln index be1920e..4565072 100644 --- a/Nerfed.sln +++ b/Nerfed.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.10.35013.160 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nerfed.Runtime", "Nerfed.Runtime\Nerfed.Runtime.csproj", "{98E09BAF-587F-4238-89BD-7693C036C233}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nerfed.Builder", "Nerfed.Builder\Nerfed.Builder.csproj", "{1B88DE56-2AD8-441E-9B10-073AA43840BF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {98E09BAF-587F-4238-89BD-7693C036C233}.Debug|Any CPU.Build.0 = Debug|Any CPU {98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.ActiveCfg = Release|Any CPU {98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.Build.0 = Release|Any CPU + {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tools/Linux/glslc b/Tools/Linux/glslc new file mode 100755 index 0000000..7b3737d Binary files /dev/null and b/Tools/Linux/glslc differ diff --git a/Tools/Nerfed.Builder b/Tools/Nerfed.Builder new file mode 100755 index 0000000..9758e62 Binary files /dev/null and b/Tools/Nerfed.Builder differ diff --git a/Tools/Nerfed.Builder.deps.json b/Tools/Nerfed.Builder.deps.json new file mode 100644 index 0000000..83b89f6 --- /dev/null +++ b/Tools/Nerfed.Builder.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Nerfed.Builder/1.0.0": { + "runtime": { + "Nerfed.Builder.dll": {} + } + } + } + }, + "libraries": { + "Nerfed.Builder/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/Tools/Nerfed.Builder.dll b/Tools/Nerfed.Builder.dll new file mode 100644 index 0000000..4ffefcd --- /dev/null +++ b/Tools/Nerfed.Builder.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6a3e03a68426fe28672e53f0a6df35e0e6282e391baf044f728d703fddfde9f +size 13824 diff --git a/Tools/Nerfed.Builder.runtimeconfig.json b/Tools/Nerfed.Builder.runtimeconfig.json new file mode 100644 index 0000000..becfaea --- /dev/null +++ b/Tools/Nerfed.Builder.runtimeconfig.json @@ -0,0 +1,12 @@ +{ + "runtimeOptions": { + "tfm": "net8.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "8.0.0" + }, + "configProperties": { + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/Tools/Win64/glslc.exe b/Tools/Win64/glslc.exe new file mode 100755 index 0000000..2d8207c --- /dev/null +++ b/Tools/Win64/glslc.exe @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1010f06ca8bfd332867d8e599309a50f9a947cd36dea000143a4f9816be70b98 +size 12196864