diff options
Diffstat (limited to 'modules')
35 files changed, 865 insertions, 234 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 8b0b7a5102..f1c022c43c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3122,29 +3122,13 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { Error err = OK; - Ref<PackedScene> scene = GDScriptCache::get_packed_scene(autoload.path, err); - if (err == OK && scene->get_state().is_valid()) { - Ref<SceneState> state = scene->get_state(); - if (state->get_node_count() > 0) { - const int ROOT_NODE = 0; - for (int i = 0; i < state->get_node_property_count(ROOT_NODE); i++) { - if (state->get_node_property_name(ROOT_NODE, i) != SNAME("script")) { - continue; - } - - Ref<GDScript> scr = state->get_node_property_value(ROOT_NODE, i); - if (scr.is_null()) { - continue; - } - - 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()); - } - } - break; + 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()); } } } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 40681d9771..f35318e4c6 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -122,7 +122,7 @@ GDScriptParserRef::~GDScriptParserRef() { GDScriptCache *GDScriptCache::singleton = nullptr; void GDScriptCache::move_script(const String &p_from, const String &p_to) { - if (singleton == nullptr) { + if (singleton == nullptr || p_from == p_to) { return; } @@ -236,16 +236,15 @@ 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); + + 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; @@ -343,7 +342,12 @@ Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_ return singleton->packed_scene_cache[p_path]; } - Ref<PackedScene> scene; + 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; @@ -361,6 +365,31 @@ Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_ 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; diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 2195932aa3..0f9d87aa67 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -102,6 +102,7 @@ public: 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() { 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_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6fd1362e68..d24cba4c59 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -147,6 +147,8 @@ 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); + + is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); } GDScriptParser::~GDScriptParser() { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0c107aa53..2548d26e14 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); 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..1967df5218 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,16 @@ <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 this GLTFDocumentExtension instance with GLTFDocument. If [param first_priority] is true, this extension will be ran first. Otherwise, it will be ran 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="write_to_filesystem"> @@ -55,11 +71,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/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..faa8ed267a 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")) { @@ -5270,6 +5271,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 +5598,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 +5689,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 +6615,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); @@ -6728,14 +6759,8 @@ 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)); } void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) { @@ -6752,22 +6777,20 @@ 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; -TypedArray<GLTFDocumentExtension> GLTFDocument::get_extensions() const { - return 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); + } + } } -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 +6875,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 +6887,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 +6901,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 +6929,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 +7052,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 +7074,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..15099efe33 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,8 @@ 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_all_gltf_document_extensions(); private: void _build_parent_hierachy(Ref<GLTFState> state); 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/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/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 c78b32ed49..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,15 +60,25 @@ 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); } @@ -72,11 +88,76 @@ void EditorNetworkProfiler::_update_frame() { } } +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,31 +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) { - nodes_data[p_frame.node].incoming_size = p_frame.incoming_size / p_frame.incoming_rpc; + rpc_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; + 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; } } @@ -174,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); @@ -193,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 65d08dcd56..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(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 f6d22b1637..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) { @@ -198,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 c8acd2eeb4..f5c092f0f9 100644 --- a/modules/multiplayer/multiplayer_debugger.h +++ b/modules/multiplayer/multiplayer_debugger.h @@ -35,6 +35,8 @@ #include "core/os/os.h" +class MultiplayerSynchronizer; + class MultiplayerDebugger { public: struct RPCNodeInfo { @@ -53,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: @@ -76,7 +101,6 @@ private: }; class RPCProfiler : public EngineProfiler { - public: private: HashMap<ObjectID, RPCNodeInfo> rpc_node_data; uint64_t last_profile_time = 0; @@ -89,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_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 93089cd50a..5042a0502d 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -41,12 +41,12 @@ #ifdef DEBUG_ENABLED _FORCE_INLINE_ void SceneMultiplayer::_profile_bandwidth(const String &p_what, int p_value) { - if (EngineDebugger::is_profiling("multiplayer")) { + if (EngineDebugger::is_profiling("multiplayer:bandwidth")) { Array values; values.push_back(p_what); values.push_back(OS::get_singleton()->get_ticks_msec()); values.push_back(p_value); - EngineDebugger::profiler_add_frame_data("multiplayer", values); + EngineDebugger::profiler_add_frame_data("multiplayer:bandwidth", values); } } #endif @@ -661,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 da8c762134..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); @@ -171,6 +196,7 @@ public: bool is_server_relay_enabled() const; Ref<SceneCacheInterface> get_path_cache() { return cache; } + 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 bc7eaba6d4..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; } @@ -635,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. @@ -682,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 bfb1623077..dbf2b3751e 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -53,12 +53,12 @@ #ifdef DEBUG_ENABLED _FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) { - if (EngineDebugger::is_profiling("rpc")) { + if (EngineDebugger::is_profiling("multiplayer:rpc")) { Array values; values.push_back(p_what); values.push_back(p_id); values.push_back(p_size); - EngineDebugger::profiler_add_frame_data("rpc", values); + EngineDebugger::profiler_add_frame_data("multiplayer:rpc", values); } } #endif @@ -295,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."); @@ -311,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,7 +422,7 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_COND(name_id_compression > 1); #ifdef DEBUG_ENABLED - _profile_node_data("rpc_out", p_from->get_instance_id(), ofs); + _profile_node_data("rpc_out", p_node->get_instance_id(), ofs); #endif // We can now set the meta @@ -410,8 +433,9 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con 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); @@ -419,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])); 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/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)); } |