From 73488f5afdd119adfba34674c8cebc541ec42a5b Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Fri, 2 May 2025 01:34:31 +0200 Subject: [PATCH] [GDExtension] Add function to register main loop callbacks --- core/extension/gdextension.cpp | 8 ++++ core/extension/gdextension.h | 5 +++ core/extension/gdextension_interface.h | 26 +++++++++++++ core/extension/gdextension_manager.cpp | 53 ++++++++++++++++++++++++++ core/extension/gdextension_manager.h | 7 ++++ main/main.cpp | 6 +++ 6 files changed, 105 insertions(+) diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 77b24ccd21e..e086d81fd99 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -686,6 +686,13 @@ void GDExtension::_register_get_classes_used_callback(GDExtensionClassLibraryPtr #endif } +void GDExtension::_register_main_loop_callbacks(GDExtensionClassLibraryPtr p_library, const GDExtensionMainLoopCallbacks *p_callbacks) { + GDExtension *self = reinterpret_cast(p_library); + self->startup_callback = p_callbacks->startup_func; + self->shutdown_callback = p_callbacks->shutdown_func; + self->frame_callback = p_callbacks->frame_func; +} + void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) { ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), vformat("Attempt to register interface function '%s', which appears to be already registered.", p_function_name)); gdextension_interface_functions.insert(p_function_name, p_function_pointer); @@ -805,6 +812,7 @@ void GDExtension::initialize_gdextensions() { register_interface_function("classdb_unregister_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_unregister_extension_class); register_interface_function("get_library_path", (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path); register_interface_function("editor_register_get_classes_used_callback", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_get_classes_used_callback); + register_interface_function("register_main_loop_callbacks", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_main_loop_callbacks); } void GDExtension::finalize_gdextensions() { diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 0daf57d6da9..fe3aa03eb40 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -94,6 +94,7 @@ class GDExtension : public Resource { static void _unregister_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name); static void _get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path); static void _register_get_classes_used_callback(GDExtensionClassLibraryPtr p_library, GDExtensionEditorGetClassesUsedCallback p_callback); + static void _register_main_loop_callbacks(GDExtensionClassLibraryPtr p_library, const GDExtensionMainLoopCallbacks *p_callbacks); GDExtensionInitialization initialization; int32_t level_initialized = -1; @@ -115,6 +116,10 @@ class GDExtension : public Resource { void clear_instance_bindings(); #endif + GDExtensionMainLoopStartupCallback startup_callback = nullptr; + GDExtensionMainLoopShutdownCallback shutdown_callback = nullptr; + GDExtensionMainLoopFrameCallback frame_callback = nullptr; + static inline HashMap gdextension_interface_functions; protected: diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 470fc56d25f..8ae5ccf3bee 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -805,6 +805,21 @@ typedef struct { const char *string; // (e.g. "Godot v3.1.4.stable.official.mono") } GDExtensionGodotVersion2; +/* Called when starting the main loop. */ +typedef void (*GDExtensionMainLoopStartupCallback)(); + +/* Called when shutting down the main loop. */ +typedef void (*GDExtensionMainLoopShutdownCallback)(); + +/* Called for every frame iteration of the main loop. */ +typedef void (*GDExtensionMainLoopFrameCallback)(); + +typedef struct { + GDExtensionMainLoopStartupCallback startup_func; + GDExtensionMainLoopShutdownCallback shutdown_func; + GDExtensionMainLoopFrameCallback frame_func; +} GDExtensionMainLoopCallbacks; + /** * @name get_godot_version * @since 4.1 @@ -3131,6 +3146,17 @@ typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const */ typedef void (*GDExtensionInterfaceEditorRegisterGetClassesUsedCallback)(GDExtensionClassLibraryPtr p_library, GDExtensionEditorGetClassesUsedCallback p_callback); +/** + * @name register_main_loop_callbacks + * @since 4.5 + * + * Registers callbacks to be called at different phases of the main loop. + * + * @param p_library A pointer the library received by the GDExtension's entry point function. + * @param p_callback A pointer to the structure that contains the callbacks. + */ +typedef void (*GDExtensionInterfaceRegisterMainLoopCallbacks)(GDExtensionClassLibraryPtr p_library, const GDExtensionMainLoopCallbacks *p_callbacks); + #ifdef __cplusplus } #endif diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 07e7588bac2..0258a626950 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -60,6 +60,14 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons emit_signal("extension_loaded", p_extension); #endif + if (startup_callback_called) { + // Extension is loading after the startup callback has already been called, + // so we call it now for this extension to make sure it doesn't miss it. + if (p_extension->startup_callback) { + p_extension->startup_callback(); + } + } + return LOAD_STATUS_OK; } @@ -76,10 +84,24 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co } } + if (!shutdown_callback_called) { + // Extension is unloading before the shutdown callback has been called, + // which means the engine hasn't shutdown yet but we want to make sure + // to call the shutdown callback so it doesn't miss it. + if (p_extension->shutdown_callback) { + p_extension->shutdown_callback(); + } + } + for (const KeyValue &kv : p_extension->class_icon_paths) { gdextension_class_icon_paths.erase(kv.key); } + // Clear main loop callbacks. + p_extension->startup_callback = nullptr; + p_extension->shutdown_callback = nullptr; + p_extension->frame_callback = nullptr; + return LOAD_STATUS_OK; } @@ -387,6 +409,37 @@ bool GDExtensionManager::ensure_extensions_loaded(const HashSet &p_exten return needs_restart; } +void GDExtensionManager::startup() { + startup_callback_called = true; + + for (const KeyValue> &E : gdextension_map) { + const Ref &extension = E.value; + if (extension->startup_callback) { + extension->startup_callback(); + } + } +} + +void GDExtensionManager::shutdown() { + shutdown_callback_called = true; + + for (const KeyValue> &E : gdextension_map) { + const Ref &extension = E.value; + if (extension->shutdown_callback) { + extension->shutdown_callback(); + } + } +} + +void GDExtensionManager::frame() { + for (const KeyValue> &E : gdextension_map) { + const Ref &extension = E.value; + if (extension->frame_callback) { + extension->frame_callback(); + } + } +} + GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; } diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index e0053e8a47d..c9538451bbd 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -39,6 +39,9 @@ class GDExtensionManager : public Object { HashMap> gdextension_map; HashMap gdextension_class_icon_paths; + bool startup_callback_called = false; + bool shutdown_callback_called = false; + static void _bind_methods(); static inline GDExtensionManager *singleton = nullptr; @@ -86,6 +89,10 @@ public: void reload_extensions(); bool ensure_extensions_loaded(const HashSet &p_extensions); + void startup(); + void shutdown(); + void frame(); + GDExtensionManager(); ~GDExtensionManager(); }; diff --git a/main/main.cpp b/main/main.cpp index 04d1503bcbe..8f668d001b5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4562,6 +4562,8 @@ int Main::start() { movie_writer->begin(DisplayServer::get_singleton()->window_get_size(), fixed_fps, Engine::get_singleton()->get_write_movie_path()); } + GDExtensionManager::get_singleton()->startup(); + if (minimum_time_msec) { uint64_t minimum_time = 1000 * minimum_time_msec; uint64_t elapsed_time = OS::get_singleton()->get_ticks_usec(); @@ -4767,6 +4769,8 @@ bool Main::iteration() { process_max = MAX(process_ticks, process_max); uint64_t frame_time = OS::get_singleton()->get_ticks_usec() - ticks; + GDExtensionManager::get_singleton()->frame(); + for (int i = 0; i < ScriptServer::get_language_count(); i++) { ScriptServer::get_language(i)->frame(); } @@ -4887,6 +4891,8 @@ void Main::cleanup(bool p_force) { } #endif + GDExtensionManager::get_singleton()->shutdown(); + for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { TextServerManager::get_singleton()->get_interface(i)->cleanup(); }