forked from Misaki/GhostEngine
Added ufbx warper
This commit is contained in:
108
src/Tools/Ghost.NativeWrapperGen/Config/WrapperConfig.cs
Normal file
108
src/Tools/Ghost.NativeWrapperGen/Config/WrapperConfig.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace Ghost.NativeWrapperGen.Config;
|
||||
|
||||
public sealed class WrapperConfig
|
||||
{
|
||||
public required string LibraryName { get; init; }
|
||||
public required string NativeNamespace { get; init; }
|
||||
public required string WrapperNamespace { get; init; }
|
||||
public required string NativeTypePrefix { get; init; }
|
||||
public string? StaticApiClassName { get; init; }
|
||||
public List<string> SkipTypes { get; init; } = [];
|
||||
public Dictionary<string, string> TypeNameOverrides { get; init; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, string> PublicTypeOverrides { get; init; } = new(StringComparer.Ordinal);
|
||||
public WrapperKindsConfig Wrappers { get; init; } = new();
|
||||
public SpecialTypesConfig SpecialTypes { get; init; } = new();
|
||||
public List<OwnedTypeConfig> OwnedTypes { get; init; } = [];
|
||||
/// <summary>
|
||||
/// Manual overrides for specific functions (adapters, special parameters, etc.).
|
||||
/// Functions not listed here are auto-routed by the 3-rule dispatch logic.
|
||||
/// </summary>
|
||||
public List<StaticMethodConfig> StaticMethods { get; init; } = [];
|
||||
public List<MarshalledTypeConfig> MarshalledTypes { get; init; } = [];
|
||||
public List<string> PartialTypes { get; init; } = [];
|
||||
/// <summary>
|
||||
/// Native types that are treated as plain value types in wrapper land (not wrapped in a pointer struct).
|
||||
/// Functions returning or taking these as non-pointer params pass them by value.
|
||||
/// </summary>
|
||||
public List<string> SkipFunctionPrefixes { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class WrapperKindsConfig
|
||||
{
|
||||
public string DefaultKind { get; init; } = "struct";
|
||||
public string DefaultOwnedKind { get; init; } = "class";
|
||||
public Dictionary<string, string> Kinds { get; init; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public sealed class SpecialTypesConfig
|
||||
{
|
||||
public List<StringTypeConfig> Strings { get; init; } = [];
|
||||
public List<BlobTypeConfig> Blobs { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class StringTypeConfig
|
||||
{
|
||||
public required string Type { get; init; }
|
||||
public string DataField { get; init; } = "data";
|
||||
public string LengthField { get; init; } = "length";
|
||||
public int CharSize { get; init; } = 8;
|
||||
public string Encoding { get; init; } = "utf8";
|
||||
public bool EmitRawSpanProperty { get; init; } = true;
|
||||
public bool EmitStringProperty { get; init; } = true;
|
||||
}
|
||||
|
||||
public sealed class BlobTypeConfig
|
||||
{
|
||||
public required string Type { get; init; }
|
||||
public string DataField { get; init; } = "data";
|
||||
public string LengthField { get; init; } = "size";
|
||||
public string ElementType { get; init; } = "byte";
|
||||
}
|
||||
|
||||
public sealed class OwnedTypeConfig
|
||||
{
|
||||
public required string NativeType { get; init; }
|
||||
public string? FreeFunction { get; init; }
|
||||
public string? RetainFunction { get; init; }
|
||||
public string? WrapperKind { get; init; }
|
||||
public string? StaticType { get; init; }
|
||||
}
|
||||
|
||||
public sealed class StaticMethodConfig
|
||||
{
|
||||
public required string NativeFunction { get; init; }
|
||||
public string? MethodName { get; init; }
|
||||
public string? StaticType { get; init; }
|
||||
public bool ThrowOnNullReturn { get; init; }
|
||||
public string? FailureMessageMember { get; init; }
|
||||
public List<StaticParameterConfig> Parameters { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class StaticParameterConfig
|
||||
{
|
||||
public required string Native { get; init; }
|
||||
public required string Adapter { get; init; }
|
||||
public string? PublicName { get; init; }
|
||||
public string? Type { get; init; }
|
||||
public string? Source { get; init; }
|
||||
public bool OptionalDefault { get; init; }
|
||||
}
|
||||
|
||||
public sealed class MarshalledTypeConfig
|
||||
{
|
||||
public required string NativeType { get; init; }
|
||||
/// <summary>
|
||||
/// Fields that need special (user-implemented partial) marshalling logic.
|
||||
/// The generator emits a backing field + partial property stub for these;
|
||||
/// the Dispose() partial stub is always emitted so the user can free them.
|
||||
/// </summary>
|
||||
public List<MarshalledPropertyConfig> MarshalledProperties { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed class MarshalledPropertyConfig
|
||||
{
|
||||
/// <summary>Native field name (snake_case).</summary>
|
||||
public required string Native { get; init; }
|
||||
/// <summary>The C# wrapper type for this field (e.g. "cstring").</summary>
|
||||
public required string Type { get; init; }
|
||||
}
|
||||
44
src/Tools/Ghost.NativeWrapperGen/Emit/CodeWriter.cs
Normal file
44
src/Tools/Ghost.NativeWrapperGen/Emit/CodeWriter.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Ghost.NativeWrapperGen.Emit;
|
||||
|
||||
internal sealed class CodeWriter
|
||||
{
|
||||
private readonly StringBuilder _builder = new();
|
||||
private int _indent;
|
||||
|
||||
public void WriteLine(string line = "")
|
||||
{
|
||||
if (line.Length == 0)
|
||||
{
|
||||
_builder.AppendLine();
|
||||
return;
|
||||
}
|
||||
|
||||
_builder.Append(' ', _indent * 4);
|
||||
_builder.AppendLine(line);
|
||||
}
|
||||
|
||||
public IDisposable IndentScope()
|
||||
{
|
||||
_indent++;
|
||||
return new Scope(this);
|
||||
}
|
||||
|
||||
public override string ToString() => _builder.ToString();
|
||||
|
||||
private sealed class Scope : IDisposable
|
||||
{
|
||||
private readonly CodeWriter _writer;
|
||||
|
||||
public Scope(CodeWriter writer)
|
||||
{
|
||||
_writer = writer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_writer._indent--;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/Tools/Ghost.NativeWrapperGen/Emit/GeneratedFile.cs
Normal file
7
src/Tools/Ghost.NativeWrapperGen/Emit/GeneratedFile.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Ghost.NativeWrapperGen.Emit;
|
||||
|
||||
public sealed class GeneratedFile
|
||||
{
|
||||
public required string FileName { get; init; }
|
||||
public required string Content { get; init; }
|
||||
}
|
||||
1090
src/Tools/Ghost.NativeWrapperGen/Emit/WrapperGeneratorEmitter.cs
Normal file
1090
src/Tools/Ghost.NativeWrapperGen/Emit/WrapperGeneratorEmitter.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
55
src/Tools/Ghost.NativeWrapperGen/Model/NativeLibrary.cs
Normal file
55
src/Tools/Ghost.NativeWrapperGen/Model/NativeLibrary.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace Ghost.NativeWrapperGen.Model;
|
||||
|
||||
public sealed class NativeLibrary
|
||||
{
|
||||
public required string NativeNamespace { get; init; }
|
||||
public required IReadOnlyList<NativeStruct> Structs { get; init; }
|
||||
public required IReadOnlyList<NativeEnum> Enums { get; init; }
|
||||
public required IReadOnlyList<NativeFunction> Functions { get; init; }
|
||||
public required IReadOnlyDictionary<string, NativeStruct> StructsByName { get; init; }
|
||||
public required IReadOnlyDictionary<string, NativeFunction> FunctionsByName { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeStruct
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Namespace { get; init; }
|
||||
public required IReadOnlyList<NativeMember> Members { get; init; }
|
||||
public required bool IsList { get; init; }
|
||||
public required bool IsPointerList { get; init; }
|
||||
public string? ListElementType { get; init; }
|
||||
public required bool IsElementLike { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeEnum
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required IReadOnlyList<string> Members { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeFunction
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string ReturnType { get; init; }
|
||||
public required IReadOnlyList<NativeParameter> Parameters { get; init; }
|
||||
public required bool IsDllImport { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeParameter
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string TypeName { get; init; }
|
||||
}
|
||||
|
||||
public sealed class NativeMember
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string TypeName { get; init; }
|
||||
public required NativeMemberKind Kind { get; init; }
|
||||
}
|
||||
|
||||
public enum NativeMemberKind
|
||||
{
|
||||
Field,
|
||||
Property,
|
||||
}
|
||||
211
src/Tools/Ghost.NativeWrapperGen/Parsing/BindingParser.cs
Normal file
211
src/Tools/Ghost.NativeWrapperGen/Parsing/BindingParser.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using Ghost.NativeWrapperGen.Config;
|
||||
using Ghost.NativeWrapperGen.Model;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Ghost.NativeWrapperGen.Parsing;
|
||||
|
||||
public sealed class BindingParser
|
||||
{
|
||||
public NativeLibrary Parse(string inputDirectory, WrapperConfig config)
|
||||
{
|
||||
var structs = new List<NativeStruct>();
|
||||
var enums = new List<NativeEnum>();
|
||||
var functions = new List<NativeFunction>();
|
||||
|
||||
foreach (var filePath in Directory.GetFiles(inputDirectory, "*.cs", SearchOption.TopDirectoryOnly).OrderBy(static p => p, StringComparer.Ordinal))
|
||||
{
|
||||
var text = File.ReadAllText(filePath);
|
||||
var tree = CSharpSyntaxTree.ParseText(text);
|
||||
var root = tree.GetRoot();
|
||||
var namespaceName = GetNamespace(root);
|
||||
|
||||
foreach (var @struct in root.DescendantNodes().OfType<StructDeclarationSyntax>())
|
||||
{
|
||||
if (@struct.Parent is not NamespaceDeclarationSyntax && @struct.Parent is not FileScopedNamespaceDeclarationSyntax)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.SkipTypes.Contains(@struct.Identifier.ValueText, StringComparer.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var members = ParseMembers(@struct);
|
||||
var listInfo = TryMatchList(members);
|
||||
|
||||
structs.Add(new NativeStruct
|
||||
{
|
||||
Name = @struct.Identifier.ValueText,
|
||||
Namespace = namespaceName,
|
||||
Members = members,
|
||||
IsList = listInfo.IsList,
|
||||
IsPointerList = listInfo.IsPointerList,
|
||||
ListElementType = listInfo.ListElementType,
|
||||
IsElementLike = members.Any(static m => m.Name == "element" && m.TypeName == "ufbx_element"),
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var @enum in root.DescendantNodes().OfType<EnumDeclarationSyntax>())
|
||||
{
|
||||
if (@enum.Parent is not NamespaceDeclarationSyntax && @enum.Parent is not FileScopedNamespaceDeclarationSyntax)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.SkipTypes.Contains(@enum.Identifier.ValueText, StringComparer.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
enums.Add(new NativeEnum
|
||||
{
|
||||
Name = @enum.Identifier.ValueText,
|
||||
Members = @enum.Members.Select(static m => m.Identifier.ValueText).ToArray(),
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var classDeclaration in root.DescendantNodes().OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
if (classDeclaration.Identifier.ValueText != "Api")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in classDeclaration.Members.OfType<MethodDeclarationSyntax>())
|
||||
{
|
||||
functions.Add(new NativeFunction
|
||||
{
|
||||
Name = method.Identifier.ValueText,
|
||||
ReturnType = NormalizeType(method.ReturnType.ToString()),
|
||||
Parameters = method.ParameterList.Parameters.Select(static p => new NativeParameter
|
||||
{
|
||||
Name = p.Identifier.ValueText,
|
||||
TypeName = NormalizeType(p.Type?.ToString() ?? "void"),
|
||||
}).ToArray(),
|
||||
IsDllImport = method.AttributeLists.SelectMany(static a => a.Attributes).Any(static a => a.Name.ToString().Contains("DllImport", StringComparison.Ordinal)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var structsByName = structs.ToDictionary(static s => s.Name, StringComparer.Ordinal);
|
||||
var functionsByName = functions.GroupBy(static f => f.Name, StringComparer.Ordinal).ToDictionary(static g => g.Key, static g => g.First(), StringComparer.Ordinal);
|
||||
|
||||
return new NativeLibrary
|
||||
{
|
||||
NativeNamespace = config.NativeNamespace,
|
||||
Structs = structs,
|
||||
Enums = enums,
|
||||
Functions = functions,
|
||||
StructsByName = structsByName,
|
||||
FunctionsByName = functionsByName,
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<NativeMember> ParseMembers(StructDeclarationSyntax @struct)
|
||||
{
|
||||
var members = new List<NativeMember>();
|
||||
|
||||
foreach (var member in @struct.Members)
|
||||
{
|
||||
switch (member)
|
||||
{
|
||||
case FieldDeclarationSyntax field:
|
||||
if (field.Declaration.Type is FunctionPointerTypeSyntax)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var variable in field.Declaration.Variables)
|
||||
{
|
||||
members.Add(new NativeMember
|
||||
{
|
||||
Name = variable.Identifier.ValueText,
|
||||
TypeName = NormalizeType(field.Declaration.Type.ToString()),
|
||||
Kind = NativeMemberKind.Field,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case PropertyDeclarationSyntax property:
|
||||
members.Add(new NativeMember
|
||||
{
|
||||
Name = property.Identifier.ValueText,
|
||||
TypeName = NormalizeType(property.Type.ToString()),
|
||||
Kind = NativeMemberKind.Property,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
private static (bool IsList, bool IsPointerList, string? ListElementType) TryMatchList(IReadOnlyList<NativeMember> members)
|
||||
{
|
||||
var data = members.FirstOrDefault(static m => m.Kind == NativeMemberKind.Field && m.Name == "data");
|
||||
var count = members.FirstOrDefault(static m => m.Kind == NativeMemberKind.Field && m.Name == "count");
|
||||
if (data is null || count is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (count.TypeName != "nuint")
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var pointerDepth = GetPointerDepth(data.TypeName);
|
||||
if (pointerDepth == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return (true, pointerDepth > 1, TrimPointers(data.TypeName));
|
||||
}
|
||||
|
||||
private static string GetNamespace(SyntaxNode root)
|
||||
{
|
||||
var fileScoped = root.DescendantNodes().OfType<FileScopedNamespaceDeclarationSyntax>().FirstOrDefault();
|
||||
if (fileScoped is not null)
|
||||
{
|
||||
return fileScoped.Name.ToString();
|
||||
}
|
||||
|
||||
var block = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
|
||||
if (block is not null)
|
||||
{
|
||||
return block.Name.ToString();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string NormalizeType(string typeName)
|
||||
{
|
||||
return typeName.Replace("ref ", string.Empty, StringComparison.Ordinal)
|
||||
.Replace("readonly ", string.Empty, StringComparison.Ordinal)
|
||||
.Trim();
|
||||
}
|
||||
|
||||
public static int GetPointerDepth(string typeName)
|
||||
{
|
||||
var depth = 0;
|
||||
foreach (var ch in typeName)
|
||||
{
|
||||
if (ch == '*')
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
public static string TrimPointers(string typeName)
|
||||
{
|
||||
return typeName.TrimEnd('*').Trim();
|
||||
}
|
||||
}
|
||||
118
src/Tools/Ghost.NativeWrapperGen/Program.cs
Normal file
118
src/Tools/Ghost.NativeWrapperGen/Program.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using Ghost.NativeWrapperGen.Config;
|
||||
using Ghost.NativeWrapperGen.Emit;
|
||||
using Ghost.NativeWrapperGen.Parsing;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.NativeWrapperGen;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = CliOptions.Parse(args);
|
||||
var config = LoadConfig(options.ConfigPath);
|
||||
|
||||
var parser = new BindingParser();
|
||||
var library = parser.Parse(options.InputPath, config);
|
||||
|
||||
var outputDirectory = options.OutputPath;
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
CleanupGeneratedFiles(outputDirectory);
|
||||
|
||||
var emitter = new WrapperGeneratorEmitter();
|
||||
foreach (var file in emitter.Emit(library, config))
|
||||
{
|
||||
var destination = Path.Combine(outputDirectory, file.FileName);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
||||
File.WriteAllText(destination, file.Content);
|
||||
Console.WriteLine($"Generated {destination}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static WrapperConfig LoadConfig(string configPath)
|
||||
{
|
||||
var json = File.ReadAllText(configPath);
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
AllowTrailingCommas = true,
|
||||
};
|
||||
|
||||
var config = JsonSerializer.Deserialize<WrapperConfig>(json, options);
|
||||
return config ?? throw new InvalidOperationException($"Failed to parse config '{configPath}'.");
|
||||
}
|
||||
|
||||
private static void CleanupGeneratedFiles(string outputDirectory)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(outputDirectory, "*.nativegen.cs", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class CliOptions
|
||||
{
|
||||
public required string ConfigPath { get; init; }
|
||||
public required string InputPath { get; init; }
|
||||
public required string OutputPath { get; init; }
|
||||
|
||||
public static CliOptions Parse(string[] args)
|
||||
{
|
||||
string? configPath = null;
|
||||
string? inputPath = null;
|
||||
string? outputPath = null;
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "--config":
|
||||
configPath = GetValue(args, ref i, "--config");
|
||||
break;
|
||||
case "--input":
|
||||
inputPath = GetValue(args, ref i, "--input");
|
||||
break;
|
||||
case "--output":
|
||||
outputPath = GetValue(args, ref i, "--output");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown argument '{args[i]}'. Expected --config, --input, --output.");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(configPath) || string.IsNullOrWhiteSpace(inputPath) || string.IsNullOrWhiteSpace(outputPath))
|
||||
{
|
||||
throw new ArgumentException("Usage: Ghost.NativeWrapperGen --config <path> --input <dir> --output <dir>");
|
||||
}
|
||||
|
||||
return new CliOptions
|
||||
{
|
||||
ConfigPath = Path.GetFullPath(configPath),
|
||||
InputPath = Path.GetFullPath(inputPath),
|
||||
OutputPath = Path.GetFullPath(outputPath),
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetValue(string[] args, ref int index, string optionName)
|
||||
{
|
||||
index++;
|
||||
if (index >= args.Length)
|
||||
{
|
||||
throw new ArgumentException($"Missing value for '{optionName}'.");
|
||||
}
|
||||
|
||||
return args[index];
|
||||
}
|
||||
}
|
||||
552
src/Tools/Ghost.NativeWrapperGen/README.md
Normal file
552
src/Tools/Ghost.NativeWrapperGen/README.md
Normal file
@@ -0,0 +1,552 @@
|
||||
# Ghost.NativeWrapperGen
|
||||
|
||||
`Ghost.NativeWrapperGen` is a CLI tool that reads low-level generated C# native bindings and emits a configurable wrapper layer.
|
||||
|
||||
It is built for bindings that look like ClangSharp output:
|
||||
|
||||
- many `partial struct` native types
|
||||
- raw pointers like `foo_node*`
|
||||
- list structs shaped like `{ T* data; nuint count; }`
|
||||
- native methods inside a static `Api` class
|
||||
|
||||
The generator is now config-driven in three key areas:
|
||||
|
||||
- wrapper kinds are configurable per type
|
||||
- string/blob special handling is configurable by type shape
|
||||
- static methods are generated from discovered native methods plus parameter adapters from config
|
||||
|
||||
## Current capabilities
|
||||
|
||||
Implemented now:
|
||||
|
||||
- parse top-level structs, enums, and `Api` methods
|
||||
- detect list structs automatically
|
||||
- generate wrapper types with configurable kinds: `class`, `struct`, `ref struct`, `readonly ref struct`
|
||||
- generate pointer-list wrappers for `T** + count`
|
||||
- generate raw void-list wrappers for `void* + count`
|
||||
- generate string/blob accessors from config instead of hardcoded type names
|
||||
- generate static wrapper methods from config-driven parameter adapters
|
||||
- generate owned-resource disposal from config
|
||||
- regenerate `ufbx` successfully and build `Ghost.Ufbx`
|
||||
|
||||
Validated commands:
|
||||
|
||||
```bash
|
||||
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json --input src/ThridParty/Ghost.Ufbx/Generated --output src/ThridParty/Ghost.Ufbx/Warper
|
||||
dotnet build src/ThridParty/Ghost.Ufbx/Ghost.Ufbx.csproj
|
||||
```
|
||||
|
||||
## CLI usage
|
||||
|
||||
```bash
|
||||
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config <config-path> --input <generated-binding-folder> --output <wrapper-output-folder>
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
- `--config`: JSON config for one library
|
||||
- `--input`: folder containing generated `.cs` binding files
|
||||
- `--output`: folder where `*.nativegen.cs` files are written
|
||||
|
||||
The generator deletes all existing `*.nativegen.cs` files inside the output folder before writing new ones.
|
||||
|
||||
## Important files
|
||||
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Program.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Config/WrapperConfig.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Parsing/BindingParser.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Transform/NamingConventions.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Transform/PublicTypeResolver.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/Emit/WrapperGeneratorEmitter.cs`
|
||||
- `src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json`
|
||||
|
||||
## Config schema
|
||||
|
||||
Example: `src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"libraryName": "ufbx",
|
||||
"nativeNamespace": "Ghost.Ufbx",
|
||||
"wrapperNamespace": "Ghost.Ufbx",
|
||||
"nativeTypePrefix": "ufbx_",
|
||||
"staticApiClassName": "Ufbx",
|
||||
"skipTypes": [
|
||||
"NativeAnnotationAttribute",
|
||||
"NativeTypeNameAttribute"
|
||||
],
|
||||
"wrappers": {
|
||||
"defaultKind": "struct",
|
||||
"defaultOwnedKind": "class",
|
||||
"kinds": {
|
||||
"ufbx_node": "ref struct",
|
||||
"ufbx_mesh": "ref struct"
|
||||
}
|
||||
},
|
||||
"specialTypes": {
|
||||
"strings": [
|
||||
{
|
||||
"type": "ufbx_string",
|
||||
"dataField": "data",
|
||||
"lengthField": "length",
|
||||
"charSize": 8,
|
||||
"encoding": "utf8",
|
||||
"emitRawSpanProperty": true,
|
||||
"emitStringProperty": true
|
||||
}
|
||||
],
|
||||
"blobs": [
|
||||
{
|
||||
"type": "ufbx_blob",
|
||||
"dataField": "data",
|
||||
"lengthField": "size",
|
||||
"elementType": "byte"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ownedTypes": [
|
||||
{
|
||||
"nativeType": "ufbx_scene",
|
||||
"freeFunction": "ufbx_free_scene",
|
||||
"retainFunction": "ufbx_retain_scene",
|
||||
"wrapperKind": "class"
|
||||
}
|
||||
],
|
||||
"staticMethods": [
|
||||
{
|
||||
"nativeFunction": "ufbx_load_file_len",
|
||||
"methodName": "LoadFile",
|
||||
"throwOnNullReturn": true,
|
||||
"failureMessageMember": "description",
|
||||
"parameters": [
|
||||
{ "native": "filename", "adapter": "utf8Path", "publicName": "pathUtf8" },
|
||||
{ "native": "filename_len", "adapter": "utf8Length", "source": "pathUtf8" },
|
||||
{ "native": "opts", "adapter": "inValue", "type": "ufbx_load_opts", "publicName": "options", "optionalDefault": true },
|
||||
{ "native": "error", "adapter": "errorOut", "type": "ufbx_error", "publicName": "error" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Base config fields
|
||||
|
||||
### `libraryName`
|
||||
|
||||
Human-readable library name.
|
||||
|
||||
### `nativeNamespace`
|
||||
|
||||
Namespace containing the low-level generated bindings.
|
||||
|
||||
### `wrapperNamespace`
|
||||
|
||||
Namespace used for emitted wrapper files.
|
||||
|
||||
### `nativeTypePrefix`
|
||||
|
||||
Prefix removed before PascalCase naming.
|
||||
|
||||
Examples:
|
||||
|
||||
- `ufbx_scene` -> `Scene`
|
||||
- `ufbx_node_list` -> `NodeList`
|
||||
|
||||
### `staticApiClassName`
|
||||
|
||||
Name of the global static wrapper class used for methods that do not target a specific wrapper type.
|
||||
|
||||
Example:
|
||||
|
||||
- `Ufbx`
|
||||
|
||||
### `skipTypes`
|
||||
|
||||
Types ignored by parsing/emission.
|
||||
|
||||
Use this for helper attributes or internal generated artifacts you do not want wrapped.
|
||||
|
||||
### `typeNameOverrides`
|
||||
|
||||
Overrides wrapper type names.
|
||||
|
||||
Example:
|
||||
|
||||
- `ufbx_string` -> `UfbxString`
|
||||
|
||||
### `publicTypeOverrides`
|
||||
|
||||
Overrides member property types.
|
||||
|
||||
This is mainly useful for raw native value types that should expose another public type.
|
||||
|
||||
## Wrapper kind configuration
|
||||
|
||||
The generator no longer assumes everything should be `ref struct`.
|
||||
|
||||
Config section:
|
||||
|
||||
```json
|
||||
"wrappers": {
|
||||
"defaultKind": "struct",
|
||||
"defaultOwnedKind": "class",
|
||||
"kinds": {
|
||||
"ufbx_scene": "class",
|
||||
"ufbx_node": "ref struct",
|
||||
"ufbx_mesh": "ref struct"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Supported values:
|
||||
|
||||
- `class`
|
||||
- `struct`
|
||||
- `ref struct`
|
||||
- `readonly ref struct`
|
||||
|
||||
Resolution order:
|
||||
|
||||
1. `ownedTypes[].wrapperKind`
|
||||
2. `wrappers.kinds[nativeType]`
|
||||
3. `wrappers.defaultOwnedKind` for owned types
|
||||
4. `wrappers.defaultKind` for all other types
|
||||
|
||||
Recommended defaults:
|
||||
|
||||
- owning types: `class`
|
||||
- lightweight non-owning value wrappers: `struct`
|
||||
- hot traversal-only wrappers: `ref struct`
|
||||
|
||||
## Special type configuration
|
||||
|
||||
This replaces hardcoded `ufbx_string` and `ufbx_blob` behavior.
|
||||
|
||||
### String special types
|
||||
|
||||
Config shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ufbx_string",
|
||||
"dataField": "data",
|
||||
"lengthField": "length",
|
||||
"charSize": 8,
|
||||
"encoding": "utf8",
|
||||
"emitRawSpanProperty": true,
|
||||
"emitStringProperty": true
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `type`: native struct type name
|
||||
- `dataField`: pointer field containing character data
|
||||
- `lengthField`: element count field
|
||||
- `charSize`: 8, 16, or 32
|
||||
- `encoding`: `utf8`, `utf16`, or `utf32`
|
||||
- `emitRawSpanProperty`: whether to emit `<Name>Bytes`
|
||||
- `emitStringProperty`: whether to emit `<Name>` string property
|
||||
|
||||
Generated helper methods look like:
|
||||
|
||||
```csharp
|
||||
public static ReadOnlySpan<byte> AsByteSpan(ufbx_string value)
|
||||
public static string GetString(ufbx_string value)
|
||||
```
|
||||
|
||||
Generated wrapper members look like:
|
||||
|
||||
```csharp
|
||||
public ReadOnlySpan<byte> NameBytes => NativeWrapperHelpers.AsByteSpan(_ptr->name);
|
||||
public string Name => NativeWrapperHelpers.GetString(_ptr->name);
|
||||
```
|
||||
|
||||
### Blob special types
|
||||
|
||||
Config shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ufbx_blob",
|
||||
"dataField": "data",
|
||||
"lengthField": "size",
|
||||
"elementType": "byte"
|
||||
}
|
||||
```
|
||||
|
||||
Generated helper methods look like:
|
||||
|
||||
```csharp
|
||||
public static ReadOnlySpan<byte> AsSpan(ufbx_blob value)
|
||||
```
|
||||
|
||||
Generated wrapper members look like:
|
||||
|
||||
```csharp
|
||||
public ReadOnlySpan<byte> Data => NativeWrapperHelpers.AsSpan(_ptr->data);
|
||||
```
|
||||
|
||||
## Owned type configuration
|
||||
|
||||
Config shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"nativeType": "ufbx_scene",
|
||||
"freeFunction": "ufbx_free_scene",
|
||||
"retainFunction": "ufbx_retain_scene",
|
||||
"wrapperKind": "class"
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `nativeType`: native pointer target type
|
||||
- `freeFunction`: optional free function in `Api`
|
||||
- `retainFunction`: reserved for future ownership helpers
|
||||
- `wrapperKind`: optional per-owned-type wrapper override
|
||||
- `staticType`: reserved target override for generated static methods
|
||||
|
||||
Current effect:
|
||||
|
||||
- if `freeFunction` is present, generator emits `Dispose()`
|
||||
- if wrapper kind is not otherwise specified, owning types use `wrappers.defaultOwnedKind`
|
||||
|
||||
## Static method generation
|
||||
|
||||
This replaces the old fixed `EntryPointConfig` model.
|
||||
|
||||
Now you configure native methods by name and provide parameter adapters.
|
||||
|
||||
Config shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"nativeFunction": "ufbx_load_memory",
|
||||
"methodName": "LoadMemory",
|
||||
"throwOnNullReturn": true,
|
||||
"failureMessageMember": "description",
|
||||
"parameters": [
|
||||
{ "native": "data", "adapter": "byteSpan", "publicName": "data" },
|
||||
{ "native": "data_size", "adapter": "byteSpanLength", "source": "data" },
|
||||
{ "native": "opts", "adapter": "inValue", "type": "ufbx_load_opts", "publicName": "options", "optionalDefault": true },
|
||||
{ "native": "error", "adapter": "errorOut", "type": "ufbx_error", "publicName": "error" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Target selection
|
||||
|
||||
By default:
|
||||
|
||||
- if a native method returns `T*` and `T` has a wrapper, the static method is generated on `T`
|
||||
- otherwise it is generated on the global static type from `staticApiClassName`
|
||||
|
||||
You can override that later with `staticType`.
|
||||
|
||||
### Current parameter adapters
|
||||
|
||||
Implemented adapters:
|
||||
|
||||
- `utf8Path`
|
||||
- public parameter: `ReadOnlySpan<byte>`
|
||||
- native argument: `sbyte*`
|
||||
- `utf8Length`
|
||||
- emits `(nuint)<source>.Length`
|
||||
- `byteSpan`
|
||||
- public parameter: `ReadOnlySpan<byte>`
|
||||
- native argument: `byte*`
|
||||
- `byteSpanLength`
|
||||
- emits `(nuint)<source>.Length`
|
||||
- `inValue`
|
||||
- public parameter: `in T`
|
||||
- native argument: `&localCopy`
|
||||
- `errorOut`
|
||||
- hides error buffer from public signature
|
||||
- emits a local native error value and passes pointer to native call
|
||||
|
||||
### Return handling
|
||||
|
||||
Current behavior:
|
||||
|
||||
- if native return is `T*` and `T` has a wrapper, emitted return type is wrapper type
|
||||
- if `throwOnNullReturn` is `true`, a null pointer return throws `InvalidOperationException`
|
||||
- if `failureMessageMember` is set and the method has an `errorOut` parameter, the generator uses that configured member to build the exception message
|
||||
- primitive and raw returns are forwarded directly
|
||||
|
||||
### Example: `ufbx_load_file_len`
|
||||
|
||||
Native signature:
|
||||
|
||||
```csharp
|
||||
public static extern ufbx_scene* ufbx_load_file_len(sbyte* filename, nuint filename_len, ufbx_load_opts* opts, ufbx_error* error);
|
||||
```
|
||||
|
||||
Generated method:
|
||||
|
||||
```csharp
|
||||
public static Scene LoadFile(ReadOnlySpan<byte> pathUtf8, in ufbx_load_opts options = default)
|
||||
```
|
||||
|
||||
### Example: `ufbx_load_memory`
|
||||
|
||||
Native signature:
|
||||
|
||||
```csharp
|
||||
public static extern ufbx_scene* ufbx_load_memory(void* data, nuint data_size, ufbx_load_opts* opts, ufbx_error* error);
|
||||
```
|
||||
|
||||
Generated method:
|
||||
|
||||
```csharp
|
||||
public static Scene LoadMemory(ReadOnlySpan<byte> data, in ufbx_load_opts options = default)
|
||||
```
|
||||
|
||||
## List handling
|
||||
|
||||
The parser recognizes list structs by shape:
|
||||
|
||||
- a `data` field
|
||||
- a `count` field
|
||||
- `count` type is `nuint`
|
||||
- `data` is a pointer type
|
||||
|
||||
### Value lists: `T* + count`
|
||||
|
||||
Generated as `ReadOnlySpan<T>` properties.
|
||||
|
||||
### Pointer lists: `T** + count`
|
||||
|
||||
Generated as iterable wrapper list types with:
|
||||
|
||||
- `Count`
|
||||
- indexer
|
||||
- `foreach` enumerator
|
||||
|
||||
### Void lists: `void* + count`
|
||||
|
||||
Generated as lightweight wrappers exposing:
|
||||
|
||||
- `Count`
|
||||
- `Data`
|
||||
|
||||
## Current naming rules
|
||||
|
||||
By default:
|
||||
|
||||
1. remove `nativeTypePrefix`
|
||||
2. split by `_`
|
||||
3. convert to PascalCase
|
||||
|
||||
Examples:
|
||||
|
||||
- `ufbx_scene` -> `Scene`
|
||||
- `ufbx_node` -> `Node`
|
||||
- `ufbx_node_list` -> `NodeList`
|
||||
|
||||
If a member name would equal the wrapper type name, `Value` is appended.
|
||||
|
||||
Example:
|
||||
|
||||
- wrapper `Props` with field `props` becomes `PropsValue`
|
||||
|
||||
## How to use for another library
|
||||
|
||||
Create a new config, for example `src/Tools/Ghost.NativeWrapperGen/configs/somelib.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"libraryName": "somelib",
|
||||
"nativeNamespace": "Ghost.SomeLib",
|
||||
"wrapperNamespace": "Ghost.SomeLib",
|
||||
"nativeTypePrefix": "somelib_",
|
||||
"staticApiClassName": "SomeLib",
|
||||
"wrappers": {
|
||||
"defaultKind": "struct",
|
||||
"defaultOwnedKind": "class",
|
||||
"kinds": {}
|
||||
},
|
||||
"specialTypes": {
|
||||
"strings": [
|
||||
{
|
||||
"type": "somelib_string",
|
||||
"dataField": "data",
|
||||
"lengthField": "length",
|
||||
"charSize": 8,
|
||||
"encoding": "utf8"
|
||||
}
|
||||
],
|
||||
"blobs": []
|
||||
},
|
||||
"ownedTypes": [
|
||||
{
|
||||
"nativeType": "somelib_context",
|
||||
"freeFunction": "somelib_free_context",
|
||||
"wrapperKind": "class"
|
||||
}
|
||||
],
|
||||
"staticMethods": [
|
||||
{
|
||||
"nativeFunction": "somelib_load_file_len",
|
||||
"methodName": "LoadFile",
|
||||
"throwOnNullReturn": true,
|
||||
"failureMessageMember": "message",
|
||||
"parameters": [
|
||||
{ "native": "path", "adapter": "utf8Path", "publicName": "pathUtf8" },
|
||||
{ "native": "path_len", "adapter": "utf8Length", "source": "pathUtf8" },
|
||||
{ "native": "options", "adapter": "inValue", "type": "somelib_load_options", "optionalDefault": true },
|
||||
{ "native": "error", "adapter": "errorOut", "type": "somelib_error" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/somelib.json --input src/ThridParty/Ghost.SomeLib/Generated --output src/ThridParty/Ghost.SomeLib/Wrapper
|
||||
```
|
||||
|
||||
## Current limitations
|
||||
|
||||
This README documents the current implementation, not a future idealized version.
|
||||
|
||||
Not implemented yet:
|
||||
|
||||
- automatic ownership inference
|
||||
- automatic retain/release policy generation
|
||||
- rich enum wrapper generation
|
||||
- source generator mode
|
||||
- MSBuild auto-hook integration
|
||||
- custom adapter plugins outside the built-in adapter set
|
||||
- direct generation from C headers
|
||||
- per-member exclusion config
|
||||
- fixed-buffer string decoding rules
|
||||
|
||||
Also note:
|
||||
|
||||
- `struct` wrappers that own native resources are possible, but not recommended as a default because copy semantics can duplicate ownership
|
||||
- current string support assumes pointer + length string structs
|
||||
- pointer-list wrappers are still emitted as `readonly ref struct` views for performance
|
||||
|
||||
## Recommended workflow
|
||||
|
||||
When changing the generator:
|
||||
|
||||
```bash
|
||||
dotnet build src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj
|
||||
dotnet run --project src/Tools/Ghost.NativeWrapperGen/Ghost.NativeWrapperGen.csproj -- --config src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json --input src/ThridParty/Ghost.Ufbx/Generated --output src/ThridParty/Ghost.Ufbx/Warper
|
||||
dotnet build src/ThridParty/Ghost.Ufbx/Ghost.Ufbx.csproj
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Use `Ghost.NativeWrapperGen` when you already have raw generated C# bindings and want a configurable modern wrapper layer without hand-writing hundreds of types.
|
||||
|
||||
The important design idea is now:
|
||||
|
||||
- keep parsing generic
|
||||
- move policy into config
|
||||
- keep adapters explicit when native signatures need shaping
|
||||
@@ -0,0 +1,49 @@
|
||||
using Ghost.NativeWrapperGen.Config;
|
||||
|
||||
namespace Ghost.NativeWrapperGen.Transform;
|
||||
|
||||
public sealed class NamingConventions
|
||||
{
|
||||
private readonly WrapperConfig _config;
|
||||
|
||||
public NamingConventions(WrapperConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public string GetWrapperTypeName(string nativeTypeName)
|
||||
{
|
||||
if (_config.TypeNameOverrides.TryGetValue(nativeTypeName, out var overrideName))
|
||||
{
|
||||
return overrideName;
|
||||
}
|
||||
|
||||
return ToPascalCase(StripKnownPrefix(nativeTypeName));
|
||||
}
|
||||
|
||||
public string GetPropertyName(string nativeName)
|
||||
{
|
||||
return ToPascalCase(nativeName);
|
||||
}
|
||||
|
||||
private string StripKnownPrefix(string nativeTypeName)
|
||||
{
|
||||
if (nativeTypeName.StartsWith(_config.NativeTypePrefix, StringComparison.Ordinal))
|
||||
{
|
||||
return nativeTypeName[_config.NativeTypePrefix.Length..];
|
||||
}
|
||||
|
||||
return nativeTypeName;
|
||||
}
|
||||
|
||||
public static string ToPascalCase(string value)
|
||||
{
|
||||
var parts = value.Split('_', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (parts.Length == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return string.Concat(parts.Select(static part => char.ToUpperInvariant(part[0]) + part[1..]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Ghost.NativeWrapperGen.Config;
|
||||
using Ghost.NativeWrapperGen.Model;
|
||||
using Ghost.NativeWrapperGen.Parsing;
|
||||
|
||||
namespace Ghost.NativeWrapperGen.Transform;
|
||||
|
||||
public sealed class PublicTypeResolver
|
||||
{
|
||||
private readonly NativeLibrary _library;
|
||||
private readonly WrapperConfig _config;
|
||||
private readonly NamingConventions _naming;
|
||||
|
||||
public PublicTypeResolver(NativeLibrary library, WrapperConfig config, NamingConventions naming)
|
||||
{
|
||||
_library = library;
|
||||
_config = config;
|
||||
_naming = naming;
|
||||
}
|
||||
|
||||
public string GetPublicType(string nativeTypeName)
|
||||
{
|
||||
if (string.Equals(nativeTypeName, "void", StringComparison.Ordinal))
|
||||
{
|
||||
return "void*";
|
||||
}
|
||||
|
||||
if (_config.PublicTypeOverrides.TryGetValue(nativeTypeName, out var overrideType))
|
||||
{
|
||||
return overrideType;
|
||||
}
|
||||
|
||||
var pointerDepth = BindingParser.GetPointerDepth(nativeTypeName);
|
||||
var baseType = BindingParser.TrimPointers(nativeTypeName);
|
||||
|
||||
if (pointerDepth == 0)
|
||||
{
|
||||
return baseType;
|
||||
}
|
||||
|
||||
if (_library.StructsByName.ContainsKey(baseType))
|
||||
{
|
||||
return pointerDepth switch
|
||||
{
|
||||
1 => _naming.GetWrapperTypeName(baseType),
|
||||
_ => nativeTypeName,
|
||||
};
|
||||
}
|
||||
|
||||
return nativeTypeName;
|
||||
}
|
||||
|
||||
public bool HasWrapper(string nativeTypeName)
|
||||
{
|
||||
return _library.StructsByName.ContainsKey(nativeTypeName);
|
||||
}
|
||||
}
|
||||
221
src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json
Normal file
221
src/Tools/Ghost.NativeWrapperGen/configs/ufbx.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"libraryName": "ufbx",
|
||||
"nativeNamespace": "Ghost.Ufbx",
|
||||
"wrapperNamespace": "Ghost.Ufbx",
|
||||
"nativeTypePrefix": "ufbx_",
|
||||
"staticApiClassName": "Ufbx",
|
||||
"skipTypes": [
|
||||
"NativeAnnotationAttribute",
|
||||
"NativeTypeNameAttribute"
|
||||
],
|
||||
"wrappers": {
|
||||
"defaultKind": "struct",
|
||||
"defaultOwnedKind": "class",
|
||||
"kinds": {
|
||||
"ufbx_node": "ref struct",
|
||||
"ufbx_mesh": "ref struct",
|
||||
"ufbx_element": "ref struct",
|
||||
"ufbx_anim": "ref struct",
|
||||
"ufbx_material": "ref struct",
|
||||
"ufbx_texture": "ref struct",
|
||||
"ufbx_props": "ref struct",
|
||||
"ufbx_prop": "ref struct",
|
||||
"ufbx_load_opts": "class"
|
||||
}
|
||||
},
|
||||
"specialTypes": {
|
||||
"strings": [
|
||||
{
|
||||
"type": "ufbx_string",
|
||||
"dataField": "data",
|
||||
"lengthField": "length",
|
||||
"charSize": 8,
|
||||
"encoding": "utf8",
|
||||
"emitRawSpanProperty": true,
|
||||
"emitStringProperty": true
|
||||
}
|
||||
],
|
||||
"blobs": [
|
||||
{
|
||||
"type": "ufbx_blob",
|
||||
"dataField": "data",
|
||||
"lengthField": "size",
|
||||
"elementType": "byte"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ownedTypes": [
|
||||
{
|
||||
"nativeType": "ufbx_scene",
|
||||
"freeFunction": "ufbx_free_scene",
|
||||
"retainFunction": "ufbx_retain_scene",
|
||||
"wrapperKind": "class"
|
||||
}
|
||||
],
|
||||
"staticMethods": [
|
||||
{
|
||||
"nativeFunction": "ufbx_load_file_len",
|
||||
"methodName": "LoadFile",
|
||||
"throwOnNullReturn": true,
|
||||
"failureMessageMember": "description",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "filename",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "pathUtf8"
|
||||
},
|
||||
{
|
||||
"native": "filename_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "pathUtf8"
|
||||
},
|
||||
{
|
||||
"native": "opts",
|
||||
"adapter": "getPtr",
|
||||
"type": "LoadOpts",
|
||||
"publicName": "options"
|
||||
},
|
||||
{
|
||||
"native": "error",
|
||||
"adapter": "errorOut",
|
||||
"type": "ufbx_error",
|
||||
"publicName": "error"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_load_memory",
|
||||
"methodName": "LoadMemory",
|
||||
"throwOnNullReturn": true,
|
||||
"failureMessageMember": "description",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "data",
|
||||
"adapter": "byteSpan",
|
||||
"publicName": "data"
|
||||
},
|
||||
{
|
||||
"native": "data_size",
|
||||
"adapter": "byteSpanLength",
|
||||
"source": "data"
|
||||
},
|
||||
{
|
||||
"native": "opts",
|
||||
"adapter": "inValue",
|
||||
"type": "ufbx_load_opts",
|
||||
"publicName": "options",
|
||||
"optionalDefault": true
|
||||
},
|
||||
{
|
||||
"native": "error",
|
||||
"adapter": "errorOut",
|
||||
"type": "ufbx_error",
|
||||
"publicName": "error"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_find_node_len",
|
||||
"methodName": "FindNode",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "name",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "name"
|
||||
},
|
||||
{
|
||||
"native": "name_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_find_element_len",
|
||||
"methodName": "FindElement",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "name",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "name"
|
||||
},
|
||||
{
|
||||
"native": "name_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_find_material_len",
|
||||
"methodName": "FindMaterial",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "name",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "name"
|
||||
},
|
||||
{
|
||||
"native": "name_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_find_anim_stack_len",
|
||||
"methodName": "FindAnimStack",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "name",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "name"
|
||||
},
|
||||
{
|
||||
"native": "name_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nativeFunction": "ufbx_find_prop_len",
|
||||
"methodName": "FindProp",
|
||||
"parameters": [
|
||||
{
|
||||
"native": "name",
|
||||
"adapter": "utf8Path",
|
||||
"publicName": "name"
|
||||
},
|
||||
{
|
||||
"native": "name_len",
|
||||
"adapter": "utf8Length",
|
||||
"source": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"marshalledTypes": [
|
||||
{
|
||||
"nativeType": "ufbx_load_opts",
|
||||
"marshalledProperties": [
|
||||
{
|
||||
"native": "obj_mtl_path",
|
||||
"type": "cstring"
|
||||
},
|
||||
{
|
||||
"native": "filename",
|
||||
"type": "cstring"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typeNameOverrides": {
|
||||
"ufbx_string": "UfbxString",
|
||||
"ufbx_blob": "UfbxBlob"
|
||||
},
|
||||
"publicTypeOverrides": {
|
||||
"ufbx_string": "string",
|
||||
"ufbx_blob": "ReadOnlySpan<byte>"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user