diff options
Diffstat (limited to 'modules')
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="""" /> <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() |