summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/gdscript.cpp285
-rw-r--r--modules/gdscript/gdscript.h16
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp126
-rw-r--r--modules/gdscript/gdscript_cache.cpp191
-rw-r--r--modules/gdscript/gdscript_cache.h28
-rw-r--r--modules/gdscript/gdscript_compiler.cpp41
-rw-r--r--modules/gdscript/gdscript_editor.cpp2
-rw-r--r--modules/gdscript/gdscript_function.cpp7
-rw-r--r--modules/gdscript/gdscript_function.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp16
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_warning.cpp6
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd (renamed from modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd)3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out (renamed from modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out)1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd12
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out2
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml29
-rw-r--r--modules/gltf/doc_classes/GLTFDocumentExtension.xml48
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml4
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.h3
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp (renamed from modules/gltf/gltf_document_extension.cpp)64
-rw-r--r--modules/gltf/extensions/gltf_document_extension.h (renamed from modules/gltf/gltf_document_extension.h)22
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp (renamed from modules/gltf/gltf_document_extension_convert_importer_mesh.cpp)2
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h (renamed from modules/gltf/gltf_document_extension_convert_importer_mesh.h)0
-rw-r--r--modules/gltf/gltf_document.cpp522
-rw-r--r--modules/gltf/gltf_document.h11
-rw-r--r--modules/gltf/gltf_state.cpp4
-rw-r--r--modules/gltf/gltf_state.h8
-rw-r--r--modules/gltf/register_types.cpp14
-rw-r--r--modules/gltf/structures/gltf_buffer_view.cpp2
-rw-r--r--modules/gridmap/doc_classes/GridMap.xml4
-rw-r--r--modules/mbedtls/packet_peer_mbed_dtls.cpp7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs19
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs45
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs48
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs13
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs14
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp200
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.h37
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.cpp45
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.h6
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp163
-rw-r--r--modules/multiplayer/multiplayer_debugger.h41
-rw-r--r--modules/multiplayer/register_types.cpp1
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp8
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp46
-rw-r--r--modules/multiplayer/scene_multiplayer.h39
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp67
-rw-r--r--modules/multiplayer/scene_replication_interface.h6
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp77
-rw-r--r--modules/multiplayer/scene_rpc_interface.h5
-rw-r--r--modules/openxr/action_map/openxr_action.cpp2
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.cpp2
-rw-r--r--modules/openxr/extensions/openxr_android_extension.cpp26
-rw-r--r--modules/openxr/extensions/openxr_android_extension.h3
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h1
-rw-r--r--modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp2
-rw-r--r--modules/openxr/openxr_api.cpp10
-rw-r--r--modules/svg/image_loader_svg.cpp41
-rw-r--r--modules/svg/image_loader_svg.h2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp5
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp2
-rw-r--r--modules/zip/doc_classes/ZIPReader.xml2
70 files changed, 1912 insertions, 598 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index bd6cef0b6e..60230257e0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -808,6 +808,11 @@ String GDScript::_get_debug_path() const {
}
Error GDScript::reload(bool p_keep_state) {
+ if (reloading) {
+ return OK;
+ }
+ reloading = true;
+
bool has_instances;
{
MutexLock lock(GDScriptLanguage::singleton->mutex);
@@ -830,6 +835,7 @@ Error GDScript::reload(bool p_keep_state) {
// Loading a template, don't parse.
#ifdef TOOLS_ENABLED
if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) {
+ reloading = false;
return OK;
}
#endif
@@ -839,11 +845,10 @@ Error GDScript::reload(bool p_keep_state) {
if (source_path.is_empty()) {
source_path = get_path();
}
- if (!source_path.is_empty()) {
- MutexLock lock(GDScriptCache::singleton->lock);
- if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
- GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this;
- }
+ Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path);
+ if (!source_path.is_empty() && cached_script.is_null()) {
+ MutexLock lock(GDScriptCache::singleton->mutex);
+ GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this);
}
}
@@ -856,6 +861,7 @@ Error GDScript::reload(bool p_keep_state) {
}
// TODO: Show all error messages.
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
+ reloading = false;
return ERR_PARSE_ERROR;
}
@@ -872,6 +878,7 @@ Error GDScript::reload(bool p_keep_state) {
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
e = e->next();
}
+ reloading = false;
return ERR_PARSE_ERROR;
}
@@ -886,8 +893,10 @@ Error GDScript::reload(bool p_keep_state) {
GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error());
}
_err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT);
+ reloading = false;
return ERR_COMPILATION_FAILED;
} else {
+ reloading = false;
return err;
}
}
@@ -900,6 +909,7 @@ Error GDScript::reload(bool p_keep_state) {
}
#endif
+ reloading = false;
return OK;
}
@@ -1006,16 +1016,22 @@ Error GDScript::load_byte_code(const String &p_path) {
}
void GDScript::set_path(const String &p_path, bool p_take_over) {
+ String old_path = path;
if (is_root_script()) {
Script::set_path(p_path, p_take_over);
}
this->path = p_path;
+ GDScriptCache::move_script(old_path, p_path);
for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
kv.value->set_path(p_path, p_take_over);
}
}
Error GDScript::load_source_code(const String &p_path) {
+ if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") {
+ return OK;
+ }
+
Vector<uint8_t> sourcef;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
@@ -1133,6 +1149,78 @@ GDScript *GDScript::get_root_script() {
return result;
}
+RBSet<GDScript *> GDScript::get_dependencies() {
+ RBSet<GDScript *> dependencies;
+
+ _get_dependencies(dependencies, this);
+ dependencies.erase(this);
+
+ return dependencies;
+}
+
+RBSet<GDScript *> GDScript::get_inverted_dependencies() {
+ RBSet<GDScript *> inverted_dependencies;
+
+ List<GDScript *> scripts;
+ {
+ MutexLock lock(GDScriptLanguage::singleton->mutex);
+
+ SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first();
+ while (elem) {
+ scripts.push_back(elem->self());
+ elem = elem->next();
+ }
+ }
+
+ for (GDScript *scr : scripts) {
+ if (scr == nullptr || scr == this || scr->destructing) {
+ continue;
+ }
+
+ RBSet<GDScript *> scr_dependencies = scr->get_dependencies();
+ if (scr_dependencies.has(this)) {
+ inverted_dependencies.insert(scr);
+ }
+ }
+
+ return inverted_dependencies;
+}
+
+RBSet<GDScript *> GDScript::get_must_clear_dependencies() {
+ RBSet<GDScript *> dependencies = get_dependencies();
+ RBSet<GDScript *> must_clear_dependencies;
+ HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies;
+
+ for (GDScript *E : dependencies) {
+ inverted_dependencies.insert(E, E->get_inverted_dependencies());
+ }
+
+ RBSet<GDScript *> cant_clear;
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ for (GDScript *F : E.value) {
+ if (!dependencies.has(F)) {
+ cant_clear.insert(E.key);
+ for (GDScript *G : E.key->get_dependencies()) {
+ cant_clear.insert(G);
+ }
+ break;
+ }
+ }
+ }
+
+ for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) {
+ if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) {
+ continue;
+ }
+ must_clear_dependencies.insert(E.key);
+ }
+
+ cant_clear.clear();
+ dependencies.clear();
+ inverted_dependencies.clear();
+ return must_clear_dependencies;
+}
+
bool GDScript::has_script_signal(const StringName &p_signal) const {
if (_signals.has(p_signal)) {
return true;
@@ -1194,6 +1282,69 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript)
return class_name;
}
+GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) {
+ Variant::Type type = p_variant.get_type();
+ if (type != Variant::Type::OBJECT)
+ return nullptr;
+
+ Object *obj = p_variant;
+ if (obj == nullptr) {
+ return nullptr;
+ }
+
+ return Object::cast_to<GDScript>(obj);
+}
+
+void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) {
+ if (skip_dependencies || p_dependencies.has(this)) {
+ return;
+ }
+ p_dependencies.insert(this);
+
+ for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
+ if (E.value == nullptr) {
+ continue;
+ }
+ for (const Variant &V : E.value->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ if (implicit_initializer) {
+ for (const Variant &V : implicit_initializer->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ if (implicit_ready) {
+ for (const Variant &V : implicit_ready->constants) {
+ GDScript *scr = _get_gdscript_from_variant(V);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+ }
+
+ for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) {
+ if (E.value != p_except) {
+ E.value->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+
+ for (const KeyValue<StringName, Variant> &E : constants) {
+ GDScript *scr = _get_gdscript_from_variant(E.value);
+ if (scr != nullptr && scr != p_except) {
+ scr->_get_dependencies(p_dependencies, p_except);
+ }
+ }
+}
+
GDScript::GDScript() :
script_list(this) {
#ifdef DEBUG_ENABLED
@@ -1253,33 +1404,58 @@ void GDScript::_init_rpc_methods_properties() {
}
}
-GDScript::~GDScript() {
- {
- MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
+void GDScript::clear() {
+ if (clearing) {
+ return;
+ }
+ clearing = true;
- while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
- // Order matters since clearing the stack may already cause
- // the GDSCriptFunctionState to be destroyed and thus removed from the list.
- pending_func_states.remove(E);
- E->self()->_clear_stack();
+ RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
+ HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids;
+
+ // Log the objectids before clearing, as a cascade of clear could
+ // remove instances that are still in the clear loop
+ for (GDScript *E : must_clear_dependencies) {
+ must_clear_dependencies_objectids.insert(E, E->get_instance_id());
+ }
+
+ for (GDScript *E : must_clear_dependencies) {
+ Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]);
+ if (obj == nullptr) {
+ continue;
}
+
+ E->skip_dependencies = true;
+ E->clear();
+ E->skip_dependencies = false;
+ GDScriptCache::remove_script(E->get_path());
}
+ RBSet<StringName> member_function_names;
for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
- memdelete(E.value);
+ member_function_names.insert(E.key);
+ }
+ for (const StringName &E : member_function_names) {
+ if (member_functions.has(E)) {
+ memdelete(member_functions[E]);
+ }
+ }
+ member_function_names.clear();
+ member_functions.clear();
+
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) {
+ E.value.data_type.script_type_ref = Ref<Script>();
}
if (implicit_initializer) {
memdelete(implicit_initializer);
}
+ implicit_initializer = nullptr;
if (implicit_ready) {
memdelete(implicit_ready);
}
-
- if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
- GDScriptCache::remove_script(get_path());
- }
+ implicit_ready = nullptr;
_save_orphaned_subclasses();
@@ -1289,6 +1465,27 @@ GDScript::~GDScript() {
_clear_doc();
}
#endif
+ clearing = false;
+}
+
+GDScript::~GDScript() {
+ if (destructing) {
+ return;
+ }
+ destructing = true;
+
+ clear();
+
+ {
+ MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
+
+ while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) {
+ // Order matters since clearing the stack may already cause
+ // the GDScriptFunctionState to be destroyed and thus removed from the list.
+ pending_func_states.remove(E);
+ E->self()->_clear_stack();
+ }
+ }
#ifdef DEBUG_ENABLED
{
@@ -1297,6 +1494,10 @@ GDScript::~GDScript() {
GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
}
#endif
+
+ if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown.
+ GDScriptCache::remove_script(get_path());
+ }
}
//////////////////////////////
@@ -2336,26 +2537,27 @@ GDScriptLanguage::~GDScriptLanguage() {
// Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit).
SelfList<GDScript> *s = script_list.first();
while (s) {
- GDScript *scr = s->self();
// This ensures the current script is not released before we can check what's the next one
// in the list (we can't get the next upfront because we don't know if the reference breaking
// will cause it -or any other after it, for that matter- to be released so the next one
// is not the same as before).
- scr->reference();
-
- for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) {
- GDScriptFunction *func = E.value;
- for (int i = 0; i < func->argument_types.size(); i++) {
- func->argument_types.write[i].script_type_ref = Ref<Script>();
+ Ref<GDScript> scr = s->self();
+ if (scr.is_valid()) {
+ for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) {
+ GDScriptFunction *func = E.value;
+ for (int i = 0; i < func->argument_types.size(); i++) {
+ func->argument_types.write[i].script_type_ref = Ref<Script>();
+ }
+ func->return_type.script_type_ref = Ref<Script>();
+ }
+ for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) {
+ E.value.data_type.script_type_ref = Ref<Script>();
}
- func->return_type.script_type_ref = Ref<Script>();
- }
- for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) {
- E.value.data_type.script_type_ref = Ref<Script>();
- }
+ // Clear backup for scripts that could slip out of the cyclic reference check
+ scr->clear();
+ }
s = s->next();
- scr->unreference();
}
singleton = nullptr;
@@ -2379,6 +2581,27 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na
return Ref<GDScript>(Object::cast_to<GDScript>(obj));
}
+Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) {
+ {
+ MutexLock lock(mutex);
+
+ SelfList<GDScript> *elem = script_list.first();
+ while (elem) {
+ GDScript *scr = elem->self();
+ scr = scr->find_class(p_name);
+ if (scr != nullptr) {
+ return scr;
+ }
+ elem = elem->next();
+ }
+ }
+
+ Ref<GDScript> scr;
+ scr.instantiate();
+ scr->fully_qualified_name = p_name;
+ return scr;
+}
+
/*************** RESOURCE ***************/
Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 61600b1258..2df89d812c 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -61,6 +61,8 @@ class GDScript : public Script {
GDCLASS(GDScript, Script);
bool tool = false;
bool valid = false;
+ bool reloading = false;
+ bool skip_dependencies = false;
struct MemberInfo {
int index = 0;
@@ -124,6 +126,8 @@ class GDScript : public Script {
int subclass_count = 0;
RBSet<Object *> instances;
+ bool destructing = false;
+ bool clearing = false;
//exported members
String source;
String path;
@@ -163,6 +167,9 @@ class GDScript : public Script {
// This method will map the class name from "RefCounted" to "MyClass.InnerClass".
static String _get_gdscript_reference_class_name(const GDScript *p_gdscript);
+ GDScript *_get_gdscript_from_variant(const Variant &p_variant);
+ void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except);
+
protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
@@ -173,6 +180,8 @@ protected:
static void _bind_methods();
public:
+ void clear();
+
virtual bool is_valid() const override { return valid; }
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -193,6 +202,10 @@ public:
const Ref<GDScriptNativeClass> &get_native() const { return native; }
const String &get_script_class_name() const { return name; }
+ RBSet<GDScript *> get_dependencies();
+ RBSet<GDScript *> get_inverted_dependencies();
+ RBSet<GDScript *> get_must_clear_dependencies();
+
virtual bool has_script_signal(const StringName &p_signal) const override;
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override;
@@ -270,6 +283,7 @@ class GDScriptInstance : public ScriptInstance {
friend class GDScriptLambdaCallable;
friend class GDScriptLambdaSelfCallable;
friend class GDScriptCompiler;
+ friend class GDScriptCache;
friend struct GDScriptUtilityFunctionsDefinitions;
ObjectID owner_id;
@@ -518,6 +532,8 @@ public:
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
+ Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name);
+
GDScriptLanguage();
~GDScriptLanguage();
};
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 3d248e3da7..9b0dc9577b 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -40,6 +40,7 @@
#include "core/templates/hash_map.h"
#include "gdscript.h"
#include "gdscript_utility_functions.h"
+#include "scene/resources/packed_scene.h"
static MethodInfo info_from_utility_func(const StringName &p_function) {
ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo());
@@ -1724,7 +1725,6 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
} else {
result.type_source = GDScriptParser::DataType::INFERRED;
}
- result.is_constant = false;
}
if (p_parameter->datatype_specifier != nullptr) {
@@ -1744,6 +1744,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame
push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value);
}
+ result.is_constant = false;
p_parameter->set_datatype(result);
}
@@ -2864,6 +2865,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->variable_source = member.variable;
member.variable->usages += 1;
break;
+ case GDScriptParser::ClassNode::Member::SIGNAL:
+ p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL;
+ break;
case GDScriptParser::ClassNode::Member::FUNCTION:
resolve_function_signature(member.function);
p_identifier->set_datatype(make_callable_type(member.function->info));
@@ -3008,6 +3012,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value;
found_source = true;
break;
+ case GDScriptParser::IdentifierNode::MEMBER_SIGNAL:
case GDScriptParser::IdentifierNode::INHERITED_VARIABLE:
mark_lambda_use_self();
break;
@@ -3111,7 +3116,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
GDScriptParser::DataType result;
result.kind = GDScriptParser::DataType::NATIVE;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) {
+ if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") {
Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path);
if (singl_parser.is_valid()) {
Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
@@ -3119,6 +3124,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
}
}
+ } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") {
+ Error err = OK;
+ Ref<GDScript> scr = GDScriptCache::get_packed_scene_script(autoload.path, err);
+ if (err == OK && scr.is_valid()) {
+ Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path());
+ if (singl_parser.is_valid()) {
+ err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ if (err == OK) {
+ result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
+ }
+ }
+ }
}
result.is_constant = true;
p_identifier->set_datatype(result);
@@ -3244,9 +3261,28 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
}
} else {
// TODO: Don't load if validating: use completion cache.
- p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
- if (p_preload->resource.is_null()) {
- push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
+
+ // Must load GDScript and PackedScenes separately to permit cyclic references
+ // as ResourceLoader::load() detect and reject those.
+ if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") {
+ Error err = OK;
+ Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path);
+ p_preload->resource = res;
+ if (err != OK) {
+ push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path);
+ }
+ } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") {
+ Error err = OK;
+ Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path);
+ p_preload->resource = res;
+ if (err != OK) {
+ push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path);
+ }
+ } else {
+ p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
+ if (p_preload->resource.is_null()) {
+ push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
+ }
}
}
}
@@ -3288,6 +3324,17 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Just try to get it.
bool valid = false;
Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
+
+ // If it's a GDScript instance, try to get the full script. Maybe it's not still completely loaded.
+ Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value);
+ if (!valid && gdscr.is_valid()) {
+ Error err = OK;
+ GDScriptCache::get_full_script(gdscr->get_path(), err);
+ if (err == OK) {
+ value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
+ }
+ }
+
if (!valid) {
push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index);
result_type.kind = GDScriptParser::DataType::VARIANT;
@@ -3670,50 +3717,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
scr = obj->get_script();
}
if (scr.is_valid()) {
- if (scr->is_valid()) {
- result.script_type = scr;
- result.script_path = scr->get_path();
- Ref<GDScript> gds = scr;
- if (gds.is_valid()) {
- result.kind = GDScriptParser::DataType::CLASS;
- // This might be an inner class, so we want to get the parser for the root.
- // But still get the inner class from that tree.
- GDScript *current = gds.ptr();
- List<StringName> class_chain;
- while (current->_owner) {
- // Push to front so it's in reverse.
- class_chain.push_front(current->name);
- current = current->_owner;
- }
-
- Ref<GDScriptParserRef> ref = get_parser_for(current->get_path());
- if (ref.is_null()) {
- push_error("Could not find script in path.", p_source);
- GDScriptParser::DataType error_type;
- error_type.kind = GDScriptParser::DataType::VARIANT;
- return error_type;
- }
- ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
+ result.script_type = scr;
+ result.script_path = scr->get_path();
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ result.kind = GDScriptParser::DataType::CLASS;
+ // This might be an inner class, so we want to get the parser for the root.
+ // But still get the inner class from that tree.
+ GDScript *current = gds.ptr();
+ List<StringName> class_chain;
+ while (current->_owner) {
+ // Push to front so it's in reverse.
+ class_chain.push_front(current->name);
+ current = current->_owner;
+ }
- GDScriptParser::ClassNode *found = ref->get_parser()->head;
+ Ref<GDScriptParserRef> ref = get_parser_for(current->get_path());
+ if (ref.is_null()) {
+ push_error("Could not find script in path.", p_source);
+ GDScriptParser::DataType error_type;
+ error_type.kind = GDScriptParser::DataType::VARIANT;
+ return error_type;
+ }
+ ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
- // It should be okay to assume this exists, since we have a complete script already.
- for (const StringName &E : class_chain) {
- found = found->get_member(E).m_class;
- }
+ GDScriptParser::ClassNode *found = ref->get_parser()->head;
- result.class_type = found;
- result.script_path = ref->get_parser()->script_path;
- } else {
- result.kind = GDScriptParser::DataType::SCRIPT;
+ // It should be okay to assume this exists, since we have a complete script already.
+ for (const StringName &E : class_chain) {
+ found = found->get_member(E).m_class;
}
- result.native_type = scr->get_instance_base_type();
+
+ result.class_type = found;
+ result.script_path = ref->get_parser()->script_path;
} else {
- push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source);
- result.kind = GDScriptParser::DataType::VARIANT;
- result.type_source = GDScriptParser::DataType::UNDETECTED;
- result.is_meta_type = false;
+ result.kind = GDScriptParser::DataType::SCRIPT;
}
+ result.native_type = scr->get_instance_base_type();
} else {
result.kind = GDScriptParser::DataType::NATIVE;
if (result.native_type == GDScriptNativeClass::get_class_static()) {
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 03a101b9fc..f35318e4c6 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -36,6 +36,7 @@
#include "gdscript_analyzer.h"
#include "gdscript_compiler.h"
#include "gdscript_parser.h"
+#include "scene/resources/packed_scene.h"
bool GDScriptParserRef::is_valid() const {
return parser != nullptr;
@@ -96,27 +97,88 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
return result;
}
-GDScriptParserRef::~GDScriptParserRef() {
+void GDScriptParserRef::clear() {
+ if (cleared) {
+ return;
+ }
+ cleared = true;
+
if (parser != nullptr) {
memdelete(parser);
}
+
if (analyzer != nullptr) {
memdelete(analyzer);
}
- MutexLock lock(GDScriptCache::singleton->lock);
+}
+
+GDScriptParserRef::~GDScriptParserRef() {
+ clear();
+
+ MutexLock lock(GDScriptCache::singleton->mutex);
GDScriptCache::singleton->parser_map.erase(path);
}
GDScriptCache *GDScriptCache::singleton = nullptr;
+void GDScriptCache::move_script(const String &p_from, const String &p_to) {
+ if (singleton == nullptr || p_from == p_to) {
+ return;
+ }
+
+ MutexLock lock(singleton->mutex);
+
+ for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
+ if (E.value.has(p_from)) {
+ E.value.insert(p_to);
+ E.value.erase(p_from);
+ }
+ }
+
+ if (singleton->parser_map.has(p_from) && !p_from.is_empty()) {
+ singleton->parser_map[p_to] = singleton->parser_map[p_from];
+ }
+ singleton->parser_map.erase(p_from);
+
+ if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) {
+ singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from];
+ }
+ singleton->shallow_gdscript_cache.erase(p_from);
+
+ if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) {
+ singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from];
+ }
+ singleton->full_gdscript_cache.erase(p_from);
+}
+
void GDScriptCache::remove_script(const String &p_path) {
- MutexLock lock(singleton->lock);
+ if (singleton == nullptr) {
+ return;
+ }
+
+ MutexLock lock(singleton->mutex);
+
+ for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
+ if (!E.value.has(p_path)) {
+ continue;
+ }
+ E.value.erase(p_path);
+ }
+
+ GDScriptCache::clear_unreferenced_packed_scenes();
+
+ if (singleton->parser_map.has(p_path)) {
+ singleton->parser_map[p_path]->clear();
+ singleton->parser_map.erase(p_path);
+ }
+
+ singleton->dependencies.erase(p_path);
singleton->shallow_gdscript_cache.erase(p_path);
singleton->full_gdscript_cache.erase(p_path);
}
Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
- MutexLock lock(singleton->lock);
+ MutexLock lock(singleton->mutex);
Ref<GDScriptParserRef> ref;
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
@@ -163,7 +225,7 @@ String GDScriptCache::get_source_code(const String &p_path) {
}
Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) {
- MutexLock lock(singleton->lock);
+ MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
}
@@ -174,23 +236,22 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
return singleton->shallow_gdscript_cache[p_path];
}
- Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error);
- if (r_error != OK) {
- return Ref<GDScript>();
- }
-
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
script->load_source_code(p_path);
- GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true);
- singleton->shallow_gdscript_cache[p_path] = script.ptr();
+ Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error);
+ if (r_error == OK) {
+ GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true);
+ }
+
+ singleton->shallow_gdscript_cache[p_path] = script;
return script;
}
Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) {
- MutexLock lock(singleton->lock);
+ MutexLock lock(singleton->mutex);
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
@@ -220,19 +281,21 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
+ singleton->full_gdscript_cache[p_path] = script;
+ singleton->shallow_gdscript_cache.erase(p_path);
+
r_error = script->reload(true);
if (r_error) {
+ singleton->shallow_gdscript_cache[p_path] = script;
+ singleton->full_gdscript_cache.erase(p_path);
return script;
}
- singleton->full_gdscript_cache[p_path] = script.ptr();
- singleton->shallow_gdscript_cache.erase(p_path);
-
return script;
}
Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
- MutexLock lock(singleton->lock);
+ MutexLock lock(singleton->mutex);
if (singleton->full_gdscript_cache.has(p_path)) {
return singleton->full_gdscript_cache[p_path];
@@ -246,11 +309,11 @@ Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) {
}
Error GDScriptCache::finish_compiling(const String &p_owner) {
- MutexLock lock(singleton->lock);
+ MutexLock lock(singleton->mutex);
// Mark this as compiled.
Ref<GDScript> script = get_cached_script(p_owner);
- singleton->full_gdscript_cache[p_owner] = script.ptr();
+ singleton->full_gdscript_cache[p_owner] = script;
singleton->shallow_gdscript_cache.erase(p_owner);
HashSet<String> depends = singleton->dependencies[p_owner];
@@ -271,13 +334,103 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
return err;
}
+Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) {
+ MutexLock lock(singleton->mutex);
+
+ if (singleton->packed_scene_cache.has(p_path)) {
+ singleton->packed_scene_dependencies[p_path].insert(p_owner);
+ return singleton->packed_scene_cache[p_path];
+ }
+
+ Ref<PackedScene> scene = ResourceCache::get_ref(p_path);
+ if (scene.is_valid()) {
+ singleton->packed_scene_cache[p_path] = scene;
+ singleton->packed_scene_dependencies[p_path].insert(p_owner);
+ return scene;
+ }
+ scene.instantiate();
+
+ r_error = OK;
+ if (p_path.is_empty()) {
+ r_error = ERR_FILE_BAD_PATH;
+ return scene;
+ }
+
+ scene->set_path(p_path);
+ singleton->packed_scene_cache[p_path] = scene;
+ singleton->packed_scene_dependencies[p_path].insert(p_owner);
+
+ scene->recreate_state();
+ scene->reload_from_file();
+ return scene;
+}
+
+Ref<GDScript> GDScriptCache::get_packed_scene_script(const String &p_path, Error &r_error) {
+ r_error = OK;
+ Ref<PackedScene> scene = get_packed_scene(p_path, r_error);
+
+ if (r_error != OK) {
+ return Ref<GDScript>();
+ }
+
+ int node_count = scene->get_state()->get_node_count();
+ if (node_count == 0) {
+ return Ref<GDScript>();
+ }
+
+ const int ROOT_NODE = 0;
+ for (int i = 0; i < scene->get_state()->get_node_property_count(ROOT_NODE); i++) {
+ if (scene->get_state()->get_node_property_name(ROOT_NODE, i) != SNAME("script")) {
+ continue;
+ }
+
+ return scene->get_state()->get_node_property_value(ROOT_NODE, i);
+ }
+
+ return Ref<GDScript>();
+}
+
+void GDScriptCache::clear_unreferenced_packed_scenes() {
+ if (singleton == nullptr) {
+ return;
+ }
+
+ MutexLock lock(singleton->mutex);
+
+ for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) {
+ if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) {
+ continue;
+ }
+
+ singleton->packed_scene_dependencies.erase(E.key);
+ singleton->packed_scene_cache.erase(E.key);
+ }
+}
+
GDScriptCache::GDScriptCache() {
singleton = this;
}
GDScriptCache::~GDScriptCache() {
+ destructing = true;
+
+ RBSet<Ref<GDScriptParserRef>> parser_map_refs;
+ for (KeyValue<String, GDScriptParserRef *> &E : parser_map) {
+ parser_map_refs.insert(E.value);
+ }
+
+ for (Ref<GDScriptParserRef> &E : parser_map_refs) {
+ if (E.is_valid())
+ E->clear();
+ }
+
+ parser_map_refs.clear();
parser_map.clear();
shallow_gdscript_cache.clear();
full_gdscript_cache.clear();
+
+ packed_scene_cache.clear();
+ packed_scene_dependencies.clear();
+
singleton = nullptr;
}
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index fcd240ba8d..0f9d87aa67 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -36,6 +36,7 @@
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
#include "gdscript.h"
+#include "scene/resources/packed_scene.h"
class GDScriptAnalyzer;
class GDScriptParser;
@@ -56,6 +57,7 @@ private:
Status status = EMPTY;
Error result = OK;
String path;
+ bool cleared = false;
friend class GDScriptCache;
@@ -64,6 +66,7 @@ public:
Status get_status() const;
GDScriptParser *get_parser() const;
Error raise_status(Status p_new_status);
+ void clear();
GDScriptParserRef() {}
~GDScriptParserRef();
@@ -72,19 +75,25 @@ public:
class GDScriptCache {
// String key is full path.
HashMap<String, GDScriptParserRef *> parser_map;
- HashMap<String, GDScript *> shallow_gdscript_cache;
- HashMap<String, GDScript *> full_gdscript_cache;
+ HashMap<String, Ref<GDScript>> shallow_gdscript_cache;
+ HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
+ HashMap<String, Ref<PackedScene>> packed_scene_cache;
+ HashMap<String, HashSet<String>> packed_scene_dependencies;
friend class GDScript;
friend class GDScriptParserRef;
+ friend class GDScriptInstance;
static GDScriptCache *singleton;
- Mutex lock;
- static void remove_script(const String &p_path);
+ bool destructing = false;
+
+ Mutex mutex;
public:
+ static void move_script(const String &p_from, const String &p_to);
+ static void remove_script(const String &p_path);
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String());
@@ -92,6 +101,17 @@ public:
static Ref<GDScript> get_cached_script(const String &p_path);
static Error finish_compiling(const String &p_owner);
+ static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = "");
+ static Ref<GDScript> get_packed_scene_script(const String &p_path, Error &r_error);
+ static void clear_unreferenced_packed_scenes();
+
+ static bool is_destructing() {
+ if (singleton == nullptr) {
+ return true;
+ }
+ return singleton->destructing;
+ };
+
GDScriptCache();
~GDScriptCache();
};
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 824625b745..f0ceb42f89 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -141,6 +141,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.script_type_ref = script;
}
result.script_type = script.ptr();
+ result.native_type = p_datatype.native_type;
}
} break;
case GDScriptParser::DataType::ENUM:
@@ -354,11 +355,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
if (class_node->identifier && class_node->identifier->name == identifier) {
res = Ref<GDScript>(main_script);
} else {
- res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
- if (res.is_null()) {
- _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
- r_error = ERR_COMPILATION_FAILED;
- return GDScriptCodeGenerator::Address();
+ String global_class_path = ScriptServer::get_global_class_path(identifier);
+ if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") {
+ Error err = OK;
+ res = GDScriptCache::get_full_script(global_class_path, err);
+ if (err != OK) {
+ _set_error("Can't load global class " + String(identifier), p_expression);
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
+ }
+ } else {
+ res = ResourceLoader::load(global_class_path);
+ if (res.is_null()) {
+ _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
+ r_error = ERR_COMPILATION_FAILED;
+ return GDScriptCodeGenerator::Address();
+ }
}
}
@@ -2172,6 +2184,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
parsing_classes.insert(p_script);
+ p_script->clearing = true;
#ifdef TOOLS_ENABLED
p_script->doc_functions.clear();
p_script->doc_variables.clear();
@@ -2194,10 +2207,24 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->base = Ref<GDScript>();
p_script->_base = nullptr;
p_script->members.clear();
+
+ // This makes possible to clear script constants and member_functions without heap-use-after-free errors.
+ HashMap<StringName, Variant> constants;
+ for (const KeyValue<StringName, Variant> &E : p_script->constants) {
+ constants.insert(E.key, E.value);
+ }
p_script->constants.clear();
+ constants.clear();
+ HashMap<StringName, GDScriptFunction *> member_functions;
for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) {
+ member_functions.insert(E.key, E.value);
+ }
+ p_script->member_functions.clear();
+ for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) {
memdelete(E.value);
}
+ member_functions.clear();
+
if (p_script->implicit_initializer) {
memdelete(p_script->implicit_initializer);
}
@@ -2212,6 +2239,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
p_script->implicit_initializer = nullptr;
p_script->implicit_ready = nullptr;
+ p_script->clearing = false;
+
p_script->tool = parser->is_tool();
if (!p_script->name.is_empty()) {
@@ -2454,10 +2483,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri
}
#ifdef TOOLS_ENABLED
-
p_script->member_lines[name] = inner_class->start_line;
#endif
-
p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 48a6e3fb51..7628bffd22 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2512,7 +2512,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) {
- if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) {
+ if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) {
const GDScriptParser::GetNodeNode *get_node = nullptr;
const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base);
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 98b3e40f1b..24a614b1ad 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() {
}
GDScriptFunction::~GDScriptFunction() {
+ get_script()->member_functions.erase(name);
+
for (int i = 0; i < lambdas.size(); i++) {
memdelete(lambdas[i]);
}
+ for (int i = 0; i < argument_types.size(); i++) {
+ argument_types.write[i].script_type_ref = Ref<Script>();
+ }
+ return_type.script_type_ref = Ref<Script>();
+
#ifdef DEBUG_ENABLED
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index e44038d6da..6e5f7a8520 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -430,6 +430,7 @@ public:
};
private:
+ friend class GDScript;
friend class GDScriptCompiler;
friend class GDScriptByteCodeGenerator;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 053d81893d..7f2f49f336 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -147,6 +147,10 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
// Networking.
register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true);
+
+#ifdef DEBUG_ENABLED
+ is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
+#endif
}
GDScriptParser::~GDScriptParser() {
@@ -648,7 +652,13 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
n_class->identifier = parse_identifier();
if (n_class->outer) {
- n_class->fqcn = n_class->outer->fqcn + "::" + n_class->identifier->name;
+ String fqcn = n_class->outer->fqcn;
+ if (fqcn.is_empty()) {
+ fqcn = script_path;
+ }
+ n_class->fqcn = fqcn + "::" + n_class->identifier->name;
+ } else {
+ n_class->fqcn = n_class->identifier->name;
}
}
@@ -1535,7 +1545,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
VariableNode *variable = static_cast<VariableNode *>(statement);
const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name);
if (local.type != SuiteNode::Local::UNDEFINED) {
- push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name));
+ push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier);
}
current_suite->add_local(variable, current_function);
break;
@@ -1550,7 +1560,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
} else {
name = "variable";
}
- push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name));
+ push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier);
}
current_suite->add_local(constant, current_function);
break;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index d8f5b866aa..f9a1c5a697 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -786,6 +786,7 @@ public:
LOCAL_VARIABLE,
LOCAL_ITERATOR, // `for` loop iterator.
LOCAL_BIND, // Pattern bind.
+ MEMBER_SIGNAL,
MEMBER_VARIABLE,
MEMBER_CONSTANT,
INHERITED_VARIABLE,
@@ -1216,13 +1217,14 @@ private:
bool can_break = false;
bool can_continue = false;
bool is_continue_match = false; // Whether a `continue` will act on a `match`.
- bool is_ignoring_warnings = false;
List<bool> multiline_stack;
ClassNode *head = nullptr;
Node *list = nullptr;
List<ParserError> errors;
+
#ifdef DEBUG_ENABLED
+ bool is_ignoring_warnings = false;
List<GDScriptWarning> warnings;
HashSet<String> ignored_warnings;
HashSet<uint32_t> ignored_warning_codes;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index a0c107aa53..36bc051643 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const {
} break;
case RETURN_VALUE_DISCARDED: {
CHECK_SYMBOLS(1);
- return "The function '" + symbols[0] + "()' returns a value, but this value is never used.";
+ return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used.";
} break;
case PROPERTY_USED_AS_FUNCTION: {
CHECK_SYMBOLS(2);
@@ -171,6 +171,10 @@ int GDScriptWarning::get_default_value(Code p_code) {
if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) {
return WarnLevel::IGNORE;
}
+ // Too spammy by default on common cases (connect, Tween, etc.).
+ if (p_code == RETURN_VALUE_DISCARDED) {
+ return WarnLevel::IGNORE;
+ }
return WarnLevel::WARN;
}
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 1ccbf9d150..7f42643c8f 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -251,7 +251,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
return false;
}
} else {
- if (next.get_extension().to_lower() == "gd") {
+ if (next.ends_with(".notest.gd")) {
+ next = dir->get_next();
+ continue;
+ } else if (next.get_extension().to_lower() == "gd") {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
Error open_err = OK;
@@ -597,6 +600,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
}
enable_stdout();
+
+ GDScriptCache::remove_script(script->get_path());
+
return result;
}
diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd
index ea744e3027..c3fc176679 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd
@@ -1,7 +1,4 @@
const A := 42
-func test():
- pass
-
func something():
return "OK"
diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd
index 276875dd5a..9d0324ead8 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd
@@ -1,4 +1,4 @@
-const Constants = preload("gdscript_to_preload.gd")
+const Constants = preload("gdscript_to_preload.notest.gd")
func test():
var a := Constants.A
diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd
new file mode 100644
index 0000000000..b730453a8a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd
@@ -0,0 +1,4 @@
+const A = preload("preload_cyclic_reference_a.notest.gd")
+
+func test():
+ A.test_cyclic_reference()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out
index d73c5eb7cd..14bb971221 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out
@@ -1 +1,2 @@
GDTEST_OK
+godot
diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd
new file mode 100644
index 0000000000..7a6035ded1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd
@@ -0,0 +1,12 @@
+const B = preload("preload_cyclic_reference_b.notest.gd")
+
+const WAITING_FOR = "godot"
+
+static func test_cyclic_reference():
+ B.test_cyclic_reference()
+
+static func test_cyclic_reference_2():
+ B.test_cyclic_reference_2()
+
+static func test_cyclic_reference_3():
+ B.test_cyclic_reference_3()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd
new file mode 100644
index 0000000000..3ea5b01156
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd
@@ -0,0 +1,10 @@
+const A = preload("preload_cyclic_reference_a.notest.gd")
+
+static func test_cyclic_reference():
+ A.test_cyclic_reference_2()
+
+static func test_cyclic_reference_2():
+ A.test_cyclic_reference_3()
+
+static func test_cyclic_reference_3():
+ print(A.WAITING_FOR)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd
index 5f73064cc0..beabf3d2e5 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd
@@ -1,4 +1,4 @@
-const preloaded : GDScript = preload("gdscript_to_preload.gd")
+const preloaded : GDScript = preload("gdscript_to_preload.notest.gd")
func test():
var preloaded_instance: preloaded = preloaded.new()
diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out
index 13f759dd46..e89bb9226f 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out
@@ -2,4 +2,4 @@ GDTEST_OK
>> WARNING
>> Line: 6
>> RETURN_VALUE_DISCARDED
->> The function 'i_return_int()' returns a value, but this value is never used.
+>> The function 'i_return_int()' returns a value that will be discarded if not used.
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index 3cd0f5c0f9..588015de62 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -16,6 +16,8 @@
<param index="3" name="flags" type="int" default="0" />
<param index="4" name="bake_fps" type="int" default="30" />
<description>
+ Takes a [PackedByteArray] defining a gLTF and returns a [GLTFState] object through the [param state] parameter.
+ [b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty.
</description>
</method>
<method name="append_from_file">
@@ -26,6 +28,8 @@
<param index="3" name="bake_fps" type="int" default="30" />
<param index="4" name="base_path" type="String" default="&quot;&quot;" />
<description>
+ Takes a path to a gLTF file and returns a [GLTFState] object through the [param state] parameter.
+ [b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty.
</description>
</method>
<method name="append_from_scene">
@@ -35,12 +39,14 @@
<param index="2" name="flags" type="int" default="0" />
<param index="3" name="bake_fps" type="int" default="30" />
<description>
+ Takes a Godot Engine scene node and returns a [GLTFState] object through the [param state] parameter.
</description>
</method>
<method name="generate_buffer">
<return type="PackedByteArray" />
<param index="0" name="state" type="GLTFState" />
<description>
+ Takes a [GLTFState] object through the [param state] parameter and returns a gLTF [PackedByteArray].
</description>
</method>
<method name="generate_scene">
@@ -48,6 +54,23 @@
<param index="0" name="state" type="GLTFState" />
<param index="1" name="bake_fps" type="int" default="30" />
<description>
+ Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node.
+ </description>
+ </method>
+ <method name="register_gltf_document_extension" qualifiers="static">
+ <return type="void" />
+ <param index="0" name="extension" type="GLTFDocumentExtension" />
+ <param index="1" name="first_priority" type="bool" default="false" />
+ <description>
+ Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is true, this extension will be run first. Otherwise, it will be run last.
+ [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode].
+ </description>
+ </method>
+ <method name="unregister_gltf_document_extension" qualifiers="static">
+ <return type="void" />
+ <param index="0" name="extension" type="GLTFDocumentExtension" />
+ <description>
+ Unregisters the given [GLTFDocumentExtension] instance.
</description>
</method>
<method name="write_to_filesystem">
@@ -55,11 +78,9 @@
<param index="0" name="state" type="GLTFState" />
<param index="1" name="path" type="String" />
<description>
+ Takes a [GLTFState] object through the [param state] parameter and writes a glTF file to the filesystem.
+ [b]Note:[/b] The extension of the glTF file determines if it is a .glb binary file or a .gltf file.
</description>
</method>
</methods>
- <members>
- <member name="extensions" type="GLTFDocumentExtension[]" setter="set_extensions" getter="get_extensions" default="[]">
- </member>
- </members>
</class>
diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
index 936794976d..87d3d9bcb0 100644
--- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml
+++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml
@@ -5,10 +5,22 @@
</brief_description>
<description>
Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export.
+ To use, make a new class extending GLTFDocumentExtension, override any methods you need, make an instance of your class, and register it using [method GLTFDocument.register_gltf_document_extension].
+ [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode].
</description>
<tutorials>
</tutorials>
<methods>
+ <method name="_convert_scene_node" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="gltf_node" type="GLTFNode" />
+ <param index="2" name="scene_node" type="Node" />
+ <description>
+ Part of the export process. This method is run after [method _export_preflight] and before [method _export_node].
+ Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node].
+ </description>
+ </method>
<method name="_export_node" qualifiers="virtual">
<return type="int" />
<param index="0" name="state" type="GLTFState" />
@@ -16,23 +28,40 @@
<param index="2" name="json" type="Dictionary" />
<param index="3" name="node" type="Node" />
<description>
+ Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post].
+ This method can be used to modify the final JSON of each node.
</description>
</method>
<method name="_export_post" qualifiers="virtual">
<return type="int" />
<param index="0" name="state" type="GLTFState" />
<description>
+ Part of the export process. This method is run last, after all other parts of the export process.
+ This method can be used to modify the final JSON of the generated GLTF file.
</description>
</method>
<method name="_export_preflight" qualifiers="virtual">
<return type="int" />
<param index="0" name="root" type="Node" />
<description>
+ Part of the export process. This method is run first, before all other parts of the export process.
+ The return value is used to determine if this GLTFDocumentExtension class should be used for exporting a given GLTF file. If [constant OK], the export will use this GLTFDocumentExtension class. If not overridden, [constant OK] is returned.
+ </description>
+ </method>
+ <method name="_generate_scene_node" qualifiers="virtual">
+ <return type="Node3D" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="gltf_node" type="GLTFNode" />
+ <param index="2" name="scene_parent" type="Node" />
+ <description>
+ Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_post_parse].
+ Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node.
</description>
</method>
<method name="_get_supported_extensions" qualifiers="virtual">
<return type="PackedStringArray" />
<description>
+ Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions].
Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded.
</description>
</method>
@@ -43,6 +72,8 @@
<param index="2" name="json" type="Dictionary" />
<param index="3" name="node" type="Node" />
<description>
+ Part of the import process. This method is run after [method _import_post_parse] and before [method _import_post].
+ This method can be used to make modifications to each of the generated Godot scene nodes.
</description>
</method>
<method name="_import_post" qualifiers="virtual">
@@ -50,18 +81,35 @@
<param index="0" name="state" type="GLTFState" />
<param index="1" name="root" type="Node" />
<description>
+ Part of the import process. This method is run last, after all other parts of the import process.
+ This method can be used to modify the final Godot scene generated by the import process.
</description>
</method>
<method name="_import_post_parse" qualifiers="virtual">
<return type="int" />
<param index="0" name="state" type="GLTFState" />
<description>
+ Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node].
+ This method can be used to modify any of the data imported so far, including any scene nodes, before running the final per-node import step.
</description>
</method>
<method name="_import_preflight" qualifiers="virtual">
<return type="int" />
<param index="0" name="state" type="GLTFState" />
+ <param index="1" name="extensions" type="PackedStringArray" />
+ <description>
+ Part of the import process. This method is run first, before all other parts of the import process.
+ The return value is used to determine if this GLTFDocumentExtension class should be used for importing a given GLTF file. If [constant OK], the import will use this GLTFDocumentExtension class. If not overridden, [constant OK] is returned.
+ </description>
+ </method>
+ <method name="_parse_node_extensions" qualifiers="virtual">
+ <return type="int" />
+ <param index="0" name="state" type="GLTFState" />
+ <param index="1" name="gltf_node" type="GLTFNode" />
+ <param index="2" name="extensions" type="Dictionary" />
<description>
+ Part of the import process. This method is run after [method _get_supported_extensions] and before [method _generate_scene_node].
+ Runs when parsing the node extensions of a GLTFNode. This method can be used to process the extension JSON data into a format that can be used by [method _generate_scene_node].
</description>
</method>
</methods>
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index d0740cf7ca..9a554a0d49 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -66,7 +66,7 @@
</description>
</method>
<method name="get_materials">
- <return type="BaseMaterial3D[]" />
+ <return type="Material[]" />
<description>
</description>
</method>
@@ -169,7 +169,7 @@
</method>
<method name="set_materials">
<return type="void" />
- <param index="0" name="materials" type="BaseMaterial3D[]" />
+ <param index="0" name="materials" type="Material[]" />
<description>
</description>
</method>
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index b17a1e4eaa..edca038532 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -33,9 +33,6 @@
#ifdef TOOLS_ENABLED
-#include "../gltf_document_extension.h"
-#include "../gltf_state.h"
-
#include "editor/import/resource_importer_scene.h"
class Animation;
diff --git a/modules/gltf/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 713779712c..f997fe8f66 100644
--- a/modules/gltf/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -31,50 +31,77 @@
#include "gltf_document_extension.h"
void GLTFDocumentExtension::_bind_methods() {
+ // Import process.
+ GDVIRTUAL_BIND(_import_preflight, "state", "extensions");
GDVIRTUAL_BIND(_get_supported_extensions);
- GDVIRTUAL_BIND(_import_preflight, "state");
+ GDVIRTUAL_BIND(_parse_node_extensions, "state", "gltf_node", "extensions");
+ GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent");
GDVIRTUAL_BIND(_import_post_parse, "state");
GDVIRTUAL_BIND(_import_node, "state", "gltf_node", "json", "node");
GDVIRTUAL_BIND(_import_post, "state", "root");
+ // Export process.
GDVIRTUAL_BIND(_export_preflight, "root");
+ GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node");
GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node");
GDVIRTUAL_BIND(_export_post, "state");
}
+// Import process.
+Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
+ ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ int err = OK;
+ GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err);
+ return Error(err);
+}
+
Vector<String> GLTFDocumentExtension::get_supported_extensions() {
Vector<String> ret;
GDVIRTUAL_CALL(_get_supported_extensions, ret);
return ret;
}
-Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
- ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
+Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
+ GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err);
return Error(err);
}
-Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state) {
+Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
+ ERR_FAIL_NULL_V(p_state, nullptr);
+ ERR_FAIL_NULL_V(p_gltf_node, nullptr);
+ ERR_FAIL_NULL_V(p_scene_parent, nullptr);
+ Node3D *ret_node = nullptr;
+ GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node);
+ return ret_node;
+}
+
+Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_import_preflight, p_state, err);
+ GDVIRTUAL_CALL(_import_post_parse, p_state, err);
return Error(err);
}
-Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
+Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_import_post_parse, p_state, err);
+ GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
return Error(err);
}
-Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
+Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
+ ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_export_post, p_state, err);
+ GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
return Error(err);
}
+
+// Export process.
Error GLTFDocumentExtension::export_preflight(Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
int err = OK;
@@ -82,20 +109,25 @@ Error GLTFDocumentExtension::export_preflight(Node *p_root) {
return Error(err);
}
-Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
+void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
+ ERR_FAIL_NULL(p_state);
+ ERR_FAIL_NULL(p_gltf_node);
+ ERR_FAIL_NULL(p_scene_node);
+ GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node);
+}
+
+Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
+ GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
return Error(err);
}
-Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
+Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
int err = OK;
- GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
+ GDVIRTUAL_CALL(_export_post, p_state, err);
return Error(err);
}
diff --git a/modules/gltf/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h
index d4bb3993dc..7cc9ca592f 100644
--- a/modules/gltf/gltf_document_extension.h
+++ b/modules/gltf/extensions/gltf_document_extension.h
@@ -31,8 +31,7 @@
#ifndef GLTF_DOCUMENT_EXTENSION_H
#define GLTF_DOCUMENT_EXTENSION_H
-#include "gltf_state.h"
-#include "structures/gltf_node.h"
+#include "../gltf_state.h"
class GLTFDocumentExtension : public Resource {
GDCLASS(GLTFDocumentExtension, Resource);
@@ -41,20 +40,31 @@ protected:
static void _bind_methods();
public:
+ // Import process.
+ virtual Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions);
virtual Vector<String> get_supported_extensions();
- virtual Error import_preflight(Ref<GLTFState> p_state);
+ virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions);
+ virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
virtual Error import_post_parse(Ref<GLTFState> p_state);
- virtual Error export_post(Ref<GLTFState> p_state);
+ virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
virtual Error import_post(Ref<GLTFState> p_state, Node *p_node);
+ // Export process.
virtual Error export_preflight(Node *p_state);
- virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
+ virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node);
+ virtual Error export_post(Ref<GLTFState> p_state);
+
+ // Import process.
+ GDVIRTUAL2R(int, _import_preflight, Ref<GLTFState>, Vector<String>);
GDVIRTUAL0R(Vector<String>, _get_supported_extensions);
- GDVIRTUAL1R(int, _import_preflight, Ref<GLTFState>);
+ GDVIRTUAL3R(int, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary);
+ GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL1R(int, _import_post_parse, Ref<GLTFState>);
GDVIRTUAL4R(int, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
GDVIRTUAL2R(int, _import_post, Ref<GLTFState>, Node *);
+ // Export process.
GDVIRTUAL1R(int, _export_preflight, Node *);
+ GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *);
GDVIRTUAL4R(int, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *);
GDVIRTUAL1R(int, _export_post, Ref<GLTFState>);
};
diff --git a/modules/gltf/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index 1620900a04..49496afb62 100644
--- a/modules/gltf/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -30,7 +30,7 @@
#include "gltf_document_extension_convert_importer_mesh.h"
-#include "gltf_state.h"
+#include "../gltf_state.h"
#include "core/error/error_macros.h"
#include "scene/3d/mesh_instance_3d.h"
diff --git a/modules/gltf/gltf_document_extension_convert_importer_mesh.h b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h
index 00e664e73f..00e664e73f 100644
--- a/modules/gltf/gltf_document_extension_convert_importer_mesh.h
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 99803ed05d..eb8f7e5ebc 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -31,8 +31,6 @@
#include "gltf_document.h"
#include "extensions/gltf_spec_gloss.h"
-#include "gltf_document_extension.h"
-#include "gltf_document_extension_convert_importer_mesh.h"
#include "gltf_state.h"
#include "core/crypto/crypto_core.h"
@@ -215,8 +213,7 @@ Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) {
return Error::FAILED;
}
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
err = ext->export_post(state);
ERR_FAIL_COND_V(err != OK, err);
@@ -454,8 +451,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) {
node["children"] = children;
}
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
ERR_CONTINUE(!state->scene_nodes.find(i));
Error err = ext->export_node(state, gltf_node, node, state->scene_nodes[i]);
@@ -629,6 +625,11 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) {
node->light = light;
}
}
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Error err = ext->parse_node_extensions(state, node, extensions);
+ ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + state->filename + ". Continuing.");
+ }
}
if (n.has("children")) {
@@ -2483,12 +2484,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) {
if (surface_i < instance_materials.size()) {
v = instance_materials.get(surface_i);
}
- Ref<BaseMaterial3D> mat = v;
+ Ref<Material> mat = v;
if (!mat.is_valid()) {
mat = import_mesh->get_surface_material(surface_i);
}
if (mat.is_valid()) {
- HashMap<Ref<BaseMaterial3D>, GLTFMaterialIndex>::Iterator material_cache_i = state->material_cache.find(mat);
+ HashMap<Ref<Material>, GLTFMaterialIndex>::Iterator material_cache_i = state->material_cache.find(mat);
if (material_cache_i && material_cache_i->value != -1) {
primitive["material"] = material_cache_i->value;
} else {
@@ -2936,16 +2937,18 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) {
}
}
- Ref<BaseMaterial3D> mat;
+ Ref<Material> mat;
String mat_name;
if (!state->discard_meshes_and_materials) {
if (p.has("material")) {
const int material = p["material"];
ERR_FAIL_INDEX_V(material, state->materials.size(), ERR_FILE_CORRUPT);
- Ref<BaseMaterial3D> mat3d = state->materials[material];
+ Ref<Material> mat3d = state->materials[material];
ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
- if (has_vertex_color) {
- mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+
+ Ref<BaseMaterial3D> base_material = mat3d;
+ if (has_vertex_color && base_material.is_valid()) {
+ base_material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
@@ -2953,7 +2956,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) {
Ref<StandardMaterial3D> mat3d;
mat3d.instantiate();
if (has_vertex_color) {
- mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ mat3d->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
}
mat = mat3d;
}
@@ -3381,8 +3384,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
Array materials;
for (int32_t i = 0; i < state->materials.size(); i++) {
Dictionary d;
-
- Ref<BaseMaterial3D> material = state->materials[i];
+ Ref<Material> material = state->materials[i];
if (material.is_null()) {
materials.push_back(d);
continue;
@@ -3390,11 +3392,12 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
if (!material->get_name().is_empty()) {
d["name"] = _gen_unique_name(state, material->get_name());
}
- {
+ Ref<BaseMaterial3D> base_material = material;
+ if (base_material.is_valid()) {
Dictionary mr;
{
Array arr;
- const Color c = material->get_albedo().srgb_to_linear();
+ const Color c = base_material->get_albedo().srgb_to_linear();
arr.push_back(c.r);
arr.push_back(c.g);
arr.push_back(c.b);
@@ -3403,167 +3406,169 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
}
{
Dictionary bct;
- Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
- GLTFTextureIndex gltf_texture_index = -1;
+ if (base_material.is_valid()) {
+ Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
+ GLTFTextureIndex gltf_texture_index = -1;
- if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
- albedo_texture->set_name(material->get_name() + "_albedo");
- gltf_texture_index = _set_texture(state, albedo_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
- }
- if (gltf_texture_index != -1) {
- bct["index"] = gltf_texture_index;
- Dictionary extensions = _serialize_texture_transform_uv1(material);
- if (!extensions.is_empty()) {
- bct["extensions"] = extensions;
- state->use_khr_texture_transform = true;
+ if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
+ albedo_texture->set_name(material->get_name() + "_albedo");
+ gltf_texture_index = _set_texture(state, albedo_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
+ }
+ if (gltf_texture_index != -1) {
+ bct["index"] = gltf_texture_index;
+ Dictionary extensions = _serialize_texture_transform_uv1(material);
+ if (!extensions.is_empty()) {
+ bct["extensions"] = extensions;
+ state->use_khr_texture_transform = true;
+ }
+ mr["baseColorTexture"] = bct;
}
- mr["baseColorTexture"] = bct;
}
}
-
- mr["metallicFactor"] = material->get_metallic();
- mr["roughnessFactor"] = material->get_roughness();
- bool has_roughness = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid();
- bool has_ao = material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid();
- bool has_metalness = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid();
- if (has_ao || has_roughness || has_metalness) {
- Dictionary mrt;
- Ref<Texture2D> roughness_texture = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS);
- BaseMaterial3D::TextureChannel roughness_channel = material->get_roughness_texture_channel();
- Ref<Texture2D> metallic_texture = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC);
- BaseMaterial3D::TextureChannel metalness_channel = material->get_metallic_texture_channel();
- Ref<Texture2D> ao_texture = material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION);
- BaseMaterial3D::TextureChannel ao_channel = material->get_ao_texture_channel();
- Ref<ImageTexture> orm_texture;
- orm_texture.instantiate();
- Ref<Image> orm_image;
- orm_image.instantiate();
- int32_t height = 0;
- int32_t width = 0;
- Ref<Image> ao_image;
- if (has_ao) {
- height = ao_texture->get_height();
- width = ao_texture->get_width();
- ao_image = ao_texture->get_image();
- Ref<ImageTexture> img_tex = ao_image;
- if (img_tex.is_valid()) {
- ao_image = img_tex->get_image();
+ if (base_material.is_valid()) {
+ mr["metallicFactor"] = base_material->get_metallic();
+ mr["roughnessFactor"] = base_material->get_roughness();
+ bool has_roughness = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid();
+ bool has_ao = base_material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid();
+ bool has_metalness = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid();
+ if (has_ao || has_roughness || has_metalness) {
+ Dictionary mrt;
+ Ref<Texture2D> roughness_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS);
+ BaseMaterial3D::TextureChannel roughness_channel = base_material->get_roughness_texture_channel();
+ Ref<Texture2D> metallic_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC);
+ BaseMaterial3D::TextureChannel metalness_channel = base_material->get_metallic_texture_channel();
+ Ref<Texture2D> ao_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION);
+ BaseMaterial3D::TextureChannel ao_channel = base_material->get_ao_texture_channel();
+ Ref<ImageTexture> orm_texture;
+ orm_texture.instantiate();
+ Ref<Image> orm_image;
+ orm_image.instantiate();
+ int32_t height = 0;
+ int32_t width = 0;
+ Ref<Image> ao_image;
+ if (has_ao) {
+ height = ao_texture->get_height();
+ width = ao_texture->get_width();
+ ao_image = ao_texture->get_image();
+ Ref<ImageTexture> img_tex = ao_image;
+ if (img_tex.is_valid()) {
+ ao_image = img_tex->get_image();
+ }
+ if (ao_image->is_compressed()) {
+ ao_image->decompress();
+ }
}
- if (ao_image->is_compressed()) {
- ao_image->decompress();
+ Ref<Image> roughness_image;
+ if (has_roughness) {
+ height = roughness_texture->get_height();
+ width = roughness_texture->get_width();
+ roughness_image = roughness_texture->get_image();
+ Ref<ImageTexture> img_tex = roughness_image;
+ if (img_tex.is_valid()) {
+ roughness_image = img_tex->get_image();
+ }
+ if (roughness_image->is_compressed()) {
+ roughness_image->decompress();
+ }
}
- }
- Ref<Image> roughness_image;
- if (has_roughness) {
- height = roughness_texture->get_height();
- width = roughness_texture->get_width();
- roughness_image = roughness_texture->get_image();
- Ref<ImageTexture> img_tex = roughness_image;
- if (img_tex.is_valid()) {
- roughness_image = img_tex->get_image();
+ Ref<Image> metallness_image;
+ if (has_metalness) {
+ height = metallic_texture->get_height();
+ width = metallic_texture->get_width();
+ metallness_image = metallic_texture->get_image();
+ Ref<ImageTexture> img_tex = metallness_image;
+ if (img_tex.is_valid()) {
+ metallness_image = img_tex->get_image();
+ }
+ if (metallness_image->is_compressed()) {
+ metallness_image->decompress();
+ }
}
- if (roughness_image->is_compressed()) {
- roughness_image->decompress();
+ Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
+ if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
+ height = albedo_texture->get_height();
+ width = albedo_texture->get_width();
}
- }
- Ref<Image> metallness_image;
- if (has_metalness) {
- height = metallic_texture->get_height();
- width = metallic_texture->get_width();
- metallness_image = metallic_texture->get_image();
- Ref<ImageTexture> img_tex = metallness_image;
- if (img_tex.is_valid()) {
- metallness_image = img_tex->get_image();
+ orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8);
+ if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) {
+ ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
}
- if (metallness_image->is_compressed()) {
- metallness_image->decompress();
+ if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) {
+ roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
}
- }
- Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO);
- if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) {
- height = albedo_texture->get_height();
- width = albedo_texture->get_width();
- }
- orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8);
- if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) {
- ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) {
- roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) {
- metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
- }
- for (int32_t h = 0; h < height; h++) {
- for (int32_t w = 0; w < width; w++) {
- Color c = Color(1.0f, 1.0f, 1.0f);
- if (has_ao) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) {
- c.r = ao_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) {
- c.r = ao_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) {
- c.r = ao_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) {
- c.r = ao_image->get_pixel(w, h).a;
+ if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) {
+ metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS);
+ }
+ for (int32_t h = 0; h < height; h++) {
+ for (int32_t w = 0; w < width; w++) {
+ Color c = Color(1.0f, 1.0f, 1.0f);
+ if (has_ao) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) {
+ c.r = ao_image->get_pixel(w, h).a;
+ }
}
- }
- if (has_roughness) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) {
- c.g = roughness_image->get_pixel(w, h).a;
+ if (has_roughness) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) {
+ c.g = roughness_image->get_pixel(w, h).a;
+ }
}
- }
- if (has_metalness) {
- if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).r;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).g;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).b;
- } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) {
- c.b = metallness_image->get_pixel(w, h).a;
+ if (has_metalness) {
+ if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).r;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).g;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).b;
+ } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) {
+ c.b = metallness_image->get_pixel(w, h).a;
+ }
}
+ orm_image->set_pixel(w, h, c);
}
- orm_image->set_pixel(w, h, c);
}
- }
- orm_image->generate_mipmaps();
- orm_texture->set_image(orm_image);
- GLTFTextureIndex orm_texture_index = -1;
- if (has_ao || has_roughness || has_metalness) {
- orm_texture->set_name(material->get_name() + "_orm");
- orm_texture_index = _set_texture(state, orm_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
- }
- if (has_ao) {
- Dictionary occt;
- occt["index"] = orm_texture_index;
- d["occlusionTexture"] = occt;
- }
- if (has_roughness || has_metalness) {
- mrt["index"] = orm_texture_index;
- Dictionary extensions = _serialize_texture_transform_uv1(material);
- if (!extensions.is_empty()) {
- mrt["extensions"] = extensions;
- state->use_khr_texture_transform = true;
+ orm_image->generate_mipmaps();
+ orm_texture->set_image(orm_image);
+ GLTFTextureIndex orm_texture_index = -1;
+ if (has_ao || has_roughness || has_metalness) {
+ orm_texture->set_name(material->get_name() + "_orm");
+ orm_texture_index = _set_texture(state, orm_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
+ }
+ if (has_ao) {
+ Dictionary occt;
+ occt["index"] = orm_texture_index;
+ d["occlusionTexture"] = occt;
+ }
+ if (has_roughness || has_metalness) {
+ mrt["index"] = orm_texture_index;
+ Dictionary extensions = _serialize_texture_transform_uv1(material);
+ if (!extensions.is_empty()) {
+ mrt["extensions"] = extensions;
+ state->use_khr_texture_transform = true;
+ }
+ mr["metallicRoughnessTexture"] = mrt;
}
- mr["metallicRoughnessTexture"] = mrt;
}
}
d["pbrMetallicRoughness"] = mr;
}
-
- if (material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) {
+ if (base_material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) {
Dictionary nt;
Ref<ImageTexture> tex;
tex.instantiate();
{
- Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL);
+ Ref<Texture2D> normal_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_NORMAL);
if (normal_texture.is_valid()) {
// Code for uncompressing RG normal maps
Ref<Image> img = normal_texture->get_image();
@@ -3593,30 +3598,30 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
GLTFTextureIndex gltf_texture_index = -1;
if (tex.is_valid() && tex->get_image().is_valid()) {
tex->set_name(material->get_name() + "_normal");
- gltf_texture_index = _set_texture(state, tex, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
+ gltf_texture_index = _set_texture(state, tex, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
- nt["scale"] = material->get_normal_scale();
+ nt["scale"] = base_material->get_normal_scale();
if (gltf_texture_index != -1) {
nt["index"] = gltf_texture_index;
d["normalTexture"] = nt;
}
}
- if (material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
- const Color c = material->get_emission().linear_to_srgb();
+ if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
+ const Color c = base_material->get_emission().linear_to_srgb();
Array arr;
arr.push_back(c.r);
arr.push_back(c.g);
arr.push_back(c.b);
d["emissiveFactor"] = arr;
}
- if (material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
+ if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) {
Dictionary et;
- Ref<Texture2D> emission_texture = material->get_texture(BaseMaterial3D::TEXTURE_EMISSION);
+ Ref<Texture2D> emission_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_EMISSION);
GLTFTextureIndex gltf_texture_index = -1;
if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) {
emission_texture->set_name(material->get_name() + "_emission");
- gltf_texture_index = _set_texture(state, emission_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
+ gltf_texture_index = _set_texture(state, emission_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT));
}
if (gltf_texture_index != -1) {
@@ -3624,14 +3629,14 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) {
d["emissiveTexture"] = et;
}
}
- const bool ds = material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED;
+ const bool ds = base_material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED;
if (ds) {
d["doubleSided"] = ds;
}
- if (material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) {
+ if (base_material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) {
d["alphaMode"] = "MASK";
- d["alphaCutoff"] = material->get_alpha_scissor_threshold();
- } else if (material->get_transparency() != BaseMaterial3D::TRANSPARENCY_DISABLED) {
+ d["alphaCutoff"] = base_material->get_alpha_scissor_threshold();
+ } else if (base_material->get_transparency() != BaseMaterial3D::TRANSPARENCY_DISABLED) {
d["alphaMode"] = "BLEND";
}
materials.push_back(d);
@@ -3837,29 +3842,37 @@ void GLTFDocument::_set_texture_transform_uv1(const Dictionary &d, Ref<BaseMater
if (d.has("extensions")) {
const Dictionary &extensions = d["extensions"];
if (extensions.has("KHR_texture_transform")) {
- const Dictionary &texture_transform = extensions["KHR_texture_transform"];
- const Array &offset_arr = texture_transform["offset"];
- if (offset_arr.size() == 2) {
- const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f);
- material->set_uv1_offset(offset_vector3);
- }
+ if (material.is_valid()) {
+ const Dictionary &texture_transform = extensions["KHR_texture_transform"];
+ const Array &offset_arr = texture_transform["offset"];
+ if (offset_arr.size() == 2) {
+ const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f);
+ material->set_uv1_offset(offset_vector3);
+ }
- const Array &scale_arr = texture_transform["scale"];
- if (scale_arr.size() == 2) {
- const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f);
- material->set_uv1_scale(scale_vector3);
+ const Array &scale_arr = texture_transform["scale"];
+ if (scale_arr.size() == 2) {
+ const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f);
+ material->set_uv1_scale(scale_vector3);
+ }
}
}
}
}
void GLTFDocument::spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss, Ref<BaseMaterial3D> p_material) {
+ if (r_spec_gloss.is_null()) {
+ return;
+ }
if (r_spec_gloss->spec_gloss_img.is_null()) {
return;
}
if (r_spec_gloss->diffuse_img.is_null()) {
return;
}
+ if (p_material.is_null()) {
+ return;
+ }
bool has_roughness = false;
bool has_metal = false;
p_material->set_roughness(1.0f);
@@ -5270,6 +5283,10 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, co
AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current);
_convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current);
}
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ ext->convert_scene_node(state, gltf_node, p_current);
+ }
GLTFNodeIndex current_node_i = state->nodes.size();
GLTFNodeIndex gltf_root = p_gltf_root;
if (gltf_root == -1) {
@@ -5593,21 +5610,32 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent
// and attach it to the bone_attachment
scene_parent = bone_attachment;
}
- if (gltf_node->mesh >= 0) {
- current_node = _generate_mesh_instance(state, node_index);
- } else if (gltf_node->camera >= 0) {
- current_node = _generate_camera(state, node_index);
- } else if (gltf_node->light >= 0) {
- current_node = _generate_light(state, node_index);
+ // Check if any GLTFDocumentExtension classes want to generate a node for us.
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ current_node = ext->generate_scene_node(state, gltf_node, scene_parent);
+ if (current_node) {
+ break;
+ }
}
-
- // We still have not managed to make a node.
+ // If none of our GLTFDocumentExtension classes generated us a node, we generate one.
if (!current_node) {
- current_node = _generate_spatial(state, node_index);
+ if (gltf_node->mesh >= 0) {
+ current_node = _generate_mesh_instance(state, node_index);
+ } else if (gltf_node->camera >= 0) {
+ current_node = _generate_camera(state, node_index);
+ } else if (gltf_node->light >= 0) {
+ current_node = _generate_light(state, node_index);
+ } else {
+ current_node = _generate_spatial(state, node_index);
+ }
}
+ // Add the node we generated and set the owner to the scene root.
scene_parent->add_child(current_node, true);
if (current_node != scene_root) {
- current_node->set_owner(scene_root);
+ Array args;
+ args.append(scene_root);
+ current_node->propagate_call(StringName("set_owner"), args);
}
current_node->set_transform(gltf_node->xform);
current_node->set_name(gltf_node->get_name());
@@ -5673,19 +5701,32 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> state, Node *scen
// and attach it to the bone_attachment
scene_parent = bone_attachment;
}
-
- // We still have not managed to make a node
- if (gltf_node->mesh >= 0) {
- current_node = _generate_mesh_instance(state, node_index);
- } else if (gltf_node->camera >= 0) {
- current_node = _generate_camera(state, node_index);
- } else if (gltf_node->light >= 0) {
- current_node = _generate_light(state, node_index);
+ // Check if any GLTFDocumentExtension classes want to generate a node for us.
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ current_node = ext->generate_scene_node(state, gltf_node, scene_parent);
+ if (current_node) {
+ break;
+ }
+ }
+ // If none of our GLTFDocumentExtension classes generated us a node, we generate one.
+ if (!current_node) {
+ if (gltf_node->mesh >= 0) {
+ current_node = _generate_mesh_instance(state, node_index);
+ } else if (gltf_node->camera >= 0) {
+ current_node = _generate_camera(state, node_index);
+ } else if (gltf_node->light >= 0) {
+ current_node = _generate_light(state, node_index);
+ } else {
+ current_node = _generate_spatial(state, node_index);
+ }
}
-
+ // Add the node we generated and set the owner to the scene root.
scene_parent->add_child(current_node, true);
if (current_node != scene_root) {
- current_node->set_owner(scene_root);
+ Array args;
+ args.append(scene_root);
+ current_node->propagate_call(StringName("set_owner"), args);
}
// Do not set transform here. Transform is already applied to our bone.
current_node->set_name(gltf_node->get_name());
@@ -6586,11 +6627,13 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess>
state->major_version = version.get_slice(".", 0).to_int();
state->minor_version = version.get_slice(".", 1).to_int();
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ document_extensions.clear();
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
- err = ext->import_preflight(state);
- ERR_FAIL_COND_V(err != OK, err);
+ err = ext->import_preflight(state, state->json["extensionsUsed"]);
+ if (err == OK) {
+ document_extensions.push_back(ext);
+ }
}
err = _parse_gltf_state(state, p_path, p_bake_fps);
@@ -6626,21 +6669,17 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) {
}
Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) {
- if (p_material.is_valid()) {
- Vector3 offset = p_material->get_uv1_offset();
- Vector3 scale = p_material->get_uv1_scale();
- return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
- }
- return Dictionary();
+ ERR_FAIL_NULL_V(p_material, Dictionary());
+ Vector3 offset = p_material->get_uv1_offset();
+ Vector3 scale = p_material->get_uv1_scale();
+ return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) {
- if (p_material.is_valid()) {
- Vector3 offset = p_material->get_uv2_offset();
- Vector3 scale = p_material->get_uv2_scale();
- return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
- }
- return Dictionary();
+ ERR_FAIL_NULL_V(p_material, Dictionary());
+ Vector3 offset = p_material->get_uv2_offset();
+ Vector3 scale = p_material->get_uv2_scale();
+ return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
Error GLTFDocument::_serialize_version(Ref<GLTFState> state) {
@@ -6728,14 +6767,10 @@ void GLTFDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
&GLTFDocument::write_to_filesystem);
- ClassDB::bind_method(D_METHOD("set_extensions", "extensions"),
- &GLTFDocument::set_extensions);
- ClassDB::bind_method(D_METHOD("get_extensions"),
- &GLTFDocument::get_extensions);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "extensions", PROPERTY_HINT_ARRAY_TYPE,
- vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "GLTFDocumentExtension"),
- PROPERTY_USAGE_DEFAULT),
- "set_extensions", "get_extensions");
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"),
+ &GLTFDocument::register_gltf_document_extension, DEFVAL(false));
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
+ &GLTFDocument::unregister_gltf_document_extension);
}
void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) {
@@ -6752,22 +6787,24 @@ void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) {
}
}
-void GLTFDocument::set_extensions(TypedArray<GLTFDocumentExtension> p_extensions) {
- document_extensions = p_extensions;
+Vector<Ref<GLTFDocumentExtension>> GLTFDocument::all_document_extensions;
+
+void GLTFDocument::register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority) {
+ if (all_document_extensions.find(p_extension) == -1) {
+ if (p_first_priority) {
+ all_document_extensions.insert(0, p_extension);
+ } else {
+ all_document_extensions.push_back(p_extension);
+ }
+ }
}
-TypedArray<GLTFDocumentExtension> GLTFDocument::get_extensions() const {
- return document_extensions;
+void GLTFDocument::unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension) {
+ all_document_extensions.erase(p_extension);
}
-GLTFDocument::GLTFDocument() {
- bool is_editor = ::Engine::get_singleton()->is_editor_hint();
- if (is_editor) {
- return;
- }
- Ref<GLTFDocumentExtensionConvertImporterMesh> extension_editor;
- extension_editor.instantiate();
- document_extensions.push_back(extension_editor);
+void GLTFDocument::unregister_all_gltf_document_extensions() {
+ all_document_extensions.clear();
}
PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> state, Error *r_err) {
@@ -6852,8 +6889,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) {
}
for (KeyValue<GLTFNodeIndex, Node *> E : state->scene_nodes) {
ERR_CONTINUE(!E.value);
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
ERR_CONTINUE(!state->json.has("nodes"));
Array nodes = state->json["nodes"];
@@ -6865,8 +6901,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) {
ERR_CONTINUE(err != OK);
}
}
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
err = ext->import_post(state, root);
ERR_CONTINUE(err != OK);
@@ -6880,11 +6915,13 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32
state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS;
state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS;
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ document_extensions.clear();
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
ERR_CONTINUE(ext.is_null());
Error err = ext->export_preflight(p_node);
- ERR_FAIL_COND_V(err != OK, FAILED);
+ if (err == OK) {
+ document_extensions.push_back(ext);
+ }
}
_convert_scene_node(state, p_node, -1, -1);
if (!state->buffers.size()) {
@@ -6906,8 +6943,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa
state->base_path = p_base_path.get_base_dir();
err = _parse(state, state->base_path, file_access, p_bake_fps);
ERR_FAIL_COND_V(err != OK, err);
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
err = ext->import_post_parse(state);
ERR_FAIL_COND_V(err != OK, err);
@@ -7030,8 +7066,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint
r_state->base_path = base_path;
err = _parse(r_state, base_path, f, p_bake_fps);
ERR_FAIL_COND_V(err != OK, err);
- for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
err = ext->import_post_parse(r_state);
ERR_FAIL_COND_V(err != OK, err);
@@ -7053,8 +7088,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> state) {
supported_extensions.insert("KHR_lights_punctual");
supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
supported_extensions.insert("KHR_texture_transform");
- for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) {
- Ref<GLTFDocumentExtension> ext = document_extensions[ext_i];
+ for (Ref<GLTFDocumentExtension> ext : document_extensions) {
ERR_CONTINUE(ext.is_null());
Vector<String> ext_supported_extensions = ext->get_supported_extensions();
for (int i = 0; i < ext_supported_extensions.size(); ++i) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 62b6e29fe0..5a0e4ff498 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -31,7 +31,7 @@
#ifndef GLTF_DOCUMENT_H
#define GLTF_DOCUMENT_H
-#include "gltf_defines.h"
+#include "extensions/gltf_document_extension.h"
#include "structures/gltf_animation.h"
#include "scene/3d/bone_attachment_3d.h"
@@ -44,13 +44,13 @@
class GLTFDocument : public Resource {
GDCLASS(GLTFDocument, Resource);
- TypedArray<GLTFDocumentExtension> document_extensions;
+ static Vector<Ref<GLTFDocumentExtension>> all_document_extensions;
+ Vector<Ref<GLTFDocumentExtension>> document_extensions;
private:
const float BAKE_FPS = 30.0f;
public:
- GLTFDocument();
const int32_t JOINT_GROUP_SIZE = 4;
enum {
@@ -76,8 +76,9 @@ protected:
static void _bind_methods();
public:
- void set_extensions(TypedArray<GLTFDocumentExtension> p_extensions);
- TypedArray<GLTFDocumentExtension> get_extensions() const;
+ static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false);
+ static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
+ static void unregister_all_gltf_document_extensions();
private:
void _build_parent_hierachy(Ref<GLTFState> state);
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index ac5665e396..6654c9e5d2 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -209,11 +209,11 @@ void GLTFState::set_meshes(TypedArray<GLTFMesh> p_meshes) {
GLTFTemplateConvert::set_from_array(meshes, p_meshes);
}
-TypedArray<BaseMaterial3D> GLTFState::get_materials() {
+TypedArray<Material> GLTFState::get_materials() {
return GLTFTemplateConvert::to_array(materials);
}
-void GLTFState::set_materials(TypedArray<BaseMaterial3D> p_materials) {
+void GLTFState::set_materials(TypedArray<Material> p_materials) {
GLTFTemplateConvert::set_from_array(materials, p_materials);
}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index e24017b0fd..1c20520b22 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -72,8 +72,8 @@ class GLTFState : public Resource {
Vector<Ref<GLTFMesh>> meshes; // meshes are loaded directly, no reason not to.
Vector<AnimationPlayer *> animation_players;
- HashMap<Ref<BaseMaterial3D>, GLTFMaterialIndex> material_cache;
- Vector<Ref<BaseMaterial3D>> materials;
+ HashMap<Ref<Material>, GLTFMaterialIndex> material_cache;
+ Vector<Ref<Material>> materials;
String scene_name;
Vector<int> root_nodes;
@@ -138,8 +138,8 @@ public:
TypedArray<GLTFMesh> get_meshes();
void set_meshes(TypedArray<GLTFMesh> p_meshes);
- TypedArray<BaseMaterial3D> get_materials();
- void set_materials(TypedArray<BaseMaterial3D> p_materials);
+ TypedArray<Material> get_materials();
+ void set_materials(TypedArray<Material> p_materials);
String get_scene_name();
void set_scene_name(String p_scene_name);
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index b9027f6e3d..a7abf256ce 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -32,11 +32,10 @@
#ifndef _3D_DISABLED
+#include "extensions/gltf_document_extension_convert_importer_mesh.h"
#include "extensions/gltf_light.h"
#include "extensions/gltf_spec_gloss.h"
#include "gltf_document.h"
-#include "gltf_document_extension.h"
-#include "gltf_document_extension_convert_importer_mesh.h"
#include "gltf_state.h"
#include "structures/gltf_accessor.h"
#include "structures/gltf_animation.h"
@@ -109,6 +108,11 @@ static void _editor_init() {
}
#endif // TOOLS_ENABLED
+#define GLTF_REGISTER_DOCUMENT_EXTENSION(m_doc_ext_class) \
+ Ref<m_doc_ext_class> extension_##m_doc_ext_class; \
+ extension_##m_doc_ext_class.instantiate(); \
+ GLTFDocument::register_gltf_document_extension(extension_##m_doc_ext_class);
+
void initialize_gltf_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
// glTF API available at runtime.
@@ -128,6 +132,11 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(GLTFState);
GDREGISTER_CLASS(GLTFTexture);
GDREGISTER_CLASS(GLTFTextureSampler);
+ // Register GLTFDocumentExtension classes with GLTFDocument.
+ bool is_editor = ::Engine::get_singleton()->is_editor_hint();
+ if (!is_editor) {
+ GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh);
+ }
}
#ifdef TOOLS_ENABLED
@@ -161,6 +170,7 @@ void uninitialize_gltf_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
+ GLTFDocument::unregister_all_gltf_document_extensions();
}
#endif // _3D_DISABLED
diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp
index ba19ed8628..a15141225b 100644
--- a/modules/gltf/structures/gltf_buffer_view.cpp
+++ b/modules/gltf/structures/gltf_buffer_view.cpp
@@ -30,8 +30,6 @@
#include "gltf_buffer_view.h"
-#include "../gltf_document_extension.h"
-
void GLTFBufferView::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_buffer"), &GLTFBufferView::get_buffer);
ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &GLTFBufferView::set_buffer);
diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml
index 0f3662c3cf..bd5c938364 100644
--- a/modules/gridmap/doc_classes/GridMap.xml
+++ b/modules/gridmap/doc_classes/GridMap.xml
@@ -25,12 +25,14 @@
<method name="clear_baked_meshes">
<return type="void" />
<description>
+ Clears all baked meshes. See [method make_baked_meshes].
</description>
</method>
<method name="get_bake_mesh_instance">
<return type="RID" />
<param index="0" name="idx" type="int" />
<description>
+ Returns [RID] of a baked mesh with the given [param idx].
</description>
</method>
<method name="get_bake_meshes">
@@ -133,6 +135,7 @@
<param index="0" name="gen_lightmap_uv" type="bool" default="false" />
<param index="1" name="lightmap_uv_texel_size" type="float" default="0.1" />
<description>
+ Bakes lightmap data for all meshes in the assigned [MeshLibrary].
</description>
</method>
<method name="map_to_local" qualifiers="const">
@@ -146,6 +149,7 @@
<return type="void" />
<param index="0" name="resource" type="Resource" />
<description>
+ Notifies the [GridMap] about changed resource and recreates octant data.
</description>
</method>
<method name="set_cell_item">
diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp
index e84d95773d..e658668355 100644
--- a/modules/mbedtls/packet_peer_mbed_dtls.cpp
+++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp
@@ -118,7 +118,6 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali
ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER);
base = p_base;
- int ret = 0;
int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE;
Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs);
@@ -130,7 +129,7 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali
status = STATUS_HANDSHAKING;
- if ((ret = _do_handshake()) != OK) {
+ if (_do_handshake() != OK) {
status = STATUS_ERROR_HOSTNAME_MISMATCH;
return FAILED;
}
@@ -158,7 +157,7 @@ Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey>
status = STATUS_HANDSHAKING;
- if ((ret = _do_handshake()) != OK) {
+ if (_do_handshake() != OK) {
status = STATUS_ERROR;
return FAILED;
}
@@ -175,7 +174,7 @@ Error PacketPeerMbedDTLS::put_packet(const uint8_t *p_buffer, int p_bytes) {
int ret = mbedtls_ssl_write(tls_ctx->get_context(), p_buffer, p_bytes);
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
- ret = 0; // non blocking io
+ // Non blocking io.
} else if (ret <= 0) {
TLSContextMbedTLS::print_mbedtls_error(ret);
_cleanup();
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
index ac8d6473a6..9a46b7d164 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#pragma warning disable CS0169
@@ -83,6 +84,10 @@ namespace Godot.SourceGenerators.Sample
[Export] private StringName[] field_StringNameArray = { "foo", "bar" };
[Export] private NodePath[] field_NodePathArray = { "foo", "bar" };
[Export] private RID[] field_RIDArray = { default, default, default };
+ // Note we use Array and not System.Array. This tests the generated namespace qualification.
+ [Export] private Int32[] field_empty_Int32Array = Array.Empty<Int32>();
+ // Note we use List and not System.Collections.Generic.
+ [Export] private int[] field_array_from_list = new List<int>(Array.Empty<int>()).ToArray();
// Variant
[Export] private Variant field_Variant = "foo";
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs
new file mode 100644
index 0000000000..a6c8e52667
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+#pragma warning disable CS0169
+#pragma warning disable CS0414
+
+namespace Godot.SourceGenerators.Sample
+{
+ [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")]
+ [SuppressMessage("ReSharper", "RedundantNameQualifier")]
+ [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ // We split the definition of ExportedFields to verify properties work across multiple files.
+ public partial class ExportedFields : Godot.Object
+ {
+ // Note we use Array and not System.Array. This tests the generated namespace qualification.
+ [Export] private Int64[] field_empty_Int64Array = Array.Empty<Int64>();
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index 8de12de23b..9e3add4262 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -165,6 +166,50 @@ namespace Godot.SourceGenerators
public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
=> namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
+ public static string FullQualifiedName(this ISymbol symbol)
+ => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
+
+ public static string FullQualifiedSyntax(this SyntaxNode node, SemanticModel sm)
+ {
+ StringBuilder sb = new();
+ FullQualifiedSyntax_(node, sm, sb, true);
+ return sb.ToString();
+ }
+
+ private static void FullQualifiedSyntax_(SyntaxNode node, SemanticModel sm, StringBuilder sb, bool isFirstNode)
+ {
+ if (node is NameSyntax ns && isFirstNode)
+ {
+ SymbolInfo nameInfo = sm.GetSymbolInfo(ns);
+ sb.Append(nameInfo.Symbol?.FullQualifiedName() ?? ns.ToString());
+ return;
+ }
+
+ bool innerIsFirstNode = true;
+ foreach (var child in node.ChildNodesAndTokens())
+ {
+ if (child.HasLeadingTrivia)
+ {
+ sb.Append(child.GetLeadingTrivia());
+ }
+
+ if (child.IsNode)
+ {
+ FullQualifiedSyntax_(child.AsNode()!, sm, sb, isFirstNode: innerIsFirstNode);
+ innerIsFirstNode = false;
+ }
+ else
+ {
+ sb.Append(child);
+ }
+
+ if (child.HasTrailingTrivia)
+ {
+ sb.Append(child.GetTrailingTrivia());
+ }
+ }
+ }
+
public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName)
=> qualifiedName
// AddSource() doesn't support angle brackets
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index 1a25d684a0..88c0e71155 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -71,29 +71,29 @@ namespace Godot.SourceGenerators
Expression = 19,
PlaceholderText = 20,
ColorNoAlpha = 21,
- ImageCompressLossy = 22,
- ImageCompressLossless = 23,
- ObjectId = 24,
- TypeString = 25,
- NodePathToEditedNode = 26,
- MethodOfVariantType = 27,
- MethodOfBaseType = 28,
- MethodOfInstance = 29,
- MethodOfScript = 30,
- PropertyOfVariantType = 31,
- PropertyOfBaseType = 32,
- PropertyOfInstance = 33,
- PropertyOfScript = 34,
- ObjectTooBig = 35,
- NodePathValidTypes = 36,
- SaveFile = 37,
- GlobalSaveFile = 38,
- IntIsObjectid = 39,
- IntIsPointer = 41,
- ArrayType = 40,
- LocaleId = 42,
- LocalizableString = 43,
- NodeType = 44,
+ ObjectId = 22,
+ TypeString = 23,
+ NodePathToEditedNode = 24,
+ MethodOfVariantType = 25,
+ MethodOfBaseType = 26,
+ MethodOfInstance = 27,
+ MethodOfScript = 28,
+ PropertyOfVariantType = 29,
+ PropertyOfBaseType = 30,
+ PropertyOfInstance = 31,
+ PropertyOfScript = 32,
+ ObjectTooBig = 33,
+ NodePathValidTypes = 34,
+ SaveFile = 35,
+ GlobalSaveFile = 36,
+ IntIsObjectid = 37,
+ IntIsPointer = 38,
+ ArrayType = 39,
+ LocaleId = 40,
+ LocalizableString = 41,
+ NodeType = 42,
+ HideQuaternionEdit = 43,
+ Password = 44,
Max = 45
}
@@ -128,12 +128,14 @@ namespace Godot.SourceGenerators
DeferredSetResource = 33554432,
EditorInstantiateObject = 67108864,
EditorBasicSetting = 134217728,
+ ReadOnly = 268435456,
Array = 536870912,
Default = 6,
DefaultIntl = 38,
NoEditor = 2
}
+ [Flags]
public enum MethodFlags
{
Normal = 1,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
index 98b9745c16..9a18ba3ab2 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
@@ -170,7 +170,13 @@ namespace Godot.SourceGenerators
.Select(s => s?.Initializer ?? null)
.FirstOrDefault();
- string? value = initializer?.Value.ToString();
+ // Fully qualify the value to avoid issues with namespaces.
+ string? value = null;
+ if (initializer != null)
+ {
+ var sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree);
+ value = initializer.Value.FullQualifiedSyntax(sm);
+ }
exportedMembers.Add(new ExportedPropertyMetadata(
property.Name, marshalType.Value, propertyType, value));
@@ -207,7 +213,13 @@ namespace Godot.SourceGenerators
.Select(s => s.Initializer)
.FirstOrDefault(i => i != null);
- string? value = initializer?.Value.ToString();
+ // This needs to be fully qualified to avoid issues with namespaces.
+ string? value = null;
+ if (initializer != null)
+ {
+ var sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree);
+ value = initializer.Value.FullQualifiedSyntax(sm);
+ }
exportedMembers.Add(new ExportedPropertyMetadata(
field.Name, marshalType.Value, fieldType, value));
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index 535391f447..96a6cde52c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -249,14 +249,11 @@ namespace Godot
/// <returns>The interpolated vector.</returns>
public readonly Vector2 BezierInterpolate(Vector2 control1, Vector2 control2, Vector2 end, real_t t)
{
- // Formula from Wikipedia article on Bezier curves
- real_t omt = 1 - t;
- real_t omt2 = omt * omt;
- real_t omt3 = omt2 * omt;
- real_t t2 = t * t;
- real_t t3 = t2 * t;
-
- return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3;
+ return new Vector2
+ (
+ Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t),
+ Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t)
+ );
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index 53bd0b0908..5fef474eed 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
@@ -243,14 +243,12 @@ namespace Godot
/// <returns>The interpolated vector.</returns>
public readonly Vector3 BezierInterpolate(Vector3 control1, Vector3 control2, Vector3 end, real_t t)
{
- // Formula from Wikipedia article on Bezier curves
- real_t omt = 1 - t;
- real_t omt2 = omt * omt;
- real_t omt3 = omt2 * omt;
- real_t t2 = t * t;
- real_t t3 = t2 * t;
-
- return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3;
+ return new Vector3
+ (
+ Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t),
+ Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t),
+ Mathf.BezierInterpolate(z, control1.z, control2.z, end.z, t)
+ );
}
/// <summary>
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index a7e5b80b66..cce22b9084 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -36,13 +36,19 @@
void EditorNetworkProfiler::_bind_methods() {
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
+ ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path")));
}
void EditorNetworkProfiler::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ node_icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons"));
+ if (activate->is_pressed()) {
+ activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+ } else {
+ activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ }
clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons")));
outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")));
@@ -54,29 +60,104 @@ void EditorNetworkProfiler::_notification(int p_what) {
}
}
-void EditorNetworkProfiler::_update_frame() {
+void EditorNetworkProfiler::_refresh() {
+ if (!dirty) {
+ return;
+ }
+ dirty = false;
+ refresh_rpc_data();
+ refresh_replication_data();
+}
+
+void EditorNetworkProfiler::refresh_rpc_data() {
counters_display->clear();
TreeItem *root = counters_display->create_item();
+ int cols = counters_display->get_columns();
- for (const KeyValue<ObjectID, RPCNodeInfo> &E : nodes_data) {
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_data) {
TreeItem *node = counters_display->create_item(root);
- for (int j = 0; j < counters_display->get_columns(); ++j) {
+ for (int j = 0; j < cols; ++j) {
node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT);
}
node->set_text(0, E.value.node_path);
- node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc));
- node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
+ node->set_text(1, E.value.incoming_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.incoming_rpc, String::humanize_size(E.value.incoming_size)));
+ node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.outgoing_rpc, String::humanize_size(E.value.outgoing_size)));
+ }
+}
+
+void EditorNetworkProfiler::refresh_replication_data() {
+ replication_display->clear();
+
+ TreeItem *root = replication_display->create_item();
+
+ for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) {
+ // Ensure the nodes have at least a temporary cache.
+ ObjectID ids[3] = { E.value.synchronizer, E.value.config, E.value.root_node };
+ for (uint32_t i = 0; i < 3; i++) {
+ const ObjectID &id = ids[i];
+ if (!node_data.has(id)) {
+ missing_node_data.insert(id);
+ node_data[id] = NodeInfo(id);
+ }
+ }
+
+ TreeItem *node = replication_display->create_item(root);
+
+ const NodeInfo &root_info = node_data[E.value.root_node];
+ const NodeInfo &sync_info = node_data[E.value.synchronizer];
+ const NodeInfo &cfg_info = node_data[E.value.config];
+
+ node->set_text(0, root_info.path.get_file());
+ node->set_icon(0, has_theme_icon(root_info.type, SNAME("EditorIcons")) ? get_theme_icon(root_info.type, SNAME("EditorIcons")) : node_icon);
+ node->set_tooltip_text(0, root_info.path);
+
+ node->set_text(1, sync_info.path.get_file());
+ node->set_icon(1, get_theme_icon("MultiplayerSynchronizer", SNAME("EditorIcons")));
+ node->set_tooltip_text(1, sync_info.path);
+
+ int cfg_idx = cfg_info.path.find("::");
+ if (cfg_info.path.begins_with("res://") && ResourceLoader::exists(cfg_info.path) && cfg_idx > 0) {
+ String res_idstr = cfg_info.path.substr(cfg_idx + 2).replace("SceneReplicationConfig_", "");
+ String scene_path = cfg_info.path.substr(0, cfg_idx);
+ node->set_text(2, vformat("%s (%s)", res_idstr, scene_path.get_file()));
+ node->add_button(2, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")));
+ node->set_tooltip_text(2, cfg_info.path);
+ node->set_metadata(2, scene_path);
+ } else {
+ node->set_text(2, cfg_info.path);
+ node->set_metadata(2, "");
+ }
+
+ node->set_text(3, vformat("%d - %d", E.value.incoming_syncs, E.value.outgoing_syncs));
+ node->set_text(4, vformat("%d - %d", E.value.incoming_size, E.value.outgoing_size));
}
}
+Array EditorNetworkProfiler::pop_missing_node_data() {
+ Array out;
+ for (const ObjectID &id : missing_node_data) {
+ out.push_back(id);
+ }
+ missing_node_data.clear();
+ return out;
+}
+
+void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) {
+ ERR_FAIL_COND(!node_data.has(p_info.id));
+ node_data[p_info.id] = p_info;
+ dirty = true;
+}
+
void EditorNetworkProfiler::_activate_pressed() {
if (activate->is_pressed()) {
+ refresh_timer->start();
activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
activate->set_text(TTR("Stop"));
} else {
+ refresh_timer->stop();
activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
activate->set_text(TTR("Start"));
}
@@ -84,25 +165,55 @@ void EditorNetworkProfiler::_activate_pressed() {
}
void EditorNetworkProfiler::_clear_pressed() {
- nodes_data.clear();
+ rpc_data.clear();
+ sync_data.clear();
+ node_data.clear();
+ missing_node_data.clear();
set_bandwidth(0, 0);
- if (frame_delay->is_stopped()) {
- frame_delay->set_wait_time(0.1);
- frame_delay->start();
+ refresh_rpc_data();
+ refresh_replication_data();
+}
+
+void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) {
+ if (!p_item) {
+ return;
+ }
+ String meta = p_item->get_metadata(p_column);
+ if (meta.size() && ResourceLoader::exists(meta)) {
+ emit_signal("open_request", meta);
}
}
-void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) {
- if (!nodes_data.has(p_frame.node)) {
- nodes_data.insert(p_frame.node, p_frame);
+void EditorNetworkProfiler::add_rpc_frame_data(const RPCNodeInfo &p_frame) {
+ dirty = true;
+ if (!rpc_data.has(p_frame.node)) {
+ rpc_data.insert(p_frame.node, p_frame);
} else {
- nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
- nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
+ rpc_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
+ rpc_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
}
+ if (p_frame.incoming_rpc) {
+ rpc_data[p_frame.node].incoming_size = p_frame.incoming_size / p_frame.incoming_rpc;
+ }
+ if (p_frame.outgoing_rpc) {
+ rpc_data[p_frame.node].outgoing_size = p_frame.outgoing_size / p_frame.outgoing_rpc;
+ }
+}
- if (frame_delay->is_stopped()) {
- frame_delay->set_wait_time(0.1);
- frame_delay->start();
+void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
+ dirty = true;
+ if (!sync_data.has(p_frame.synchronizer)) {
+ sync_data[p_frame.synchronizer] = p_frame;
+ } else {
+ sync_data[p_frame.synchronizer].incoming_syncs += p_frame.incoming_syncs;
+ sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs;
+ }
+ SyncInfo &info = sync_data[p_frame.synchronizer];
+ if (info.incoming_syncs) {
+ info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs;
+ }
+ if (info.outgoing_syncs) {
+ info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs;
}
}
@@ -168,9 +279,17 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
// Set initial texts in the incoming/outgoing bandwidth labels
set_bandwidth(0, 0);
+ HSplitContainer *sc = memnew(HSplitContainer);
+ add_child(sc);
+ sc->set_v_size_flags(SIZE_EXPAND_FILL);
+ sc->set_h_size_flags(SIZE_EXPAND_FILL);
+ sc->set_split_offset(100 * EDSCALE);
+
+ // RPC
counters_display = memnew(Tree);
- counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
+ counters_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
+ counters_display->set_h_size_flags(SIZE_EXPAND_FILL);
counters_display->set_hide_folding(true);
counters_display->set_hide_root(true);
counters_display->set_columns(3);
@@ -187,11 +306,42 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
counters_display->set_column_expand(2, false);
counters_display->set_column_clip_content(2, true);
counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);
- add_child(counters_display);
+ sc->add_child(counters_display);
+
+ // Replication
+ replication_display = memnew(Tree);
+ replication_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE);
+ replication_display->set_v_size_flags(SIZE_EXPAND_FILL);
+ replication_display->set_h_size_flags(SIZE_EXPAND_FILL);
+ replication_display->set_hide_folding(true);
+ replication_display->set_hide_root(true);
+ replication_display->set_columns(5);
+ replication_display->set_column_titles_visible(true);
+ replication_display->set_column_title(0, TTR("Root"));
+ replication_display->set_column_expand(0, true);
+ replication_display->set_column_clip_content(0, true);
+ replication_display->set_column_custom_minimum_width(0, 80 * EDSCALE);
+ replication_display->set_column_title(1, TTR("Synchronizer"));
+ replication_display->set_column_expand(1, true);
+ replication_display->set_column_clip_content(1, true);
+ replication_display->set_column_custom_minimum_width(1, 80 * EDSCALE);
+ replication_display->set_column_title(2, TTR("Config"));
+ replication_display->set_column_expand(2, true);
+ replication_display->set_column_clip_content(2, true);
+ replication_display->set_column_custom_minimum_width(2, 80 * EDSCALE);
+ replication_display->set_column_title(3, TTR("Count"));
+ replication_display->set_column_expand(3, false);
+ replication_display->set_column_clip_content(3, true);
+ replication_display->set_column_custom_minimum_width(3, 80 * EDSCALE);
+ replication_display->set_column_title(4, TTR("Size"));
+ replication_display->set_column_expand(4, false);
+ replication_display->set_column_clip_content(4, true);
+ replication_display->set_column_custom_minimum_width(4, 80 * EDSCALE);
+ replication_display->connect("button_clicked", callable_mp(this, &EditorNetworkProfiler::_replication_button_clicked));
+ sc->add_child(replication_display);
- frame_delay = memnew(Timer);
- frame_delay->set_wait_time(0.1);
- frame_delay->set_one_shot(true);
- add_child(frame_delay);
- frame_delay->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_update_frame));
+ refresh_timer = memnew(Timer);
+ refresh_timer->set_wait_time(0.5);
+ refresh_timer->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_refresh));
+ add_child(refresh_timer);
}
diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h
index 98d12e3c0a..630747d988 100644
--- a/modules/multiplayer/editor/editor_network_profiler.h
+++ b/modules/multiplayer/editor/editor_network_profiler.h
@@ -43,30 +43,55 @@
class EditorNetworkProfiler : public VBoxContainer {
GDCLASS(EditorNetworkProfiler, VBoxContainer)
+public:
+ struct NodeInfo {
+ ObjectID id;
+ String type;
+ String path;
+
+ NodeInfo() {}
+ NodeInfo(const ObjectID &p_id) {
+ id = p_id;
+ path = String::num_int64(p_id);
+ }
+ };
+
private:
using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo;
+ using SyncInfo = MultiplayerDebugger::SyncInfo;
+ bool dirty = false;
+ Timer *refresh_timer = nullptr;
Button *activate = nullptr;
Button *clear_button = nullptr;
Tree *counters_display = nullptr;
LineEdit *incoming_bandwidth_text = nullptr;
LineEdit *outgoing_bandwidth_text = nullptr;
+ Tree *replication_display = nullptr;
- Timer *frame_delay = nullptr;
-
- HashMap<ObjectID, RPCNodeInfo> nodes_data;
-
- void _update_frame();
+ HashMap<ObjectID, RPCNodeInfo> rpc_data;
+ HashMap<ObjectID, SyncInfo> sync_data;
+ HashMap<ObjectID, NodeInfo> node_data;
+ HashSet<ObjectID> missing_node_data;
+ Ref<Texture2D> node_icon;
void _activate_pressed();
void _clear_pressed();
+ void _refresh();
+ void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
- void add_node_frame_data(const RPCNodeInfo p_frame);
+ void refresh_rpc_data();
+ void refresh_replication_data();
+
+ Array pop_missing_node_data();
+ void add_node_data(const NodeInfo &p_info);
+ void add_rpc_frame_data(const RPCNodeInfo &p_frame);
+ void add_sync_frame_data(const SyncInfo &p_frame);
void set_bandwidth(int p_incoming, int p_outgoing);
bool is_profiling();
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
index 00b1537827..c5cf3e6f24 100644
--- a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
@@ -36,10 +36,18 @@
#include "editor/editor_node.h"
+void MultiplayerEditorDebugger::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path")));
+}
+
bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const {
return p_capture == "multiplayer";
}
+void MultiplayerEditorDebugger::_open_request(const String &p_path) {
+ emit_signal("open_request", p_path);
+}
+
bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
ERR_FAIL_COND_V(!profilers.has(p_session), false);
EditorNetworkProfiler *profiler = profilers[p_session];
@@ -47,10 +55,31 @@ bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_
MultiplayerDebugger::RPCFrame frame;
frame.deserialize(p_data);
for (int i = 0; i < frame.infos.size(); i++) {
- profiler->add_node_frame_data(frame.infos[i]);
+ profiler->add_rpc_frame_data(frame.infos[i]);
+ }
+ return true;
+ } else if (p_message == "multiplayer:syncs") {
+ MultiplayerDebugger::ReplicationFrame frame;
+ frame.deserialize(p_data);
+ for (const KeyValue<ObjectID, MultiplayerDebugger::SyncInfo> &E : frame.infos) {
+ profiler->add_sync_frame_data(E.value);
+ }
+ Array missing = profiler->pop_missing_node_data();
+ if (missing.size()) {
+ // Asks for the object information.
+ get_session(p_session)->send_message("multiplayer:cache", missing);
+ }
+ return true;
+ } else if (p_message == "multiplayer:cache") {
+ ERR_FAIL_COND_V(p_data.size() % 3, false);
+ for (int i = 0; i < p_data.size(); i += 3) {
+ EditorNetworkProfiler::NodeInfo info;
+ info.id = p_data[i].operator ObjectID();
+ info.type = p_data[i + 1].operator String();
+ info.path = p_data[i + 2].operator String();
+ profiler->add_node_data(info);
}
return true;
-
} else if (p_message == "multiplayer:bandwidth") {
ERR_FAIL_COND_V(p_data.size() < 2, false);
profiler->set_bandwidth(p_data[0], p_data[1]);
@@ -62,8 +91,9 @@ bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_
void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) {
Ref<EditorDebuggerSession> session = get_session(p_session_id);
ERR_FAIL_COND(session.is_null());
- session->toggle_profiler("multiplayer", p_enable);
- session->toggle_profiler("rpc", p_enable);
+ session->toggle_profiler("multiplayer:bandwidth", p_enable);
+ session->toggle_profiler("multiplayer:rpc", p_enable);
+ session->toggle_profiler("multiplayer:replication", p_enable);
}
void MultiplayerEditorDebugger::setup_session(int p_session_id) {
@@ -71,20 +101,25 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) {
ERR_FAIL_COND(session.is_null());
EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler);
profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
+ profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request));
profiler->set_name(TTR("Network Profiler"));
session->add_session_tab(profiler);
profilers[p_session_id] = profiler;
}
+/// MultiplayerEditorPlugin
+
MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
repl_editor = memnew(ReplicationEditor);
button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
button->hide();
repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned));
debugger.instantiate();
+ debugger->connect("open_request", callable_mp(this, &MultiplayerEditorPlugin::_open_request));
}
-MultiplayerEditorPlugin::~MultiplayerEditorPlugin() {
+void MultiplayerEditorPlugin::_open_request(const String &p_path) {
+ get_editor_interface()->open_scene_from_path(p_path);
}
void MultiplayerEditorPlugin::_notification(int p_what) {
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h
index 6d1514cdb1..f29a70e897 100644
--- a/modules/multiplayer/editor/multiplayer_editor_plugin.h
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h
@@ -42,8 +42,12 @@ class MultiplayerEditorDebugger : public EditorDebuggerPlugin {
private:
HashMap<int, EditorNetworkProfiler *> profilers;
+ void _open_request(const String &p_path);
void _profiler_activate(bool p_enable, int p_session_id);
+protected:
+ static void _bind_methods();
+
public:
virtual bool has_capture(const String &p_capture) const override;
virtual bool capture(const String &p_message, const Array &p_data, int p_index) override;
@@ -62,6 +66,7 @@ private:
ReplicationEditor *repl_editor = nullptr;
Ref<MultiplayerEditorDebugger> debugger;
+ void _open_request(const String &p_path);
void _node_removed(Node *p_node);
void _pinned();
@@ -75,7 +80,6 @@ public:
virtual void make_visible(bool p_visible) override;
MultiplayerEditorPlugin();
- ~MultiplayerEditorPlugin();
};
#endif // MULTIPLAYER_EDITOR_PLUGIN_H
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
index 3d22af04dc..9086ee6ec9 100644
--- a/modules/multiplayer/multiplayer_debugger.cpp
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -30,6 +30,9 @@
#include "multiplayer_debugger.h"
+#include "multiplayer_synchronizer.h"
+#include "scene_replication_config.h"
+
#include "core/debugger/engine_debugger.h"
#include "scene/main/node.h"
@@ -38,19 +41,51 @@ List<Ref<EngineProfiler>> multiplayer_profilers;
void MultiplayerDebugger::initialize() {
Ref<BandwidthProfiler> bandwidth;
bandwidth.instantiate();
- bandwidth->bind("multiplayer");
+ bandwidth->bind("multiplayer:bandwidth");
multiplayer_profilers.push_back(bandwidth);
Ref<RPCProfiler> rpc_profiler;
rpc_profiler.instantiate();
- rpc_profiler->bind("rpc");
+ rpc_profiler->bind("multiplayer:rpc");
multiplayer_profilers.push_back(rpc_profiler);
+
+ Ref<ReplicationProfiler> replication_profiler;
+ replication_profiler.instantiate();
+ replication_profiler->bind("multiplayer:replication");
+ multiplayer_profilers.push_back(replication_profiler);
+
+ EngineDebugger::register_message_capture("multiplayer", EngineDebugger::Capture(nullptr, &_capture));
}
void MultiplayerDebugger::deinitialize() {
multiplayer_profilers.clear();
}
+Error MultiplayerDebugger::_capture(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
+ if (p_msg == "cache") {
+ Array out;
+ for (int i = 0; i < p_args.size(); i++) {
+ ObjectID id = p_args[i].operator ObjectID();
+ Object *obj = ObjectDB::get_instance(id);
+ ERR_CONTINUE(!obj);
+ if (Object::cast_to<SceneReplicationConfig>(obj)) {
+ out.push_back(id);
+ out.push_back(obj->get_class());
+ out.push_back(((SceneReplicationConfig *)obj)->get_path());
+ } else if (Object::cast_to<Node>(obj)) {
+ out.push_back(id);
+ out.push_back(obj->get_class());
+ out.push_back(String(((Node *)obj)->get_path()));
+ } else {
+ ERR_FAIL_V(FAILED);
+ }
+ }
+ EngineDebugger::get_singleton()->send_message("multiplayer:cache", out);
+ return OK;
+ }
+ ERR_FAIL_V(FAILED);
+}
+
// BandwidthProfiler
int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
@@ -126,12 +161,14 @@ void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_
Array MultiplayerDebugger::RPCFrame::serialize() {
Array arr;
- arr.push_back(infos.size() * 4);
+ arr.push_back(infos.size() * 6);
for (int i = 0; i < infos.size(); ++i) {
arr.push_back(uint64_t(infos[i].node));
arr.push_back(infos[i].node_path);
arr.push_back(infos[i].incoming_rpc);
+ arr.push_back(infos[i].incoming_size);
arr.push_back(infos[i].outgoing_rpc);
+ arr.push_back(infos[i].outgoing_size);
}
return arr;
}
@@ -139,15 +176,18 @@ Array MultiplayerDebugger::RPCFrame::serialize() {
bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
ERR_FAIL_COND_V(p_arr.size() < 1, false);
uint32_t size = p_arr[0];
- ERR_FAIL_COND_V(size % 4, false);
+ ERR_FAIL_COND_V(size % 6, false);
ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
- infos.resize(size / 4);
+ infos.resize(size / 6);
int idx = 1;
- for (uint32_t i = 0; i < size / 4; ++i) {
+ for (uint32_t i = 0; i < size / 6; i++) {
infos.write[i].node = uint64_t(p_arr[idx]);
infos.write[i].node_path = p_arr[idx + 1];
infos.write[i].incoming_rpc = p_arr[idx + 2];
- infos.write[i].outgoing_rpc = p_arr[idx + 3];
+ infos.write[i].incoming_size = p_arr[idx + 3];
+ infos.write[i].outgoing_rpc = p_arr[idx + 4];
+ infos.write[i].outgoing_size = p_arr[idx + 5];
+ idx += 6;
}
return true;
}
@@ -159,8 +199,6 @@ void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) {
rpc_node_data.insert(p_node, RPCNodeInfo());
rpc_node_data[p_node].node = p_node;
rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
- rpc_node_data[p_node].incoming_rpc = 0;
- rpc_node_data[p_node].outgoing_rpc = 0;
}
void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) {
@@ -168,15 +206,18 @@ void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts
}
void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) {
- ERR_FAIL_COND(p_data.size() < 2);
- const ObjectID id = p_data[0];
- const String what = p_data[1];
+ ERR_FAIL_COND(p_data.size() != 3);
+ const String what = p_data[0];
+ const ObjectID id = p_data[1];
+ const int size = p_data[2];
init_node(id);
RPCNodeInfo &info = rpc_node_data[id];
if (what == "rpc_in") {
info.incoming_rpc++;
+ info.incoming_size += size;
} else if (what == "rpc_out") {
info.outgoing_rpc++;
+ info.outgoing_size += size;
}
}
@@ -192,3 +233,101 @@ void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_proces
EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
}
}
+
+// ReplicationProfiler
+
+MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) {
+ ERR_FAIL_COND(!p_sync);
+ synchronizer = p_sync->get_instance_id();
+ if (p_sync->get_replication_config().is_valid()) {
+ config = p_sync->get_replication_config()->get_instance_id();
+ }
+ if (p_sync->get_root_node()) {
+ root_node = p_sync->get_root_node()->get_instance_id();
+ }
+}
+
+void MultiplayerDebugger::SyncInfo::write_to_array(Array &r_arr) const {
+ r_arr.push_back(synchronizer);
+ r_arr.push_back(config);
+ r_arr.push_back(root_node);
+ r_arr.push_back(incoming_syncs);
+ r_arr.push_back(incoming_size);
+ r_arr.push_back(outgoing_syncs);
+ r_arr.push_back(outgoing_size);
+}
+
+bool MultiplayerDebugger::SyncInfo::read_from_array(const Array &p_arr, int p_offset) {
+ ERR_FAIL_COND_V(p_arr.size() - p_offset < 7, false);
+ synchronizer = int64_t(p_arr[p_offset]);
+ config = int64_t(p_arr[p_offset + 1]);
+ root_node = int64_t(p_arr[p_offset + 2]);
+ incoming_syncs = p_arr[p_offset + 3];
+ incoming_size = p_arr[p_offset + 4];
+ outgoing_syncs = p_arr[p_offset + 5];
+ outgoing_size = p_arr[p_offset + 6];
+ return true;
+}
+
+Array MultiplayerDebugger::ReplicationFrame::serialize() {
+ Array arr;
+ arr.push_back(infos.size() * 7);
+ for (const KeyValue<ObjectID, SyncInfo> &E : infos) {
+ E.value.write_to_array(arr);
+ }
+ return arr;
+}
+
+bool MultiplayerDebugger::ReplicationFrame::deserialize(const Array &p_arr) {
+ ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ uint32_t size = p_arr[0];
+ ERR_FAIL_COND_V(size % 7, false);
+ ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 7; i++) {
+ SyncInfo info;
+ if (!info.read_from_array(p_arr, idx)) {
+ return false;
+ }
+ infos[info.synchronizer] = info;
+ idx += 7;
+ }
+ return true;
+}
+
+void MultiplayerDebugger::ReplicationProfiler::toggle(bool p_enable, const Array &p_opts) {
+ sync_data.clear();
+}
+
+void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() != 3);
+ const String what = p_data[0];
+ const ObjectID id = p_data[1];
+ const uint64_t size = p_data[2];
+ MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id));
+ ERR_FAIL_COND(!sync);
+ if (!sync_data.has(id)) {
+ sync_data[id] = SyncInfo(sync);
+ }
+ SyncInfo &info = sync_data[id];
+ if (what == "sync_in") {
+ info.incoming_syncs++;
+ info.incoming_size += size;
+ } else if (what == "sync_out") {
+ info.outgoing_syncs++;
+ info.outgoing_size += size;
+ }
+}
+
+void MultiplayerDebugger::ReplicationProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_profile_time > 100) {
+ last_profile_time = pt;
+ ReplicationFrame frame;
+ for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) {
+ frame.infos[E.key] = E.value;
+ }
+ sync_data.clear();
+ EngineDebugger::get_singleton()->send_message("multiplayer:syncs", frame.serialize());
+ }
+}
diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h
index 4efd1da016..f5c092f0f9 100644
--- a/modules/multiplayer/multiplayer_debugger.h
+++ b/modules/multiplayer/multiplayer_debugger.h
@@ -35,13 +35,17 @@
#include "core/os/os.h"
+class MultiplayerSynchronizer;
+
class MultiplayerDebugger {
public:
struct RPCNodeInfo {
ObjectID node;
String node_path;
int incoming_rpc = 0;
+ int incoming_size = 0;
int outgoing_rpc = 0;
+ int outgoing_size = 0;
};
struct RPCFrame {
@@ -51,6 +55,29 @@ public:
bool deserialize(const Array &p_arr);
};
+ struct SyncInfo {
+ ObjectID synchronizer;
+ ObjectID config;
+ ObjectID root_node;
+ int incoming_syncs = 0;
+ int incoming_size = 0;
+ int outgoing_syncs = 0;
+ int outgoing_size = 0;
+
+ void write_to_array(Array &r_arr) const;
+ bool read_from_array(const Array &p_arr, int p_offset);
+
+ SyncInfo() {}
+ SyncInfo(MultiplayerSynchronizer *p_sync);
+ };
+
+ struct ReplicationFrame {
+ HashMap<ObjectID, SyncInfo> infos;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
private:
class BandwidthProfiler : public EngineProfiler {
protected:
@@ -74,7 +101,6 @@ private:
};
class RPCProfiler : public EngineProfiler {
- public:
private:
HashMap<ObjectID, RPCNodeInfo> rpc_node_data;
uint64_t last_profile_time = 0;
@@ -87,6 +113,19 @@ private:
void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
};
+ class ReplicationProfiler : public EngineProfiler {
+ private:
+ HashMap<ObjectID, SyncInfo> sync_data;
+ uint64_t last_profile_time = 0;
+
+ public:
+ void toggle(bool p_enable, const Array &p_opts);
+ void add(const Array &p_data);
+ void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
+ };
+
+ static Error _capture(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
+
public:
static void initialize();
static void deinitialize();
diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp
index 2bf1041029..ca85a46f31 100644
--- a/modules/multiplayer/register_types.cpp
+++ b/modules/multiplayer/register_types.cpp
@@ -47,6 +47,7 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(SceneReplicationConfig);
GDREGISTER_CLASS(MultiplayerSpawner);
GDREGISTER_CLASS(MultiplayerSynchronizer);
+ GDREGISTER_CLASS(OfflineMultiplayerPeer);
GDREGISTER_CLASS(SceneMultiplayer);
MultiplayerAPI::set_default_interface("SceneMultiplayer");
MultiplayerDebugger::initialize();
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index 7df9b95b30..f0da4f9dfc 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -99,10 +99,6 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND(multiplayer_peer.is_null());
-#ifdef DEBUG_ENABLED
- multiplayer->profile_bandwidth("out", packet.size());
-#endif
-
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
multiplayer->send_command(p_from, packet.ptr(), packet.size());
@@ -155,10 +151,6 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_V(multiplayer_peer.is_null(), ERR_BUG);
-#ifdef DEBUG_ENABLED
- multiplayer->profile_bandwidth("out", packet.size() * p_peers.size());
-#endif
-
Error err = OK;
for (int peer_id : p_peers) {
multiplayer_peer->set_transfer_channel(0);
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index db7c5037cd..5042a0502d 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -40,13 +40,13 @@
#endif
#ifdef DEBUG_ENABLED
-void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) {
- if (EngineDebugger::is_profiling("multiplayer")) {
+_FORCE_INLINE_ void SceneMultiplayer::_profile_bandwidth(const String &p_what, int p_value) {
+ if (EngineDebugger::is_profiling("multiplayer:bandwidth")) {
Array values;
- values.push_back(p_inout);
+ values.push_back(p_what);
values.push_back(OS::get_singleton()->get_ticks_msec());
- values.push_back(p_size);
- EngineDebugger::profiler_add_frame_data("multiplayer", values);
+ values.push_back(p_value);
+ EngineDebugger::profiler_add_frame_data("multiplayer:bandwidth", values);
}
}
#endif
@@ -91,6 +91,10 @@ Error SceneMultiplayer::poll() {
Error err = multiplayer_peer->get_packet(&packet, len);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err));
+#ifdef DEBUG_ENABLED
+ _profile_bandwidth("in", len);
+#endif
+
if (pending_peers.has(sender)) {
if (pending_peers[sender].local) {
// If the auth is over, admit the peer at the first packet.
@@ -220,10 +224,6 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int
ERR_FAIL_COND_MSG(root_path.is_empty(), "Multiplayer root was not initialized. If you are using custom multiplayer, remember to set the root path via SceneMultiplayer.set_root_path before using it.");
ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small.");
-#ifdef DEBUG_ENABLED
- profile_bandwidth("in", p_packet_len);
-#endif
-
// Extract the `packet_type` from the LSB three bits:
uint8_t packet_type = p_packet[0] & CMD_MASK;
@@ -258,6 +258,13 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int
}
}
+#ifdef DEBUG_ENABLED
+_FORCE_INLINE_ Error SceneMultiplayer::_send(const uint8_t *p_packet, int p_packet_len) {
+ _profile_bandwidth("out", p_packet_len);
+ return multiplayer_peer->put_packet(p_packet, p_packet_len);
+}
+#endif
+
Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_packet_len) {
if (server_relay && get_unique_id() != 1 && p_to != 1 && multiplayer_peer->is_server_relay_supported()) {
// Send relay packet.
@@ -268,19 +275,19 @@ Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_pa
relay_buffer->put_data(p_packet, p_packet_len);
multiplayer_peer->set_target_peer(1);
const Vector<uint8_t> data = relay_buffer->get_data_array();
- return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ return _send(data.ptr(), relay_buffer->get_position());
}
if (p_to > 0) {
ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG);
multiplayer_peer->set_target_peer(p_to);
- return multiplayer_peer->put_packet(p_packet, p_packet_len);
+ return _send(p_packet, p_packet_len);
} else {
for (const int &pid : connected_peers) {
if (p_to && pid == -p_to) {
continue;
}
multiplayer_peer->set_target_peer(pid);
- multiplayer_peer->put_packet(p_packet, p_packet_len);
+ _send(p_packet, p_packet_len);
}
return OK;
}
@@ -319,7 +326,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
multiplayer_peer->set_transfer_channel(p_channel);
if (peer > 0) {
multiplayer_peer->set_target_peer(peer);
- multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ _send(data.ptr(), relay_buffer->get_position());
} else {
for (const int &P : connected_peers) {
// Not to sender, nor excluded.
@@ -327,7 +334,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
continue;
}
multiplayer_peer->set_target_peer(P);
- multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
+ _send(data.ptr(), relay_buffer->get_position());
}
}
if (peer == 0 || peer == -1) {
@@ -373,11 +380,11 @@ void SceneMultiplayer::_admit_peer(int p_id) {
// Send new peer to already connected.
encode_uint32(p_id, &buf[2]);
multiplayer_peer->set_target_peer(P);
- multiplayer_peer->put_packet(buf, sizeof(buf));
+ _send(buf, sizeof(buf));
// Send already connected to new peer.
encode_uint32(P, &buf[2]);
multiplayer_peer->set_target_peer(p_id);
- multiplayer_peer->put_packet(buf, sizeof(buf));
+ _send(buf, sizeof(buf));
}
}
@@ -412,7 +419,7 @@ void SceneMultiplayer::_del_peer(int p_id) {
continue;
}
multiplayer_peer->set_target_peer(P);
- multiplayer_peer->put_packet(buf, sizeof(buf));
+ _send(buf, sizeof(buf));
}
}
@@ -468,7 +475,7 @@ Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) {
multiplayer_peer->set_target_peer(p_to);
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
- return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2);
+ return _send(packet_cache.ptr(), p_data.size() + 2);
}
Error SceneMultiplayer::complete_auth(int p_peer) {
@@ -478,7 +485,7 @@ Error SceneMultiplayer::complete_auth(int p_peer) {
pending_peers[p_peer].local = true;
// Notify the remote peer that the authentication has completed.
uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH };
- Error err = multiplayer_peer->put_packet(buf, 2);
+ Error err = _send(buf, 2);
// The remote peer already reported the authentication as completed, so admit the peer.
// May generate new packets, so it must happen after sending confirmation.
if (pending_peers[p_peer].remote) {
@@ -654,6 +661,7 @@ SceneMultiplayer::SceneMultiplayer() {
replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this)));
rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this)));
cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
+ set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer)));
}
SceneMultiplayer::~SceneMultiplayer() {
diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h
index b0ecc48f8c..1a8de11f3f 100644
--- a/modules/multiplayer/scene_multiplayer.h
+++ b/modules/multiplayer/scene_multiplayer.h
@@ -37,6 +37,31 @@
#include "scene_replication_interface.h"
#include "scene_rpc_interface.h"
+class OfflineMultiplayerPeer : public MultiplayerPeer {
+ GDCLASS(OfflineMultiplayerPeer, MultiplayerPeer);
+
+public:
+ virtual int get_available_packet_count() const override { return 0; }
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override {
+ *r_buffer = nullptr;
+ r_buffer_size = 0;
+ return OK;
+ }
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override { return OK; }
+ virtual int get_max_packet_size() const override { return 0; }
+
+ virtual void set_target_peer(int p_peer_id) override {}
+ virtual int get_packet_peer() const override { return 0; }
+ virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; };
+ virtual int get_packet_channel() const override { return 0; }
+ virtual void disconnect_peer(int p_peer, bool p_force = false) override {}
+ virtual bool is_server() const override { return true; }
+ virtual void poll() override {}
+ virtual void close() override {}
+ virtual int get_unique_id() const override { return TARGET_PEER_SERVER; }
+ virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; };
+};
+
class SceneMultiplayer : public MultiplayerAPI {
GDCLASS(SceneMultiplayer, MultiplayerAPI);
@@ -103,6 +128,15 @@ private:
Ref<SceneReplicationInterface> replicator;
Ref<SceneRPCInterface> rpc;
+#ifdef DEBUG_ENABLED
+ _FORCE_INLINE_ void _profile_bandwidth(const String &p_what, int p_value);
+ _FORCE_INLINE_ Error _send(const uint8_t *p_packet, int p_packet_len); // Also profiles.
+#else
+ _FORCE_INLINE_ Error _send(const uint8_t *p_packet, int p_packet_len) {
+ return multiplayer_peer->put_packet(p_packet, p_packet_len);
+ }
+#endif
+
protected:
static void _bind_methods();
@@ -162,10 +196,7 @@ public:
bool is_server_relay_enabled() const;
Ref<SceneCacheInterface> get_path_cache() { return cache; }
-
-#ifdef DEBUG_ENABLED
- void profile_bandwidth(const String &p_inout, int p_size);
-#endif
+ Ref<SceneReplicationInterface> get_replicator() { return replicator; }
SceneMultiplayer();
~SceneMultiplayer();
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index f1bab7327a..7d9437936a 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -32,6 +32,7 @@
#include "scene_multiplayer.h"
+#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "scene/main/node.h"
#include "scene/scene_string_names.h"
@@ -40,6 +41,18 @@
if (packet_cache.size() < m_amount) \
packet_cache.resize(m_amount);
+#ifdef DEBUG_ENABLED
+_FORCE_INLINE_ void SceneReplicationInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) {
+ if (EngineDebugger::is_profiling("multiplayer:replication")) {
+ Array values;
+ values.push_back(p_what);
+ values.push_back(p_id);
+ values.push_back(p_size);
+ EngineDebugger::profiler_add_frame_data("multiplayer:replication", values);
+ }
+}
+#endif
+
SceneReplicationInterface::TrackedNode &SceneReplicationInterface::_track(const ObjectID &p_id) {
if (!tracked_nodes.has(p_id)) {
tracked_nodes[p_id] = TrackedNode(p_id);
@@ -244,15 +257,54 @@ void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid)
Node *node = sync->get_root_node();
ERR_FAIL_COND(!node); // Bug.
const ObjectID oid = node->get_instance_id();
- if (spawned_nodes.has(oid)) {
+ if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) {
_update_spawn_visibility(p_peer, oid);
}
_update_sync_visibility(p_peer, sync);
}
+bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer) const {
+ if (!tracked_nodes.has(p_oid)) {
+ return true; // Untracked nodes are always visible to RPCs.
+ }
+ ERR_FAIL_COND_V(p_peer < 0, false);
+ const TrackedNode &tnode = tracked_nodes[p_oid];
+ if (tnode.synchronizers.is_empty()) {
+ return true; // No synchronizers means no visibility restrictions.
+ }
+ if (tnode.remote_peer && uint32_t(p_peer) == tnode.remote_peer) {
+ return true; // RPCs on spawned nodes are always visible to spawner.
+ } else if (spawned_nodes.has(p_oid)) {
+ // It's a spwaned node we control, this can be fast
+ if (p_peer) {
+ return peers_info.has(p_peer) && peers_info[p_peer].spawn_nodes.has(p_oid);
+ } else {
+ for (const KeyValue<int, PeerInfo> &E : peers_info) {
+ if (!E.value.spawn_nodes.has(p_oid)) {
+ return false; // Not public.
+ }
+ }
+ return true; // All peers have this node.
+ }
+ } else {
+ // Cycle object synchronizers to check visibility.
+ for (const ObjectID &sid : tnode.synchronizers) {
+ MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid);
+ ERR_CONTINUE(!sync);
+ // RPC visibility is composed using OR when multiple synchronizers are present.
+ // Note that we don't really care about authority here which may lead to unexpected
+ // results when using multiple synchronizers to control the same node.
+ if (sync->is_visible_to(p_peer)) {
+ return true;
+ }
+ }
+ return false; // Not visible.
+ }
+}
+
Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) {
ERR_FAIL_COND_V(!p_sync, ERR_BUG);
- if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority()) {
+ if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) {
return OK;
}
@@ -362,13 +414,8 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) {
ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
-#ifdef DEBUG_ENABLED
- multiplayer->profile_bandwidth("out", p_size);
-#endif
-
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
peer->set_transfer_channel(0);
peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
@@ -640,6 +687,9 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p
MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size);
ofs += size;
}
+#ifdef DEBUG_ENABLED
+ _profile_node_data("sync_out", oid, size);
+#endif
}
if (ofs > 3) {
// Got some left over to send.
@@ -687,6 +737,9 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu
err = MultiplayerSynchronizer::set_state(props, node, vars);
ERR_FAIL_COND_V(err, err);
ofs += size;
+#ifdef DEBUG_ENABLED
+ _profile_node_data("sync_in", sync->get_instance_id(), size);
+#endif
}
return OK;
}
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index c8bd96eb87..30d58f7129 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -105,6 +105,10 @@ private:
return p_id.is_valid() ? Object::cast_to<T>(ObjectDB::get_instance(p_id)) : nullptr;
}
+#ifdef DEBUG_ENABLED
+ _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id, int p_size);
+#endif
+
public:
static void make_default();
@@ -121,6 +125,8 @@ public:
Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len);
+ bool is_rpc_visible(const ObjectID &p_oid, int p_peer) const;
+
SceneReplicationInterface(SceneMultiplayer *p_multiplayer) {
multiplayer = p_multiplayer;
}
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index acc113c901..dbf2b3751e 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -52,16 +52,15 @@
#define BYTE_ONLY_OR_NO_ARGS_FLAG (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT)
#ifdef DEBUG_ENABLED
-_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {
- if (EngineDebugger::is_profiling("rpc")) {
+_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) {
+ if (EngineDebugger::is_profiling("multiplayer:rpc")) {
Array values;
- values.push_back(p_id);
values.push_back(p_what);
- EngineDebugger::profiler_add_frame_data("rpc", values);
+ values.push_back(p_id);
+ values.push_back(p_size);
+ EngineDebugger::profiler_add_frame_data("multiplayer:rpc", values);
}
}
-#else
-_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {}
#endif
// Returns the packet size stripping the node path added when the node is not yet cached.
@@ -277,7 +276,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
argp.resize(argc);
#ifdef DEBUG_ENABLED
- _profile_node_data("rpc_in", p_node->get_instance_id());
+ _profile_node_data("rpc_in", p_node->get_instance_id(), p_packet_len);
#endif
int out;
@@ -296,7 +295,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
}
}
-void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) {
+void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) {
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer.");
@@ -312,12 +311,35 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + ".");
}
- // See if all peers have cached path (if so, call can be fast).
- int psc_id;
- const bool has_all_peers = multiplayer->get_path_cache()->send_object_cache(p_from, p_to, psc_id);
+ // See if all peers have cached path (if so, call can be fast) while building the RPC target list.
+ HashSet<int> targets;
+ Ref<SceneCacheInterface> cache = multiplayer->get_path_cache();
+ int psc_id = -1;
+ bool has_all_peers = true;
+ const ObjectID oid = p_node->get_instance_id();
+ if (p_to > 0) {
+ ERR_FAIL_COND_MSG(!multiplayer->get_replicator()->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to));
+ targets.insert(p_to);
+ has_all_peers = cache->send_object_cache(p_node, p_to, psc_id);
+ } else {
+ bool restricted = !multiplayer->get_replicator()->is_rpc_visible(oid, 0);
+ for (const int &P : multiplayer->get_connected_peers()) {
+ if (p_to < 0 && P == -p_to) {
+ continue; // Excluded peer.
+ }
+ if (restricted && !multiplayer->get_replicator()->is_rpc_visible(oid, P)) {
+ continue; // Not visible to this peer.
+ }
+ targets.insert(P);
+ bool has_peer = cache->send_object_cache(p_node, P, psc_id);
+ has_all_peers = has_all_peers && has_peer;
+ }
+ }
+ if (targets.is_empty()) {
+ return; // No one in sight.
+ }
// Create base packet, lots of hardcode because it must be tight.
-
int ofs = 0;
#define MAKE_ROOM(m_amount) \
@@ -399,20 +421,21 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
ERR_FAIL_COND(node_id_compression > 3);
ERR_FAIL_COND(name_id_compression > 1);
- // We can now set the meta
- packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0);
-
#ifdef DEBUG_ENABLED
- multiplayer->profile_bandwidth("out", ofs);
+ _profile_node_data("rpc_out", p_node->get_instance_id(), ofs);
#endif
+ // We can now set the meta
+ packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0);
+
// Take chance and set transfer mode, since all send methods will use it.
peer->set_transfer_channel(p_config.channel);
peer->set_transfer_mode(p_config.transfer_mode);
if (has_all_peers) {
- // They all have verified paths, so send fast.
- multiplayer->send_command(p_to, packet_cache.ptr(), ofs);
+ for (const int P : targets) {
+ multiplayer->send_command(P, packet_cache.ptr(), ofs);
+ }
} else {
// Unreachable because the node ID is never compressed if the peers doesn't know it.
CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32);
@@ -420,23 +443,15 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con
// Not all verified path, so send one by one.
// Append path at the end, since we will need it for some packets.
- NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path());
+ NodePath from_path = multiplayer->get_root_path().rel_path_to(p_node->get_path());
CharString pname = String(from_path).utf8();
int path_len = encode_cstring(pname.get_data(), nullptr);
MAKE_ROOM(ofs + path_len);
encode_cstring(pname.get_data(), &(packet_cache.write[ofs]));
- for (const int &P : multiplayer->get_connected_peers()) {
- if (p_to < 0 && P == -p_to) {
- continue; // Continue, excluded.
- }
-
- if (p_to > 0 && P != p_to) {
- continue; // Continue, not for this peer.
- }
-
+ // Not all verified path, so check which needs the longer packet.
+ for (const int P : targets) {
bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P);
-
if (confirmed) {
// This one confirmed path, so use id.
encode_uint32(psc_id, &(packet_cache.write[1]));
@@ -477,10 +492,6 @@ Error SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_
}
if (p_peer_id != caller_id) {
-#ifdef DEBUG_ENABLED
- _profile_node_data("rpc_out", node->get_instance_id());
-#endif
-
_send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount);
}
diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h
index aa9be525a2..800293714c 100644
--- a/modules/multiplayer/scene_rpc_interface.h
+++ b/modules/multiplayer/scene_rpc_interface.h
@@ -81,8 +81,11 @@ private:
HashMap<ObjectID, RPCConfigCache> rpc_cache;
+#ifdef DEBUG_ENABLED
+ _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id, int p_size);
+#endif
+
protected:
- _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id);
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp
index 0fb4f0773f..7e02f0374d 100644
--- a/modules/openxr/action_map/openxr_action.cpp
+++ b/modules/openxr/action_map/openxr_action.cpp
@@ -42,7 +42,7 @@ void OpenXRAction::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_toplevel_paths", "toplevel_paths"), &OpenXRAction::set_toplevel_paths);
ClassDB::bind_method(D_METHOD("get_toplevel_paths"), &OpenXRAction::get_toplevel_paths);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "toplevel_paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_toplevel_paths", "get_toplevel_paths");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "toplevel_paths"), "set_toplevel_paths", "get_toplevel_paths");
BIND_ENUM_CONSTANT(OPENXR_ACTION_BOOL);
BIND_ENUM_CONSTANT(OPENXR_ACTION_FLOAT);
diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp
index 99d7a17acf..abb714c3bb 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile.cpp
@@ -38,7 +38,7 @@ void OpenXRIPBinding::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths);
ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths);
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"), "set_paths", "get_paths");
ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path);
ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path);
diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp
index 8f6d5c28db..ea539f2053 100644
--- a/modules/openxr/extensions/openxr_android_extension.cpp
+++ b/modules/openxr/extensions/openxr_android_extension.cpp
@@ -47,7 +47,8 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() {
OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) :
OpenXRExtensionWrapper(p_openxr_api) {
singleton = this;
- request_extensions[XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME] = nullptr; // must be available
+ request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = nullptr; // must be available
+ request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available;
}
void OpenXRAndroidExtension::on_before_instance_created() {
@@ -68,6 +69,29 @@ void OpenXRAndroidExtension::on_before_instance_created() {
ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR");
}
+// We're keeping the Android create info struct here to avoid including openxr_platform.h in a header, which would break other extensions.
+// This is reasonably safe as the struct is only used during intialization and the extension is a singleton.
+static XrInstanceCreateInfoAndroidKHR instance_create_info;
+
+void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void *p_next_pointer) {
+ if (!create_instance_extension_available) {
+ return nullptr;
+ }
+
+ JNIEnv *env = get_jni_env();
+ JavaVM *vm;
+ env->GetJavaVM(&vm);
+ jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
+
+ instance_create_info = {
+ .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
+ .next = p_next_pointer,
+ .applicationVM = vm,
+ .applicationActivity = activity_object
+ };
+ return &instance_create_info;
+}
+
OpenXRAndroidExtension::~OpenXRAndroidExtension() {
singleton = nullptr;
}
diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h
index eda7022064..ca6011559a 100644
--- a/modules/openxr/extensions/openxr_android_extension.h
+++ b/modules/openxr/extensions/openxr_android_extension.h
@@ -41,12 +41,15 @@ public:
OpenXRAndroidExtension(OpenXRAPI *p_openxr_api);
virtual void on_before_instance_created() override;
+ virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) override;
virtual ~OpenXRAndroidExtension() override;
private:
static OpenXRAndroidExtension *singleton;
+ bool create_instance_extension_available = false;
+
// Initialize the loader
EXT_PROTO_XRRESULT_FUNC1(xrInitializeLoaderKHR, (const XrLoaderInitInfoBaseHeaderKHR *), loaderInitInfo)
};
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index c417c90d11..77b52ab355 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -65,6 +65,7 @@ public:
virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; }
virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; }
virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; }
+ virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; }
virtual void on_before_instance_created() {}
virtual void on_instance_created(const XrInstance p_instance) {}
diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
index 4d996e6283..29208efb20 100644
--- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
+++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp
@@ -91,8 +91,6 @@ bool OpenXRHTCViveTrackerExtension::is_path_supported(const String &p_path) {
return available;
} else if (p_path == "/user/vive_tracker_htcx/role/chest") {
return available;
- } else if (p_path == "/user/vive_tracker_htcx/role/chest") {
- return available;
} else if (p_path == "/user/vive_tracker_htcx/role/camera") {
return available;
} else if (p_path == "/user/vive_tracker_htcx/role/keyboard") {
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 88111afede..b7c95415d0 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -299,9 +299,17 @@ bool OpenXRAPI::create_instance() {
XR_CURRENT_API_VERSION // apiVersion
};
+ void *next_pointer = nullptr;
+ for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
+ void *np = wrapper->set_instance_create_info_and_get_next_pointer(next_pointer);
+ if (np != nullptr) {
+ next_pointer = np;
+ }
+ }
+
XrInstanceCreateInfo instance_create_info = {
XR_TYPE_INSTANCE_CREATE_INFO, // type
- nullptr, // next
+ next_pointer, // next
0, // createFlags
application_info, // applicationInfo
0, // enabledApiLayerCount, need to find out if we need support for this?
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index b8c412a201..2dba4916a0 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -67,8 +67,8 @@ void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &p_colo
}
}
-void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map) {
- ERR_FAIL_COND(Math::is_zero_approx(p_scale));
+Error ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map) {
+ ERR_FAIL_COND_V_MSG(Math::is_zero_approx(p_scale), ERR_INVALID_PARAMETER, "ImageLoaderSVG: Can't load SVG with a scale of 0.");
if (p_color_map.size()) {
_replace_color_property(p_color_map, "stop-color=\"", p_string);
@@ -81,13 +81,23 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin
tvg::Result result = picture->load((const char *)bytes.ptr(), bytes.size(), "svg", true);
if (result != tvg::Result::Success) {
- return;
+ return ERR_INVALID_DATA;
}
float fw, fh;
picture->size(&fw, &fh);
- uint32_t width = MIN(round(fw * p_scale), 16 * 1024);
- uint32_t height = MIN(round(fh * p_scale), 16 * 1024);
+ uint32_t width = round(fw * p_scale);
+ uint32_t height = round(fh * p_scale);
+
+ const uint32_t max_dimension = 16384;
+ if (width > max_dimension || height > max_dimension) {
+ WARN_PRINT(vformat(
+ String::utf8("ImageLoaderSVG: Target canvas dimensions %d×%d (with scale %.2f) exceed the max supported dimensions %d×%d. The target canvas will be scaled down."),
+ width, height, p_scale, max_dimension, max_dimension));
+ width = MIN(width, max_dimension);
+ height = MIN(height, max_dimension);
+ }
+
picture->size(width, height);
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
@@ -97,25 +107,25 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin
tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT);
if (res != tvg::Result::Success) {
memfree(buffer);
- ERR_FAIL_MSG("ImageLoaderSVG can't create image.");
+ ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
}
res = sw_canvas->push(std::move(picture));
if (res != tvg::Result::Success) {
memfree(buffer);
- ERR_FAIL_MSG("ImageLoaderSVG can't create image.");
+ ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't insert ThorVG picture on canvas.");
}
res = sw_canvas->draw();
if (res != tvg::Result::Success) {
memfree(buffer);
- ERR_FAIL_MSG("ImageLoaderSVG can't create image.");
+ ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas.");
}
res = sw_canvas->sync();
if (res != tvg::Result::Success) {
memfree(buffer);
- ERR_FAIL_MSG("ImageLoaderSVG can't create image.");
+ ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't sync ThorVG canvas.");
}
Vector<uint8_t> image;
@@ -136,6 +146,7 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin
memfree(buffer);
p_image->set_data(width, height, false, Image::FORMAT_RGBA8, image);
+ return OK;
}
void ImageLoaderSVG::get_recognized_extensions(List<String> *p_extensions) const {
@@ -145,13 +156,19 @@ void ImageLoaderSVG::get_recognized_extensions(List<String> *p_extensions) const
Error ImageLoaderSVG::load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
String svg = p_fileaccess->get_as_utf8_string();
+ Error err;
if (p_flags & FLAG_CONVERT_COLORS) {
- create_image_from_string(p_image, svg, p_scale, false, forced_color_map);
+ err = create_image_from_string(p_image, svg, p_scale, false, forced_color_map);
} else {
- create_image_from_string(p_image, svg, p_scale, false, HashMap<Color, Color>());
+ err = create_image_from_string(p_image, svg, p_scale, false, HashMap<Color, Color>());
+ }
+
+ if (err != OK) {
+ return err;
+ } else if (p_image->is_empty()) {
+ return ERR_INVALID_DATA;
}
- ERR_FAIL_COND_V(p_image->is_empty(), FAILED);
if (p_flags & FLAG_FORCE_LINEAR) {
p_image->srgb_to_linear();
}
diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h
index b0b0963c15..84511f1708 100644
--- a/modules/svg/image_loader_svg.h
+++ b/modules/svg/image_loader_svg.h
@@ -41,7 +41,7 @@ class ImageLoaderSVG : public ImageFormatLoader {
public:
static void set_forced_color_map(const HashMap<Color, Color> &p_color_map);
- void create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map);
+ Error create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map);
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 166325c551..cf2d8c9986 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4750,7 +4750,10 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
i += (sd_glyphs[i].count - 1);
}
}
- ERR_FAIL_COND_V_MSG(sd_shift != sd->break_inserts, false, "Invalid break insert count!");
+ if (sd_shift < sd->break_inserts) {
+ // Note: should not happen with a normal text, but might be a case with special fonts that substitute a long string (with breaks opportunities in it) with a single glyph (like Font Awesome).
+ glyphs_new.resize(sd->glyphs.size() + sd_shift);
+ }
if (sd->break_inserts > 0) {
sd->glyphs = glyphs_new;
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index 792103cd31..63909257d9 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -438,9 +438,7 @@ void AudioStreamOggVorbis::maybe_update_info() {
}
if (i == 0) {
packet->b_o_s = 1;
- }
- if (i == 0) {
ERR_FAIL_COND(!vorbis_synthesis_idheader(packet));
}
diff --git a/modules/zip/doc_classes/ZIPReader.xml b/modules/zip/doc_classes/ZIPReader.xml
index 717116a531..055201b105 100644
--- a/modules/zip/doc_classes/ZIPReader.xml
+++ b/modules/zip/doc_classes/ZIPReader.xml
@@ -9,7 +9,7 @@
func read_zip_file():
var reader := ZIPReader.new()
var err := reader.open("user://archive.zip")
- if err == OK:
+ if err != OK:
return PackedByteArray()
var res := reader.read_file("hello.txt")
reader.close()