Skip to content

Commit

Permalink
feat: Add type safe Signal support
Browse files Browse the repository at this point in the history
Add objects for connecting signals from Godot objects.

Need to support exporting these signals to Godot, and add documentation.

Also add support for Dart getters to be Godot Properties.
  • Loading branch information
fuzzybinary committed Feb 2, 2025
1 parent 8b07a17 commit 9c90f0f
Show file tree
Hide file tree
Showing 31 changed files with 635 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ working, 🟨 - Partially working, ❌ - Not working)
| Memory efficiency / Leak prevention || All RefCounted objects appear to be working correclty. |
| Godot Editor inspector integration || |
| Godot Editor -> Dart LSP Integration || |
| Dart Macro Support || |
| Augmentation Support || |

# Setup

Expand Down
4 changes: 2 additions & 2 deletions example/2d_tutorial/src/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,14 @@ packages:
path: "../../../src/dart/godot_dart"
relative: true
source: path
version: "0.7.0"
version: "0.8.0"
godot_dart_build:
dependency: "direct dev"
description:
path: "../../../src/dart/godot_dart_build"
relative: true
source: path
version: "0.5.0"
version: "0.6.0"
graphs:
dependency: transitive
description:
Expand Down
4 changes: 2 additions & 2 deletions example/networking-demo/src/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,14 @@ packages:
path: "../../../src/dart/godot_dart"
relative: true
source: path
version: "0.7.0"
version: "0.8.0"
godot_dart_build:
dependency: "direct dev"
description:
path: "../../../src/dart/godot_dart_build"
relative: true
source: path
version: "0.5.0"
version: "0.6.0"
graphs:
dependency: transitive
description:
Expand Down
4 changes: 2 additions & 2 deletions src/assets/src/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ environment:

dependencies:
ffi: ^2.0.1
godot_dart: ^0.6.0
godot_dart: ^0.9.0
collection: ^1.17.2

dev_dependencies:
lints: ^2.0.0
build_runner: ^2.3.3
godot_dart_build: ^0.4.0
godot_dart_build: ^0.7.0
21 changes: 5 additions & 16 deletions src/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,20 @@ add_subdirectory(${GODOT_CPP_DIR} "godot-cpp")

add_library(godot_dart SHARED
dart_bindings.cpp
dart_bindings.h
dart_instance_binding.cpp
dart_instance_binding.h
dart_helpers.h
"script/dart_script_instance.cpp"
"script/dart_script_instance.h"
gde_c_interface.cpp
gde_c_interface.h
gde_dart_converters.cpp
gde_dart_converters.h
gde_wrapper.cpp
gde_wrapper.h
godot_dart.cpp
godot_dart_runtime_plugin.h
godot_dart_runtime_plugin.cpp
godot_string_wrappers.cpp
godot_string_wrappers.h
ref_counted_wrapper.cpp
ref_counted_wrapper.h
editor/godot_dart_editor_plugin.cpp
editor/godot_dart_editor_plugin.h
editor/dart_templates.cpp
editor/dart_templates.h
"editor/dart_progress_dialog.h"
"editor/dart_progress_dialog.cpp"
script/dart_script_language.cpp
script/dart_script_language.h
script/dart_script.h
script/dart_script.cpp
script/dart_resource_format.h
script/dart_resource_format.cpp
)

Expand Down Expand Up @@ -101,6 +85,11 @@ elseif(LINUX)
set(THREADS_PREFER_PTHREAD_FLAG ON)

find_package(Threads REQUIRED)
# Find shared libraries next to the executable
set_target_properties(godot_dart PROPERTIES
BUILD_WITH_INSTALL_RPATH FALSE
LINK_FLAGS "-rpath,./")

target_link_libraries(godot_dart
Threads::Threads
${CMAKE_DL_LIBS}
Expand Down
131 changes: 126 additions & 5 deletions src/cpp/dart_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <dart_tools_api.h>

#include <gdextension_interface.h>
#include <godot_cpp/godot.hpp>
#include <godot_cpp/classes/editor_file_system.hpp>
#include <godot_cpp/classes/editor_interface.hpp>
#include <godot_cpp/variant/string.hpp>
Expand Down Expand Up @@ -79,6 +80,16 @@ bool GodotDartBindings::initialize(const char *script_path, const char *package_
_engine_classes_library = Dart_NewPersistentHandle(engine_classes_library);
}

// Find the Variant / builting classes library. This is needed to lookup variant types
{
Dart_Handle builtin_classes_package_name =
Dart_NewStringFromCString("package:godot_dart/src/gen/builtins.dart");
DART_CHECK_RET(variant_library, Dart_LookupLibrary(builtin_classes_package_name), false,
"GodotDart: Initialization Error (Could not find the `builtins.dart` "
"package)");
_variant_classes_library = Dart_NewPersistentHandle(variant_library);
}

// Find the DartBindings "library" (just the file) and set us as the native callback handler
{
Dart_Handle native_bindings_library_name =
Expand Down Expand Up @@ -242,7 +253,7 @@ void GodotDartBindings::execute_on_dart_thread(std::function<void()> work) {
_work_lock.unlock();
}

Dart_Handle GodotDartBindings::new_dart_void_pointer(void *ptr) {
Dart_Handle GodotDartBindings::new_dart_void_pointer(const void *ptr) {
Dart_Handle dart_int = Dart_NewIntegerFromUint64(reinterpret_cast<uint64_t>(ptr));
Dart_Handle args[1] = {dart_int};

Expand Down Expand Up @@ -404,13 +415,25 @@ Dart_Handle GodotDartBindings::find_dart_type(Dart_Handle type_name) {
type_name = Dart_NewStringFromCString("GodotObject");
}

// Check engine classes first:
DART_CHECK_RET(engine_classes_library, Dart_HandleFromPersistent(_engine_classes_library), Dart_Null(),
"Error getting class class library.")
"Error getting engine class library.")

DART_CHECK_RET(type, Dart_GetNonNullableType(engine_classes_library, type_name, 0, nullptr), Dart_Null(),
"Could not find type in the engine_classes_library.");
Dart_Handle type = Dart_GetNonNullableType(engine_classes_library, type_name, 0, nullptr);
if (!Dart_IsError(type)) {
return type;
}

DART_CHECK_RET(variant_library, Dart_HandleFromPersistent(_variant_classes_library), Dart_Null(),
"Error getting variant library.")
type = Dart_GetNonNullableType(variant_library, type_name, 0, nullptr);
if (!Dart_IsError(type)) {
return type;
}

return type;
GD_PRINT_ERROR("GodotDart: Could not find a needed type!");

return Dart_Null();
}

void GodotDartBindings::add_property(const TypeInfo &bind_type, Dart_Handle dart_prop_info) {
Expand Down Expand Up @@ -976,6 +999,104 @@ GDE_EXPORT GDExtensionScriptInstanceDataPtr get_script_instance(GDExtensionConst
return gde_object_get_script_instance(godot_object, script_language->_owner);
}

void call_dart_signal(void* callable_userdata, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) {
GodotDartBindings *bindings = GodotDartBindings::instance();

bindings->execute_on_dart_thread([&] {
DartBlockScope scope;

Dart_Handle signal = Dart_HandleFromPersistent((Dart_PersistentHandle)callable_userdata);
// Create List<Variant> from the variants we're given
Dart_Handle variant_type = Dart_HandleFromPersistent(bindings->_variant_type);
Dart_Handle null_variant = Dart_New(variant_type, Dart_Null(), 0, nullptr);
Dart_Handle signal_args = Dart_NewListOfTypeFilled(variant_type, null_variant, p_argument_count);
{

Dart_Handle variant_constructor_name = Dart_NewStringFromCString("fromVariantPtr");
Dart_Handle args[] = {
Dart_Null()
};

for(int i = 0; i < p_argument_count; ++i) {
GDExtensionConstVariantPtr variant_ptr = p_args[i];
args[0] = bindings->new_dart_void_pointer(variant_ptr);
Dart_Handle variant_arg = Dart_New(variant_type, variant_constructor_name, 1, args);
Dart_ListSetAt(signal_args, i, variant_arg);
}
}

Dart_Handle args[] = {
signal_args
};
Dart_Handle result = Dart_Invoke(signal, Dart_NewStringFromCString("call"), 1, args);
if (Dart_IsError(result)) {
GD_PRINT_ERROR("GodotDart: Error performing signal call: ");
GD_PRINT_ERROR(Dart_GetError(result));
*r_error = GDExtensionCallError{
GDEXTENSION_CALL_ERROR_INVALID_METHOD,
0, 0,
};
} else {
*r_error = GDExtensionCallError{
GDEXTENSION_CALL_OK, 0, 0
};
}
});
}

GDExtensionInt get_signal_argument_count(void* callable_userdata, GDExtensionBool* r_is_valid) {
GodotDartBindings *bindings = GodotDartBindings::instance();

int64_t arg_count;
bindings->execute_on_dart_thread([&] {
DartBlockScope scope;
Dart_Handle signal = Dart_HandleFromPersistent((Dart_PersistentHandle)callable_userdata);

Dart_Handle arg_count_h = Dart_GetField(signal, Dart_NewStringFromCString("arguments"));
Dart_IntegerToInt64(arg_count_h, &arg_count);
});

return arg_count;
}

void free_dart_signal(void* callable_userdata) {
GodotDartBindings *bindings = GodotDartBindings::instance();

bindings->execute_on_dart_thread([&] {
DartBlockScope scope;

Dart_Handle signal = Dart_HandleFromPersistent((Dart_PersistentHandle)callable_userdata);
Dart_Invoke(signal, Dart_NewStringFromCString("clear"), 0, nullptr);

Dart_DeletePersistentHandle((Dart_PersistentHandle)callable_userdata);
});
}

GDE_EXPORT Dart_Handle create_signal_callable(Dart_Handle signal_callable, GDObjectInstanceID target) {
GDEWrapper *gde = GDEWrapper::instance();

GDExtensionCallableCustomInfo2 info = {};
info.callable_userdata = Dart_NewPersistentHandle(signal_callable);
info.token = gde->get_library_ptr();
info.object_id = target;
info.call_func = call_dart_signal;
info.get_argument_count_func = get_signal_argument_count;

godot::Callable callable;
godot::internal::gdextension_interface_callable_custom_create2(callable._native_ptr(), &info);

GodotDartBindings *bindings = GodotDartBindings::instance();
DART_CHECK_RET(callable_type, bindings->find_dart_type(Dart_NewStringFromCString("Callable")), Dart_Null(), "Could not find Callable type!");

Dart_Handle callable_opaque_ptr = bindings->new_dart_void_pointer(callable._native_ptr());
Dart_Handle args[] {
callable_opaque_ptr,
};
DART_CHECK_RET(dart_callable, Dart_New(callable_type, Dart_NewStringFromCString("copyPtr"), 1, args), Dart_Null(), "Could not create Dart Callable.");

return dart_callable;
}

GDE_EXPORT void finalize_variant(GDExtensionVariantPtr variant) {
if (variant == nullptr) {
return;
Expand Down
3 changes: 2 additions & 1 deletion src/cpp/dart_bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class GodotDartBindings {
Dart_Handle find_dart_type(Dart_Handle type_name);
void add_property(const TypeInfo &bind_type, Dart_Handle dart_prop_info);
void execute_on_dart_thread(std::function<void()> work);
Dart_Handle new_dart_void_pointer(void *ptr);
Dart_Handle new_dart_void_pointer(const void *ptr);
void perform_frame_maintanance();

void add_pending_ref_change(DartGodotInstanceBinding *bindings);
Expand Down Expand Up @@ -84,6 +84,7 @@ class GodotDartBindings {

Dart_PersistentHandle _godot_dart_library;
Dart_PersistentHandle _engine_classes_library;
Dart_PersistentHandle _variant_classes_library;
Dart_PersistentHandle _native_library;

// Some things we need often
Expand Down
6 changes: 3 additions & 3 deletions src/cpp/dart_instance_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ static void __engine_binding_free_callback(void *p_token, void *p_instance, void
if (Dart_IsError(result)) {
GD_PRINT_ERROR("GodotDart: Error detaching owner during instance free: ");
GD_PRINT_ERROR(Dart_GetError(result));
}

delete binding;
}
}

delete binding;
});
}

Expand Down
15 changes: 14 additions & 1 deletion src/cpp/script/dart_script_instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,23 @@ void script_instance_free(GDExtensionScriptInstanceDataPtr p_instance) {
}

gde->execute_on_dart_thread([&] {
DartBlockScope scope;

DartScriptInstance *instance = reinterpret_cast<DartScriptInstance *>(p_instance);
if (instance->is_placeholder()) {
instance->get_dart_script()->dart_placeholder_erased(instance);
}
}

Dart_Handle dart_object = instance->get_dart_object();

if (!Dart_IsNull(dart_object)) {
Dart_Handle result = Dart_Invoke(dart_object, Dart_NewStringFromCString("detachOwner"), 0, nullptr);
if (Dart_IsError(result)) {
GD_PRINT_ERROR("GodotDart: Error detaching owner during instance free: ");
GD_PRINT_ERROR(Dart_GetError(result));
}
}

delete instance;
});
}
Expand Down
6 changes: 6 additions & 0 deletions src/dart/godot_dart/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.9.0

- Add type safe `SignalX` objects supporting automatic registering / deregistering.
- Fixed an issue with ScriptInstances not detaching themselves from their Dart counterparts on deletion.
- Attempted to refactor several files to make analysis faster.

## 0.8.0

- Fix casting to builtin types from `Variant`.
Expand Down
8 changes: 7 additions & 1 deletion src/dart/godot_dart/lib/godot_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ library godot_dart;

import 'dart:ffi';

import 'godot_dart.dart';
import 'src/core/gdextension.dart';
import 'src/core/gdextension_ffi_bindings.dart';
import 'src/core/type_info.dart';
import 'src/extensions/async_extensions.dart';
import 'src/gen/engine_classes.dart';
import 'src/gen/utility_functions.dart';
import 'src/reloader/hot_reloader.dart';
import 'src/variant/variant.dart';

export 'src/annotations/godot_script.dart';
export 'src/core/core_types.dart';
export 'src/core/signals.dart' hide SignalCallable;
export 'src/core/gdextension.dart';
export 'src/core/property_info.dart';
export 'src/core/rpc_info.dart';
Expand Down
Loading

0 comments on commit 9c90f0f

Please sign in to comment.