diff options
Diffstat (limited to 'modules')
70 files changed, 1099 insertions, 588 deletions
diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index f2f51d9dd3..1ea9399c02 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -29,11 +29,12 @@ /*************************************************************************/ #include "editor_scene_importer_assimp.h" + #include "core/bind/core_bind.h" #include "core/io/image_loader.h" #include "editor/editor_file_system.h" +#include "editor/editor_settings.h" #include "editor/import/resource_importer_scene.h" -#include "editor_settings.h" #include "import_utils.h" #include "scene/3d/camera.h" #include "scene/3d/light.h" @@ -1350,4 +1351,4 @@ void EditorSceneImporterAssimp::_generate_node( _generate_node(state, recursive_state.skeleton, recursive_state.assimp_node->mChildren[i], recursive_state.new_node != NULL ? recursive_state.new_node : recursive_state.parent_node); } -}
\ No newline at end of file +} diff --git a/modules/bullet/collision_object_bullet.h b/modules/bullet/collision_object_bullet.h index c9430bec18..04231b0814 100644 --- a/modules/bullet/collision_object_bullet.h +++ b/modules/bullet/collision_object_bullet.h @@ -167,14 +167,18 @@ public: _FORCE_INLINE_ const VSet<RID> &get_exceptions() const { return exceptions; } _FORCE_INLINE_ void set_collision_layer(uint32_t p_layer) { - collisionLayer = p_layer; - on_collision_filters_change(); + if (collisionLayer != p_layer) { + collisionLayer = p_layer; + on_collision_filters_change(); + } } _FORCE_INLINE_ uint32_t get_collision_layer() const { return collisionLayer; } _FORCE_INLINE_ void set_collision_mask(uint32_t p_mask) { - collisionMask = p_mask; - on_collision_filters_change(); + if (collisionMask != p_mask) { + collisionMask = p_mask; + on_collision_filters_change(); + } } _FORCE_INLINE_ uint32_t get_collision_mask() const { return collisionMask; } diff --git a/modules/bullet/rigid_body_bullet.cpp b/modules/bullet/rigid_body_bullet.cpp index 8d21b25b20..f29c4cb9ca 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -411,6 +411,8 @@ void RigidBodyBullet::on_collision_filters_change() { if (space) { space->reload_collision_filters(this); } + + set_activation_state(true); } void RigidBodyBullet::on_collision_checker_start() { @@ -471,7 +473,7 @@ void RigidBodyBullet::assert_no_constraints() { void RigidBodyBullet::set_activation_state(bool p_active) { if (p_active) { - btBody->setActivationState(ACTIVE_TAG); + btBody->activate(); } else { btBody->setActivationState(WANTS_DEACTIVATION); } diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index d2b16b0fd1..e74c29769f 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -42,6 +42,7 @@ #include "servers/physics_server.h" #include "soft_body_bullet.h" +#include <BulletCollision/BroadphaseCollision/btBroadphaseProxy.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btGhostObject.h> #include <BulletCollision/NarrowPhaseCollision/btGjkEpaPenetrationDepthSolver.h> @@ -459,9 +460,13 @@ void SpaceBullet::remove_area(AreaBullet *p_area) { } void SpaceBullet::reload_collision_filters(AreaBullet *p_area) { - // This is necessary to change collision filter - dynamicsWorld->removeCollisionObject(p_area->get_bt_ghost()); - dynamicsWorld->addCollisionObject(p_area->get_bt_ghost(), p_area->get_collision_layer(), p_area->get_collision_mask()); + btGhostObject *ghost_object = p_area->get_bt_ghost(); + + btBroadphaseProxy *ghost_proxy = ghost_object->getBroadphaseHandle(); + ghost_proxy->m_collisionFilterGroup = p_area->get_collision_layer(); + ghost_proxy->m_collisionFilterMask = p_area->get_collision_mask(); + + dynamicsWorld->refreshBroadphaseProxy(ghost_object); } void SpaceBullet::add_rigid_body(RigidBodyBullet *p_body) { @@ -482,9 +487,13 @@ void SpaceBullet::remove_rigid_body(RigidBodyBullet *p_body) { } void SpaceBullet::reload_collision_filters(RigidBodyBullet *p_body) { - // This is necessary to change collision filter - remove_rigid_body(p_body); - add_rigid_body(p_body); + btRigidBody *rigid_body = p_body->get_bt_rigid_body(); + + btBroadphaseProxy *body_proxy = rigid_body->getBroadphaseProxy(); + body_proxy->m_collisionFilterGroup = p_body->get_collision_layer(); + body_proxy->m_collisionFilterMask = p_body->get_collision_mask(); + + dynamicsWorld->refreshBroadphaseProxy(rigid_body); } void SpaceBullet::add_soft_body(SoftBodyBullet *p_body) { diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index b47377cbc4..8f4a8de895 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -66,7 +66,7 @@ if env['builtin_freetype']: env.Prepend(CPPPATH=[thirdparty_dir + "/include"]) env_freetype.Append(CPPDEFINES=['FT2_BUILD_LIBRARY', 'FT_CONFIG_OPTION_USE_PNG']) - if (env['target'] != 'release'): + if (env['target'] == 'debug'): env_freetype.Append(CPPDEFINES=['ZLIB_DEBUG']) # Also requires libpng headers diff --git a/modules/gdnative/doc_classes/GDNativeLibrary.xml b/modules/gdnative/doc_classes/GDNativeLibrary.xml index 7e1cac243a..aa48ab44f2 100644 --- a/modules/gdnative/doc_classes/GDNativeLibrary.xml +++ b/modules/gdnative/doc_classes/GDNativeLibrary.xml @@ -1,35 +1,50 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GDNativeLibrary" inherits="Resource" category="Core" version="3.2"> <brief_description> + An external library containing functions or script classes to use in Godot. </brief_description> <description> + A GDNative library can implement [NativeScript]s, global functions to call with the [GDNative] class, or low-level engine extensions through interfaces such as [ARVRInterfaceGDNative]. The library must be compiled for each platform and architecture that the project will run on. </description> <tutorials> + <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-c-example.html</link> + <link>https://docs.godotengine.org/en/latest/tutorials/plugins/gdnative/gdnative-cpp-example.html</link> </tutorials> <methods> <method name="get_current_dependencies" qualifiers="const"> <return type="PoolStringArray"> </return> <description> + Returns paths to all dependency libraries for the current platform and architecture. </description> </method> <method name="get_current_library_path" qualifiers="const"> <return type="String"> </return> <description> + Returns the path to the dynamic library file for the current platform and architecture. </description> </method> </methods> <members> <member name="config_file" type="ConfigFile" setter="set_config_file" getter="get_config_file"> + This resource in INI-style [ConfigFile] format, as in [code].gdnlib[/code] files. </member> <member name="load_once" type="bool" setter="set_load_once" getter="should_load_once" default="true"> + If [code]true[/code], Godot loads only one copy of the library and each script that references the library will share static data like static or global variables. + If [code]false[/code], Godot loads a separate copy of the library into memory for each script that references it. </member> <member name="reloadable" type="bool" setter="set_reloadable" getter="is_reloadable" default="true"> + If [code]true[/code], the editor will temporarily unload the library whenever the user switches away from the editor window, allowing the user to recompile the library without restarting Godot. + [b]Note:[/b] If the library defines tool scripts that run inside the editor, [code]reloadable[/code] must be [code]false[/code]. Otherwise, the editor will attempt to unload the tool scripts while they're in use and crash. </member> <member name="singleton" type="bool" setter="set_singleton" getter="is_singleton" default="false"> + If [code]true[/code], Godot loads the library at startup rather than the first time a script uses the library, calling [code]{prefix}gdnative_singleton[/code] after initializing the library (where [code]{prefix}[/code] is the value of [member symbol_prefix]). The library remains loaded as long as Godot is running. + [b]Note:[/b] A singleton library cannot be [member reloadable]. </member> <member name="symbol_prefix" type="String" setter="set_symbol_prefix" getter="get_symbol_prefix" default=""godot_""> + The prefix this library's entry point functions begin with. For example, a GDNativeLibrary would declare its [code]gdnative_init[/code] function as [code]godot_gdnative_init[/code] by default. + On platforms that require statically linking libraries (currently only iOS), each library must have a different [code]symbol_prefix[/code]. </member> </members> <constants> diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 783ad4e147..ee9e71d4a0 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -339,7 +339,7 @@ bool GDNative::initialize() { if (err || !library_init) { OS::get_singleton()->close_dynamic_library(native_handle); native_handle = NULL; - ERR_PRINT("Failed to obtain godot_gdnative_init symbol"); + ERR_PRINTS("Failed to obtain " + library->get_symbol_prefix() + "gdnative_init symbol"); return false; } diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 6ff6262b56..0194199133 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -277,7 +277,7 @@ void register_gdnative_types() { proc_ptr); if (err != OK) { - ERR_PRINT((String("No godot_gdnative_singleton in \"" + singleton->get_library()->get_current_library_path()) + "\" found").utf8().get_data()); + ERR_PRINTS("No " + lib->get_symbol_prefix() + "gdnative_singleton in \"" + singleton->get_library()->get_current_library_path() + "\" found"); } else { singleton_gdnatives.push_back(singleton); ((void (*)())proc_ptr)(); diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.cpp b/modules/gdnative/videodecoder/video_stream_gdnative.cpp index f3c34fd5e0..14b7f9a2ef 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.cpp +++ b/modules/gdnative/videodecoder/video_stream_gdnative.cpp @@ -279,7 +279,7 @@ void VideoStreamPlaybackGDNative::set_paused(bool p_paused) { paused = p_paused; } -Ref<Texture> VideoStreamPlaybackGDNative::get_texture() { +Ref<Texture> VideoStreamPlaybackGDNative::get_texture() const { return texture; } diff --git a/modules/gdnative/videodecoder/video_stream_gdnative.h b/modules/gdnative/videodecoder/video_stream_gdnative.h index 9aed1fd2a0..5ff7acb616 100644 --- a/modules/gdnative/videodecoder/video_stream_gdnative.h +++ b/modules/gdnative/videodecoder/video_stream_gdnative.h @@ -168,7 +168,7 @@ public: //virtual int mix(int16_t* p_buffer,int p_frames)=0; - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 9b3bf8ad5b..1d82735328 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1944,11 +1944,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } - } else { - for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); - r_result.insert(option.display, option); - } + } + for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index bdeea9cef3..83d02e4977 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1419,7 +1419,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!container->iter_init(*counter, valid)) { #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "'."; + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'."; OPCODE_BREAK; } #endif @@ -1432,7 +1432,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "'."; + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'."; OPCODE_BREAK; } #endif @@ -1452,7 +1452,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!container->iter_next(*counter, valid)) { #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; + err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?)."; OPCODE_BREAK; } #endif @@ -1465,7 +1465,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; + err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?)."; OPCODE_BREAK; } #endif diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 21434cd150..9d229adb2a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -89,8 +89,8 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) { // be more python-like - int current = tab_level.back()->get(); - tab_level.push_back(current); + IndentLevel current_level = indent_level.back()->get(); + indent_level.push_back(current_level); return true; //_set_error("newline expected after ':'."); //return false; @@ -105,12 +105,19 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { int indent = tokenizer->get_token_line_indent(); - int current = tab_level.back()->get(); - if (indent <= current) { + int tabs = tokenizer->get_token_line_tab_indent(); + IndentLevel current_level = indent_level.back()->get(); + IndentLevel new_indent(indent, tabs); + if (new_indent.is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); return false; } - tab_level.push_back(indent); + if (indent <= current_level.indent) { + return false; + } + + indent_level.push_back(new_indent); tokenizer->advance(); return true; @@ -858,11 +865,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s if (current_function) { int arg_idx = current_function->arguments.find(identifier); if (arg_idx != -1) { - if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { - // Assignment is not really usage - current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1; - } else { - current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; + switch (tokenizer->get_token()) { + case GDScriptTokenizer::TK_OP_ASSIGN_ADD: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR: + case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR: + case GDScriptTokenizer::TK_OP_ASSIGN_DIV: + case GDScriptTokenizer::TK_OP_ASSIGN_MOD: + case GDScriptTokenizer::TK_OP_ASSIGN_MUL: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT: + case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT: + case GDScriptTokenizer::TK_OP_ASSIGN_SUB: + case GDScriptTokenizer::TK_OP_ASSIGN: { + // Assignment is not really usage + } break; + default: { + current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1; + } } } } @@ -2213,7 +2232,7 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) { } void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); p_block->has_return = true; @@ -2228,13 +2247,11 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { break; // go back a level } - if (pending_newline != -1) { - pending_newline = -1; - } + pending_newline = -1; PatternBranchNode *branch = alloc_node<PatternBranchNode>(); branch->body = alloc_node<BlockNode>(); @@ -2685,7 +2702,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) { void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); #ifdef DEBUG_ENABLED @@ -2698,9 +2715,13 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { bool is_first_line = true; while (true) { - if (!is_first_line && tab_level.back()->prev() && tab_level.back()->prev()->get() == indent_level) { + if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) { + if (indent_level.back()->prev()->get().is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return; + } // pythonic single-line expression, don't parse future lines - tab_level.pop_back(); + indent_level.pop_back(); p_block->end_line = tokenizer->get_token_line(); return; } @@ -2710,7 +2731,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { p_block->end_line = tokenizer->get_token_line(); return; //go back a level } @@ -2914,14 +2935,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline()) ; - if (tab_level.back()->get() < indent_level) { //not at current indent level + if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level p_block->end_line = tokenizer->get_token_line(); return; } if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) { - if (tab_level.back()->get() > indent_level) { + if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; @@ -2969,7 +2990,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { } else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) { - if (tab_level.back()->get() > indent_level) { + if (indent_level.back()->get().indent > current_level.indent) { _set_error("Invalid indentation."); return; } @@ -3341,32 +3362,45 @@ bool GDScriptParser::_parse_newline() { if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) { + IndentLevel current_level = indent_level.back()->get(); int indent = tokenizer->get_token_line_indent(); - int current_indent = tab_level.back()->get(); + int tabs = tokenizer->get_token_line_tab_indent(); + IndentLevel new_level(indent, tabs); + + if (new_level.is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return false; + } - if (indent > current_indent) { + if (indent > current_level.indent) { _set_error("Unexpected indentation."); return false; } - if (indent < current_indent) { + if (indent < current_level.indent) { - while (indent < current_indent) { + while (indent < current_level.indent) { //exit block - if (tab_level.size() == 1) { + if (indent_level.size() == 1) { _set_error("Invalid indentation. Bug?"); return false; } - tab_level.pop_back(); + indent_level.pop_back(); - if (tab_level.back()->get() < indent) { + if (indent_level.back()->get().indent < indent) { _set_error("Unindent does not match any outer indentation level."); return false; } - current_indent = tab_level.back()->get(); + + if (indent_level.back()->get().is_mixed(current_level)) { + _set_error("Mixed tabs and spaces in indentation."); + return false; + } + + current_level = indent_level.back()->get(); } tokenizer->advance(); @@ -3464,7 +3498,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) { void GDScriptParser::_parse_class(ClassNode *p_class) { - int indent_level = tab_level.back()->get(); + IndentLevel current_level = indent_level.back()->get(); while (true) { @@ -3472,7 +3506,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (error_set) return; - if (indent_level > tab_level.back()->get()) { + if (current_level.indent > indent_level.back()->get().indent) { p_class->end_line = tokenizer->get_token_line(); return; //go back a level } @@ -6111,12 +6145,18 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data break; } + // Some classes are prefixed with `_` internally + if (!ClassDB::class_exists(expr_native)) { + expr_native = "_" + expr_native; + } + switch (p_container.kind) { case DataType::NATIVE: { if (p_container.is_meta_type) { return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static()); } else { - return ClassDB::is_parent_class(expr_native, p_container.native_type); + StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type); + return ClassDB::is_parent_class(expr_native, container_native); } } break; case DataType::SCRIPT: @@ -8546,8 +8586,8 @@ void GDScriptParser::clear() { validating = false; for_completion = false; error_set = false; - tab_level.clear(); - tab_level.push_back(0); + indent_level.clear(); + indent_level.push_back(IndentLevel(0, 0)); error_line = 0; error_column = 0; pending_newline = -1; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 04ce9cf4c6..93557d745d 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -552,7 +552,27 @@ private: int pending_newline; - List<int> tab_level; + struct IndentLevel { + int indent; + int tabs; + + bool is_mixed(IndentLevel other) { + return ( + (indent == other.indent && tabs != other.tabs) || + (indent > other.indent && tabs < other.tabs) || + (indent < other.indent && tabs > other.tabs)); + } + + IndentLevel() : + indent(0), + tabs(0) {} + + IndentLevel(int p_indent, int p_tabs) : + indent(p_indent), + tabs(p_tabs) {} + }; + + List<IndentLevel> indent_level; String base_path; String self_path; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 8b20b0ff48..23a86f8d2b 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -450,11 +450,11 @@ void GDScriptTokenizerText::_make_error(const String &p_error) { tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; } -void GDScriptTokenizerText::_make_newline(int p_spaces) { +void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) { TokenData &tk = tk_rb[tk_rb_pos]; tk.type = TK_NEWLINE; - tk.constant = p_spaces; + tk.constant = Vector2(p_indentation, p_tabs); tk.line = line; tk.col = column; tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE; @@ -511,33 +511,6 @@ void GDScriptTokenizerText::_advance() { case ' ': INCPOS(1); continue; - case '\n': { - line++; - INCPOS(1); - column = 1; - int i = 0; - while (true) { - if (GETCHAR(i) == ' ') { - if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES; - if (file_indent_type != INDENT_SPACES) { - _make_error("Spaces used for indentation in tab-indented file!"); - return; - } - } else if (GETCHAR(i) == '\t') { - if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS; - if (file_indent_type != INDENT_TABS) { - _make_error("Tabs used for indentation in space-indented file!"); - return; - } - } else { - break; // not indentation anymore - } - i++; - } - - _make_newline(i); - return; - } case '#': { // line comment skip #ifdef DEBUG_ENABLED String comment; @@ -565,33 +538,34 @@ void GDScriptTokenizerText::_advance() { ignore_warnings = true; } #endif // DEBUG_ENABLED + FALLTHROUGH; + } + case '\n': { + line++; INCPOS(1); + bool used_spaces = false; + int tabs = 0; column = 1; - line++; int i = 0; while (true) { if (GETCHAR(i) == ' ') { - if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_SPACES; - if (file_indent_type != INDENT_SPACES) { - _make_error("Spaces used for indentation in tab-indented file!"); - return; - } + i++; + used_spaces = true; } else if (GETCHAR(i) == '\t') { - if (file_indent_type == INDENT_NONE) file_indent_type = INDENT_TABS; - if (file_indent_type != INDENT_TABS) { - _make_error("Tabs used for indentation in space-indented file!"); + if (used_spaces) { + _make_error("Spaces used before tabs on a line"); return; } + i++; + tabs++; } else { break; // not indentation anymore } - i++; } - _make_newline(i); + _make_newline(i, tabs); return; - - } break; + } case '/': { switch (GETCHAR(1)) { @@ -849,12 +823,8 @@ void GDScriptTokenizerText::_advance() { _make_error("Unterminated String"); return; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - _make_error("Malformed hex constant in string"); - return; - } - CharType v; + CharType v = 0; if (c >= '0' && c <= '9') { v = c - '0'; } else if (c >= 'a' && c <= 'f') { @@ -864,8 +834,8 @@ void GDScriptTokenizerText::_advance() { v = c - 'A'; v += 10; } else { - ERR_PRINT("BUG"); - v = 0; + _make_error("Malformed hex constant in string"); + return; } res <<= 4; @@ -1112,7 +1082,6 @@ void GDScriptTokenizerText::set_code(const String &p_code) { ignore_warnings = false; #endif // DEBUG_ENABLED last_error = ""; - file_indent_type = INDENT_NONE; for (int i = 0; i < MAX_LOOKAHEAD + 1; i++) _advance(); } @@ -1187,7 +1156,17 @@ int GDScriptTokenizerText::get_token_line_indent(int p_offset) const { int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); - return tk_rb[ofs].constant; + return tk_rb[ofs].constant.operator Vector2().x; +} + +int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const { + + ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0); + ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0); + + int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE; + ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0); + return tk_rb[ofs].constant.operator Vector2().y; } String GDScriptTokenizerText::get_token_error(int p_offset) const { diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 89d586b912..58749012b7 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -168,6 +168,7 @@ public: virtual int get_token_line(int p_offset = 0) const = 0; virtual int get_token_column(int p_offset = 0) const = 0; virtual int get_token_line_indent(int p_offset = 0) const = 0; + virtual int get_token_line_tab_indent(int p_offset = 0) const = 0; virtual String get_token_error(int p_offset = 0) const = 0; virtual void advance(int p_amount = 1) = 0; #ifdef DEBUG_ENABLED @@ -205,7 +206,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer { }; void _make_token(Token p_type); - void _make_newline(int p_spaces = 0); + void _make_newline(int p_indentation = 0, int p_tabs = 0); void _make_identifier(const StringName &p_identifier); void _make_built_in_func(GDScriptFunctions::Function p_func); void _make_constant(const Variant &p_constant); @@ -222,11 +223,6 @@ class GDScriptTokenizerText : public GDScriptTokenizer { int tk_rb_pos; String last_error; bool error_flag; - enum { - INDENT_NONE, - INDENT_SPACES, - INDENT_TABS, - } file_indent_type; #ifdef DEBUG_ENABLED Vector<Pair<int, String> > warning_skips; @@ -245,6 +241,7 @@ public: virtual int get_token_line(int p_offset = 0) const; virtual int get_token_column(int p_offset = 0) const; virtual int get_token_line_indent(int p_offset = 0) const; + virtual int get_token_line_tab_indent(int p_offset = 0) const; virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); @@ -283,6 +280,7 @@ public: virtual int get_token_line(int p_offset = 0) const; virtual int get_token_column(int p_offset = 0) const; virtual int get_token_line_indent(int p_offset = 0) const; + virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; } virtual const Variant &get_token_constant(int p_offset = 0) const; virtual String get_token_error(int p_offset = 0) const; virtual void advance(int p_amount = 1); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index ae44137fef..6b5c26ec81 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -157,7 +157,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.selectionRange.start.line = r_symbol.range.start.line; r_symbol.detail = "class " + r_symbol.name; bool is_root_class = &r_symbol == &class_symbol; - r_symbol.documentation = parse_documentation_as_markdown(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); + r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class); for (int i = 0; i < p_class->variables.size(); ++i) { @@ -184,7 +184,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += " = " + JSON::print(m.default_value); } - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; @@ -204,7 +204,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.range.end.line = symbol.range.start.line; symbol.range.end.character = lines[line].length(); symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; symbol.detail = "signal " + signal.name + "("; @@ -233,7 +233,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.range.end.line = symbol.range.start.line; symbol.range.end.character = lines[line].length(); symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); symbol.uri = uri; symbol.script_path = path; @@ -298,10 +298,10 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN const int line = LINE_NUMBER_TO_INDEX(p_func->line); r_symbol.range.start.line = line; r_symbol.range.start.character = p_func->column; - r_symbol.range.end.line = MAX(p_func->body->end_line - 2, p_func->body->line); + r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line); r_symbol.range.end.character = lines[r_symbol.range.end.line].length(); r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation_as_markdown(line); + r_symbol.documentation = parse_documentation(line); r_symbol.uri = uri; r_symbol.script_path = path; @@ -359,65 +359,11 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) { symbol.detail += ": " + var->datatype.to_string(); } - symbol.documentation = parse_documentation_as_markdown(line); + symbol.documentation = parse_documentation(line); r_symbol.children.push_back(symbol); } } -String ExtendGDScriptParser::marked_documentation(const String &p_bbcode) { - - String markdown = p_bbcode.strip_edges(); - - Vector<String> lines = markdown.split("\n"); - bool in_code_block = false; - int code_block_indent = -1; - - markdown = ""; - for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; - int block_start = line.find("[codeblock]"); - if (block_start != -1) { - code_block_indent = block_start; - in_code_block = true; - line = "\n"; - } else if (in_code_block) { - line = "\t" + line.substr(code_block_indent, line.length()); - } - - if (in_code_block && line.find("[/codeblock]") != -1) { - line = "\n"; - in_code_block = false; - } - - if (!in_code_block) { - line = line.strip_edges(); - line = line.replace("[code]", "`"); - line = line.replace("[/code]", "`"); - line = line.replace("[i]", "*"); - line = line.replace("[/i]", "*"); - line = line.replace("[b]", "**"); - line = line.replace("[/b]", "**"); - line = line.replace("[u]", "__"); - line = line.replace("[/u]", "__"); - line = line.replace("[method ", "`"); - line = line.replace("[member ", "`"); - line = line.replace("[signal ", "`"); - line = line.replace("[enum ", "`"); - line = line.replace("[constant ", "`"); - line = line.replace("[", "`"); - line = line.replace("]", "`"); - } - - if (!in_code_block && i < lines.size() - 1) { - line += "\n\n"; - } else if (i < lines.size() - 1) { - line += "\n"; - } - markdown += line; - } - return markdown; -} - String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { ERR_FAIL_INDEX_V(p_line, lines.size(), String()); @@ -576,10 +522,6 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i return ret; } -String ExtendGDScriptParser::parse_documentation_as_markdown(int p_line, bool p_docs_down) { - return marked_documentation(parse_documentation(p_line, p_docs_down)); -} - const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const { if (p_line <= 0) { return &class_symbol; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 71db78c245..a6e0ca5534 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -75,11 +75,6 @@ class ExtendGDScriptParser : public GDScriptParser { Array member_completions; - String parse_documentation_as_markdown(int p_line, bool p_docs_down = false); - -public: - static String marked_documentation(const String &p_bbcode); - public: _FORCE_INLINE_ const String &get_path() const { return path; } _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index ce3de9bc3b..90646f73ba 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -134,6 +134,22 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { } void GDScriptLanguageProtocol::initialized(const Variant &p_params) { + + lsp::GodotCapabilities capabilities; + + DocData *doc = EditorHelp::get_doc_data(); + for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + + lsp::GodotNativeClassInfo gdclass; + gdclass.name = E->get().name; + gdclass.class_doc = &(E->get()); + if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) { + gdclass.class_info = ptr; + } + capabilities.native_classes.push_back(gdclass); + } + + notify_client("gdscript/capabilities", capabilities.to_json()); } void GDScriptLanguageProtocol::poll() { @@ -153,7 +169,13 @@ Error GDScriptLanguageProtocol::start(int p_port) { } void GDScriptLanguageProtocol::stop() { + const int *ptr = clients.next(NULL); + while (ptr) { + clients.get(*ptr)->close(); + ptr = clients.next(ptr); + } server->stop(); + clients.clear(); } void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) { diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 62c212a2bd..8d58b99e02 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -56,8 +56,9 @@ void GDScriptLanguageServer::_notification(int p_what) { void GDScriptLanguageServer::thread_main(void *p_userdata) { GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata); while (!self->thread_exit) { + // Poll 20 times per second self->protocol.poll(); - OS::get_singleton()->delay_usec(10); + OS::get_singleton()->delay_usec(50000); } } diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index f51f3671dd..b83db718b8 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -39,6 +39,7 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); @@ -75,6 +76,11 @@ lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_ return doc; } +void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol *symbol) { + ERR_FAIL_NULL(symbol); + GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true)); +} + void GDScriptTextDocument::initialize() { if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { @@ -102,6 +108,21 @@ void GDScriptTextDocument::initialize() { } } +Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) { + + Variant ret; + + lsp::NativeSymbolInspectParams params; + params.load(p_params); + + if (const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_native_symbol(params)) { + ret = symbol->to_json(true); + notify_client_show_symbol(symbol); + } + + return ret; +} + Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { Dictionary params = p_params["textDocument"]; String uri = params["uri"]; @@ -335,31 +356,35 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) { const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); - } else if (!symbol->native_class.empty() && GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { - String id; - switch (symbol->kind) { - case lsp::SymbolKind::Class: - id = "class_name:" + symbol->name; - break; - case lsp::SymbolKind::Constant: - id = "class_constant:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Property: - case lsp::SymbolKind::Variable: - id = "class_property:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Enum: - id = "class_enum:" + symbol->native_class + ":" + symbol->name; - break; - case lsp::SymbolKind::Method: - case lsp::SymbolKind::Function: - id = "class_method:" + symbol->native_class + ":" + symbol->name; - break; - default: - id = "class_global:" + symbol->native_class + ":" + symbol->name; - break; + } else if (!symbol->native_class.empty()) { + if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { + String id; + switch (symbol->kind) { + case lsp::SymbolKind::Class: + id = "class_name:" + symbol->name; + break; + case lsp::SymbolKind::Constant: + id = "class_constant:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Property: + case lsp::SymbolKind::Variable: + id = "class_property:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Enum: + id = "class_enum:" + symbol->native_class + ":" + symbol->name; + break; + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Function: + id = "class_method:" + symbol->native_class + ":" + symbol->name; + break; + default: + id = "class_global:" + symbol->native_class + ":" + symbol->name; + break; + } + call_deferred("show_native_symbol_in_editor", id); + } else { + notify_client_show_symbol(symbol); } - call_deferred("show_native_symbol_in_editor", id); } } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 0b8103f175..235e2c3f6e 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -52,8 +52,10 @@ protected: private: lsp::TextDocumentItem load_document_item(const Variant &p_param); + void notify_client_show_symbol(const lsp::DocumentSymbol *symbol); public: + Variant nativeSymbol(const Dictionary &p_params); Array documentSymbol(const Dictionary &p_params); Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index b42464aa8a..c289ff6c07 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -198,7 +198,7 @@ Error GDScriptWorkspace::initialize() { if (!class_data.inherits.empty()) { class_symbol.detail += " extends " + class_data.inherits; } - class_symbol.documentation = ExtendGDScriptParser::marked_documentation(class_data.brief_description) + "\n" + ExtendGDScriptParser::marked_documentation(class_data.description); + class_symbol.documentation = class_data.brief_description + "\n" + class_data.description; for (int i = 0; i < class_data.constants.size(); i++) { const DocData::ConstantDoc &const_data = class_data.constants[i]; @@ -211,7 +211,7 @@ Error GDScriptWorkspace::initialize() { symbol.detail += ": " + const_data.enumeration; } symbol.detail += " = " + const_data.value; - symbol.documentation = ExtendGDScriptParser::marked_documentation(const_data.description); + symbol.documentation = const_data.description; class_symbol.children.push_back(symbol); } @@ -232,7 +232,7 @@ Error GDScriptWorkspace::initialize() { } else { symbol.detail += ": " + data.type; } - symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + symbol.documentation = data.description; class_symbol.children.push_back(symbol); } @@ -269,8 +269,12 @@ Error GDScriptWorkspace::initialize() { params += params.empty() ? "..." : ", ..."; } - symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + data.return_type; - symbol.documentation = ExtendGDScriptParser::marked_documentation(data.description); + String return_type = data.return_type; + if (return_type.empty()) { + return_type = "void"; + } + symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type; + symbol.documentation = data.description; class_symbol.children.push_back(symbol); } @@ -475,6 +479,24 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP } } +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) { + + if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) { + const lsp::DocumentSymbol &symbol = E->get(); + if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) { + return &symbol; + } + + for (int i = 0; i < symbol.children.size(); ++i) { + if (symbol.children[i].name == p_params.symbol_name) { + return &(symbol.children[i]); + } + } + } + + return NULL; +} + void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) { if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) { const List<lsp::DocumentLink> &links = parser->get_document_links(); diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 23e89ea3f3..a416ae1075 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -80,7 +80,7 @@ public: const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); - + const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index e60e28cc15..a048af88bb 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -31,12 +31,17 @@ #ifndef GODOT_LSP_H #define GODOT_LSP_H -#include "core/variant.h" +#include "core/class_db.h" +#include "core/list.h" +#include "editor/doc/doc_data.h" namespace lsp { typedef String DocumentUri; +/** Format BBCode documentation from DocData to markdown */ +static String marked_documentation(const String &p_bbcode); + /** * Text documents are identified using a URI. On the protocol level, URIs are passed as strings. */ @@ -282,6 +287,9 @@ struct Command { } }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not + namespace TextDocumentSyncKind { /** * Documents should not be synced at all. @@ -488,7 +496,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["change"] = save.to_json(); + dict["save"] = save.to_json(); return dict; } }; @@ -592,6 +600,7 @@ struct TextDocumentContentChangeEvent { } }; +// Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** * Reports an error. @@ -692,6 +701,7 @@ struct Diagnostic { } }; +// Use namespace instead of enumeration to follow the LSP specifications /** * Describes the content type that a client supports in various * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. @@ -756,6 +766,9 @@ struct MarkupContent { } }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. /** * The kind of a completion entry. */ @@ -787,6 +800,7 @@ static const int Operator = 24; static const int TypeParameter = 25; }; // namespace CompletionItemKind +// Use namespace instead of enumeration to follow the LSP specifications /** * Defines whether the insert text in a completion item should be interpreted as * plain text or a snippet. @@ -979,36 +993,39 @@ struct CompletionList { Vector<CompletionItem> items; }; +// Use namespace instead of enumeration to follow the LSP specifications +// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc /** * A symbol kind. */ namespace SymbolKind { -static const int File = 1; -static const int Module = 2; -static const int Namespace = 3; -static const int Package = 4; -static const int Class = 5; -static const int Method = 6; -static const int Property = 7; -static const int Field = 8; -static const int Constructor = 9; -static const int Enum = 10; -static const int Interface = 11; -static const int Function = 12; -static const int Variable = 13; -static const int Constant = 14; -static const int String = 15; -static const int Number = 16; -static const int Boolean = 17; -static const int Array = 18; -static const int Object = 19; -static const int Key = 20; -static const int Null = 21; -static const int EnumMember = 22; -static const int Struct = 23; -static const int Event = 24; -static const int Operator = 25; -static const int TypeParameter = 26; +static const int File = 0; +static const int Module = 1; +static const int Namespace = 2; +static const int Package = 3; +static const int Class = 4; +static const int Method = 5; +static const int Property = 6; +static const int Field = 7; +static const int Constructor = 8; +static const int Enum = 9; +static const int Interface = 10; +static const int Function = 11; +static const int Variable = 12; +static const int Constant = 13; +static const int String = 14; +static const int Number = 15; +static const int Boolean = 16; +static const int Array = 17; +static const int Object = 18; +static const int Key = 19; +static const int Null = 20; +static const int EnumMember = 21; +static const int Struct = 22; +static const int Event = 23; +static const int Operator = 24; +static const int TypeParameter = 25; }; // namespace SymbolKind /** @@ -1134,7 +1151,7 @@ struct DocumentSymbol { */ Vector<DocumentSymbol> children; - _FORCE_INLINE_ Dictionary to_json() const { + Dictionary to_json(bool with_doc = false) const { Dictionary dict; dict["name"] = name; dict["detail"] = detail; @@ -1142,10 +1159,14 @@ struct DocumentSymbol { dict["deprecated"] = deprecated; dict["range"] = range.to_json(); dict["selectionRange"] = selectionRange.to_json(); + if (with_doc) { + dict["documentation"] = documentation; + dict["native_class"] = native_class; + } Array arr; arr.resize(children.size()); for (int i = 0; i < children.size(); i++) { - arr[i] = children[i].to_json(); + arr[i] = children[i].to_json(with_doc); } dict["children"] = arr; return dict; @@ -1177,7 +1198,7 @@ struct DocumentSymbol { markdown.value = "\t" + detail + "\n\n"; } if (documentation.length()) { - markdown.value += documentation + "\n\n"; + markdown.value += marked_documentation(documentation) + "\n\n"; } if (script_path.length()) { markdown.value += "Defined in [" + script_path + "](" + uri + ")"; @@ -1229,6 +1250,17 @@ struct DocumentSymbol { } }; +struct NativeSymbolInspectParams { + + String native_class; + String symbol_name; + + void load(const Dictionary &p_params) { + native_class = p_params["native_class"]; + symbol_name = p_params["symbol_name"]; + } +}; + /** * Enum of known range kinds */ @@ -1289,6 +1321,7 @@ struct FoldingRange { } }; +// Use namespace instead of enumeration to follow the LSP specifications /** * How a completion was triggered */ @@ -1536,6 +1569,94 @@ struct InitializeResult { } }; +struct GodotNativeClassInfo { + + String name; + const DocData::ClassDoc *class_doc = NULL; + const ClassDB::ClassInfo *class_info = NULL; + + Dictionary to_json() { + Dictionary dict; + dict["name"] = name; + dict["inherits"] = class_doc->inherits; + return dict; + } +}; + +/** Features not included in the standart lsp specifications */ +struct GodotCapabilities { + + /** + * Native class list + */ + List<GodotNativeClassInfo> native_classes; + + Dictionary to_json() { + Dictionary dict; + Array classes; + for (List<GodotNativeClassInfo>::Element *E = native_classes.front(); E; E = E->next()) { + classes.push_back(E->get().to_json()); + } + dict["native_classes"] = classes; + return dict; + } +}; + +/** Format BBCode documentation from DocData to markdown */ +static String marked_documentation(const String &p_bbcode) { + + String markdown = p_bbcode.strip_edges(); + + Vector<String> lines = markdown.split("\n"); + bool in_code_block = false; + int code_block_indent = -1; + + markdown = ""; + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + int block_start = line.find("[codeblock]"); + if (block_start != -1) { + code_block_indent = block_start; + in_code_block = true; + line = "\n"; + } else if (in_code_block) { + line = "\t" + line.substr(code_block_indent, line.length()); + } + + if (in_code_block && line.find("[/codeblock]") != -1) { + line = "\n"; + in_code_block = false; + } + + if (!in_code_block) { + line = line.strip_edges(); + line = line.replace("[code]", "`"); + line = line.replace("[/code]", "`"); + line = line.replace("[i]", "*"); + line = line.replace("[/i]", "*"); + line = line.replace("[b]", "**"); + line = line.replace("[/b]", "**"); + line = line.replace("[u]", "__"); + line = line.replace("[/u]", "__"); + line = line.replace("[method ", "`"); + line = line.replace("[member ", "`"); + line = line.replace("[signal ", "`"); + line = line.replace("[enum ", "`"); + line = line.replace("[constant ", "`"); + line = line.replace("[", "`"); + line = line.replace("]", "`"); + } + + if (!in_code_block && i < lines.size() - 1) { + line += "\n\n"; + } else if (i < lines.size() - 1) { + line += "\n"; + } + markdown += line; + } + return markdown; +} + } // namespace lsp #endif diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 1bd3d72066..b762868f2c 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -217,6 +217,14 @@ Deprecated, use [member mesh_library] instead. </member> </members> + <signals> + <signal name="cell_size_changed"> + <argument index="0" name="cell_size" type="Vector3"> + </argument> + <description> + </description> + </signal> + </signals> <constants> <constant name="INVALID_CELL_ITEM" value="-1"> Invalid cell item that can be used in [method set_cell_item] to clear cells (or represent an empty cell in [method get_cell_item]). diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index b36afd4386..47ac0de7f9 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -227,10 +227,10 @@ Ref<MeshLibrary> GridMap::get_mesh_library() const { } void GridMap::set_cell_size(const Vector3 &p_size) { - ERR_FAIL_COND(p_size.x < 0.001 || p_size.y < 0.001 || p_size.z < 0.001); cell_size = p_size; _recreate_octant_data(); + emit_signal("cell_size_changed", cell_size); } Vector3 GridMap::get_cell_size() const { @@ -902,6 +902,8 @@ void GridMap::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask"); BIND_CONSTANT(INVALID_CELL_ITEM); + + ADD_SIGNAL(MethodInfo("cell_size_changed", PropertyInfo(Variant::VECTOR3, "cell_size"))); } void GridMap::set_clip(bool p_enabled, bool p_clip_above, int p_floor, Vector3::Axis p_axis) { diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index c97524a54d..f79aea9531 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -40,11 +40,8 @@ void GridMapEditor::_node_removed(Node *p_node) { - if (p_node == node) { + if (p_node == node) node = NULL; - hide(); - mesh_library_palette->hide(); - } } void GridMapEditor::_configure() { @@ -279,6 +276,7 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform = Transform(); cursor_transform.origin = cursor_origin; cursor_transform.basis.set_orthogonal_index(cursor_rot); + cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; if (cursor_instance.is_valid()) { @@ -301,7 +299,7 @@ void GridMapEditor::_update_selection_transform() { } Transform xf; - xf.scale(Vector3(1, 1, 1) * (Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size()); + xf.scale((Vector3(1, 1, 1) + (selection.end - selection.begin)) * node->get_cell_size()); xf.origin = selection.begin * node->get_cell_size(); VisualServer::get_singleton()->instance_set_transform(selection_instance, node->get_global_transform() * xf); @@ -353,7 +351,14 @@ void GridMapEditor::_set_selection(bool p_active, const Vector3 &p_begin, const selection.click = p_begin; selection.current = p_end; - _update_selection_transform(); + if (is_visible_in_tree()) { + _update_selection_transform(); + } + + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CLEAR), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_CUT), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_DUPLICATE), !selection.active); + options->get_popup()->set_item_disabled(options->get_popup()->get_item_index(MENU_OPTION_SELECTION_FILL), !selection.active); } bool GridMapEditor::do_input_action(Camera *p_camera, const Point2 &p_point, bool p_click) { @@ -591,7 +596,7 @@ void GridMapEditor::_update_paste_indicator() { Basis item_rot; item_rot.set_orthogonal_index(item.orientation); - xf.basis = item_rot * xf.basis; + xf.basis = item_rot * xf.basis * node->get_cell_scale(); VisualServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf); } @@ -929,9 +934,10 @@ void GridMapEditor::update_palette() { } void GridMapEditor::edit(GridMap *p_gridmap) { + if (!p_gridmap && node) + node->disconnect("cell_size_changed", this, "_draw_grids"); node = p_gridmap; - VS *vs = VS::get_singleton(); input_action = INPUT_NONE; selection.active = false; @@ -957,75 +963,13 @@ void GridMapEditor::edit(GridMap *p_gridmap) { set_process(true); - Vector3 edited_floor = p_gridmap->has_meta("_editor_floor_") ? p_gridmap->get_meta("_editor_floor_") : Variant(); clip_mode = p_gridmap->has_meta("_editor_clip_") ? ClipMode(p_gridmap->get_meta("_editor_clip_").operator int()) : CLIP_DISABLED; - for (int i = 0; i < 3; i++) { - if (vs->mesh_get_surface_count(grid[i]) > 0) - vs->mesh_remove_surface(grid[i], 0); - edit_floor[i] = edited_floor[i]; - } - - { - - // Update grids. - indicator_mat.instance(); - indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); - indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); - indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); - indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); - - Vector<Vector3> grid_points[3]; - Vector<Color> grid_colors[3]; - - float cell_size[3] = { p_gridmap->get_cell_size().x, p_gridmap->get_cell_size().y, p_gridmap->get_cell_size().z }; - - for (int i = 0; i < 3; i++) { - - Vector3 axis; - axis[i] = 1; - Vector3 axis_n1; - axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3]; - Vector3 axis_n2; - axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3]; - - for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { - - for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { - - Vector3 p = axis_n1 * j + axis_n2 * k; - float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; - float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2); - - Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); - float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2); - - grid_points[i].push_back(p); - grid_points[i].push_back(pk); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transk)); - - grid_points[i].push_back(p); - grid_points[i].push_back(pj); - grid_colors[i].push_back(Color(1, 1, 1, trans)); - grid_colors[i].push_back(Color(1, 1, 1, transj)); - } - } - - Array d; - d.resize(VS::ARRAY_MAX); - d[VS::ARRAY_VERTEX] = grid_points[i]; - d[VS::ARRAY_COLOR] = grid_colors[i]; - VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d); - VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); - } - } - + _draw_grids(node->get_cell_size()); update_grid(); _update_clip(); + + node->connect("cell_size_changed", this, "_draw_grids"); } void GridMapEditor::_update_clip() { @@ -1055,6 +999,61 @@ void GridMapEditor::update_grid() { updating = false; } +void GridMapEditor::_draw_grids(const Vector3 &cell_size) { + Vector3 edited_floor = node->has_meta("_editor_floor_") ? node->get_meta("_editor_floor_") : Variant(); + + for (int i = 0; i < 3; i++) { + if (VS::get_singleton()->mesh_get_surface_count(grid[i]) > 0) + VS::get_singleton()->mesh_remove_surface(grid[i], 0); + edit_floor[i] = edited_floor[i]; + } + + Vector<Vector3> grid_points[3]; + Vector<Color> grid_colors[3]; + + for (int i = 0; i < 3; i++) { + + Vector3 axis; + axis[i] = 1; + Vector3 axis_n1; + axis_n1[(i + 1) % 3] = cell_size[(i + 1) % 3]; + Vector3 axis_n2; + axis_n2[(i + 2) % 3] = cell_size[(i + 2) % 3]; + + for (int j = -GRID_CURSOR_SIZE; j <= GRID_CURSOR_SIZE; j++) { + + for (int k = -GRID_CURSOR_SIZE; k <= GRID_CURSOR_SIZE; k++) { + + Vector3 p = axis_n1 * j + axis_n2 * k; + float trans = Math::pow(MAX(0, 1.0 - (Vector2(j, k).length() / GRID_CURSOR_SIZE)), 2); + + Vector3 pj = axis_n1 * (j + 1) + axis_n2 * k; + float transj = Math::pow(MAX(0, 1.0 - (Vector2(j + 1, k).length() / GRID_CURSOR_SIZE)), 2); + + Vector3 pk = axis_n1 * j + axis_n2 * (k + 1); + float transk = Math::pow(MAX(0, 1.0 - (Vector2(j, k + 1).length() / GRID_CURSOR_SIZE)), 2); + + grid_points[i].push_back(p); + grid_points[i].push_back(pk); + grid_colors[i].push_back(Color(1, 1, 1, trans)); + grid_colors[i].push_back(Color(1, 1, 1, transk)); + + grid_points[i].push_back(p); + grid_points[i].push_back(pj); + grid_colors[i].push_back(Color(1, 1, 1, trans)); + grid_colors[i].push_back(Color(1, 1, 1, transj)); + } + } + + Array d; + d.resize(VS::ARRAY_MAX); + d[VS::ARRAY_VERTEX] = grid_points[i]; + d[VS::ARRAY_COLOR] = grid_colors[i]; + VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d); + VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid()); + } +} + void GridMapEditor::_notification(int p_what) { switch (p_what) { @@ -1193,6 +1192,7 @@ void GridMapEditor::_bind_methods() { ClassDB::bind_method("_node_removed", &GridMapEditor::_node_removed); ClassDB::bind_method(D_METHOD("_set_display_mode", "mode"), &GridMapEditor::_set_display_mode); + ClassDB::bind_method("_draw_grids", &GridMapEditor::_draw_grids); } GridMapEditor::GridMapEditor(EditorNode *p_editor) { @@ -1465,9 +1465,16 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { } } - selection.active = false; + _set_selection(false); updating = false; accumulated_floor_delta = 0.0; + + indicator_mat.instance(); + indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true); + indicator_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true); + indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); } GridMapEditor::~GridMapEditor() { diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index 48a07e9c7f..42e62f2842 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -201,7 +201,8 @@ class GridMapEditor : public VBoxContainer { EditorNode *editor; - void update_grid(); + void update_grid(); // Change which and where the grid is displayed + void _draw_grids(const Vector3 &cell_size); void _configure(); void _menu_option(int); void update_palette(); diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index ca656b4b9b..204f4e8905 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -237,6 +237,7 @@ Ref<CryptoKey> CryptoMbedTLS::generate_rsa(int p_bytes) { Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoKey> p_key, String p_issuer_name, String p_not_before, String p_not_after) { Ref<CryptoKeyMbedTLS> key = static_cast<Ref<CryptoKeyMbedTLS> >(p_key); + ERR_FAIL_COND_V_MSG(key.is_null(), NULL, "Invalid private key argument."); mbedtls_x509write_cert crt; mbedtls_x509write_crt_init(&crt); diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index 417032da54..ab37d89955 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -160,9 +160,16 @@ namespace GodotTools if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build - // Make sure to update the API assemblies if they happen to be missing. Just in - // case the user decided to delete them at some point after they were loaded. - Internal.UpdateApiAssembliesFromPrebuilt(); + // Make sure the API assemblies are up to date before building the project. + // We may not have had the chance to update the release API assemblies, and the debug ones + // may have been deleted by the user at some point after they were loaded by the Godot editor. + string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug"); + + if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) + { + ShowBuildErrorDialog("Failed to update the Godot API assemblies"); + return false; + } var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 7da7cff933..12edd651df 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -34,7 +34,7 @@ namespace GodotTools private bool CreateProjectSolution() { - using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3)) { pr.Step("Generating C# project...".TTR()); @@ -73,9 +73,23 @@ namespace GodotTools return false; } - // Make sure to update the API assemblies if they happen to be missing. Just in - // case the user decided to delete them at some point after they were loaded. - Internal.UpdateApiAssembliesFromPrebuilt(); + pr.Step("Updating Godot API assemblies...".TTR()); + + string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug"); + + if (!string.IsNullOrEmpty(debugApiAssembliesError)) + { + ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError); + return false; + } + + string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release"); + + if (!string.IsNullOrEmpty(releaseApiAssembliesError)) + { + ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError); + return false; + } pr.Step("Done".TTR()); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 9e24138143..01aa0d0ab1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -40,8 +40,7 @@ namespace GodotTools.Ides protected ILogger Logger { - get => logger ?? (logger = new ConsoleLogger()); - set => logger = value; + get => logger ?? (logger = new GodotLogger()); } private void StartServer() diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 7783576910..836c9c11e4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -10,8 +10,8 @@ namespace GodotTools.Internals public const string CSharpLanguageType = "CSharpScript"; public const string CSharpLanguageExtension = "cs"; - public static string UpdateApiAssembliesFromPrebuilt() => - internal_UpdateApiAssembliesFromPrebuilt(); + public static string UpdateApiAssembliesFromPrebuilt(string config) => + internal_UpdateApiAssembliesFromPrebuilt(config); public static string FullTemplatesDir => internal_FullTemplatesDir(); @@ -55,7 +55,7 @@ namespace GodotTools.Internals // Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_UpdateApiAssembliesFromPrebuilt(); + private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config); [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_FullTemplatesDir(); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index 0e6c58c9d7..748447005f 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -75,7 +75,7 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr p_editor_proj_dir, p_editor_compile_items, GDMono::get_singleton()->get_tools_project_editor_assembly()); } else { - MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain"); + MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ApiSolutionGeneration"); CRASH_COND(temp_domain == NULL); _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 5a84d9e3b8..1564d73c2a 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -230,31 +230,9 @@ uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies); } -float godot_icall_Globals_EditorScale() { - return EDSCALE; -} - -MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); - Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); -} - -MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); - Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); -} - -MonoString *godot_icall_Globals_TTR(MonoString *p_text) { - String text = GDMonoMarshal::mono_string_to_godot(p_text); - return GDMonoMarshal::mono_string_from_godot(TTR(text)); -} - -MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() { - String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(); +MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { + String config = GDMonoMarshal::mono_string_to_godot(p_config); + String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config); return GDMonoMarshal::mono_string_from_godot(error_str); } @@ -365,6 +343,29 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { } } +float godot_icall_Globals_EditorScale() { + return EDSCALE; +} + +MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoString *godot_icall_Globals_TTR(MonoString *p_text) { + String text = GDMonoMarshal::mono_string_to_godot(p_text); + return GDMonoMarshal::mono_string_from_godot(TTR(text)); +} + MonoString *godot_icall_Utils_OS_GetPlatformName() { String os_name = OS::get_singleton()->get_name(); return GDMonoMarshal::mono_string_from_godot(os_name); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 80a7335b1d..e83152d668 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -32,9 +32,13 @@ #include <mono/metadata/image.h> +#include "core/os/os.h" + #include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_assembly.h" +namespace GodotSharpExport { + String get_assemblyref_name(MonoImage *p_image, int index) { const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); @@ -45,7 +49,7 @@ String get_assemblyref_name(MonoImage *p_image, int index) { return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); } -Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) { +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { @@ -96,8 +100,8 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co return OK; } -Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); +Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) { + MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport"); ERR_FAIL_NULL_V(export_domain, FAILED); _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); @@ -110,7 +114,9 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'."); Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); + GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies); } + +} // namespace GodotSharpExport diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 8d121a6bc3..58e46e2f2d 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -39,10 +39,11 @@ namespace GodotSharpExport { +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); + Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies); -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); } // namespace GodotSharpExport diff --git a/modules/mono/glue/Managed/Files/Plane.cs b/modules/mono/glue/Managed/Files/Plane.cs index a13161d2e6..26e717e089 100644 --- a/modules/mono/glue/Managed/Files/Plane.cs +++ b/modules/mono/glue/Managed/Files/Plane.cs @@ -82,12 +82,12 @@ namespace Godot return Mathf.Abs(dist) <= epsilon; } - public Vector3 Intersect3(Plane b, Plane c) + public Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); - if (Mathf.Abs(denom) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(denom)) + return null; Vector3 result = b._normal.Cross(c._normal) * D + c._normal.Cross(_normal) * b.D + @@ -96,34 +96,35 @@ namespace Godot return result / denom; } - public Vector3 IntersectRay(Vector3 from, Vector3 dir) + public Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); - if (Mathf.Abs(den) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(den)) + return null; real_t dist = (_normal.Dot(from) - D) / den; // This is a ray, before the emitting pos (from) does not exist if (dist > Mathf.Epsilon) - return new Vector3(); + return null; return from + dir * -dist; } - public Vector3 IntersectSegment(Vector3 begin, Vector3 end) + public Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; real_t den = _normal.Dot(segment); - if (Mathf.Abs(den) <= Mathf.Epsilon) - return new Vector3(); + if (Mathf.IsZeroApprox(den)) + return null; real_t dist = (_normal.Dot(begin) - D) / den; + // Only allow dist to be in the range of 0 to 1, with tolerance. if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon) - return new Vector3(); + return null; return begin + segment * -dist; } diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 4b2525c692..5fa8aed5a9 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -43,6 +43,8 @@ #include "utils/android_utils.h" #endif +#include "mono_gd/gd_mono.h" + namespace GodotSharpDirs { String _get_expected_build_config() { @@ -59,20 +61,6 @@ String _get_expected_build_config() { #endif } -String _get_expected_api_build_config() { -#ifdef TOOLS_ENABLED - return "Debug"; -#else - -#ifdef DEBUG_ENABLED - return "Debug"; -#else - return "Release"; -#endif - -#endif -} - String _get_mono_user_dir() { #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { @@ -134,7 +122,7 @@ private: res_data_dir = "res://.mono"; res_metadata_dir = res_data_dir.plus_file("metadata"); res_assemblies_base_dir = res_data_dir.plus_file("assemblies"); - res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config()); + res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); // TODO use paths from csproj diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 544bfc4615..504b8d41d0 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -381,10 +381,10 @@ void GDMono::initialize_load_assemblies() { } bool GDMono::_are_api_assemblies_out_of_sync() { - bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated); + bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated); #ifdef TOOLS_ENABLED if (!out_of_sync) - out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync; + out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync; #endif return out_of_sync; } @@ -523,10 +523,10 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMo return true; } -APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) { - APIAssembly::Version api_assembly_version; +ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) { + ApiAssemblyInfo::Version api_assembly_version; - const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ? + const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE ? BINDINGS_CLASS_NATIVECALLS : BINDINGS_CLASS_NATIVECALLS_EDITOR; @@ -549,8 +549,8 @@ APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssemb return api_assembly_version; } -String APIAssembly::to_string(APIAssembly::Type p_type) { - return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR"; +String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) { + return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR"; } bool GDMono::_load_corlib_assembly() { @@ -567,16 +567,12 @@ bool GDMono::_load_corlib_assembly() { } #ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) { - - bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ? - GDMono::get_singleton()->core_api_assembly_out_of_sync : - GDMono::get_singleton()->editor_api_assembly_out_of_sync; +bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) { String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); - String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; + String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; // Create destination directory if needed if (!DirAccess::exists(dst_dir)) { @@ -590,35 +586,102 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const Stri } } + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + String xml_file = assembly_name + ".xml"; + if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) + WARN_PRINTS("Failed to copy '" + xml_file + "'."); + + String pdb_file = assembly_name + ".pdb"; + if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) + WARN_PRINTS("Failed to copy '" + pdb_file + "'."); + String assembly_file = assembly_name + ".dll"; - String assembly_src = src_dir.plus_file(assembly_file); - String assembly_dst = dst_dir.plus_file(assembly_file); + if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) { + ERR_PRINTS("Failed to copy '" + assembly_file + "'."); + return false; + } + + return true; +} - if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); +static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) { + String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - String xml_file = assembly_name + ".xml"; - if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy '" + xml_file + "'."); + if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path)) + return false; - String pdb_file = assembly_name + ".pdb"; - if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy '" + pdb_file + "'."); + String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); - Error err = da->copy(assembly_src, assembly_dst); + if (!FileAccess::exists(cached_api_hash_path)) + return false; - if (err != OK) { - ERR_PRINTS("Failed to copy '" + assembly_file + "'."); - return false; - } + Ref<ConfigFile> cfg; + cfg.instance(); + Error cfg_err = cfg->load(cached_api_hash_path); + ERR_FAIL_COND_V(cfg_err != OK, false); - api_assembly_out_of_sync = false; + // Checking the modified time is good enough + if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") || + FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) { + return false; } + r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") || + GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") || + GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") || + GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") || + GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") || + GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash"); + return true; } -String GDMono::update_api_assemblies_from_prebuilt() { +static void create_cached_api_hash_for(const String &p_api_assemblies_dir) { + + String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); + + Ref<ConfigFile> cfg; + cfg.instance(); + + cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path)); + cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path)); + + cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version()); + cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); + cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version()); + cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); + + // This assumes the prebuilt api assemblies we copied to the project are not out of sync + cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash()); + cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash()); + + Error err = cfg->save(cached_api_hash_path); + ERR_FAIL_COND(err != OK); +} + +bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) { + MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies"); + ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies"); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); + + _GDMONO_SCOPE_DOMAIN_(temp_domain); + + GDMono::LoadedApiAssembly temp_core_api_assembly; + GDMono::LoadedApiAssembly temp_editor_api_assembly; + + if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly, + p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) { + return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync; + } + + return true; // Failed to load, assume they're outdated assemblies +} + +String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) { #define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ ( \ @@ -629,46 +692,55 @@ String GDMono::update_api_assemblies_from_prebuilt() { String("and the prebuilt assemblies are missing.") : \ String("and we failed to copy the prebuilt assemblies."))) - bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync; + String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); - String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) - return String(); // No update needed + bool api_assemblies_out_of_sync = false; + + if (p_core_api_out_of_sync && p_editor_api_out_of_sync) { + api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync; + } else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) { + // Determine if they're out of sync + if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) { + api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config); + } + } - const int CONFIGS_LEN = 2; - String configs[CONFIGS_LEN] = { String("Debug"), String("Release") }; + // Note: Even if only one of the assemblies if missing or out of sync, we update both - for (int i = 0; i < CONFIGS_LEN; i++) { - String config = configs[i]; + if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) + return String(); // No update needed - print_verbose("Updating '" + config + "' API assemblies"); + print_verbose("Updating '" + p_config + "' API assemblies"); - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config); - String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); + String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); + String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); - } + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { + return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false); + } - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) || - !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) { - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); - } + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) || + !copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) { + return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true); } + // Cache the api hash of the assemblies we just copied + create_cached_api_hash_for(dst_assemblies_dir); + return String(); // Updated successfully #undef FAIL_REASON } #endif -bool GDMono::_load_core_api_assembly() { +bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (core_api_assembly) + if (r_loaded_api_assembly.assembly) return true; #ifdef TOOLS_ENABLED @@ -676,101 +748,115 @@ bool GDMono::_load_core_api_assembly() { // If running the project manager, load it from the prebuilt API directory String assembly_dir = !Main::is_project_manager() ? - GodotSharpDirs::get_res_assemblies_dir() : - GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) : + GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); bool success = FileAccess::exists(assembly_path) && - load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly); + load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); #else - bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly); + bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &r_loaded_api_assembly.assembly, p_refonly); #endif if (success) { - APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE); - core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; - if (!core_api_assembly_out_of_sync) { - GDMonoUtils::update_godot_api_cache(); - - _install_trace_listener(); - } + ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE); + r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || + GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; } else { - core_api_assembly_out_of_sync = false; + r_loaded_api_assembly.out_of_sync = false; } return success; } #ifdef TOOLS_ENABLED -bool GDMono::_load_editor_api_assembly() { +bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (editor_api_assembly) + if (r_loaded_api_assembly.assembly) return true; // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date // If running the project manager, load it from the prebuilt API directory String assembly_dir = !Main::is_project_manager() ? - GodotSharpDirs::get_res_assemblies_dir() : - GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) : + GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); bool success = FileAccess::exists(assembly_path) && - load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly); + load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); if (success) { - APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR); - editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; + ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR); + r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || + GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || + GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; } else { - editor_api_assembly_out_of_sync = false; + r_loaded_api_assembly.out_of_sync = false; } return success; } #endif -bool GDMono::_try_load_api_assemblies() { - - if (!_load_core_api_assembly()) { +bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, + const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) { + if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) { if (OS::get_singleton()->is_stdout_verbose()) print_error("Mono: Failed to load Core API assembly"); return false; } #ifdef TOOLS_ENABLED - if (!_load_editor_api_assembly()) { + if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) { if (OS::get_singleton()->is_stdout_verbose()) print_error("Mono: Failed to load Editor API assembly"); return false; } - if (editor_api_assembly_out_of_sync) + if (r_editor_api_assembly.out_of_sync) return false; #endif // Check if the core API assembly is out of sync only after trying to load the // editor API assembly. Otherwise, if both assemblies are out of sync, we would // only update the former as we won't know the latter also needs to be updated. - if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) + if (r_core_api_assembly.out_of_sync) + return false; + + if (p_callback) + return p_callback(); + + return true; +} + +bool GDMono::_on_core_api_assembly_loaded() { + GDMonoUtils::update_godot_api_cache(); + + if (!GDMonoUtils::mono_cache.godot_api_cache_updated) return false; + get_singleton()->_install_trace_listener(); + return true; } +bool GDMono::_try_load_api_assemblies_preset() { + return _try_load_api_assemblies(core_api_assembly, editor_api_assembly, + get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded); +} + void GDMono::_load_api_assemblies() { - bool api_assemblies_loaded = _try_load_api_assemblies(); + bool api_assemblies_loaded = _try_load_api_assemblies_preset(); if (!api_assemblies_loaded) { #ifdef TOOLS_ENABLED - // The API assemblies are out of sync. Fine, try one more time, but this time - // update them from the prebuilt assemblies directory before trying to load them. + // The API assemblies are out of sync or some other error happened. Fine, try one more time, but + // this time update them from the prebuilt assemblies directory before trying to load them again. // Shouldn't happen. The project manager loads the prebuilt API assemblies CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies."); @@ -780,7 +866,7 @@ void GDMono::_load_api_assemblies() { CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain."); // 2. Update the API assemblies - String update_error = update_api_assemblies_from_prebuilt(); + String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync); CRASH_COND_MSG(!update_error.empty(), update_error); // 3. Load the scripts domain again @@ -788,7 +874,7 @@ void GDMono::_load_api_assemblies() { CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); // 4. Try loading the updated assemblies - api_assemblies_loaded = _try_load_api_assemblies(); + api_assemblies_loaded = _try_load_api_assemblies_preset(); #endif } @@ -796,14 +882,14 @@ void GDMono::_load_api_assemblies() { // welp... too bad if (_are_api_assemblies_out_of_sync()) { - if (core_api_assembly_out_of_sync) { + if (core_api_assembly.out_of_sync) { ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync."); } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); } #ifdef TOOLS_ENABLED - if (editor_api_assembly_out_of_sync) { + if (editor_api_assembly.out_of_sync) { ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync."); } #endif @@ -852,15 +938,14 @@ void GDMono::_install_trace_listener() { #ifdef DEBUG_ENABLED // Install the trace listener now before the project assembly is loaded - typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **); + GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); + GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener"); + MonoException *exc = NULL; - GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); - DebuggingUtils_InstallTraceListener install_func = - (DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener"); - install_func((MonoObject **)&exc); + install_func->invoke_raw(NULL, NULL, &exc); if (exc) { - ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); GDMonoUtils::debug_print_unhandled_exception(exc); + ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); } #endif } @@ -871,7 +956,7 @@ Error GDMono::_load_scripts_domain() { print_verbose("Mono: Loading scripts domain..."); - scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); + scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts"); ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain."); @@ -903,10 +988,13 @@ Error GDMono::_unload_scripts_domain() { _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); - core_api_assembly = NULL; + core_api_assembly.assembly = NULL; +#ifdef TOOLS_ENABLED + editor_api_assembly.assembly = NULL; +#endif + project_assembly = NULL; #ifdef TOOLS_ENABLED - editor_api_assembly = NULL; tools_assembly = NULL; tools_project_editor_assembly = NULL; #endif @@ -1076,16 +1164,9 @@ GDMono::GDMono() { root_domain = NULL; scripts_domain = NULL; - core_api_assembly_out_of_sync = false; -#ifdef TOOLS_ENABLED - editor_api_assembly_out_of_sync = false; -#endif - corlib_assembly = NULL; - core_api_assembly = NULL; project_assembly = NULL; #ifdef TOOLS_ENABLED - editor_api_assembly = NULL; tools_assembly = NULL; tools_project_editor_assembly = NULL; #endif diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 343d68bc2d..e14a0d8409 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -41,7 +41,7 @@ #include "../utils/mono_reg_utils.h" #endif -namespace APIAssembly { +namespace ApiAssemblyInfo { enum Type { API_CORE, API_EDITOR @@ -76,7 +76,7 @@ struct Version { }; String to_string(Type p_type); -} // namespace APIAssembly +} // namespace ApiAssemblyInfo class GDMono { @@ -86,44 +86,58 @@ public: POLICY_LOG_ERROR }; + struct LoadedApiAssembly { + GDMonoAssembly *assembly; + bool out_of_sync; + + LoadedApiAssembly() : + assembly(NULL), + out_of_sync(false) { + } + }; + private: bool runtime_initialized; bool finalizing_scripts_domain; + UnhandledExceptionPolicy unhandled_exception_policy; + MonoDomain *root_domain; MonoDomain *scripts_domain; - bool core_api_assembly_out_of_sync; -#ifdef TOOLS_ENABLED - bool editor_api_assembly_out_of_sync; -#endif + HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; GDMonoAssembly *corlib_assembly; - GDMonoAssembly *core_api_assembly; GDMonoAssembly *project_assembly; #ifdef TOOLS_ENABLED - GDMonoAssembly *editor_api_assembly; GDMonoAssembly *tools_assembly; GDMonoAssembly *tools_project_editor_assembly; #endif - HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; - - UnhandledExceptionPolicy unhandled_exception_policy; + LoadedApiAssembly core_api_assembly; + LoadedApiAssembly editor_api_assembly; - void _domain_assemblies_cleanup(uint32_t p_domain_id); + typedef bool (*CoreApiAssemblyLoadedCallback)(); bool _are_api_assemblies_out_of_sync(); + bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config); + + bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); +#ifdef TOOLS_ENABLED + bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); +#endif + + static bool _on_core_api_assembly_loaded(); bool _load_corlib_assembly(); - bool _load_core_api_assembly(); #ifdef TOOLS_ENABLED - bool _load_editor_api_assembly(); bool _load_tools_assemblies(); #endif bool _load_project_assembly(); - bool _try_load_api_assemblies(); + bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, + const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback); + bool _try_load_api_assemblies_preset(); void _load_api_assemblies(); void _install_trace_listener(); @@ -133,6 +147,8 @@ private: Error _load_scripts_domain(); Error _unload_scripts_domain(); + void _domain_assemblies_cleanup(uint32_t p_domain_id); + uint64_t api_core_hash; #ifdef TOOLS_ENABLED uint64_t api_editor_hash; @@ -166,9 +182,21 @@ public: #endif // TOOLS_ENABLED #endif // DEBUG_METHODS_ENABLED + _FORCE_INLINE_ static String get_expected_api_build_config() { +#ifdef TOOLS_ENABLED + return "Debug"; +#else +#ifdef DEBUG_ENABLED + return "Debug"; +#else + return "Release"; +#endif +#endif + } + #ifdef TOOLS_ENABLED - bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config); - String update_api_assemblies_from_prebuilt(); + bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config); + String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL); #endif static GDMono *get_singleton() { return singleton; } @@ -188,10 +216,10 @@ public: _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.assembly; } _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED - _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; } _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; } _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; } #endif diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index e385f4c601..6504fbe423 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -550,6 +550,8 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) } MonoDomain *create_domain(const String &p_friendly_name) { + print_verbose("Mono: Creating domain '" + p_friendly_name + "'..."); + MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL); if (domain) { diff --git a/modules/recast/navigation_mesh_generator.cpp b/modules/recast/navigation_mesh_generator.cpp index c5b60f2dca..320591cf7c 100644 --- a/modules/recast/navigation_mesh_generator.cpp +++ b/modules/recast/navigation_mesh_generator.cpp @@ -131,7 +131,7 @@ void EditorNavigationMeshGenerator::_add_faces(const PoolVector3Array &p_faces, } } -void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask) { +void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { @@ -263,8 +263,10 @@ void EditorNavigationMeshGenerator::_parse_geometry(Transform p_accumulated_tran p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); } - for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask); + if (p_recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_verticies, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + } } } @@ -439,7 +441,21 @@ void EditorNavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p Vector<float> vertices; Vector<int> indices; - _parse_geometry(Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse(), p_node, vertices, indices, p_nav_mesh->get_parsed_geometry_type(), p_nav_mesh->get_collision_mask()); + List<Node *> parse_nodes; + + if (p_nav_mesh->get_source_geometry_mode() == NavigationMesh::SOURCE_GEOMETRY_NAVMESH_CHILDREN) { + parse_nodes.push_back(p_node); + } else { + p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); + } + + Transform navmesh_xform = Object::cast_to<Spatial>(p_node)->get_transform().affine_inverse(); + for (const List<Node *>::Element *E = parse_nodes.front(); E; E = E->next()) { + int geometry_type = p_nav_mesh->get_parsed_geometry_type(); + uint32_t collision_mask = p_nav_mesh->get_collision_mask(); + bool recurse_children = p_nav_mesh->get_source_geometry_mode() != NavigationMesh::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + _parse_geometry(navmesh_xform, E->get(), vertices, indices, geometry_type, collision_mask, recurse_children); + } if (vertices.size() > 0 && indices.size() > 0) { diff --git a/modules/recast/navigation_mesh_generator.h b/modules/recast/navigation_mesh_generator.h index 30a6e3c835..f19622a4a9 100644 --- a/modules/recast/navigation_mesh_generator.h +++ b/modules/recast/navigation_mesh_generator.h @@ -47,7 +47,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); static void _add_faces(const PoolVector3Array &p_faces, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices); - static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask); + static void _parse_geometry(Transform p_accumulated_transform, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices, int p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep, diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 28a8b77283..ed1a7f682b 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -368,7 +368,7 @@ float VideoStreamPlaybackTheora::get_time() const { return time - AudioServer::get_singleton()->get_output_latency() - delay_compensation; //-((get_total())/(float)vi.rate); }; -Ref<Texture> VideoStreamPlaybackTheora::get_texture() { +Ref<Texture> VideoStreamPlaybackTheora::get_texture() const { return texture; } diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 0c37d33358..b241722cd1 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -147,7 +147,7 @@ public: void set_file(const String &p_file); - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 093901ad07..c1a4c58620 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -1916,8 +1916,6 @@ bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant & return false; } -#ifdef TOOLS_ENABLED - static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) { if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene) @@ -1937,8 +1935,6 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const return NULL; } -#endif - void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { if (p_from != graph) { @@ -2456,12 +2452,8 @@ Ref<Texture> VisualScriptEditor::get_icon() { } bool VisualScriptEditor::is_unsaved() { -#ifdef TOOLS_ENABLED return script->is_edited() || script->are_subnodes_edited(); -#else - return false; -#endif } Variant VisualScriptEditor::get_edit_state() { diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 5a00469eea..fbf021e7b6 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -228,7 +228,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _update_node_size(int p_id); void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input); - Vector2 _get_available_pos(bool centered = true, Vector2 pos = Vector2()) const; + Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const; StringName _get_function_of_node(int p_id) const; void _move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id); diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 7f36549ae4..4e90a08009 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -1039,7 +1039,7 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const } List<PropertyInfo> props; - ClassDB::get_property_list(_get_base_type(), &props, true); + ClassDB::get_property_list(_get_base_type(), &props, false); for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { if (E->get().name == property) { PropertyInfo pinfo = PropertyInfo(E->get().type, "value", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); @@ -1808,7 +1808,7 @@ PropertyInfo VisualScriptPropertyGet::get_input_value_port_info(int p_idx) const PropertyInfo VisualScriptPropertyGet::get_output_value_port_info(int p_idx) const { List<PropertyInfo> props; - ClassDB::get_property_list(_get_base_type(), &props, true); + ClassDB::get_property_list(_get_base_type(), &props, false); for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { if (E->get().name == property) { return PropertyInfo(E->get().type, "value." + String(index)); diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 42d4c5e209..62b818150c 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -32,7 +32,7 @@ #include "core/os/keyboard.h" #include "editor/editor_node.h" -#include "editor_scale.h" +#include "editor/editor_scale.h" #include "modules/visual_script/visual_script.h" #include "modules/visual_script/visual_script_builtin_funcs.h" #include "modules/visual_script/visual_script_flow_control.h" diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h index 13ce9bdca2..3a7c8de6a2 100644 --- a/modules/visual_script/visual_script_property_selector.h +++ b/modules/visual_script/visual_script_property_selector.h @@ -31,8 +31,8 @@ #ifndef VISUALSCRIPT_PROPERTYSELECTOR_H #define VISUALSCRIPT_PROPERTYSELECTOR_H +#include "editor/editor_help.h" #include "editor/property_editor.h" -#include "editor_help.h" #include "scene/gui/rich_text_label.h" class VisualScriptPropertySelector : public ConfirmationDialog { @@ -45,7 +45,7 @@ class VisualScriptPropertySelector : public ConfirmationDialog { void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); - void get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box); + void get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box); void _sbox_input(const Ref<InputEvent> &p_ie); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index fa3602ad27..4ce0db3746 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -230,7 +230,7 @@ void VideoStreamPlaybackWebm::set_audio_track(int p_idx) { audio_track = p_idx; } -Ref<Texture> VideoStreamPlaybackWebm::get_texture() { +Ref<Texture> VideoStreamPlaybackWebm::get_texture() const { return texture; } diff --git a/modules/webm/video_stream_webm.h b/modules/webm/video_stream_webm.h index ddcbb1eb08..4f79d46cce 100644 --- a/modules/webm/video_stream_webm.h +++ b/modules/webm/video_stream_webm.h @@ -90,7 +90,7 @@ public: virtual void set_audio_track(int p_idx); - virtual Ref<Texture> get_texture(); + virtual Ref<Texture> get_texture() const; virtual void update(float p_delta); virtual void set_mix_callback(AudioMixCallback p_callback, void *p_userdata); diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index c3baf9de83..705e3485f5 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -21,10 +21,13 @@ </argument> <argument index="2" name="gd_mp_api" type="bool" default="false"> </argument> + <argument index="3" name="custom_headers" type="PoolStringArray" default="PoolStringArray( )"> + </argument> <description> Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). + You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request (not supported in HTML5 platform). </description> </method> <method name="disconnect_from_host"> @@ -38,8 +41,25 @@ Disconnects this client from the connected host. See [method WebSocketPeer.close] for more information. </description> </method> + <method name="get_connected_host" qualifiers="const"> + <return type="String"> + </return> + <description> + Return the IP address of the currently connected host. + </description> + </method> + <method name="get_connected_port" qualifiers="const"> + <return type="int"> + </return> + <description> + Return the IP port of the currently connected host. + </description> + </method> </methods> <members> + <member name="trusted_ssl_certificate" type="X509Certificate" setter="set_trusted_ssl_certificate" getter="get_trusted_ssl_certificate"> + If specified, this [X509Certificate] will be the only one accepted when connecting to an SSL host. Any other certificate provided by the server will be regarded as invalid. + </member> <member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled"> If [code]true[/code], SSL certificate verification is enabled. [b]Note:[/b] You must specify the certificates to be used in the Project Settings for it to work when exported. diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 63318e5874..86f2dae64f 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -82,6 +82,17 @@ </description> </method> </methods> + <members> + <member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain"> + When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake. + </member> + <member name="private_key" type="CryptoKey" setter="set_private_key" getter="get_private_key"> + When set to a valid [CryptoKey] (along with [member ssl_certificate]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol). + </member> + <member name="ssl_certificate" type="X509Certificate" setter="set_ssl_certificate" getter="get_ssl_certificate"> + When set to a valid [X509Certificate] (along with [member private_key]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol). + </member> + </members> <signals> <signal name="client_close_request"> <argument index="0" name="id" type="int"> diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp index 409cc9f699..983db60d5e 100644 --- a/modules/websocket/emws_client.cpp +++ b/modules/websocket/emws_client.cpp @@ -64,13 +64,26 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int } } -Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { +Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) { + + String proto_string; + for (int i = 0; i < p_protocols.size(); i++) { + if (i != 0) + proto_string += ","; + proto_string += p_protocols[i]; + } - String proto_string = p_protocols.join(","); String str = "ws://"; - if (p_ssl) + if (p_custom_headers.size()) { + WARN_PRINT_ONCE("Custom headers are not supported in in HTML5 platform."); + } + if (p_ssl) { str = "wss://"; + if (ssl_cert.is_valid()) { + WARN_PRINT_ONCE("Custom SSL certificate is not supported in HTML5 platform."); + } + } str += p_host + ":" + itos(p_port) + p_path; _is_connecting = true; @@ -193,12 +206,12 @@ void EMWSClient::disconnect_from_host(int p_code, String p_reason) { IP_Address EMWSClient::get_connected_host() const { - return IP_Address(); + ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); }; uint16_t EMWSClient::get_connected_port() const { - return 1025; + ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); }; int EMWSClient::get_max_packet_size() const { diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h index 1811d05eea..67705891b2 100644 --- a/modules/websocket/emws_client.h +++ b/modules/websocket/emws_client.h @@ -50,7 +50,7 @@ public: bool _is_connecting; Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()); Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); IP_Address get_connected_host() const; diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp index c4bb459ad0..9a6a30d613 100644 --- a/modules/websocket/emws_server.cpp +++ b/modules/websocket/emws_server.cpp @@ -33,7 +33,7 @@ #include "emws_server.h" #include "core/os/os.h" -Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { +Error EMWSServer::listen(int p_port, Vector<String> p_protocols, bool gd_mp_api) { return FAILED; } diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h index a5e5b4090e..e8da8c26b4 100644 --- a/modules/websocket/emws_server.h +++ b/modules/websocket/emws_server.h @@ -43,7 +43,7 @@ class EMWSServer : public WebSocketServer { public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + Error listen(int p_port, Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false); void stop(); bool is_listening() const; bool has_peer(int p_id) const; diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index 4ff5404c61..8bbd5aa37f 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -40,7 +40,7 @@ WebSocketClient::WebSocketClient() { WebSocketClient::~WebSocketClient() { } -Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protocols, bool gd_mp_api) { +Error WebSocketClient::connect_to_url(String p_url, const Vector<String> p_protocols, bool gd_mp_api, const Vector<String> p_custom_headers) { _is_multiplayer = gd_mp_api; String host = p_url; @@ -72,7 +72,7 @@ Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protoco host = host.substr(0, p_len); } - return connect_to_host(host, path, port, ssl, p_protocols); + return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers); } void WebSocketClient::set_verify_ssl_enabled(bool p_verify_ssl) { @@ -85,6 +85,17 @@ bool WebSocketClient::is_verify_ssl_enabled() const { return verify_ssl; } +Ref<X509Certificate> WebSocketClient::get_trusted_ssl_certificate() const { + + return ssl_cert; +} + +void WebSocketClient::set_trusted_ssl_certificate(Ref<X509Certificate> p_cert) { + + ERR_FAIL_COND(get_connection_status() != CONNECTION_DISCONNECTED); + ssl_cert = p_cert; +} + bool WebSocketClient::is_server() const { return false; @@ -132,13 +143,20 @@ void WebSocketClient::_on_error() { } void WebSocketClient::_bind_methods() { - ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api", "custom_headers"), &WebSocketClient::connect_to_url, DEFVAL(Vector<String>()), DEFVAL(false), DEFVAL(Vector<String>())); ClassDB::bind_method(D_METHOD("disconnect_from_host", "code", "reason"), &WebSocketClient::disconnect_from_host, DEFVAL(1000), DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_connected_host"), &WebSocketClient::get_connected_host); + ClassDB::bind_method(D_METHOD("get_connected_port"), &WebSocketClient::get_connected_port); ClassDB::bind_method(D_METHOD("set_verify_ssl_enabled", "enabled"), &WebSocketClient::set_verify_ssl_enabled); ClassDB::bind_method(D_METHOD("is_verify_ssl_enabled"), &WebSocketClient::is_verify_ssl_enabled); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "verify_ssl", PROPERTY_HINT_NONE, "", 0), "set_verify_ssl_enabled", "is_verify_ssl_enabled"); + ClassDB::bind_method(D_METHOD("get_trusted_ssl_certificate"), &WebSocketClient::get_trusted_ssl_certificate); + ClassDB::bind_method(D_METHOD("set_trusted_ssl_certificate"), &WebSocketClient::set_trusted_ssl_certificate); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "trusted_ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_trusted_ssl_certificate", "get_trusted_ssl_certificate"); + ADD_SIGNAL(MethodInfo("data_received")); ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); ADD_SIGNAL(MethodInfo("server_close_request", PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h index 7ddb9468a5..08fd6798fe 100644 --- a/modules/websocket/websocket_client.h +++ b/modules/websocket/websocket_client.h @@ -31,6 +31,7 @@ #ifndef WEBSOCKET_CLIENT_H #define WEBSOCKET_CLIENT_H +#include "core/crypto/crypto.h" #include "core/error_list.h" #include "websocket_multiplayer_peer.h" #include "websocket_peer.h" @@ -43,17 +44,20 @@ class WebSocketClient : public WebSocketMultiplayerPeer { protected: Ref<WebSocketPeer> _peer; bool verify_ssl; + Ref<X509Certificate> ssl_cert; static void _bind_methods(); public: - Error connect_to_url(String p_url, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + Error connect_to_url(String p_url, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false, const Vector<String> p_custom_headers = Vector<String>()); void set_verify_ssl_enabled(bool p_verify_ssl); bool is_verify_ssl_enabled() const; + Ref<X509Certificate> get_trusted_ssl_certificate() const; + void set_trusted_ssl_certificate(Ref<X509Certificate> p_cert); virtual void poll() = 0; - virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0; + virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()) = 0; virtual void disconnect_from_host(int p_code = 1000, String p_reason = "") = 0; virtual IP_Address get_connected_host() const = 0; virtual uint16_t get_connected_port() const = 0; diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index ef5f6f5c20..c7414075ed 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -42,19 +42,58 @@ WebSocketServer::~WebSocketServer() { void WebSocketServer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening); - ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(Vector<String>()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop); ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address); ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port); ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_private_key"), &WebSocketServer::get_private_key); + ClassDB::bind_method(D_METHOD("set_private_key"), &WebSocketServer::set_private_key); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", 0), "set_private_key", "get_private_key"); + + ClassDB::bind_method(D_METHOD("get_ssl_certificate"), &WebSocketServer::get_ssl_certificate); + ClassDB::bind_method(D_METHOD("set_ssl_certificate"), &WebSocketServer::set_ssl_certificate); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ssl_certificate", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ssl_certificate", "get_ssl_certificate"); + + ClassDB::bind_method(D_METHOD("get_ca_chain"), &WebSocketServer::get_ca_chain); + ClassDB::bind_method(D_METHOD("set_ca_chain"), &WebSocketServer::set_ca_chain); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ca_chain", "get_ca_chain"); + ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close"))); ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); } +Ref<CryptoKey> WebSocketServer::get_private_key() const { + return private_key; +} + +void WebSocketServer::set_private_key(Ref<CryptoKey> p_key) { + ERR_FAIL_COND(is_listening()); + private_key = p_key; +} + +Ref<X509Certificate> WebSocketServer::get_ssl_certificate() const { + return ssl_cert; +} + +void WebSocketServer::set_ssl_certificate(Ref<X509Certificate> p_cert) { + ERR_FAIL_COND(is_listening()); + ssl_cert = p_cert; +} + +Ref<X509Certificate> WebSocketServer::get_ca_chain() const { + return ca_chain; +} + +void WebSocketServer::set_ca_chain(Ref<X509Certificate> p_ca_chain) { + ERR_FAIL_COND(is_listening()); + ca_chain = p_ca_chain; +} + NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { if (is_listening()) return CONNECTION_CONNECTED; diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h index 83c0c10419..0b39f94473 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -31,6 +31,7 @@ #ifndef WEBSOCKET_H #define WEBSOCKET_H +#include "core/crypto/crypto.h" #include "core/reference.h" #include "websocket_multiplayer_peer.h" #include "websocket_peer.h" @@ -43,9 +44,13 @@ class WebSocketServer : public WebSocketMultiplayerPeer { protected: static void _bind_methods(); + Ref<CryptoKey> private_key; + Ref<X509Certificate> ssl_cert; + Ref<X509Certificate> ca_chain; + public: virtual void poll() = 0; - virtual Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false) = 0; + virtual Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false) = 0; virtual void stop() = 0; virtual bool is_listening() const = 0; virtual bool has_peer(int p_id) const = 0; @@ -62,6 +67,15 @@ public: void _on_disconnect(int32_t p_peer_id, bool p_was_clean); void _on_close_request(int32_t p_peer_id, int p_code, String p_reason); + Ref<CryptoKey> get_private_key() const; + void set_private_key(Ref<CryptoKey> p_key); + + Ref<X509Certificate> get_ssl_certificate() const; + void set_ssl_certificate(Ref<X509Certificate> p_cert); + + Ref<X509Certificate> get_ca_chain() const; + void set_ca_chain(Ref<X509Certificate> p_ca_chain); + virtual Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) = 0; WebSocketServer(); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 0006a057e0..ad70c9c0e1 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -86,6 +86,7 @@ void WSLClient::_do_handshake() { WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); data->obj = this; data->conn = _connection; + data->tcp = _tcp; data->is_server = false; data->id = 1; _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); @@ -151,7 +152,7 @@ bool WSLClient::_verify_headers(String &r_protocol) { return true; } -Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { +Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) { ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); @@ -180,7 +181,12 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _connection = _tcp; _use_ssl = p_ssl; _host = p_host; - _protocols = p_protocols; + // Strip edges from protocols. + _protocols.resize(p_protocols.size()); + String *pw = _protocols.ptrw(); + for (int i = 0; i < p_protocols.size(); i++) { + pw[i] = p_protocols[i].strip_edges(); + } _key = WSLPeer::generate_key(); // TODO custom extra headers (allow overriding this too?) @@ -199,6 +205,9 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } request += "\r\n"; } + for (int i = 0; i < p_custom_headers.size(); i++) { + request += p_custom_headers[i] + "\r\n"; + } request += "\r\n"; _request = request.utf8(); @@ -236,7 +245,7 @@ void WSLClient::poll() { ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); ERR_FAIL_COND_MSG(ssl.is_null(), "SSL is not available in this build."); ssl->set_blocking_handshake_enabled(false); - if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) { + if (ssl->connect_to_stream(_tcp, verify_ssl, _host, ssl_cert) != OK) { disconnect_from_host(); _on_error(); return; @@ -293,7 +302,7 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { _key = ""; _host = ""; - _protocols.resize(0); + _protocols.clear(); _use_ssl = false; _request = ""; @@ -305,12 +314,14 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { IP_Address WSLClient::get_connected_host() const { - return IP_Address(); + ERR_FAIL_COND_V(!_peer->is_connected_to_host(), IP_Address()); + return _peer->get_connected_host(); } uint16_t WSLClient::get_connected_port() const { - return 1025; + ERR_FAIL_COND_V(!_peer->is_connected_to_host(), 0); + return _peer->get_connected_port(); } Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index 57dfd635b7..870be94a87 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -64,7 +64,7 @@ private: String _key; String _host; - PoolVector<String> _protocols; + Vector<String> _protocols; bool _use_ssl; void _do_handshake(); @@ -72,7 +72,7 @@ private: public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>()); int get_max_packet_size() const; Ref<WebSocketPeer> get_peer(int p_peer_id) const; void disconnect_from_host(int p_code = 1000, String p_reason = ""); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 74fb901232..32beccde5d 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -208,7 +208,6 @@ void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigne _data = p_data; _data->peer = this; _data->valid = true; - _connection = Ref<StreamPeer>(_data->conn); if (_data->is_server) wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); @@ -302,18 +301,16 @@ void WSLPeer::close(int p_code, String p_reason) { IP_Address WSLPeer::get_connected_host() const { - ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); + ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), IP_Address()); - IP_Address ip; - return ip; + return _data->tcp->get_connected_host(); } uint16_t WSLPeer::get_connected_port() const { - ERR_FAIL_COND_V(!is_connected_to_host(), 0); + ERR_FAIL_COND_V(!is_connected_to_host() || _data->tcp.is_null(), 0); - uint16_t port = 0; - return port; + return _data->tcp->get_connected_port(); } void WSLPeer::invalidate() { diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index d51b304fe1..01ad250468 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -35,6 +35,7 @@ #include "core/error_list.h" #include "core/io/packet_peer.h" +#include "core/io/stream_peer_tcp.h" #include "core/ring_buffer.h" #include "packet_buffer.h" #include "websocket_peer.h" @@ -55,6 +56,7 @@ public: void *obj; void *peer; Ref<StreamPeer> conn; + Ref<StreamPeerTCP> tcp; int id; wslay_event_context_ptr ctx; @@ -77,7 +79,6 @@ private: static bool _wsl_poll(struct PeerData *p_data); static void _wsl_destroy(struct PeerData **p_data); - Ref<StreamPeer> _connection; struct PeerData *_data; uint8_t _is_string; // Our packet info is just a boolean (is_string), using uint8_t for it. diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index efb526eed1..2181775b99 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -35,6 +35,7 @@ #include "core/project_settings.h" WSLServer::PendingPeer::PendingPeer() { + use_ssl = false; time = 0; has_request = false; response_sent = 0; @@ -42,7 +43,7 @@ WSLServer::PendingPeer::PendingPeer() { memset(req_buf, 0, sizeof(req_buf)); } -bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { +bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols) { Vector<String> psa = String((char *)req_buf).split("\r\n"); int len = psa.size(); ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); @@ -79,11 +80,12 @@ bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { if (headers.has("sec-websocket-protocol")) { Vector<String> protos = headers["sec-websocket-protocol"].split(","); for (int i = 0; i < protos.size(); i++) { + String proto = protos[i].strip_edges(); // Check if we have the given protocol for (int j = 0; j < p_protocols.size(); j++) { - if (protos[i] != p_protocols[j]) + if (proto != p_protocols[j]) continue; - protocol = protos[i]; + protocol = proto; break; } // Found a protocol @@ -97,9 +99,19 @@ bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { return true; } -Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { +Error WSLServer::PendingPeer::do_handshake(const Vector<String> p_protocols) { if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) return ERR_TIMEOUT; + if (use_ssl) { + Ref<StreamPeerSSL> ssl = static_cast<Ref<StreamPeerSSL> >(connection); + if (ssl.is_null()) + return FAILED; + ssl->poll(); + if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) + return ERR_BUSY; + else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) + return FAILED; + } if (!has_request) { int read = 0; while (true) { @@ -143,11 +155,16 @@ Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { return OK; } -Error WSLServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { +Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp_api) { ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); _is_multiplayer = gd_mp_api; - _protocols = p_protocols; + // Strip edges from protocols. + _protocols.resize(p_protocols.size()); + String *pw = _protocols.ptrw(); + for (int i = 0; i < p_protocols.size(); i++) { + pw[i] = p_protocols[i].strip_edges(); + } _server->listen(p_port); return OK; @@ -185,6 +202,7 @@ void WSLServer::poll() { WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); data->obj = this; data->conn = ppeer->connection; + data->tcp = ppeer->tcp; data->is_server = true; data->id = id; @@ -204,12 +222,21 @@ void WSLServer::poll() { return; while (_server->is_connection_available()) { - Ref<StreamPeer> conn = _server->take_connection(); + Ref<StreamPeerTCP> conn = _server->take_connection(); if (is_refusing_new_connections()) continue; // Conn will go out-of-scope and be closed. Ref<PendingPeer> peer = memnew(PendingPeer); - peer->connection = conn; + if (private_key.is_valid() && ssl_cert.is_valid()) { + Ref<StreamPeerSSL> ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ssl->set_blocking_handshake_enabled(false); + ssl->accept_stream(conn, private_key, ssl_cert, ca_chain); + peer->connection = ssl; + peer->use_ssl = true; + } else { + peer->connection = conn; + } + peer->tcp = conn; peer->time = OS::get_singleton()->get_ticks_msec(); _pending.push_back(peer); } @@ -231,6 +258,7 @@ void WSLServer::stop() { } _pending.clear(); _peer_map.clear(); + _protocols.clear(); } bool WSLServer::has_peer(int p_id) const { diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h index 2ceb941073..aae563355e 100644 --- a/modules/websocket/wsl_server.h +++ b/modules/websocket/wsl_server.h @@ -36,6 +36,7 @@ #include "websocket_server.h" #include "wsl_peer.h" +#include "core/io/stream_peer_ssl.h" #include "core/io/stream_peer_tcp.h" #include "core/io/tcp_server.h" @@ -49,10 +50,12 @@ private: class PendingPeer : public Reference { private: - bool _parse_request(const PoolStringArray p_protocols); + bool _parse_request(const Vector<String> p_protocols); public: + Ref<StreamPeerTCP> tcp; Ref<StreamPeer> connection; + bool use_ssl; int time; uint8_t req_buf[WSL_MAX_HEADER_SIZE]; @@ -65,7 +68,7 @@ private: PendingPeer(); - Error do_handshake(const PoolStringArray p_protocols); + Error do_handshake(const Vector<String> p_protocols); }; int _in_buf_size; @@ -75,11 +78,11 @@ private: List<Ref<PendingPeer> > _pending; Ref<TCP_Server> _server; - PoolStringArray _protocols; + Vector<String> _protocols; public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); - Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + Error listen(int p_port, const Vector<String> p_protocols = Vector<String>(), bool gd_mp_api = false); void stop(); bool is_listening() const; int get_max_packet_size() const; |