Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for handling internals #364

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions src/CodeGenerator/CodeGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
</Project>
40 changes: 27 additions & 13 deletions src/CodeGenerator/ImguiDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ static int GetInt(JToken token, string key)
if (v == null) return 0;
return v.ToObject<int>();
}
public void LoadFrom(string directory)
public void LoadFrom(string directory, bool useInternals = false)
{

JObject typesJson;
Expand Down Expand Up @@ -65,23 +65,27 @@ public void LoadFrom(string directory)
{
JProperty jp = (JProperty)jt;
string name = jp.Name;
if (typeLocations?[jp.Name]?.Value<string>().Contains("internal") ?? false) {
bool isInternal = typeLocations?[jp.Name]?.Value<string>().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<string>().Contains("internal") ?? false) {
bool isInternal = typeLocations?[jp.Name]?.Value<string>().Contains("internal") ?? false;

if (!useInternals && isInternal)
return null;
}

TypeReference[] fields = jp.Values().Select(v =>
{
if (v["type"].ToString().Contains("static")) { return null; }
Expand All @@ -94,7 +98,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 =>
Expand All @@ -120,7 +124,9 @@ public void LoadFrom(string directory)
}
}
if (friendlyName == null) { return null; }
if (val["location"]?.ToString().Contains("internal") ?? false) return null;
bool isInternal = val["location"]?.ToString().Contains("internal") ?? false;
if (!useInternals && isInternal)
return null;

string exportedName = ov_cimguiname;
if (exportedName == null)
Expand Down Expand Up @@ -184,7 +190,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);
Expand Down Expand Up @@ -231,8 +238,9 @@ class EnumDefinition
public string Name { get; }
public string FriendlyName { get; }
public EnumMember[] Members { get; }
public bool IsInternal { get; }

public EnumDefinition(string name, EnumMember[] elements)
public EnumDefinition(string name, EnumMember[] elements, bool isInternal)
{
Name = name;
if (Name.EndsWith('_'))
Expand All @@ -250,6 +258,7 @@ public EnumDefinition(string name, EnumMember[] elements)
{
_sanitizedNames.Add(el.Name, SanitizeMemberName(el.Name));
}
IsInternal = isInternal;
}

public string SanitizeNames(string text)
Expand Down Expand Up @@ -302,11 +311,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;
}
}

Expand Down Expand Up @@ -496,6 +507,7 @@ class OverloadDefinition
public string Comment { get; }
public bool IsConstructor { get; }
public bool IsDestructor { get; }
public bool IsInternal { get; }

public OverloadDefinition(
string exportedName,
Expand All @@ -506,7 +518,8 @@ public OverloadDefinition(
string structName,
string comment,
bool isConstructor,
bool isDestructor)
bool isDestructor,
bool isInternal)
{
ExportedName = exportedName;
FriendlyName = friendlyName;
Expand All @@ -518,11 +531,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);
}
}
}
176 changes: 121 additions & 55 deletions src/CodeGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,83 @@
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<int> Main(string[] args)
{
string outputPath;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure why github has decided to diff this in this way, but this is all new code apart from the declarations of the outputPath and libraryName variables which was moved up from below. You can see this code in raw format in this comment. This does a few things:

  1. Creates three parameters
  2. Adds them to the command line parser
  3. Runs the parser
  4. If the parser passes, it moves on to the normal logic you had in this file which now starts on line 89

https://github.com/mellinoe/ImGui.NET/blob/70a7b35459b260377b7487981b65839a02e3a8c6/src/CodeGenerator/Program.cs#L21-L87

if (args.Length > 0)
{
outputPath = args[0];
}
else
{
outputPath = AppContext.BaseDirectory;
}
// internal vars for command line results used by the rest of the program.
bool runApp = false;
string outputPath = string.Empty;
string libraryName = string.Empty;
bool useInternals = false;

if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
#region Command line handler
var optionOutputPath = new Option<DirectoryInfo>(
aliases: new[] { "--outputDir", "-o" },
description: "The directory to place generated code files.",
parseArgument: result =>
{
if (result.Tokens.Count == 0)
return new DirectoryInfo(AppContext.BaseDirectory);

string libraryName;
if (args.Length > 1)
{
libraryName = args[1];
}
else
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<string>(
aliases: new[] { "--library", "-l" },
description: "The library to read parse.",
getDefaultValue: () => "cimgui")
.FromAmong("cimgui", "cimplot", "cimnodes", "cimguizmo");

var optionInternal = new Option<bool>(
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) =>
{
libraryName = "cimgui";
}
outputPath = outputPathValue.FullName;
libraryName = libNameValue;
useInternals = useInternalValue;

runApp = true;

}, optionOutputPath, optionLibraryname, optionInternal);

var commandResult = await rootCommand.InvokeAsync(args);

if (!runApp)
return commandResult;

#endregion

string projectNamespace = libraryName switch
{
Expand Down Expand Up @@ -78,7 +124,7 @@ static void Main(string[] args)

string definitionsPath = Path.Combine(AppContext.BaseDirectory, "definitions", libraryName);
var defs = new ImguiDefinitions();
defs.LoadFrom(definitionsPath);
defs.LoadFrom(definitionsPath, useInternals);

Console.WriteLine($"Outputting generated code files to {outputPath}.");

Expand Down Expand Up @@ -118,7 +164,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)
Expand Down Expand Up @@ -284,6 +330,10 @@ static void Main(string[] args)
{
writer.Using("ImGuiNET");
}
if (useInternals)
{
writer.Using($"{projectNamespace}{InternalNamespace}");
}
writer.WriteLine(string.Empty);
writer.PushBlock($"namespace {projectNamespace}");
writer.PushBlock($"public static unsafe partial class {classPrefix}Native");
Expand Down Expand Up @@ -347,6 +397,7 @@ static void Main(string[] args)
writer.PopBlock();
}

// Root ImGui* class items - Noninternal
using (CSharpCodeWriter writer = new CSharpCodeWriter(Path.Combine(outputPath, $"{classPrefix}.gen.cs")))
{
writer.Using("System");
Expand All @@ -360,52 +411,65 @@ static void Main(string[] args)
writer.WriteLine(string.Empty);
writer.PushBlock($"namespace {projectNamespace}");
writer.PushBlock($"public static unsafe partial class {classPrefix}");
foreach (FunctionDefinition fd in defs.Functions)
EmitFunctions(false);
writer.PopBlock();
writer.PopBlock();

if (useInternals)
{
if (TypeInfo.SkippedFunctions.Contains(fd.Name)) { continue; }
writer.PushBlock($"namespace {projectNamespace}{InternalNamespace}");
writer.PushBlock($"public static unsafe partial class {classPrefix}");
EmitFunctions(true);
writer.PopBlock();
writer.PopBlock();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs the function overload processing logic twice, once for non-internals, to put things in the ImGuiNET.ImGui class, and then a second time for internals which go into the ImGuiNET.Internal.ImGui class.


foreach (OverloadDefinition overload in fd.Overloads)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was promoted to a local function so it could run twice, with the internal flag set or not. The code is the same except for line 433. Originally this method does a ton of processing on the member data only to do a final check, if it isn't a member function (and thus belongs on the ImGui class) then returns. I'm unsure why that was done at the end of all the logic, so I moved it here to just filter them out at the start. Now this loop ONLY processes non-member functions.

void EmitFunctions(bool isInternal)
{
foreach (FunctionDefinition fd in defs.Functions)
{
string exportedName = overload.ExportedName;
if (exportedName.StartsWith("ig"))
{
exportedName = exportedName.Substring(2, exportedName.Length - 2);
}
if (exportedName.Contains("~")) { continue; }
if (overload.Parameters.Any(tr => tr.Type.Contains('('))) { continue; } // TODO: Parse function pointer parameters.
if (TypeInfo.SkippedFunctions.Contains(fd.Name)) { continue; }

bool hasVaList = false;
for (int i = 0; i < overload.Parameters.Length; i++)
foreach (OverloadDefinition overload in fd.Overloads.Where(o => !o.IsMemberFunction && o.IsInternal == isInternal))
{
TypeReference p = overload.Parameters[i];
string paramType = GetTypeString(p.Type, p.IsFunctionPointer);
if (p.Name == "...") { continue; }
string exportedName = overload.ExportedName;
if (exportedName.StartsWith("ig"))
{
exportedName = exportedName.Substring(2, exportedName.Length - 2);
}
if (exportedName.Contains("~")) { continue; }
if (overload.Parameters.Any(tr => tr.Type.Contains('('))) { continue; } // TODO: Parse function pointer parameters.

if (paramType == "va_list")
bool hasVaList = false;
for (int i = 0; i < overload.Parameters.Length; i++)
{
hasVaList = true;
break;
TypeReference p = overload.Parameters[i];
string paramType = GetTypeString(p.Type, p.IsFunctionPointer);
if (p.Name == "...") { continue; }

if (paramType == "va_list")
{
hasVaList = true;
break;
}
}
}
if (hasVaList) { continue; }
if (hasVaList) { continue; }

KeyValuePair<string, string>[] orderedDefaults = overload.DefaultValues.OrderByDescending(
kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray();
KeyValuePair<string, string>[] orderedDefaults = overload.DefaultValues.OrderByDescending(
kvp => GetIndex(overload.Parameters, kvp.Key)).ToArray();

for (int i = overload.DefaultValues.Count; i >= 0; i--)
{
if (overload.IsMemberFunction) { continue; }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the line I removed and added to the main loop to filter out member functions.

Dictionary<string, string> defaults = new Dictionary<string, string>();
for (int j = 0; j < i; j++)
for (int i = overload.DefaultValues.Count; i >= 0; i--)
{
defaults.Add(orderedDefaults[j].Key, orderedDefaults[j].Value);
Dictionary<string, string> defaults = new Dictionary<string, string>();
for (int j = 0; j < i; j++)
{
defaults.Add(orderedDefaults[j].Key, orderedDefaults[j].Value);
}
EmitOverload(writer, overload, defaults, null, classPrefix);
}
EmitOverload(writer, overload, defaults, null, classPrefix);
}
}
}
writer.PopBlock();
writer.PopBlock();
}

foreach (var method in defs.Variants)
Expand All @@ -415,6 +479,8 @@ 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;
}

private static bool IsStringFieldName(string name)
Expand Down
Loading