diff --git a/src/CodeGenerator/CodeGenerator.csproj b/src/CodeGenerator/CodeGenerator.csproj index 693bb04a..0781663c 100644 --- a/src/CodeGenerator/CodeGenerator.csproj +++ b/src/CodeGenerator/CodeGenerator.csproj @@ -24,6 +24,7 @@ - + + diff --git a/src/CodeGenerator/ImguiDefinitions.cs b/src/CodeGenerator/ImguiDefinitions.cs index 23edaee5..337f8d0b 100644 --- a/src/CodeGenerator/ImguiDefinitions.cs +++ b/src/CodeGenerator/ImguiDefinitions.cs @@ -22,7 +22,7 @@ static int GetInt(JToken token, string key) if (v == null) return 0; return v.ToObject(); } - public void LoadFrom(string directory) + public void LoadFrom(string directory, bool useInternals = false) { JObject typesJson; @@ -66,23 +66,27 @@ public void LoadFrom(string directory) { JProperty jp = (JProperty)jt; string name = jp.Name; - if (typeLocations?[jp.Name]?.Value().Contains("internal") ?? false) { + bool isInternal = typeLocations?[jp.Name]?.Value().Contains("internal") ?? false; + + if (!useInternals && isInternal) return null; - } + EnumMember[] elements = jp.Values().Select(v => { return new EnumMember(v["name"].ToString(), v["calc_value"].ToString()); }).ToArray(); - return new EnumDefinition(name, elements); + return new EnumDefinition(name, elements, isInternal); }).Where(x => x != null).ToArray(); Types = typesJson["structs"].Select(jt => { JProperty jp = (JProperty)jt; string name = jp.Name; - if (typeLocations?[jp.Name]?.Value().Contains("internal") ?? false) { + bool isInternal = typeLocations?[jp.Name]?.Value().Contains("internal") ?? false; + + if (!useInternals && isInternal) return null; - } + TypeReference[] fields = jp.Values().Select(v => { if (v["type"].ToString().Contains("static")) { return null; } @@ -95,7 +99,7 @@ public void LoadFrom(string directory) v["template_type"]?.ToString(), Enums); }).Where(tr => tr != null).ToArray(); - return new TypeDefinition(name, fields); + return new TypeDefinition(name, fields, isInternal); }).Where(x => x != null).ToArray(); Functions = functionsJson.Children().Select(jt => @@ -112,16 +116,22 @@ public void LoadFrom(string directory) { friendlyName = "Destroy"; } - //skip internal functions + // Hunt for internal and react + bool isInternal = val["location"]?.ToString().Contains("internal") ?? false; var typename = val["stname"]?.ToString(); if (!string.IsNullOrEmpty(typename)) { - if (!Types.Any(x => x.Name == val["stname"]?.ToString())) { + TypeDefinition foundType = Types.FirstOrDefault(x => x.Name == val["stname"]?.ToString()); + + if (foundType != null) + isInternal = foundType.IsInternal; + else return null; - } } if (friendlyName == null) { return null; } - if (val["location"]?.ToString().Contains("internal") ?? false) return null; + + if (!useInternals && isInternal) + return null; string exportedName = ov_cimguiname; if (exportedName == null) @@ -185,7 +195,8 @@ public void LoadFrom(string directory) structName, comment, isConstructor, - isDestructor); + isDestructor, + isInternal); }).Where(od => od != null).ToArray(); if(overloads.Length == 0) return null; return new FunctionDefinition(name, overloads, Enums); @@ -232,8 +243,9 @@ class EnumDefinition public string[] Names { get; } public string[] FriendlyNames { get; } public EnumMember[] Members { get; } + public bool IsInternal { get; } - public EnumDefinition(string name, EnumMember[] elements) + public EnumDefinition(string name, EnumMember[] elements, bool isInternal) { if (TypeInfo.AlternateEnumPrefixes.TryGetValue(name, out string altName)) { @@ -265,6 +277,7 @@ public EnumDefinition(string name, EnumMember[] elements) { _sanitizedNames.Add(el.Name, SanitizeMemberName(el.Name)); } + IsInternal = isInternal; } public string SanitizeNames(string text) @@ -336,11 +349,13 @@ class TypeDefinition { public string Name { get; } public TypeReference[] Fields { get; } + public bool IsInternal { get; } - public TypeDefinition(string name, TypeReference[] fields) + public TypeDefinition(string name, TypeReference[] fields, bool isInternal) { Name = name; Fields = fields; + IsInternal = isInternal; } } @@ -530,6 +545,7 @@ class OverloadDefinition public string Comment { get; } public bool IsConstructor { get; } public bool IsDestructor { get; } + public bool IsInternal { get; } public OverloadDefinition( string exportedName, @@ -540,7 +556,8 @@ public OverloadDefinition( string structName, string comment, bool isConstructor, - bool isDestructor) + bool isDestructor, + bool isInternal) { ExportedName = exportedName; FriendlyName = friendlyName; @@ -552,11 +569,12 @@ public OverloadDefinition( Comment = comment; IsConstructor = isConstructor; IsDestructor = isDestructor; + IsInternal = isInternal; } public OverloadDefinition WithParameters(TypeReference[] parameters) { - return new OverloadDefinition(ExportedName, FriendlyName, parameters, DefaultValues, ReturnType, StructName, Comment, IsConstructor, IsDestructor); + return new OverloadDefinition(ExportedName, FriendlyName, parameters, DefaultValues, ReturnType, StructName, Comment, IsConstructor, IsDestructor, IsInternal); } } } diff --git a/src/CodeGenerator/Program.cs b/src/CodeGenerator/Program.cs index 8597a718..1645bd4d 100644 --- a/src/CodeGenerator/Program.cs +++ b/src/CodeGenerator/Program.cs @@ -8,37 +8,85 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.CommandLine; namespace CodeGenerator { internal static class Program { - static void Main(string[] args) + private const string InternalNamespace = ".Internal"; + + static async Task Main(string[] args) { - string outputPath; - if (args.Length > 0) + // internal vars for command line results used by the rest of the program. + bool runApp = false; + string outputPath = string.Empty; + string outputPathInternal = string.Empty; + string libraryName = string.Empty; + bool useInternals = false; + + #region Command line handler + var optionOutputPath = new Option( + aliases: new[] { "--outputDir", "-o" }, + description: "The directory to place generated code files.", + parseArgument: result => + { + if (result.Tokens.Count == 0) + return Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, "Generated")); + + string value = result.Tokens.Single().Value; + + try { return Directory.CreateDirectory(value); } + catch (Exception) { result.ErrorMessage = $"Unable to create directory: {value}"; return null; } + }, + isDefault: true); + + var optionLibraryname = new Option( + aliases: new[] { "--library", "-l" }, + description: "The library to read parse.", + getDefaultValue: () => "cimgui") + .FromAmong("cimgui", "cimplot", "cimnodes", "cimguizmo"); + + var optionInternal = new Option( + name: "--internal", + description: "When set to true, includes the internal header file.", + parseArgument: result => + { + // Using parse with isDefault: false, instead of the normal validation, allows us to use "--internal" without specifying true to mean true. + if (result.Tokens.Count == 0) + return true; + + if (bool.TryParse(result.Tokens.Single().Value, out var value)) + return value; + + result.ErrorMessage = "Invalid option for --internal. Value must be true or false."; + return false; // ignored because of error message. + }, + isDefault: false); + + var rootCommand = new RootCommand("Generates code for the ImGui.NET libraries based on the cimgui definition files."); + + rootCommand.AddOption(optionInternal); + rootCommand.AddOption(optionOutputPath); + rootCommand.AddOption(optionLibraryname); + + rootCommand.SetHandler((outputPathValue, libNameValue, useInternalValue) => { - outputPath = args[0]; - } - else - { - outputPath = AppContext.BaseDirectory; - } + outputPath = outputPathValue.FullName; + outputPathInternal = Path.Combine(outputPath, "Internal"); + libraryName = libNameValue; + useInternals = useInternalValue; - if (!Directory.Exists(outputPath)) - { - Directory.CreateDirectory(outputPath); - } + runApp = true; - string libraryName; - if (args.Length > 1) - { - libraryName = args[1]; - } - else - { - libraryName = "cimgui"; - } + }, optionOutputPath, optionLibraryname, optionInternal); + + var commandResult = await rootCommand.InvokeAsync(args); + + if (!runApp) + return commandResult; + + #endregion string projectNamespace = libraryName switch { @@ -78,15 +126,22 @@ static void Main(string[] args) string definitionsPath = Path.Combine(AppContext.BaseDirectory, "definitions", libraryName); var defs = new ImguiDefinitions(); - defs.LoadFrom(definitionsPath); + defs.LoadFrom(definitionsPath, useInternals); + // Directory should be created by the optionOutputPath command parser Console.WriteLine($"Outputting generated code files to {outputPath}."); + + if (useInternals) + { + Console.WriteLine($"Outputting internals generated code files to {outputPathInternal}."); + Directory.CreateDirectory(outputPathInternal); + } foreach (EnumDefinition ed in defs.Enums) { - using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, ed.FriendlyNames[0] + ".gen.cs"))) + using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(GetOutputPath(ed.IsInternal), ed.FriendlyNames[0] + ".gen.cs"))) { - writer.PushBlock($"namespace {projectNamespace}"); + writer.PushBlock($"namespace {projectNamespace}{(ed.IsInternal ? InternalNamespace : string.Empty)}"); if (ed.FriendlyNames[0].Contains("Flags")) { writer.WriteLine("[System.Flags]"); @@ -107,7 +162,7 @@ static void Main(string[] args) { if (TypeInfo.CustomDefinedTypes.Contains(td.Name)) { continue; } - using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, td.Name + ".gen.cs"))) + using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(GetOutputPath(td.IsInternal), td.Name + ".gen.cs"))) { writer.Using("System"); writer.Using("System.Numerics"); @@ -118,7 +173,7 @@ static void Main(string[] args) writer.Using("ImGuiNET"); } writer.WriteLine(string.Empty); - writer.PushBlock($"namespace {projectNamespace}"); + writer.PushBlock($"namespace {projectNamespace}{(td.IsInternal ? InternalNamespace : string.Empty)}"); writer.PushBlock($"public unsafe partial struct {td.Name}"); foreach (TypeReference field in td.Fields) @@ -160,6 +215,8 @@ static void Main(string[] args) string typeStr = GetTypeString(field.Type, field.IsFunctionPointer); string rawType = typeStr; + if (TypeInfo.SkippedMembers.Contains($"{ptrTypeName}.{field.Name}")) { continue; } + if (TypeInfo.WellKnownFieldReplacements.TryGetValue(field.Type, out string wellKnownFieldType)) { typeStr = wellKnownFieldType; @@ -168,7 +225,22 @@ static void Main(string[] args) if (field.ArraySize != 0) { string addrTarget = TypeInfo.LegalFixedTypes.Contains(rawType) ? $"NativePtr->{field.Name}" : $"&NativePtr->{field.Name}_0"; - writer.WriteLine($"public RangeAccessor<{typeStr}> {field.Name} => new RangeAccessor<{typeStr}>({addrTarget}, {field.ArraySize});"); + if (typeStr.Contains("*")) + { + if (GetWrappedType(typeStr, out string wrappedTypeName)) + { + writer.WriteLine($"public RangeAccessor<{wrappedTypeName}> {field.Name} => new RangeAccessor<{wrappedTypeName}>({addrTarget}, {field.ArraySize});"); + } + + // Try best to hit the primitive pointers + else if (TypeInfo.LegalFixedTypes.Contains(typeStr[0..^1])) + writer.WriteLine($"public RangeAccessor<{typeStr[0..^1]}> {field.Name} => new RangeAccessor<{typeStr[0..^1]}>({addrTarget}, {field.ArraySize});"); + + else + throw new Exception("Expected to wrap type, but wrapped type not found"); + } + else + writer.WriteLine($"public RangeAccessor<{typeStr}> {field.Name} => new RangeAccessor<{typeStr}>({addrTarget}, {field.ArraySize});"); } else if (typeStr.Contains("ImVector")) { @@ -275,6 +347,7 @@ static void Main(string[] args) } } + // Root ImGuiNative declarations - NonInternal using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, $"{classPrefix}Native.gen.cs"))) { writer.Using("System"); @@ -287,10 +360,41 @@ static void Main(string[] args) writer.WriteLine(string.Empty); writer.PushBlock($"namespace {projectNamespace}"); writer.PushBlock($"public static unsafe partial class {classPrefix}Native"); + EmitImGuiNativeFunctions(writer, false); + writer.PopBlock(); + writer.PopBlock(); + } + + // Root ImGuiNative declarations - Internal + if (useInternals) + { + using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPathInternal, $"{classPrefix}Native.gen.cs"))) + { + writer.Using("System"); + writer.Using("System.Numerics"); + writer.Using("System.Runtime.InteropServices"); + if (referencesImGui) + { + writer.Using("ImGuiNET"); + } + writer.Using($"{projectNamespace}{InternalNamespace}"); + writer.WriteLine(string.Empty); + writer.PushBlock($"namespace {projectNamespace}"); + writer.PushBlock($"public static unsafe partial class {classPrefix}Native"); + EmitImGuiNativeFunctions(writer, true); + writer.PopBlock(); + writer.PopBlock(); + } + } + + // Function referenced by the non-internals and internals when building the appropriate ImGuiNative declarations + void EmitImGuiNativeFunctions(CSharpCodeWriter writer, bool isInternal) + { foreach (FunctionDefinition fd in defs.Functions) { foreach (OverloadDefinition overload in fd.Overloads) { + if (overload.IsInternal != isInternal) { continue; } string exportedName = overload.ExportedName; if (exportedName.Contains("~")) { continue; } if (exportedName.Contains("ImVector_")) { continue; } @@ -343,10 +447,9 @@ static void Main(string[] args) writer.WriteLine($"public static extern {ret} {methodName}({parameters});"); } } - writer.PopBlock(); - writer.PopBlock(); } + // Root ImGui* class items - Noninternal using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, $"{classPrefix}.gen.cs"))) { writer.Using("System"); @@ -360,11 +463,41 @@ static void Main(string[] args) writer.WriteLine(string.Empty); writer.PushBlock($"namespace {projectNamespace}"); writer.PushBlock($"public static unsafe partial class {classPrefix}"); + EmitImGuiFunctions(writer, false); + writer.PopBlock(); + writer.PopBlock(); + } + + // Root ImGui* class items - Internal + if (useInternals) + { + using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPathInternal, $"{classPrefix}.gen.cs"))) + { + writer.Using("System"); + writer.Using("System.Numerics"); + writer.Using("System.Runtime.InteropServices"); + writer.Using("System.Text"); + if (referencesImGui) + { + writer.Using("ImGuiNET"); + } + writer.WriteLine(string.Empty); + writer.PushBlock($"namespace {projectNamespace}{InternalNamespace}"); + writer.PushBlock($"public static unsafe partial class {classPrefix}"); + EmitImGuiFunctions(writer, true); + writer.PopBlock(); + writer.PopBlock(); + } + } + + // Function referenced by the non-internals and internals when building the appropriate ImGui class + void EmitImGuiFunctions(CSharpCodeWriter writer, bool isInternal) + { foreach (FunctionDefinition fd in defs.Functions) { if (TypeInfo.SkippedFunctions.Contains(fd.Name)) { continue; } - foreach (OverloadDefinition overload in fd.Overloads) + foreach (OverloadDefinition overload in fd.Overloads.Where(o => !o.IsMemberFunction && o.IsInternal == isInternal)) { string exportedName = overload.ExportedName; if (exportedName.StartsWith("ig")) @@ -373,13 +506,14 @@ static void Main(string[] args) } if (exportedName.Contains("~")) { continue; } if (overload.Parameters.Any(tr => tr.Type.Contains('('))) { continue; } // TODO: Parse function pointer parameters. - + if ((overload.FriendlyName == "GetID" || overload.FriendlyName == "PushID") && overload.Parameters.Length > 1) { // skip ImGui.Get/PushID(start, end) overloads as they would overlap with existing continue; } + bool hasVaList = false; for (int i = 0; i < overload.Parameters.Length; i++) { @@ -400,7 +534,6 @@ static void Main(string[] args) for (int i = overload.DefaultValues.Count; i >= 0; i--) { - if (overload.IsMemberFunction) { continue; } Dictionary defaults = new Dictionary(); for (int j = 0; j < i; j++) { @@ -410,8 +543,6 @@ static void Main(string[] args) } } } - writer.PopBlock(); - writer.PopBlock(); } foreach (var method in defs.Variants) @@ -421,6 +552,14 @@ static void Main(string[] args) if (!variant.Used) Console.WriteLine($"Error: Variants targetting parameter {variant.Name} with type {variant.OriginalType} could not be applied to method {method.Key}."); } } + + return 0; + + // Helper to determine the path + string GetOutputPath(bool isInternal) + { + return isInternal ? outputPathInternal : outputPath; + } } private static bool IsStringFieldName(string name) diff --git a/src/CodeGenerator/Properties/launchSettings.json b/src/CodeGenerator/Properties/launchSettings.json new file mode 100644 index 00000000..fdb2be9e --- /dev/null +++ b/src/CodeGenerator/Properties/launchSettings.json @@ -0,0 +1,36 @@ +{ + "profiles": { + "CodeGenerator - ImGui": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImGui.NET\\Generated -l cimgui" + }, + "CodeGenerator - ImPlot": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImPlot.NET\\Generated -l cimplot" + }, + "CodeGenerator - ImNodes": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImNodes.NET\\Generated -l cimnodes" + }, + "CodeGenerator - ImGuizmo": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImGuizmo.NET\\Generated -l cimguizmo" + }, + "CodeGenerator (internal) - ImGui": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImGui.NET\\Generated -l cimgui --internal" + }, + "CodeGenerator (internal) - ImPlot": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImPlot.NET\\Generated -l cimplot --internal" + }, + "CodeGenerator (internal) - ImNodes": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImNodes.NET\\Generated -l cimnodes --internal" + }, + "CodeGenerator (internal) - ImGuizmo": { + "commandName": "Project", + "commandLineArgs": "-o ..\\..\\..\\..\\src\\ImGuizmo.NET\\Generated -l cimguizmo --internal" + } + } +} \ No newline at end of file diff --git a/src/CodeGenerator/TypeInfo.cs b/src/CodeGenerator/TypeInfo.cs index 45c89300..0b4194aa 100644 --- a/src/CodeGenerator/TypeInfo.cs +++ b/src/CodeGenerator/TypeInfo.cs @@ -22,6 +22,7 @@ public class TypeInfo { "ImS64", "long" }, { "unsigned short", "ushort" }, { "unsigned int", "uint" }, + { "ImVec1", "float" }, { "ImVec2", "Vector2" }, { "ImVec2_Simple", "Vector2" }, { "ImVec3", "Vector3" }, @@ -56,11 +57,25 @@ public class TypeInfo { "ImPlotGetter", "IntPtr" }, { "ImPlotTransform", "IntPtr" }, { "ImGuiKeyChord", "ImGuiKey" }, + // internals + { "char[5]", "byte*"}, + { "ImGuiDir*", "IntPtr" }, + //{ "ImGuiStoragePair", "IntPtr" }, + { "ImGuiDockRequest", "IntPtr" }, + { "ImGuiDockNodeSettings", "IntPtr" }, + { "ImGuiTableColumnIdx", "sbyte" }, + { "ImGuiTableDrawChannelIdx", "byte"}, + { "ImGuiContextHookCallback", "IntPtr" }, + { "ImGuiErrorLogCallback", "IntPtr" }, + { "ImGuiSelectionUserData", "ulong" }, //ImU64 + { "ImGuiKeyRoutingIndex", "short" } //ImS16 + //{ "ImGuiSizeCallback", "IntPtr"} }; public static readonly List WellKnownEnums = new List() { - "ImGuiMouseButton" + "ImGuiMouseButton", + "ImGuiDir" }; public static readonly Dictionary AlternateEnumPrefixes = new Dictionary() @@ -70,7 +85,7 @@ public class TypeInfo public static readonly Dictionary AlternateEnumPrefixSubstitutions = new Dictionary() { - { "ImGuiMod_", "Mod" }, + { "ImGuiMod_", "Mod" } }; public static readonly Dictionary WellKnownFieldReplacements = new Dictionary() @@ -81,6 +96,7 @@ public class TypeInfo public static readonly HashSet CustomDefinedTypes = new HashSet() { "ImVector", + "ImVec1", "ImVec2", "ImVec4", "ImGuiStoragePair", @@ -105,10 +121,11 @@ public class TypeInfo { "ImPlotPoint(0,0)", "new ImPlotPoint { x = 0, y = 0 }" }, { "ImPlotPoint(1,1)", "new ImPlotPoint { x = 1, y = 1 }" }, { "ImDrawCornerFlags_All", "ImDrawCornerFlags.All" }, + { "ImGuiCond_Once", "ImGuiCond.Once"}, + { "ImGuiTypingSelectFlags_None", "ImGuiTypingSelectFlags.None" }, { "ImPlotFlags_None", "ImPlotFlags.None"}, { "ImPlotAxisFlags_None", "ImPlotAxisFlags.None"}, { "ImPlotAxisFlags_NoGridLines", "ImPlotAxisFlags.NoGridLines"}, - { "ImGuiCond_Once", "ImGuiCond.Once"}, { "ImPlotOrientation_Vertical", "ImPlotOrientation.Vertical"}, { "PinShape_CircleFilled", "PinShape.CircleFilled"}, { "ImGuiPopupFlags_None", "ImGuiPopupFlags.None"}, @@ -137,6 +154,7 @@ public class TypeInfo { "in", "@in" }, { "out", "@out" }, { "ref", "@ref" }, + { "base", "@base" } }; public static readonly HashSet LegalFixedTypes = new HashSet() @@ -158,7 +176,16 @@ public class TypeInfo { "igInputText", "igInputTextMultiline", - "igInputTextWithHint" + "igInputTextWithHint", + }; + + public static readonly HashSet SkippedMembers = new HashSet() + { + // This can be used to rip members out of internal when you simply want to exclude them. + // You must be careful when using this to not mess with data structures that are used + // by ImGui + // Add an entry for the type.name, such as: + // "ImGuiContextPtr.StyleVarStack" }; } } \ No newline at end of file