diff options
Diffstat (limited to 'modules')
89 files changed, 4026 insertions, 1398 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/btRayShape.h b/modules/bullet/btRayShape.h index 7f3229b3e8..09c1f6c241 100644 --- a/modules/bullet/btRayShape.h +++ b/modules/bullet/btRayShape.h @@ -62,7 +62,7 @@ public: virtual void setMargin(btScalar margin); - void setSlipsOnSlope(bool p_slipOnSlope); + void setSlipsOnSlope(bool p_slipsOnSlope); bool getSlipsOnSlope() const { return slipsOnSlope; } const btTransform &getSupportPoint() const { return m_cacheSupportPoint; } 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..d611810bfa 100644 --- a/modules/bullet/rigid_body_bullet.cpp +++ b/modules/bullet/rigid_body_bullet.cpp @@ -126,16 +126,16 @@ void BulletPhysicsDirectBodyState::add_torque(const Vector3 &p_torque) { body->apply_torque(p_torque); } -void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_j) { - body->apply_central_impulse(p_j); +void BulletPhysicsDirectBodyState::apply_central_impulse(const Vector3 &p_impulse) { + body->apply_central_impulse(p_impulse); } -void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_j) { - body->apply_impulse(p_pos, p_j); +void BulletPhysicsDirectBodyState::apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse) { + body->apply_impulse(p_pos, p_impulse); } -void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_j) { - body->apply_torque_impulse(p_j); +void BulletPhysicsDirectBodyState::apply_torque_impulse(const Vector3 &p_impulse) { + body->apply_torque_impulse(p_impulse); } void BulletPhysicsDirectBodyState::set_sleep_state(bool p_enable) { @@ -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); } @@ -918,7 +920,7 @@ void RigidBodyBullet::reload_space_override_modificator() { currentArea = areasWhereIam[i]; - if (PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) { + if (!currentArea || PhysicsServer::AREA_SPACE_OVERRIDE_DISABLED == currentArea->get_spOv_mode()) { continue; } diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index f63148092f..0b6dc997db 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -114,8 +114,8 @@ public: virtual void add_force(const Vector3 &p_force, const Vector3 &p_pos); virtual void add_torque(const Vector3 &p_torque); virtual void apply_central_impulse(const Vector3 &p_impulse); - virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_j); - virtual void apply_torque_impulse(const Vector3 &p_j); + virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse); + virtual void apply_torque_impulse(const Vector3 &p_impulse); virtual void set_sleep_state(bool p_enable); virtual bool is_sleeping() const; 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/etc/image_etc.cpp b/modules/etc/image_etc.cpp index 6f54436bf9..f0cbf6ae28 100644 --- a/modules/etc/image_etc.cpp +++ b/modules/etc/image_etc.cpp @@ -168,6 +168,7 @@ static void _compress_etc(Image *p_img, float p_lossy_quality, bool force_etc1_f } PoolVector<uint8_t>::Read r = img->get_data().read(); + ERR_FAIL_COND(!r.ptr()); unsigned int target_size = Image::get_image_data_size(imgw, imgh, etc_format, p_img->has_mipmaps()); int mmc = 1 + (p_img->has_mipmaps() ? Image::get_image_required_mipmaps(imgw, imgh, etc_format) : 0); 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/include/gdnative/array.h b/modules/gdnative/include/gdnative/array.h index 2e3ce58033..a27626325e 100644 --- a/modules/gdnative/include/gdnative/array.h +++ b/modules/gdnative/include/gdnative/array.h @@ -132,7 +132,7 @@ void GDAPI godot_array_destroy(godot_array *p_self); godot_array GDAPI godot_array_duplicate(const godot_array *p_self, const godot_bool p_deep); -godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_delta, const godot_bool p_deep); +godot_array GDAPI godot_array_slice(const godot_array *p_self, const godot_int p_begin, const godot_int p_end, const godot_int p_step, const godot_bool p_deep); godot_variant GDAPI godot_array_max(const godot_array *p_self); diff --git a/modules/gdnative/register_types.cpp b/modules/gdnative/register_types.cpp index 6ff6262b56..fa59c704d5 100644 --- a/modules/gdnative/register_types.cpp +++ b/modules/gdnative/register_types.cpp @@ -158,7 +158,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" "extern void add_ios_init_callback(void (*cb)());\n"; String linker_flags = ""; - for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; String code = declare_pattern.replace("$name", full_name); code = code.replace("$weak", expected_symbols[i].is_required ? "" : " __attribute__((weak))"); @@ -174,7 +174,7 @@ void GDNativeExportPlugin::_export_file(const String &p_path, const String &p_ty additional_code += String("void $prefixinit() {\n").replace("$prefix", lib->get_symbol_prefix()); String register_pattern = " if (&$name) register_dynamic_symbol((char *)\"$name\", (void *)$name);\n"; - for (unsigned int i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { + for (unsigned long i = 0; i < sizeof(expected_symbols) / sizeof(expected_symbols[0]); ++i) { String full_name = lib->get_symbol_prefix() + expected_symbols[i].name; additional_code += register_pattern.replace("$name", full_name); } @@ -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/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 788db7fb86..1d0567dd8d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -590,10 +590,10 @@ extends Sprite var elapsed = 0.0 func _process(delta): - var min_angle = deg2rad(0.0) - var max_angle = deg2rad(90.0) - rotation = lerp_angle(min_angle, max_angle, elapsed) - elapsed += delta + var min_angle = deg2rad(0.0) + var max_angle = deg2rad(90.0) + rotation = lerp_angle(min_angle, max_angle, elapsed) + elapsed += delta [/codeblock] </description> </method> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 963b40529d..4d6279074c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -247,7 +247,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ in_function_args = false; } - if (expect_type && prev_is_char) { + if (expect_type && (prev_is_char || str[j] == '=')) { expect_type = false; } @@ -364,20 +364,28 @@ void GDScriptSyntaxHighlighter::_update_cache() { number_color = text_editor->get_color("number_color"); member_color = text_editor->get_color("member_variable_color"); - EditorSettings *settings = EditorSettings::get_singleton(); - String text_editor_color_theme = settings->get("text_editor/theme/color_theme"); + const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); + const bool default_theme = text_editor_color_theme == "Default"; - bool default_theme = text_editor_color_theme == "Default"; - bool dark_theme = settings->is_dark_theme(); - - function_definition_color = default_theme ? Color(0.0, 0.88, 1.0) : dark_theme ? Color(0.0, 0.88, 1.0) : Color(0.0, 0.65, 0.73); - node_path_color = default_theme ? Color(0.39, 0.76, 0.35) : dark_theme ? Color(0.39, 0.76, 0.35) : Color(0.32, 0.55, 0.29); + if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) { + function_definition_color = Color(0.4, 0.9, 1.0); + node_path_color = Color(0.39, 0.76, 0.35); + } else { + function_definition_color = Color(0.0, 0.65, 0.73); + node_path_color = Color(0.32, 0.55, 0.29); + } EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); if (text_editor_color_theme == "Adaptive" || default_theme) { - settings->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", function_definition_color, true); - settings->set_initial_value("text_editor/highlighting/gdscript/node_path_color", node_path_color, true); + EditorSettings::get_singleton()->set_initial_value( + "text_editor/highlighting/gdscript/function_definition_color", + function_definition_color, + true); + EditorSettings::get_singleton()->set_initial_value( + "text_editor/highlighting/gdscript/node_path_color", + node_path_color, + true); } function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); 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_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 97790e00bb..185eb6c3fc 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -572,37 +572,32 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case OBJ_WEAKREF: { VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::OBJECT) { - + if (p_args[0]->get_type() == Variant::OBJECT) { + if (p_args[0]->is_ref()) { + Ref<WeakRef> wref = memnew(WeakRef); + REF r = *p_args[0]; + if (r.is_valid()) { + wref->set_ref(r); + } + r_ret = wref; + } else { + Ref<WeakRef> wref = memnew(WeakRef); + Object *obj = *p_args[0]; + if (obj) { + wref->set_obj(obj); + } + r_ret = wref; + } + } else if (p_args[0]->get_type() == Variant::NIL) { + Ref<WeakRef> wref = memnew(WeakRef); + r_ret = wref; + } else { r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::OBJECT; r_ret = Variant(); return; } - - if (p_args[0]->is_ref()) { - - REF r = *p_args[0]; - if (!r.is_valid()) { - r_ret = Variant(); - return; - } - - Ref<WeakRef> wref = memnew(WeakRef); - wref->set_ref(r); - r_ret = wref; - } else { - Object *obj = *p_args[0]; - if (!obj) { - r_ret = Variant(); - return; - } - Ref<WeakRef> wref = memnew(WeakRef); - wref->set_obj(obj); - r_ret = wref; - } - } break; case FUNC_FUNCREF: { VALIDATE_ARG_COUNT(2); @@ -1279,6 +1274,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ if (err != OK) { r_ret = Variant(); + ERR_PRINTS(vformat("Error parsing JSON at line %s: %s", errl, errs)); } } break; 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 6db8cb2c88..6b5c26ec81 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -105,6 +105,40 @@ void ExtendGDScriptParser::update_symbols() { } } +void ExtendGDScriptParser::update_document_links(const String &p_code) { + document_links.clear(); + + GDScriptTokenizerText tokenizer; + FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); + tokenizer.set_code(p_code); + while (true) { + if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) { + break; + } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) { + Variant const_val = tokenizer.get_token_constant(); + if (const_val.get_type() == Variant::STRING) { + String path = const_val; + bool exists = fs->file_exists(path); + if (!exists) { + path = get_path().get_base_dir() + "/" + path; + exists = fs->file_exists(path); + } + if (exists) { + String value = const_val; + lsp::DocumentLink link; + link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); + link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line()); + link.range.end.line = link.range.start.line; + link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column()); + link.range.start.character = link.range.end.character - value.length(); + document_links.push_back(link); + } + } + } + tokenizer.advance(); + } +} + void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); @@ -123,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) { @@ -150,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; @@ -170,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 + "("; @@ -199,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; @@ -264,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; @@ -325,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 = "'''gdscript\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\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()); @@ -542,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; @@ -572,6 +548,10 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String return NULL; } +const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const { + return document_links; +} + const Array &ExtendGDScriptParser::get_member_completions() { if (member_completions.empty()) { @@ -755,5 +735,6 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false); update_diagnostics(); update_symbols(); + update_document_links(p_code); return err; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index dd0453d3ff..a6e0ca5534 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -56,12 +56,14 @@ class ExtendGDScriptParser : public GDScriptParser { lsp::DocumentSymbol class_symbol; Vector<lsp::Diagnostic> diagnostics; + List<lsp::DocumentLink> document_links; ClassMembers members; HashMap<String, ClassMembers> inner_classes; void update_diagnostics(); void update_symbols(); + void update_document_links(const String &p_code); void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol); void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol); @@ -73,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; } @@ -93,6 +90,7 @@ public: const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; + const List<lsp::DocumentLink> &get_document_links() const; const Array &get_member_completions(); Dictionary generate_api() const; 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 7c58c7aa15..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"]; @@ -271,8 +292,17 @@ Array GDScriptTextDocument::codeLens(const Dictionary &p_params) { return arr; } -Variant GDScriptTextDocument::documentLink(const Dictionary &p_params) { - Variant ret; +Array GDScriptTextDocument::documentLink(const Dictionary &p_params) { + Array ret; + + lsp::DocumentLinkParams params; + params.load(p_params); + + List<lsp::DocumentLink> links; + GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links); + for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { + ret.push_back(E->get().to_json()); + } return ret; } @@ -326,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 d15022d2c4..235e2c3f6e 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -52,14 +52,16 @@ 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); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); - Variant documentLink(const Dictionary &p_params); + Array documentLink(const Dictionary &p_params); Array colorPresentation(const Dictionary &p_params); Variant hover(const Dictionary &p_params); Array definition(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 1901daacff..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,33 @@ 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(); + for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { + r_list.push_back(E->get()); + } + } +} + Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) { Dictionary api; if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index adce169d4b..a416ae1075 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -53,7 +53,6 @@ protected: ExtendGDScriptParser *get_parse_successed_script(const String &p_path); ExtendGDScriptParser *get_parse_result(const String &p_path); - void strip_flat_symbols(const String &p_branch); void list_script_files(const String &p_root_dir, List<String> &r_files); public: @@ -81,7 +80,8 @@ 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); GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 3e57b6ee7e..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. */ @@ -199,6 +204,41 @@ struct TextDocumentPositionParams { } }; +struct DocumentLinkParams { + /** + * The document to provide document links for. + */ + TextDocumentIdentifier textDocument; + + _FORCE_INLINE_ void load(const Dictionary &p_params) { + textDocument.load(p_params["textDocument"]); + } +}; + +/** + * A document link is a range in a text document that links to an internal or external resource, like another + * text document or a web site. + */ +struct DocumentLink { + + /** + * The range this link applies to. + */ + Range range; + + /** + * The uri this link points to. If missing a resolve request is sent later. + */ + DocumentUri target; + + Dictionary to_json() const { + Dictionary dict; + dict["range"] = range.to_json(); + dict["target"] = target; + return dict; + } +}; + /** * A textual edit applicable to a text document. */ @@ -247,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. @@ -453,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; } }; @@ -557,6 +600,7 @@ struct TextDocumentContentChangeEvent { } }; +// Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** * Reports an error. @@ -657,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`. @@ -721,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. */ @@ -752,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. @@ -944,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 /** @@ -1099,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; @@ -1107,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; @@ -1142,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 + ")"; @@ -1194,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 */ @@ -1254,6 +1321,7 @@ struct FoldingRange { } }; +// Use namespace instead of enumeration to follow the LSP specifications /** * How a completion was triggered */ @@ -1501,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/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index e1bba60f2f..ea90cce83d 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -47,11 +47,11 @@ void JSONRPC::_bind_methods() { ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification); ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant())); - BIND_ENUM_CONSTANT(PARSE_ERROR) - BIND_ENUM_CONSTANT(INVALID_REQUEST) - BIND_ENUM_CONSTANT(METHOD_NOT_FOUND) - BIND_ENUM_CONSTANT(INVALID_PARAMS) - BIND_ENUM_CONSTANT(INTERNAL_ERROR) + BIND_ENUM_CONSTANT(PARSE_ERROR); + BIND_ENUM_CONSTANT(INVALID_REQUEST); + BIND_ENUM_CONSTANT(METHOD_NOT_FOUND); + BIND_ENUM_CONSTANT(INVALID_PARAMS); + BIND_ENUM_CONSTANT(INTERNAL_ERROR); } Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const { 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/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 4c1e47ecad..eb2c2dd77c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -34,7 +34,7 @@ namespace GodotTools.Build if (_msbuildToolsPath.Empty()) { - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'."); } } @@ -142,7 +142,7 @@ namespace GodotTools.Build int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, blocking: true, output: (Godot.Collections.Array) outputArray); - if (exitCode == 0) + if (exitCode != 0) return string.Empty; if (outputArray.Count == 0) 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/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 28f098d323..28cab2ab61 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -30,7 +30,7 @@ #include "bindings_generator.h" -#ifdef DEBUG_METHODS_ENABLED +#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) #include "core/engine.h" #include "core/global_constants.h" diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 26718f1d11..8f3676940b 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -36,7 +36,7 @@ #include "editor/doc/doc_data.h" #include "editor/editor_help.h" -#ifdef DEBUG_METHODS_ENABLED +#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) #include "core/ustring.h" 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 b2e1deca01..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; } @@ -430,8 +430,8 @@ void GDMono::_register_internal_calls() { } void GDMono::_initialize_and_check_api_hashes() { - #ifdef MONO_GLUE_ENABLED +#ifdef DEBUG_METHODS_ENABLED if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { ERR_PRINT("Mono: Core API hash mismatch."); } @@ -441,6 +441,7 @@ void GDMono::_initialize_and_check_api_hashes() { ERR_PRINT("Mono: Editor API hash mismatch."); } #endif // TOOLS_ENABLED +#endif // DEBUG_METHODS_ENABLED #endif // MONO_GLUE_ENABLED } @@ -522,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; @@ -548,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() { @@ -566,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)) { @@ -589,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; + } - if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + return true; +} - 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 + "'."); +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 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 + "'."); + if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path)) + return false; - Error err = da->copy(assembly_src, assembly_dst); + String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); - if (err != OK) { - ERR_PRINTS("Failed to copy '" + assembly_file + "'."); - return false; - } + if (!FileAccess::exists(cached_api_hash_path)) + 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) \ ( \ @@ -628,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; - const int CONFIGS_LEN = 2; - String configs[CONFIGS_LEN] = { String("Debug"), String("Release") }; + 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); + } + } - for (int i = 0; i < CONFIGS_LEN; i++) { - String config = configs[i]; + // Note: Even if only one of the assemblies if missing or out of sync, we update both - print_verbose("Updating '" + config + "' API assemblies"); + if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) + return String(); // No update needed - 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"); + print_verbose("Updating '" + p_config + "' API assemblies"); - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); - } + 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"); - // 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); - } + 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(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 @@ -675,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."); @@ -779,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 @@ -787,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 } @@ -795,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 @@ -851,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 } @@ -870,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."); @@ -902,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 @@ -1075,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 4f7d3791f7..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; + LoadedApiAssembly core_api_assembly; + LoadedApiAssembly editor_api_assembly; - UnhandledExceptionPolicy unhandled_exception_policy; - - 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; @@ -151,6 +167,7 @@ protected: static GDMono *singleton; public: +#ifdef DEBUG_METHODS_ENABLED uint64_t get_api_core_hash() { if (api_core_hash == 0) api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE); @@ -162,11 +179,24 @@ public: api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR); return api_editor_hash; } +#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; } @@ -186,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/config.py b/modules/visual_script/config.py index 04e1a40b81..087a13a200 100644 --- a/modules/visual_script/config.py +++ b/modules/visual_script/config.py @@ -11,6 +11,7 @@ def get_doc_classes(): "VisualScriptBuiltinFunc", "VisualScriptClassConstant", "VisualScriptComment", + "VisualScriptComposeArray", "VisualScriptCondition", "VisualScriptConstant", "VisualScriptConstructor", @@ -28,6 +29,7 @@ def get_doc_classes(): "VisualScriptIndexSet", "VisualScriptInputAction", "VisualScriptIterator", + "VisualScriptLists", "VisualScriptLocalVarSet", "VisualScriptLocalVar", "VisualScriptMathConstant", diff --git a/modules/visual_script/doc_classes/VisualScriptComposeArray.xml b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml new file mode 100644 index 0000000000..92efbc51d1 --- /dev/null +++ b/modules/visual_script/doc_classes/VisualScriptComposeArray.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualScriptComposeArray" inherits="VisualScriptLists" category="Core" version="3.2"> + <brief_description> + A Visual Script Node used to create array from a list of items. + </brief_description> + <description> + A Visual Script Node used to compose array from the list of elements provided with custom in-graph UI hard coded in the VisualScript Editor. + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/visual_script/doc_classes/VisualScriptLists.xml b/modules/visual_script/doc_classes/VisualScriptLists.xml new file mode 100644 index 0000000000..8cf3eb1d38 --- /dev/null +++ b/modules/visual_script/doc_classes/VisualScriptLists.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualScriptLists" inherits="VisualScriptNode" category="Core" version="3.2"> + <brief_description> + A Visual Script virtual class for in-graph editable nodes. + </brief_description> + <description> + A Visual Script virtual class that defines the shape and the default behaviour of the nodes that have to be in-graph editable nodes. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_input_data_port"> + <return type="void"> + </return> + <argument index="0" name="type" type="int" enum="Variant.Type"> + </argument> + <argument index="1" name="name" type="String"> + </argument> + <argument index="2" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="add_output_data_port"> + <return type="void"> + </return> + <argument index="0" name="type" type="int" enum="Variant.Type"> + </argument> + <argument index="1" name="name" type="String"> + </argument> + <argument index="2" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="remove_input_data_port"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="remove_output_data_port"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <description> + </description> + </method> + <method name="set_input_data_port_name"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="name" type="String"> + </argument> + <description> + </description> + </method> + <method name="set_input_data_port_type"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="type" type="int" enum="Variant.Type"> + </argument> + <description> + </description> + </method> + <method name="set_output_data_port_name"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="name" type="String"> + </argument> + <description> + </description> + </method> + <method name="set_output_data_port_type"> + <return type="void"> + </return> + <argument index="0" name="index" type="int"> + </argument> + <argument index="1" name="type" type="int" enum="Variant.Type"> + </argument> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp index 24b96223d7..49272345fe 100644 --- a/modules/visual_script/register_types.cpp +++ b/modules/visual_script/register_types.cpp @@ -56,6 +56,8 @@ void register_visual_script_types() { ClassDB::register_virtual_class<VisualScriptNode>(); ClassDB::register_class<VisualScriptFunctionState>(); ClassDB::register_class<VisualScriptFunction>(); + ClassDB::register_virtual_class<VisualScriptLists>(); + ClassDB::register_class<VisualScriptComposeArray>(); ClassDB::register_class<VisualScriptOperator>(); ClassDB::register_class<VisualScriptVariableSet>(); ClassDB::register_class<VisualScriptVariableGet>(); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 6bed1742eb..0cacd0f0b5 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1014,17 +1014,16 @@ void VisualScript::get_script_method_list(List<MethodInfo> *p_list) const { Ref<VisualScriptFunction> func = E->get().nodes[E->get().function_id].node; if (func.is_valid()) { - for (int i = 0; i < func->get_argument_count(); i++) { PropertyInfo arg; arg.name = func->get_argument_name(i); arg.type = func->get_argument_type(i); mi.arguments.push_back(arg); } + + p_list->push_back(mi); } } - - p_list->push_back(mi); } } @@ -1137,6 +1136,9 @@ void VisualScript::_set_data(const Dictionary &p_data) { Array funcs = d["functions"]; functions.clear(); + Vector2 last_pos = Vector2(-100 * funcs.size(), -100 * funcs.size()); // this is the center of the last fn box + Vector2 last_size = Vector2(0.0, 0.0); + for (int i = 0; i < funcs.size(); i++) { Dictionary func = funcs[i]; @@ -1149,11 +1151,42 @@ void VisualScript::_set_data(const Dictionary &p_data) { Array nodes = func["nodes"]; - for (int j = 0; j < nodes.size(); j += 3) { + if (!d.has("vs_unify") && nodes.size() > 0) { + Vector2 top_left = nodes[1]; + Vector2 bottom_right = nodes[1]; - add_node(name, nodes[j], nodes[j + 2], nodes[j + 1]); - } + for (int j = 0; j < nodes.size(); j += 3) { + Point2 pos = nodes[j + 1]; + if (pos.y > top_left.y) { + top_left.y = pos.y; + } + if (pos.y < bottom_right.y) { + bottom_right.y = pos.y; + } + if (pos.x > bottom_right.x) { + bottom_right.x = pos.x; + } + if (pos.x < top_left.x) { + top_left.x = pos.x; + } + } + + Vector2 size = Vector2(bottom_right.x - top_left.x, top_left.y - bottom_right.y); + + Vector2 offset = last_pos + (last_size / 2.0) + (size / 2.0); // dunno I might just keep it in one axis but diagonal feels better.... + last_pos = offset; + last_size = size; + + for (int j = 0; j < nodes.size(); j += 3) { + add_node(name, nodes[j], nodes[j + 2], offset + nodes[j + 1]); // also add an additional buffer if you want to + } + + } else { + for (int j = 0; j < nodes.size(); j += 3) { + add_node(name, nodes[j], nodes[j + 2], nodes[j + 1]); + } + } Array sequence_connections = func["sequence_connections"]; for (int j = 0; j < sequence_connections.size(); j += 3) { @@ -1254,8 +1287,8 @@ Dictionary VisualScript::_get_data() const { } d["functions"] = funcs; - d["is_tool_script"] = is_tool_script; + d["vs_unify"] = true; return d; } @@ -1330,6 +1363,10 @@ VisualScript::VisualScript() { base_type = "Object"; } +StringName VisualScript::get_default_func() const { + return StringName("f_312843592"); +} + Set<int> VisualScript::get_output_sequence_ports_connected(const String &edited_func, int from_node) { List<VisualScript::SequenceConnection> *sc = memnew(List<VisualScript::SequenceConnection>); get_sequence_connection_list(edited_func, sc); @@ -1403,6 +1440,10 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const { for (const Map<StringName, VisualScript::Function>::Element *E = script->functions.front(); E; E = E->next()) { + if (E->key() == script->get_default_func()) { + continue; + } + MethodInfo mi; mi.name = E->key(); if (E->get().function_id >= 0 && E->get().nodes.has(E->get().function_id)) { @@ -1421,8 +1462,6 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const { if (!vsf->is_sequenced()) { //assumed constant if not sequenced mi.flags |= METHOD_FLAG_CONST; } - - //vsf->Get_ for now at least it does not return.. } } @@ -1431,6 +1470,9 @@ void VisualScriptInstance::get_method_list(List<MethodInfo> *p_list) const { } bool VisualScriptInstance::has_method(const StringName &p_method) const { + if (p_method == script->get_default_func()) + return false; + return script->functions.has(p_method); } @@ -2002,6 +2044,9 @@ Ref<Script> VisualScriptInstance::get_script() const { MultiplayerAPI::RPCMode VisualScriptInstance::get_rpc_mode(const StringName &p_method) const { + if (p_method == script->get_default_func()) + return MultiplayerAPI::RPC_MODE_DISABLED; + const Map<StringName, VisualScript::Function>::Element *E = script->functions.find(p_method); if (!E) { return MultiplayerAPI::RPC_MODE_DISABLED; @@ -2050,11 +2095,14 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o for (const Map<StringName, VisualScript::Variable>::Element *E = script->variables.front(); E; E = E->next()) { variables[E->key()] = E->get().default_value; - //no hacer que todo exporte, solo las que queres! } for (const Map<StringName, VisualScript::Function>::Element *E = script->functions.front(); E; E = E->next()) { + if (E->key() == script->get_default_func()) { + continue; + } + Function function; function.node = E->get().function_id; function.max_stack = 0; @@ -2091,6 +2139,7 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o for (const Map<int, VisualScript::Function::NodeData>::Element *F = E->get().nodes.front(); F; F = F->next()) { Ref<VisualScriptNode> node = F->get().node; + VisualScriptNodeInstance *instance = node->instance(this); //create instance ERR_FAIL_COND(!instance); diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 14927c4363..a035f6d42d 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -239,6 +239,7 @@ private: PropertyInfo info; Variant default_value; bool _export; + // add getter & setter options here }; Map<StringName, Function> functions; @@ -267,6 +268,8 @@ protected: static void _bind_methods(); public: + // TODO: Remove it in future when breaking changes are acceptable + StringName get_default_func() const; void add_function(const StringName &p_name); bool has_function(const StringName &p_name) const; void remove_function(const StringName &p_name); diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 7262dde359..ca0d77f047 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -421,31 +421,42 @@ void VisualScriptEditor::_update_graph_connections() { graph->clear_connections(); - List<VisualScript::SequenceConnection> sequence_conns; - script->get_sequence_connection_list(edited_func, &sequence_conns); - - for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { + List<StringName> funcs; + script->get_function_list(&funcs); - graph->connect_node(itos(E->get().from_node), E->get().from_output, itos(E->get().to_node), 0); + if (funcs.size() <= 0) { + updating_graph = false; + return; } - List<VisualScript::DataConnection> data_conns; - script->get_data_connection_list(edited_func, &data_conns); - - for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) { + for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) { - VisualScript::DataConnection dc = E->get(); + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(F->get(), &sequence_conns); - Ref<VisualScriptNode> from_node = script->get_node(edited_func, E->get().from_node); - Ref<VisualScriptNode> to_node = script->get_node(edited_func, E->get().to_node); + for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { - if (to_node->has_input_sequence_port()) { - dc.to_port++; + graph->connect_node(itos(E->get().from_node), E->get().from_output, itos(E->get().to_node), 0); } - dc.from_port += from_node->get_output_sequence_port_count(); + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(F->get(), &data_conns); - graph->connect_node(itos(E->get().from_node), dc.from_port, itos(E->get().to_node), dc.to_port); + for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) { + + VisualScript::DataConnection dc = E->get(); + + Ref<VisualScriptNode> from_node = script->get_node(F->get(), E->get().from_node); + Ref<VisualScriptNode> to_node = script->get_node(F->get(), E->get().to_node); + + if (to_node->has_input_sequence_port()) { + dc.to_port++; + } + + dc.from_port += from_node->get_output_sequence_port_count(); + + graph->connect_node(itos(E->get().from_node), dc.from_port, itos(E->get().to_node), dc.to_port); + } } } @@ -474,7 +485,10 @@ void VisualScriptEditor::_update_graph(int p_only_id) { } } - if (!script->has_function(edited_func)) { + List<StringName> funcs; + script->get_function_list(&funcs); + + if (funcs.size() <= 0) { graph->hide(); select_func_text->show(); updating_graph = false; @@ -516,254 +530,390 @@ void VisualScriptEditor::_update_graph(int p_only_id) { Ref<Texture> seq_port = Control::get_icon("VisualShaderPort", "EditorIcons"); - List<int> ids; - script->get_node_list(edited_func, &ids); - StringName editor_icons = "EditorIcons"; + for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) { // loop through all the functions - for (List<int>::Element *E = ids.front(); E; E = E->next()) { + List<int> ids; + script->get_node_list(F->get(), &ids); + StringName editor_icons = "EditorIcons"; - if (p_only_id >= 0 && p_only_id != E->get()) - continue; + for (List<int>::Element *E = ids.front(); E; E = E->next()) { - Ref<VisualScriptNode> node = script->get_node(edited_func, E->get()); - Vector2 pos = script->get_node_position(edited_func, E->get()); + if (p_only_id >= 0 && p_only_id != E->get()) + continue; - GraphNode *gnode = memnew(GraphNode); - gnode->set_title(node->get_caption()); - gnode->set_offset(pos * EDSCALE); - if (error_line == E->get()) { - gnode->set_overlay(GraphNode::OVERLAY_POSITION); - } else if (node->is_breakpoint()) { - gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); - } + Ref<VisualScriptNode> node = script->get_node(F->get(), E->get()); + Vector2 pos = script->get_node_position(F->get(), E->get()); - gnode->set_meta("__vnode", node); - gnode->set_name(itos(E->get())); - gnode->connect("dragged", this, "_node_moved", varray(E->get())); - gnode->connect("close_request", this, "_remove_node", varray(E->get()), CONNECT_DEFERRED); + GraphNode *gnode = memnew(GraphNode); + gnode->set_title(node->get_caption()); + gnode->set_offset(pos * EDSCALE); + if (error_line == E->get()) { + gnode->set_overlay(GraphNode::OVERLAY_POSITION); + } else if (node->is_breakpoint()) { + gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); + } - if (E->get() != script->get_function_node_id(edited_func)) { - //function can't be erased - gnode->set_show_close_button(true); - } + gnode->set_meta("__vnode", node); + gnode->set_name(itos(E->get())); + gnode->connect("dragged", this, "_node_moved", varray(E->get())); + gnode->connect("close_request", this, "_remove_node", varray(E->get()), CONNECT_DEFERRED); - bool has_gnode_text = false; + if (E->get() != script->get_function_node_id(F->get())) { + //function can't be erased + gnode->set_show_close_button(true); + } - if (Object::cast_to<VisualScriptExpression>(node.ptr())) { - has_gnode_text = true; - LineEdit *line_edit = memnew(LineEdit); - line_edit->set_text(node->get_text()); - line_edit->set_expand_to_text_length(true); - line_edit->add_font_override("font", get_font("source", "EditorFonts")); - gnode->add_child(line_edit); - line_edit->connect("text_changed", this, "_expression_text_changed", varray(E->get())); - } else { - String text = node->get_text(); - if (!text.empty()) { + bool has_gnode_text = false; + + Ref<VisualScriptLists> nd_list = node; + bool is_vslist = nd_list.is_valid(); + if (is_vslist) { + HBoxContainer *hbnc = memnew(HBoxContainer); + if (nd_list->is_input_port_editable()) { + has_gnode_text = true; + Button *btn = memnew(Button); + btn->set_text("Add Input Port"); + hbnc->add_child(btn); + btn->connect("pressed", this, "_add_input_port", varray(E->get())); + } + if (nd_list->is_output_port_editable()) { + if (nd_list->is_input_port_editable()) + hbnc->add_spacer(); + has_gnode_text = true; + Button *btn = memnew(Button); + btn->set_text("Add Output Port"); + hbnc->add_child(btn); + btn->connect("pressed", this, "_add_output_port", varray(E->get())); + } + gnode->add_child(hbnc); + } else if (Object::cast_to<VisualScriptExpression>(node.ptr())) { has_gnode_text = true; - Label *label = memnew(Label); - label->set_text(text); - gnode->add_child(label); + LineEdit *line_edit = memnew(LineEdit); + line_edit->set_text(node->get_text()); + line_edit->set_expand_to_text_length(true); + line_edit->add_font_override("font", get_font("source", "EditorFonts")); + gnode->add_child(line_edit); + line_edit->connect("text_changed", this, "_expression_text_changed", varray(E->get())); + } else { + String text = node->get_text(); + if (!text.empty()) { + has_gnode_text = true; + Label *label = memnew(Label); + label->set_text(text); + gnode->add_child(label); + } } - } - - if (Object::cast_to<VisualScriptComment>(node.ptr())) { - Ref<VisualScriptComment> vsc = node; - gnode->set_comment(true); - gnode->set_resizable(true); - gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE); - gnode->connect("resize_request", this, "_comment_node_resized", varray(E->get())); - } - if (node_styles.has(node->get_category())) { - Ref<StyleBoxFlat> sbf = node_styles[node->get_category()]; - if (gnode->is_comment()) - sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox("comment", "GraphNode"); - - Color c = sbf->get_border_color(); - c.a = 1; - if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) { - Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); - mono_color.a = 0.85; - c = mono_color; + if (Object::cast_to<VisualScriptComment>(node.ptr())) { + Ref<VisualScriptComment> vsc = node; + gnode->set_comment(true); + gnode->set_resizable(true); + gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE); + gnode->connect("resize_request", this, "_comment_node_resized", varray(E->get())); } - gnode->add_color_override("title_color", c); - c.a = 0.7; - gnode->add_color_override("close_color", c); - gnode->add_color_override("resizer_color", c); - gnode->add_style_override("frame", sbf); - } - - const Color mono_color = get_color("mono_color", "Editor"); - - int slot_idx = 0; - - bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String(); - if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) { - // IF has_gnode_text is true BUT we have no sequence ports to draw (in here), - // we still draw the disabled default ones to shift up the slots by one, - // so the slots DON'T start with the content text. - - // IF has_gnode_text is false, but we DO want to draw default sequence ports, - // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly. - if (!has_gnode_text) { - Label *dummy = memnew(Label); - dummy->set_text(" "); - gnode->add_child(dummy); + if (node_styles.has(node->get_category())) { + Ref<StyleBoxFlat> sbf = node_styles[node->get_category()]; + if (gnode->is_comment()) + sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox("comment", "GraphNode"); + + Color c = sbf->get_border_color(); + c.a = 1; + if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) { + Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); + mono_color.a = 0.85; + c = mono_color; + } + gnode->add_color_override("title_color", c); + c.a = 0.7; + gnode->add_color_override("close_color", c); + gnode->add_color_override("resizer_color", c); + gnode->add_style_override("frame", sbf); } - gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port); - slot_idx++; - } - int mixed_seq_ports = 0; + const Color mono_color = get_color("mono_color", "Editor"); - if (!single_seq_output) { + int slot_idx = 0; - if (node->has_mixed_input_and_sequence_ports()) { - mixed_seq_ports = node->get_output_sequence_port_count(); - } else { - for (int i = 0; i < node->get_output_sequence_port_count(); i++) { + bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String(); + if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) { + // IF has_gnode_text is true BUT we have no sequence ports to draw (in here), + // we still draw the disabled default ones to shift up the slots by one, + // so the slots DON'T start with the content text. - Label *text2 = memnew(Label); - text2->set_text(node->get_output_sequence_port_text(i)); - text2->set_align(Label::ALIGN_RIGHT); - gnode->add_child(text2); - gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port); - slot_idx++; + // IF has_gnode_text is false, but we DO want to draw default sequence ports, + // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly. + if (!has_gnode_text) { + Label *dummy = memnew(Label); + dummy->set_text(" "); + gnode->add_child(dummy); } + gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port); + slot_idx++; } - } - for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) { + int mixed_seq_ports = 0; - bool left_ok = false; - Variant::Type left_type = Variant::NIL; - String left_name; + if (!single_seq_output) { - if (i < node->get_input_value_port_count()) { - PropertyInfo pi = node->get_input_value_port_info(i); - left_ok = true; - left_type = pi.type; - left_name = pi.name; + if (node->has_mixed_input_and_sequence_ports()) { + mixed_seq_ports = node->get_output_sequence_port_count(); + } else { + for (int i = 0; i < node->get_output_sequence_port_count(); i++) { + + Label *text2 = memnew(Label); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + gnode->add_child(text2); + gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port); + slot_idx++; + } + } } - bool right_ok = false; - Variant::Type right_type = Variant::NIL; - String right_name; + for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) { - if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) { - PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports); - right_ok = true; - right_type = pi.type; - right_name = pi.name; - } + bool left_ok = false; + Variant::Type left_type = Variant::NIL; + String left_name; - HBoxContainer *hbc = memnew(HBoxContainer); + if (i < node->get_input_value_port_count()) { + PropertyInfo pi = node->get_input_value_port_info(i); + left_ok = true; + left_type = pi.type; + left_name = pi.name; + } - if (left_ok) { + bool right_ok = false; + Variant::Type right_type = Variant::NIL; + String right_name; - Ref<Texture> t; - if (left_type >= 0 && left_type < Variant::VARIANT_MAX) { - t = type_icons[left_type]; - } - if (t.is_valid()) { - TextureRect *tf = memnew(TextureRect); - tf->set_texture(t); - tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); - hbc->add_child(tf); + if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) { + PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports); + right_ok = true; + right_type = pi.type; + right_name = pi.name; } + VBoxContainer *vbc = memnew(VBoxContainer); + HBoxContainer *hbc = memnew(HBoxContainer); + HBoxContainer *hbc2 = memnew(HBoxContainer); + vbc->add_child(hbc); + vbc->add_child(hbc2); + if (left_ok) { + + Ref<Texture> t; + if (left_type >= 0 && left_type < Variant::VARIANT_MAX) { + t = type_icons[left_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } - hbc->add_child(memnew(Label(left_name))); + if (is_vslist) { + if (nd_list->is_input_port_name_editable()) { + LineEdit *name_box = memnew(LineEdit); + hbc->add_child(name_box); + name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); + name_box->set_text(left_name); + name_box->set_expand_to_text_length(true); + name_box->connect("resized", this, "_update_node_size", varray(E->get())); + name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, E->get(), i, true)); + } else { + hbc->add_child(memnew(Label(left_name))); + } - if (left_type != Variant::NIL && !script->is_input_value_port_connected(edited_func, E->get(), i)) { + if (nd_list->is_input_port_type_editable()) { + OptionButton *opbtn = memnew(OptionButton); + for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) { + opbtn->add_item(Variant::get_type_name(Variant::Type(j))); + } + opbtn->select(left_type); + opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + hbc->add_child(opbtn); + opbtn->connect("item_selected", this, "_change_port_type", varray(E->get(), i, true), CONNECT_DEFERRED); + } - PropertyInfo pi = node->get_input_value_port_info(i); - Button *button = memnew(Button); - Variant value = node->get_default_input_value(i); - if (value.get_type() != left_type) { - //different type? for now convert - //not the same, reconvert - Variant::CallError ce; - const Variant *existingp = &value; - value = Variant::construct(left_type, &existingp, 1, ce, false); + Button *rmbtn = memnew(Button); + rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons")); + hbc->add_child(rmbtn); + rmbtn->connect("pressed", this, "_remove_input_port", varray(E->get(), i), CONNECT_DEFERRED); + } else { + hbc->add_child(memnew(Label(left_name))); } - if (left_type == Variant::COLOR) { - button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); - button->connect("draw", this, "_draw_color_over_button", varray(button, value)); - } else if (left_type == Variant::OBJECT && Ref<Resource>(value).is_valid()) { + if (left_type != Variant::NIL && !script->is_input_value_port_connected(F->get(), E->get(), i)) { + + PropertyInfo pi = node->get_input_value_port_info(i); + Button *button = memnew(Button); + Variant value = node->get_default_input_value(i); + if (value.get_type() != left_type) { + //different type? for now convert + //not the same, reconvert + Variant::CallError ce; + const Variant *existingp = &value; + value = Variant::construct(left_type, &existingp, 1, ce, false); + } - Ref<Resource> res = value; - Array arr; - arr.push_back(button->get_instance_id()); - arr.push_back(String(value)); - EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr); + if (left_type == Variant::COLOR) { + button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + button->connect("draw", this, "_draw_color_over_button", varray(button, value)); + } else if (left_type == Variant::OBJECT && Ref<Resource>(value).is_valid()) { - } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) { + Ref<Resource> res = value; + Array arr; + arr.push_back(button->get_instance_id()); + arr.push_back(String(value)); + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr); - button->set_text(pi.hint_string.get_slice(",", value)); - } else { + } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) { + + button->set_text(pi.hint_string.get_slice(",", value)); + } else { - button->set_text(value); + button->set_text(value); + } + button->connect("pressed", this, "_default_value_edited", varray(button, E->get(), i)); + hbc2->add_child(button); } - button->connect("pressed", this, "_default_value_edited", varray(button, E->get(), i)); - hbc->add_child(button); + } else { + Control *c = memnew(Control); + c->set_custom_minimum_size(Size2(10, 0) * EDSCALE); + hbc->add_child(c); } - } else { - Control *c = memnew(Control); - c->set_custom_minimum_size(Size2(10, 0) * EDSCALE); - hbc->add_child(c); - } - hbc->add_spacer(); + hbc->add_spacer(); + hbc2->add_spacer(); - if (i < mixed_seq_ports) { + if (i < mixed_seq_ports) { - Label *text2 = memnew(Label); - text2->set_text(node->get_output_sequence_port_text(i)); - text2->set_align(Label::ALIGN_RIGHT); - hbc->add_child(text2); - } + Label *text2 = memnew(Label); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + hbc->add_child(text2); + } - if (right_ok) { + if (right_ok) { + + if (is_vslist) { + Button *rmbtn = memnew(Button); + rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons")); + hbc->add_child(rmbtn); + rmbtn->connect("pressed", this, "_remove_output_port", varray(E->get(), i), CONNECT_DEFERRED); + + if (nd_list->is_output_port_type_editable()) { + OptionButton *opbtn = memnew(OptionButton); + for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) { + opbtn->add_item(Variant::get_type_name(Variant::Type(j))); + } + opbtn->select(right_type); + opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + hbc->add_child(opbtn); + opbtn->connect("item_selected", this, "_change_port_type", varray(E->get(), i, false), CONNECT_DEFERRED); + } - hbc->add_child(memnew(Label(right_name))); + if (nd_list->is_output_port_name_editable()) { + LineEdit *name_box = memnew(LineEdit); + hbc->add_child(name_box); + name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); + name_box->set_text(right_name); + name_box->set_expand_to_text_length(true); + name_box->connect("resized", this, "_update_node_size", varray(E->get())); + name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, E->get(), i, false)); + } else { + hbc->add_child(memnew(Label(right_name))); + } + } else { + hbc->add_child(memnew(Label(right_name))); + } - Ref<Texture> t; - if (right_type >= 0 && right_type < Variant::VARIANT_MAX) { - t = type_icons[right_type]; + Ref<Texture> t; + if (right_type >= 0 && right_type < Variant::VARIANT_MAX) { + t = type_icons[right_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } } - if (t.is_valid()) { - TextureRect *tf = memnew(TextureRect); - tf->set_texture(t); - tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); - hbc->add_child(tf); + + gnode->add_child(vbc); + + bool dark_theme = get_constant("dark_theme", "Editor"); + if (i < mixed_seq_ports) { + gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref<Texture>(), seq_port); + } else { + gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme)); } + + slot_idx++; } - gnode->add_child(hbc); + graph->add_child(gnode); - bool dark_theme = get_constant("dark_theme", "Editor"); - if (i < mixed_seq_ports) { - gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref<Texture>(), seq_port); - } else { - gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme)); + if (gnode->is_comment()) { + graph->move_child(gnode, 0); } - - slot_idx++; } + } + _update_graph_connections(); + // use default_func instead of default_func for now I think that should be good stop gap solution to ensure not breaking anything + graph->call_deferred("set_scroll_ofs", script->get_function_scroll(default_func) * EDSCALE); + updating_graph = false; +} - graph->add_child(gnode); +void VisualScriptEditor::_change_port_type(int p_select, int p_id, int p_port, bool is_input) { - if (gnode->is_comment()) { - graph->move_child(gnode, 0); - } + StringName func = _get_function_of_node(p_id); + + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) + return; + + undo_redo->create_action("Change Port Type"); + if (is_input) { + undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_type", p_port, Variant::Type(p_select)); + undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_type", p_port, vsn->get_input_value_port_info(p_port).type); + } else { + undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_type", p_port, Variant::Type(p_select)); + undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_type", p_port, vsn->get_output_value_port_info(p_port).type); } + undo_redo->commit_action(); +} - _update_graph_connections(); - graph->call_deferred("set_scroll_ofs", script->get_function_scroll(edited_func) * EDSCALE); //may need to adapt a bit, let it do so - updating_graph = false; +void VisualScriptEditor::_update_node_size(int p_id) { + + Node *node = graph->get_node(itos(p_id)); + if (Object::cast_to<Control>(node)) + Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller +} +void VisualScriptEditor::_port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input) { + StringName func = _get_function_of_node(p_id); + + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) + return; + + String text; + + if (Object::cast_to<LineEdit>(p_name_box)) + text = Object::cast_to<LineEdit>(p_name_box)->get_text(); + else + return; + + undo_redo->create_action("Change Port Name"); + if (is_input) { + undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_name", p_port, text); + undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_name", p_port, vsn->get_input_value_port_info(p_port).name); + } else { + undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_name", p_port, text); + undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_name", p_port, vsn->get_output_value_port_info(p_port).name); + } + undo_redo->commit_action(); } void VisualScriptEditor::_update_members() { @@ -784,11 +934,16 @@ void VisualScriptEditor::_update_members() { List<StringName> func_names; script->get_function_list(&func_names); for (List<StringName>::Element *E = func_names.front(); E; E = E->next()) { + + if (E->get() == default_func) { + continue; + } + TreeItem *ti = members->create_item(functions); ti->set_text(0, E->get()); ti->set_selectable(0, true); - ti->set_editable(0, true); ti->set_metadata(0, E->get()); + ti->add_button(0, Control::get_icon("Edit", "EditorIcons"), 0); if (selected == E->get()) ti->select(0); } @@ -888,15 +1043,15 @@ void VisualScriptEditor::_member_selected() { if (ti->get_parent() == members->get_root()->get_children()) { - if (edited_func != selected) { - - revert_on_drag = edited_func; - edited_func = selected; - _update_members(); - _update_graph(); +#ifdef OSX_ENABLED + bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL); +#endif + if (held_ctrl) { + ERR_FAIL_COND(!script->has_function(selected)); + _center_on_node(selected, script->get_function_node_id(selected)); } - - return; //or crash because it will become invalid } } @@ -936,9 +1091,6 @@ void VisualScriptEditor::_member_edited() { if (ti->get_parent() == root->get_children()) { - if (edited_func == selected) { - edited_func = new_name; - } selected = new_name; int node_id = script->get_function_node_id(name); @@ -950,10 +1102,27 @@ void VisualScriptEditor::_member_edited() { undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name); undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name); if (func.is_valid()) { - undo_redo->add_do_method(func.ptr(), "set_name", new_name); undo_redo->add_undo_method(func.ptr(), "set_name", name); } + + // also fix all function calls + List<StringName> flst; + script->get_function_list(&flst); + for (List<StringName>::Element *E = flst.front(); E; E = E->next()) { + List<int> lst; + script->get_node_list(E->get(), &lst); + for (List<int>::Element *F = lst.front(); F; F = F->next()) { + Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get()); + if (!fncall.is_valid()) + continue; + if (fncall->get_function() == name) { + undo_redo->add_do_method(fncall.ptr(), "set_function", new_name); + undo_redo->add_undo_method(fncall.ptr(), "set_function", name); + } + } + } + undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); undo_redo->add_do_method(this, "_update_graph"); @@ -962,8 +1131,6 @@ void VisualScriptEditor::_member_edited() { undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); undo_redo->commit_action(); - // _update_graph(); - return; //or crash because it will become invalid } @@ -998,6 +1165,112 @@ void VisualScriptEditor::_member_edited() { } } +void VisualScriptEditor::_create_function_dialog() { + function_create_dialog->popup_centered(); + func_name_box->set_text(""); + func_name_box->grab_focus(); + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + Node *nd = func_input_vbox->get_child(i); + nd->queue_delete(); + } +} + +void VisualScriptEditor::_create_function() { + String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text()); + selected = name; + Vector2 ofs = _get_available_pos(); + + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(name); + + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + OptionButton *opbtn = Object::cast_to<OptionButton>(func_input_vbox->get_child(i)->get_child(3)); + LineEdit *lne = Object::cast_to<LineEdit>(func_input_vbox->get_child(i)->get_child(1)); + if (!opbtn || !lne) + continue; + Variant::Type arg_type = Variant::Type(opbtn->get_selected()); + String arg_name = lne->get_text(); + func_node->add_argument(arg_type, arg_name); + } + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(), "add_function", name); + undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_function", name); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + _update_graph(); +} + +void VisualScriptEditor::_add_node_dialog() { + _generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true); +} + +void VisualScriptEditor::_add_func_input() { + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->set_h_size_flags(SIZE_EXPAND_FILL); + + Label *name_label = memnew(Label); + name_label->set_text(TTR("Name:")); + hbox->add_child(name_label); + + LineEdit *name_box = memnew(LineEdit); + name_box->set_h_size_flags(SIZE_EXPAND_FILL); + name_box->set_text("input"); + name_box->connect("focus_entered", this, "_deselect_input_names"); + hbox->add_child(name_box); + + Label *type_label = memnew(Label); + type_label->set_text(TTR("Type:")); + hbox->add_child(type_label); + + OptionButton *type_box = memnew(OptionButton); + type_box->set_custom_minimum_size(Size2(120 * EDSCALE, 0)); + for (int i = Variant::NIL; i < Variant::VARIANT_MAX; i++) + type_box->add_item(Variant::get_type_name(Variant::Type(i))); + type_box->select(1); + hbox->add_child(type_box); + + Button *delete_button = memnew(Button); + delete_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons")); + delete_button->set_tooltip(vformat(TTR("Delete input port"))); + hbox->add_child(delete_button); + + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + LineEdit *line_edit = (LineEdit *)func_input_vbox->get_child(i)->get_child(1); + line_edit->deselect(); + } + + func_input_vbox->add_child(hbox); + hbox->set_meta("id", hbox->get_position_in_parent()); + + delete_button->connect("pressed", this, "_remove_func_input", varray(hbox)); + + name_box->select_all(); + name_box->grab_focus(); +} + +void VisualScriptEditor::_remove_func_input(Node *p_node) { + func_input_vbox->remove_child(p_node); + p_node->queue_delete(); +} + +void VisualScriptEditor::_deselect_input_names() { + int cn = func_input_vbox->get_child_count(); + for (int i = 0; i < cn; i++) { + LineEdit *lne = Object::cast_to<LineEdit>(func_input_vbox->get_child(i)->get_child(1)); + if (lne) + lne->deselect(); + } +} + void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) { TreeItem *ti = Object::cast_to<TreeItem>(p_item); @@ -1010,7 +1283,6 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt //add function, this one uses menu if (p_button == 1) { - new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true); return; @@ -1018,7 +1290,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt String name = _validate_name("new_function"); selected = name; - edited_func = selected; + Vector2 ofs = _get_available_pos(); Ref<VisualScriptFunction> func_node; func_node.instance(); @@ -1026,7 +1298,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt undo_redo->create_action(TTR("Add Function")); undo_redo->add_do_method(script.ptr(), "add_function", name); - undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node); + undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs); undo_redo->add_undo_method(script.ptr(), "remove_function", name); undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); @@ -1073,135 +1345,193 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt undo_redo->commit_action(); return; //or crash because it will become invalid } + } else if (ti->get_parent() == root->get_children()) { + selected = ti->get_text(0); + function_name_edit->set_position(Input::get_singleton()->get_mouse_position() - Vector2(60, -10)); + function_name_edit->popup(); + function_name_box->set_text(selected); + function_name_box->select_all(); } } -void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) { +void VisualScriptEditor::_add_input_port(int p_id) { - Ref<VisualScriptExpression> vse = script->get_node(edited_func, p_id); - if (!vse.is_valid()) + StringName func = _get_function_of_node(p_id); + + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) return; updating_graph = true; - undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_property(vse.ptr(), "expression", p_text); - undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression")); + undo_redo->create_action(TTR("Add Input Port"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsn.ptr(), "add_input_data_port", Variant::NIL, "arg", -1); undo_redo->add_do_method(this, "_update_graph", p_id); + + undo_redo->add_undo_method(vsn.ptr(), "remove_input_data_port", vsn->get_input_value_port_count()); undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + undo_redo->commit_action(); +} - Node *node = graph->get_node(itos(p_id)); - if (Object::cast_to<Control>(node)) - Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller +void VisualScriptEditor::_add_output_port(int p_id) { + + StringName func = _get_function_of_node(p_id); + + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) + return; + + updating_graph = true; + + undo_redo->create_action(TTR("Add Output Port"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsn.ptr(), "add_output_data_port", Variant::NIL, "arg", -1); + undo_redo->add_do_method(this, "_update_graph", p_id); + + undo_redo->add_undo_method(vsn.ptr(), "remove_output_data_port", vsn->get_output_value_port_count()); + undo_redo->add_undo_method(this, "_update_graph", p_id); updating_graph = false; + + undo_redo->commit_action(); } -void VisualScriptEditor::_available_node_doubleclicked() { +void VisualScriptEditor::_remove_input_port(int p_id, int p_port) { + + StringName func = _get_function_of_node(p_id); - if (edited_func == String()) + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) return; - TreeItem *item = nodes->get_selected(); + updating_graph = true; - if (!item) - return; + undo_redo->create_action(TTR("Remove Input Port"), UndoRedo::MERGE_ENDS); + + int conn_from = -1, conn_port = -1; + script->get_input_value_port_connection_source(func, p_id, p_port, &conn_from, &conn_port); + + if (conn_from != -1) + undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_id, p_port); + + undo_redo->add_do_method(vsn.ptr(), "remove_input_data_port", p_port); + undo_redo->add_do_method(this, "_update_graph", p_id); + + if (conn_from != -1) + undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_id, p_port); + + undo_redo->add_undo_method(vsn.ptr(), "add_input_data_port", vsn->get_input_value_port_info(p_port).type, vsn->get_input_value_port_info(p_port).name, p_port); + undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_remove_output_port(int p_id, int p_port) { + + StringName func = _get_function_of_node(p_id); - String which = item->get_metadata(0); - if (which == String()) + Ref<VisualScriptLists> vsn = script->get_node(func, p_id); + if (!vsn.is_valid()) return; - Vector2 ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } + updating_graph = true; - ofs /= EDSCALE; + undo_redo->create_action(TTR("Remove Output Port"), UndoRedo::MERGE_ENDS); - while (true) { - bool exists = false; - List<int> existing; - script->get_node_list(edited_func, &existing); - for (List<int>::Element *E = existing.front(); E; E = E->next()) { - Point2 pos = script->get_node_position(edited_func, E->get()); - if (pos.distance_to(ofs) < 15) { - ofs += Vector2(graph->get_snap(), graph->get_snap()); - exists = true; - break; - } + List<VisualScript::DataConnection> data_connections; + script->get_data_connection_list(func, &data_connections); + + HashMap<int, Set<int> > conn_map; + for (const List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) { + if (E->get().from_node == p_id && E->get().from_port == p_port) { + // push into the connections map + if (!conn_map.has(E->get().to_node)) + conn_map.set(E->get().to_node, Set<int>()); + conn_map[E->get().to_node].insert(E->get().to_port); } + } - if (exists) - continue; - break; + undo_redo->add_do_method(vsn.ptr(), "remove_output_data_port", p_port); + undo_redo->add_do_method(this, "_update_graph", p_id); + + List<int> keys; + conn_map.get_key_list(&keys); + for (const List<int>::Element *E = keys.front(); E; E = E->next()) { + for (const Set<int>::Element *F = conn_map[E->get()].front(); F; F = F->next()) { + undo_redo->add_undo_method(script.ptr(), "data_connect", func, p_id, p_port, E->get(), F->get()); + } } - Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(which); - int new_id = script->get_available_id(); + undo_redo->add_undo_method(vsn.ptr(), "add_output_data_port", vsn->get_output_value_port_info(p_port).type, vsn->get_output_value_port_info(p_port).name, p_port); + undo_redo->add_undo_method(this, "_update_graph", p_id); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); + updating_graph = false; - Node *node = graph->get_node(itos(new_id)); - if (node) { - graph->set_selected(node); - _node_selected(node); - } + undo_redo->commit_action(); } -void VisualScriptEditor::_update_available_nodes() { +void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) { + + StringName func = _get_function_of_node(p_id); - nodes->clear(); + Ref<VisualScriptExpression> vse = script->get_node(func, p_id); + if (!vse.is_valid()) + return; - TreeItem *root = nodes->create_item(); + updating_graph = true; - Map<String, TreeItem *> path_cache; + undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(vse.ptr(), "expression", p_text); + undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression")); + undo_redo->add_do_method(this, "_update_graph", p_id); + undo_redo->add_undo_method(this, "_update_graph", p_id); + undo_redo->commit_action(); - String filter = node_filter->get_text(); + Node *node = graph->get_node(itos(p_id)); + if (Object::cast_to<Control>(node)) + Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller - List<String> fnodes; - VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); + updating_graph = false; +} - for (List<String>::Element *E = fnodes.front(); E; E = E->next()) { +Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const { + if (centered) + ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; - Vector<String> path = E->get().split("/"); + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + ofs = ofs.snapped(Vector2(snap, snap)); + } - if (filter != String() && path.size() && path[path.size() - 1].findn(filter) == -1) - continue; + ofs /= EDSCALE; - String sp; - TreeItem *parent = root; - - for (int i = 0; i < path.size() - 1; i++) { - - if (i > 0) - sp += ","; - sp += path[i]; - if (!path_cache.has(sp)) { - TreeItem *pathn = nodes->create_item(parent); - pathn->set_selectable(0, false); - pathn->set_text(0, path[i].capitalize()); - path_cache[sp] = pathn; - parent = pathn; - if (filter == String()) { - pathn->set_collapsed(true); //should remember state + while (true) { + bool exists = false; + List<StringName> all_fn; + script->get_function_list(&all_fn); + for (List<StringName>::Element *F = all_fn.front(); F; F = F->next()) { + StringName curr_fn = F->get(); + List<int> existing; + script->get_node_list(curr_fn, &existing); + for (List<int>::Element *E = existing.front(); E; E = E->next()) { + Point2 pos = script->get_node_position(curr_fn, E->get()); + if (pos.distance_to(ofs) < 50) { + ofs += Vector2(graph->get_snap(), graph->get_snap()); + exists = true; + break; } - } else { - parent = path_cache[sp]; } } - - TreeItem *item = nodes->create_item(parent); - item->set_text(0, path[path.size() - 1].capitalize()); - item->set_selectable(0, true); - item->set_metadata(0, E->get()); + if (exists) + continue; + break; } + + return ofs; } String VisualScriptEditor::_validate_name(const String &p_name) const { @@ -1227,6 +1557,8 @@ String VisualScriptEditor::_validate_name(const String &p_name) const { void VisualScriptEditor::_on_nodes_delete() { + // delete all the selected nodes + List<int> to_erase; for (int i = 0; i < graph->get_child_count(); i++) { @@ -1245,26 +1577,30 @@ void VisualScriptEditor::_on_nodes_delete() { for (List<int>::Element *F = to_erase.front(); F; F = F->next()) { - undo_redo->add_do_method(script.ptr(), "remove_node", edited_func, F->get()); - undo_redo->add_undo_method(script.ptr(), "add_node", edited_func, F->get(), script->get_node(edited_func, F->get()), script->get_node_position(edited_func, F->get())); + int cr_node = F->get(); + + StringName func = _get_function_of_node(cr_node); + + undo_redo->add_do_method(script.ptr(), "remove_node", func, cr_node); + undo_redo->add_undo_method(script.ptr(), "add_node", func, cr_node, script->get_node(func, cr_node), script->get_node_position(func, cr_node)); List<VisualScript::SequenceConnection> sequence_conns; - script->get_sequence_connection_list(edited_func, &sequence_conns); + script->get_sequence_connection_list(func, &sequence_conns); for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { - if (E->get().from_node == F->get() || E->get().to_node == F->get()) { - undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, E->get().from_node, E->get().from_output, E->get().to_node); + if (E->get().from_node == cr_node || E->get().to_node == cr_node) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node); } } List<VisualScript::DataConnection> data_conns; - script->get_data_connection_list(edited_func, &data_conns); + script->get_data_connection_list(func, &data_conns); for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) { if (E->get().from_node == F->get() || E->get().to_node == F->get()) { - undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); } } } @@ -1276,13 +1612,16 @@ void VisualScriptEditor::_on_nodes_delete() { void VisualScriptEditor::_on_nodes_duplicate() { - List<int> to_duplicate; + Set<int> to_duplicate; + List<StringName> funcs; for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); if (gn) { if (gn->is_selected() && gn->is_close_button_visible()) { - to_duplicate.push_back(gn->get_name().operator String().to_int()); + int id = gn->get_name().operator String().to_int(); + to_duplicate.insert(id); + funcs.push_back(_get_function_of_node(id)); } } } @@ -1294,18 +1633,42 @@ void VisualScriptEditor::_on_nodes_duplicate() { int idc = script->get_available_id() + 1; Set<int> to_select; + HashMap<int, int> remap; - for (List<int>::Element *F = to_duplicate.front(); F; F = F->next()) { + for (Set<int>::Element *F = to_duplicate.front(); F; F = F->next()) { - Ref<VisualScriptNode> node = script->get_node(edited_func, F->get()); + // duplicate from the specifc function but place it into the default func as it would lack the connections + StringName func = _get_function_of_node(F->get()); + Ref<VisualScriptNode> node = script->get_node(func, F->get()); Ref<VisualScriptNode> dupe = node->duplicate(true); int new_id = idc++; + remap.set(F->get(), new_id); + to_select.insert(new_id); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, dupe, script->get_node_position(edited_func, F->get()) + Vector2(20, 20)); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, dupe, script->get_node_position(func, F->get()) + Vector2(20, 20)); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); } + + for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) { + List<VisualScript::SequenceConnection> seqs; + script->get_sequence_connection_list(F->get(), &seqs); + for (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) { + if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); + } + } + + List<VisualScript::DataConnection> data; + script->get_data_connection_list(F->get(), &data); + for (List<VisualScript::DataConnection>::Element *E = data.front(); E; E = E->next()) { + if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) { + undo_redo->add_do_method(script.ptr(), "data_connect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); + } + } + } + undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -1320,22 +1683,45 @@ void VisualScriptEditor::_on_nodes_duplicate() { } if (to_select.size()) { - EditorNode::get_singleton()->push_item(script->get_node(edited_func, to_select.front()->get()).ptr()); + EditorNode::get_singleton()->push_item(script->get_node(default_func, to_select.front()->get()).ptr()); } } -void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) { +void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) { + if (node_centered) + port_action_pos = graph->get_size() / 2.0f; + else + port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); + + new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text + + // ensure that the dialog fits inside the graph + Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); + pos.x = pos.x > bounds.x ? bounds.x : pos.x; + pos.y = pos.y > bounds.y ? bounds.y : pos.y; - Ref<InputEventMouseButton> mb = p_event; + if (pos != Vector2()) + new_connect_node_select->set_position(pos); +} + +void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) { + // GUI input for VS Editor Plugin + Ref<InputEventMouseButton> key = p_event; - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { - revert_on_drag = String(); //so we can still drag functions + if (key.is_valid() && !key->is_pressed()) { + mouse_up_position = Input::get_singleton()->get_mouse_position(); } } -void VisualScriptEditor::_generic_search(String p_base_type) { - port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); - new_connect_node_select->select_from_visual_script(p_base_type, false); +void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> key = p_event; + + if (key.is_valid() && key->is_pressed() && key->get_button_mask() == BUTTON_RIGHT) { + saved_position = graph->get_local_mouse_position(); + + Point2 gpos = Input::get_singleton()->get_mouse_position(); + _generic_search(script->get_instance_base_type(), gpos); + } } void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) { @@ -1365,28 +1751,82 @@ void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) { } } } + + Ref<InputEventMouseButton> btn = p_event; + if (btn.is_valid() && btn->is_doubleclick()) { + TreeItem *ti = members->get_selected(); + if (ti && ti->get_parent() == members->get_root()->get_children()) // to check if it's a function + _center_on_node(ti->get_metadata(0), script->get_function_node_id(ti->get_metadata(0))); + } } -Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { +void VisualScriptEditor::_rename_function(const String &name, const String &new_name) { - if (p_from == nodes) { + if (!new_name.is_valid_identifier()) { - TreeItem *it = nodes->get_item_at_position(p_point); - if (!it) - return Variant(); - String type = it->get_metadata(0); - if (type == String()) - return Variant(); + EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:") + " " + new_name); + return; + } - Dictionary dd; - dd["type"] = "visual_script_node_drag"; - dd["node_type"] = type; + if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) { - Label *label = memnew(Label); - label->set_text(it->get_text(0)); - set_drag_preview(label); - return dd; + EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:") + " " + new_name); + return; + } + + int node_id = script->get_function_node_id(name); + Ref<VisualScriptFunction> func; + if (script->has_node(name, node_id)) { + func = script->get_node(name, node_id); + } + undo_redo->create_action(TTR("Rename Function")); + undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name); + undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name); + if (func.is_valid()) { + undo_redo->add_do_method(func.ptr(), "set_name", new_name); + undo_redo->add_undo_method(func.ptr(), "set_name", name); + } + + // also fix all function calls + List<StringName> flst; + script->get_function_list(&flst); + for (List<StringName>::Element *E = flst.front(); E; E = E->next()) { + List<int> lst; + script->get_node_list(E->get(), &lst); + for (List<int>::Element *F = lst.front(); F; F = F->next()) { + Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get()); + if (!fncall.is_valid()) + continue; + if (fncall->get_function() == name) { + undo_redo->add_do_method(fncall.ptr(), "set_function", new_name); + undo_redo->add_undo_method(fncall.ptr(), "set_function", name); + } + } + } + + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); +} + +void VisualScriptEditor::_fn_name_box_input(const Ref<InputEvent> &p_event) { + + if (!function_name_edit->is_visible()) + return; + + Ref<InputEventKey> key = p_event; + if (key.is_valid() && key->is_pressed() && key->get_scancode() == KEY_ENTER) { + function_name_edit->hide(); + _rename_function(selected, function_name_box->get_text()); + function_name_box->clear(); } +} + +Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (p_from == members) { @@ -1406,11 +1846,6 @@ Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f dd["type"] = "visual_script_function_drag"; dd["function"] = type; - if (revert_on_drag != String()) { - edited_func = revert_on_drag; //revert so function does not change - revert_on_drag = String(); - _update_graph(); - } } else if (it->get_parent() == root->get_children()->get_next()) { dd["type"] = "visual_script_variable_drag"; @@ -1481,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) @@ -1502,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) { @@ -1530,15 +1961,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da ofs /= EDSCALE; - Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(d["node_type"]); - int new_id = script->get_available_id(); - - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); + int new_id = _create_new_node_from_name(d["node_type"], ofs, default_func); Node *node = graph->get_node(itos(new_id)); if (node) { @@ -1579,8 +2002,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -1609,11 +2032,11 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs); undo_redo->add_do_method(vnode.ptr(), "set_base_type", script->get_instance_base_type()); undo_redo->add_do_method(vnode.ptr(), "set_function", d["function"]); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -1642,8 +2065,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -1672,8 +2095,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Preload Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, prnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, prnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -1713,8 +2136,8 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da prnode.instance(); prnode->set_preload(res); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, prnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, prnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); new_ids.push_back(new_id); new_id++; ofs += Vector2(20, 20) * EDSCALE; @@ -1740,7 +2163,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); if (!sn) { - EditorNode::get_singleton()->show_warning("Can't drop nodes because script '" + get_name() + "' is not used in this scene."); + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop nodes because script '%s' is not used in this scene."), get_name())); return; } @@ -1782,20 +2205,20 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da scene_node.instance(); scene_node->set_node_path(sn->get_path_to(node)); n = scene_node; - } else { + // ! Doesn't work properly Ref<VisualScriptFunctionCall> call; call.instance(); call->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH); call->set_base_path(sn->get_path_to(node)); call->set_base_type(node->get_class()); n = call; - method_select->select_from_instance(node); + method_select->select_from_instance(node, "", true, node->get_class()); selecting_method_id = base_id; } - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, n, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, n, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id); base_id++; ofs += Vector2(25, 25); @@ -1810,7 +2233,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); if (!sn && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { - EditorNode::get_singleton()->show_warning("Can't drop properties because script '" + get_name() + "' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."); + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop properties because script '%s' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."), get_name())); return; } @@ -1866,13 +2289,13 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da vnode = pget; } - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, vnode, ofs); undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); if (!use_get) { undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); } - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -1913,12 +2336,12 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } vnode = pget; } - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, base_id, vnode, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, vnode, ofs); undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); if (!use_get) { undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); } - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, base_id); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -1929,7 +2352,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type, const bool p_connecting) { - Ref<VisualScriptFunctionCall> vsfc = script->get_node(edited_func, selecting_method_id); + Ref<VisualScriptFunctionCall> vsfc = script->get_node(default_func, selecting_method_id); if (!vsfc.is_valid()) return; vsfc->set_function(p_method); @@ -1986,8 +2409,16 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) { script->connect("node_ports_changed", this, "_node_ports_changed"); + default_func = script->get_default_func(); + + if (!script->has_function(default_func)) // this is the supposed default function + { + script->add_function(default_func); + script->set_edited(true); //so that if a function was added it's saved + } + + _update_graph(); _update_members(); - _update_available_nodes(); } Vector<String> VisualScriptEditor::get_functions() { @@ -2021,18 +2452,14 @@ 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() { Dictionary d; - d["function"] = edited_func; + d["function"] = default_func; d["scroll"] = graph->get_scroll_ofs(); d["zoom"] = graph->get_zoom(); d["using_snap"] = graph->is_using_snap(); @@ -2044,8 +2471,7 @@ void VisualScriptEditor::set_edit_state(const Variant &p_state) { Dictionary d = p_state; if (d.has("function")) { - edited_func = d["function"]; - selected = edited_func; + selected = default_func; } _update_graph(); @@ -2065,16 +2491,24 @@ void VisualScriptEditor::set_edit_state(const Variant &p_state) { } } -void VisualScriptEditor::_center_on_node(int p_id) { +void VisualScriptEditor::_center_on_node(const StringName &p_func, int p_id) { Node *n = graph->get_node(itos(p_id)); GraphNode *gn = Object::cast_to<GraphNode>(n); + + // clear selection + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gnd = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gnd) + gnd->set_selected(false); + } + if (gn) { gn->set_selected(true); Vector2 new_scroll = gn->get_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5; graph->set_scroll_ofs(new_scroll); - script->set_function_scroll(edited_func, new_scroll / EDSCALE); - script->set_edited(true); //so it's saved + script->set_function_scroll(p_func, new_scroll / EDSCALE); + script->set_edited(true); } } @@ -2091,13 +2525,10 @@ void VisualScriptEditor::goto_line(int p_line, bool p_with_error) { if (script->has_node(E->get(), p_line)) { - edited_func = E->get(); - selected = edited_func; _update_graph(); _update_members(); - call_deferred("call_deferred", "_center_on_node", p_line); //editor might be just created and size might not exist yet - + call_deferred("call_deferred", "_center_on_node", E->get(), p_line); //editor might be just created and size might not exist yet return; } } @@ -2132,6 +2563,7 @@ void VisualScriptEditor::tag_saved_version() { } void VisualScriptEditor::reload(bool p_soft) { + _update_graph(); } void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) { @@ -2155,10 +2587,9 @@ void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) { void VisualScriptEditor::add_callback(const String &p_function, PoolStringArray p_args) { if (script->has_function(p_function)) { - edited_func = p_function; - selected = edited_func; _update_members(); _update_graph(); + _center_on_node(p_function, script->get_function_node_id(p_function)); return; } @@ -2189,13 +2620,10 @@ void VisualScriptEditor::add_callback(const String &p_function, PoolStringArray script->add_function(p_function); script->add_node(p_function, script->get_available_id(), func); - edited_func = p_function; - selected = edited_func; _update_members(); _update_graph(); - graph->call_deferred("set_scroll_ofs", script->get_function_scroll(edited_func)); //for first time it might need to be later - //undo_redo->clear_history(); + _center_on_node(p_function, script->get_function_node_id(p_function)); } bool VisualScriptEditor::show_members_overview() { @@ -2233,7 +2661,7 @@ void VisualScriptEditor::_toggle_tool_script() { void VisualScriptEditor::clear_edit_menu() { memdelete(edit_menu); - memdelete(left_vsplit); + memdelete(members_section); } void VisualScriptEditor::_change_base_type_callback() { @@ -2297,46 +2725,66 @@ void VisualScriptEditor::_end_node_move() { undo_redo->commit_action(); } -void VisualScriptEditor::_move_node(String func, int p_id, const Vector2 &p_to) { +void VisualScriptEditor::_move_node(const StringName &p_func, int p_id, const Vector2 &p_to) { - if (func == String(edited_func)) { - Node *node = graph->get_node(itos(p_id)); - if (Object::cast_to<GraphNode>(node)) - Object::cast_to<GraphNode>(node)->set_offset(p_to); + if (!script->has_function(p_func)) + return; + + Node *node = graph->get_node(itos(p_id)); + + if (Object::cast_to<GraphNode>(node)) + Object::cast_to<GraphNode>(node)->set_offset(p_to); + + script->set_node_position(p_func, p_id, p_to / EDSCALE); +} + +StringName VisualScriptEditor::_get_function_of_node(int p_id) const { + + List<StringName> funcs; + script->get_function_list(&funcs); + for (List<StringName>::Element *E = funcs.front(); E; E = E->next()) { + if (script->has_node(E->get(), p_id)) { + return E->get(); + } } - script->set_node_position(edited_func, p_id, p_to / EDSCALE); + + return ""; // this is passed to avoid crash and is tested against later } void VisualScriptEditor::_node_moved(Vector2 p_from, Vector2 p_to, int p_id) { - undo_redo->add_do_method(this, "_move_node", String(edited_func), p_id, p_to); - undo_redo->add_undo_method(this, "_move_node", String(edited_func), p_id, p_from); + StringName func = _get_function_of_node(p_id); + + undo_redo->add_do_method(this, "_move_node", func, p_id, p_to); + undo_redo->add_undo_method(this, "_move_node", func, p_id, p_from); } void VisualScriptEditor::_remove_node(int p_id) { undo_redo->create_action(TTR("Remove VisualScript Node")); - undo_redo->add_do_method(script.ptr(), "remove_node", edited_func, p_id); - undo_redo->add_undo_method(script.ptr(), "add_node", edited_func, p_id, script->get_node(edited_func, p_id), script->get_node_position(edited_func, p_id)); + StringName func = _get_function_of_node(p_id); + + undo_redo->add_do_method(script.ptr(), "remove_node", func, p_id); + undo_redo->add_undo_method(script.ptr(), "add_node", func, p_id, script->get_node(func, p_id), script->get_node_position(func, p_id)); List<VisualScript::SequenceConnection> sequence_conns; - script->get_sequence_connection_list(edited_func, &sequence_conns); + script->get_sequence_connection_list(func, &sequence_conns); for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { if (E->get().from_node == p_id || E->get().to_node == p_id) { - undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, E->get().from_node, E->get().from_output, E->get().to_node); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node); } } List<VisualScript::DataConnection> data_conns; - script->get_data_connection_list(edited_func, &data_conns); + script->get_data_connection_list(func, &data_conns); for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) { if (E->get().from_node == p_id || E->get().to_node == p_id) { - undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", func, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); } } @@ -2348,15 +2796,29 @@ void VisualScriptEditor::_remove_node(int p_id) { void VisualScriptEditor::_node_ports_changed(const String &p_func, int p_id) { - if (p_func != String(edited_func)) - return; - _update_graph(p_id); } +bool VisualScriptEditor::node_has_sequence_connections(const StringName &p_func, int p_id) { + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(p_func, &sequence_conns); + + for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { + int from = E->get().from_node; + int to = E->get().to_node; + + if (to == p_id || from == p_id) + return true; + } + + return false; +} + void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) { - Ref<VisualScriptNode> from_node = script->get_node(edited_func, p_from.to_int()); + StringName from_func = _get_function_of_node(p_from.to_int()); + + Ref<VisualScriptNode> from_node = script->get_node(from_func, p_from.to_int()); ERR_FAIL_COND(!from_node.is_valid()); bool from_seq; @@ -2365,7 +2827,9 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) return; //can't connect this, it's invalid - Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int()); + StringName to_func = _get_function_of_node(p_to.to_int()); + + Ref<VisualScriptNode> to_node = script->get_node(to_func, p_to.to_int()); ERR_FAIL_COND(!to_node.is_valid()); bool to_seq; @@ -2376,29 +2840,170 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, ERR_FAIL_COND(from_seq != to_seq); + // Do all the checks here + StringName func; // this the func where we store the one the nodes at the end of the resolution on having multiple nodes + undo_redo->create_action(TTR("Connect Nodes")); + if (from_func == to_func) { + func = to_func; + } else if (from_seq) { + // this is a sequence connection + _move_nodes_with_rescan(to_func, from_func, p_to.to_int()); // this function moves the nodes from func1 to func2 + func = from_func; + } else { + if (node_has_sequence_connections(to_func, p_to.to_int())) { + if (node_has_sequence_connections(from_func, p_from.to_int())) { + ERR_PRINT("Trying to connect between different sequence node trees"); + return; + } else { + _move_nodes_with_rescan(from_func, to_func, p_from.to_int()); + func = to_func; + } + } else if (node_has_sequence_connections(from_func, p_from.to_int())) { + if (from_func == default_func) { + _move_nodes_with_rescan(from_func, to_func, p_from.to_int()); + func = to_func; + } else { + _move_nodes_with_rescan(to_func, from_func, p_to.to_int()); + func = from_func; + } + } else { + if (to_func == default_func) { + _move_nodes_with_rescan(to_func, from_func, p_to.to_int()); + func = from_func; + } else { + _move_nodes_with_rescan(from_func, to_func, p_from.to_int()); + func = to_func; + } + } + } + if (from_seq) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, p_from.to_int(), from_port, p_to.to_int()); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_do_method(script.ptr(), "sequence_connect", func, p_from.to_int(), from_port, p_to.to_int()); + // this undo error on undo after move can't be removed without painful gymnastics + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, p_from.to_int(), from_port, p_to.to_int()); } else { + bool converted = false; + int conv_node = -1; + + Ref<VisualScriptOperator> oper = to_node; + if (oper.is_valid() && oper->get_typed() == Variant::NIL) { + // it's an operator Node and if the type is already nil + if (from_node->get_output_value_port_info(from_port).type != Variant::NIL) { + oper->set_typed(from_node->get_output_value_port_info(from_port).type); + } + } - // disconnect current, and connect the new one - if (script->is_input_value_port_connected(edited_func, p_to.to_int(), to_port)) { - int conn_from; - int conn_port; - script->get_input_value_port_connection_source(edited_func, p_to.to_int(), to_port, &conn_from, &conn_port); - undo_redo->add_do_method(script.ptr(), "data_disconnect", edited_func, conn_from, conn_port, p_to.to_int(), to_port); - undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, conn_from, conn_port, p_to.to_int(), to_port); + Ref<VisualScriptOperator> operf = from_node; + if (operf.is_valid() && operf->get_typed() == Variant::NIL) { + // it's an operator Node and if the type is already nil + if (to_node->get_input_value_port_info(to_port).type != Variant::NIL) { + operf->set_typed(to_node->get_input_value_port_info(to_port).type); + } } - undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); - undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); - //update nodes in sgraph - undo_redo->add_do_method(this, "_update_graph", p_from.to_int()); - undo_redo->add_do_method(this, "_update_graph", p_to.to_int()); - undo_redo->add_undo_method(this, "_update_graph", p_from.to_int()); - undo_redo->add_undo_method(this, "_update_graph", p_to.to_int()); + Variant::Type to_type = to_node->get_input_value_port_info(to_port).type; + Variant::Type from_type = from_node->get_output_value_port_info(from_port).type; + + if (to_type != Variant::NIL && from_type != Variant::NIL && to_type != from_type) { + // add a constructor node between the ports + bool exceptions = false; // true if there are any exceptions + exceptions = exceptions || (to_type == Variant::INT && from_type == Variant::REAL); + exceptions = exceptions || (to_type == Variant::REAL && from_type == Variant::INT); + if (Variant::can_convert(from_type, to_type) && !exceptions) { + MethodInfo mi; + mi.name = Variant::get_type_name(to_type); + PropertyInfo pi; + pi.name = "from"; + pi.type = from_type; + mi.arguments.push_back(pi); + mi.return_val.type = to_type; + // we know that this is allowed so create a new constructor node + Ref<VisualScriptConstructor> constructor; + constructor.instance(); + constructor->set_constructor_type(to_type); + constructor->set_constructor(mi); + // add the new constructor node + + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(p_from)); + GraphNode *gn2 = Object::cast_to<GraphNode>(graph->get_node(p_to)); + if (gn && gn2) { + Vector2 from_node_size = gn->get_rect().get_size(); + Vector2 to_node_size = gn2->get_rect().get_size(); + Vector2 to_node_pos = script->get_node_position(func, p_to.to_int()); + Vector2 from_node_pos = script->get_node_position(func, p_from.to_int()); + Vector2 new_to_node_pos = from_node_pos; + Vector2 constructor_pos; + if ((to_node_pos.x - from_node_pos.x) < 0) { + // to is behind from node + if (to_node_pos.x > (from_node_pos.x - to_node_size.x - 240)) + new_to_node_pos.x = from_node_pos.x - to_node_size.x - 240; // approx size of construtor node + padding + else + new_to_node_pos.x = to_node_pos.x; + new_to_node_pos.y = to_node_pos.y; + constructor_pos.x = from_node_pos.x - 210; + constructor_pos.y = to_node_pos.y; + } else { + // to is ahead of from node + if (to_node_pos.x < (from_node_size.x + from_node_pos.x + 240)) + new_to_node_pos.x = from_node_size.x + from_node_pos.x + 240; // approx size of construtor node + padding + else + new_to_node_pos.x = to_node_pos.x; + new_to_node_pos.y = to_node_pos.y; + constructor_pos.x = from_node_size.x + from_node_pos.x + 10; + constructor_pos.y = to_node_pos.y; + } + undo_redo->add_do_method(this, "_move_node", func, p_to.to_int(), new_to_node_pos); + undo_redo->add_undo_method(this, "_move_node", func, p_to.to_int(), to_node_pos); + conv_node = script->get_available_id(); + undo_redo->add_do_method(script.ptr(), "add_node", func, conv_node, constructor, _get_available_pos(false, constructor_pos)); + undo_redo->add_undo_method(script.ptr(), "remove_node", func, conv_node); + converted = true; + } + } + } + + // disconnect current, and connect the new one + if (script->is_input_value_port_connected(func, p_to.to_int(), to_port)) { + if (can_swap && data_disconnect_node == p_to.to_int()) { + int conn_from; + int conn_port; + script->get_input_value_port_connection_source(func, p_to.to_int(), to_port, &conn_from, &conn_port); + undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port); + undo_redo->add_do_method(script.ptr(), "data_connect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_to.to_int(), to_port); + can_swap = false; // swapped + } else { + int conn_from; + int conn_port; + script->get_input_value_port_connection_source(func, p_to.to_int(), to_port, &conn_from, &conn_port); + undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_to.to_int(), to_port); + } + } + if (!converted) { + undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, p_to.to_int(), to_port); + } else { + // this is noice + undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, conv_node, 0); + undo_redo->add_do_method(script.ptr(), "data_connect", func, conv_node, 0, p_to.to_int(), to_port); + // I don't think this is needed but gonna leave it here for now... until I need to finalise it all + undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, conv_node, 0); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conv_node, 0, p_to.to_int(), to_port); + } + //update nodes in graph + if (!converted) { + undo_redo->add_do_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_do_method(this, "_update_graph", p_to.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_to.to_int()); + } else { + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + } } undo_redo->add_do_method(this, "_update_graph_connections"); @@ -2409,7 +3014,10 @@ void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) { - Ref<VisualScriptNode> from_node = script->get_node(edited_func, p_from.to_int()); + StringName func = _get_function_of_node(p_from.to_int()); + ERR_FAIL_COND(func != _get_function_of_node(p_to.to_int())); + + Ref<VisualScriptNode> from_node = script->get_node(func, p_from.to_int()); ERR_FAIL_COND(!from_node.is_valid()); bool from_seq; @@ -2418,7 +3026,7 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) return; //can't connect this, it's invalid - Ref<VisualScriptNode> to_node = script->get_node(edited_func, p_to.to_int()); + Ref<VisualScriptNode> to_node = script->get_node(func, p_to.to_int()); ERR_FAIL_COND(!to_node.is_valid()); bool to_seq; @@ -2429,15 +3037,20 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl ERR_FAIL_COND(from_seq != to_seq); - undo_redo->create_action(TTR("Connect Nodes")); + undo_redo->create_action(TTR("Disconnect Nodes")); if (from_seq) { - undo_redo->add_do_method(script.ptr(), "sequence_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int()); - undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_do_method(script.ptr(), "sequence_disconnect", func, p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, p_from.to_int(), from_port, p_to.to_int()); } else { - undo_redo->add_do_method(script.ptr(), "data_disconnect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); - undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, p_from.to_int(), from_port, p_to.to_int(), to_port); - //update nodes in sgraph + + can_swap = true; + data_disconnect_node = p_to.to_int(); + data_disconnect_port = to_port; + + undo_redo->add_do_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, p_to.to_int(), to_port); + //update relevant nodes in the graph undo_redo->add_do_method(this, "_update_graph", p_from.to_int()); undo_redo->add_do_method(this, "_update_graph", p_to.to_int()); undo_redo->add_undo_method(this, "_update_graph", p_from.to_int()); @@ -2449,6 +3062,216 @@ void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_sl undo_redo->commit_action(); } +void VisualScriptEditor::_move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id) { + + Set<int> nodes_to_move; + HashMap<int, Map<int, int> > seqconns_to_move; // from => List(outp, to) + HashMap<int, Map<int, Pair<int, int> > > dataconns_to_move; // to => List(inp_p => from, outp) + + nodes_to_move.insert(p_id); + Set<int> sequence_connections; + { + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(p_func_from, &sequence_conns); + + HashMap<int, Map<int, int> > seqcons; // from => List(out_p => to) + + for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) { + int from = E->get().from_node; + int to = E->get().to_node; + int out_p = E->get().from_output; + if (!seqcons.has(from)) + seqcons.set(from, Map<int, int>()); + seqcons[from].insert(out_p, to); + sequence_connections.insert(to); + sequence_connections.insert(from); + } + + int conn = p_id; + List<int> stack; + HashMap<int, Set<int> > seen; // from, outp + while (seqcons.has(conn)) { + for (auto E = seqcons[conn].front(); E; E = E->next()) { + if (seen.has(conn) && seen[conn].has(E->key())) { + if (!E->next()) { + if (stack.size() > 0) { + conn = stack.back()->get(); + stack.pop_back(); + break; + } + conn = -101; + break; + } + continue; + } + if (!seen.has(conn)) + seen.set(conn, Set<int>()); + seen[conn].insert(E->key()); + stack.push_back(conn); + if (!seqconns_to_move.has(conn)) + seqconns_to_move.set(conn, Map<int, int>()); + seqconns_to_move[conn].insert(E->key(), E->get()); + conn = E->get(); + nodes_to_move.insert(conn); + break; + } + if (!seqcons.has(conn) && stack.size() > 0) { + conn = stack.back()->get(); + stack.pop_back(); + } + } + } + + { + List<VisualScript::DataConnection> data_connections; + script->get_data_connection_list(p_func_from, &data_connections); + + HashMap<int, Map<int, Pair<int, int> > > connections; + + for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) { + int from = E->get().from_node; + int to = E->get().to_node; + int out_p = E->get().from_port; + int in_p = E->get().to_port; + + if (!connections.has(to)) + connections.set(to, Map<int, Pair<int, int> >()); + connections[to].insert(in_p, Pair<int, int>(from, out_p)); + } + + // go through the HashMap and do all sorts of crazy ass stuff now... + Set<int> nodes_to_be_added; + for (Set<int>::Element *F = nodes_to_move.front(); F; F = F->next()) { + HashMap<int, Set<int> > seen; + List<int> stack; + int id = F->get(); + while (connections.has(id)) { + for (auto E = connections[id].front(); E; E = E->next()) { + if (seen.has(id) && seen[id].has(E->key())) { + if (!E->next()) { + if (stack.size() > 0) { + id = stack.back()->get(); + stack.pop_back(); + break; + } + id = -11; // I assume ids can't be negative should confirm it... + break; + } + continue; + } + + if (sequence_connections.has(E->get().first)) { + if (!nodes_to_move.has(E->get().first)) { + if (stack.size() > 0) { + id = stack.back()->get(); + stack.pop_back(); + break; + } + id = -11; // I assume ids can't be negative should confirm it... + break; + } + } + + if (!seen.has(id)) + seen.set(id, Set<int>()); + seen[id].insert(E->key()); + stack.push_back(id); + if (!dataconns_to_move.has(id)) + dataconns_to_move.set(id, Map<int, Pair<int, int> >()); + dataconns_to_move[id].insert(E->key(), Pair<int, int>(E->get().first, E->get().second)); + id = E->get().first; + nodes_to_be_added.insert(id); + break; + } + if (!connections.has(id) && stack.size() > 0) { + id = stack.back()->get(); + stack.pop_back(); + } + } + } + for (Set<int>::Element *E = nodes_to_be_added.front(); E; E = E->next()) { + nodes_to_move.insert(E->get()); + } + } + + // * this is primarily for the sake of the having proper undo + List<VisualScript::SequenceConnection> seqext; + List<VisualScript::DataConnection> dataext; + + List<VisualScript::SequenceConnection> seq_connections; + script->get_sequence_connection_list(p_func_from, &seq_connections); + + for (List<VisualScript::SequenceConnection>::Element *E = seq_connections.front(); E; E = E->next()) { + if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) { + seqext.push_back(E->get()); + } else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) { + seqext.push_back(E->get()); + } + } + + List<VisualScript::DataConnection> data_connections; + script->get_data_connection_list(p_func_from, &data_connections); + + for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) { + if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) { + dataext.push_back(E->get()); + } else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) { + dataext.push_back(E->get()); + } + } + + // undo_redo->create_action("Rescan Functions"); + + for (Set<int>::Element *E = nodes_to_move.front(); E; E = E->next()) { + int id = E->get(); + + undo_redo->add_do_method(script.ptr(), "remove_node", p_func_from, id); + undo_redo->add_do_method(script.ptr(), "add_node", p_func_to, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id)); + + undo_redo->add_undo_method(script.ptr(), "remove_node", p_func_to, id); + undo_redo->add_undo_method(script.ptr(), "add_node", p_func_from, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id)); + } + + List<int> skeys; + seqconns_to_move.get_key_list(&skeys); + for (List<int>::Element *E = skeys.front(); E; E = E->next()) { + int from_node = E->get(); + for (Map<int, int>::Element *F = seqconns_to_move[from_node].front(); F; F = F->next()) { + int from_port = F->key(); + int to_node = F->get(); + undo_redo->add_do_method(script.ptr(), "sequence_connect", p_func_to, from_node, from_port, to_node); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, from_node, from_port, to_node); + } + } + + List<int> keys; + dataconns_to_move.get_key_list(&keys); + for (List<int>::Element *E = keys.front(); E; E = E->next()) { + int to_node = E->get(); // to_node + for (Map<int, Pair<int, int> >::Element *F = dataconns_to_move[E->get()].front(); F; F = F->next()) { + int inp_p = F->key(); + Pair<int, int> fro = F->get(); + + undo_redo->add_do_method(script.ptr(), "data_connect", p_func_to, fro.first, fro.second, to_node, inp_p); + undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, fro.first, fro.second, to_node, inp_p); + } + } + + // this to have proper undo operations + for (List<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, E->get().from_node, E->get().from_output, E->get().to_node); + } + for (List<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) { + undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + // this doesn't need do methods as they are handled by the subsequent do calls implicitly + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + // undo_redo->commit_action(); +} + void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos) { Node *node = graph->get_node(p_from); @@ -2456,7 +3279,9 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro if (!gn) return; - Ref<VisualScriptNode> vsn = script->get_node(edited_func, p_from.to_int()); + StringName func = _get_function_of_node(p_from.to_int()); + + Ref<VisualScriptNode> vsn = script->get_node(func, p_from.to_int()); if (!vsn.is_valid()) return; @@ -2466,12 +3291,11 @@ void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_fro port_action_node = p_from.to_int(); port_action_output = p_from_slot; - _port_action_menu(CREATE_ACTION); + _port_action_menu(CREATE_ACTION, func); } else { - port_action_output = p_from_slot - vsn->get_output_sequence_port_count(); port_action_node = p_from.to_int(); - _port_action_menu(CREATE_CALL_SET_GET); + _port_action_menu(CREATE_CALL_SET_GET, func); } } @@ -2485,7 +3309,9 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac visited_nodes.insert(p_port_action_node); - Ref<VisualScriptNode> node = script->get_node(edited_func, p_port_action_node); + StringName func = _get_function_of_node(p_port_action_node); + + Ref<VisualScriptNode> node = script->get_node(func, p_port_action_node); if (!node.is_valid()) { @@ -2504,7 +3330,7 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac int from_node; int from_port; - if (script->get_input_value_port_connection_source(edited_func, p_port_action_node, i, &from_node, &from_port)) { + if (script->get_input_value_port_connection_source(func, p_port_action_node, i, &from_node, &from_port)) { g = _guess_output_type(from_node, from_port, visited_nodes); } else { @@ -2529,7 +3355,7 @@ VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_ac return node->guess_output_type(in_guesses.ptrw(), p_port_action_output); } -void VisualScriptEditor::_port_action_menu(int p_option) { +void VisualScriptEditor::_port_action_menu(int p_option, const StringName &func) { Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; if (graph->is_using_snap()) { @@ -2553,8 +3379,10 @@ void VisualScriptEditor::_port_action_menu(int p_option) { } else { n->set_base_type("Object"); } - - String type_string = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + String type_string; + if (script->get_node(func, port_action_node)->get_output_value_port_count() > 0) { + type_string = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + } if (tg.type == Variant::OBJECT) { if (tg.script.is_valid()) { new_connect_node_select->select_from_script(tg.script, ""); @@ -2568,10 +3396,19 @@ void VisualScriptEditor::_port_action_menu(int p_option) { } else { new_connect_node_select->select_from_basic_type(tg.type); } + // ensure that the dialog fits inside the graph + Vector2 pos = mouse_up_position; + Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); + pos.x = pos.x > bounds.x ? bounds.x : pos.x; + pos.y = pos.y > bounds.y ? bounds.y : pos.y; + new_connect_node_select->set_position(pos); } break; case CREATE_ACTION: { VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); - PropertyInfo property_info = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output); + PropertyInfo property_info; + if (script->get_node(func, port_action_node)->get_output_value_port_count() > 0) { + property_info = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output); + } if (tg.type == Variant::OBJECT) { if (property_info.type == Variant::OBJECT && property_info.hint_string != String()) { new_connect_node_select->select_from_action(property_info.hint_string); @@ -2583,25 +3420,18 @@ void VisualScriptEditor::_port_action_menu(int p_option) { } else { new_connect_node_select->select_from_action(Variant::get_type_name(tg.type)); } + // ensure that the dialog fits inside the graph + Vector2 pos = mouse_up_position; + Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); + pos.x = pos.x > bounds.x ? bounds.x : pos.x; + pos.y = pos.y > bounds.y ? bounds.y : pos.y; + new_connect_node_select->set_position(pos); } break; } } -void VisualScriptEditor::new_node(Ref<VisualScriptNode> vnode, Vector2 ofs) { - Set<int> vn; - Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); - int new_id = script->get_available_id(); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); - undo_redo->add_do_method(this, "_update_graph", new_id); - undo_redo->add_undo_method(this, "_update_graph", new_id); - undo_redo->commit_action(); - - port_action_new_node = new_id; -} - void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) { + undo_redo->create_action(TTR("Connect Node Data")); VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr()); if (vnode_return != NULL && vnode_old->get_output_value_port_count() > 0) { @@ -2620,12 +3450,14 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua if (port >= value_count) { port = 0; } - undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, port_action_node, port, new_id, 0); - undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, port_action_node, port, new_id, 0); + StringName func = _get_function_of_node(port_action_node); + undo_redo->add_do_method(script.ptr(), "data_connect", func, port_action_node, port, new_id, 0); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, port_action_node, port, new_id, 0); undo_redo->commit_action(); } void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { + Vector2 ofs = graph->get_scroll_ofs() + port_action_pos; if (graph->is_using_snap()) { int snap = graph->get_snap(); @@ -2635,19 +3467,29 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri Set<int> vn; + bool port_node_exists = true; + + StringName func = _get_function_of_node(port_action_node); + if (func == StringName()) { + func = default_func; + port_node_exists = false; + } + if (p_category == "visualscript") { Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text); - Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); + Ref<VisualScriptNode> vnode_old; + if (port_node_exists) + vnode_old = script->get_node(func, port_action_node); int new_id = script->get_available_id(); - if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) { - Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; + if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && vnode_old.is_valid()) { + Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type); } - if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && script->get_node(edited_func, port_action_node).is_valid()) { - Variant::Type type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).type; - String hint_name = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && vnode_old.is_valid()) { + Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; + String hint_name = vnode_old->get_output_value_port_info(port_action_output).hint_string; if (type == Variant::OBJECT) { Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name); @@ -2657,14 +3499,15 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type)); } } + undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode_new, ofs); + undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode_new, ofs); if (vnode_old.is_valid() && p_connecting) { connect_seq(vnode_old, vnode_new, new_id); connect_data(vnode_old, vnode_new, new_id); } - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); @@ -2727,16 +3570,24 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } - new_node(vnode, ofs); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); - Ref<VisualScriptNode> vsn = script->get_node(edited_func, port_action_new_node); + port_action_new_node = new_id; + + Ref<VisualScriptNode> vsn = script->get_node(func, port_action_new_node); if (Object::cast_to<VisualScriptFunctionCall>(vsn.ptr())) { Ref<VisualScriptFunctionCall> vsfc = vsn; vsfc->set_function(p_text); - if (p_connecting) { + if (port_node_exists && p_connecting) { VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); if (tg.type == Variant::OBJECT) { @@ -2745,9 +3596,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri if (tg.gdclass != StringName()) { vsfc->set_base_type(tg.gdclass); - } else if (script->get_node(edited_func, port_action_node).is_valid()) { - PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; - String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + } else if (script->get_node(func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { vsfc->set_base_type(base_type); @@ -2769,8 +3620,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } - // if connecting from another node the call mode shouldn't be self - if (p_connecting) { + if (port_node_exists && p_connecting) { if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) { Ref<VisualScriptPropertySet> vsp = vsn; @@ -2781,9 +3631,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri if (tg.gdclass != StringName()) { vsp->set_base_type(tg.gdclass); - } else if (script->get_node(edited_func, port_action_node).is_valid()) { - PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; - String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + } else if (script->get_node(func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { vsp->set_base_type(base_type); @@ -2811,9 +3661,9 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri if (tg.gdclass != StringName()) { vsp->set_base_type(tg.gdclass); - } else if (script->get_node(edited_func, port_action_node).is_valid()) { - PropertyHint hint = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint; - String base_type = script->get_node(edited_func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; + } else if (script->get_node(func, port_action_node).is_valid()) { + PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string; if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) { vsp->set_base_type(base_type); } @@ -2830,16 +3680,20 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } } - Ref<VisualScriptNode> vnode_old = script->get_node(edited_func, port_action_node); - if (vnode_old.is_valid() && p_connecting) { - connect_seq(vnode_old, vnode, port_action_new_node); - connect_data(vnode_old, vnode, port_action_new_node); + if (port_node_exists) { + Ref<VisualScriptNode> vnode_old = script->get_node(func, port_action_node); + if (vnode_old.is_valid() && p_connecting) { + connect_seq(vnode_old, vnode, port_action_new_node); + connect_data(vnode_old, vnode, port_action_new_node); + } } _update_graph(port_action_new_node); - _update_graph_connections(); + if (port_node_exists) + _update_graph_connections(); } void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) { + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr()); if (vnode_operator != NULL && !vnode_operator->has_input_sequence_port()) { return; @@ -2855,27 +3709,29 @@ void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<Visual return; } + StringName func = _get_function_of_node(port_action_node); + undo_redo->create_action(TTR("Connect Node Sequence")); int pass_port = -vnode_old->get_output_sequence_port_count() + 1; int return_port = port_action_output - 1; if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") && - !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(pass_port)) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, pass_port, new_id); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, pass_port, new_id); + !script->get_output_sequence_ports_connected(func, port_action_node).has(pass_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, pass_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, pass_port, new_id); } else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") && - !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(return_port)) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, return_port, new_id); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, return_port, new_id); + !script->get_output_sequence_ports_connected(func, port_action_node).has(return_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, return_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, return_port, new_id); } else { for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) { int count = vnode_old->get_output_sequence_port_count(); - if (port_action_output < count && !script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port_action_output)) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port_action_output, new_id); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port_action_output, new_id); + if (port_action_output < count && !script->get_output_sequence_ports_connected(func, port_action_node).has(port_action_output)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port_action_output, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, port_action_output, new_id); break; - } else if (!script->get_output_sequence_ports_connected(edited_func, port_action_node).has(port)) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, port_action_node, port, new_id); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, port_action_node, port, new_id); + } else if (!script->get_output_sequence_ports_connected(func, port_action_node).has(port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, port, new_id); break; } } @@ -2908,7 +3764,6 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons } selected = name; - edited_func = selected; Ref<VisualScriptFunction> func_node; func_node.instance(); func_node->set_name(name); @@ -2920,14 +3775,16 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string); } - undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node); + Vector2 ofs = _get_available_pos(); + + undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs); if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { Ref<VisualScriptReturn> ret_node; ret_node.instance(); ret_node->set_return_type(minfo.return_val.type); ret_node->set_enable_return_value(true); ret_node->set_name(name); - undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id() + 1, ret_node, Vector2(500, 0)); + undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id() + 1, ret_node, _get_available_pos(false, ofs + Vector2(500, 0))); } undo_redo->add_undo_method(script.ptr(), "remove_function", name); @@ -2942,31 +3799,30 @@ void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, cons } void VisualScriptEditor::_cancel_connect_node() { - // Causes crashes - //script->remove_node(edited_func, port_action_new_node); - _update_graph(); + // ensure the cancel is done + port_action_new_node = -1; } -void VisualScriptEditor::_create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point) { - Vector2 ofs = graph->get_scroll_ofs() + p_point; - if (graph->is_using_snap()) { - int snap = graph->get_snap(); - ofs = ofs.snapped(Vector2(snap, snap)); - } - ofs /= EDSCALE; +int VisualScriptEditor::_create_new_node_from_name(const String &p_text, const Vector2 &p_point, const StringName &p_func) { + + StringName func = default_func; + if (p_func != StringName()) + func = p_func; + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text); int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode, p_point); + undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id); undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); + return new_id; } void VisualScriptEditor::_default_value_changed() { - Ref<VisualScriptNode> vsn = script->get_node(edited_func, editing_id); + Ref<VisualScriptNode> vsn = script->get_node(_get_function_of_node(editing_id), editing_id); if (vsn.is_null()) return; @@ -2981,7 +3837,7 @@ void VisualScriptEditor::_default_value_changed() { void VisualScriptEditor::_default_value_edited(Node *p_button, int p_id, int p_input_port) { - Ref<VisualScriptNode> vsn = script->get_node(edited_func, p_id); + Ref<VisualScriptNode> vsn = script->get_node(_get_function_of_node(p_id), p_id); if (vsn.is_null()) return; @@ -3000,16 +3856,18 @@ void VisualScriptEditor::_default_value_edited(Node *p_button, int p_id, int p_i if (pinfo.type == Variant::NODE_PATH) { Node *edited_scene = get_tree()->get_edited_scene_root(); - Node *script_node = _find_script_node(edited_scene, edited_scene, script); + if (edited_scene) { // Fixing an old crash bug ( Visual Script Crashes on editing NodePath with an empty scene open) + Node *script_node = _find_script_node(edited_scene, edited_scene, script); - if (script_node) { - //pick a node relative to the script, IF the script exists - pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; - pinfo.hint_string = script_node->get_path(); - } else { - //pick a path relative to edited scene - pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; - pinfo.hint_string = get_tree()->get_edited_scene_root()->get_path(); + if (script_node) { + //pick a node relative to the script, IF the script exists + pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; + pinfo.hint_string = script_node->get_path(); + } else { + //pick a path relative to edited scene + pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; + pinfo.hint_string = get_tree()->get_edited_scene_root()->get_path(); + } } } @@ -3036,62 +3894,64 @@ void VisualScriptEditor::_hide_timer() { hint_text->hide(); } -void VisualScriptEditor::_node_filter_changed(const String &p_text) { - - _update_available_nodes(); -} - void VisualScriptEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY || (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree())) { - - node_filter->set_right_icon(Control::get_icon("Search", "EditorIcons")); - node_filter->set_clear_button_enabled(true); - - if (p_what == NOTIFICATION_READY) { + switch (p_what) { + case NOTIFICATION_READY: { variable_editor->connect("changed", this, "_update_members"); signal_editor->connect("changed", this, "_update_members"); + FALLTHROUGH; } + case NOTIFICATION_THEME_CHANGED: { + if (p_what != NOTIFICATION_READY && !is_visible_in_tree()) { + return; + } - Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); + edit_variable_edit->add_style_override("bg", get_stylebox("bg", "Tree")); + edit_signal_edit->add_style_override("bg", get_stylebox("bg", "Tree")); + func_input_scroll->add_style_override("bg", get_stylebox("bg", "Tree")); - bool dark_theme = tm->get_constant("dark_theme", "Editor"); + Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); - List<Pair<String, Color> > colors; - if (dark_theme) { - colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96))); - colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51))); - colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81))); - colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87))); - colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96))); - colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69))); - } else { - colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26))); - colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38))); - colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51))); - colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82))); - colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95))); - colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49))); - } + bool dark_theme = tm->get_constant("dark_theme", "Editor"); - for (List<Pair<String, Color> >::Element *E = colors.front(); E; E = E->next()) { - Ref<StyleBoxFlat> sb = tm->get_stylebox("frame", "GraphNode"); - if (!sb.is_null()) { - Ref<StyleBoxFlat> frame_style = sb->duplicate(); - Color c = sb->get_border_color(); - Color cn = E->get().second; - cn.a = c.a; - frame_style->set_border_color(cn); - node_styles[E->get().first] = frame_style; + List<Pair<String, Color> > colors; + if (dark_theme) { + colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96))); + colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51))); + colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81))); + colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87))); + colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96))); + colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69))); + } else { + colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26))); + colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38))); + colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51))); + colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82))); + colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95))); + colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49))); } - } - if (is_visible_in_tree() && script.is_valid()) { - _update_members(); - _update_graph(); - } - } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - left_vsplit->set_visible(is_visible_in_tree()); + for (List<Pair<String, Color> >::Element *E = colors.front(); E; E = E->next()) { + Ref<StyleBoxFlat> sb = tm->get_stylebox("frame", "GraphNode"); + if (!sb.is_null()) { + Ref<StyleBoxFlat> frame_style = sb->duplicate(); + Color c = sb->get_border_color(); + Color cn = E->get().second; + cn.a = c.a; + frame_style->set_border_color(cn); + node_styles[E->get().first] = frame_style; + } + } + + if (is_visible_in_tree() && script.is_valid()) { + _update_members(); + _update_graph(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + members_section->set_visible(is_visible_in_tree()); + } break; } } @@ -3102,8 +3962,9 @@ void VisualScriptEditor::_graph_ofs_changed(const Vector2 &p_ofs) { updating_graph = true; - if (script->has_function(edited_func)) { - script->set_function_scroll(edited_func, graph->get_scroll_ofs() / EDSCALE); + // Just use the default func for all the properties that need to be handled for drawing rather than adding to the Visual Script Class + if (script->has_function(default_func)) { + script->set_function_scroll(default_func, graph->get_scroll_ofs() / EDSCALE); script->set_edited(true); } updating_graph = false; @@ -3114,7 +3975,9 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_ if (updating_graph) return; - Ref<VisualScriptComment> vsc = script->get_node(edited_func, p_node); + StringName func = _get_function_of_node(p_node); + + Ref<VisualScriptComment> vsc = script->get_node(func, p_node); if (vsc.is_null()) return; @@ -3132,7 +3995,7 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_ undo_redo->add_undo_method(vsc.ptr(), "set_size", vsc->get_size()); undo_redo->commit_action(); - gn->set_custom_minimum_size(p_new_size); //for this time since graph update is blocked + gn->set_custom_minimum_size(p_new_size); gn->set_size(Size2(1, 1)); graph->set_block_minimum_size_adjust(false); updating_graph = false; @@ -3152,7 +4015,8 @@ void VisualScriptEditor::_menu_option(int p_what) { if (gn) { if (gn->is_selected()) { int id = String(gn->get_name()).to_int(); - Ref<VisualScriptNode> vsn = script->get_node(edited_func, id); + StringName func = _get_function_of_node(id); + Ref<VisualScriptNode> vsn = script->get_node(func, id); if (vsn.is_valid()) { vsn->set_breakpoint(!vsn->is_breakpoint()); reselect.push_back(gn->get_name()); @@ -3174,28 +4038,30 @@ void VisualScriptEditor::_menu_option(int p_what) { } break; case EDIT_COPY_NODES: case EDIT_CUT_NODES: { - - if (!script->has_function(edited_func)) + if (!script->has_function(default_func)) break; clipboard->nodes.clear(); clipboard->data_connections.clear(); clipboard->sequence_connections.clear(); + Set<String> funcs; for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); if (gn) { if (gn->is_selected()) { int id = String(gn->get_name()).to_int(); - Ref<VisualScriptNode> node = script->get_node(edited_func, id); + StringName func = _get_function_of_node(id); + Ref<VisualScriptNode> node = script->get_node(func, id); if (Object::cast_to<VisualScriptFunction>(*node)) { EditorNode::get_singleton()->show_warning(TTR("Can't copy the function node.")); return; } if (node.is_valid()) { clipboard->nodes[id] = node->duplicate(true); - clipboard->nodes_positions[id] = script->get_node_position(edited_func, id); + clipboard->nodes_positions[id] = script->get_node_position(func, id); + funcs.insert(String(func)); } } } @@ -3204,37 +4070,38 @@ void VisualScriptEditor::_menu_option(int p_what) { if (clipboard->nodes.empty()) break; - List<VisualScript::SequenceConnection> sequence_connections; + for (Set<String>::Element *F = funcs.front(); F; F = F->next()) { + List<VisualScript::SequenceConnection> sequence_connections; - script->get_sequence_connection_list(edited_func, &sequence_connections); + script->get_sequence_connection_list(F->get(), &sequence_connections); - for (List<VisualScript::SequenceConnection>::Element *E = sequence_connections.front(); E; E = E->next()) { + for (List<VisualScript::SequenceConnection>::Element *E = sequence_connections.front(); E; E = E->next()) { - if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { - clipboard->sequence_connections.insert(E->get()); + clipboard->sequence_connections.insert(E->get()); + } } - } - List<VisualScript::DataConnection> data_connections; + List<VisualScript::DataConnection> data_connections; - script->get_data_connection_list(edited_func, &data_connections); + script->get_data_connection_list(F->get(), &data_connections); - for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) { + for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) { - if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { + if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) { - clipboard->data_connections.insert(E->get()); + clipboard->data_connections.insert(E->get()); + } } } - if (p_what == EDIT_CUT_NODES) { _on_nodes_delete(); // oh yeah, also delete on cut } } break; case EDIT_PASTE_NODES: { - if (!script->has_function(edited_func)) + if (!script->has_function(default_func)) break; if (clipboard->nodes.empty()) { @@ -3252,11 +4119,15 @@ void VisualScriptEditor::_menu_option(int p_what) { Set<Vector2> existing_positions; { - List<int> nodes; - script->get_node_list(edited_func, &nodes); - for (List<int>::Element *E = nodes.front(); E; E = E->next()) { - Vector2 pos = script->get_node_position(edited_func, E->get()).snapped(Vector2(2, 2)); - existing_positions.insert(pos); + List<StringName> functions; + script->get_function_list(&functions); + for (List<StringName>::Element *F = functions.front(); F; F = F->next()) { + List<int> nodes; + script->get_node_list(F->get(), &nodes); + for (List<int>::Element *E = nodes.front(); E; E = E->next()) { + Vector2 pos = script->get_node_position(F->get(), E->get()).snapped(Vector2(2, 2)); + existing_positions.insert(pos); + } } } @@ -3275,20 +4146,20 @@ void VisualScriptEditor::_menu_option(int p_what) { paste_pos += Vector2(20, 20) * EDSCALE; } - undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, node, paste_pos); - undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); + undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, node, paste_pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id); } for (Set<VisualScript::SequenceConnection>::Element *E = clipboard->sequence_connections.front(); E; E = E->next()) { - undo_redo->add_do_method(script.ptr(), "sequence_connect", edited_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); - undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", edited_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); + undo_redo->add_do_method(script.ptr(), "sequence_connect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); } for (Set<VisualScript::DataConnection>::Element *E = clipboard->data_connections.front(); E; E = E->next()) { - undo_redo->add_do_method(script.ptr(), "data_connect", edited_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); - undo_redo->add_undo_method(script.ptr(), "data_disconnect", edited_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); + undo_redo->add_do_method(script.ptr(), "data_connect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", default_func, remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); } undo_redo->add_do_method(this, "_update_graph"); @@ -3304,6 +4175,275 @@ void VisualScriptEditor::_menu_option(int p_what) { } } } break; + case EDIT_CREATE_FUNCTION: { + + StringName function = ""; + Map<int, Ref<VisualScriptNode> > nodes; + Set<int> selections; + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected()) { + int id = String(gn->get_name()).to_int(); + StringName func = _get_function_of_node(id); + Ref<VisualScriptNode> node = script->get_node(func, id); + if (Object::cast_to<VisualScriptFunction>(*node)) { + EditorNode::get_singleton()->show_warning(TTR("Can't create function with a function node.")); + return; + } + if (node.is_valid()) { + if (func != function && function != StringName("")) { + EditorNode::get_singleton()->show_warning(TTR("Can't create function of nodes from nodes of multiple functions.")); + return; + } + nodes.insert(id, node); + selections.insert(id); + function = func; + } + } + } + } + + if (nodes.size() == 0) { + return; // nothing to be done if there are no valid nodes selected + } + + Set<VisualScript::SequenceConnection> seqmove; + Set<VisualScript::DataConnection> datamove; + + Set<VisualScript::SequenceConnection> seqext; + Set<VisualScript::DataConnection> dataext; + + int start_node = -1; + Set<int> end_nodes; + if (nodes.size() == 1) { + Ref<VisualScriptNode> nd = script->get_node(function, nodes.front()->key()); + if (nd.is_valid() && nd->has_input_sequence_port()) + start_node = nodes.front()->key(); + else { + EditorNode::get_singleton()->show_warning(TTR("Select atleast one node with sequence port.")); + return; + } + } else { + List<VisualScript::SequenceConnection> seqs; + script->get_sequence_connection_list(function, &seqs); + + if (seqs.size() == 0) { + // in case there are no sequence connections + // select the top most node cause that's probably how + // the user wants to connect the nodes + int top_nd = -1; + Vector2 top; + for (Map<int, Ref<VisualScriptNode> >::Element *E = nodes.front(); E; E = E->next()) { + Ref<VisualScriptNode> nd = script->get_node(function, E->key()); + if (nd.is_valid() && nd->has_input_sequence_port()) { + if (top_nd < 0) { + top_nd = E->key(); + top = script->get_node_position(function, top_nd); + } + Vector2 pos = script->get_node_position(function, E->key()); + if (top.y > pos.y) { + top_nd = E->key(); + top = pos; + } + } + } + Ref<VisualScriptNode> nd = script->get_node(function, top_nd); + if (nd.is_valid() && nd->has_input_sequence_port()) + start_node = top_nd; + else { + EditorNode::get_singleton()->show_warning(TTR("Select atleast one node with sequence port.")); + return; + } + } else { + // pick the node with input sequence + Set<int> nodes_from; + Set<int> nodes_to; + for (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) { + if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) { + seqmove.insert(E->get()); + nodes_from.insert(E->get().from_node); + } else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) { + seqext.insert(E->get()); + } else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) { + if (start_node == -1) { + seqext.insert(E->get()); + start_node = E->get().to_node; + } else { + EditorNode::get_singleton()->show_warning(TTR("Try to only have one sequence input in selection.")); + return; + } + } + nodes_to.insert(E->get().to_node); + } + + // to use to add return nodes + _get_ends(start_node, seqs, selections, end_nodes); + + if (start_node == -1) { + // if we still don't have a start node then + // run through the nodes and select the first tree node + // ie node without any input sequence but output sequence + for (Set<int>::Element *E = nodes_from.front(); E; E = E->next()) { + if (!nodes_to.has(E->get())) { + start_node = E->get(); + } + } + } + } + } + + if (start_node == -1) { + return; // this should not happen, but just in case something goes wrong + } + + List<Variant::Type> inputs; // input types + List<Pair<int, int> > input_connections; + { + List<VisualScript::DataConnection> dats; + script->get_data_connection_list(function, &dats); + for (List<VisualScript::DataConnection>::Element *E = dats.front(); E; E = E->next()) { + if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) { + datamove.insert(E->get()); + } else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) { + // add all these as inputs for the Function + Ref<VisualScriptNode> node = script->get_node(function, E->get().to_node); + if (node.is_valid()) { + dataext.insert(E->get()); + PropertyInfo pi = node->get_input_value_port_info(E->get().to_port); + inputs.push_back(pi.type); + input_connections.push_back(Pair<int, int>(E->get().to_node, E->get().to_port)); + } + } else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) { + dataext.insert(E->get()); + } + } + } + + String new_fn = _validate_name("new_function"); + + Vector2 ofs = _get_available_pos(false, script->get_node_position(function, start_node) - Vector2(80, 150)); + + Ref<VisualScriptFunction> func_node; + func_node.instance(); + func_node->set_name(new_fn); + + undo_redo->create_action(TTR("Create Function")); + + undo_redo->add_do_method(script.ptr(), "add_function", new_fn); + int fn_id = script->get_available_id(); + undo_redo->add_do_method(script.ptr(), "add_node", new_fn, fn_id, func_node, ofs); + undo_redo->add_undo_method(script.ptr(), "remove_function", new_fn); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + + // Move the nodes + + for (Map<int, Ref<VisualScriptNode> >::Element *E = nodes.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "remove_node", function, E->key()); + undo_redo->add_do_method(script.ptr(), "add_node", new_fn, E->key(), E->get(), script->get_node_position(function, E->key())); + + // undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, E->key()); not needed cause we already remove the function :P + undo_redo->add_undo_method(script.ptr(), "add_node", function, E->key(), E->get(), script->get_node_position(function, E->key())); + } + + for (Set<VisualScript::SequenceConnection>::Element *E = seqmove.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, E->get().from_node, E->get().from_output, E->get().to_node); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, E->get().from_node, E->get().from_output, E->get().to_node); + } + + for (Set<VisualScript::DataConnection>::Element *E = datamove.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", function, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + + // Add undo for external connections as well so that it's easier to revert back and forth + // these didn't require do methods as it's already handled internally by other do calls + for (Set<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, E->get().from_node, E->get().from_output, E->get().to_node); + } + for (Set<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) { + undo_redo->add_undo_method(script.ptr(), "data_connect", function, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + + // I don't really think we need support for non sequenced functions at this moment + undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, fn_id, 0, start_node); + + // end nodes are mapped to the return nodes with data connections if possible + int m = 1; + for (Set<int>::Element *G = end_nodes.front(); G; G = G->next()) { + Ref<VisualScriptReturn> ret_node; + ret_node.instance(); + + int ret_id = fn_id + (m++); + selections.insert(ret_id); + Vector2 ofsi = _get_available_pos(false, script->get_node_position(function, G->get()) + Vector2(80, -100)); + undo_redo->add_do_method(script.ptr(), "add_node", new_fn, ret_id, ret_node, ofsi); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, ret_id); + + undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, G->get(), 0, ret_id); + // add data outputs from each of the end_nodes + Ref<VisualScriptNode> vsn = script->get_node(function, G->get()); + if (vsn.is_valid() && vsn->get_output_value_port_count() > 0) { + ret_node->set_enable_return_value(true); + // use the zeroth data port cause that's the likely one that is planned to be used + ret_node->set_return_type(vsn->get_output_value_port_info(0).type); + undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, G->get(), 0, ret_id, 0); + } + } + + // * might make the system more intelligent by checking port from info. + int i = 0; + List<Pair<int, int> >::Element *F = input_connections.front(); + for (List<Variant::Type>::Element *E = inputs.front(); E && F; E = E->next(), F = F->next()) { + func_node->add_argument(E->get(), "arg_" + String::num_int64(i), i); + undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, fn_id, i, F->get().first, F->get().second); + i++; // increment i + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + undo_redo->commit_action(); + + // make sure all Nodes get marked for selection so that they can be moved together + selections.insert(fn_id); + for (int k = 0; k < graph->get_child_count(); k++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(k)); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(selections.has(id)); + } + } + + // Ensure Preview Selection is of newly created function node + if (selections.size()) { + EditorNode::get_singleton()->push_item(func_node.ptr()); + } + + } break; + case REFRESH_GRAPH: { + _update_graph(); + } break; + } +} + +// this is likely going to be very slow and I am not sure if I should keep it +// but I hope that it will not be a problem considering that we won't be creating functions so frequently +// and cyclic connections would be a problem but hopefully we won't let them get to this point +void VisualScriptEditor::_get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes) { + for (const List<VisualScript::SequenceConnection>::Element *E = p_seqs.front(); E; E = E->next()) { + int from = E->get().from_node; + int to = E->get().to_node; + + if (from == p_node && p_selected.has(to)) { + // this is an interior connection move forward to the to node + _get_ends(to, p_seqs, p_selected, r_end_nodes); + } else if (from == p_node && !p_selected.has(to)) { + r_end_nodes.insert(from); + } } } @@ -3316,6 +4456,9 @@ void VisualScriptEditor::_member_rmb_selected(const Vector2 &p_pos) { member_popup->set_position(members->get_global_position() + p_pos); member_popup->set_size(Vector2()); + function_name_edit->set_position(members->get_global_position() + p_pos); + function_name_edit->set_size(Vector2()); + TreeItem *root = members->get_root(); Ref<Texture> del_icon = Control::get_icon("Remove", "EditorIcons"); @@ -3326,6 +4469,8 @@ void VisualScriptEditor::_member_rmb_selected(const Vector2 &p_pos) { member_type = MEMBER_FUNCTION; member_name = ti->get_text(0); + member_popup->add_icon_shortcut(edit_icon, ED_GET_SHORTCUT("visual_script_editor/edit_member"), MEMBER_EDIT); + member_popup->add_separator(); member_popup->add_icon_shortcut(del_icon, ED_GET_SHORTCUT("visual_script_editor/delete_selected"), MEMBER_REMOVE); member_popup->popup(); return; @@ -3393,6 +4538,11 @@ void VisualScriptEditor::_member_option(int p_option) { undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); + } else if (p_option == MEMBER_EDIT) { + selected = members->get_selected()->get_text(0); + function_name_edit->popup(); + function_name_box->set_text(selected); + function_name_box->select_all(); } } break; case MEMBER_VARIABLE: { @@ -3429,7 +4579,6 @@ void VisualScriptEditor::_member_option(int p_option) { undo_redo->add_undo_method(this, "_update_members"); undo_redo->commit_action(); } else if (p_option == MEMBER_EDIT) { - signal_editor->edit(name); edit_signal_dialog->set_title(TTR("Editing Signal:") + " " + name); edit_signal_dialog->popup_centered_minsize(Size2(400, 300) * EDSCALE); @@ -3450,6 +4599,11 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_member_edited", &VisualScriptEditor::_member_edited); ClassDB::bind_method("_member_selected", &VisualScriptEditor::_member_selected); ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members); + ClassDB::bind_method("_members_gui_input", &VisualScriptEditor::_members_gui_input); + ClassDB::bind_method("_member_rmb_selected", &VisualScriptEditor::_member_rmb_selected); + ClassDB::bind_method("_member_option", &VisualScriptEditor::_member_option); + ClassDB::bind_method("_fn_name_box_input", &VisualScriptEditor::_fn_name_box_input); + ClassDB::bind_method("_change_base_type", &VisualScriptEditor::_change_base_type); ClassDB::bind_method("_change_base_type_callback", &VisualScriptEditor::_change_base_type_callback); ClassDB::bind_method("_toggle_tool_script", &VisualScriptEditor::_toggle_tool_script); @@ -3461,7 +4615,14 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_remove_node", &VisualScriptEditor::_remove_node); ClassDB::bind_method("_update_graph", &VisualScriptEditor::_update_graph, DEFVAL(-1)); ClassDB::bind_method("_node_ports_changed", &VisualScriptEditor::_node_ports_changed); - ClassDB::bind_method("_available_node_doubleclicked", &VisualScriptEditor::_available_node_doubleclicked); + + ClassDB::bind_method("_create_function_dialog", &VisualScriptEditor::_create_function_dialog); + ClassDB::bind_method("_create_function", &VisualScriptEditor::_create_function); + ClassDB::bind_method("_add_node_dialog", &VisualScriptEditor::_add_node_dialog); + ClassDB::bind_method("_add_func_input", &VisualScriptEditor::_add_func_input); + ClassDB::bind_method("_remove_func_input", &VisualScriptEditor::_remove_func_input); + ClassDB::bind_method("_deselect_input_names", &VisualScriptEditor::_deselect_input_names); + ClassDB::bind_method("_default_value_edited", &VisualScriptEditor::_default_value_edited); ClassDB::bind_method("_default_value_changed", &VisualScriptEditor::_default_value_changed); ClassDB::bind_method("_menu_option", &VisualScriptEditor::_menu_option); @@ -3474,15 +4635,23 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_selected_new_virtual_method", &VisualScriptEditor::_selected_new_virtual_method); ClassDB::bind_method("_cancel_connect_node", &VisualScriptEditor::_cancel_connect_node); - ClassDB::bind_method("_create_new_node", &VisualScriptEditor::_create_new_node); + ClassDB::bind_method("_create_new_node_from_name", &VisualScriptEditor::_create_new_node_from_name); ClassDB::bind_method("_expression_text_changed", &VisualScriptEditor::_expression_text_changed); + ClassDB::bind_method("_add_input_port", &VisualScriptEditor::_add_input_port); + ClassDB::bind_method("_add_output_port", &VisualScriptEditor::_add_output_port); + ClassDB::bind_method("_remove_input_port", &VisualScriptEditor::_remove_input_port); + ClassDB::bind_method("_remove_output_port", &VisualScriptEditor::_remove_output_port); + ClassDB::bind_method("_change_port_type", &VisualScriptEditor::_change_port_type); + ClassDB::bind_method("_update_node_size", &VisualScriptEditor::_update_node_size); + ClassDB::bind_method("_port_name_focus_out", &VisualScriptEditor::_port_name_focus_out); ClassDB::bind_method("get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw); ClassDB::bind_method("can_drop_data_fw", &VisualScriptEditor::can_drop_data_fw); ClassDB::bind_method("drop_data_fw", &VisualScriptEditor::drop_data_fw); ClassDB::bind_method("_input", &VisualScriptEditor::_input); - ClassDB::bind_method("_members_gui_input", &VisualScriptEditor::_members_gui_input); + ClassDB::bind_method("_graph_gui_input", &VisualScriptEditor::_graph_gui_input); + ClassDB::bind_method("_on_nodes_delete", &VisualScriptEditor::_on_nodes_delete); ClassDB::bind_method("_on_nodes_duplicate", &VisualScriptEditor::_on_nodes_duplicate); @@ -3493,17 +4662,10 @@ void VisualScriptEditor::_bind_methods() { ClassDB::bind_method("_graph_connect_to_empty", &VisualScriptEditor::_graph_connect_to_empty); ClassDB::bind_method("_update_graph_connections", &VisualScriptEditor::_update_graph_connections); - ClassDB::bind_method("_node_filter_changed", &VisualScriptEditor::_node_filter_changed); ClassDB::bind_method("_selected_method", &VisualScriptEditor::_selected_method); ClassDB::bind_method("_draw_color_over_button", &VisualScriptEditor::_draw_color_over_button); - ClassDB::bind_method("_member_rmb_selected", &VisualScriptEditor::_member_rmb_selected); - - ClassDB::bind_method("_member_option", &VisualScriptEditor::_member_option); - - ClassDB::bind_method("_update_available_nodes", &VisualScriptEditor::_update_available_nodes); - ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search); } @@ -3513,6 +4675,8 @@ VisualScriptEditor::VisualScriptEditor() { clipboard = memnew(Clipboard); } updating_graph = false; + saved_pos_dirty = false; + saved_position = Vector2(0, 0); edit_menu = memnew(MenuButton); edit_menu->set_text(TTR("Edit")); @@ -3524,61 +4688,50 @@ VisualScriptEditor::VisualScriptEditor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/copy_nodes"), EDIT_COPY_NODES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), EDIT_PASTE_NODES); - + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/create_function"), EDIT_CREATE_FUNCTION); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/refresh_nodes"), REFRESH_GRAPH); edit_menu->get_popup()->connect("id_pressed", this, "_menu_option"); - left_vsplit = memnew(VSplitContainer); - ScriptEditor::get_singleton()->get_left_list_split()->call_deferred("add_child", left_vsplit); //add but wait until done settig up this - left_vsplit->set_v_size_flags(SIZE_EXPAND_FILL); - left_vsplit->set_stretch_ratio(2); - left_vsplit->hide(); - - VBoxContainer *left_vb = memnew(VBoxContainer); - left_vsplit->add_child(left_vb); - left_vb->set_v_size_flags(SIZE_EXPAND_FILL); - //left_vb->set_custom_minimum_size(Size2(230, 1) * EDSCALE); + members_section = memnew(VBoxContainer); + // Add but wait until done setting up this. + ScriptEditor::get_singleton()->get_left_list_split()->call_deferred("add_child", members_section); + members_section->set_v_size_flags(SIZE_EXPAND_FILL); CheckButton *tool_script_check = memnew(CheckButton); tool_script_check->set_text(TTR("Make Tool:")); - left_vb->add_child(tool_script_check); + members_section->add_child(tool_script_check); tool_script_check->connect("pressed", this, "_toggle_tool_script"); - base_type_select = memnew(Button); - left_vb->add_margin_child(TTR("Base Type:"), base_type_select); - base_type_select->connect("pressed", this, "_change_base_type"); + /// Members /// members = memnew(Tree); - left_vb->add_margin_child(TTR("Members:"), members, true); + members_section->add_margin_child(TTR("Members:"), members, true); + members->set_custom_minimum_size(Size2(0, 50 * EDSCALE)); members->set_hide_root(true); members->connect("button_pressed", this, "_member_button"); members->connect("item_edited", this, "_member_edited"); members->connect("cell_selected", this, "_member_selected", varray(), CONNECT_DEFERRED); members->connect("gui_input", this, "_members_gui_input"); + members->connect("item_rmb_selected", this, "_member_rmb_selected"); + members->set_allow_rmb_select(true); members->set_allow_reselect(true); members->set_hide_folding(true); members->set_drag_forwarding(this); - VBoxContainer *left_vb2 = memnew(VBoxContainer); - left_vsplit->add_child(left_vb2); - left_vb2->set_v_size_flags(SIZE_EXPAND_FILL); - - VBoxContainer *vbc_nodes = memnew(VBoxContainer); - HBoxContainer *hbc_nodes = memnew(HBoxContainer); - node_filter = memnew(LineEdit); - node_filter->connect("text_changed", this, "_node_filter_changed"); - hbc_nodes->add_child(node_filter); - node_filter->set_h_size_flags(SIZE_EXPAND_FILL); - vbc_nodes->add_child(hbc_nodes); - - nodes = memnew(Tree); - vbc_nodes->add_child(nodes); - nodes->set_v_size_flags(SIZE_EXPAND_FILL); + member_popup = memnew(PopupMenu); + add_child(member_popup); + member_popup->connect("id_pressed", this, "_member_option"); - left_vb2->add_margin_child(TTR("Available Nodes:"), vbc_nodes, true); + function_name_edit = memnew(PopupDialog); + function_name_box = memnew(LineEdit); + function_name_edit->add_child(function_name_box); + function_name_edit->set_h_size_flags(SIZE_EXPAND); + function_name_box->connect("gui_input", this, "_fn_name_box_input"); + function_name_box->set_expand_to_text_length(true); + add_child(function_name_edit); - nodes->set_hide_root(true); - nodes->connect("item_activated", this, "_available_node_doubleclicked"); - nodes->set_drag_forwarding(this); + /// Actual Graph /// graph = memnew(GraphEdit); add_child(graph); @@ -3589,10 +4742,77 @@ VisualScriptEditor::VisualScriptEditor() { graph->connect("_end_node_move", this, "_end_node_move"); graph->connect("delete_nodes_request", this, "_on_nodes_delete"); graph->connect("duplicate_nodes_request", this, "_on_nodes_duplicate"); + graph->connect("gui_input", this, "_graph_gui_input"); graph->set_drag_forwarding(this); graph->hide(); graph->connect("scroll_offset_changed", this, "_graph_ofs_changed"); + /// Add Buttons to Top Bar/Zoom bar. + HBoxContainer *graph_hbc = graph->get_zoom_hbox(); + + Label *base_lbl = memnew(Label); + base_lbl->set_text("Change Base Type: "); + graph_hbc->add_child(base_lbl); + + base_type_select = memnew(Button); + base_type_select->connect("pressed", this, "_change_base_type"); + graph_hbc->add_child(base_type_select); + + Button *add_nds = memnew(Button); + add_nds->set_text("Add Nodes..."); + graph_hbc->add_child(add_nds); + add_nds->connect("pressed", this, "_add_node_dialog"); + + Button *fn_btn = memnew(Button); + fn_btn->set_text("Add Function..."); + graph_hbc->add_child(fn_btn); + fn_btn->connect("pressed", this, "_create_function_dialog"); + + // Add Function Dialog. + VBoxContainer *function_vb = memnew(VBoxContainer); + function_vb->set_v_size_flags(SIZE_EXPAND_FILL); + + HBoxContainer *func_name_hbox = memnew(HBoxContainer); + function_vb->add_child(func_name_hbox); + + Label *func_name_label = memnew(Label); + func_name_label->set_text(TTR("Name:")); + func_name_hbox->add_child(func_name_label); + + func_name_box = memnew(LineEdit); + func_name_box->set_h_size_flags(SIZE_EXPAND_FILL); + func_name_box->set_placeholder(TTR("function_name")); + func_name_box->set_text(""); + func_name_box->connect("focus_entered", this, "_deselect_input_names"); + func_name_hbox->add_child(func_name_box); + + // Add minor setting for function if needed, here! + + function_vb->add_child(memnew(HSeparator)); + + Button *add_input_button = memnew(Button); + add_input_button->set_h_size_flags(SIZE_EXPAND_FILL); + add_input_button->set_text(TTR("Add Input")); + add_input_button->connect("pressed", this, "_add_func_input"); + function_vb->add_child(add_input_button); + + func_input_scroll = memnew(ScrollContainer); + func_input_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + function_vb->add_child(func_input_scroll); + + func_input_vbox = memnew(VBoxContainer); + func_input_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + func_input_scroll->add_child(func_input_vbox); + + function_create_dialog = memnew(ConfirmationDialog); + function_create_dialog->set_custom_minimum_size(Size2(450, 300) * EDSCALE); + function_create_dialog->set_v_size_flags(SIZE_EXPAND_FILL); + function_create_dialog->set_title(TTR("Create Function")); + function_create_dialog->add_child(function_vb); + function_create_dialog->get_ok()->set_text(TTR("Create")); + function_create_dialog->get_ok()->connect("pressed", this, "_create_function"); + add_child(function_create_dialog); + select_func_text = memnew(Label); select_func_text->set_text(TTR("Select or create a function to edit its graph.")); select_func_text->set_align(Label::ALIGN_CENTER); @@ -3613,7 +4833,7 @@ VisualScriptEditor::VisualScriptEditor() { hint_text_timer->connect("timeout", this, "_hide_timer"); add_child(hint_text_timer); - //allowed casts (connections) + // Allowed casts (connections). for (int i = 0; i < Variant::VARIANT_MAX; i++) { graph->add_valid_connection_type(Variant::NIL, i); graph->add_valid_connection_type(i, Variant::NIL); @@ -3653,7 +4873,7 @@ VisualScriptEditor::VisualScriptEditor() { edit_variable_edit->edit(variable_editor); select_base_type = memnew(CreateDialog); - select_base_type->set_base_type("Object"); //anything goes + select_base_type->set_base_type("Object"); // Anything goes. select_base_type->connect("create", this, "_change_base_type_callback"); add_child(select_base_type); @@ -3661,8 +4881,8 @@ VisualScriptEditor::VisualScriptEditor() { updating_members = false; - set_process_input(true); //for revert on drag - set_process_unhandled_input(true); //for revert on drag + set_process_input(true); + set_process_unhandled_input(true); default_value_edit = memnew(CustomPropertyEditor); add_child(default_value_edit); @@ -3675,25 +4895,18 @@ VisualScriptEditor::VisualScriptEditor() { new_connect_node_select = memnew(VisualScriptPropertySelector); add_child(new_connect_node_select); + new_connect_node_select->set_resizable(true); new_connect_node_select->connect("selected", this, "_selected_connect_node"); new_connect_node_select->get_cancel()->connect("pressed", this, "_cancel_connect_node"); new_virtual_method_select = memnew(VisualScriptPropertySelector); add_child(new_virtual_method_select); new_virtual_method_select->connect("selected", this, "_selected_new_virtual_method"); - - member_popup = memnew(PopupMenu); - add_child(member_popup); - members->connect("item_rmb_selected", this, "_member_rmb_selected"); - members->set_allow_rmb_select(true); - member_popup->connect("id_pressed", this, "_member_option"); - - _VisualScriptEditor::get_singleton()->connect("custom_nodes_updated", this, "_update_available_nodes"); } VisualScriptEditor::~VisualScriptEditor() { - undo_redo->clear_history(); //avoid crashes + undo_redo->clear_history(); // Avoid crashes. memdelete(signal_editor); memdelete(variable_editor); } @@ -3724,12 +4937,14 @@ static void register_editor_callback() { ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD + KEY_C); ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD + KEY_X); ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD + KEY_V); + ED_SHORTCUT("visual_script_editor/create_function", TTR("Make Function"), KEY_MASK_CMD + KEY_G); + ED_SHORTCUT("visual_script_editor/refresh_nodes", TTR("Refresh Graph"), KEY_MASK_CMD + KEY_R); ED_SHORTCUT("visual_script_editor/edit_member", TTR("Edit Member"), KEY_MASK_CMD + KEY_E); } void VisualScriptEditor::register_editor() { - //too early to register stuff here, request a callback + // Too early to register stuff here, request a callback. EditorNode::add_plugin_init_callback(register_editor_callback); } diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 5df9b1a004..fbf021e7b6 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -59,6 +59,8 @@ class VisualScriptEditor : public ScriptEditorBase { EDIT_COPY_NODES, EDIT_CUT_NODES, EDIT_PASTE_NODES, + EDIT_CREATE_FUNCTION, + REFRESH_GRAPH }; enum PortAction { @@ -79,17 +81,19 @@ class VisualScriptEditor : public ScriptEditorBase { MEMBER_SIGNAL }; - VSplitContainer *left_vsplit; + VBoxContainer *members_section; MenuButton *edit_menu; Ref<VisualScript> script; Button *base_type_select; - GraphEdit *graph; + LineEdit *func_name_box; + ScrollContainer *func_input_scroll; + VBoxContainer *func_input_vbox; + ConfirmationDialog *function_create_dialog; - LineEdit *node_filter; - TextureRect *node_filter_icon; + GraphEdit *graph; VisualScriptEditorSignalEdit *signal_editor; @@ -110,7 +114,8 @@ class VisualScriptEditor : public ScriptEditorBase { UndoRedo *undo_redo; Tree *members; - Tree *nodes; + PopupDialog *function_name_edit; + LineEdit *function_name_box; Label *hint_text; Timer *hint_text_timer; @@ -133,6 +138,7 @@ class VisualScriptEditor : public ScriptEditorBase { HashMap<StringName, Ref<StyleBox> > node_styles; StringName edited_func; + StringName default_func; void _update_graph_connections(); void _update_graph(int p_only_id = -1); @@ -165,9 +171,13 @@ class VisualScriptEditor : public ScriptEditorBase { int port_action_output; Vector2 port_action_pos; int port_action_new_node; - void _port_action_menu(int p_option); - void new_node(Ref<VisualScriptNode> vnode, Vector2 ofs); + bool saved_pos_dirty; + Vector2 saved_position; + + Vector2 mouse_up_position; + + void _port_action_menu(int p_option, const StringName &p_func); void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id); @@ -175,13 +185,13 @@ class VisualScriptEditor : public ScriptEditorBase { void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id); void _cancel_connect_node(); - void _create_new_node(const String &p_text, const String &p_category, const Vector2 &p_point); + int _create_new_node_from_name(const String &p_text, const Vector2 &p_point, const StringName &p_func = StringName()); void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); int error_line; void _node_selected(Node *p_node); - void _center_on_node(int p_id); + void _center_on_node(const StringName &p_func, int p_id); void _node_filter_changed(const String &p_text); void _change_base_type_callback(); @@ -192,7 +202,9 @@ class VisualScriptEditor : public ScriptEditorBase { void _begin_node_move(); void _end_node_move(); - void _move_node(String func, int p_id, const Vector2 &p_to); + void _move_node(const StringName &p_func, int p_id, const Vector2 &p_to); + + void _get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes); void _node_moved(Vector2 p_from, Vector2 p_to, int p_id); void _remove_node(int p_id); @@ -201,21 +213,44 @@ class VisualScriptEditor : public ScriptEditorBase { void _graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos); void _node_ports_changed(const String &p_func, int p_id); - void _available_node_doubleclicked(); + void _node_create(); void _update_available_nodes(); void _member_button(Object *p_item, int p_column, int p_button); void _expression_text_changed(const String &p_text, int p_id); + void _add_input_port(int p_id); + void _add_output_port(int p_id); + void _remove_input_port(int p_id, int p_port); + void _remove_output_port(int p_id, int p_port); + void _change_port_type(int p_select, int p_id, int p_port, bool is_input); + 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); - String revert_on_drag; + Vector2 _get_available_pos(bool centered = true, Vector2 ofs = Vector2()) const; + StringName _get_function_of_node(int p_id) const; - void _input(const Ref<InputEvent> &p_event); + void _move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id); + bool node_has_sequence_connections(const StringName &p_func, int p_id); - void _generic_search(String p_base_type = ""); + void _generic_search(String p_base_type = "", Vector2 pos = Vector2(), bool node_centered = false); + void _input(const Ref<InputEvent> &p_event); + void _graph_gui_input(const Ref<InputEvent> &p_event); void _members_gui_input(const Ref<InputEvent> &p_event); + void _fn_name_box_input(const Ref<InputEvent> &p_event); + void _rename_function(const String &p_name, const String &p_new_name); + + void _create_function_dialog(); + void _create_function(); + void _add_func_input(); + void _remove_func_input(Node *p_node); + void _deselect_input_names(); + void _add_node_dialog(); + void _node_item_selected(); + void _node_item_unselected(); + void _on_nodes_delete(); void _on_nodes_duplicate(); @@ -226,6 +261,10 @@ class VisualScriptEditor : public ScriptEditorBase { int editing_id; int editing_input; + bool can_swap; + int data_disconnect_node; + int data_disconnect_port; + void _default_value_changed(); void _default_value_edited(Node *p_button, int p_id, int p_input_port); @@ -240,7 +279,7 @@ class VisualScriptEditor : public ScriptEditorBase { void _draw_color_over_button(Object *obj, Color p_color); void _button_resource_previewed(const String &p_path, const Ref<Texture> &p_preview, const Ref<Texture> &p_small_preview, Variant p_ud); - VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes); + VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &p_visited_nodes); void _member_rmb_selected(const Vector2 &p_pos); void _member_option(int p_option); diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index c330fa1bc0..4e90a08009 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -133,10 +133,12 @@ int VisualScriptFunctionCall::get_input_value_port_count() const { MethodBind *mb = ClassDB::get_method(_get_base_type(), function); if (mb) { - return mb->get_argument_count() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - use_default_args; + int defaulted_args = mb->get_argument_count() < use_default_args ? mb->get_argument_count() : use_default_args; + return mb->get_argument_count() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - defaulted_args; } - return method_cache.arguments.size() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - use_default_args; + int defaulted_args = method_cache.arguments.size() < use_default_args ? method_cache.arguments.size() : use_default_args; + return method_cache.arguments.size() + (call_mode == CALL_MODE_INSTANCE ? 1 : 0) + (rpc_call_mode >= RPC_RELIABLE_TO_ID ? 1 : 0) - defaulted_args; } } int VisualScriptFunctionCall::get_output_value_port_count() const { @@ -1037,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); @@ -1056,13 +1058,6 @@ PropertyInfo VisualScriptPropertySet::get_output_value_port_info(int p_idx) cons if (call_mode == CALL_MODE_BASIC_TYPE) { return PropertyInfo(basic_type, "out"); } else if (call_mode == CALL_MODE_INSTANCE) { - List<PropertyInfo> props; - ClassDB::get_property_list(_get_base_type(), &props, true); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - if (E->get().name == property) { - return PropertyInfo(E->get().type, "pass", PROPERTY_HINT_TYPE_STRING, E->get().hint_string); - } - } return PropertyInfo(Variant::OBJECT, "pass", PROPERTY_HINT_TYPE_STRING, get_base_type()); } else { return PropertyInfo(); @@ -1813,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_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index 65820b4c15..957127fe61 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -356,6 +356,441 @@ int VisualScriptFunction::get_stack_size() const { } ////////////////////////////////////////// +/////////////////LISTS//////////////////// +////////////////////////////////////////// + +int VisualScriptLists::get_output_sequence_port_count() const { + if (sequenced) + return 1; + return 0; +} +bool VisualScriptLists::has_input_sequence_port() const { + return sequenced; +} + +String VisualScriptLists::get_output_sequence_port_text(int p_port) const { + return ""; +} + +int VisualScriptLists::get_input_value_port_count() const { + return inputports.size(); +} +int VisualScriptLists::get_output_value_port_count() const { + return outputports.size(); +} + +PropertyInfo VisualScriptLists::get_input_value_port_info(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, inputports.size(), PropertyInfo()); + + PropertyInfo pi; + pi.name = inputports[p_idx].name; + pi.type = inputports[p_idx].type; + return pi; +} +PropertyInfo VisualScriptLists::get_output_value_port_info(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, outputports.size(), PropertyInfo()); + + PropertyInfo pi; + pi.name = outputports[p_idx].name; + pi.type = outputports[p_idx].type; + return pi; +} + +bool VisualScriptLists::is_input_port_editable() const { + return ((flags & INPUT_EDITABLE) == INPUT_EDITABLE); +} +bool VisualScriptLists::is_input_port_name_editable() const { + return ((flags & INPUT_NAME_EDITABLE) == INPUT_NAME_EDITABLE); +} +bool VisualScriptLists::is_input_port_type_editable() const { + return ((flags & INPUT_TYPE_EDITABLE) == INPUT_TYPE_EDITABLE); +} + +bool VisualScriptLists::is_output_port_editable() const { + return ((flags & OUTPUT_EDITABLE) == OUTPUT_EDITABLE); +} +bool VisualScriptLists::is_output_port_name_editable() const { + return ((flags & INPUT_NAME_EDITABLE) == INPUT_NAME_EDITABLE); +} +bool VisualScriptLists::is_output_port_type_editable() const { + return ((flags & INPUT_TYPE_EDITABLE) == INPUT_TYPE_EDITABLE); +} + +// for the inspector +bool VisualScriptLists::_set(const StringName &p_name, const Variant &p_value) { + + if (p_name == "input_count" && is_input_port_editable()) { + + int new_argc = p_value; + int argc = inputports.size(); + if (argc == new_argc) + return true; + + inputports.resize(new_argc); + + for (int i = argc; i < new_argc; i++) { + inputports.write[i].name = "arg" + itos(i + 1); + inputports.write[i].type = Variant::NIL; + } + ports_changed_notify(); + _change_notify(); + return true; + } + if (String(p_name).begins_with("input_") && is_input_port_editable()) { + int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1; + ERR_FAIL_INDEX_V(idx, inputports.size(), false); + String what = String(p_name).get_slice("/", 1); + if (what == "type") { + + Variant::Type new_type = Variant::Type(int(p_value)); + inputports.write[idx].type = new_type; + ports_changed_notify(); + + return true; + } + + if (what == "name") { + + inputports.write[idx].name = p_value; + ports_changed_notify(); + return true; + } + } + + if (p_name == "output_count" && is_output_port_editable()) { + + int new_argc = p_value; + int argc = outputports.size(); + if (argc == new_argc) + return true; + + outputports.resize(new_argc); + + for (int i = argc; i < new_argc; i++) { + outputports.write[i].name = "arg" + itos(i + 1); + outputports.write[i].type = Variant::NIL; + } + ports_changed_notify(); + _change_notify(); + return true; + } + if (String(p_name).begins_with("output_") && is_output_port_editable()) { + int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1; + ERR_FAIL_INDEX_V(idx, outputports.size(), false); + String what = String(p_name).get_slice("/", 1); + if (what == "type") { + + Variant::Type new_type = Variant::Type(int(p_value)); + outputports.write[idx].type = new_type; + ports_changed_notify(); + + return true; + } + + if (what == "name") { + + outputports.write[idx].name = p_value; + ports_changed_notify(); + return true; + } + } + + if (p_name == "sequenced/sequenced") { + sequenced = p_value; + ports_changed_notify(); + return true; + } + + return false; +} +bool VisualScriptLists::_get(const StringName &p_name, Variant &r_ret) const { + + if (p_name == "input_count" && is_input_port_editable()) { + r_ret = inputports.size(); + return true; + } + if (String(p_name).begins_with("input_") && is_input_port_editable()) { + int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1; + ERR_FAIL_INDEX_V(idx, inputports.size(), false); + String what = String(p_name).get_slice("/", 1); + if (what == "type") { + r_ret = inputports[idx].type; + return true; + } + if (what == "name") { + r_ret = inputports[idx].name; + return true; + } + } + + if (p_name == "output_count" && is_output_port_editable()) { + r_ret = outputports.size(); + return true; + } + if (String(p_name).begins_with("output_") && is_output_port_editable()) { + int idx = String(p_name).get_slicec('_', 1).get_slicec('/', 0).to_int() - 1; + ERR_FAIL_INDEX_V(idx, outputports.size(), false); + String what = String(p_name).get_slice("/", 1); + if (what == "type") { + r_ret = outputports[idx].type; + return true; + } + if (what == "name") { + r_ret = outputports[idx].name; + return true; + } + } + + if (p_name == "sequenced/sequenced") { + r_ret = sequenced; + return true; + } + + return false; +} +void VisualScriptLists::_get_property_list(List<PropertyInfo> *p_list) const { + + if (is_input_port_editable()) { + p_list->push_back(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,256")); + String argt = "Any"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + + for (int i = 0; i < inputports.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, "input_" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt)); + p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i + 1) + "/name")); + } + } + + if (is_output_port_editable()) { + p_list->push_back(PropertyInfo(Variant::INT, "output_count", PROPERTY_HINT_RANGE, "0,256")); + String argt = "Any"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + + for (int i = 0; i < outputports.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, "output_" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt)); + p_list->push_back(PropertyInfo(Variant::STRING, "output_" + itos(i + 1) + "/name")); + } + } + p_list->push_back(PropertyInfo(Variant::BOOL, "sequenced/sequenced")); +} + +// input data port interaction +void VisualScriptLists::add_input_data_port(Variant::Type p_type, const String &p_name, int p_index) { + + if (!is_input_port_editable()) + return; + + Port inp; + inp.name = p_name; + inp.type = p_type; + if (p_index >= 0) + inputports.insert(p_index, inp); + else + inputports.push_back(inp); + + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::set_input_data_port_type(int p_idx, Variant::Type p_type) { + + if (!is_input_port_type_editable()) + return; + + ERR_FAIL_INDEX(p_idx, inputports.size()); + + inputports.write[p_idx].type = p_type; + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::set_input_data_port_name(int p_idx, const String &p_name) { + + if (!is_input_port_name_editable()) + return; + + ERR_FAIL_INDEX(p_idx, inputports.size()); + + inputports.write[p_idx].name = p_name; + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::remove_input_data_port(int p_argidx) { + + if (!is_input_port_editable()) + return; + + ERR_FAIL_INDEX(p_argidx, inputports.size()); + + inputports.remove(p_argidx); + + ports_changed_notify(); + _change_notify(); +} + +// output data port interaction +void VisualScriptLists::add_output_data_port(Variant::Type p_type, const String &p_name, int p_index) { + + if (!is_output_port_editable()) + return; + + Port out; + out.name = p_name; + out.type = p_type; + if (p_index >= 0) + outputports.insert(p_index, out); + else + outputports.push_back(out); + + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::set_output_data_port_type(int p_idx, Variant::Type p_type) { + + if (!is_output_port_type_editable()) + return; + + ERR_FAIL_INDEX(p_idx, outputports.size()); + + outputports.write[p_idx].type = p_type; + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::set_output_data_port_name(int p_idx, const String &p_name) { + + if (!is_output_port_name_editable()) + return; + + ERR_FAIL_INDEX(p_idx, outputports.size()); + + outputports.write[p_idx].name = p_name; + ports_changed_notify(); + _change_notify(); +} +void VisualScriptLists::remove_output_data_port(int p_argidx) { + + if (!is_output_port_editable()) + return; + + ERR_FAIL_INDEX(p_argidx, outputports.size()); + + outputports.remove(p_argidx); + + ports_changed_notify(); + _change_notify(); +} + +// sequences +void VisualScriptLists::set_sequenced(bool p_enable) { + if (sequenced == p_enable) + return; + sequenced = p_enable; + ports_changed_notify(); +} +bool VisualScriptLists::is_sequenced() const { + return sequenced; +} + +VisualScriptLists::VisualScriptLists() { + // initialize + sequenced = false; + flags = 0; +} + +void VisualScriptLists::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_input_data_port", "type", "name", "index"), &VisualScriptLists::add_input_data_port); + ClassDB::bind_method(D_METHOD("set_input_data_port_name", "index", "name"), &VisualScriptLists::set_input_data_port_name); + ClassDB::bind_method(D_METHOD("set_input_data_port_type", "index", "type"), &VisualScriptLists::set_input_data_port_type); + ClassDB::bind_method(D_METHOD("remove_input_data_port", "index"), &VisualScriptLists::remove_input_data_port); + + ClassDB::bind_method(D_METHOD("add_output_data_port", "type", "name", "index"), &VisualScriptLists::add_output_data_port); + ClassDB::bind_method(D_METHOD("set_output_data_port_name", "index", "name"), &VisualScriptLists::set_output_data_port_name); + ClassDB::bind_method(D_METHOD("set_output_data_port_type", "index", "type"), &VisualScriptLists::set_output_data_port_type); + ClassDB::bind_method(D_METHOD("remove_output_data_port", "index"), &VisualScriptLists::remove_output_data_port); +} + +////////////////////////////////////////// +//////////////COMPOSEARRAY//////////////// +////////////////////////////////////////// + +int VisualScriptComposeArray::get_output_sequence_port_count() const { + if (sequenced) + return 1; + return 0; +} +bool VisualScriptComposeArray::has_input_sequence_port() const { + return sequenced; +} + +String VisualScriptComposeArray::get_output_sequence_port_text(int p_port) const { + return ""; +} + +int VisualScriptComposeArray::get_input_value_port_count() const { + return inputports.size(); +} +int VisualScriptComposeArray::get_output_value_port_count() const { + return 1; +} + +PropertyInfo VisualScriptComposeArray::get_input_value_port_info(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, inputports.size(), PropertyInfo()); + + PropertyInfo pi; + pi.name = inputports[p_idx].name; + pi.type = inputports[p_idx].type; + return pi; +} +PropertyInfo VisualScriptComposeArray::get_output_value_port_info(int p_idx) const { + PropertyInfo pi; + pi.name = "out"; + pi.type = Variant::ARRAY; + return pi; +} + +String VisualScriptComposeArray::get_caption() const { + return "Compose Array"; +} +String VisualScriptComposeArray::get_text() const { + return ""; +} + +class VisualScriptComposeArrayNode : public VisualScriptNodeInstance { +public: + int input_count = 0; + virtual int get_working_memory_size() const { return 0; } + + virtual int step(const Variant **p_inputs, Variant **p_outputs, StartMode p_start_mode, Variant *p_working_mem, Variant::CallError &r_error, String &r_error_str) { + + if (input_count > 0) { + Array arr; + for (int i = 0; i < input_count; i++) + arr.push_back((*p_inputs[i])); + Variant va = Variant(arr); + + *p_outputs[0] = va; + } + + return 0; + } +}; + +VisualScriptNodeInstance *VisualScriptComposeArray::instance(VisualScriptInstance *p_instance) { + + VisualScriptComposeArrayNode *instance = memnew(VisualScriptComposeArrayNode); + instance->input_count = inputports.size(); + return instance; +} + +VisualScriptComposeArray::VisualScriptComposeArray() { + // initialize stuff here + sequenced = false; + flags = INPUT_EDITABLE; +} + +////////////////////////////////////////// ////////////////OPERATOR////////////////// ////////////////////////////////////////// @@ -3640,6 +4075,14 @@ VisualScriptDeconstruct::VisualScriptDeconstruct() { type = Variant::NIL; } +template <Variant::Type T> +static Ref<VisualScriptNode> create_node_deconst_typed(const String &p_name) { + Ref<VisualScriptDeconstruct> node; + node.instance(); + node->set_deconstruct_type(T); + return node; +} + void register_visual_script_nodes() { VisualScriptLanguage::singleton->add_register_func("data/set_variable", create_node_generic<VisualScriptVariableSet>); @@ -3697,7 +4140,17 @@ void register_visual_script_nodes() { VisualScriptLanguage::singleton->add_register_func("operators/logic/in", create_op_node<Variant::OP_IN>); VisualScriptLanguage::singleton->add_register_func("operators/logic/select", create_node_generic<VisualScriptSelect>); - VisualScriptLanguage::singleton->add_register_func("functions/deconstruct", create_node_generic<VisualScriptDeconstruct>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::VECTOR2), create_node_deconst_typed<Variant::Type::VECTOR2>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::VECTOR3), create_node_deconst_typed<Variant::Type::VECTOR3>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::COLOR), create_node_deconst_typed<Variant::Type::COLOR>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::RECT2), create_node_deconst_typed<Variant::Type::RECT2>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::TRANSFORM2D), create_node_deconst_typed<Variant::Type::TRANSFORM2D>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::PLANE), create_node_deconst_typed<Variant::Type::PLANE>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::QUAT), create_node_deconst_typed<Variant::Type::QUAT>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::AABB), create_node_deconst_typed<Variant::Type::AABB>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::BASIS), create_node_deconst_typed<Variant::Type::BASIS>); + VisualScriptLanguage::singleton->add_register_func("functions/deconstruct/" + Variant::get_type_name(Variant::Type::TRANSFORM), create_node_deconst_typed<Variant::Type::TRANSFORM>); + VisualScriptLanguage::singleton->add_register_func("functions/compose_array", create_node_generic<VisualScriptComposeArray>); for (int i = 1; i < Variant::VARIANT_MAX; i++) { diff --git a/modules/visual_script/visual_script_nodes.h b/modules/visual_script/visual_script_nodes.h index 762a1bdfb6..c7354cb0d8 100644 --- a/modules/visual_script/visual_script_nodes.h +++ b/modules/visual_script/visual_script_nodes.h @@ -103,6 +103,103 @@ public: VisualScriptFunction(); }; +class VisualScriptLists : public VisualScriptNode { + + GDCLASS(VisualScriptLists, VisualScriptNode) + + struct Port { + String name; + Variant::Type type; + }; + +protected: + Vector<Port> inputports; + Vector<Port> outputports; + + enum { + OUTPUT_EDITABLE = 0x0001, + OUTPUT_NAME_EDITABLE = 0x0002, + OUTPUT_TYPE_EDITABLE = 0x0004, + INPUT_EDITABLE = 0x0008, + INPUT_NAME_EDITABLE = 0x000F, + INPUT_TYPE_EDITABLE = 0x0010, + }; + + int flags; + + bool sequenced; + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + virtual bool is_output_port_editable() const; + virtual bool is_output_port_name_editable() const; + virtual bool is_output_port_type_editable() const; + + virtual bool is_input_port_editable() const; + virtual bool is_input_port_name_editable() const; + virtual bool is_input_port_type_editable() const; + + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + virtual String get_output_sequence_port_text(int p_port) const; + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const = 0; + virtual String get_text() const = 0; + virtual String get_category() const = 0; + + void add_input_data_port(Variant::Type p_type, const String &p_name, int p_index = -1); + void set_input_data_port_type(int p_idx, Variant::Type p_type); + void set_input_data_port_name(int p_idx, const String &p_name); + void remove_input_data_port(int p_argidx); + + void add_output_data_port(Variant::Type p_type, const String &p_name, int p_index = -1); + void set_output_data_port_type(int p_idx, Variant::Type p_type); + void set_output_data_port_name(int p_idx, const String &p_name); + void remove_output_data_port(int p_argidx); + + void set_sequenced(bool p_enable); + bool is_sequenced() const; + + VisualScriptLists(); +}; + +class VisualScriptComposeArray : public VisualScriptLists { + + GDCLASS(VisualScriptComposeArray, VisualScriptLists) + +public: + virtual int get_output_sequence_port_count() const; + virtual bool has_input_sequence_port() const; + + virtual String get_output_sequence_port_text(int p_port) const; + + virtual int get_input_value_port_count() const; + virtual int get_output_value_port_count() const; + + virtual PropertyInfo get_input_value_port_info(int p_idx) const; + virtual PropertyInfo get_output_value_port_info(int p_idx) const; + + virtual String get_caption() const; + virtual String get_text() const; + virtual String get_category() const { return "functions"; } + + virtual VisualScriptNodeInstance *instance(VisualScriptInstance *p_instance); + + VisualScriptComposeArray(); +}; + class VisualScriptOperator : public VisualScriptNode { GDCLASS(VisualScriptOperator, VisualScriptNode); diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 764807cffd..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" @@ -200,13 +200,10 @@ void VisualScriptPropertySelector::_update_search() { Object *obj = ObjectDB::get_instance(script); if (Object::cast_to<Script>(obj)) { - methods.push_back(MethodInfo("*Script Methods")); Object::cast_to<Script>(obj)->get_script_method_list(&methods); - - } else { - methods.push_back(MethodInfo("*" + String(E->get()))); - ClassDB::get_method_list(E->get(), &methods, true, true); } + + ClassDB::get_method_list(E->get(), &methods, true, true); } } for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { @@ -274,6 +271,7 @@ void VisualScriptPropertySelector::_update_search() { get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); + get_visual_node_names("functions/deconstruct/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box); if (type == Variant::INT) { get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box); @@ -327,7 +325,7 @@ void VisualScriptPropertySelector::create_visualscript_item(const String &name, } } -void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &filter, bool &found, TreeItem *const root, LineEdit *const search_box) { +void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box) { Map<String, TreeItem *> path_cache; List<String> fnodes; @@ -338,37 +336,59 @@ void VisualScriptPropertySelector::get_visual_node_names(const String &root_filt continue; } Vector<String> path = E->get().split("/"); - bool is_filter = false; - for (Set<String>::Element *F = filter.front(); F; F = F->next()) { - if (path.size() >= 2 && path[1].findn(F->get()) != -1) { - is_filter = true; + + // check if the name has the filter + bool in_filter = false; + Vector<String> tx_filters = search_box->get_text().split(" "); + for (int i = 0; i < tx_filters.size(); i++) { + if (tx_filters[i] == "") { + in_filter = true; + } else { + in_filter = false; + } + if (E->get().findn(tx_filters[i]) != -1) { + in_filter = true; break; } } - if (is_filter) { + if (!in_filter) { continue; } - if (search_box->get_text() != String() && E->get().findn(search_box->get_text()) == -1) { + bool in_modifier = false | p_modifiers.empty(); + for (Set<String>::Element *F = p_modifiers.front(); F && in_modifier; F = F->next()) { + if (E->get().findn(F->get()) != -1) + in_modifier = true; + } + if (!in_modifier) { continue; } + TreeItem *item = search_options->create_item(root); - VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(E->get()); + Ref<VisualScriptOperator> vnode_operator = vnode; String type_name; - if (vnode_operator != NULL) { + if (vnode_operator.is_valid()) { String type; if (path.size() >= 2) { type = path[1]; } type_name = type.capitalize() + " "; } - VisualScriptFunctionCall *vnode_function_call = Object::cast_to<VisualScriptFunctionCall>(*VisualScriptLanguage::singleton->create_node_from_name(E->get())); - if (vnode_function_call != NULL) { + Ref<VisualScriptFunctionCall> vnode_function_call = vnode; + if (vnode_function_call.is_valid()) { String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type()); type_name = basic_type.capitalize() + " "; } - - Vector<String> desc = path[path.size() - 1].replace("(", "( ").replace(")", " )").replace(",", ", ").split(" "); + Ref<VisualScriptConstructor> vnode_constructor = vnode; + if (vnode_constructor.is_valid()) { + type_name = "Construct "; + } + Ref<VisualScriptDeconstruct> vnode_deconstruct = vnode; + if (vnode_deconstruct.is_valid()) { + type_name = "Deconstruct "; + } + Vector<String> desc = path[path.size() - 1].replace("(", " ").replace(")", " ").replace(",", " ").split(" "); for (int i = 0; i < desc.size(); i++) { desc.write[i] = desc[i].capitalize(); if (desc[i].ends_with(",")) { @@ -504,7 +524,7 @@ void VisualScriptPropertySelector::_notification(int p_what) { } } -void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting) { +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { base_type = p_base; selected = p_current; @@ -515,7 +535,10 @@ void VisualScriptPropertySelector::select_method_from_base_type(const String &p_ virtuals_only = p_virtuals_only; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); connecting = p_connecting; @@ -526,7 +549,7 @@ void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> & type_filter = p_type_filter; } -void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting) { +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting, bool clear_text) { base_type = p_base; selected = p_current; @@ -538,7 +561,10 @@ void VisualScriptPropertySelector::select_from_base_type(const String &p_base, c virtuals_only = p_virtuals_only; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); seq_connect = p_seq_connect; connecting = p_connecting; @@ -546,7 +572,7 @@ void VisualScriptPropertySelector::select_from_base_type(const String &p_base, c _update_search(); } -void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting) { +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting, bool clear_text) { ERR_FAIL_COND(p_script.is_null()); base_type = p_script->get_instance_base_type(); @@ -559,7 +585,10 @@ void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_scrip virtuals_only = false; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); seq_connect = false; connecting = p_connecting; @@ -567,7 +596,7 @@ void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_scrip _update_search(); } -void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting) { +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting, bool clear_text) { ERR_FAIL_COND(p_type == Variant::NIL); base_type = ""; selected = p_current; @@ -579,7 +608,10 @@ void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, virtuals_only = false; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); seq_connect = false; connecting = p_connecting; @@ -587,7 +619,7 @@ void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, _update_search(); } -void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting) { +void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting, bool clear_text) { base_type = p_type; selected = p_current; type = Variant::NIL; @@ -598,7 +630,10 @@ void VisualScriptPropertySelector::select_from_action(const String &p_type, cons virtuals_only = false; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); seq_connect = true; connecting = p_connecting; @@ -606,8 +641,8 @@ void VisualScriptPropertySelector::select_from_action(const String &p_type, cons _update_search(); } -void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting) { - base_type = ""; +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting, const String &p_basetype, bool clear_text) { + base_type = p_basetype; selected = p_current; type = Variant::NIL; script = 0; @@ -617,7 +652,10 @@ void VisualScriptPropertySelector::select_from_instance(Object *p_instance, cons virtuals_only = false; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); seq_connect = false; connecting = p_connecting; @@ -625,7 +663,7 @@ void VisualScriptPropertySelector::select_from_instance(Object *p_instance, cons _update_search(); } -void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting) { +void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting, bool clear_text) { base_type = p_base; selected = ""; type = Variant::NIL; @@ -635,7 +673,10 @@ void VisualScriptPropertySelector::select_from_visual_script(const String &p_bas instance = NULL; virtuals_only = false; show_window(.5f); - search_box->set_text(""); + if (clear_text) + search_box->set_text(""); + else + search_box->select_all(); search_box->grab_focus(); connecting = p_connecting; @@ -646,7 +687,7 @@ void VisualScriptPropertySelector::show_window(float p_screen_ratio) { Rect2 rect; Point2 window_size = get_viewport_rect().size; rect.size = (window_size * p_screen_ratio).floor(); - rect.size.x = rect.size.x / 1.25f; + rect.size.x = rect.size.x / 2.2f; rect.position = ((window_size - rect.size) / 2.0f).floor(); popup(rect); } diff --git a/modules/visual_script/visual_script_property_selector.h b/modules/visual_script/visual_script_property_selector.h index 6235e4ba1d..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); @@ -74,13 +74,13 @@ protected: static void _bind_methods(); public: - void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true); - void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true); - void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true); - void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true); - void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true); - void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true); - void select_from_visual_script(const String &p_base, const bool p_connecting = true); + void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true, bool clear_text = true); + void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); + void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); + void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); + void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true, const String &p_basetype = "", bool clear_text = true); + void select_from_visual_script(const String &p_base, const bool p_connecting = true, bool clear_text = true); void show_window(float p_screen_ratio); 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; |