Skip to content

Commit

Permalink
feat: Use ptrcalls over variant calls
Browse files Browse the repository at this point in the history
Ptrcalls are more efficient as they don't need to go back and forth through Variant.
Also added support for vararg methods and fixes Godot calling un-overriden functions in Dart ScriptInstances.

There's some cleanup that can be done in the generator, as contructors also use ptrcalls that could be cleaned up to use the Engine versions.
  • Loading branch information
fuzzybinary committed Sep 8, 2024
1 parent beef3bc commit 7cf3ebd
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 73 deletions.
6 changes: 3 additions & 3 deletions src/cpp/dart_script_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ void DartScriptInstance::call(const godot::StringName *p_method, const GDExtensi
Dart_Invoke(dart_script_info, Dart_NewStringFromCString("getMethodInfo"), 1, method_info_args),
"Failed getting method");
if (Dart_IsNull(method_info)) {
r_error->error = GDEXTENSION_CALL_ERROR_INVALID_METHOD;
return;
}

Expand Down Expand Up @@ -364,14 +365,13 @@ void DartScriptInstance::call(const godot::StringName *p_method, const GDExtensi
gde_variant_new_copy(r_return, reinterpret_cast<GDExtensionConstVariantPtr>(variantDataPtr));
}
}

r_error->error = GDEXTENSION_CALL_OK;
});

if (dart_args != nullptr) {
delete[] dart_args;
}

// TODO: How do we throw exceptions in Godot?
r_error->error = GDEXTENSION_CALL_OK;
}

void DartScriptInstance::notification(int32_t p_what, bool p_reversed) {
Expand Down
2 changes: 2 additions & 0 deletions src/dart/godot_dart/lib/src/core/gdextension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class GodotDart {
for (int i = 0; i < args.length; ++i) {
(argArray + i).value = args[i];
}
int o = 5;
(argArray + 1).value = o as GDExtensionConstTypePtr;

ffiBindings.gde_object_method_bind_ptrcall(
function, instance?.nativePtr ?? nullptr, argArray, ret);
Expand Down
17 changes: 17 additions & 0 deletions src/dart/godot_dart/lib/src/godot_dart_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'dart:async';
import 'dart:ffi';

import 'package:ffi/ffi.dart';

import '../godot_dart.dart';
import 'core/gdextension_ffi_bindings.dart';

extension TNode on Node {
T? getNodeT<T>([String? path]) {
Expand Down Expand Up @@ -58,3 +62,16 @@ Future<void> toSignal(GodotObject source, String signal) {
final awaiter = SignalAwaiter(source: source, signalName: signal);
return awaiter.completer.future;
}

extension StringExtensions on String {
static String fromGodotStringPtr(GDExtensionTypePtr ptr) {
return using((arena) {
int length =
gde.ffiBindings.gde_string_to_utf16_chars(ptr.cast(), nullptr, 0);
final chars = arena.allocate<Uint16>(sizeOf<Uint16>() * length);
gde.ffiBindings
.gde_string_to_utf16_chars(ptr.cast(), chars.cast(), length);
return chars.cast<Utf16>().toDartString(length: length);
});
}
}
107 changes: 90 additions & 17 deletions tools/binding_generator/lib/src/common_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart';
import '../../godot_dart_extensions.dart';
import '../../core/core_types.dart';
import '../../core/gdextension_ffi_bindings.dart';
import '../../core/gdextension.dart';
Expand Down Expand Up @@ -75,9 +76,14 @@ import '../../variant/variant.dart';

String? argumentAllocation(ArgumentProxy arg) {
if (arg.needsAllocation) {
var ffiType = getFFIType(arg);
var ffiType = getFFIType(arg, forPtrCall: true);
final argName = escapeName(arg.name).toLowerCamelCase();
return 'final ${argName}Ptr = arena.allocate<$ffiType>(sizeOf<$ffiType>())..value = $argName;';
final bang = arg.defaultArgumentValue != null ? '!' : '';
final value = arg.typeCategory == TypeCategory.enumType ||
arg.typeCategory == TypeCategory.bitfieldType
? '.value'
: '';
return 'final ${argName}Ptr = arena.allocate<$ffiType>(sizeOf<$ffiType>())..value = $argName$bang$value;';
} else if (arg.typeCategory == TypeCategory.engineClass) {
final argName = escapeName(arg.name).toLowerCamelCase();
return 'final ${argName}Ptr = arena.allocate<GDExtensionObjectPtr>(sizeOf<GDExtensionObjectPtr>())..value = ($argName?.nativePtr ?? nullptr);';
Expand Down Expand Up @@ -469,62 +475,129 @@ List<String> getUsedTypes(Map<String, dynamic> api) {
return usedTypes.toList();
}

void convertPtrArgument(int index, ArgumentProxy argument, CodeSink o) {
void convertPtrArgumentToDart(
String argumentName, ArgumentProxy argument, CodeSink o) {
// Special case, converting to Dart strings from GDString or StringName
final varName = escapeName(argument.name).toLowerCamelCase();
var decl = 'final ${argument.dartType} $varName';
if (argument.type == 'String') {
o.p('final GDString gd$varName = GDString.copyPtr((args + $index).value);');
o.p('final GDString gd$varName = GDString.copyPtr(${argumentName}.value);');
o.p('$decl = gd$varName.toDartString();');
return;
} else if (argument.type == 'StringName') {
o.p('final StringName gd$varName = StringName.copyPtr((args + $index).value);');
o.p('final StringName gd$varName = StringName.copyPtr(${argumentName}.value);');
o.p('$decl = GDString.fromStringName(gd$varName).toDartString();');
return;
}

switch (argument.typeCategory) {
case TypeCategory.engineClass:
if (argument.isRefCounted) {
o.p('$decl = ${argument.rawDartType}.fromOwner(gde.ffiBindings.gde_ref_get_object((args + $index).value));');
o.p('$decl = ${argument.rawDartType}.fromOwner(gde.ffiBindings.gde_ref_get_object(${argumentName}.value));');
} else {
o.p('$decl = ${argument.rawDartType}.fromOwner((args + $index).cast<Pointer<Pointer<Void>>>().value.value);');
o.p('$decl = ${argument.rawDartType}.fromOwner(${argumentName}.cast<Pointer<Pointer<Void>>>().value.value);');
}
break;
case TypeCategory.builtinClass:
if (argument.type == 'Variant') {
o.p('$decl = Variant.fromVariantPtr((args + $index).value);');
o.p('$decl = Variant.fromVariantPtr(${argumentName}.value);');
} else {
o.p('$decl = ${argument.rawDartType}.copyPtr((args + $index).value);');
o.p('$decl = ${argument.rawDartType}.copyPtr(${argumentName}.value);');
}
break;
case TypeCategory.primitive:
final castType =
argument.isPointer ? argument.dartType : getFFIType(argument);
o.p('$decl = (args + $index).cast<Pointer<$castType>>().value.value;');
o.p('$decl = ${argumentName}.cast<Pointer<$castType>>().value.value;');
break;
case TypeCategory.nativeStructure:
if (argument.isOptional) {
o.p('final ${argument.name}Ptr = (args + $index).cast<Pointer<${argument.dartType}>>().value;');
o.p('final ${argument.name}Ptr = ${argumentName}.cast<Pointer<${argument.dartType}>>().value;');
o.p('$decl = ${argument.name}Ptr == nullptr ? null : ${argument.name}Ptr.ref;');
} else if (argument.isPointer) {
o.p('$decl = (args + $index).cast<Pointer<${argument.dartType}>>().value.value;');
o.p('$decl = ${argumentName}.cast<Pointer<${argument.dartType}>>().value.value;');
} else {
o.p('$decl = (args + $index).cast<Pointer<${argument.dartType}>>().value.ref;');
o.p('$decl = ${argumentName}.cast<Pointer<${argument.dartType}>>().value.ref;');
}
break;
case TypeCategory.enumType:
o.p('$decl = ${argument.dartType}.fromValue((args + $index).cast<Pointer<Uint32>>().value.value);');
o.p('$decl = ${argument.dartType}.fromValue(${argumentName}.cast<Pointer<Uint32>>().value.value);');
break;
case TypeCategory.bitfieldType:
o.p('$decl = (args + $index).cast<Pointer<Uint32>>().value.value;');
o.p('$decl = ${argumentName}.cast<Pointer<Uint32>>().value.value;');
break;
case TypeCategory.typedArray:
o.p('$decl = ${argument.dartType}.copyPtr((args + $index).value);');
o.p('$decl = ${argument.dartType}.copyPtr(${argumentName}.value);');
break;
case TypeCategory.voidType:
if (argument.dartType.startsWith('Pointer')) {
o.p('$decl = (args + $index).value;');
o.p('$decl = ${argumentName}.value;');
}
break;
}
}

void converDartToPtrArgument(
String argumentName, ArgumentProxy argument, CodeSink o) {
// Special case, converting to Dart strings from GDString or StringName
final varName = escapeName(argument.name).toLowerCamelCase();
if (argument.type == 'String') {
o.p('final gd$varName = GDString.fromString($varName);');
o.p('$argumentName.value = gd$varName.nativePtr.cast();');
return;
} else if (argument.type == 'StringName') {
o.p('final gd$varName = StringName.fromString($varName);');
o.p('$argumentName.value = gd$varName.nativePtr.cast();');
return;
}

switch (argument.typeCategory) {
case TypeCategory.voidType:
throw Exception('Invalid voidType as argument!');
case TypeCategory.primitive:
var ffiType = getFFIType(argument, forPtrCall: true);
if (ffiType == null && argument.isPointer) {
ffiType = 'Pointer<Void>';
}
o.p('final ${varName}Ptr = arena.allocate<$ffiType>(sizeOf<$ffiType>())..value = $varName;');
o.p('$argumentName.value = ${varName}Ptr.cast();');
break;
case TypeCategory.engineClass:
final value = argument.isOptional
? '$varName?.nativePtr ?? nullptr'
: '$varName.nativePtr';
o.p('final ${varName}Ptr = arena.allocate<GDExtensionConstTypePtr>(sizeOf<GDExtensionObjectPtr>())..value = $value;');
o.p('$argumentName.value = ${varName}Ptr.cast();');
break;
case TypeCategory.builtinClass:
final bang = argument.defaultArgumentValue != null ? '!' : '';
//final optional = argument.isOptional ? ' ?? nullptr' : '';
o.p('$argumentName.value = $varName$bang.nativePtr.cast();');
break;
case TypeCategory.nativeStructure:
if (argument.isPointer) {
o.p('final ${varName}Ptr = arena.allocate<GDExtensionPtr>(sizeOf<GDExtensionConstTypePtr>())..value = $varName.nativerPtr;');
o.p('$argumentName.value = ${varName}Ptr;');
} else {
final optional = argument.isOptional ? ' ?? nullptr' : '';
o.p('$argumentName.value = ${varName}.nativePtr$optional;');
}
break;
case TypeCategory.enumType:
final bang = argument.defaultArgumentValue != null ? '!' : '';
o.p('final ${varName}Ptr = arena.allocate<Uint32>(sizeOf<Uint32>())..value = $varName$bang.value;');
o.p('$argumentName.value = ${varName}Ptr.cast();');
break;
case TypeCategory.bitfieldType:
o.p('final ${varName}Ptr = arena.allocate<Uint32>(sizeOf<Uint32>())..value = $varName;');
o.p('$argumentName.value = ${varName}Ptr.cast();');
break;
case TypeCategory.typedArray:
if (argument.isOptional) {
o.p('$argumentName.value = ${varName}?.nativePtr?.cast() ?? nullptr;');
} else {
final bang = argument.defaultArgumentValue != null ? '!' : '';
o.p('$argumentName.value = $varName$bang.nativePtr.cast();');
}
break;
}
Expand Down
Loading

0 comments on commit 7cf3ebd

Please sign in to comment.