From 6c44c80c62b0e0b778f5340f18e4de108de4ecd5 Mon Sep 17 00:00:00 2001 From: Gergely Kis Date: Wed, 24 Sep 2025 06:36:20 +0200 Subject: [PATCH] LibGodot: Core - Build Godot Engine as a Library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a new GodotInstance GDCLASS that provides startup and iteration commands to control a Godot instance. * Adds a libgodot_create_godot_instance entry point that creates a new Godot instance and returns a GodotInstance object. * Adds a libgodot_destroy_godot_instance entry point that destroys the Godot instance. Sample Apps: https://github.com/migeran/libgodot_project Developed by [Migeran](https://migeran.com) Sponsors & Acknowledgements: * Initial development sponsored by [Smirk Software](https://www.smirk.gg/) * Rebasing to Godot 4.3 and further development sponsored by [Xibbon Inc.](https://xibbon.com) * The GDExtension registration of the host process & build system changes were based on @Faolan-Rad's LibGodot PR: https://github.com/godotengine/godot/pull/72883 * Thanks to Ben Rog-Wilhelm (Zorbathut) for creating a smaller, minimal version for easier review. * Thanks to Ernest Lee (iFire) for his support Co-Authored-By: Gabor Koncz Co-Authored-By: Ben Rog-Wilhelm --- SConstruct | 15 +++ core/config/project_settings.cpp | 5 + .../extension/gdextension_function_loader.cpp | 74 ++++++++++ core/extension/gdextension_function_loader.h | 54 ++++++++ core/extension/gdextension_manager.cpp | 11 +- core/extension/gdextension_manager.h | 4 + core/extension/godot_instance.cpp | 126 ++++++++++++++++++ core/extension/godot_instance.h | 59 ++++++++ core/extension/libgodot.h | 73 ++++++++++ core/register_core_types.cpp | 3 + core/variant/native_ptr.h | 1 + doc/classes/GDExtensionManager.xml | 8 ++ doc/classes/GodotInstance.xml | 55 ++++++++ methods.py | 10 +- platform/linuxbsd/SCsub | 13 +- platform/linuxbsd/detect.py | 2 +- platform/linuxbsd/libgodot_linuxbsd.cpp | 71 ++++++++++ platform/macos/SCsub | 13 +- platform/macos/detect.py | 7 +- platform/macos/libgodot_macos.mm | 74 ++++++++++ platform/windows/SCsub | 15 ++- platform/windows/detect.py | 4 +- platform/windows/libgodot_windows.cpp | 71 ++++++++++ 23 files changed, 752 insertions(+), 16 deletions(-) create mode 100644 core/extension/gdextension_function_loader.cpp create mode 100644 core/extension/gdextension_function_loader.h create mode 100644 core/extension/godot_instance.cpp create mode 100644 core/extension/godot_instance.h create mode 100644 core/extension/libgodot.h create mode 100644 doc/classes/GodotInstance.xml create mode 100644 platform/linuxbsd/libgodot_linuxbsd.cpp create mode 100644 platform/macos/libgodot_macos.mm create mode 100644 platform/windows/libgodot_windows.cpp diff --git a/SConstruct b/SConstruct index d2a34358363..2263f817555 100644 --- a/SConstruct +++ b/SConstruct @@ -264,6 +264,14 @@ opts.Add( True, ) ) +opts.Add( + EnumVariable( + "library_type", + "Build library type", + "executable", + ("executable", "static_library", "shared_library"), + ) +) # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) @@ -556,6 +564,13 @@ if not env["deprecated"]: if env["precision"] == "double": env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) +# Library Support +if env["library_type"] != "executable": + if "library" not in env.get("supported", []): + print_error(f"Library builds unsupported for {env['platform']}") + Exit(255) + env.Append(CPPDEFINES=["LIBGODOT_ENABLED"]) + # Default num_jobs to local cpu count if not user specified. # SCons has a peculiarity where user-specified options won't be overridden # by SetOption, so we can rely on this to know if we should use our default. diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 4d269c6a0fb..81004dc51c7 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -83,6 +83,11 @@ const PackedStringArray ProjectSettings::get_required_features() { // Returns the features supported by this build of Godot. Includes all required features. const PackedStringArray ProjectSettings::_get_supported_features() { PackedStringArray features = get_required_features(); + +#ifdef LIBGODOT_ENABLED + features.append("LibGodot"); +#endif + #ifdef MODULE_MONO_ENABLED features.append("C#"); #endif diff --git a/core/extension/gdextension_function_loader.cpp b/core/extension/gdextension_function_loader.cpp new file mode 100644 index 00000000000..afe55eb9225 --- /dev/null +++ b/core/extension/gdextension_function_loader.cpp @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* gdextension_function_loader.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gdextension_function_loader.h" + +#include "gdextension.h" + +Error GDExtensionFunctionLoader::open_library(const String &p_path) { + ERR_FAIL_COND_V_MSG(!p_path.begins_with("libgodot://"), ERR_FILE_NOT_FOUND, "Function based GDExtensions should have a path starting with libgodot://"); + ERR_FAIL_COND_V_MSG(!initialization_function, ERR_DOES_NOT_EXIST, "Initialization function is required for function based GDExtensions."); + + library_path = p_path; + + return OK; +} + +Error GDExtensionFunctionLoader::initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref &p_extension, GDExtensionInitialization *r_initialization) { + ERR_FAIL_COND_V_MSG(!initialization_function, ERR_DOES_NOT_EXIST, "Initialization function is required for function based GDExtensions."); + GDExtensionBool ret = initialization_function(p_get_proc_address, p_extension.ptr(), r_initialization); + + if (ret) { + return OK; + } else { + ERR_FAIL_V_MSG(FAILED, "GDExtension initialization function for '" + library_path + "' returned an error."); + } +} + +void GDExtensionFunctionLoader::close_library() { + initialization_function = nullptr; + library_path.clear(); +} + +bool GDExtensionFunctionLoader::is_library_open() const { + return !library_path.is_empty(); +} + +bool GDExtensionFunctionLoader::has_library_changed() const { + return false; +} + +bool GDExtensionFunctionLoader::library_exists() const { + return true; +} + +void GDExtensionFunctionLoader::set_initialization_function(GDExtensionInitializationFunction p_initialization_function) { + initialization_function = p_initialization_function; +} diff --git a/core/extension/gdextension_function_loader.h b/core/extension/gdextension_function_loader.h new file mode 100644 index 00000000000..febda097ca8 --- /dev/null +++ b/core/extension/gdextension_function_loader.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* gdextension_function_loader.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/extension/gdextension_loader.h" +#include "core/os/shared_object.h" + +class GDExtension; + +class GDExtensionFunctionLoader : public GDExtensionLoader { + friend class GDExtensionManager; + friend class GDExtension; + + String library_path; + GDExtensionInitializationFunction initialization_function = nullptr; + +public: + virtual Error open_library(const String &p_path) override; + virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref &p_extension, GDExtensionInitialization *r_initialization) override; + virtual void close_library() override; + virtual bool is_library_open() const override; + virtual bool has_library_changed() const override; + virtual bool library_exists() const override; + + void set_initialization_function(GDExtensionInitializationFunction p_initialization_function); +}; diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index a8b22997288..166e522c006 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -30,6 +30,7 @@ #include "gdextension_manager.h" +#include "core/extension/gdextension_function_loader.h" #include "core/extension/gdextension_library_loader.h" #include "core/extension/gdextension_special_compat_hashes.h" #include "core/io/dir_access.h" @@ -114,7 +115,14 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String & Ref loader; loader.instantiate(); - return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader); + return load_extension_with_loader(p_path, loader); +} + +GDExtensionManager::LoadStatus GDExtensionManager::load_extension_from_function(const String &p_path, GDExtensionConstPtr p_init_func) { + Ref func_loader; + func_loader.instantiate(); + func_loader->set_initialization_function((GDExtensionInitializationFunction)*p_init_func.data); + return load_extension_with_loader(p_path, func_loader); } GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref &p_loader) { @@ -454,6 +462,7 @@ GDExtensionManager *GDExtensionManager::get_singleton() { void GDExtensionManager::_bind_methods() { ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension); + ClassDB::bind_method(D_METHOD("load_extension_from_function", "path", "init_func"), &GDExtensionManager::load_extension_from_function); ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension); ClassDB::bind_method(D_METHOD("unload_extension", "path"), &GDExtensionManager::unload_extension); ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &GDExtensionManager::is_extension_loaded); diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index 93d9130a919..8fe4213ae72 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -31,6 +31,9 @@ #pragma once #include "core/extension/gdextension.h" +#include "core/variant/native_ptr.h" + +GDVIRTUAL_NATIVE_PTR(GDExtensionInitializationFunction) class GDExtensionManager : public Object { GDCLASS(GDExtensionManager, Object); @@ -66,6 +69,7 @@ private: public: LoadStatus load_extension(const String &p_path); + LoadStatus load_extension_from_function(const String &p_path, GDExtensionConstPtr p_init_func); LoadStatus load_extension_with_loader(const String &p_path, const Ref &p_loader); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); diff --git a/core/extension/godot_instance.cpp b/core/extension/godot_instance.cpp new file mode 100644 index 00000000000..343c4eb0790 --- /dev/null +++ b/core/extension/godot_instance.cpp @@ -0,0 +1,126 @@ +/**************************************************************************/ +/* godot_instance.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "godot_instance.h" + +#include "core/extension/gdextension_manager.h" +#include "core/os/main_loop.h" +#include "main/main.h" +#include "servers/display/display_server.h" + +void GodotInstance::_bind_methods() { + ClassDB::bind_method(D_METHOD("start"), &GodotInstance::start); + ClassDB::bind_method(D_METHOD("is_started"), &GodotInstance::is_started); + ClassDB::bind_method(D_METHOD("iteration"), &GodotInstance::iteration); + ClassDB::bind_method(D_METHOD("focus_in"), &GodotInstance::focus_in); + ClassDB::bind_method(D_METHOD("focus_out"), &GodotInstance::focus_out); + ClassDB::bind_method(D_METHOD("pause"), &GodotInstance::pause); + ClassDB::bind_method(D_METHOD("resume"), &GodotInstance::resume); +} + +GodotInstance::GodotInstance() { +} + +GodotInstance::~GodotInstance() { +} + +bool GodotInstance::initialize(GDExtensionInitializationFunction p_init_func) { + print_verbose("Godot Instance initialization"); + GDExtensionManager *gdextension_manager = GDExtensionManager::get_singleton(); + GDExtensionConstPtr ptr((const GDExtensionInitializationFunction *)&p_init_func); + GDExtensionManager::LoadStatus status = gdextension_manager->load_extension_from_function("libgodot://main", ptr); + return status == GDExtensionManager::LoadStatus::LOAD_STATUS_OK; +} + +bool GodotInstance::start() { + print_verbose("GodotInstance::start()"); + Error err = Main::setup2(); + if (err != OK) { + return false; + } + started = Main::start() == EXIT_SUCCESS; + if (started) { + OS::get_singleton()->get_main_loop()->initialize(); + } + return started; +} + +bool GodotInstance::is_started() { + return started; +} + +bool GodotInstance::iteration() { + DisplayServer::get_singleton()->process_events(); + return Main::iteration(); +} + +void GodotInstance::stop() { + print_verbose("GodotInstance::stop()"); + if (started) { + OS::get_singleton()->get_main_loop()->finalize(); + } + started = false; +} + +void GodotInstance::focus_out() { + print_verbose("GodotInstance::focus_out()"); + if (started) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + } +} + +void GodotInstance::focus_in() { + print_verbose("GodotInstance::focus_in()"); + if (started) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } + } +} + +void GodotInstance::pause() { + print_verbose("GodotInstance::pause()"); + if (started) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); + } + } +} + +void GodotInstance::resume() { + print_verbose("GodotInstance::resume()"); + if (started) { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); + } + } +} diff --git a/core/extension/godot_instance.h b/core/extension/godot_instance.h new file mode 100644 index 00000000000..08d44d15d0f --- /dev/null +++ b/core/extension/godot_instance.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* godot_instance.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/extension/gdextension_interface.h" +#include "core/object/class_db.h" + +class GodotInstance : public Object { + GDCLASS(GodotInstance, Object); + + bool started = false; + +protected: + static void _bind_methods(); + +public: + GodotInstance(); + ~GodotInstance(); + + bool initialize(GDExtensionInitializationFunction p_init_func); + + bool start(); + bool is_started(); + bool iteration(); + void stop(); + + void focus_out(); + void focus_in(); + void pause(); + void resume(); +}; diff --git a/core/extension/libgodot.h b/core/extension/libgodot.h new file mode 100644 index 00000000000..ad965f8cde0 --- /dev/null +++ b/core/extension/libgodot.h @@ -0,0 +1,73 @@ +/**************************************************************************/ +/* libgodot.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "gdextension_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Export macros for DLL visibility +#if defined(_MSC_VER) || defined(__MINGW32__) +#define LIBGODOT_API __declspec(dllexport) +#elif defined(__GNUC__) || defined(__clang__) +#define LIBGODOT_API __attribute__((visibility("default"))) +#endif // if defined(_MSC_VER) + +/** + * @name libgodot_create_godot_instance + * @since 4.6 + * + * Creates a new Godot instance. + * + * @param p_argc The number of command line arguments. + * @param p_argv The C-style array of command line arguments. + * @param p_init_func GDExtension initialization function of the host application. + * + * @return A pointer to created \ref GodotInstance GDExtension object or nullptr if there was an error. + */ +LIBGODOT_API GDExtensionObjectPtr libgodot_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func); + +/** + * @name libgodot_destroy_godot_instance + * @since 4.6 + * + * Destroys an existing Godot instance. + * + * @param p_godot_instance The reference to the GodotInstance object to destroy. + * + */ +LIBGODOT_API void libgodot_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance); + +#ifdef __cplusplus +} +#endif diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index e0e2d6836be..6b694776ab2 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -39,6 +39,7 @@ #include "core/debugger/engine_profiler.h" #include "core/extension/gdextension.h" #include "core/extension/gdextension_manager.h" +#include "core/extension/godot_instance.h" #include "core/input/input.h" #include "core/input/input_map.h" #include "core/input/shortcut.h" @@ -289,6 +290,8 @@ void register_core_types() { GDREGISTER_CLASS(GDExtension); + GDREGISTER_ABSTRACT_CLASS(GodotInstance); + GDREGISTER_ABSTRACT_CLASS(GDExtensionManager); GDREGISTER_ABSTRACT_CLASS(ResourceUID); diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h index e97bee03547..a0715e28cd9 100644 --- a/core/variant/native_ptr.h +++ b/core/variant/native_ptr.h @@ -31,6 +31,7 @@ #pragma once #include "core/math/audio_frame.h" +#include "core/variant/binder_common.h" #include "core/variant/method_ptrcall.h" #include "core/variant/type_info.h" diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml index 712097ba766..93d79c86b4f 100644 --- a/doc/classes/GDExtensionManager.xml +++ b/doc/classes/GDExtensionManager.xml @@ -39,6 +39,14 @@ Loads an extension by absolute file path. The [param path] needs to point to a valid [GDExtension]. Returns [constant LOAD_STATUS_OK] if successful. + + + + + + Loads the extension already in address space via the given path and initialization function. The [param path] needs to be unique and start with [code]"libgodot://"[/code]. Returns [constant LOAD_STATUS_OK] if successful. + + diff --git a/doc/classes/GodotInstance.xml b/doc/classes/GodotInstance.xml new file mode 100644 index 00000000000..a071cb0adf9 --- /dev/null +++ b/doc/classes/GodotInstance.xml @@ -0,0 +1,55 @@ + + + + Provides access to an embedded Godot instance. + + + GodotInstance represents a running Godot instance that is controlled from an outside codebase, without a perpetual main loop. It is created by the C API [code]libgodot_create_godot_instance[/code]. Only one may be created per process. + + + + + + + + Notifies the instance that it is now in focus. + + + + + + Notifies the instance that it is now not in focus. + + + + + + Returns [code]true[/code] if this instance has been fully started. + + + + + + Runs a single iteration of the main loop. Returns [code]true[/code] if the engine is attempting to quit. + + + + + + Notifies the instance that it is going to be paused. + + + + + + Notifies the instance that it is being resumed. + + + + + + Finishes this instance's startup sequence. Returns [code]true[/code] on success. + + + + diff --git a/methods.py b/methods.py index b0fab65f657..e48e88a304f 100644 --- a/methods.py +++ b/methods.py @@ -88,15 +88,19 @@ def redirect_emitter(target, source, env): Emitter to automatically redirect object/library build files to the `bin/obj` directory, retaining subfolder structure. External build files will attempt to retain subfolder structure relative to their environment's parent directory, sorted under `bin/obj/external`. - If `redirect_build_objects` is `False`, or an external build file isn't relative to the - passed environment, this emitter does nothing. + If `redirect_build_objects` is `False`, an external build file isn't relative to the passed + environment, or a file is being written directly into `bin`, this emitter does nothing. """ if not env["redirect_build_objects"]: return target, source redirected_targets = [] for item in target: - if base_folder in (path := Path(item.get_abspath()).resolve()).parents: + path = Path(item.get_abspath()).resolve() + + if path.parent == base_folder / "bin": + pass + elif base_folder in path.parents: item = env.File(f"#bin/obj/{path.relative_to(base_folder)}") elif (alt_base := Path(env.Dir(".").get_abspath()).resolve().parent) in path.parents: item = env.File(f"#bin/obj/external/{path.relative_to(alt_base)}") diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 7de1450bf9d..8be5b536d58 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -13,6 +13,11 @@ common_linuxbsd = [ "freedesktop_at_spi_monitor.cpp", ] +if env["library_type"] == "executable": + common_linuxbsd += ["godot_linuxbsd.cpp"] +else: + common_linuxbsd += ["libgodot_linuxbsd.cpp"] + if env["use_sowrap"]: common_linuxbsd.append("xkbcommon-so_wrap.c") @@ -35,7 +40,13 @@ if env["dbus"]: if env["use_sowrap"]: common_linuxbsd.append("dbus-so_wrap.c") -prog = env.add_program("#bin/godot", ["godot_linuxbsd.cpp"] + common_linuxbsd) +if env["library_type"] == "static_library": + prog = env.add_library("#bin/godot", common_linuxbsd) +elif env["library_type"] == "shared_library": + env.Append(CCFLAGS=["-fPIC"]) + prog = env.add_shared_library("#bin/godot", common_linuxbsd) +else: + prog = env.add_program("#bin/godot", common_linuxbsd) if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, env.Run(platform_linuxbsd_builders.make_debug_linuxbsd)) diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 5f929c59258..03fdddd5dea 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -67,7 +67,7 @@ def get_doc_path(): def get_flags(): return { "arch": detect_arch(), - "supported": ["mono"], + "supported": ["library", "mono"], } diff --git a/platform/linuxbsd/libgodot_linuxbsd.cpp b/platform/linuxbsd/libgodot_linuxbsd.cpp new file mode 100644 index 00000000000..6ae9d4c5c71 --- /dev/null +++ b/platform/linuxbsd/libgodot_linuxbsd.cpp @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* libgodot_linuxbsd.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "core/extension/libgodot.h" + +#include "core/extension/godot_instance.h" +#include "main/main.h" + +#include "os_linuxbsd.h" + +static OS_LinuxBSD *os = nullptr; + +static GodotInstance *instance = nullptr; + +GDExtensionObjectPtr libgodot_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func) { + ERR_FAIL_COND_V_MSG(instance != nullptr, nullptr, "Only one Godot Instance may be created."); + + os = new OS_LinuxBSD(); + + Error err = Main::setup(p_argv[0], p_argc - 1, &p_argv[1], false); + if (err != OK) { + return nullptr; + } + + instance = memnew(GodotInstance); + if (!instance->initialize(p_init_func)) { + memdelete(instance); + // Note: When Godot Engine supports reinitialization, clear the instance pointer here. + //instance = nullptr; + return nullptr; + } + + return (GDExtensionObjectPtr)instance; +} + +void libgodot_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance) { + GodotInstance *godot_instance = (GodotInstance *)p_godot_instance; + if (instance == godot_instance) { + godot_instance->stop(); + memdelete(godot_instance); + instance = nullptr; + Main::cleanup(); + } +} diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 19b0acb74f3..30bf5c6f387 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -21,7 +21,6 @@ files = [ "godot_window_delegate.mm", "godot_window.mm", "key_mapping_macos.mm", - "godot_main_macos.mm", "godot_menu_delegate.mm", "godot_menu_item.mm", "godot_open_save_delegate.mm", @@ -39,7 +38,17 @@ if env.editor_build: "editor/embedded_process_macos.mm", ] -prog = env.add_program("#bin/godot", files) +if env["library_type"] == "executable": + files += ["godot_main_macos.mm"] +else: + files += ["libgodot_macos.mm"] + +if env["library_type"] == "static_library": + prog = env.add_library("#bin/godot", files) +elif env["library_type"] == "shared_library": + prog = env.add_shared_library("#bin/godot", files) +else: + prog = env.add_program("#bin/godot", files) if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, env.Run(platform_macos_builders.make_debug_macos)) diff --git a/platform/macos/detect.py b/platform/macos/detect.py index 321e22b5314..c5846c34a1e 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -61,7 +61,7 @@ def get_flags(): "arch": detect_arch(), "use_volk": False, "metal": True, - "supported": ["metal", "mono"], + "supported": ["library", "metal", "mono"], } @@ -172,8 +172,9 @@ def configure(env: "SConsEnvironment"): env.Append(CCFLAGS=["-fsanitize=thread"]) env.Append(LINKFLAGS=["-fsanitize=thread"]) - env.Append(LINKFLAGS=["-Wl,-stack_size," + hex(STACK_SIZE_SANITIZERS)]) - else: + if env["library_type"] == "executable": + env.Append(LINKFLAGS=["-Wl,-stack_size," + hex(STACK_SIZE_SANITIZERS)]) + elif env["library_type"] == "executable": env.Append(LINKFLAGS=["-Wl,-stack_size," + hex(STACK_SIZE)]) if env["use_coverage"]: diff --git a/platform/macos/libgodot_macos.mm b/platform/macos/libgodot_macos.mm new file mode 100644 index 00000000000..d410d730c0b --- /dev/null +++ b/platform/macos/libgodot_macos.mm @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* libgodot_macos.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "core/extension/libgodot.h" + +#include "core/extension/godot_instance.h" +#include "main/main.h" + +#include "os_macos.h" + +static OS_MacOS *os = nullptr; + +static GodotInstance *instance = nullptr; + +GDExtensionObjectPtr libgodot_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func) { + ERR_FAIL_COND_V_MSG(instance != nullptr, nullptr, "Only one Godot Instance may be created."); + + uint32_t remaining_args = p_argc - 1; + os = new OS_MacOS_NSApp(p_argv[0], remaining_args, remaining_args > 0 ? &p_argv[1] : nullptr); + + @autoreleasepool { + Error err = Main::setup(p_argv[0], remaining_args, remaining_args > 0 ? &p_argv[1] : nullptr, false); + if (err != OK) { + return nullptr; + } + + instance = memnew(GodotInstance); + if (!instance->initialize(p_init_func)) { + memdelete(instance); + instance = nullptr; + return nullptr; + } + + return (GDExtensionObjectPtr)instance; + } +} + +void libgodot_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance) { + GodotInstance *godot_instance = (GodotInstance *)p_godot_instance; + if (instance == godot_instance) { + godot_instance->stop(); + memdelete(godot_instance); + // Note: When Godot Engine supports reinitialization, clear the instance pointer here. + //instance = nullptr; + Main::cleanup(); + } +} diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 62d28f495f8..181b3a09c89 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -13,7 +13,6 @@ from methods import redirect_emitter sources = [] common_win = [ - "godot_windows.cpp", "os_windows.cpp", "display_server_windows.cpp", "key_mapping_windows.cpp", @@ -28,6 +27,11 @@ common_win = [ "drop_target_windows.cpp", ] +if env["library_type"] == "executable": + common_win += ["godot_windows.cpp"] +else: + common_win += ["libgodot_windows.cpp"] + if env.msvc: common_win += ["crash_handler_windows_seh.cpp"] else: @@ -39,7 +43,7 @@ common_win_wrap = [ env_wrap = env.Clone() -if env["arch"] == "x86_64": +if env["arch"] == "x86_64" and env["library_type"] == "executable": env_cpp_check = env.Clone() env_cpp_check.add_source_files(sources, ["cpu_feature_validation.c"]) if env.msvc: @@ -76,7 +80,12 @@ sources += res_obj if env["accesskit"] and not env.msvc: sources += env.DEFLIB("uiautomationcore") -prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) +if env["library_type"] == "static_library": + prog = env.add_library("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) +elif env["library_type"] == "shared_library": + prog = env.add_shared_library("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) +else: + prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) arrange_program_clean(prog) env.Depends(prog, "godot.manifest") diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 1a2345c4a33..9a1157d1100 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -233,7 +233,7 @@ def get_flags(): return { "arch": arch, - "supported": ["d3d12", "dcomp", "mono", "xaudio2"], + "supported": ["d3d12", "dcomp", "library", "mono", "xaudio2"], } @@ -243,7 +243,7 @@ def configure_msvc(env: "SConsEnvironment"): ## Build type # TODO: Re-evaluate the need for this / streamline with common config. - if env["target"] == "template_release": + if env["target"] == "template_release" and env["library_type"] == "executable": env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) if env["windows_subsystem"] == "gui": diff --git a/platform/windows/libgodot_windows.cpp b/platform/windows/libgodot_windows.cpp new file mode 100644 index 00000000000..28466fe187c --- /dev/null +++ b/platform/windows/libgodot_windows.cpp @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* libgodot_windows.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "core/extension/libgodot.h" + +#include "core/extension/godot_instance.h" +#include "main/main.h" + +#include "os_windows.h" + +static OS_Windows *os = nullptr; + +static GodotInstance *instance = nullptr; + +GDExtensionObjectPtr libgodot_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func) { + ERR_FAIL_COND_V_MSG(instance != nullptr, nullptr, "Only one Godot Instance may be created at a time."); + + os = new OS_Windows(GetModuleHandle(nullptr)); + + Error err = Main::setup(p_argv[0], p_argc - 1, &p_argv[1], false); + if (err != OK) { + return nullptr; + } + + instance = memnew(GodotInstance); + if (!instance->initialize(p_init_func)) { + memdelete(instance); + instance = nullptr; + return nullptr; + } + + return (GDExtensionObjectPtr)instance; +} + +void libgodot_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance) { + GodotInstance *godot_instance = (GodotInstance *)p_godot_instance; + if (instance == godot_instance) { + godot_instance->stop(); + memdelete(godot_instance); + // Note: When Godot Engine supports reinitialization, clear the instance pointer here. + //instance = nullptr; + Main::cleanup(); + } +}