Skip to content

Commit

Permalink
LibGodot Feature
Browse files Browse the repository at this point in the history
* Add a new GodotInstance GDCLASS that provides startup, shutdown and iteration commands
* Add a new GDExtension entry point that creates a new Godot instance and returns a GodotInstance object.
* This object can be used to control the GodotInstance.
* Allow specifying an external native rendering surface handle to render Godot into the UI of a host application.
  * It is also possible to embed multiple Godot windows into the UI of a host application.
  * Currently supported on MacOS and iOS
  * Currently supported on Windows

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: godotengine#72883

Co-Authored-By: Gabor Koncz <[email protected]>
Co-Authored-By: K. S. Ernest (iFire) Lee <[email protected]>
  • Loading branch information
fire and konczg committed Sep 11, 2024
1 parent 77dcf97 commit 6a874d1
Show file tree
Hide file tree
Showing 110 changed files with 2,914 additions and 327 deletions.
17 changes: 17 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ opts.Add("custom_modules", "A list of comma-separated directory paths containing
opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True))

# Advanced options
opts.Add(
EnumVariable(
"library_type",
"Build library type",
"executable",
("executable", "static_library", "shared_library"),
)
)
opts.Add(BoolVariable("dev_mode", "Alias for dev options: verbose=yes warnings=extra werror=yes tests=yes", False))
opts.Add(BoolVariable("tests", "Build the unit tests", False))
opts.Add(BoolVariable("fast_unsafe", "Enable unsafe options for faster rebuilds", False))
Expand Down Expand Up @@ -320,6 +328,15 @@ if env["import_env_vars"]:
if env.scons_version < (4, 3) and not env["platform"]:
env["platform"] = env["p"]

if env["library_type"] == "static_library":
env.Append(CPPDEFINES=["LIBRARY_ENABLED"])
elif env["library_type"] == "shared_library":
env.Append(CPPDEFINES=["LIBRARY_ENABLED"])
env.Append(CCFLAGS=["-fPIC"])
env.Append(STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME=True)
else:
env.__class__.add_program = methods.add_program

if env["platform"] == "":
# Missing `platform` argument, try to detect platform automatically
if (
Expand Down
5 changes: 5 additions & 0 deletions core/config/project_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,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 LIBRARY_ENABLED
features.append("Embedded");
#endif

#ifdef MODULE_MONO_ENABLED
features.append("C#");
#endif
Expand Down
24 changes: 18 additions & 6 deletions core/extension/gdextension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -812,9 +812,14 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb
}

GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr;
GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization);
return initialize_extension_function(initialization_function, p_entry_symbol);
}

if (ret) {
Error GDExtension::initialize_extension_function(GDExtensionInitializationFunction initialization_function, const String &p_entry_symbol) {
if (library == nullptr) {
embedded = true;
}
if (initialization_function(&gdextension_get_proc_address, this, &initialization)) {
level_initialized = -1;
return OK;
} else {
Expand All @@ -825,6 +830,9 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb
}

void GDExtension::close_library() {
if (embedded) {
return;
}
ERR_FAIL_NULL(library);
OS::get_singleton()->close_dynamic_library(library);

Expand All @@ -836,17 +844,21 @@ void GDExtension::close_library() {
#endif
}

bool GDExtension::is_embedded() const {
return embedded;
}

bool GDExtension::is_library_open() const {
return library != nullptr;
return (library != nullptr) || embedded;
}

GDExtension::InitializationLevel GDExtension::get_minimum_library_initialization_level() const {
ERR_FAIL_NULL_V(library, INITIALIZATION_LEVEL_CORE);
ERR_FAIL_COND_V(library == nullptr && !embedded, INITIALIZATION_LEVEL_CORE);
return InitializationLevel(initialization.minimum_initialization_level);
}

void GDExtension::initialize_library(InitializationLevel p_level) {
ERR_FAIL_NULL(library);
ERR_FAIL_COND(library == nullptr && !embedded);
ERR_FAIL_COND_MSG(p_level <= int32_t(level_initialized), vformat("Level '%d' must be higher than the current level '%d'", p_level, level_initialized));

level_initialized = int32_t(p_level);
Expand All @@ -856,7 +868,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) {
initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level));
}
void GDExtension::deinitialize_library(InitializationLevel p_level) {
ERR_FAIL_NULL(library);
ERR_FAIL_COND(library == nullptr && !embedded);
ERR_FAIL_COND(p_level > int32_t(level_initialized));

level_initialized = int32_t(p_level) - 1;
Expand Down
3 changes: 3 additions & 0 deletions core/extension/gdextension.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class GDExtension : public Resource {

friend class GDExtensionManager;

bool embedded = false;
void *library = nullptr; // pointer if valid,
String library_path;
bool reloadable = false;
Expand Down Expand Up @@ -127,6 +128,7 @@ class GDExtension : public Resource {
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);

Error initialize_extension_function(GDExtensionInitializationFunction initialization_function, const String &p_entry_symbol);
Error open_library(const String &p_path, const String &p_entry_symbol, Vector<SharedObject> *p_dependencies = nullptr);
void close_library();

Expand All @@ -146,6 +148,7 @@ class GDExtension : public Resource {
#endif

public:
bool is_embedded() const;
bool is_library_open() const;

#ifdef TOOLS_ENABLED
Expand Down
17 changes: 17 additions & 0 deletions core/extension/gdextension_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "core/config/engine.h"
#include "core/extension/gdextension.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/godot_runtime_api.h"
#include "core/io/file_access.h"
#include "core/io/xml_parser.h"
#include "core/object/class_db.h"
Expand Down Expand Up @@ -1526,6 +1527,20 @@ static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_c
return class_info ? class_info->class_ptr : nullptr;
}

GDExtensionObjectPtr gdextension_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func) {
#ifdef LIBRARY_ENABLED
return create_godot_instance(p_argc, p_argv, p_init_func);
#else
return nullptr;
#endif
}

static void gdextension_destroy_godot_instance(GDExtensionObjectPtr p_godot_instance) {
#ifdef LIBRARY_ENABLED
destroy_godot_instance(p_godot_instance);
#endif
}

static void gdextension_editor_add_plugin(GDExtensionConstStringNamePtr p_classname) {
#ifdef TOOLS_ENABLED
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
Expand Down Expand Up @@ -1710,6 +1725,8 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len);
REGISTER_INTERFACE_FUNC(image_ptrw);
REGISTER_INTERFACE_FUNC(image_ptr);
REGISTER_INTERFACE_FUNC(create_godot_instance);
REGISTER_INTERFACE_FUNC(destroy_godot_instance);
}

#undef REGISTER_INTERFACE_FUNCTION
8 changes: 8 additions & 0 deletions core/extension/gdextension_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -2957,6 +2957,14 @@ typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *
*/
typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size);

typedef GDExtensionObjectPtr (*GDExtensionInterfaceCreateGodotInstance)(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func);
typedef void (*GDExtensionInterfaceDestroyGodotInstance)(GDExtensionObjectPtr p_godot_instance);

#if defined(_WIN32)
__declspec(dllexport)
#endif
GDExtensionObjectPtr gdextension_create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func);

#ifdef __cplusplus
}
#endif
Expand Down
14 changes: 13 additions & 1 deletion core/extension/gdextension_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void GDExtensionManager::_reload_all_scripts() {
}
#endif // TOOLS_ENABLED

void GDExtensionManager::load_extensions() {
void GDExtensionManager::load_extensions(GDExtensionInitializationFunction p_init_func) {
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
while (f.is_valid() && !f->eof_reached()) {
String s = f->get_line().strip_edges();
Expand All @@ -236,6 +236,18 @@ void GDExtensionManager::load_extensions() {
}

OS::get_singleton()->load_platform_gdextensions();

if (p_init_func) {
Ref<GDExtension> libgodot;
libgodot.instantiate();
Error err = libgodot->initialize_extension_function(p_init_func, "lib_godot");
if (err != OK) {
ERR_PRINT("Error initializing extension function");
} else {
libgodot->set_path("res://__LibGodot");
GDExtensionManager::get_singleton()->load_extension("res://__LibGodot");
}
}
}

void GDExtensionManager::reload_extensions() {
Expand Down
2 changes: 1 addition & 1 deletion core/extension/gdextension_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class GDExtensionManager : public Object {

static GDExtensionManager *get_singleton();

void load_extensions();
void load_extensions(GDExtensionInitializationFunction p_init_func = nullptr);
void reload_extensions();

GDExtensionManager();
Expand Down
69 changes: 69 additions & 0 deletions core/extension/godot_instance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**************************************************************************/
/* 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 "main/main.h"
#include "servers/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("shutdown"), &GodotInstance::shutdown);
}

GodotInstance::GodotInstance() {
}

GodotInstance::~GodotInstance() {
}

bool GodotInstance::start() {
Error err = Main::setup2();
if (err != OK) {
return false;
}
started = Main::start() == EXIT_SUCCESS;
return started;
}

bool GodotInstance::is_started() {
return started;
}

bool GodotInstance::iteration() {
DisplayServer::get_singleton()->process_events();
return Main::iteration();
}

void GodotInstance::shutdown() {
started = false;
Main::cleanup();
}
55 changes: 55 additions & 0 deletions core/extension/godot_instance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/

#ifndef GODOT_INSTANCE_H
#define GODOT_INSTANCE_H

#include "core/error/error_list.h"
#include "core/object/class_db.h"
#include "core/object/object.h"

class GodotInstance : public Object {
GDCLASS(GodotInstance, Object);

static void _bind_methods();

bool started = false;

public:
GodotInstance();
~GodotInstance();

virtual bool start();
virtual bool is_started();
virtual bool iteration();
virtual void shutdown();
};

#endif // GODOT_INSTANCE_H
40 changes: 40 additions & 0 deletions core/extension/godot_runtime_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**************************************************************************/
/* godot_runtime_api.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. */
/**************************************************************************/

#ifndef GODOT_RUNTIME_API_H
#define GODOT_RUNTIME_API_H

#include "core/extension/gdextension_interface.h"

GDExtensionObjectPtr create_godot_instance(int p_argc, char *p_argv[], GDExtensionInitializationFunction p_init_func);

void destroy_godot_instance(GDExtensionObjectPtr p_godot_instance);

#endif // GODOT_RUNTIME_API_H
Loading

0 comments on commit 6a874d1

Please sign in to comment.