Skip to content

Commit

Permalink
feat: Generate Godot utility functions
Browse files Browse the repository at this point in the history
This allows us to get weak references on objects.
Fix Variant casting to prevent a crash.
Add `getWeak` extension method on GodotObject

refs: #22
  • Loading branch information
fuzzybinary committed Oct 27, 2024
1 parent f5abb87 commit 4f6767a
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/cpp/dart_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1034,4 +1034,5 @@ GDE_EXPORT void *safe_new_persistent_handle(Dart_Handle handle) {

return (void *)result;
}

}
10 changes: 10 additions & 0 deletions src/cpp/gde_c_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ void gde_variant_set_indexed(GDExtensionVariantPtr p_self, GDExtensionInt p_inde
}
}

static GDExtensionInterfaceVariantGetPtrUtilityFunction _variant_get_ptr_utility_function_func = nullptr;
GDExtensionPtrUtilityFunction gde_variant_get_ptr_utility_function(GDExtensionConstStringNamePtr p_function,
GDExtensionInt p_hash) {
if (gde_variant_get_ptr_utility_function) {
return _variant_get_ptr_utility_function_func(p_function, p_hash);
}
return nullptr;
}

static GDExtensionInterfaceStringNewWithUtf8Chars _string_new_with_utf8_chars_func = nullptr;
void gde_string_new_with_utf8_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents) {
if (_string_new_with_utf8_chars_func) {
Expand Down Expand Up @@ -400,6 +409,7 @@ void gde_init_c_interface(GDExtensionInterfaceGetProcAddress get_proc_address) {
LOAD_METHOD(variant_get_ptr_keyed_checker, GDExtensionInterfaceVariantGetPtrKeyedChecker);
LOAD_METHOD(variant_get_indexed, GDExtensionInterfaceVariantGetIndexed);
LOAD_METHOD(variant_set_indexed, GDExtensionInterfaceVariantSetIndexed);
LOAD_METHOD(variant_get_ptr_utility_function, GDExtensionInterfaceVariantGetPtrUtilityFunction);
LOAD_METHOD(string_new_with_utf8_chars, GDExtensionInterfaceStringNewWithUtf8Chars);
LOAD_METHOD(string_to_utf8_chars, GDExtensionInterfaceStringToUtf8Chars);
LOAD_METHOD(string_to_utf16_chars, GDExtensionInterfaceStringToUtf16Chars);
Expand Down
2 changes: 2 additions & 0 deletions src/cpp/gde_c_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ GDE_EXPORT void gde_variant_set_indexed(GDExtensionVariantPtr p_self, GDExtensio
GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid,
GDExtensionBool *r_oob);

GDE_EXPORT GDExtensionPtrUtilityFunction gde_variant_get_ptr_utility_function(GDExtensionConstStringNamePtr p_function,
GDExtensionInt p_hash);
GDE_EXPORT void gde_string_new_with_utf8_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents);
GDE_EXPORT GDExtensionInt gde_string_to_utf8_chars(GDExtensionConstStringPtr p_self, char *r_text,
GDExtensionInt p_max_write_length);
Expand Down
4 changes: 3 additions & 1 deletion src/cpp/godot_dart_runtime_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ void GodotDartRuntimePlugin::shutdown_dart_bindings() {
}
} else {
// This should also not happen. If it's weak, Dart should have destroyed it.
assert(false);
// TODO: This does appear to happen if Dart is holding a WeakRef object, but not
// sure there's anything we have to do here
// assert(false);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/dart/godot_dart/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- Adjust generate global constats to avoid unnecessary prefixes.
- Have `Variant.getType` return `VariantType` instead of int.
- Add `Variant.cast` to support getting an object directly from a Variant.
- Add generation of Godot utility functions under `GD` static class.
- Add `getWeak` extension method on `GodotObject`.

## 0.5.2

Expand Down
2 changes: 2 additions & 0 deletions src/dart/godot_dart/lib/godot_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export 'src/extensions/async_extensions.dart';
export 'src/extensions/core_extensions.dart';
export 'src/gen/classes/engine_classes.dart';
export 'src/gen/global_constants.dart';
export 'src/gen/utility_functions.dart';
export 'src/gen/variant/builtins.dart';
export 'src/variant/variant.dart' hide getToTypeConstructor;

Expand Down Expand Up @@ -49,6 +50,7 @@ void _registerGodot(int libraryAddress, int bindingCallbacks) {
initVariantBindings(ffiInterface);
TypeInfo.initTypeMappings();

GD.initBindings();
SignalAwaiter.bind();
CallbackAwaiter.bind();

Expand Down
29 changes: 29 additions & 0 deletions src/dart/godot_dart/lib/src/core/gdextension_ffi_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,25 @@ class GDExtensionFFI {
void Function(GDExtensionVariantPtr, int, GDExtensionConstVariantPtr,
ffi.Pointer<GDExtensionBool>, ffi.Pointer<GDExtensionBool>)>();

GDExtensionPtrUtilityFunction gde_variant_get_ptr_utility_function(
GDExtensionConstStringNamePtr p_function,
int p_hash,
) {
return _gde_variant_get_ptr_utility_function(
p_function,
p_hash,
);
}

late final _gde_variant_get_ptr_utility_functionPtr = _lookup<
ffi.NativeFunction<
GDExtensionPtrUtilityFunction Function(GDExtensionConstStringNamePtr,
GDExtensionInt)>>('gde_variant_get_ptr_utility_function');
late final _gde_variant_get_ptr_utility_function =
_gde_variant_get_ptr_utility_functionPtr.asFunction<
GDExtensionPtrUtilityFunction Function(
GDExtensionConstStringNamePtr, int)>();

void gde_string_new_with_utf8_chars(
GDExtensionUninitializedStringPtr r_dest,
ffi.Pointer<ffi.Char> p_contents,
Expand Down Expand Up @@ -2409,6 +2428,16 @@ typedef GDExtensionPtrKeyedCheckerFunction = ffi.Uint32 Function(
GDExtensionConstVariantPtr p_base, GDExtensionConstVariantPtr p_key);
typedef DartGDExtensionPtrKeyedCheckerFunction = int Function(
GDExtensionConstVariantPtr p_base, GDExtensionConstVariantPtr p_key);
typedef GDExtensionPtrUtilityFunction
= ffi.Pointer<ffi.NativeFunction<GDExtensionPtrUtilityFunctionFunction>>;
typedef GDExtensionPtrUtilityFunctionFunction = ffi.Void Function(
GDExtensionTypePtr r_return,
ffi.Pointer<GDExtensionConstTypePtr> p_args,
ffi.Int p_argument_count);
typedef DartGDExtensionPtrUtilityFunctionFunction = void Function(
GDExtensionTypePtr r_return,
ffi.Pointer<GDExtensionConstTypePtr> p_args,
int p_argument_count);
typedef GDExtensionConstStringPtr = ffi.Pointer<ffi.Void>;
typedef char16_t = ffi.Uint16;
typedef Dartchar16_t = int;
Expand Down
6 changes: 6 additions & 0 deletions src/dart/godot_dart/lib/src/extensions/core_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ extension StringExtensions on String {
});
}
}

extension WeakRefExtension on Object {
WeakRef? getWeak() {
return GD.weakref(Variant(this)).cast<WeakRef>();
}
}
8 changes: 6 additions & 2 deletions src/dart/godot_dart/lib/src/variant/variant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,12 @@ class Variant implements Finalizable {
}

T? cast<T>() {
var typeInfo = gde.dartBindings.getGodotTypeInfo(T);
return convertFromVariant(this, typeInfo) as T?;
if (T is BuiltinType) {
var typeInfo = gde.dartBindings.getGodotTypeInfo(T);
return convertFromVariant(this, typeInfo) as T?;
}
final obj = convertFromVariant(this, GodotObject.sTypeInfo) as GodotObject;
return obj.cast<T>();
}

void _attachFinalizer() {
Expand Down
79 changes: 79 additions & 0 deletions tools/binding_generator/lib/binding_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Future<void> generate(GenerationOptions options) async {
await generateGlobalConstants(
apiInfo, options.outputDirectory, options.buildConfig);

print('Generating utility functions...');
await generateUtilityFunctions(
apiInfo, options.outputDirectory, options.buildConfig);

print('Generating native structures...');
await generateNativeStructures(
apiInfo, options.outputDirectory, options.buildConfig);
Expand All @@ -79,3 +83,78 @@ Future<void> generateGlobalConstants(

await o.close();
}

Future<void> generateUtilityFunctions(
GodotApiInfo apiInfo, String outputDirectory, String buildConfig) async {
final file = File(path.join(outputDirectory, 'utility_functions.dart'));
final o = CodeSink(file);

o.p(header);
o.p("import 'dart:ffi';");
o.nl();
o.p("import 'package:ffi/ffi.dart';");
o.nl();
o.p("import '../core/gdextension_ffi_bindings.dart';");
o.p("import '../core/gdextension.dart';");
o.p("import '../variant/variant.dart';");
o.p("import 'classes/object.dart';");
o.p("import 'variant/packed_int64_array.dart';");
o.p("import 'variant/packed_byte_array.dart';");
o.p("import 'variant/rid.dart';");
o.p("import 'variant/string.dart';");
o.p("import 'variant/string_name.dart';");

o.nl();
o.b('class GD {', () {
// Functions
for (final utilityFunction in apiInfo.api.utilityFunctions) {
final methodSignature = makeSignature(utilityFunction);
o.b('$methodSignature {', () {
final arguments =
utilityFunction.arguments?.map((e) => e.proxy).toList() ?? [];
final returnInfo =
ArgumentProxy.fromReturnType(utilityFunction.returnType);
final hasReturn = returnInfo.typeCategory != TypeCategory.voidType;
final retString = hasReturn ? 'return ' : '';
o.b('${retString}using((arena) {', () {
final argumentsVar = createPtrcallArguments(o, arguments);

if (hasReturn) {
writeReturnAllocation(returnInfo, o);
}

final returnPtr = hasReturn ? 'retPtr.cast()' : 'nullptr.cast()';

o.p('void Function(GDExtensionTypePtr, Pointer<GDExtensionConstTypePtr>, int) m =');
o.p(' _bindings.${utilityFunction.name}Func!.asFunction();');
o.p('m($returnPtr, $argumentsVar, ${arguments.length});');

if (hasReturn) {
writeReturnRead(returnInfo, o);
}
}, '});');
}, '}');
o.nl();
}
o.nl();

// Binding init
o.p('static final _bindings = _UtilityFunctionBindings();');
o.b('static initBindings() {', () {
o.p('final ffi = gde.ffiBindings;');
for (final utilityFunction in apiInfo.api.utilityFunctions) {
final name = utilityFunction.name;
o.p("_bindings.${name}Func = ffi.gde_variant_get_ptr_utility_function(StringName.fromString('$name').nativePtr.cast(), ${utilityFunction.hash});");
}
}, '}');
}, '}');

// Binding class
o.b('class _UtilityFunctionBindings {', () {
for (final utilityFunction in apiInfo.api.utilityFunctions) {
final name = utilityFunction.name;
o.p('GDExtensionPtrUtilityFunction? ${name}Func;');
}
}, '}');
o.nl();
}
20 changes: 20 additions & 0 deletions tools/binding_generator/lib/src/common_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -360,13 +360,33 @@ extension ClassMethodConverter on BuiltinClassMethod {
}
}

extension UtilityMethodConverter on UtilityFunction {
ClassMethod asMethodData() {
return ClassMethod(
name: name,
isConst: false,
isVararg: isVararg,
isStatic: true,
isVirtual: false,
hash: hash,
returnValue: returnType == null
? null
: ReturnValue(type: returnTypeValues.reverse[returnType!]!),
arguments:
arguments?.map((e) => Argument(name: e.name, type: e.type)).toList(),
);
}
}

String makeSignature(dynamic functionData, {bool useGodotStringTypes = false}) {
assert(functionData is BuiltinClassMethod || functionData is ClassMethod);
ClassMethod methodData;
if (functionData is ClassMethod) {
methodData = functionData;
} else if (functionData is BuiltinClassMethod) {
methodData = functionData.asMethodData();
} else if (functionData is UtilityFunction) {
methodData = functionData.asMethodData();
} else {
return '';
}
Expand Down
25 changes: 25 additions & 0 deletions tools/binding_generator/lib/src/godot_api_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,31 @@ class ArgumentProxy {
);
}

factory ArgumentProxy.fromReturnType(ReturnType? returnType) {
if (returnType == null) return ArgumentProxy.fromTypeName('void');

final typeName = returnTypeValues.reverse[returnType]!;
final dartType = godotTypeToDartType(typeName);
final typeCategory = GodotApiInfo.instance().getTypeCategory(typeName);
final isRefCounted = typeCategory == TypeCategory.engineClass &&
GodotApiInfo.instance().isRefCounted(typeName);
final isOptional = typeCategory == TypeCategory.engineClass;
return ArgumentProxy._(
name: 'ret',
type: typeName,
rawDartType: godotTypeToRawDartType(typeName),
dartType: dartType,
isOptional: isOptional,
isPointer: false,
isRefCounted: isRefCounted,
typeCategory: typeCategory,
meta: null,
defaultArgumentValue: null,
defaultReturnValue:
_getDefaultReturnValue(typeName, dartType, isOptional),
);
}

factory ArgumentProxy.fromTypeName(String? typeName) {
final dartType = godotTypeToDartType(typeName);
final isPointer = typeName?.endsWith('*') ?? false;
Expand Down

0 comments on commit 4f6767a

Please sign in to comment.