diff options
Diffstat (limited to 'modules')
28 files changed, 680 insertions, 190 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..3a536b42c1 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()); @@ -3111,7 +3112,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); @@ -3244,9 +3245,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 +3308,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 +3701,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..40681d9771 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) { + 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); } @@ -185,12 +247,12 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e 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(); + 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 +282,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 +310,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 +335,73 @@ 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; + 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; +} + +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..2195932aa3 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,16 @@ 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 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_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..6fd1362e68 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -648,7 +648,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 +1541,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 +1556,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/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/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/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp index a7e5b80b66..c78b32ed49 100644 --- a/modules/multiplayer/editor/editor_network_profiler.cpp +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -67,8 +67,8 @@ void EditorNetworkProfiler::_update_frame() { } 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))); } } @@ -99,6 +99,12 @@ void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) { nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; } + if (p_frame.incoming_rpc) { + nodes_data[p_frame.node].incoming_size = p_frame.incoming_size / p_frame.incoming_rpc; + } + if (p_frame.outgoing_rpc) { + nodes_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); diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h index 98d12e3c0a..65d08dcd56 100644 --- a/modules/multiplayer/editor/editor_network_profiler.h +++ b/modules/multiplayer/editor/editor_network_profiler.h @@ -66,7 +66,7 @@ protected: static void _bind_methods(); public: - void add_node_frame_data(const RPCNodeInfo p_frame); + void add_node_frame_data(RPCNodeInfo p_frame); void set_bandwidth(int p_incoming, int p_outgoing); bool is_profiling(); diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp index 3d22af04dc..f6d22b1637 100644 --- a/modules/multiplayer/multiplayer_debugger.cpp +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -126,12 +126,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 +141,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 +164,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 +171,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; } } diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h index 4efd1da016..c8acd2eeb4 100644 --- a/modules/multiplayer/multiplayer_debugger.h +++ b/modules/multiplayer/multiplayer_debugger.h @@ -41,7 +41,9 @@ public: ObjectID node; String node_path; int incoming_rpc = 0; + int incoming_size = 0; int outgoing_rpc = 0; + int outgoing_size = 0; }; struct RPCFrame { 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..93089cd50a 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -40,12 +40,12 @@ #endif #ifdef DEBUG_ENABLED -void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) { +_FORCE_INLINE_ void SceneMultiplayer::_profile_bandwidth(const String &p_what, int p_value) { if (EngineDebugger::is_profiling("multiplayer")) { 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); + values.push_back(p_value); EngineDebugger::profiler_add_frame_data("multiplayer", values); } } @@ -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) { diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index b0ecc48f8c..da8c762134 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -103,6 +103,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(); @@ -163,10 +172,6 @@ public: Ref<SceneCacheInterface> get_path_cache() { return cache; } -#ifdef DEBUG_ENABLED - void profile_bandwidth(const String &p_inout, int p_size); -#endif - SceneMultiplayer(); ~SceneMultiplayer(); }; diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index f1bab7327a..bc7eaba6d4 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -362,13 +362,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); diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index acc113c901..bfb1623077 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) { +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) { if (EngineDebugger::is_profiling("rpc")) { Array values; - values.push_back(p_id); values.push_back(p_what); + values.push_back(p_id); + values.push_back(p_size); EngineDebugger::profiler_add_frame_data("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; @@ -399,13 +398,13 @@ 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_from->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); @@ -477,10 +476,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); |