Skip to content

Commit 73b9153

Browse files
committed
[WIP] GDExtension bindings generator
1 parent 5404fdd commit 73b9153

18 files changed

Lines changed: 720 additions & 15 deletions

src/Godot.Bindings/Core/GodotObject.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private unsafe GodotObject(GodotObjectCreationOptions options)
107107
GodotBridge.GDExtensionInterface.object_set_instance((void*)NativePtr, &extensionClassName, (void*)gcHandlePtr);
108108
}
109109

110-
if (!options.InstanceBindingAlreadyBound)
110+
if (!options.InstanceBindingAlreadyBound && !IsGDExtensionDefinedType())
111111
{
112112
var bindingCallbacks = GDExtensionInstanceBindingCallbacks.Default;
113113
if (!IsUserDefinedType())
@@ -148,6 +148,13 @@ bool IsUserDefinedType()
148148
// If this type is not defined in this assembly, it must be a user-defined type.
149149
return GetType().Assembly != typeof(GodotObject).Assembly;
150150
}
151+
152+
bool IsGDExtensionDefinedType()
153+
{
154+
// If this type is defined in the GDExtensionBindings assembly,
155+
// it must be a type defined by a GDExtension that we generated bindings for.
156+
return GetType().Assembly.GetName().Name == "GDExtensionBindings";
157+
}
151158
}
152159

153160
/// <summary>

src/Godot.Bindings/Godot.Bindings.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
<InternalsVisibleTo Include="Godot.Bindings.IntegrationTests.TestGame" />
2828
</ItemGroup>
2929

30+
<ItemGroup>
31+
<!-- The generated GDExtension bindings need to use internal API. -->
32+
<InternalsVisibleTo Include="GDExtensionBindings" />
33+
</ItemGroup>
34+
3035
<!-- Reference the project that contains the MSBuild task to ensure the build order is correct. -->
3136
<ItemGroup>
3237
<ProjectReference Include="..\Godot.BindingsGeneration.Tasks\Godot.BindingsGeneration.Tasks.csproj" ReferenceOutputAssembly="false" />

src/Godot.Bindings/NativeInterop/InteropUtils.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Frozen;
3+
using System.Collections.Concurrent;
34
using System.Diagnostics.CodeAnalysis;
45
using Godot.Bridge;
56

@@ -25,6 +26,8 @@ public unsafe void Invoke([DynamicallyAccessedMembers(DynamicallyAccessedMemberT
2526

2627
private static FrozenDictionary<StringName, GDExtensionInstanceBindingCallbacks> _bindingCallbacks;
2728

29+
private static ConcurrentDictionary<StringName, GDExtensionInstanceBindingCallbacks> _extensionBindingCallbacks { get; } = new(StringNameEqualityComparer.Default);
30+
2831
private static FrozenDictionary<StringName, RegisterVirtualOverrideHandler> _registerVirtualOverridesHelpers;
2932

3033
static InteropUtils()
@@ -39,6 +42,11 @@ internal static bool TryGetBindingCallbacks(StringName className, [NotNullWhen(t
3942
return true;
4043
}
4144

45+
if (_extensionBindingCallbacks.TryGetValue(className, out bindingCallbacks))
46+
{
47+
return true;
48+
}
49+
4250
return false;
4351
}
4452

@@ -50,9 +58,25 @@ internal static bool TryGetBindingCallbacks(NativeGodotStringName className, [No
5058
return true;
5159
}
5260

61+
var extensionBindingCallbacksLookup = _extensionBindingCallbacks.GetAlternateLookup<NativeGodotStringName>();
62+
if (extensionBindingCallbacksLookup.TryGetValue(className, out bindingCallbacks))
63+
{
64+
return true;
65+
}
66+
5367
return false;
5468
}
5569

70+
internal static void RegisterExtensionBindingCallbacks(StringName extensionClassName, GDExtensionInstanceBindingCallbacks bindingCallbacks)
71+
{
72+
if (_bindingCallbacks.ContainsKey(extensionClassName) || _extensionBindingCallbacks.ContainsKey(extensionClassName))
73+
{
74+
throw new InvalidOperationException($"Binding callbacks for '{extensionClassName}' are already registered.");
75+
}
76+
77+
_extensionBindingCallbacks[extensionClassName] = bindingCallbacks;
78+
}
79+
5680
internal static void RegisterVirtualOverrides([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type type, ClassRegistrationContext context)
5781
{
5882
// It's fine if there is no helper for this class, it may just mean there are no virtual overrides to register.

src/Godot.BindingsGeneration/BindingsDataCollectors/BuiltInClassesBindingsDataCollector.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ internal sealed class BuiltInClassesBindingsDataCollector : BindingsDataCollecto
1818

1919
public override void Initialize(BindingsData.CollectionContext context)
2020
{
21+
if (context.IsExtension)
22+
{
23+
// Built-in classes are only generated for the core API, not for GDExtensions.
24+
return;
25+
}
26+
2127
foreach (var engineClass in context.Api.BuiltInClasses)
2228
{
2329
if (!ShouldGenerateBuiltInClass(engineClass.Name) && !ShouldGenerateBuiltInClassHelpers(engineClass.Name))
@@ -62,6 +68,12 @@ void RegisterBuiltInType(string engineTypeName, TypeInfo type)
6268

6369
public override void Populate(BindingsData.CollectionContext context)
6470
{
71+
if (context.IsExtension)
72+
{
73+
// Built-in classes are only generated for the core API, not for GDExtensions.
74+
return;
75+
}
76+
6577
foreach (var engineClass in context.Api.BuiltInClasses)
6678
{
6779
if (!_builtinClasses.TryGetValue(engineClass.Name, out var type))

src/Godot.BindingsGeneration/BindingsDataCollectors/EngineClassesBindingsDataCollector.cs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,53 @@ public override void Populate(BindingsData.CollectionContext context)
8787
PopulateEngineClassEnumsAndConstants(context, engineClass);
8888
}
8989

90+
if (context.IsExtension)
91+
{
92+
// InteropUtils is only generated for the core API, not for GDExtensions.
93+
// Instead, we generate a module initializer in each for each GDExtension that populates InteropUtils.
94+
context.AddGeneratedType("NativeInterop/InteropUtils.cs", new TypeInfo("InteropUtils", context.Options.Namespace)
95+
{
96+
VisibilityAttributes = VisibilityAttributes.Assembly,
97+
TypeAttributes = TypeAttributes.ReferenceType,
98+
IsStatic = true,
99+
DeclaredFields =
100+
{
101+
new FieldInfo("_isInitialized", KnownTypes.SystemBoolean)
102+
{
103+
VisibilityAttributes = VisibilityAttributes.Private,
104+
IsStatic = true,
105+
},
106+
},
107+
DeclaredMethods =
108+
{
109+
new MethodInfo("EnsureHelpersInitialized")
110+
{
111+
VisibilityAttributes = VisibilityAttributes.Assembly,
112+
Attributes =
113+
{
114+
"[global::System.Runtime.CompilerServices.ModuleInitializer]",
115+
},
116+
IsStatic = true,
117+
Body = MethodBody.Create(writer =>
118+
{
119+
writer.WriteLine("if (global::System.Threading.Interlocked.Exchange(ref _isInitialized, true))");
120+
writer.WriteLine("{");
121+
writer.WriteLine(" return;");
122+
writer.WriteLine("}");
123+
124+
foreach (var engineClass in context.Api.Classes)
125+
{
126+
var type = context.TypeDB.GetTypeFromEngineName(engineClass.Name);
127+
128+
writer.WriteLine($"global::Godot.NativeInterop.InteropUtils.RegisterExtensionBindingCallbacks({type.FullNameWithGlobal}.NativeName, {type.FullNameWithGlobal}.BindingCallbacks);");
129+
}
130+
}),
131+
},
132+
}
133+
});
134+
return;
135+
}
136+
90137
// Generate method in InteropUtils to initialize the dictionary with helpers
91138
// used to access constructors and static methods for a Godot class from the
92139
// native class name.
@@ -575,6 +622,10 @@ private static void PopulateEngineClassSignals(BindingsData.CollectionContext co
575622
foreach (var engineSignal in engineClass.Signals)
576623
{
577624
string eventName = NamingUtils.SnakeToPascalCase(engineSignal.Name);
625+
if (context.IsExtension)
626+
{
627+
eventName = $"{eventName}Signal";
628+
}
578629

579630
List<TypeInfo> signalParameterTypes = [];
580631
List<ParameterInfo> signalParameters = [];
@@ -909,15 +960,19 @@ void AddMemberNameOrRename(HashSet<string> usedNames, MemberInfo member, string
909960

910961
foreach (var engineSignal in engineClass.Signals)
911962
{
912-
AddCachedStringName(signalNamesType, engineSignal.Name);
963+
AddCachedStringName(signalNamesType, engineSignal.Name, context.IsExtension ? "Signal" : null);
913964
}
914965

915966
type.NestedTypes.Add(signalNamesType);
916967
}
917968

918-
static void AddCachedStringName(TypeInfo cacheType, string engineName)
969+
static void AddCachedStringName(TypeInfo cacheType, string engineName, string? suffix = null)
919970
{
920971
string name = NamingUtils.SnakeToPascalCase(engineName);
972+
if (!string.IsNullOrEmpty(suffix))
973+
{
974+
name = $"{name}{suffix}";
975+
}
921976

922977
var nameField = new FieldInfo($"_{engineName}_Value", KnownTypes.GodotStringName)
923978
{

src/Godot.BindingsGeneration/BindingsDataCollectors/GlobalConstantsBindingsDataCollector.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ internal sealed class GlobalConstantsBindingsDataCollector : BindingsDataCollect
77
{
88
public override void Populate(BindingsData.CollectionContext context)
99
{
10+
if (context.IsExtension)
11+
{
12+
// Global constants are only generated for the core API, not for GDExtensions.
13+
return;
14+
}
15+
1016
var globals = new TypeInfo("GlobalConstants", context.Options.Namespace)
1117
{
1218
VisibilityAttributes = VisibilityAttributes.Assembly,

src/Godot.BindingsGeneration/BindingsDataCollectors/NativeStructuresBindingsDataCollector.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@ internal sealed class NativeStructuresBindingsDataCollector : BindingsDataCollec
1313

1414
public override void Initialize(BindingsData.CollectionContext context)
1515
{
16-
// Native structures format use C/C++ type names.
17-
context.TypeDB.RegisterTypeName("uint8_t", KnownTypes.SystemByte);
18-
context.TypeDB.RegisterTypeName("uint16_t", KnownTypes.SystemUInt16);
19-
context.TypeDB.RegisterTypeName("uint32_t", KnownTypes.SystemUInt32);
20-
context.TypeDB.RegisterTypeName("uint64_t", KnownTypes.SystemUInt64);
21-
context.TypeDB.RegisterTypeName("int8_t", KnownTypes.SystemSByte);
22-
context.TypeDB.RegisterTypeName("int16_t", KnownTypes.SystemInt16);
23-
context.TypeDB.RegisterTypeName("int32_t", KnownTypes.SystemInt32);
24-
context.TypeDB.RegisterTypeName("int64_t", KnownTypes.SystemInt64);
25-
context.TypeDB.RegisterTypeName("real_t", context.Options.FloatPrecision == GodotFloatTypePrecision.Double ? KnownTypes.SystemDouble : KnownTypes.SystemSingle);
26-
context.TypeDB.RegisterTypeName("ObjectID", KnownTypes.SystemUInt64);
16+
if (!context.IsExtension)
17+
{
18+
// Native structures format use C/C++ type names.
19+
context.TypeDB.RegisterTypeName("uint8_t", KnownTypes.SystemByte);
20+
context.TypeDB.RegisterTypeName("uint16_t", KnownTypes.SystemUInt16);
21+
context.TypeDB.RegisterTypeName("uint32_t", KnownTypes.SystemUInt32);
22+
context.TypeDB.RegisterTypeName("uint64_t", KnownTypes.SystemUInt64);
23+
context.TypeDB.RegisterTypeName("int8_t", KnownTypes.SystemSByte);
24+
context.TypeDB.RegisterTypeName("int16_t", KnownTypes.SystemInt16);
25+
context.TypeDB.RegisterTypeName("int32_t", KnownTypes.SystemInt32);
26+
context.TypeDB.RegisterTypeName("int64_t", KnownTypes.SystemInt64);
27+
context.TypeDB.RegisterTypeName("real_t", context.Options.FloatPrecision == GodotFloatTypePrecision.Double ? KnownTypes.SystemDouble : KnownTypes.SystemSingle);
28+
context.TypeDB.RegisterTypeName("ObjectID", KnownTypes.SystemUInt64);
29+
}
2730

2831
foreach (var nativeStructure in context.Api.NativeStructures)
2932
{

src/Godot.BindingsGeneration/BindingsDataCollectors/UtilityFunctionsBindingsDataCollector.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ internal sealed class UtilityFunctionsBindingsDataCollector : BindingsDataCollec
77
{
88
public override void Populate(BindingsData.CollectionContext context)
99
{
10+
if (context.IsExtension)
11+
{
12+
// Utility functions are only generated for the core API, not for GDExtensions.
13+
return;
14+
}
15+
1016
var utilityFunctionsType = new TypeInfo("UtilityFunctions", context.Options.Namespace)
1117
{
1218
VisibilityAttributes = VisibilityAttributes.Assembly,

src/Godot.BindingsGeneration/BindingsDataCollectors/VariadicGenericsBindingsDataCollector.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ internal sealed class VariadicGenericsBindingsDataCollector : BindingsDataCollec
99
{
1010
public override void Populate(BindingsData.CollectionContext context)
1111
{
12+
if (context.IsExtension)
13+
{
14+
// Variadic generics are only generated for the core API, not for GDExtensions.
15+
return;
16+
}
17+
1218
// Generate GodotObject.TryCallVirtualMethod<T..> and GodotObject.CallVirtualMethod<T..>
1319
{
1420
var type = new TypeInfo("GodotObject", context.Options.Namespace)

src/Godot.BindingsGeneration/BindingsGenerator/BindingsData.CollectionContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ internal sealed class CollectionContext
2424
/// </summary>
2525
public GodotApi Api { get; }
2626

27+
/// <summary>
28+
/// Indicates whether the API being generated is a GDExtension instead of the core engine API.
29+
/// </summary>
30+
public bool IsExtension { get; init; }
31+
2732
/// <summary>
2833
/// Options that were provided to the generator to configure how the
2934
/// bindings should be generated.

0 commit comments

Comments
 (0)