diff options
139 files changed, 2501 insertions, 826 deletions
diff --git a/SConstruct b/SConstruct index aa5e3a98c8..128c5b0e92 100644 --- a/SConstruct +++ b/SConstruct @@ -484,6 +484,13 @@ if selected_platform in platform_list: if env['minizip']: env.Append(CPPDEFINES=['MINIZIP_ENABLED']) + editor_module_list = ['regex'] + for x in editor_module_list: + if not env['module_' + x + '_enabled']: + if env['tools']: + print("Build option 'module_" + x + "_enabled=no' cannot be used with 'tools=yes' (editor), only with 'tools=no' (export template).") + sys.exit(255) + if not env['verbose']: methods.no_verbose(sys, env) diff --git a/core/io/image_loader.h b/core/io/image_loader.h index ae4b72a534..f1fccc5230 100644 --- a/core/io/image_loader.h +++ b/core/io/image_loader.h @@ -41,20 +41,8 @@ @author Juan Linietsky <reduzio@gmail.com> */ -/** - * @class ImageScanLineLoader - * @author Juan Linietsky <reduzio@gmail.com> - * - - */ class ImageLoader; -/** - * @class ImageLoader - * Base Class and singleton for loading images from disk - * Can load images in one go, or by scanline - */ - class ImageFormatLoader { friend class ImageLoader; friend class ResourceFormatLoaderImage; diff --git a/core/math/a_star.h b/core/math/a_star.h index ec333efc1d..cbabcce974 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -32,7 +32,6 @@ #define ASTAR_H #include "core/reference.h" -#include "core/self_list.h" /** A* pathfinding algorithm diff --git a/core/math/transform.cpp b/core/math/transform.cpp index 7ff7cac914..4056975da8 100644 --- a/core/math/transform.cpp +++ b/core/math/transform.cpp @@ -213,3 +213,8 @@ Transform::Transform(const Basis &p_basis, const Vector3 &p_origin) : basis(p_basis), origin(p_origin) { } + +Transform::Transform(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz, real_t ox, real_t oy, real_t oz) { + basis = Basis(xx, xy, xz, yx, yy, yz, zx, zy, zz); + origin = Vector3(ox, oy, oz); +} diff --git a/core/math/transform.h b/core/math/transform.h index 2f43f6b035..df0a036218 100644 --- a/core/math/transform.h +++ b/core/math/transform.h @@ -108,6 +108,7 @@ public: operator String() const; + Transform(real_t xx, real_t xy, real_t xz, real_t yx, real_t yy, real_t yz, real_t zx, real_t zy, real_t zz, real_t ox, real_t oy, real_t oz); Transform(const Basis &p_basis, const Vector3 &p_origin = Vector3()); Transform() {} }; diff --git a/core/os/dir_access.h b/core/os/dir_access.h index 704eedae5b..3c0528112b 100644 --- a/core/os/dir_access.h +++ b/core/os/dir_access.h @@ -97,6 +97,18 @@ public: virtual Error rename(String p_from, String p_to) = 0; virtual Error remove(String p_name) = 0; + // Meant for editor code when we want to quickly remove a file without custom + // handling (e.g. removing a cache file). + static void remove_file_or_error(String p_path) { + DirAccess *da = create(ACCESS_FILESYSTEM); + if (da->file_exists(p_path)) { + if (da->remove(p_path) != OK) { + ERR_FAIL_MSG("Cannot remove file or directory: " + p_path); + } + } + memdelete(da); + } + virtual String get_filesystem_type() const = 0; static String get_full_path(const String &p_path, AccessType p_access); static DirAccess *create_for_path(const String &p_path); diff --git a/core/script_debugger_remote.cpp b/core/script_debugger_remote.cpp index 5f01e043c4..6dd9054aaa 100644 --- a/core/script_debugger_remote.cpp +++ b/core/script_debugger_remote.cpp @@ -601,9 +601,19 @@ void ScriptDebuggerRemote::_send_object_id(ObjectID p_id) { } } } + if (Node *node = Object::cast_to<Node>(obj)) { - PropertyInfo pi(Variant::NODE_PATH, String("Node/path")); - properties.push_front(PropertyDesc(pi, node->get_path())); + // in some cases node will not be in tree here + // for instance where it created as variable and not yet added to tree + // in such cases we can't ask for it's path + if (node->is_inside_tree()) { + PropertyInfo pi(Variant::NODE_PATH, String("Node/path")); + properties.push_front(PropertyDesc(pi, node->get_path())); + } else { + PropertyInfo pi(Variant::STRING, String("Node/path")); + properties.push_front(PropertyDesc(pi, "[Orphan]")); + } + } else if (Resource *res = Object::cast_to<Resource>(obj)) { if (Script *s = Object::cast_to<Script>(res)) { Map<StringName, Variant> constants; diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 1f6e5bb653..eca5d0567b 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -1955,19 +1955,27 @@ void register_variant_methods() { _VariantCall::add_variant_constant(Variant::VECTOR2, "UP", Vector2(0, -1)); _VariantCall::add_variant_constant(Variant::VECTOR2, "DOWN", Vector2(0, 1)); - _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "IDENTITY", Transform2D(1, 0, 0, 1, 0, 0)); + _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "IDENTITY", Transform2D()); _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_X", Transform2D(-1, 0, 0, 1, 0, 0)); _VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_Y", Transform2D(1, 0, 0, -1, 0, 0)); - Transform identity_transform, transform_x, transform_y, transform_z; - identity_transform.set(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0); + Transform identity_transform = Transform(); + Transform flip_x_transform = Transform(-1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0); + Transform flip_y_transform = Transform(1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0); + Transform flip_z_transform = Transform(1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0); _VariantCall::add_variant_constant(Variant::TRANSFORM, "IDENTITY", identity_transform); - transform_x.set(-1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0); - _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_X", transform_x); - transform_y.set(1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0, 0); - _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_Y", transform_y); - transform_z.set(1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0); - _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_Z", transform_z); + _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_X", flip_x_transform); + _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_Y", flip_y_transform); + _VariantCall::add_variant_constant(Variant::TRANSFORM, "FLIP_Z", flip_z_transform); + + Basis identity_basis = Basis(); + Basis flip_x_basis = Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1); + Basis flip_y_basis = Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); + Basis flip_z_basis = Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); + _VariantCall::add_variant_constant(Variant::BASIS, "IDENTITY", identity_basis); + _VariantCall::add_variant_constant(Variant::BASIS, "FLIP_X", flip_x_basis); + _VariantCall::add_variant_constant(Variant::BASIS, "FLIP_Y", flip_y_basis); + _VariantCall::add_variant_constant(Variant::BASIS, "FLIP_Z", flip_z_basis); _VariantCall::add_variant_constant(Variant::PLANE, "PLANE_YZ", Plane(Vector3(1, 0, 0), 0)); _VariantCall::add_variant_constant(Variant::PLANE, "PLANE_XZ", Plane(Vector3(0, 1, 0), 0)); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 0428140908..b25de3cf99 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1317,10 +1317,10 @@ No hint for the edited property. </constant> <constant name="PROPERTY_HINT_RANGE" value="1" enum="PropertyHint"> - Hints that an integer or float property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"allow_greater"[/code] and/or [code]"allow_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"-360,360,1,allow_greater,allow_lesser"[/code]. + Hints that an integer or float property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"-360,360,1,or_greater,or_lesser"[/code]. </constant> <constant name="PROPERTY_HINT_EXP_RANGE" value="2" enum="PropertyHint"> - Hints that an integer or float property should be within an exponential range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"allow_greater"[/code] and/or [code]"allow_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"0.01,100,0.01,allow_greater"[/code]. + Hints that an integer or float property should be within an exponential range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"0.01,100,0.01,or_greater"[/code]. </constant> <constant name="PROPERTY_HINT_ENUM" value="3" enum="PropertyHint"> Hints that an integer, float or string property is an enumerated value to pick in a list specified via a hint string such as [code]"Hello,Something,Else"[/code]. diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 6f82b103db..2d3ceebed5 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -32,12 +32,6 @@ Adds an [AudioEffect] effect to the bus [code]bus_idx[/code] at [code]at_position[/code]. </description> </method> - <method name="capture_get_device"> - <return type="String"> - </return> - <description> - </description> - </method> <method name="capture_get_device_list"> <return type="Array"> </return> @@ -45,14 +39,6 @@ Returns the names of all audio input devices detected on the system. </description> </method> - <method name="capture_set_device"> - <return type="void"> - </return> - <argument index="0" name="name" type="String"> - </argument> - <description> - </description> - </method> <method name="capture_start"> <return type="int" enum="Error"> </return> @@ -423,17 +409,25 @@ <member name="bus_count" type="int" setter="set_bus_count" getter="get_bus_count" default="1"> Number of available audio buses. </member> + <member name="capture_device" type="String" setter="capture_set_device" getter="capture_get_device" default=""""> + Name of the current device for audio input (see [method capture_get_device_list]). + </member> <member name="device" type="String" setter="set_device" getter="get_device" default=""Default""> Name of the current device for audio output (see [method get_device_list]). </member> - <member name="capture_device" type="String" setter="capture_set_device" getter="capture_get_device" default=""Default""> - Name of the current device for audio input (see [method capture_get_device_list]). - </member> <member name="global_rate_scale" type="float" setter="set_global_rate_scale" getter="get_global_rate_scale" default="1.0"> Scales the rate at which audio is played (i.e. setting it to [code]0.5[/code] will make the audio be played twice as fast). </member> </members> <signals> + <signal name="audio_mix_callback"> + <description> + </description> + </signal> + <signal name="audio_update_callback"> + <description> + </description> + </signal> <signal name="bus_layout_changed"> <description> Emitted when the [AudioBusLayout] changes. diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 4d5c76a75c..df9438e695 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -208,5 +208,13 @@ </member> </members> <constants> + <constant name="IDENTITY" value="Basis( 1, 0, 0, 0, 1, 0, 0, 0, 1 )"> + </constant> + <constant name="FLIP_X" value="Basis( -1, 0, 0, 0, 1, 0, 0, 0, 1 )"> + </constant> + <constant name="FLIP_Y" value="Basis( 1, 0, 0, 0, -1, 0, 0, 0, 1 )"> + </constant> + <constant name="FLIP_Z" value="Basis( 1, 0, 0, 0, 1, 0, 0, 0, -1 )"> + </constant> </constants> </class> diff --git a/doc/classes/Camera2D.xml b/doc/classes/Camera2D.xml index f524a02934..16fb483249 100644 --- a/doc/classes/Camera2D.xml +++ b/doc/classes/Camera2D.xml @@ -110,7 +110,7 @@ <member name="drag_margin_bottom" type="float" setter="set_drag_margin" getter="get_drag_margin" default="0.2"> Bottom margin needed to drag the camera. A value of [code]1[/code] makes the camera move only when reaching the edge of the screen. </member> - <member name="drag_margin_h_enabled" type="bool" setter="set_h_drag_enabled" getter="is_h_drag_enabled" default="true"> + <member name="drag_margin_h_enabled" type="bool" setter="set_h_drag_enabled" getter="is_h_drag_enabled" default="false"> If [code]true[/code], the camera only moves when reaching the horizontal drag margins. If [code]false[/code], the camera moves horizontally regardless of margins. </member> <member name="drag_margin_left" type="float" setter="set_drag_margin" getter="get_drag_margin" default="0.2"> @@ -122,7 +122,7 @@ <member name="drag_margin_top" type="float" setter="set_drag_margin" getter="get_drag_margin" default="0.2"> Top margin needed to drag the camera. A value of [code]1[/code] makes the camera move only when reaching the edge of the screen. </member> - <member name="drag_margin_v_enabled" type="bool" setter="set_v_drag_enabled" getter="is_v_drag_enabled" default="true"> + <member name="drag_margin_v_enabled" type="bool" setter="set_v_drag_enabled" getter="is_v_drag_enabled" default="false"> If [code]true[/code], the camera only moves when reaching the vertical drag margins. If [code]false[/code], the camera moves vertically regardless of margins. </member> <member name="editor_draw_drag_margin" type="bool" setter="set_margin_drawing_enabled" getter="is_margin_drawing_enabled" default="false"> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 6c3cf66c20..acceffb3bf 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -229,7 +229,7 @@ [codeblock] func _ready(): modulate = get_color("font_color", "Button") #get the color defined for button fonts - [/codeblock] + [/codeblock] </description> </method> <method name="get_combined_minimum_size" qualifiers="const"> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index d55e810c9e..4f7a6d89a9 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -169,6 +169,22 @@ Selects the file, with the path provided by [code]file[/code], in the FileSystem dock. </description> </method> + <method name="set_distraction_free_mode"> + <return type="void"> + </return> + <argument index="0" name="enter" type="bool"> + </argument> + <description> + </description> + </method> + <method name="set_main_screen_editor"> + <return type="void"> + </return> + <argument index="0" name="name" type="String"> + </argument> + <description> + </description> + </method> <method name="set_plugin_enabled"> <return type="void"> </return> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index fddc5e9d36..89e2f0580b 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -128,7 +128,7 @@ <argument index="3" name="ud" type="Variant" default="null"> </argument> <description> - Adds a custom menu to [b]Project > Tools[/b] as [code]name[/code] that calls [code]callback[/code] on an instance of [code]handler[/code] with a parameter [code]ud[/code] when user activates it. + Adds a custom menu item to [b]Project > Tools[/b] as [code]name[/code] that calls [code]callback[/code] on an instance of [code]handler[/code] with a parameter [code]ud[/code] when user activates it. </description> </method> <method name="add_tool_submenu_item"> @@ -139,7 +139,7 @@ <argument index="1" name="submenu" type="Object"> </argument> <description> - Like [method add_tool_menu_item] but adds the [code]submenu[/code] item inside the [code]name[/code] menu. + Adds a custom submenu under [b]Project > Tools >[/b] [code]name[/code]. [code]submenu[/code] should be an object of class [PopupMenu]. This submenu should be cleaned up using [code]remove_tool_menu_item(name)[/code]. </description> </method> <method name="apply_changes" qualifiers="virtual"> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index a4df0d5c19..10be66feb8 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -406,24 +406,24 @@ <description> </description> </method> - <method name="save_png" qualifiers="const"> + <method name="save_exr" qualifiers="const"> <return type="int" enum="Error"> </return> <argument index="0" name="path" type="String"> </argument> + <argument index="1" name="grayscale" type="bool" default="false"> + </argument> <description> - Saves the image as a PNG file to [code]path[/code]. + Saves the image as an EXR file to [code]path[/code]. If grayscale is true and the image has only one channel, it will be saved explicitely as monochrome rather than one red channel. This function will return [constant ERR_UNAVAILABLE] if Godot was compiled without the TinyEXR module. </description> </method> - <method name="save_exr" qualifiers="const"> + <method name="save_png" qualifiers="const"> <return type="int" enum="Error"> </return> <argument index="0" name="path" type="String"> </argument> - <argument index="1" name="grayscale" type="bool" default="false"> - </argument> <description> - Saves the image as an EXR file to [code]path[/code]. If grayscale is true and the image has only one channel, it will be saved explicitely as monochrome rather than one red channel. This function will return [constant ERR_UNAVAILABLE] if Godot was compiled without the TinyEXR module. + Saves the image as a PNG file to [code]path[/code]. </description> </method> <method name="set_pixel"> diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml index 1e24aadfd9..fb8168c344 100644 --- a/doc/classes/Popup.xml +++ b/doc/classes/Popup.xml @@ -56,6 +56,13 @@ Popup (show the control in modal form) in the center of the screen relative to the current canvas transform, scaled at a ratio of size of the screen. </description> </method> + <method name="set_as_minsize"> + <return type="void"> + </return> + <description> + Shrink popup to keep to the minimum size of content. + </description> + </method> </methods> <members> <member name="popup_exclusive" type="bool" setter="set_exclusive" getter="is_exclusive" default="false"> diff --git a/doc/classes/VisualShaderNodeVectorScalarMix.xml b/doc/classes/VisualShaderNodeVectorScalarMix.xml new file mode 100644 index 0000000000..d83c2e7d44 --- /dev/null +++ b/doc/classes/VisualShaderNodeVectorScalarMix.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeVectorScalarMix" inherits="VisualShaderNode" category="Core" version="3.2"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/drivers/gles2/rasterizer_canvas_gles2.cpp b/drivers/gles2/rasterizer_canvas_gles2.cpp index 8a177e32b0..4716b29e03 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -1650,6 +1650,7 @@ void RasterizerCanvasGLES2::canvas_render_items(Item *p_item_list, int p_z, cons //always re-set uniforms, since light parameters changed _set_uniforms(); + state.canvas_shader.use_material((void *)material_ptr); glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 4); RasterizerStorageGLES2::Texture *t = storage->texture_owner.getornull(light->texture); diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index a0188da4f6..a30af87516 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -5691,21 +5691,49 @@ void RasterizerStorageGLES2::initialize() { GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + glBindFramebuffer(GL_FRAMEBUFFER, system_fbo); + glDeleteFramebuffers(1, &fbo); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &depth); + if (status == GL_FRAMEBUFFER_COMPLETE) { config.depth_internalformat = GL_DEPTH_COMPONENT; config.depth_type = GL_UNSIGNED_INT; } else { + // If it fails, test to see if it supports a framebuffer texture using UNSIGNED_SHORT + // This is needed because many OSX devices don't support either UNSIGNED_INT or UNSIGNED_SHORT + config.depth_internalformat = GL_DEPTH_COMPONENT16; config.depth_type = GL_UNSIGNED_SHORT; - } - glBindFramebuffer(GL_FRAMEBUFFER, system_fbo); - glDeleteFramebuffers(1, &fbo); - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &depth); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glGenTextures(1, &depth); + glBindTexture(GL_TEXTURE_2D, depth); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 32, 32, 0, GL_DEPTH_COMPONENT16, GL_UNSIGNED_SHORT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0); + + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + //if it fails again depth textures aren't supported, use rgba shadows and renderbuffer for depth + config.support_depth_texture = false; + config.use_rgba_3d_shadows = true; + } + + glBindFramebuffer(GL_FRAMEBUFFER, system_fbo); + glDeleteFramebuffers(1, &fbo); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &depth); + } } else { - // Will use renderbuffer for depth + // Will use renderbuffer for depth, on mobile check for 24 bit depth support if (config.extensions.has("GL_OES_depth24")) { config.depth_internalformat = _DEPTH_COMPONENT24_OES; config.depth_type = GL_UNSIGNED_INT; diff --git a/drivers/gles2/shader_compiler_gles2.cpp b/drivers/gles2/shader_compiler_gles2.cpp index 935cb32fda..9c9a8e9717 100644 --- a/drivers/gles2/shader_compiler_gles2.cpp +++ b/drivers/gles2/shader_compiler_gles2.cpp @@ -802,6 +802,14 @@ String ShaderCompilerGLES2::_dump_node_code(SL::Node *p_node, int p_level, Gener code += "else\n"; code += _dump_node_code(cf_node->blocks[1], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning); } + } else if (cf_node->flow_op == SL::FLOW_OP_DO) { + code += _mktab(p_level); + code += "do"; + code += _dump_node_code(cf_node->blocks[0], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning); + code += _mktab(p_level); + code += "while ("; + code += _dump_node_code(cf_node->expressions[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning); + code += ");"; } else if (cf_node->flow_op == SL::FLOW_OP_WHILE) { code += _mktab(p_level); code += "while ("; diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index f4faed8736..1a97225e6b 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -200,6 +200,8 @@ void RasterizerCanvasGLES3::canvas_end() { glBindBufferBase(GL_UNIFORM_BUFFER, 0, 0); glColorMask(1, 1, 1, 1); + glVertexAttrib4f(VS::ARRAY_COLOR, 1, 1, 1, 1); + state.using_texture_rect = false; state.using_ninepatch = false; } diff --git a/drivers/gles3/shader_compiler_gles3.cpp b/drivers/gles3/shader_compiler_gles3.cpp index d48ee7709c..6b477ec78b 100644 --- a/drivers/gles3/shader_compiler_gles3.cpp +++ b/drivers/gles3/shader_compiler_gles3.cpp @@ -799,6 +799,11 @@ String ShaderCompilerGLES3::_dump_node_code(SL::Node *p_node, int p_level, Gener code += _mktab(p_level) + "else\n"; code += _dump_node_code(cfnode->blocks[1], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning); } + } else if (cfnode->flow_op == SL::FLOW_OP_DO) { + code += _mktab(p_level) + "do"; + code += _dump_node_code(cfnode->blocks[0], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning); + code += _mktab(p_level) + "while (" + _dump_node_code(cfnode->expressions[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + ");"; + } else if (cfnode->flow_op == SL::FLOW_OP_WHILE) { code += _mktab(p_level) + "while (" + _dump_node_code(cfnode->expressions[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning) + ")\n"; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 89a88dc6e7..5b1a460a54 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -163,16 +163,16 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) result_col = col; _update_results_count(); - set_error(vformat(TTR("Found %d match(es)."), results_count)); } else { result_line = -1; result_col = -1; text_edit->set_search_text(""); text_edit->set_search_flags(p_flags); text_edit->set_current_search_result(line, col); - set_error(text.empty() ? "" : TTR("No Matches")); } + _update_matches_label(); + return found; } @@ -283,20 +283,6 @@ void FindReplaceBar::_get_search_from(int &r_line, int &r_col) { r_line = text_edit->cursor_get_line(); r_col = text_edit->cursor_get_column(); - if (text_edit->is_selection_active() && !replace_all_mode) { - - int selection_line = text_edit->get_selection_from_line(); - - if (text_edit->get_selection_text() == get_search_text() && r_line == selection_line) { - - int selection_from_col = text_edit->get_selection_from_column(); - - if (r_col >= selection_from_col && r_col <= text_edit->get_selection_to_column()) { - r_col = selection_from_col; - } - } - } - if (r_line == result_line && r_col >= result_col && r_col <= result_col + get_search_text().length()) { r_col = result_col; } @@ -332,6 +318,18 @@ void FindReplaceBar::_update_results_count() { } } +void FindReplaceBar::_update_matches_label() { + + if (search_text->get_text().empty() || results_count == -1) { + matches_label->hide(); + } else { + matches_label->show(); + + matches_label->add_color_override("font_color", results_count > 0 ? Color(1, 1, 1) : EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor")); + matches_label->set_text(vformat(results_count == 1 ? TTR("%d match.") : TTR("%d matches."), results_count)); + } +} + bool FindReplaceBar::search_current() { uint32_t flags = 0; @@ -349,6 +347,9 @@ bool FindReplaceBar::search_current() { bool FindReplaceBar::search_prev() { + if (!is_visible()) + popup_search(true); + uint32_t flags = 0; String text = get_search_text(); @@ -361,13 +362,17 @@ bool FindReplaceBar::search_prev() { int line, col; _get_search_from(line, col); + if (text_edit->is_selection_active()) + col--; //Skip currently selected word. - col -= text.length(); - if (col < 0) { - line -= 1; - if (line < 0) - line = text_edit->get_line_count() - 1; - col = text_edit->get_line(line).length(); + if (line == result_line && col == result_col) { + col -= text.length(); + if (col < 0) { + line -= 1; + if (line < 0) + line = text_edit->get_line_count() - 1; + col = text_edit->get_line(line).length(); + } } return _search(flags, line, col); @@ -375,6 +380,9 @@ bool FindReplaceBar::search_prev() { bool FindReplaceBar::search_next() { + if (!is_visible()) + popup_search(true); + uint32_t flags = 0; String text; if (replace_all_mode) @@ -411,38 +419,54 @@ void FindReplaceBar::_hide_bar() { text_edit->set_search_text(""); result_line = -1; result_col = -1; - set_error(""); hide(); } -void FindReplaceBar::_show_search() { +void FindReplaceBar::_show_search(bool p_focus_replace, bool p_show_only) { show(); - search_text->call_deferred("grab_focus"); + if (p_show_only) + return; + + if (p_focus_replace) { + search_text->deselect(); + replace_text->call_deferred("grab_focus"); + } else { + replace_text->deselect(); + search_text->call_deferred("grab_focus"); + } if (text_edit->is_selection_active() && !selection_only->is_pressed()) { search_text->set_text(text_edit->get_selection_text()); } if (!get_search_text().empty()) { - search_text->select_all(); - search_text->set_cursor_position(search_text->get_text().length()); - search_current(); + if (p_focus_replace) { + replace_text->select_all(); + replace_text->set_cursor_position(replace_text->get_text().length()); + } else { + search_text->select_all(); + search_text->set_cursor_position(search_text->get_text().length()); + } + + results_count = -1; + _update_results_count(); + _update_matches_label(); } } -void FindReplaceBar::popup_search() { +void FindReplaceBar::popup_search(bool p_show_only) { - replace_text->hide(); + if (!is_visible()) + replace_text->hide(); hbc_button_replace->hide(); hbc_option_replace->hide(); - _show_search(); + _show_search(false, p_show_only); } void FindReplaceBar::popup_replace() { if (!replace_text->is_visible_in_tree()) { - replace_text->clear(); replace_text->show(); hbc_button_replace->show(); hbc_option_replace->show(); @@ -450,7 +474,7 @@ void FindReplaceBar::popup_replace() { selection_only->set_pressed((text_edit->is_selection_active() && text_edit->get_selection_from_line() < text_edit->get_selection_to_line())); - _show_search(); + _show_search(is_visible() || text_edit->is_selection_active()); } void FindReplaceBar::_search_options_changed(bool p_pressed) { @@ -557,6 +581,7 @@ FindReplaceBar::FindReplaceBar() { vbc_lineedit = memnew(VBoxContainer); add_child(vbc_lineedit); + vbc_lineedit->set_alignment(ALIGN_CENTER); vbc_lineedit->set_h_size_flags(SIZE_EXPAND_FILL); VBoxContainer *vbc_button = memnew(VBoxContainer); add_child(vbc_button); @@ -565,8 +590,10 @@ FindReplaceBar::FindReplaceBar() { HBoxContainer *hbc_button_search = memnew(HBoxContainer); vbc_button->add_child(hbc_button_search); + hbc_button_search->set_alignment(ALIGN_END); hbc_button_replace = memnew(HBoxContainer); vbc_button->add_child(hbc_button_replace); + hbc_button_replace->set_alignment(ALIGN_END); HBoxContainer *hbc_option_search = memnew(HBoxContainer); vbc_option->add_child(hbc_option_search); @@ -580,6 +607,10 @@ FindReplaceBar::FindReplaceBar() { search_text->connect("text_changed", this, "_search_text_changed"); search_text->connect("text_entered", this, "_search_text_entered"); + matches_label = memnew(Label); + hbc_button_search->add_child(matches_label); + matches_label->hide(); + find_prev = memnew(ToolButton); hbc_button_search->add_child(find_prev); find_prev->set_focus_mode(FOCUS_NONE); diff --git a/editor/code_editor.h b/editor/code_editor.h index 700e72627c..f2a55cfb70 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -64,6 +64,7 @@ class FindReplaceBar : public HBoxContainer { GDCLASS(FindReplaceBar, HBoxContainer); LineEdit *search_text; + Label *matches_label; ToolButton *find_prev; ToolButton *find_next; CheckBox *case_sensitive; @@ -90,8 +91,9 @@ class FindReplaceBar : public HBoxContainer { void _get_search_from(int &r_line, int &r_col); void _update_results_count(); + void _update_matches_label(); - void _show_search(); + void _show_search(bool p_focus_replace = false, bool p_show_only = false); void _hide_bar(); void _editor_text_changed(); @@ -123,7 +125,7 @@ public: void set_text_edit(TextEdit *p_text_edit); - void popup_search(); + void popup_search(bool p_show_only = false); void popup_replace(); bool search_current(); diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index ed262d9c4f..e1f2635275 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -35,6 +35,7 @@ #include "core/io/resource_saver.h" #include "core/io/zip_io.h" #include "core/math/crypto_core.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/project_settings.h" #include "core/script_language.h" @@ -884,6 +885,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String engine_cfb = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp" + config_file); ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb); + DirAccess::remove_file_or_error(engine_cfb); p_func(p_udata, "res://" + config_file, data, idx, total); @@ -916,8 +918,10 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c memdelete(ftmp); //close tmp file - if (err) + if (err != OK) { + DirAccess::remove_file_or_error(tmppath); return err; + } pd.file_ofs.sort(); //do sort, so we can do binary search later @@ -926,11 +930,17 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c if (!p_embed) { // Regular output to separate PCK file f = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); + if (!f) { + DirAccess::remove_file_or_error(tmppath); + ERR_FAIL_V(ERR_CANT_CREATE); + } } else { // Append to executable f = FileAccess::open(p_path, FileAccess::READ_WRITE); - ERR_FAIL_COND_V(!f, ERR_FILE_CANT_OPEN); + if (!f) { + DirAccess::remove_file_or_error(tmppath); + ERR_FAIL_V(ERR_FILE_CANT_OPEN); + } f->seek_end(); embed_pos = f->get_position(); @@ -995,13 +1005,13 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c f->store_8(0); } - //save the rest of the data + // Save the rest of the data. ftmp = FileAccess::open(tmppath, FileAccess::READ); if (!ftmp) { memdelete(f); - ERR_EXPLAIN("Can't open file to read from path: " + String(tmppath)); - ERR_FAIL_V(ERR_CANT_CREATE); + DirAccess::remove_file_or_error(tmppath); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open file to read from path: " + String(tmppath)); } const int bufsize = 16384; @@ -1035,6 +1045,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c } memdelete(f); + DirAccess::remove_file_or_error(tmppath); return OK; } @@ -1043,8 +1054,6 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, co EditorProgress ep("savezip", TTR("Packing"), 102, true); - //FileAccess *tmp = FileAccess::open(tmppath,FileAccess::WRITE); - FileAccess *src_f; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io); @@ -1694,11 +1703,18 @@ void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, con bool convert = GLOBAL_GET("editor/convert_text_resources_to_binary_on_export"); if (!convert) return; - String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("file.res"); + String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpfile.res"); Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path); - ERR_FAIL_COND(err != OK); + if (err != OK) { + DirAccess::remove_file_or_error(tmp_path); + ERR_FAIL(); + } Vector<uint8_t> data = FileAccess::get_file_as_array(tmp_path); - ERR_FAIL_COND(data.size() == 0); + if (data.size() == 0) { + DirAccess::remove_file_or_error(tmp_path); + ERR_FAIL(); + } + DirAccess::remove_file_or_error(tmp_path); add_file(p_path + ".converted.res", data, true); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 7aa24c7476..385b6924df 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1738,19 +1738,13 @@ void FindBar::_update_results_count() { void FindBar::_update_matches_label() { - if (results_count > 0) { - matches_label->show(); - - matches_label->add_color_override("font_color", Color(1, 1, 1)); - matches_label->set_text(vformat(TTR("Found %d match(es)."), results_count)); - } else if (search_text->get_text().empty()) { - + if (search_text->get_text().empty() || results_count == -1) { matches_label->hide(); } else { matches_label->show(); - matches_label->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor")); - matches_label->set_text(TTR("No Matches")); + matches_label->add_color_override("font_color", results_count > 0 ? Color(1, 1, 1) : EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor")); + matches_label->set_text(vformat(results_count == 1 ? TTR("%d match.") : TTR("%d matches."), results_count)); } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ae2a6d2802..866a90ff10 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4366,6 +4366,15 @@ bool EditorNode::ensure_main_scene(bool p_from_native) { return true; } +void EditorNode::run_play() { + _menu_option_confirm(RUN_STOP, true); + _run(false); +} + +void EditorNode::run_stop() { + _menu_option_confirm(RUN_STOP, false); +} + int EditorNode::get_current_tab() { return scene_tabs->get_current_tab(); } @@ -5934,19 +5943,19 @@ EditorNode::EditorNode() { p->add_shortcut(ED_SHORTCUT("editor/new_scene", TTR("New Scene")), FILE_NEW_SCENE); p->add_shortcut(ED_SHORTCUT("editor/new_inherited_scene", TTR("New Inherited Scene...")), FILE_NEW_INHERITED_SCENE); p->add_shortcut(ED_SHORTCUT("editor/open_scene", TTR("Open Scene..."), KEY_MASK_CMD + KEY_O), FILE_OPEN_SCENE); + p->add_shortcut(ED_SHORTCUT("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_T), FILE_OPEN_PREV); + p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT); + p->add_separator(); p->add_shortcut(ED_SHORTCUT("editor/save_scene", TTR("Save Scene"), KEY_MASK_CMD + KEY_S), FILE_SAVE_SCENE); p->add_shortcut(ED_SHORTCUT("editor/save_scene_as", TTR("Save Scene As..."), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_S), FILE_SAVE_AS_SCENE); p->add_shortcut(ED_SHORTCUT("editor/save_all_scenes", TTR("Save All Scenes"), KEY_MASK_ALT + KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_S), FILE_SAVE_ALL_SCENES); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT("editor/close_scene", TTR("Close Scene"), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_W), FILE_CLOSE); - p->add_separator(); - p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT); - p->add_shortcut(ED_SHORTCUT("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KEY_MASK_CMD + KEY_MASK_SHIFT + KEY_T), FILE_OPEN_PREV); + p->add_separator(); p->add_shortcut(ED_SHORTCUT("editor/quick_open", TTR("Quick Open..."), KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_O), FILE_QUICK_OPEN); p->add_shortcut(ED_SHORTCUT("editor/quick_open_scene", TTR("Quick Open Scene..."), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_O), FILE_QUICK_OPEN_SCENE); p->add_shortcut(ED_SHORTCUT("editor/quick_open_script", TTR("Quick Open Script..."), KEY_MASK_ALT + KEY_MASK_CMD + KEY_O), FILE_QUICK_OPEN_SCRIPT); + p->add_separator(); PopupMenu *pm_export = memnew(PopupMenu); pm_export->set_name("Export"); @@ -5959,8 +5968,10 @@ EditorNode::EditorNode() { p->add_separator(); p->add_shortcut(ED_SHORTCUT("editor/undo", TTR("Undo"), KEY_MASK_CMD + KEY_Z), EDIT_UNDO, true); p->add_shortcut(ED_SHORTCUT("editor/redo", TTR("Redo"), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_Z), EDIT_REDO, true); + p->add_separator(); p->add_shortcut(ED_SHORTCUT("editor/revert_scene", TTR("Revert Scene")), EDIT_REVERT); + p->add_shortcut(ED_SHORTCUT("editor/close_scene", TTR("Close Scene"), KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_W), FILE_CLOSE); recent_scenes = memnew(PopupMenu); recent_scenes->set_name("RecentScenes"); @@ -5980,27 +5991,27 @@ EditorNode::EditorNode() { p = project_menu->get_popup(); p->set_hide_on_window_lose_focus(true); - p->add_shortcut(ED_SHORTCUT("editor/project_settings", TTR("Project Settings")), RUN_SETTINGS); - p->add_separator(); + p->add_shortcut(ED_SHORTCUT("editor/project_settings", TTR("Project Settings...")), RUN_SETTINGS); p->connect("id_pressed", this, "_menu_option"); - p->add_shortcut(ED_SHORTCUT("editor/export", TTR("Export")), FILE_EXPORT_PROJECT); + + p->add_separator(); + p->add_shortcut(ED_SHORTCUT("editor/export", TTR("Export...")), FILE_EXPORT_PROJECT); + p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE); + p->add_item(TTR("Open Project Data Folder"), RUN_PROJECT_DATA_FOLDER); plugin_config_dialog = memnew(PluginConfigDialog); plugin_config_dialog->connect("plugin_ready", this, "_on_plugin_ready"); gui_base->add_child(plugin_config_dialog); + p->add_separator(); tool_menu = memnew(PopupMenu); tool_menu->set_name("Tools"); tool_menu->connect("index_pressed", this, "_tool_menu_option"); - p->add_separator(); p->add_child(tool_menu); p->add_submenu_item(TTR("Tools"), "Tools"); - tool_menu->add_item(TTR("Orphan Resource Explorer"), TOOLS_ORPHAN_RESOURCES); - tool_menu->add_item(TTR("Open Project Data Folder"), RUN_PROJECT_DATA_FOLDER); - p->add_separator(); - p->add_item(TTR("Install Android Build Template"), FILE_INSTALL_ANDROID_SOURCE); - p->add_separator(); + tool_menu->add_item(TTR("Orphan Resource Explorer..."), TOOLS_ORPHAN_RESOURCES); + p->add_separator(); #ifdef OSX_ENABLED p->add_shortcut(ED_SHORTCUT("editor/quit_to_project_list", TTR("Quit to Project List"), KEY_MASK_SHIFT + KEY_MASK_ALT + KEY_Q), RUN_PROJECT_MANAGER, true); #else @@ -6052,7 +6063,7 @@ EditorNode::EditorNode() { p = settings_menu->get_popup(); p->set_hide_on_window_lose_focus(true); - p->add_shortcut(ED_SHORTCUT("editor/editor_settings", TTR("Editor Settings")), SETTINGS_PREFERENCES); + p->add_shortcut(ED_SHORTCUT("editor/editor_settings", TTR("Editor Settings...")), SETTINGS_PREFERENCES); p->add_separator(); editor_layouts = memnew(PopupMenu); @@ -6087,11 +6098,8 @@ EditorNode::EditorNode() { } p->add_separator(); - p->add_item(TTR("Manage Editor Features"), SETTINGS_MANAGE_FEATURE_PROFILES); - - p->add_separator(); - - p->add_item(TTR("Manage Export Templates"), SETTINGS_MANAGE_EXPORT_TEMPLATES); + p->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES); + p->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES); // Help Menu help_menu = memnew(MenuButton); diff --git a/editor/editor_node.h b/editor/editor_node.h index 8d536a1b86..2fdc864a78 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -869,6 +869,9 @@ public: static void add_build_callback(EditorBuildCallback p_callback); bool ensure_main_scene(bool p_from_native); + + void run_play(); + void run_stop(); }; struct EditorProgress { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 3300228921..20ba07c102 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -116,7 +116,7 @@ void EditorPropertyMultilineText::_open_big_text() { add_child(big_text_dialog); } - big_text_dialog->popup_centered_ratio(); + big_text_dialog->popup_centered_clamped(Size2(1000, 900) * EDSCALE, 0.8); big_text->set_text(text->get_text()); big_text->grab_focus(); } diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 9966394025..379b8a2980 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -347,6 +347,11 @@ void EditorSpinSlider::_value_input_closed() { //focus_exited signal void EditorSpinSlider::_value_focus_exited() { + + // discontinue because the focus_exit was caused by right-click context menu + if (value_input->get_menu()->is_visible()) + return; + _evaluate_input_text(); // focus is not on the same element after the vlalue_input was exited // -> focus is on next element diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index ecfad4d146..72a4b43737 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -69,6 +69,9 @@ void ExportTemplateManager::_update_template_list() { memdelete(d); String current_version = VERSION_FULL_CONFIG; + // Downloadable export templates are only available for stable, alpha, beta and RC versions. + // Therefore, don't display download-related features when using a development version + const bool downloads_available = String(VERSION_STATUS) != String("dev"); Label *current = memnew(Label); current->set_h_size_flags(SIZE_EXPAND_FILL); @@ -76,10 +79,14 @@ void ExportTemplateManager::_update_template_list() { if (templates.has(current_version)) { current->add_color_override("font_color", get_color("success_color", "Editor")); - Button *redownload = memnew(Button); - redownload->set_text(TTR("Re-Download")); - current_hb->add_child(redownload); - redownload->connect("pressed", this, "_download_template", varray(current_version)); + + // Only display a redownload button if it can be downloaded in the first place + if (downloads_available) { + Button *redownload = memnew(Button); + redownload->set_text(TTR("Redownload")); + current_hb->add_child(redownload); + redownload->connect("pressed", this, "_download_template", varray(current_version)); + } Button *uninstall = memnew(Button); uninstall->set_text(TTR("Uninstall")); @@ -91,6 +98,12 @@ void ExportTemplateManager::_update_template_list() { current->add_color_override("font_color", get_color("error_color", "Editor")); Button *redownload = memnew(Button); redownload->set_text(TTR("Download")); + + if (!downloads_available) { + redownload->set_disabled(true); + redownload->set_tooltip(TTR("Official export templates aren't available for development builds.")); + } + redownload->connect("pressed", this, "_download_template", varray(current_version)); current_hb->add_child(redownload); current->set_text(current_version + " " + TTR("(Missing)")); @@ -422,14 +435,16 @@ void ExportTemplateManager::_http_download_templates_completed(int p_status, int String path = download_templates->get_download_file(); template_list_state->set_text(TTR("Download Complete.")); template_downloader->hide(); - int ret = _install_from_file(path, false); + bool ret = _install_from_file(path, false); if (ret) { - Error err = OS::get_singleton()->move_to_trash(path); + // Clean up downloaded file. + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + Error err = da->remove(path); if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Cannot remove:") + "\n" + path + "\n"); + EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n"); } } else { - WARN_PRINTS(vformat(TTR("Templates installation failed. The problematic templates archives can be found at '%s'."), path)); + EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path)); } } } break; @@ -458,7 +473,7 @@ void ExportTemplateManager::_begin_template_download(const String &p_url) { Error err = download_templates->request(p_url); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Error requesting url: ") + p_url); + EditorNode::get_singleton()->show_warning(TTR("Error requesting URL:") + " " + p_url); return; } diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 8665467f2d..cc2efb92ae 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -547,6 +547,7 @@ FindInFilesPanel::FindInFilesPanel() { _results_display->connect("item_edited", this, "_on_item_edited"); _results_display->set_hide_root(true); _results_display->set_select_mode(Tree::SELECT_ROW); + _results_display->set_allow_rmb_select(true); _results_display->create_item(); // Root vbc->add_child(_results_display); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 4a2295fab2..e053a34a4d 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -354,16 +354,16 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int } break; case HTTPRequest::RESULT_REQUEST_FAILED: { error_text = TTR("Request failed, return code:") + " " + itos(p_code); - status->set_text(TTR("Request Failed.")); + status->set_text(TTR("Request failed.")); } break; case HTTPRequest::RESULT_DOWNLOAD_FILE_CANT_OPEN: case HTTPRequest::RESULT_DOWNLOAD_FILE_WRITE_ERROR: { - error_text = TTR("Cannot save response to") + " " + download->get_download_file(); + error_text = TTR("Cannot save response to:") + " " + download->get_download_file(); status->set_text(TTR("Write error.")); } break; case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: { error_text = TTR("Request failed, too many redirects"); - status->set_text(TTR("Redirect Loop.")); + status->set_text(TTR("Redirect loop.")); } break; case HTTPRequest::RESULT_TIMEOUT: { error_text = TTR("Request failed, timeout"); @@ -472,9 +472,8 @@ void EditorAssetLibraryItemDownload::_notification(int p_what) { } void EditorAssetLibraryItemDownload::_close() { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - da->remove(download->get_download_file()); //clean up removed file - memdelete(da); + // Clean up downloaded file. + DirAccess::remove_file_or_error(download->get_download_file()); queue_delete(); } @@ -759,7 +758,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PoolByt switch (image_queue[p_queue_id].image_type) { case IMAGE_QUEUE_ICON: - image->resize(64 * EDSCALE, 64 * EDSCALE, Image::INTERPOLATE_CUBIC); + image->resize(64 * EDSCALE, 64 * EDSCALE, Image::INTERPOLATE_LANCZOS); break; case IMAGE_QUEUE_THUMBNAIL: { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ff134ff2d1..98c2f21e45 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4998,11 +4998,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p->set_hide_on_checkable_item_selection(false); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_grid", TTR("Snap to Grid")), SNAP_USE_GRID); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_rotation_snap", TTR("Use Rotation Snap")), SNAP_USE_ROTATION); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/configure_snap", TTR("Configure Snap...")), SNAP_CONFIGURE); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_relative", TTR("Snap Relative")), SNAP_RELATIVE); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_pixel_snap", TTR("Use Pixel Snap")), SNAP_USE_PIXEL); p->add_submenu_item(TTR("Smart Snapping"), "SmartSnapping"); + p->add_separator(); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/configure_snap", TTR("Configure Snap...")), SNAP_CONFIGURE); + smartsnap_config_popup = memnew(PopupMenu); p->add_child(smartsnap_config_popup); smartsnap_config_popup->set_name("SmartSnapping"); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index ec391186c3..76c7545874 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -490,9 +490,6 @@ void ScriptEditor::_update_recent_scripts() { Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array()); recent_scripts->clear(); - recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/open_recent", TTR("Open Recent"))); - recent_scripts->add_separator(); - String path; for (int i = 0; i < rc.size(); i++) { @@ -515,11 +512,6 @@ void ScriptEditor::_open_recent_script(int p_idx) { return; } - // take two for the open recent button - if (p_idx > 0) { - p_idx -= 2; - } - Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array()); ERR_FAIL_INDEX(p_idx, rc.size()); @@ -1000,7 +992,7 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->clear_filters(); file_dialog->popup_centered_ratio(); - file_dialog->set_title(TTR("New TextFile...")); + file_dialog->set_title(TTR("New Text File...")); } break; case FILE_OPEN: { file_dialog->set_mode(EditorFileDialog::MODE_OPEN_FILE); @@ -1072,6 +1064,7 @@ void ScriptEditor::_menu_option(int p_option) { save_all_scripts(); } break; case SEARCH_IN_FILES: { + _on_find_in_files_requested(""); } break; case SEARCH_HELP: { @@ -3240,7 +3233,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { file_menu->set_switch_on_hover(true); file_menu->get_popup()->set_hide_on_window_lose_focus(true); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script...")), FILE_NEW); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New TextFile...")), FILE_NEW_TEXTFILE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File...")), FILE_NEW_TEXTFILE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTR("Open...")), FILE_OPEN); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T), FILE_REOPEN_CLOSED); file_menu->get_popup()->add_submenu_item(TTR("Open Recent"), "RecentScripts", FILE_OPEN_RECENT); @@ -3273,17 +3266,20 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { theme_submenu->connect("id_pressed", this, "_theme_option"); theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/import_theme", TTR("Import Theme...")), THEME_IMPORT); theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/reload_theme", TTR("Reload Theme")), THEME_RELOAD); + theme_submenu->add_separator(); theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme", TTR("Save Theme")), THEME_SAVE); theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As...")), THEME_SAVE_AS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KEY_MASK_CMD | KEY_W), FILE_CLOSE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_all", TTR("Close All")), CLOSE_ALL); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_other_tabs", TTR("Close Other Tabs")), CLOSE_OTHER_TABS); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS); + file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTR("Run"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_X), FILE_RUN); + file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_scripts_panel", TTR("Toggle Scripts Panel"), KEY_MASK_CMD | KEY_BACKSLASH), TOGGLE_SCRIPTS_PANEL); file_menu->get_popup()->connect("id_pressed", this, "_menu_option"); @@ -3572,8 +3568,7 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T); - ED_SHORTCUT("script_editor/open_recent", TTR("Open Recent")); - ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files")); + ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); } ScriptEditorPlugin::~ScriptEditorPlugin() { diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index ddb87b200d..bded590351 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -30,6 +30,7 @@ #include "script_text_editor.h" +#include "core/math/expression.h" #include "core/os/keyboard.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" @@ -1147,6 +1148,32 @@ void ScriptTextEditor::_edit_option(int p_op) { _convert_case(CodeTextEditor::CAPITALIZE); } break; + case EDIT_EVALUATE: { + + Expression expression; + Vector<String> lines = code_editor->get_text_edit()->get_selection_text().split("\n"); + PoolStringArray results; + + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); //extract the whitespace at the beginning + + if (expression.parse(line) == OK) { + Variant result = expression.execute(Array(), Variant(), false); + if (expression.get_error_text() == "") { + results.append(whitespace + (String)result); + } else { + results.append(line); + } + } else { + results.append(line); + } + } + + code_editor->get_text_edit()->begin_complex_operation(); //prevents creating a two-step undo + code_editor->get_text_edit()->insert_text_at_cursor(results.join("\n")); + code_editor->get_text_edit()->end_complex_operation(); + } break; case SEARCH_FIND: { code_editor->get_find_replace_bar()->popup_search(); @@ -1170,7 +1197,6 @@ void ScriptTextEditor::_edit_option(int p_op) { // Yep, because it doesn't make sense to instance this dialog for every single script open... // So this will be delegated to the ScriptEditor. emit_signal("search_in_files_requested", selected_text); - } break; case SEARCH_LOCATE_FUNCTION: { @@ -1630,16 +1656,17 @@ void ScriptTextEditor::_color_changed(const Color &p_color) { void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition) { context_menu->clear(); - if (p_selection) { - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/cut"), EDIT_CUT); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/copy"), EDIT_COPY); - } + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO); + context_menu->add_separator(); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/cut"), EDIT_CUT); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/copy"), EDIT_COPY); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/paste"), EDIT_PASTE); + context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/select_all"), EDIT_SELECT_ALL); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); - context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO); + context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_left"), EDIT_INDENT_LEFT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); @@ -1650,6 +1677,7 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p context_menu->add_separator(); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); } if (p_foldable) context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); @@ -1750,6 +1778,7 @@ ScriptTextEditor::ScriptTextEditor() { edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/complete_symbol"), EDIT_COMPLETE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); @@ -1875,6 +1904,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_D); ED_SHORTCUT("script_text_editor/complete_symbol", TTR("Complete Symbol"), KEY_MASK_CMD | KEY_SPACE); #endif + ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_E); ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T); ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent to Spaces"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Y); ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent to Tabs"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_I); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 4b2307555b..38c6da5c33 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -120,6 +120,7 @@ class ScriptTextEditor : public ScriptEditorBase { EDIT_TO_UPPERCASE, EDIT_TO_LOWERCASE, EDIT_CAPITALIZE, + EDIT_EVALUATE, EDIT_TOGGLE_FOLD_LINE, EDIT_FOLD_ALL_LINES, EDIT_UNFOLD_ALL_LINES, diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 58234ba954..f2b1aad59d 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -3551,7 +3551,7 @@ SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, Ed view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_half_resolution", TTR("Half Resolution")), VIEW_HALF_RESOLUTION); view_menu->get_popup()->add_separator(); view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTR("Audio Listener")), VIEW_AUDIO_LISTENER); - view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTR("Doppler Enable")), VIEW_AUDIO_DOPPLER); + view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTR("Enable Doppler")), VIEW_AUDIO_DOPPLER); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS), true); view_menu->get_popup()->add_separator(); @@ -5647,10 +5647,11 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { p = transform_menu->get_popup(); p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap Object to Floor"), KEY_PAGEDOWN), MENU_SNAP_TO_FLOOR); - p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTR("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP); - p->add_separator(); p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTR("Transform Dialog...")), MENU_TRANSFORM_DIALOG); + p->add_separator(); + p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTR("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP); + p->connect("id_pressed", this, "_menu_item_pressed"); view_menu = memnew(MenuButton); @@ -5674,11 +5675,11 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { p->add_submenu_item(TTR("Gizmos"), "GizmosMenu"); p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID); + p->add_separator(); - p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings")), MENU_VIEW_CAMERA_SETTINGS); + p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS); p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true); p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index a8fbadb773..89e419ede8 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -221,19 +221,19 @@ void TextEditor::_validate_script() { void TextEditor::_update_bookmark_list() { - bookmarks_menu->get_popup()->clear(); + bookmarks_menu->clear(); - bookmarks_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - bookmarks_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); - bookmarks_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); - bookmarks_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT); + bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV); Array bookmark_list = code_editor->get_text_edit()->get_bookmarks_array(); if (bookmark_list.size() == 0) { return; } - bookmarks_menu->get_popup()->add_separator(); + bookmarks_menu->add_separator(); for (int i = 0; i < bookmark_list.size(); i++) { String line = code_editor->get_text_edit()->get_line(bookmark_list[i]).strip_edges(); @@ -242,17 +242,17 @@ void TextEditor::_update_bookmark_list() { line = line.substr(0, 50); } - bookmarks_menu->get_popup()->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->get_popup()->set_item_metadata(bookmarks_menu->get_popup()->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); + bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); } } void TextEditor::_bookmark_item_pressed(int p_idx) { if (p_idx < 4) { // Any item before the separator. - _edit_option(bookmarks_menu->get_popup()->get_item_id(p_idx)); + _edit_option(bookmarks_menu->get_item_id(p_idx)); } else { - code_editor->goto_line(bookmarks_menu->get_popup()->get_item_metadata(p_idx)); + code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx)); } } @@ -482,6 +482,14 @@ void TextEditor::_edit_option(int p_op) { code_editor->get_find_replace_bar()->popup_replace(); } break; + case SEARCH_IN_FILES: { + + String selected_text = code_editor->get_text_edit()->get_selection_text(); + + // Yep, because it doesn't make sense to instance this dialog for every single script open... + // So this will be delegated to the ScriptEditor. + emit_signal("search_in_files_requested", selected_text); + } break; case SEARCH_GOTO_LINE: { goto_line_dialog->popup_find_line(tx); @@ -558,7 +566,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { int to_column = tx->get_selection_to_column(); if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text + // Right click is outside the selected text. tx->deselect(); } } @@ -637,17 +645,14 @@ TextEditor::TextEditor() { search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV); search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE); search_menu->get_popup()->add_separator(); - search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); - - goto_line_dialog = memnew(GotoLineDialog); - add_child(goto_line_dialog); + search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); edit_menu = memnew(MenuButton); + edit_hb->add_child(edit_menu); edit_menu->set_text(TTR("Edit")); edit_menu->set_switch_on_hover(true); edit_menu->get_popup()->connect("id_pressed", this, "_edit_option"); - edit_hb->add_child(edit_menu); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO); edit_menu->get_popup()->add_separator(); @@ -689,13 +694,25 @@ TextEditor::TextEditor() { highlighter_menu->add_radio_check_item(TTR("Standard")); highlighter_menu->connect("id_pressed", this, "_change_syntax_highlighter"); - bookmarks_menu = memnew(MenuButton); - edit_hb->add_child(bookmarks_menu); - bookmarks_menu->set_text(TTR("Bookmarks")); - bookmarks_menu->set_switch_on_hover(true); + MenuButton *goto_menu = memnew(MenuButton); + edit_hb->add_child(goto_menu); + goto_menu->set_text(TTR("Go To")); + goto_menu->set_switch_on_hover(true); + goto_menu->get_popup()->connect("id_pressed", this, "_edit_option"); + + goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE); + goto_menu->get_popup()->add_separator(); + + bookmarks_menu = memnew(PopupMenu); + bookmarks_menu->set_name(TTR("Bookmarks")); + goto_menu->get_popup()->add_child(bookmarks_menu); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); _update_bookmark_list(); bookmarks_menu->connect("about_to_show", this, "_update_bookmark_list"); - bookmarks_menu->get_popup()->connect("index_pressed", this, "_bookmark_item_pressed"); + bookmarks_menu->connect("index_pressed", this, "_bookmark_item_pressed"); + + goto_line_dialog = memnew(GotoLineDialog); + add_child(goto_line_dialog); code_editor->get_text_edit()->set_drag_forwarding(this); } diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index c0d4052646..c8b49a61e0 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -46,7 +46,7 @@ private: MenuButton *edit_menu; PopupMenu *highlighter_menu; MenuButton *search_menu; - MenuButton *bookmarks_menu; + PopupMenu *bookmarks_menu; PopupMenu *context_menu; GotoLineDialog *goto_line_dialog; @@ -87,6 +87,7 @@ private: SEARCH_FIND_NEXT, SEARCH_FIND_PREV, SEARCH_REPLACE, + SEARCH_IN_FILES, SEARCH_GOTO_LINE, BOOKMARK_TOGGLE, BOOKMARK_GOTO_NEXT, diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 5b67d259ba..56357f298a 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -36,6 +36,7 @@ void ThemeEditor::edit(const Ref<Theme> &p_theme) { theme = p_theme; + main_panel->set_theme(p_theme); main_container->set_theme(p_theme); } @@ -53,6 +54,7 @@ void ThemeEditor::_propagate_redraw(Control *p_at) { void ThemeEditor::_refresh_interval() { + _propagate_redraw(main_panel); _propagate_redraw(main_container); } @@ -130,14 +132,14 @@ void ThemeEditor::_save_template_cbk(String fname) { Map<String, _TECategory> categories; - //fill types + // Fill types. List<StringName> type_list; Theme::get_default()->get_type_list(&type_list); for (List<StringName>::Element *E = type_list.front(); E; E = E->next()) { categories.insert(E->get(), _TECategory()); } - //fill default theme + // Fill default theme. for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) { _TECategory &tc = E->get(); @@ -256,7 +258,7 @@ void ThemeEditor::_save_template_cbk(String fname) { file->store_line(""); file->store_line(""); - //write default theme + // Write default theme. for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) { _TECategory &tc = E->get(); @@ -501,7 +503,7 @@ void ThemeEditor::_theme_menu_cbk(int p_option) { type_select_label->show(); type_select->show(); - if (p_option == POPUP_ADD) { //add + if (p_option == POPUP_ADD) { // Add. add_del_dialog->set_title(TTR("Add Item")); add_del_dialog->get_ok()->set_text(TTR("Add")); @@ -509,7 +511,7 @@ void ThemeEditor::_theme_menu_cbk(int p_option) { base_theme = Theme::get_default(); - } else if (p_option == POPUP_CLASS_ADD) { //add + } else if (p_option == POPUP_CLASS_ADD) { // Add. add_del_dialog->set_title(TTR("Add All Items")); add_del_dialog->get_ok()->set_text(TTR("Add All")); @@ -552,12 +554,10 @@ void ThemeEditor::_theme_menu_cbk(int p_option) { type_menu->get_popup()->clear(); - if (p_option == 0 || p_option == 1) { //add + if (p_option == 0 || p_option == 1) { // Add. List<StringName> new_types; theme->get_type_list(&new_types); - - //uh kind of sucks for (List<StringName>::Element *F = new_types.front(); F; F = F->next()) { bool found = false; @@ -583,15 +583,17 @@ void ThemeEditor::_theme_menu_cbk(int p_option) { void ThemeEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - - time_left -= get_process_delta_time(); - if (time_left < 0) { - time_left = 1.5; - _refresh_interval(); - } - } else if (p_what == NOTIFICATION_THEME_CHANGED) { - theme_menu->set_icon(get_icon("Theme", "EditorIcons")); + switch (p_what) { + case NOTIFICATION_PROCESS: { + time_left -= get_process_delta_time(); + if (time_left < 0) { + time_left = 1.5; + _refresh_interval(); + } + } break; + case NOTIFICATION_THEME_CHANGED: { + theme_menu->set_icon(get_icon("Theme", "EditorIcons")); + } break; } } @@ -629,35 +631,34 @@ ThemeEditor::ThemeEditor() { top_menu->add_child(theme_menu); theme_menu->get_popup()->connect("id_pressed", this, "_theme_menu_cbk"); - scroll = memnew(ScrollContainer); + ScrollContainer *scroll = memnew(ScrollContainer); add_child(scroll); - scroll->set_theme(Theme::get_default()); scroll->set_enable_v_scroll(true); scroll->set_enable_h_scroll(false); scroll->set_v_size_flags(SIZE_EXPAND_FILL); - main_container = memnew(MarginContainer); - scroll->add_child(main_container); - main_container->set_theme(Theme::get_default()); - main_container->set_clip_contents(true); - main_container->set_custom_minimum_size(Size2(700, 0) * EDSCALE); - main_container->set_v_size_flags(SIZE_EXPAND_FILL); - main_container->set_h_size_flags(SIZE_EXPAND_FILL); + MarginContainer *root_container = memnew(MarginContainer); + scroll->add_child(root_container); + root_container->set_theme(Theme::get_default()); + root_container->set_clip_contents(true); + root_container->set_custom_minimum_size(Size2(700, 0) * EDSCALE); + root_container->set_v_size_flags(SIZE_EXPAND_FILL); + root_container->set_h_size_flags(SIZE_EXPAND_FILL); //// Preview Controls //// - Panel *panel = memnew(Panel); - main_container->add_child(panel); + main_panel = memnew(Panel); + root_container->add_child(main_panel); - MarginContainer *mc = memnew(MarginContainer); - main_container->add_child(mc); - mc->add_constant_override("margin_right", 4 * EDSCALE); - mc->add_constant_override("margin_top", 4 * EDSCALE); - mc->add_constant_override("margin_left", 4 * EDSCALE); - mc->add_constant_override("margin_bottom", 4 * EDSCALE); + main_container = memnew(MarginContainer); + root_container->add_child(main_container); + main_container->add_constant_override("margin_right", 4 * EDSCALE); + main_container->add_constant_override("margin_top", 4 * EDSCALE); + main_container->add_constant_override("margin_left", 4 * EDSCALE); + main_container->add_constant_override("margin_bottom", 4 * EDSCALE); HBoxContainer *main_hb = memnew(HBoxContainer); - mc->add_child(main_hb); + main_container->add_child(main_hb); VBoxContainer *first_vb = memnew(VBoxContainer); main_hb->add_child(first_vb); @@ -695,19 +696,19 @@ ThemeEditor::ThemeEditor() { test_menu_button->get_popup()->add_separator(); test_menu_button->get_popup()->add_check_item(TTR("Check Item")); test_menu_button->get_popup()->add_check_item(TTR("Checked Item")); - test_menu_button->get_popup()->set_item_checked(3, true); + test_menu_button->get_popup()->set_item_checked(4, true); test_menu_button->get_popup()->add_separator(); test_menu_button->get_popup()->add_radio_check_item(TTR("Radio Item")); test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item")); - test_menu_button->get_popup()->set_item_checked(6, true); + test_menu_button->get_popup()->set_item_checked(7, true); test_menu_button->get_popup()->add_separator(TTR("Named Sep.")); PopupMenu *test_submenu = memnew(PopupMenu); test_menu_button->get_popup()->add_child(test_submenu); test_submenu->set_name("submenu"); test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu"); - test_submenu->add_item(TTR("Item 1")); - test_submenu->add_item(TTR("Item 2")); + test_submenu->add_item(TTR("Subitem 1")); + test_submenu->add_item(TTR("Subitem 2")); first_vb->add_child(test_menu_button); OptionButton *test_option_button = memnew(OptionButton); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index cc236907a9..6ffee46569 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -45,7 +45,7 @@ class ThemeEditor : public VBoxContainer { GDCLASS(ThemeEditor, VBoxContainer); - ScrollContainer *scroll; + Panel *main_panel; MarginContainer *main_container; Ref<Theme> theme; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index feb2cdd071..b939bae5be 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1109,7 +1109,7 @@ void ProjectList::load_project_icon(int p_index) { Error err = img->load(item.icon.replace_first("res://", item.path + "/")); if (err == OK) { - img->resize(default_icon->get_width(), default_icon->get_height()); + img->resize(default_icon->get_width(), default_icon->get_height(), Image::INTERPOLATE_LANCZOS); Ref<ImageTexture> it = memnew(ImageTexture); it->create_from_image(img); icon = it; diff --git a/editor/pvrtc_compress.cpp b/editor/pvrtc_compress.cpp index 84cd6da3d9..7cdd900d25 100644 --- a/editor/pvrtc_compress.cpp +++ b/editor/pvrtc_compress.cpp @@ -32,6 +32,7 @@ #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "editor_settings.h" @@ -52,57 +53,75 @@ static void _compress_image(Image::CompressMode p_mode, Image *p_image) { _base_image_compress_pvrtc2_func(p_image); else if (_base_image_compress_pvrtc4_func) _base_image_compress_pvrtc4_func(p_image); - break; case Image::COMPRESS_PVRTC4: if (_base_image_compress_pvrtc4_func) _base_image_compress_pvrtc4_func(p_image); - break; - default: ERR_FAIL(); + default: + ERR_FAIL_MSG("Unsupported Image compress mode used in PVRTC module."); } return; } - String tmppath = EditorSettings::get_singleton()->get_cache_dir(); - - List<String> args; + String tmppath = EditorSettings::get_singleton()->get_cache_dir(); String src_img = tmppath.plus_file("_tmp_src_img.png"); String dst_img = tmppath.plus_file("_tmp_dst_img.pvr"); + List<String> args; args.push_back("-i"); args.push_back(src_img); args.push_back("-o"); args.push_back(dst_img); args.push_back("-f"); - switch (p_mode) { - case Image::COMPRESS_PVRTC2: args.push_back("PVRTC2"); break; - case Image::COMPRESS_PVRTC4: args.push_back("PVRTC4"); break; - case Image::COMPRESS_ETC: args.push_back("ETC"); break; - default: ERR_FAIL(); + switch (p_mode) { + case Image::COMPRESS_PVRTC2: + args.push_back("PVRTC2"); + break; + case Image::COMPRESS_PVRTC4: + args.push_back("PVRTC4"); + break; + case Image::COMPRESS_ETC: + args.push_back("ETC"); + break; + default: + ERR_FAIL_MSG("Unsupported Image compress mode used in PVRTC module."); } if (EditorSettings::get_singleton()->get("filesystem/import/pvrtc_fast_conversion").operator bool()) { args.push_back("-pvrtcfast"); } - if (p_image->has_mipmaps()) + if (p_image->has_mipmaps()) { args.push_back("-m"); + } + // Save source PNG. Ref<ImageTexture> t = memnew(ImageTexture); t->create_from_image(Ref<Image>(p_image), 0); ResourceSaver::save(src_img, t); Error err = OS::get_singleton()->execute(ttpath, args, true); - ERR_EXPLAIN(TTR("Could not execute PVRTC tool:") + " " + ttpath); - ERR_FAIL_COND(err != OK); + if (err != OK) { + // Clean up generated files. + DirAccess::remove_file_or_error(src_img); + DirAccess::remove_file_or_error(dst_img); + ERR_FAIL_MSG("Could not execute PVRTC tool: " + ttpath); + } t = ResourceLoader::load(dst_img, "Texture"); - - ERR_EXPLAIN(TTR("Can't load back converted image using PVRTC tool:") + " " + dst_img); - ERR_FAIL_COND(t.is_null()); + if (t.is_null()) { + // Clean up generated files. + DirAccess::remove_file_or_error(src_img); + DirAccess::remove_file_or_error(dst_img); + ERR_FAIL_MSG("Can't load back converted image using PVRTC tool."); + } p_image->copy_internals_from(t->get_data()); + + // Clean up generated files. + DirAccess::remove_file_or_error(src_img); + DirAccess::remove_file_or_error(dst_img); } static void _compress_pvrtc2(Image *p_image) { diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index ed9a24311d..7d0f40fe91 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -44,6 +44,23 @@ void ScriptCreateDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + String lang = ScriptServer::get_language(i)->get_name(); + Ref<Texture> lang_icon = get_icon(lang, "EditorIcons"); + if (lang_icon.is_valid()) { + language_menu->set_item_icon(i, lang_icon); + } + } + String last_lang = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); + Ref<Texture> last_lang_icon; + if (!last_lang.empty()) { + last_lang_icon = get_icon(last_lang, "EditorIcons"); + } else { + last_lang_icon = language_menu->get_item_icon(default_language); + } + if (last_lang_icon.is_valid()) { + language_menu->set_icon(last_lang_icon); + } path_button->set_icon(get_icon("Folder", "EditorIcons")); parent_browse_button->set_icon(get_icon("Folder", "EditorIcons")); parent_search_button->set_icon(get_icon("ClassList", "EditorIcons")); @@ -671,13 +688,13 @@ ScriptCreateDialog::ScriptCreateDialog() { gc->add_child(l); gc->add_child(language_menu); - int default_lang = 0; + default_language = 0; for (int i = 0; i < ScriptServer::get_language_count(); i++) { String lang = ScriptServer::get_language(i)->get_name(); language_menu->add_item(lang); if (lang == "GDScript") { - default_lang = i; + default_language = i; } } @@ -691,8 +708,8 @@ ScriptCreateDialog::ScriptCreateDialog() { } } } else { - language_menu->select(default_lang); - current_language = default_lang; + language_menu->select(default_language); + current_language = default_language; } language_menu->connect("item_selected", this, "_lang_changed"); diff --git a/editor/script_create_dialog.h b/editor/script_create_dialog.h index 288b8f604b..202846fd3c 100644 --- a/editor/script_create_dialog.h +++ b/editor/script_create_dialog.h @@ -76,6 +76,7 @@ class ScriptCreateDialog : public ConfirmationDialog { bool is_built_in; bool built_in_enabled; int current_language; + int default_language; bool re_check_path; String script_template; Vector<String> template_list; diff --git a/main/tests/test_string.cpp b/main/tests/test_string.cpp index ab5fb64252..7a41880645 100644 --- a/main/tests/test_string.cpp +++ b/main/tests/test_string.cpp @@ -432,6 +432,10 @@ bool test_26() { OS::get_singleton()->print("\n\nTest 26: RegEx substitution\n"); +#ifndef MODULE_REGEX_ENABLED + OS::get_singleton()->print("\tRegEx module disabled, can't run test."); + return false; +#else String s = "Double all the vowels."; OS::get_singleton()->print("\tString: %ls\n", s.c_str()); @@ -443,6 +447,7 @@ bool test_26() { OS::get_singleton()->print("\tResult: %ls\n", s.c_str()); return (s == "Doouublee aall thee vooweels."); +#endif } struct test_27_data { diff --git a/modules/assimp/editor_scene_importer_assimp.cpp b/modules/assimp/editor_scene_importer_assimp.cpp index 65fa8b6459..05f9120a07 100644 --- a/modules/assimp/editor_scene_importer_assimp.cpp +++ b/modules/assimp/editor_scene_importer_assimp.cpp @@ -150,8 +150,7 @@ Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_f 0; const aiScene *scene = importer.ReadFile(s_path.c_str(), post_process_Steps); - ERR_EXPLAIN(String("Open Asset Import failed to open: ") + String(importer.GetErrorString())); - ERR_FAIL_COND_V(scene == NULL, NULL); + ERR_FAIL_COND_V_MSG(scene == NULL, NULL, String("Open Asset Import failed to open: ") + String(importer.GetErrorString()) + "."); return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights); } @@ -348,8 +347,7 @@ void EditorSceneImporterAssimp::_fill_node_relationships(ImportState &state, con } else if (ownership[name] != p_skeleton_id) { //oh, it's from another skeleton? fine.. reparent all bones to this skeleton. int prev_owner = ownership[name]; - ERR_EXPLAIN("A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported."); - ERR_FAIL_COND(skeleton_map.has(prev_owner)); + ERR_FAIL_COND_MSG(skeleton_map.has(prev_owner), "A previous skeleton exists for bone '" + name + "', this type of skeleton layout is unsupported."); for (Map<String, int>::Element *E = ownership.front(); E; E = E->next()) { if (E->get() == prev_owner) { E->get() = p_skeleton_id; @@ -779,8 +777,7 @@ Ref<Texture> EditorSceneImporterAssimp::_load_texture(ImportState &state, String t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY); return t; } else if (tex->CheckFormat("dds")) { - ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented"); - ERR_FAIL_COND_V(true, Ref<Texture>()); + ERR_FAIL_V_MSG(Ref<Texture>(), "Open Asset Import: Embedded dds not implemented."); //Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); //ERR_FAIL_COND_V(img.is_null(), Ref<Texture>()); //Ref<ImageTexture> t; diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 88732dff33..5a32fa1c2c 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -241,9 +241,9 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, case BI_CMYKRLE8: case BI_CMYKRLE4: { // Stop parsing - ERR_EXPLAIN("Compressed BMP files are not supported: " + f->get_path()); + String bmp_path = f->get_path(); f->close(); - ERR_FAIL_V(ERR_UNAVAILABLE); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "Compressed BMP files are not supported: " + bmp_path + "."); } break; } // Don't rely on sizeof(bmp_file_header) as structure padding diff --git a/modules/bullet/bullet_physics_server.cpp b/modules/bullet/bullet_physics_server.cpp index 038001996d..e01928191a 100644 --- a/modules/bullet/bullet_physics_server.cpp +++ b/modules/bullet/bullet_physics_server.cpp @@ -1548,8 +1548,7 @@ void BulletPhysicsServer::free(RID p_rid) { bulletdelete(space); } else { - ERR_EXPLAIN("Invalid ID"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid ID."); } } diff --git a/modules/bullet/cone_twist_joint_bullet.cpp b/modules/bullet/cone_twist_joint_bullet.cpp index bc7fd52cf6..97b9a81f77 100644 --- a/modules/bullet/cone_twist_joint_bullet.cpp +++ b/modules/bullet/cone_twist_joint_bullet.cpp @@ -83,8 +83,7 @@ void ConeTwistJointBullet::set_param(PhysicsServer::ConeTwistJointParam p_param, coneConstraint->setLimit(coneConstraint->getSwingSpan1(), coneConstraint->getSwingSpan2(), coneConstraint->getTwistSpan(), coneConstraint->getLimitSoftness(), coneConstraint->getBiasFactor(), p_value); break; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); break; } } @@ -102,8 +101,7 @@ real_t ConeTwistJointBullet::get_param(PhysicsServer::ConeTwistJointParam p_para case PhysicsServer::CONE_TWIST_JOINT_RELAXATION: return coneConstraint->getRelaxationFactor(); default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/generic_6dof_joint_bullet.cpp b/modules/bullet/generic_6dof_joint_bullet.cpp index 0d2c46c579..4aae87c220 100644 --- a/modules/bullet/generic_6dof_joint_bullet.cpp +++ b/modules/bullet/generic_6dof_joint_bullet.cpp @@ -174,8 +174,7 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint = p_value; break; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); break; } } @@ -216,8 +215,7 @@ real_t Generic6DOFJointBullet::get_param(Vector3::Axis p_axis, PhysicsServer::G6 case PhysicsServer::G6DOF_JOINT_ANGULAR_SPRING_EQUILIBRIUM_POINT: return sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_equilibriumPoint; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } @@ -255,8 +253,7 @@ void Generic6DOFJointBullet::set_flag(Vector3::Axis p_axis, PhysicsServer::G6DOF sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_enableSpring = p_value; break; default: - ERR_EXPLAIN("This flag " + itos(p_flag) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The flag " + itos(p_flag) + " is deprecated."); break; } } diff --git a/modules/bullet/hinge_joint_bullet.cpp b/modules/bullet/hinge_joint_bullet.cpp index b7e1e1a4c2..4d26e729db 100644 --- a/modules/bullet/hinge_joint_bullet.cpp +++ b/modules/bullet/hinge_joint_bullet.cpp @@ -117,8 +117,7 @@ void HingeJointBullet::set_param(PhysicsServer::HingeJointParam p_param, real_t hingeConstraint->setMaxMotorImpulse(p_value); break; default: - ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The HingeJoint parameter " + itos(p_param) + " is deprecated."); break; } } @@ -143,8 +142,7 @@ real_t HingeJointBullet::get_param(PhysicsServer::HingeJointParam p_param) const case PhysicsServer::HINGE_JOINT_MOTOR_MAX_IMPULSE: return hingeConstraint->getMaxMotorImpulse(); default: - ERR_EXPLAIN("The HingeJoint parameter " + itos(p_param) + " is deprecated."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The HingeJoint parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/pin_joint_bullet.cpp b/modules/bullet/pin_joint_bullet.cpp index c9c4d1af7e..8d404e7f04 100644 --- a/modules/bullet/pin_joint_bullet.cpp +++ b/modules/bullet/pin_joint_bullet.cpp @@ -85,8 +85,7 @@ real_t PinJointBullet::get_param(PhysicsServer::PinJointParam p_param) const { case PhysicsServer::PIN_JOINT_IMPULSE_CLAMP: return p2pConstraint->m_setting.m_impulseClamp; default: - ERR_EXPLAIN("This parameter " + itos(p_param) + " is deprecated"); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("The parameter " + itos(p_param) + " is deprecated."); return 0; } } diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp index f15bcec914..85f47c3bbb 100644 --- a/modules/bullet/shape_bullet.cpp +++ b/modules/bullet/shape_bullet.cpp @@ -505,8 +505,7 @@ void HeightMapShapeBullet::set_data(const Variant &p_data) { } } else { - ERR_EXPLAIN("Expected PoolRealArray or float Image."); - ERR_FAIL(); + ERR_FAIL_MSG("Expected PoolRealArray or float Image."); } ERR_FAIL_COND(l_width <= 0); diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index f475ba23f6..d2b16b0fd1 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -581,10 +581,8 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { } else { world_mem = malloc(sizeof(btDiscreteDynamicsWorld)); } - if (!world_mem) { - ERR_EXPLAIN("Out of memory"); - ERR_FAIL(); - } + + ERR_FAIL_COND_MSG(!world_mem, "Out of memory."); if (p_create_soft_world) { collisionConfiguration = bulletnew(GodotSoftCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index 4628bd9a5b..0220dcae4a 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -108,8 +108,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (r_error) *r_error = ERR_FILE_CORRUPT; - ERR_EXPLAIN("Unable to open DDS texture file: " + p_path); - ERR_FAIL_COND_V(err != OK, RES()); + ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open DDS texture file: " + p_path + "."); uint32_t magic = f->get_32(); uint32_t hsize = f->get_32(); @@ -128,8 +127,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, if (magic != DDS_MAGIC || hsize != 124 || !(flags & DDSD_PIXELFORMAT) || !(flags & DDSD_CAPS)) { - ERR_EXPLAIN("Invalid or Unsupported DDS texture file: " + p_path); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Invalid or unsupported DDS texture file: " + p_path + "."); } /* uint32_t format_size = */ f->get_32(); @@ -218,9 +216,7 @@ RES ResourceFormatDDS::load(const String &p_path, const String &p_original_path, } else { printf("unrecognized fourcc %x format_flags: %x - rgbbits %i - red_mask %x green mask %x blue mask %x alpha mask %x\n", format_fourcc, format_flags, format_rgb_bits, format_red_mask, format_green_mask, format_blue_mask, format_alpha_mask); - ERR_EXPLAIN("Unrecognized or Unsupported color layout in DDS: " + p_path); - - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unrecognized or unsupported color layout in DDS: " + p_path + "."); } if (!(flags & DDSD_MIPMAPCOUNT)) diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index a5a356ced2..44f69ca261 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -543,10 +543,7 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer if (target_peer != 0) { E = peer_map.find(ABS(target_peer)); - if (!E) { - ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + "."); } ENetPacket *packet = enet_packet_create(NULL, p_buffer_size + 8, packet_flags); @@ -794,11 +791,7 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { ERR_FAIL_COND(p_channel < -1 || p_channel >= channel_count); - - if (p_channel == SYSCH_CONFIG) { - ERR_EXPLAIN("Channel " + itos(SYSCH_CONFIG) + " is reserved"); - ERR_FAIL(); - } + ERR_FAIL_COND_MSG(p_channel == SYSCH_CONFIG, "Channel " + itos(SYSCH_CONFIG) + " is reserved."); transfer_channel = p_channel; } diff --git a/modules/etc/texture_loader_pkm.cpp b/modules/etc/texture_loader_pkm.cpp index ff925480b8..3337460dfc 100644 --- a/modules/etc/texture_loader_pkm.cpp +++ b/modules/etc/texture_loader_pkm.cpp @@ -56,8 +56,7 @@ RES ResourceFormatPKM::load(const String &p_path, const String &p_original_path, if (r_error) *r_error = ERR_FILE_CORRUPT; - ERR_EXPLAIN("Unable to open PKM texture file: " + p_path); - ERR_FAIL_COND_V(err != OK, RES()); + ERR_FAIL_COND_V_MSG(err != OK, RES(), "Unable to open PKM texture file: " + p_path + "."); // big endian f->set_endian_swap(true); diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py index 7ab0e01108..20c1a2233c 100644 --- a/modules/gdnative/gdnative_builders.py +++ b/modules/gdnative/gdnative_builders.py @@ -185,7 +185,7 @@ def _build_gdnative_api_struct_source(api): 'extern const godot_gdnative_core_' + ('{0}_{1}_api_struct api_{0}_{1}'.format(core['version']['major'], core['version']['minor'])) + ' = {', '\tGDNATIVE_' + core['type'] + ',', '\t{' + str(core['version']['major']) + ', ' + str(core['version']['minor']) + '},', - '\t' + ('NULL' if not core['next'] else ('(const godot_gdnative_api_struct *)& api_{0}_{1}'.format(core['version']['major'], core['version']['minor']))) + ',' + '\t' + ('NULL' if not core['next'] else ('(const godot_gdnative_api_struct *)& api_{0}_{1}'.format(core['next']['version']['major'], core['next']['version']['minor']))) + ',' ] for funcdef in core['api']: diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index b8a13ed91b..62117dcaf3 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -32,6 +32,7 @@ #include "core/io/file_access_encrypted.h" #include "core/io/resource_loader.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "editor/gdscript_highlighter.h" #include "gdscript.h" @@ -117,6 +118,9 @@ public: file = FileAccess::get_file_as_array(tmp_path); add_file(p_path.get_basename() + ".gde", file, true); + // Clean up temporary file. + DirAccess::remove_file_or_error(tmp_path); + } else { add_file(p_path.get_basename() + ".gdc", file, true); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index bdecbbdbad..b36afd4386 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -196,16 +196,14 @@ bool GridMap::get_collision_layer_bit(int p_bit) const { #ifndef DISABLE_DEPRECATED void GridMap::set_theme(const Ref<MeshLibrary> &p_theme) { - ERR_EXPLAIN("GridMap.theme/set_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/set_mesh_library() instead."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("GridMap.theme/set_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/set_mesh_library() instead."); set_mesh_library(p_theme); } Ref<MeshLibrary> GridMap::get_theme() const { - ERR_EXPLAIN("GridMap.theme/get_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/get_mesh_library() instead."); - WARN_DEPRECATED; + WARN_DEPRECATED_MSG("GridMap.theme/get_theme() is deprecated and will be removed in a future version. Use GridMap.mesh_library/get_mesh_library() instead."); return get_mesh_library(); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 5a21833ffa..07b4f7f596 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -863,19 +863,21 @@ void GridMapEditor::_icon_size_changed(float p_value) { void GridMapEditor::update_palette() { int selected = mesh_library_palette->get_current(); + float min_size = EDITOR_DEF("editors/grid_map/preview_size", 64); + min_size *= EDSCALE; + mesh_library_palette->clear(); if (display_mode == DISPLAY_THUMBNAIL) { mesh_library_palette->set_max_columns(0); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_TOP); + mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5)); } else if (display_mode == DISPLAY_LIST) { mesh_library_palette->set_max_columns(1); mesh_library_palette->set_icon_mode(ItemList::ICON_MODE_LEFT); + mesh_library_palette->set_fixed_column_width(0); } - float min_size = EDITOR_DEF("editors/grid_map/preview_size", 64); - min_size *= EDSCALE; mesh_library_palette->set_fixed_icon_size(Size2(min_size, min_size)); - mesh_library_palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1.5)); mesh_library_palette->set_max_text_lines(2); Ref<MeshLibrary> mesh_library = node->get_mesh_library(); @@ -1269,7 +1271,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { options->get_popup()->add_item(TTR("Fill Selection"), MENU_OPTION_SELECTION_FILL, KEY_MASK_CTRL + KEY_F); options->get_popup()->add_separator(); - options->get_popup()->add_item(TTR("Settings"), MENU_OPTION_GRIDMAP_SETTINGS); + options->get_popup()->add_item(TTR("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS); settings_dialog = memnew(ConfirmationDialog); settings_dialog->set_title(TTR("GridMap Settings")); diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index f75a4a926a..e610619b54 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -102,10 +102,7 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, FileAccess *f, bool p_force len <<= 8; len |= f->get_8(); - if (len != width) { - ERR_EXPLAIN("invalid decoded scanline length, corrupt HDR"); - ERR_FAIL_V(ERR_FILE_CORRUPT); - } + ERR_FAIL_COND_V_MSG(len != width, ERR_FILE_CORRUPT, "Invalid decoded scanline length, corrupt HDR."); for (int k = 0; k < 4; ++k) { int i = 0; diff --git a/modules/mono/.gitignore b/modules/mono/.gitignore new file mode 100644 index 0000000000..fa6d00cbbb --- /dev/null +++ b/modules/mono/.gitignore @@ -0,0 +1,2 @@ +# Do not ignore solution files inside the mono module. Overrides Godot's global gitignore. +!*.sln diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index c47cfc8a38..35daa6d307 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -84,10 +84,16 @@ def build(env_mono): source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll'] sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames] - target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + target_filenames = [ + 'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll', + 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll' + ] if env_mono['target'] == 'debug': - target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + target_filenames += [ + 'GodotTools.pdb', 'GodotTools.IdeConnection.pdb', 'GodotTools.BuildLogger.pdb', + 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb' + ] targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 9f0eb58896..f751719531 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -113,8 +113,8 @@ def configure(env, env_mono): else: env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)) - env.Append(LIBS='psapi') - env.Append(LIBS='version') + env.Append(LIBS=['psapi']) + env.Append(LIBS=['version']) else: mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') @@ -124,7 +124,7 @@ def configure(env, env_mono): if env.msvc: env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) else: - env.Append(LIBS=mono_lib_name) + env.Append(LIBS=[mono_lib_name]) mono_bin_path = os.path.join(mono_root, 'bin') diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs new file mode 100644 index 0000000000..7a2ff2ca56 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs @@ -0,0 +1,33 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public class ConsoleLogger : ILogger + { + public void LogDebug(string message) + { + Console.WriteLine("DEBUG: " + message); + } + + public void LogInfo(string message) + { + Console.WriteLine("INFO: " + message); + } + + public void LogWarning(string message) + { + Console.WriteLine("WARN: " + message); + } + + public void LogError(string message) + { + Console.WriteLine("ERROR: " + message); + } + + public void LogError(string message, Exception e) + { + Console.WriteLine("EXCEPTION: " + message); + Console.WriteLine(e); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs new file mode 100644 index 0000000000..be89638241 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs @@ -0,0 +1,94 @@ +using System; +using Path = System.IO.Path; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeBase : IDisposable + { + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private readonly string projectMetadataDir; + + protected const string MetaFileName = "ide_server_meta.txt"; + protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName); + + private GodotIdeConnection connection; + protected readonly object ConnectionLock = new object(); + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected; + + public event Action Connected + { + add + { + if (connection != null && !connection.IsDisposed) + connection.Connected += value; + } + remove + { + if (connection != null && !connection.IsDisposed) + connection.Connected -= value; + } + } + + protected GodotIdeConnection Connection + { + get => connection; + set + { + connection?.Dispose(); + connection = value; + } + } + + protected GodotIdeBase(string projectMetadataDir) + { + this.projectMetadataDir = projectMetadataDir; + } + + protected void DisposeConnection() + { + lock (ConnectionLock) + { + connection?.Dispose(); + } + } + + ~GodotIdeBase() + { + Dispose(disposing: false); + } + + public void Dispose() + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) // lock may not be fair + return; + IsDisposed = true; + } + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + connection?.Dispose(); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs new file mode 100644 index 0000000000..4f56a8d71b --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeClient : GodotIdeBase + { + protected GodotIdeMetadata GodotIdeMetadata; + + private readonly FileSystemWatcher fsWatcher; + + protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + // FileSystemWatcher requires an existing directory + if (!File.Exists(projectMetadataDir)) + Directory.CreateDirectory(projectMetadataDir); + + fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName); + } + + private void OnMetaFileChanged(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null && metadata != GodotIdeMetadata) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private void OnMetaFileDeleted(object sender, FileSystemEventArgs e) + { + if (IsDisposed) + return; + + if (IsConnected) + DisposeConnection(); + + // The file may have been re-created + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (IsConnected || !File.Exists(MetaFilePath)) + return; + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + } + } + + private GodotIdeMetadata? ReadMetadataFile() + { + using (var reader = File.OpenText(MetaFilePath)) + { + string portStr = reader.ReadLine(); + + if (portStr == null) + return null; + + string editorExecutablePath = reader.ReadLine(); + + if (editorExecutablePath == null) + return null; + + if (!int.TryParse(portStr, out int port)) + return null; + + return new GodotIdeMetadata(port, editorExecutablePath); + } + } + + private void ConnectToServer() + { + var tcpClient = new TcpClient(); + + Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage); + Connection.Logger = Logger; + + try + { + Logger.LogInfo("Connecting to Godot Ide Server"); + + tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port); + + Logger.LogInfo("Connection open with Godot Ide Server"); + + var clientThread = new Thread(Connection.Start) + { + IsBackground = true, + Name = "Godot Ide Connection Client" + }; + clientThread.Start(); + } + catch (SocketException e) + { + if (e.SocketErrorCode == SocketError.ConnectionRefused) + Logger.LogError("The connection to the Godot Ide Server was refused"); + else + throw; + } + } + + public void Start() + { + Logger.LogInfo("Starting Godot Ide Client"); + + fsWatcher.Changed += OnMetaFileChanged; + fsWatcher.Deleted += OnMetaFileDeleted; + fsWatcher.EnableRaisingEvents = true; + + lock (ConnectionLock) + { + if (IsDisposed) + return; + + if (!File.Exists(MetaFilePath)) + { + Logger.LogInfo("There is no Godot Ide Server running"); + return; + } + + var metadata = ReadMetadataFile(); + + if (metadata != null) + { + GodotIdeMetadata = metadata.Value; + ConnectToServer(); + } + else + { + Logger.LogError("Failed to read Godot Ide metadata file"); + } + } + } + + public bool WriteMessage(Message message) + { + return Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + fsWatcher?.Dispose(); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["OpenFile"] = args => + { + switch (args.Length) + { + case 1: + OpenFile(file: args[0]); + return; + case 2: + OpenFile(file: args[0], line: int.Parse(args[1])); + return; + case 3: + OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2])); + return; + default: + throw new ArgumentException(); + } + } + }; + } + + protected abstract void OpenFile(string file); + protected abstract void OpenFile(string file, int line); + protected abstract void OpenFile(string file, int line, int column); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs new file mode 100644 index 0000000000..e7e81f175e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs @@ -0,0 +1,207 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net.Sockets; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public abstract class GodotIdeConnection : IDisposable + { + protected const string Version = "1.0"; + + protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}"; + protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}"; + + private const int ClientWriteTimeout = 8000; + private readonly TcpClient tcpClient; + + private TextReader clientReader; + private TextWriter clientWriter; + + private readonly object writeLock = new object(); + + private readonly Func<Message, bool> messageHandler; + + public event Action Connected; + + private ILogger logger; + + public ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + public bool IsDisposed { get; private set; } = false; + + public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected; + + protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler) + { + this.tcpClient = tcpClient; + this.messageHandler = messageHandler; + } + + public void Start() + { + try + { + if (!StartConnection()) + return; + + string messageLine; + while ((messageLine = ReadLine()) != null) + { + if (!MessageParser.TryParse(messageLine, out Message msg)) + { + Logger.LogError($"Received message with invalid format: {messageLine}"); + continue; + } + + Logger.LogDebug($"Received message: {msg}"); + + if (msg.Id == "close") + { + Logger.LogInfo("Closing connection"); + return; + } + + try + { + try + { + Debug.Assert(messageHandler != null); + + if (!messageHandler(msg)) + Logger.LogError($"Received unknown message: {msg}"); + } + catch (Exception e) + { + Logger.LogError($"Message handler for '{msg}' failed with exception", e); + } + } + catch (Exception e) + { + Logger.LogError($"Exception thrown from message handler. Message: {msg}", e); + } + } + } + catch (Exception e) + { + Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e); + } + finally + { + Dispose(); + } + } + + private bool StartConnection() + { + NetworkStream clientStream = tcpClient.GetStream(); + + clientReader = new StreamReader(clientStream, Encoding.UTF8); + + lock (writeLock) + clientWriter = new StreamWriter(clientStream, Encoding.UTF8); + + clientStream.WriteTimeout = ClientWriteTimeout; + + if (!WriteHandshake()) + { + Logger.LogError("Could not write handshake"); + return false; + } + + if (!IsValidResponseHandshake(ReadLine())) + { + Logger.LogError("Received invalid handshake"); + return false; + } + + Connected?.Invoke(); + + Logger.LogInfo("Godot Ide connection started"); + + return true; + } + + private string ReadLine() + { + try + { + return clientReader?.ReadLine(); + } + catch (Exception e) + { + if (IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Interrupted) + return null; + } + + throw; + } + } + + public bool WriteMessage(Message message) + { + Logger.LogDebug($"Sending message {message}"); + + var messageComposer = new MessageComposer(); + + messageComposer.AddArgument(message.Id); + foreach (string argument in message.Arguments) + messageComposer.AddArgument(argument); + + return WriteLine(messageComposer.ToString()); + } + + protected bool WriteLine(string text) + { + if (clientWriter == null || IsDisposed || !IsConnected) + return false; + + lock (writeLock) + { + try + { + clientWriter.WriteLine(text); + clientWriter.Flush(); + } + catch (Exception e) + { + if (!IsDisposed) + { + var se = e as SocketException ?? e.InnerException as SocketException; + if (se != null && se.SocketErrorCode == SocketError.Shutdown) + Logger.LogInfo("Client disconnected ungracefully"); + else + Logger.LogError("Exception thrown when trying to write to client", e); + + Dispose(); + } + } + } + + return true; + } + + protected abstract bool WriteHandshake(); + protected abstract bool IsValidResponseHandshake(string handshakeLine); + + public void Dispose() + { + if (IsDisposed) + return; + + IsDisposed = true; + + clientReader?.Dispose(); + clientWriter?.Dispose(); + ((IDisposable) tcpClient)?.Dispose(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs new file mode 100644 index 0000000000..1b11a14358 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionClient : GodotIdeConnection + { + public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ClientHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ServerHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs new file mode 100644 index 0000000000..aa98dc7ca3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs @@ -0,0 +1,24 @@ +using System; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace GodotTools.IdeConnection +{ + public class GodotIdeConnectionServer : GodotIdeConnection + { + public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler) + : base(tcpClient, messageHandler) + { + } + + protected override bool WriteHandshake() + { + return WriteLine(ServerHandshake); + } + + protected override bool IsValidResponseHandshake(string handshakeLine) + { + return handshakeLine == ClientHandshake; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs new file mode 100644 index 0000000000..d16daba0e2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs @@ -0,0 +1,45 @@ +namespace GodotTools.IdeConnection +{ + public struct GodotIdeMetadata + { + public int Port { get; } + public string EditorExecutablePath { get; } + + public GodotIdeMetadata(int port, string editorExecutablePath) + { + Port = port; + EditorExecutablePath = editorExecutablePath; + } + + public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b) + { + return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath; + } + + public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj is GodotIdeMetadata metadata) + return metadata == this; + + return false; + } + + public bool Equals(GodotIdeMetadata other) + { + return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath; + } + + public override int GetHashCode() + { + unchecked + { + return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj new file mode 100644 index 0000000000..84c08251ab --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.IdeConnection</RootNamespace> + <AssemblyName>GodotTools.IdeConnection</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ConsoleLogger.cs" /> + <Compile Include="GodotIdeMetadata.cs" /> + <Compile Include="GodotIdeBase.cs" /> + <Compile Include="GodotIdeClient.cs" /> + <Compile Include="GodotIdeConnection.cs" /> + <Compile Include="GodotIdeConnectionClient.cs" /> + <Compile Include="GodotIdeConnectionServer.cs" /> + <Compile Include="ILogger.cs" /> + <Compile Include="Message.cs" /> + <Compile Include="MessageComposer.cs" /> + <Compile Include="MessageParser.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs new file mode 100644 index 0000000000..614bb30271 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs @@ -0,0 +1,13 @@ +using System; + +namespace GodotTools.IdeConnection +{ + public interface ILogger + { + void LogDebug(string message); + void LogInfo(string message); + void LogWarning(string message); + void LogError(string message); + void LogError(string message, Exception e); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs new file mode 100644 index 0000000000..f24d324ae3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs @@ -0,0 +1,21 @@ +using System.Linq; + +namespace GodotTools.IdeConnection +{ + public struct Message + { + public string Id { get; set; } + public string[] Arguments { get; set; } + + public Message(string id, params string[] arguments) + { + Id = id; + Arguments = arguments; + } + + public override string ToString() + { + return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs new file mode 100644 index 0000000000..9e4cd6ec1a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public class MessageComposer + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + + private static readonly char[] CharsToEscape = { '\\', '"' }; + + public void AddArgument(string argument) + { + AddArgument(argument, quoted: argument.Contains(",")); + } + + public void AddArgument(string argument, bool quoted) + { + if (stringBuilder.Length > 0) + stringBuilder.Append(','); + + if (quoted) + { + stringBuilder.Append('"'); + + foreach (char @char in argument) + { + if (CharsToEscape.Contains(@char)) + stringBuilder.Append('\\'); + stringBuilder.Append(@char); + } + + stringBuilder.Append('"'); + } + else + { + stringBuilder.Append(argument); + } + } + + public override string ToString() + { + return stringBuilder.ToString(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs new file mode 100644 index 0000000000..ed691e481f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace GodotTools.IdeConnection +{ + public static class MessageParser + { + public static bool TryParse(string messageLine, out Message message) + { + var arguments = new List<string>(); + var stringBuilder = new StringBuilder(); + + bool expectingArgument = true; + + for (int i = 0; i < messageLine.Length; i++) + { + char @char = messageLine[i]; + + if (@char == ',') + { + if (expectingArgument) + arguments.Add(string.Empty); + + expectingArgument = true; + continue; + } + + bool quoted = false; + + if (messageLine[i] == '"') + { + quoted = true; + i++; + } + + while (i < messageLine.Length) + { + @char = messageLine[i]; + + if (quoted && @char == '"') + { + i++; + break; + } + + if (@char == '\\') + { + i++; + if (i < messageLine.Length) + break; + + stringBuilder.Append(messageLine[i]); + } + else if (!quoted && @char == ',') + { + break; // We don't increment the counter to allow the colon to be parsed after this + } + else + { + stringBuilder.Append(@char); + } + + i++; + } + + arguments.Add(stringBuilder.ToString()); + stringBuilder.Clear(); + + expectingArgument = false; + } + + if (arguments.Count == 0) + { + message = new Message(); + return false; + } + + message = new Message + { + Id = arguments[0], + Arguments = arguments.Skip(1).ToArray() + }; + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c7c00e66a2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.IdeConnection")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index 6f7d44bec2..a3438ea5f3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,5 +33,9 @@ Global {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 53ff0891d5..44813f962c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -9,7 +9,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBottomPanel : VBoxContainer + public class BottomPanel : VBoxContainer { private EditorInterface editorInterface; @@ -34,7 +34,7 @@ namespace GodotTools for (int i = 0; i < buildTabs.GetChildCount(); i++) { - var tab = (MonoBuildTab) buildTabs.GetChild(i); + var tab = (BuildTab) buildTabs.GetChild(i); if (tab == null) continue; @@ -49,11 +49,11 @@ namespace GodotTools itemTooltip += "\nStatus: "; if (tab.BuildExited) - itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored"; else itemTooltip += "Running"; - if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error) itemTooltip += $"\nErrors: {tab.ErrorCount}"; itemTooltip += $"\nWarnings: {tab.WarningCount}"; @@ -68,15 +68,15 @@ namespace GodotTools } } - public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + public BuildTab GetBuildTabFor(BuildInfo buildInfo) { - foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren())) { if (buildTab.BuildInfo.Equals(buildInfo)) return buildTab; } - var newBuildTab = new MonoBuildTab(buildInfo); + var newBuildTab = new BuildTab(buildInfo); AddBuildTab(newBuildTab); return newBuildTab; @@ -120,7 +120,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.WarningsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -132,7 +132,7 @@ namespace GodotTools if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) throw new InvalidOperationException("No tab selected"); - var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + var buildTab = (BuildTab) buildTabs.GetChild(currentTab); buildTab.ErrorsVisible = pressed; buildTab.UpdateIssuesList(); } @@ -145,7 +145,7 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) { @@ -166,7 +166,7 @@ namespace GodotTools Internal.GodotIs32Bits() ? "32" : "64" }; - bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines); if (!buildSuccess) return; @@ -193,9 +193,9 @@ namespace GodotTools int selectedItem = selectedItems[0]; - var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem); - OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName)); } public override void _Notification(int what) @@ -211,13 +211,13 @@ namespace GodotTools } } - public void AddBuildTab(MonoBuildTab buildTab) + public void AddBuildTab(BuildTab buildTab) { buildTabs.AddChild(buildTab); RaiseBuildTab(buildTab); } - public void RaiseBuildTab(MonoBuildTab buildTab) + public void RaiseBuildTab(BuildTab buildTab) { if (buildTab.GetParent() != buildTabs) throw new InvalidOperationException("Build tab is not in the tabs list"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d8cb9024c3..9a2b2e3a26 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -46,8 +46,8 @@ namespace GodotTools.Build { if (OS.IsWindows()) { - return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") - == GodotSharpBuilds.BuildTool.MsBuildMono; + return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == BuildManager.BuildTool.MsBuildMono; } return false; @@ -103,16 +103,16 @@ namespace GodotTools.Build return process; } - public static int Build(MonoBuildInfo monoBuildInfo) + public static int Build(BuildInfo buildInfo) { - return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return Build(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } - public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + public static async Task<int> BuildAsync(BuildInfo buildInfo) { - return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, - monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + return await BuildAsync(buildInfo.Solution, buildInfo.Configuration, + buildInfo.LogsDirPath, buildInfo.CustomProperties); } public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 926aabdf89..4c1e47ecad 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -19,13 +19,13 @@ namespace GodotTools.Build public static string FindMsBuild() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); if (OS.IsWindows()) { switch (buildTool) { - case GodotSharpBuilds.BuildTool.MsBuildVs: + case BuildManager.BuildTool.MsBuildVs: { if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) { @@ -34,7 +34,7 @@ namespace GodotTools.Build if (_msbuildToolsPath.Empty()) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); } } @@ -43,13 +43,13 @@ namespace GodotTools.Build return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); } - case GodotSharpBuilds.BuildTool.MsBuildMono: + case BuildManager.BuildTool.MsBuildMono: { string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); if (!File.Exists(msbuildPath)) { - throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); } return msbuildPath; @@ -61,7 +61,7 @@ namespace GodotTools.Build if (OS.IsUnix()) { - if (buildTool == GodotSharpBuilds.BuildTool.MsBuildMono) + if (buildTool == BuildManager.BuildTool.MsBuildMono) { if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) { @@ -71,7 +71,7 @@ namespace GodotTools.Build if (_msbuildUnixPath.Empty()) { - throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'"); } return _msbuildUnixPath; diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs index 858e852392..70bd552f2f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { [Serializable] - public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization { public string Solution { get; } public string Configuration { get; } @@ -17,7 +17,7 @@ namespace GodotTools public override bool Equals(object obj) { - if (obj is MonoBuildInfo other) + if (obj is BuildInfo other) return other.Solution == Solution && other.Configuration == Configuration; return false; @@ -34,11 +34,11 @@ namespace GodotTools } } - private MonoBuildInfo() + private BuildInfo() { } - public MonoBuildInfo(string solution, string configuration) + public BuildInfo(string solution, string configuration) { Solution = solution; Configuration = configuration; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index de3a4d9a6e..417032da54 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -6,15 +6,13 @@ using GodotTools.Build; using GodotTools.Internals; using GodotTools.Utils; using static GodotTools.Internals.Globals; -using Error = Godot.Error; using File = GodotTools.Utils.File; -using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class GodotSharpBuilds + public static class BuildManager { - private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>(); public const string PropNameMsbuildMono = "MSBuild (Mono)"; public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; @@ -28,7 +26,7 @@ namespace GodotTools MsBuildVs } - private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -38,29 +36,21 @@ namespace GodotTools File.Delete(issuesFile); } - private static string _ApiFolderName(ApiAssemblyType apiType) - { - ulong apiHash = apiType == ApiAssemblyType.Core ? - Internal.GetCoreApiHash() : - Internal.GetEditorApiHash(); - return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; - } - private static void ShowBuildErrorDialog(string message) { GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); - GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + GodotSharpEditor.Instance.BottomPanel.ShowBuildTab(); } - public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); - public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException(); - private static string GetLogFilePath(MonoBuildInfo buildInfo) + private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); } - private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + private static string GetIssuesFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName); } @@ -71,7 +61,7 @@ namespace GodotTools Godot.GD.Print(text); } - public static bool Build(MonoBuildInfo buildInfo) + public static bool Build(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -80,7 +70,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); buildTab.OnBuildStart(); // Required in order to update the build tasks list @@ -103,7 +93,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -120,7 +110,7 @@ namespace GodotTools } } - public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + public static async Task<bool> BuildAsync(BuildInfo buildInfo) { if (BuildsInProgress.Contains(buildInfo)) throw new InvalidOperationException("A build is already in progress"); @@ -129,7 +119,7 @@ namespace GodotTools try { - MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); try { @@ -148,7 +138,7 @@ namespace GodotTools if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); return exitCode == 0; } @@ -165,32 +155,6 @@ namespace GodotTools } } - public static bool BuildApiSolution(string apiSlnDir, string config) - { - string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); - - string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); - string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); - - string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); - string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); - - if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) - return true; // The assemblies are in the output folder; assume the solution is already built - - var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); - - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings - - if (Build(apiBuildInfo)) - return true; - - ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); - return false; - } - public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -201,13 +165,13 @@ namespace GodotTools Internal.UpdateApiAssembliesFromPrebuilt(); var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); + var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { pr.Step("Building project solution", 0); - var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config); // Add Godot defines string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; @@ -240,11 +204,28 @@ namespace GodotTools string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest; + + if (currentPlayRequest != null) + { + if (currentPlayRequest.Value.HasDebugger) + { + // Set the environment variable that will tell the player to connect to the IDE debugger + // TODO: We should probably add a better way to do this + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", + "--debugger-agent=transport=dt_socket" + + $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" + + ",server=n"); + } + + return true; // Requested play from an external editor/IDE which already built the project + } + var godotDefines = new[] { Godot.OS.GetName(), diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index 3a74fa2f66..807a20d9a1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -7,7 +7,7 @@ using Path = System.IO.Path; namespace GodotTools { - public class MonoBuildTab : VBoxContainer + public class BuildTab : VBoxContainer { public enum BuildResults { @@ -55,7 +55,7 @@ namespace GodotTools } } - public MonoBuildInfo BuildInfo { get; private set; } + public BuildInfo BuildInfo { get; private set; } private void _LoadIssuesFromFile(string csvFile) { @@ -199,7 +199,7 @@ namespace GodotTools ErrorCount = 0; UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExit(BuildResults result) @@ -207,10 +207,10 @@ namespace GodotTools BuildExited = true; BuildResult = result; - _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void OnBuildExecFailed(string cause) @@ -227,7 +227,7 @@ namespace GodotTools UpdateIssuesList(); - GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); } public void RestartBuild() @@ -235,7 +235,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build already started"); - GodotSharpBuilds.RestartBuild(this); + BuildManager.RestartBuild(this); } public void StopBuild() @@ -243,7 +243,7 @@ namespace GodotTools if (!BuildExited) throw new InvalidOperationException("Build is not in progress"); - GodotSharpBuilds.StopBuild(this); + BuildManager.StopBuild(this); } public override void _Ready() @@ -255,11 +255,11 @@ namespace GodotTools AddChild(issuesList); } - private MonoBuildTab() + private BuildTab() { } - public MonoBuildTab(MonoBuildInfo buildInfo) + public BuildTab(BuildInfo buildInfo) { BuildInfo = buildInfo; } diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 4535ed7247..c021a9051e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -9,7 +9,7 @@ using Directory = GodotTools.Utils.Directory; namespace GodotTools { - public static class CSharpProject + public static class CsProjOperations { public static string GenerateGameProject(string dir, string name) { diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs new file mode 100644 index 0000000000..4312ca0230 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs @@ -0,0 +1,11 @@ +namespace GodotTools +{ + public enum ExternalEditorId + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 9b5afb94a3..099c7fcb56 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -2,16 +2,18 @@ using Godot; using GodotTools.Utils; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; +using GodotTools.Ides; using GodotTools.Internals; using GodotTools.ProjectEditor; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -using Path = System.IO.Path; using OS = GodotTools.Utils.OS; namespace GodotTools { + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings editorSettings; @@ -24,12 +26,11 @@ namespace GodotTools private ToolButton bottomPanelBtn; - private MonoDevelopInstance monoDevelopInstance; - private MonoDevelopInstance visualStudioForMacInstance; + public GodotIdeManager GodotIdeManager { get; private set; } private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization - public MonoBottomPanel MonoBottomPanel { get; private set; } + public BottomPanel BottomPanel { get; private set; } private bool CreateProjectSolution() { @@ -44,7 +45,7 @@ namespace GodotTools if (name.Empty()) name = "UnnamedProject"; - string guid = CSharpProject.GenerateGameProject(path, name); + string guid = CsProjOperations.GenerateGameProject(path, name); if (guid.Length > 0) { @@ -133,7 +134,7 @@ namespace GodotTools return; // Failed to create solution } - Instance.MonoBottomPanel.BuildProjectPressed(); + Instance.BottomPanel.BuildProjectPressed(); } public override void _Notification(int what) @@ -153,21 +154,12 @@ namespace GodotTools } } - public enum MenuOptions + private enum MenuOptions { CreateSln, AboutCSharp, } - public enum ExternalEditor - { - None, - VisualStudio, // TODO (Windows-only) - VisualStudioForMac, // Mac-only - MonoDevelop, - VsCode - } - public void ShowErrorDialog(string message, string title = "Error") { errorDialog.WindowTitle = title; @@ -184,11 +176,30 @@ namespace GodotTools public Error OpenInExternalEditor(Script script, int line, int col) { - var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor"); switch (editor) { - case ExternalEditor.VsCode: + case ExternalEditorId.None: + // Tells the caller to fallback to the global external editor settings or the built-in editor + return Error.Unavailable; + case ExternalEditorId.VisualStudio: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + GodotIdeManager.SendOpenFile(scriptPath, line + 1, col); + else + GodotIdeManager.SendOpenFile(scriptPath); + + break; + } + + case ExternalEditorId.VsCode: { if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) { @@ -273,47 +284,6 @@ namespace GodotTools break; } - case ExternalEditor.VisualStudioForMac: - goto case ExternalEditor.MonoDevelop; - case ExternalEditor.MonoDevelop: - { - MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) - { - if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) - { - if (visualStudioForMacInstance == null) - visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); - - return visualStudioForMacInstance; - } - - if (monoDevelopInstance == null) - monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); - - return monoDevelopInstance; - } - - string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); - - if (line >= 0) - scriptPath += $";{line + 1};{col}"; - - try - { - GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); - } - catch (FileNotFoundException) - { - string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; - GD.PushError($"Cannot find code editor: {editorName}"); - return Error.FileNotFound; - } - - break; - } - - case ExternalEditor.None: - return Error.Unavailable; default: throw new ArgumentOutOfRangeException(); } @@ -323,12 +293,12 @@ namespace GodotTools public bool OverridesExternalEditor() { - return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; } public override bool Build() { - return GodotSharpBuilds.EditorBuildCallback(); + return BuildManager.EditorBuildCallback(); } public override void EnablePlugin() @@ -347,9 +317,9 @@ namespace GodotTools errorDialog = new AcceptDialog(); editorBaseControl.AddChild(errorDialog); - MonoBottomPanel = new MonoBottomPanel(); + BottomPanel = new BottomPanel(); - bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR()); + bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); @@ -407,7 +377,7 @@ namespace GodotTools if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { // Make sure the existing project has Api assembly references configured correctly - CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); + CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); } else { @@ -427,25 +397,25 @@ namespace GodotTools AddControlToContainer(CustomControlContainer.Toolbar, buildButton); // External editor settings - EditorDef("mono/editor/external_editor", ExternalEditor.None); + EditorDef("mono/editor/external_editor", ExternalEditorId.None); string settingsHintStr = "Disabled"; if (OS.IsWindows()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsOSX()) { - settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + - $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } else if (OS.IsUnix()) { - settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + - $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditorId.VsCode}"; } editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary @@ -461,7 +431,10 @@ namespace GodotTools AddExportPlugin(exportPlugin); exportPluginWeak = WeakRef(exportPlugin); - GodotSharpBuilds.Initialize(); + BuildManager.Initialize(); + + GodotIdeManager = new GodotIdeManager(); + AddChild(GodotIdeManager); } protected override void Dispose(bool disposing) @@ -478,6 +451,8 @@ namespace GodotTools exportPluginWeak.Dispose(); } + + GodotIdeManager?.Dispose(); } public void OnBeforeSerialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs index b80fe1fab7..aefc51545e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -65,14 +65,14 @@ namespace GodotTools string buildConfig = isDebug ? "Debug" : "Release"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); AddFile(scriptsMetadataPath, scriptsMetadataPath); // Turn export features into defines var godotDefines = features; - if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines)) + if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) { GD.PushError("Failed to build project"); return; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 01e8c87d14..e2d576caef 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -39,26 +39,31 @@ </ItemGroup> <ItemGroup> <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="ExternalEditorId.cs" /> + <Compile Include="Ides\GodotIdeManager.cs" /> + <Compile Include="Ides\GodotIdeServer.cs" /> + <Compile Include="Ides\MonoDevelop\EditorId.cs" /> + <Compile Include="Ides\MonoDevelop\Instance.cs" /> <Compile Include="Internals\BindingsGenerator.cs" /> <Compile Include="Internals\EditorProgress.cs" /> <Compile Include="Internals\GodotSharpDirs.cs" /> <Compile Include="Internals\Internal.cs" /> <Compile Include="Internals\ScriptClassParser.cs" /> <Compile Include="Internals\Globals.cs" /> - <Compile Include="MonoDevelopInstance.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Build\BuildSystem.cs" /> <Compile Include="Utils\Directory.cs" /> <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\NotifyAwaiter.cs" /> <Compile Include="Utils\OS.cs" /> <Compile Include="GodotSharpEditor.cs" /> - <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="BuildManager.cs" /> <Compile Include="HotReloadAssemblyWatcher.cs" /> - <Compile Include="MonoBuildInfo.cs" /> - <Compile Include="MonoBuildTab.cs" /> - <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="BuildInfo.cs" /> + <Compile Include="BuildTab.cs" /> + <Compile Include="BottomPanel.cs" /> <Compile Include="GodotSharpExport.cs" /> - <Compile Include="CSharpProject.cs" /> + <Compile Include="CsProjOperations.cs" /> <Compile Include="Utils\CollectionExtensions.cs" /> </ItemGroup> <ItemGroup> @@ -66,6 +71,10 @@ <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> <Name>GodotTools.BuildLogger</Name> </ProjectReference> + <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj"> + <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project> + <Name>GodotTools.IdeConnection</Name> + </ProjectReference> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> <Name>GodotTools.ProjectEditor</Name> @@ -75,8 +84,5 @@ <Name>GodotTools.Core</Name> </ProjectReference> </ItemGroup> - <ItemGroup> - <Folder Include="Editor" /> - </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs new file mode 100644 index 0000000000..9e24138143 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; +using Godot; +using GodotTools.IdeConnection; +using GodotTools.Internals; + +namespace GodotTools.Ides +{ + public class GodotIdeManager : Node, ISerializationListener + { + public GodotIdeServer GodotIdeServer { get; private set; } + + private MonoDevelop.Instance monoDevelInstance; + private MonoDevelop.Instance vsForMacInstance; + + private GodotIdeServer GetRunningServer() + { + if (GodotIdeServer != null && !GodotIdeServer.IsDisposed) + return GodotIdeServer; + StartServer(); + return GodotIdeServer; + } + + public override void _Ready() + { + StartServer(); + } + + public void OnBeforeSerialize() + { + GodotIdeServer?.Dispose(); + } + + public void OnAfterDeserialize() + { + StartServer(); + } + + private ILogger logger; + + protected ILogger Logger + { + get => logger ?? (logger = new ConsoleLogger()); + set => logger = value; + } + + private void StartServer() + { + GodotIdeServer?.Dispose(); + GodotIdeServer = new GodotIdeServer(LaunchIde, + OS.GetExecutablePath(), + ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir)); + + GodotIdeServer.Logger = Logger; + + GodotIdeServer.StartServer(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + GodotIdeServer?.Dispose(); + } + + private void LaunchIde() + { + var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface() + .GetEditorSettings().GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditorId.None: + case ExternalEditorId.VisualStudio: + case ExternalEditorId.VsCode: + throw new NotSupportedException(); + case ExternalEditorId.VisualStudioForMac: + goto case ExternalEditorId.MonoDevelop; + case ExternalEditorId.MonoDevelop: + { + MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) + { + if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac) + { + vsForMacInstance = vsForMacInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); + return vsForMacInstance; + } + + monoDevelInstance = monoDevelInstance ?? + new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop); + return monoDevelInstance; + } + + try + { + var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath); + + if (!instance.IsRunning) + instance.Execute(); + } + catch (FileNotFoundException) + { + string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop"; + GD.PushError($"Cannot find code editor: {editorName}"); + } + + break; + } + + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void WriteMessage(string id, params string[] arguments) + { + GetRunningServer().WriteMessage(new Message(id, arguments)); + } + + public void SendOpenFile(string file) + { + WriteMessage("OpenFile", file); + } + + public void SendOpenFile(string file, int line) + { + WriteMessage("OpenFile", file, line.ToString()); + } + + public void SendOpenFile(string file, int line, int column) + { + WriteMessage("OpenFile", file, line.ToString(), column.ToString()); + } + + private class GodotLogger : ILogger + { + public void LogDebug(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogInfo(string message) + { + if (OS.IsStdoutVerbose()) + Console.WriteLine(message); + } + + public void LogWarning(string message) + { + GD.PushWarning(message); + } + + public void LogError(string message) + { + GD.PushError(message); + } + + public void LogError(string message, Exception e) + { + GD.PushError(message + "\n" + e); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs new file mode 100644 index 0000000000..d515254e65 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GodotTools.IdeConnection; +using GodotTools.Internals; +using GodotTools.Utils; +using File = System.IO.File; +using Thread = System.Threading.Thread; + +namespace GodotTools.Ides +{ + public class GodotIdeServer : GodotIdeBase + { + private readonly TcpListener listener; + private readonly FileStream metaFile; + private readonly Action launchIdeAction; + private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>(); + + private async Task<bool> AwaitClientConnected() + { + return await clientConnectedAwaiter.Reset(); + } + + public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir) + : base(projectMetadataDir) + { + messageHandlers = InitializeMessageHandlers(); + + this.launchIdeAction = launchIdeAction; + + // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing... + const FileShare metaFileShare = FileShare.ReadWrite; + + metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare); + + listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0)); + listener.Start(); + + int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port; + using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8)) + { + metaFileWriter.WriteLine(port); + metaFileWriter.WriteLine(editorExecutablePath); + } + + StartServer(); + } + + public void StartServer() + { + var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"}; + serverThread.Start(); + } + + private void RunServerThread() + { + SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext); + + try + { + while (!IsDisposed) + { + TcpClient tcpClient = listener.AcceptTcpClient(); + + Logger.LogInfo("Connection open with Ide Client"); + + lock (ConnectionLock) + { + Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage); + Connection.Logger = Logger; + } + + Connected += () => clientConnectedAwaiter.SetResult(true); + + Connection.Start(); + } + } + catch (Exception e) + { + if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted)) + throw; + } + } + + public async void WriteMessage(Message message) + { + async Task LaunchIde() + { + if (IsConnected) + return; + + launchIdeAction(); + await Task.WhenAny(Task.Delay(10000), AwaitClientConnected()); + } + + await LaunchIde(); + + if (!IsConnected) + { + Logger.LogError("Cannot write message: Godot Ide Server not connected"); + return; + } + + Connection.WriteMessage(message); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + listener?.Stop(); + + metaFile?.Dispose(); + + File.Delete(MetaFilePath); + } + } + + protected virtual bool HandleMessage(Message message) + { + if (messageHandlers.TryGetValue(message.Id, out var action)) + { + action(message.Arguments); + return true; + } + + return false; + } + + private readonly Dictionary<string, Action<string[]>> messageHandlers; + + private Dictionary<string, Action<string[]>> InitializeMessageHandlers() + { + return new Dictionary<string, Action<string[]>> + { + ["Play"] = args => + { + switch (args.Length) + { + case 0: + Play(); + return; + case 2: + Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1])); + return; + default: + throw new ArgumentException(); + } + }, + ["ReloadScripts"] = args => ReloadScripts() + }; + } + + private void DispatchToMainThread(Action action) + { + var d = new SendOrPostCallback(state => action()); + Godot.Dispatcher.SynchronizationContext.Post(d, null); + } + + private void Play() + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void Play(string debuggerHost, int debuggerPort) + { + DispatchToMainThread(() => + { + CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort); + Internal.EditorRunPlay(); + CurrentPlayRequest = null; + }); + } + + private void ReloadScripts() + { + DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts); + } + + public PlayRequest? CurrentPlayRequest { get; private set; } + + public struct PlayRequest + { + public bool HasDebugger { get; } + public string DebuggerHost { get; } + public int DebuggerPort { get; } + + public PlayRequest(string debuggerHost, int debuggerPort) + { + HasDebugger = true; + DebuggerHost = debuggerHost; + DebuggerPort = debuggerPort; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs new file mode 100644 index 0000000000..1dfc91d6d1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs @@ -0,0 +1,8 @@ +namespace GodotTools.Ides.MonoDevelop +{ + public enum EditorId + { + MonoDevelop = 0, + VisualStudioForMac = 1 + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 61a0a992ce..1fdccf5bbd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -1,4 +1,3 @@ -using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; @@ -6,22 +5,18 @@ using System.Diagnostics; using GodotTools.Internals; using GodotTools.Utils; -namespace GodotTools +namespace GodotTools.Ides.MonoDevelop { - public class MonoDevelopInstance + public class Instance { - public enum EditorId - { - MonoDevelop = 0, - VisualStudioForMac = 1 - } - private readonly string solutionFile; private readonly EditorId editorId; private Process process; - public void Execute(params string[] files) + public bool IsRunning => process != null && !process.HasExited; + + public void Execute() { bool newWindow = process == null || process.HasExited; @@ -29,7 +24,7 @@ namespace GodotTools string command; - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { string bundleId = BundleIds[editorId]; @@ -61,16 +56,6 @@ namespace GodotTools if (newWindow) args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); - foreach (var file in files) - { - int semicolonIndex = file.IndexOf(';'); - - string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex); - string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex); - - args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); - } - if (command == null) throw new FileNotFoundException(); @@ -80,7 +65,7 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true }); } else @@ -89,14 +74,14 @@ namespace GodotTools { FileName = command, Arguments = string.Join(" ", args), - UseShellExecute = false + UseShellExecute = true })?.Dispose(); } } - public MonoDevelopInstance(string solutionFile, EditorId editorId) + public Instance(string solutionFile, EditorId editorId) { - if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX()) + if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX()) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); this.solutionFile = solutionFile; @@ -106,9 +91,9 @@ namespace GodotTools private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames; private static readonly IReadOnlyDictionary<EditorId, string> BundleIds; - static MonoDevelopInstance() + static Instance() { - if (Utils.OS.IsOSX()) + if (OS.IsOSX()) { ExecutableNames = new Dictionary<EditorId, string> { @@ -122,7 +107,7 @@ namespace GodotTools {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } - else if (Utils.OS.IsWindows()) + else if (OS.IsWindows()) { ExecutableNames = new Dictionary<EditorId, string> { @@ -133,7 +118,7 @@ namespace GodotTools {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } - else if (Utils.OS.IsUnix()) + else if (OS.IsUnix()) { ExecutableNames = new Dictionary<EditorId, string> { diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 9526dd3c6f..7783576910 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -46,6 +46,12 @@ namespace GodotTools.Internals public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + public static void EditorRunPlay() => internal_EditorRunPlay(); + + public static void EditorRunStop() => internal_EditorRunStop(); + + public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + // Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] @@ -95,5 +101,14 @@ namespace GodotTools.Internals [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_MonoWindowsInstallRoot(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunPlay(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorRunStop(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebugger_ReloadScripts(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index 288c65de74..e3c2c822a5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; namespace GodotTools.Utils { @@ -17,5 +18,12 @@ namespace GodotTools.Utils return orElse; } + + public static IEnumerable<string> EnumerateLines(this TextReader textReader) + { + string line; + while ((line = textReader.ReadLine()) != null) + yield return line; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs new file mode 100644 index 0000000000..a3490fa89f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public sealed class NotifyAwaiter<T> : INotifyCompletion + { + private Action continuation; + private Exception exception; + private T result; + + public bool IsCompleted { get; private set; } + + public T GetResult() + { + if (exception != null) + throw exception; + return result; + } + + public void OnCompleted(Action continuation) + { + if (this.continuation != null) + throw new InvalidOperationException("This awaiter has already been listened"); + this.continuation = continuation; + } + + public void SetResult(T result) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.result = result; + + continuation?.Invoke(); + } + + public void SetException(Exception exception) + { + if (IsCompleted) + throw new InvalidOperationException("This awaiter is already completed"); + + IsCompleted = true; + this.exception = exception; + + continuation?.Invoke(); + } + + public NotifyAwaiter<T> Reset() + { + continuation = null; + exception = null; + result = default; + IsCompleted = false; + return this; + } + + public NotifyAwaiter<T> GetAwaiter() + { + return this; + } + } +} diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 0014aaca70..7db1090e2a 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -350,6 +350,21 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { #endif } +void godot_icall_Internal_EditorRunPlay() { + EditorNode::get_singleton()->run_play(); +} + +void godot_icall_Internal_EditorRunStop() { + EditorNode::get_singleton()->run_stop(); +} + +void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { + ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + if (sed) { + sed->reload_scripts(); + } +} + MonoString *godot_icall_Utils_OS_GetPlatformName() { String os_name = OS::get_singleton()->get_name(); return GDMonoMarshal::mono_string_from_godot(os_name); @@ -414,7 +429,9 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); - mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); // Globals mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); diff --git a/modules/mono/glue/Managed/Files/Basis.cs b/modules/mono/glue/Managed/Files/Basis.cs index 421532b68f..0eb76e9c63 100644 --- a/modules/mono/glue/Managed/Files/Basis.cs +++ b/modules/mono/glue/Managed/Files/Basis.cs @@ -12,40 +12,6 @@ namespace Godot [StructLayout(LayoutKind.Sequential)] public struct Basis : IEquatable<Basis> { - private static readonly Basis identity = new Basis - ( - 1f, 0f, 0f, - 0f, 1f, 0f, - 0f, 0f, 1f - ); - - private static readonly Basis[] orthoBases = { - new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), - new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), - new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), - new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), - new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), - new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), - new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), - new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), - new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), - new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), - new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), - new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), - new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), - new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), - new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), - new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), - new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), - new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), - new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) - }; - // NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally. /// <summary> @@ -64,7 +30,6 @@ namespace Godot /// </summary> public Vector3 y { - get => Column1; set => Column1 = value; } @@ -75,7 +40,6 @@ namespace Godot /// </summary> public Vector3 z { - get => Column2; set => Column2 = value; } @@ -115,8 +79,6 @@ namespace Godot } } - public static Basis Identity => identity; - public Vector3 Scale { get @@ -361,7 +323,7 @@ namespace Godot for (int i = 0; i < 24; i++) { - if (orthoBases[i] == orth) + if (orth == _orthoBases[i]) return i; } @@ -531,6 +493,43 @@ namespace Godot } } + private static readonly Basis[] _orthoBases = { + new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), + new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), + new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), + new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), + new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), + new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), + new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), + new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), + new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), + new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), + new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), + new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), + new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), + new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) + }; + + private static readonly Basis _identity = new Basis(1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipX = new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1); + private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1); + private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1); + + public static Basis Identity { get { return _identity; } } + public static Basis FlipX { get { return _flipX; } } + public static Basis FlipY { get { return _flipY; } } + public static Basis FlipZ { get { return _flipZ; } } + public Basis(Quat quat) { real_t s = 2.0f / quat.LengthSquared; diff --git a/modules/mono/glue/Managed/Files/Dispatcher.cs b/modules/mono/glue/Managed/Files/Dispatcher.cs new file mode 100644 index 0000000000..072e0f20ff --- /dev/null +++ b/modules/mono/glue/Managed/Files/Dispatcher.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; + +namespace Godot +{ + public static class Dispatcher + { + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler(); + + public static GodotSynchronizationContext SynchronizationContext => + godot_icall_DefaultGodotTaskScheduler().Context; + } +} diff --git a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs index e727781d63..4b5e3f8761 100644 --- a/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs +++ b/modules/mono/glue/Managed/Files/GodotSynchronizationContext.cs @@ -6,17 +6,16 @@ namespace Godot { public class GodotSynchronizationContext : SynchronizationContext { - private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); public override void Post(SendOrPostCallback d, object state) { - queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); + _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); } public void ExecutePendingContinuations() { - KeyValuePair<SendOrPostCallback, object> workItem; - while (queue.TryTake(out workItem)) + while (_queue.TryTake(out var workItem)) { workItem.Key(workItem.Value); } diff --git a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs index 9a40fef5a9..8eaeea50dc 100644 --- a/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs +++ b/modules/mono/glue/Managed/Files/GodotTaskScheduler.cs @@ -8,7 +8,7 @@ namespace Godot { public class GodotTaskScheduler : TaskScheduler { - private GodotSynchronizationContext Context { get; set; } + internal GodotSynchronizationContext Context { get; } private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); public GodotTaskScheduler() @@ -28,14 +28,10 @@ namespace Godot protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { if (SynchronizationContext.Current != Context) - { return false; - } if (taskWasPreviouslyQueued) - { TryDequeue(task); - } return TryExecuteTask(task); } @@ -52,7 +48,8 @@ namespace Godot { lock (_tasks) { - return _tasks.ToArray(); + foreach (Task task in _tasks) + yield return task; } } diff --git a/modules/mono/glue/Managed/Managed.csproj b/modules/mono/glue/Managed/Managed.csproj index 61f738922b..ad55fe9539 100644 --- a/modules/mono/glue/Managed/Managed.csproj +++ b/modules/mono/glue/Managed/Managed.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -37,4 +37,4 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project> +</Project>
\ No newline at end of file diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp index 27fd715f60..8b9a1380d8 100644 --- a/modules/mono/glue/gd_glue.cpp +++ b/modules/mono/glue/gd_glue.cpp @@ -210,6 +210,10 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) { return GDMonoMarshal::mono_string_from_godot(vars); } +MonoObject *godot_icall_DefaultGodotTaskScheduler() { + return GDMonoUtils::mono_cache.task_scheduler_handle->get_target(); +} + void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var); mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert); @@ -233,6 +237,9 @@ void godot_register_gd_icalls() { mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists); mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes); mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str); + + // Dispatcher + mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler); } #endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h index d4e20e2887..a34c0bc50f 100644 --- a/modules/mono/glue/gd_glue.h +++ b/modules/mono/glue/gd_glue.h @@ -75,6 +75,8 @@ MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_object MonoString *godot_icall_GD_var2str(MonoObject *p_var); +MonoObject *godot_icall_DefaultGodotTaskScheduler(); + // Register internal calls void godot_register_gd_icalls(); diff --git a/modules/mono/icons/icon_c_#.svg b/modules/mono/icons/icon_c_#.svg new file mode 100644 index 0000000000..69664ca553 --- /dev/null +++ b/modules/mono/icons/icon_c_#.svg @@ -0,0 +1,5 @@ +<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(0 -1036.4)"> +<path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-0.55228 0-1-0.4478-1-1 0-0.5523 0.44772-1 1-1h1v-2zm1-9-0.56445 2.2578c-0.23643 0.076-0.46689 0.1692-0.68945 0.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-0.11191 0.2211-0.20723 0.4502-0.28516 0.6855l-2.2539 0.5625v2h5.2715c-0.17677-0.3037-0.27041-0.6486-0.27148-1 9.6e-6 -1.1046 0.89543-2 2-2s2 0.8954 2 2c-4.817e-4 0.3512-0.093442 0.6961-0.26953 1h5.2695v-2l-2.2578-0.5645c-0.07594-0.2357-0.1693-0.4655-0.2793-0.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-0.22113-0.1119-0.45028-0.2073-0.68555-0.2852l-0.5625-2.2539zm4 9c-0.71466-1e-4 -1.3751 0.3811-1.7324 1-0.35727 0.6188-0.35727 1.3812 0 2 0.35733 0.6189 1.0178 1.0001 1.7324 1h-2v2h2c0.71466 1e-4 1.3751-0.3811 1.7324-1 0.35727-0.6188 0.35727-1.3812 0-2-0.35733-0.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0"/> +</g> +</svg> diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index ee1ecf3be7..eed8812305 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -556,14 +556,14 @@ bool GDMono::_load_corlib_assembly() { } #ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { +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; - String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String dst_dir = GodotSharpDirs::get_res_assemblies_dir(); + 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; @@ -626,18 +626,28 @@ String GDMono::update_api_assemblies_from_prebuilt() { if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) return String(); // No update needed - print_verbose("Updating API assemblies"); + const int CONFIGS_LEN = 2; + String configs[CONFIGS_LEN] = { String("Debug"), String("Release") }; - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - 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"); + for (int i = 0; i < CONFIGS_LEN; i++) { + String config = configs[i]; - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + print_verbose("Updating '" + config + "' API assemblies"); - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR)) - return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + 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"); + + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false); + } + + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) || + !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) { + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true); + } + } return String(); // Updated successfully diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index c5bcce4fa1..4f7d3791f7 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -165,7 +165,7 @@ public: #endif #ifdef TOOLS_ENABLED - bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type); + bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config); String update_api_assemblies_from_prebuilt(); #endif diff --git a/modules/opus/audio_stream_opus.cpp b/modules/opus/audio_stream_opus.cpp index 615081d818..d3e8d3c9bb 100644 --- a/modules/opus/audio_stream_opus.cpp +++ b/modules/opus/audio_stream_opus.cpp @@ -287,8 +287,7 @@ int AudioStreamPlaybackOpus::mix(int16_t *p_buffer, int p_frames) { int ret = op_read(opus_file, (opus_int16 *)p_buffer, todo * stream_channels, ¤t_section); if (ret < 0) { playing = false; - ERR_EXPLAIN("Error reading Opus File: " + file); - ERR_BREAK(ret < 0); + ERR_BREAK_MSG(ret < 0, "Error reading Opus file: " + file + "."); } else if (ret == 0) { // end of song, reload? op_free(opus_file); diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 8b1f21d95d..cf6b396180 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -149,8 +149,7 @@ RES ResourceFormatPVR::load(const String &p_path, const String &p_original_path, format = Image::FORMAT_ETC; break; default: - ERR_EXPLAIN("Unsupported format in PVR texture: " + itos(flags & 0xFF)); - ERR_FAIL_V(RES()); + ERR_FAIL_V_MSG(RES(), "Unsupported format in PVR texture: " + itos(flags & 0xFF) + "."); } w.release(); diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp index 64f4c169cb..9b0a55eae3 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_compress_squish.cpp @@ -57,8 +57,7 @@ void image_decompress_squish(Image *p_image) { } else if (p_image->get_format() == Image::FORMAT_RGTC_RG) { squish_flags = squish::kBc5; } else { - ERR_EXPLAIN("Squish: Can't decompress unknown format: " + itos(p_image->get_format())); - ERR_FAIL_COND(true); + ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + "."); return; } diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index b0cd648734..a2ef88d130 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -109,12 +109,10 @@ Error ImageLoaderSVG::_create_image(Ref<Image> p_image, const PoolVector<uint8_t float upscale = upsample ? 2.0 : 1.0; int w = (int)(svg_image->width * p_scale * upscale); - ERR_EXPLAIN(vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max width.", rtos(p_scale))); - ERR_FAIL_COND_V(w > Image::MAX_WIDTH, ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_COND_V_MSG(w > Image::MAX_WIDTH, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max width.", rtos(p_scale))); int h = (int)(svg_image->height * p_scale * upscale); - ERR_EXPLAIN(vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max height.", rtos(p_scale))); - ERR_FAIL_COND_V(h > Image::MAX_HEIGHT, ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_COND_V_MSG(h > Image::MAX_HEIGHT, ERR_PARAMETER_RANGE_ERROR, vformat("Can't create image from SVG with scale %s, the resulting image size exceeds max height.", rtos(p_scale))); PoolVector<uint8_t> dst_image; dst_image.resize(w * h * 4); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index 4425565afa..3f8b2b1831 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -328,8 +328,7 @@ void VisualScript::add_node(const StringName &p_func, int p_id, const Ref<Visual if (Object::cast_to<VisualScriptFunction>(*p_node)) { //the function indeed - ERR_EXPLAIN("A function node already has been set here."); - ERR_FAIL_COND(func.function_id >= 0); + ERR_FAIL_COND_MSG(func.function_id >= 0, "A function node has already been set here."); func.function_id = p_id; } @@ -1917,8 +1916,7 @@ Variant VisualScriptInstance::call(const StringName &p_method, const Variant **p if (!E) { r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; - ERR_EXPLAIN("No VisualScriptFunction node in function!"); - ERR_FAIL_V(Variant()); + ERR_FAIL_V_MSG(Variant(), "No VisualScriptFunction node in function."); } VisualScriptNodeInstance *node = E->get(); @@ -1974,8 +1972,7 @@ String VisualScriptInstance::to_string(bool *r_valid) { if (ret.get_type() != Variant::STRING) { if (r_valid) *r_valid = false; - ERR_EXPLAIN("Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); - ERR_FAIL_V(String()); + ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String."); } if (r_valid) *r_valid = true; @@ -2262,15 +2259,10 @@ Variant VisualScriptFunctionState::_signal_callback(const Variant **p_args, int ERR_FAIL_COND_V(function == StringName(), Variant()); #ifdef DEBUG_ENABLED - if (instance_id && !ObjectDB::get_instance(instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); - ERR_FAIL_V(Variant()); - } - if (script_id && !ObjectDB::get_instance(script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(instance_id && !ObjectDB::get_instance(instance_id), Variant(), "Resumed after yield, but class instance is gone."); + ERR_FAIL_COND_V_MSG(script_id && !ObjectDB::get_instance(script_id), Variant(), "Resumed after yield, but script is gone."); + #endif r_error.error = Variant::CallError::CALL_OK; @@ -2329,15 +2321,10 @@ Variant VisualScriptFunctionState::resume(Array p_args) { ERR_FAIL_COND_V(function == StringName(), Variant()); #ifdef DEBUG_ENABLED - if (instance_id && !ObjectDB::get_instance(instance_id)) { - ERR_EXPLAIN("Resumed after yield, but class instance is gone"); - ERR_FAIL_V(Variant()); - } - if (script_id && !ObjectDB::get_instance(script_id)) { - ERR_EXPLAIN("Resumed after yield, but script is gone"); - ERR_FAIL_V(Variant()); - } + ERR_FAIL_COND_V_MSG(instance_id && !ObjectDB::get_instance(instance_id), Variant(), "Resumed after yield, but class instance is gone."); + ERR_FAIL_COND_V_MSG(script_id && !ObjectDB::get_instance(script_id), Variant(), "Resumed after yield, but script is gone."); + #endif Variant::CallError r_error; diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index 2f4a45f108..2f56e778b9 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -116,8 +116,7 @@ int AudioStreamPlaybackOGGVorbis::mix(int16_t *p_buffer, int p_frames) { if (ret < 0) { playing = false; - ERR_EXPLAIN("Error reading OGG Vorbis File: " + file); - ERR_BREAK(ret < 0); + ERR_BREAK_MSG(ret < 0, "Error reading OGG Vorbis file: " + file + "."); } else if (ret == 0) { // end of song, reload? ov_clear(&vf); diff --git a/modules/webm/video_stream_webm.cpp b/modules/webm/video_stream_webm.cpp index 3670edc9ea..fa3602ad27 100644 --- a/modules/webm/video_stream_webm.cpp +++ b/modules/webm/video_stream_webm.cpp @@ -53,8 +53,7 @@ public: file = FileAccess::open(p_file, FileAccess::READ); - ERR_EXPLAIN("Failed loading resource: '" + p_file + "';"); - ERR_FAIL_COND(!file); + ERR_FAIL_COND_MSG(!file, "Failed loading resource: '" + p_file + "'."); } ~MkvReader() { diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 630c15f140..8986e49cf0 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -84,8 +84,7 @@ static Ref<Image> _webp_lossy_unpack(const PoolVector<uint8_t> &p_buffer) { ERR_FAIL_COND_V(r[0] != 'W' || r[1] != 'E' || r[2] != 'B' || r[3] != 'P', Ref<Image>()); WebPBitstreamFeatures features; if (WebPGetFeatures(&r[4], size, &features) != VP8_STATUS_OK) { - ERR_EXPLAIN("Error unpacking WEBP image:"); - ERR_FAIL_V(Ref<Image>()); + ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image."); } /* diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp index 069918cc9c..996db35cba 100644 --- a/modules/webrtc/webrtc_data_channel_js.cpp +++ b/modules/webrtc/webrtc_data_channel_js.cpp @@ -68,10 +68,8 @@ void WebRTCDataChannelJS::_on_error() { } void WebRTCDataChannelJS::_on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string) { - if (in_buffer.space_left() < (int)(p_size + 5)) { - ERR_EXPLAIN("Buffer full! Dropping data"); - ERR_FAIL(); - } + + ERR_FAIL_COND_MSG(in_buffer.space_left() < (int)(p_size + 5), "Buffer full! Dropping data."); uint8_t is_string = p_is_string ? 1 : 0; in_buffer.write((uint8_t *)&p_size, 4); diff --git a/modules/webrtc/webrtc_multiplayer.cpp b/modules/webrtc/webrtc_multiplayer.cpp index 17dafff93a..a759b17b83 100644 --- a/modules/webrtc/webrtc_multiplayer.cpp +++ b/modules/webrtc/webrtc_multiplayer.cpp @@ -321,10 +321,8 @@ Error WebRTCMultiplayer::put_packet(const uint8_t *p_buffer, int p_buffer_size) if (target_peer > 0) { E = peer_map.find(target_peer); - if (!E) { - ERR_EXPLAIN("Invalid Target Peer: " + itos(target_peer)); - ERR_FAIL_V(ERR_INVALID_PARAMETER); - } + ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + "."); + ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); diff --git a/modules/webrtc/webrtc_peer_connection_gdnative.cpp b/modules/webrtc/webrtc_peer_connection_gdnative.cpp index af98aa750a..5e9dcb5366 100644 --- a/modules/webrtc/webrtc_peer_connection_gdnative.cpp +++ b/modules/webrtc/webrtc_peer_connection_gdnative.cpp @@ -51,13 +51,11 @@ Error WebRTCPeerConnectionGDNative::set_default_library(const godot_net_webrtc_l WebRTCPeerConnection *WebRTCPeerConnectionGDNative::_create() { WebRTCPeerConnectionGDNative *obj = memnew(WebRTCPeerConnectionGDNative); - ERR_EXPLAIN("Default GDNative WebRTC implementation not defined."); - ERR_FAIL_COND_V(!default_library, obj); + ERR_FAIL_COND_V_MSG(!default_library, obj, "Default GDNative WebRTC implementation not defined."); // Call GDNative constructor Error err = (Error)default_library->create_peer_connection(obj); - ERR_EXPLAIN("GDNative default library constructor returned an error"); - ERR_FAIL_COND_V(err != OK, obj); + ERR_FAIL_COND_V_MSG(err != OK, obj, "GDNative default library constructor returned an error."); return obj; } diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index a9f38f1a82..58d4c52688 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -131,14 +131,12 @@ void EMWSPeer::close(int p_code, String p_reason) { IP_Address EMWSPeer::get_connected_host() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(IP_Address()); + ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export."); }; uint16_t EMWSPeer::get_connected_port() const { - ERR_EXPLAIN("Not supported in HTML5 export"); - ERR_FAIL_V(0); + ERR_FAIL_V_MSG(0, "Not supported in HTML5 export."); }; EMWSPeer::EMWSPeer() { diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index e24cb850ec..62f09bfbf9 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -98,16 +98,14 @@ void WebSocketMultiplayerPeer::_bind_methods() { // int WebSocketMultiplayerPeer::get_available_packet_count() const { - ERR_EXPLAIN("Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_available_packet_count to get available packet count from peers when not using the MultiplayerAPI."); return _incoming_packets.size(); } Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).get_packet/var to communicate with peers when not using the MultiplayerAPI."); r_buffer_size = 0; @@ -127,8 +125,7 @@ Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buff Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_EXPLAIN("Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, ERR_UNCONFIGURED, "Please use get_peer(ID).put_packet/var to communicate with peers when not using the MultiplayerAPI."); PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); @@ -160,8 +157,7 @@ void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { int WebSocketMultiplayerPeer::get_packet_peer() const { - ERR_EXPLAIN("This function is not available when not using the MultiplayerAPI."); - ERR_FAIL_COND_V(!_is_multiplayer, 1); + ERR_FAIL_COND_V_MSG(!_is_multiplayer, 1, "This function is not available when not using the MultiplayerAPI."); ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); return _incoming_packets.front()->get().source; @@ -354,8 +350,7 @@ void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, u _peer_id = id; break; default: - ERR_EXPLAIN("Invalid multiplayer message"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid multiplayer message."); break; } } diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index af75f2a320..cf2ad55b4a 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -53,8 +53,7 @@ void WSLClient::_do_handshake() { // Header is too big disconnect_from_host(); _on_error(); - ERR_EXPLAIN("Response headers too big"); - ERR_FAIL(); + ERR_FAIL_MSG("Response headers too big."); } Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); if (err == ERR_FILE_EOF) { @@ -81,8 +80,7 @@ void WSLClient::_do_handshake() { if (!_verify_headers(protocol)) { disconnect_from_host(); _on_error(); - ERR_EXPLAIN("Invalid response headers"); - ERR_FAIL(); + ERR_FAIL_MSG("Invalid response headers."); } // Create peer. WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); @@ -103,29 +101,18 @@ bool WSLClient::_verify_headers(String &r_protocol) { String s = (char *)_resp_buf; Vector<String> psa = s.split("\r\n"); int len = psa.size(); - if (len < 4) { - ERR_EXPLAIN("Not enough response headers."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); Vector<String> req = psa[0].split(" ", false); - if (req.size() < 2) { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + // Wrong protocol - if (req[0] != "HTTP/1.1" || req[1] != "101") { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req[0] != "HTTP/1.1" || req[1] != "101", false, "Invalid protocol or status code."); Map<String, String> headers; for (int i = 1; i < len; i++) { Vector<String> header = psa[i].split(":", false, 1); - if (header.size() != 2) { - ERR_EXPLAIN("Invalid header -> " + psa[i]); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i] + "."); String name = header[0].to_lower(); String value = header[1].strip_edges(); if (headers.has(name)) @@ -251,8 +238,7 @@ void WSLClient::poll() { if (_connection == _tcp) { // Start SSL handshake ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); - ERR_EXPLAIN("SSL is not available in this build"); - ERR_FAIL_COND(ssl.is_null()); + 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) { disconnect_from_host(); @@ -332,8 +318,7 @@ uint16_t WSLClient::get_connected_port() const { } Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(_connection.is_valid(), FAILED); + ERR_FAIL_COND_V_MSG(_connection.is_valid(), FAILED, "Buffers sizes can only be set before listening or connecting."); _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; _in_pkt_size = nearest_shift(p_in_packets - 1); diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 0d09a4d74e..1feae420b9 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -45,29 +45,18 @@ WSLServer::PendingPeer::PendingPeer() { bool WSLServer::PendingPeer::_parse_request(const PoolStringArray p_protocols) { Vector<String> psa = String((char *)req_buf).split("\r\n"); int len = psa.size(); - if (len < 4) { - ERR_EXPLAIN("Not enough response headers."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(len < 4, false, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); Vector<String> req = psa[0].split(" ", false); - if (req.size() < 2) { - ERR_EXPLAIN("Invalid protocol or status code."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req.size() < 2, false, "Invalid protocol or status code."); + // Wrong protocol - if (req[0] != "GET" || req[2] != "HTTP/1.1") { - ERR_EXPLAIN("Invalid method or HTTP version."); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", false, "Invalid method or HTTP version."); Map<String, String> headers; for (int i = 1; i < len; i++) { Vector<String> header = psa[i].split(":", false, 1); - if (header.size() != 2) { - ERR_EXPLAIN("Invalid header -> " + psa[i]); - ERR_FAIL_V(false); - } + ERR_FAIL_COND_V_MSG(header.size() != 2, false, "Invalid header -> " + psa[i]); String name = header[0].to_lower(); String value = header[1].strip_edges(); if (headers.has(name)) @@ -115,11 +104,7 @@ Error WSLServer::PendingPeer::do_handshake(PoolStringArray p_protocols) { if (!has_request) { int read = 0; while (true) { - if (req_pos >= WSL_MAX_HEADER_SIZE) { - // Header is too big - ERR_EXPLAIN("Response headers too big"); - ERR_FAIL_V(ERR_OUT_OF_MEMORY); - } + ERR_FAIL_COND_V_MSG(req_pos >= WSL_MAX_HEADER_SIZE, ERR_OUT_OF_MEMORY, "Response headers too big."); Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) // Got an error return FAILED; @@ -277,8 +262,7 @@ void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { } Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(_server->is_listening(), FAILED); + ERR_FAIL_COND_V_MSG(_server->is_listening(), FAILED, "Buffers sizes can only be set before listening or connecting."); _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; _in_pkt_size = nearest_shift(p_in_packets - 1); diff --git a/platform/SCsub b/platform/SCsub index 20c89ae8c6..38bab59d74 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -29,4 +29,4 @@ with open_utf8('register_platform_apis.gen.cpp', 'w') as f: env.add_source_files(env.platform_sources, 'register_platform_apis.gen.cpp') lib = env.add_library('platform', env.platform_sources) -env.Prepend(LIBS=lib) +env.Prepend(LIBS=[lib]) diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index dc0a63d171..16e49e8a38 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -32,6 +32,7 @@ #include "core/io/marshalls.h" #include "core/io/zip_io.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "core/project_settings.h" @@ -1398,8 +1399,9 @@ public: return ERR_UNCONFIGURED; } - //export_temp + // Export_temp APK. if (ep.step("Exporting APK", 0)) { + device_lock->unlock(); return ERR_SKIP; } @@ -1409,11 +1411,20 @@ public: if (use_reverse) p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; - String export_to = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); - Error err = export_project(p_preset, true, export_to, p_debug_flags); - if (err) { - device_lock->unlock(); - return err; + String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport.apk"); + +#define CLEANUP_AND_RETURN(m_err) \ + { \ + DirAccess::remove_file_or_error(tmp_export_path); \ + device_lock->unlock(); \ + return m_err; \ + } + + // Export to temporary APK before sending to device. + Error err = export_project(p_preset, true, tmp_export_path, p_debug_flags); + + if (err != OK) { + CLEANUP_AND_RETURN(err); } List<String> args; @@ -1425,7 +1436,7 @@ public: if (remove_prev) { if (ep.step("Uninstalling...", 1)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } print_line("Uninstalling previous version: " + devices[p_device].name); @@ -1440,7 +1451,7 @@ public: print_line("Installing to device (please wait...): " + devices[p_device].name); if (ep.step("Installing to device (please wait...)", 2)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } args.clear(); @@ -1448,13 +1459,12 @@ public: args.push_back(devices[p_device].id); args.push_back("install"); args.push_back("-r"); - args.push_back(export_to); + args.push_back(tmp_export_path); err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not install to device."); - device_lock->unlock(); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } if (use_remote) { @@ -1508,7 +1518,7 @@ public: } if (ep.step("Running on Device...", 3)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } args.clear(); args.push_back("-s"); @@ -1528,11 +1538,11 @@ public: err = OS::get_singleton()->execute(adb, args, true, NULL, NULL, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not execute on device."); - device_lock->unlock(); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } - device_lock->unlock(); - return OK; + + CLEANUP_AND_RETURN(OK); +#undef CLEANUP_AND_RETURN } virtual Ref<Texture> get_run_icon() const { @@ -2023,8 +2033,16 @@ public: zlib_filefunc_def io2 = io; FileAccess *dst_f = NULL; io2.opaque = &dst_f; - String unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); - zipFile unaligned_apk = zipOpen2(unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); + + String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned.apk"); + +#define CLEANUP_AND_RETURN(m_err) \ + { \ + DirAccess::remove_file_or_error(tmp_unaligned_path); \ + return m_err; \ + } + + zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, NULL, &io2); bool use_32_fb = p_preset->get("graphics/32_bits_framebuffer"); bool immersive = p_preset->get("screen/immersive_mode"); @@ -2152,7 +2170,7 @@ public: } if (ep.step("Adding Files...", 1)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } Error err = OK; Vector<String> cl = cmdline.strip_edges().split(" "); @@ -2184,7 +2202,7 @@ public: unzClose(pkg); EditorNode::add_io_error("Could not write expansion package file: " + apkfname); - return OK; + CLEANUP_AND_RETURN(ERR_SKIP); } cl.push_back("--use_apk_expansion"); @@ -2271,8 +2289,8 @@ public: zipClose(unaligned_apk, NULL); unzClose(pkg); - if (err) { - return err; + if (err != OK) { + CLEANUP_AND_RETURN(err); } if (_signed) { @@ -2280,7 +2298,7 @@ public: String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); if (!FileAccess::exists(jarsigner)) { EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); - return OK; + CLEANUP_AND_RETURN(OK); } String keystore; @@ -2300,7 +2318,7 @@ public: } if (ep.step("Signing debug APK...", 103)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } } else { @@ -2309,13 +2327,13 @@ public: user = release_username; if (ep.step("Signing release APK...", 103)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } } if (!FileAccess::exists(keystore)) { EditorNode::add_io_error("Could not find keystore, unable to export."); - return ERR_FILE_CANT_OPEN; + CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN); } List<String> args; @@ -2333,30 +2351,30 @@ public: args.push_back(keystore); args.push_back("-storepass"); args.push_back(password); - args.push_back(unaligned_path); + args.push_back(tmp_unaligned_path); args.push_back(user); int retval; OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); if (retval) { EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } if (ep.step("Verifying APK...", 104)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } args.clear(); args.push_back("-verify"); args.push_back("-keystore"); args.push_back(keystore); - args.push_back(unaligned_path); + args.push_back(tmp_unaligned_path); args.push_back("-verbose"); OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); if (retval) { EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); - return ERR_CANT_CREATE; + CLEANUP_AND_RETURN(ERR_CANT_CREATE); } } @@ -2365,14 +2383,14 @@ public: static const int ZIP_ALIGNMENT = 4; if (ep.step("Aligning APK...", 105)) { - return ERR_SKIP; + CLEANUP_AND_RETURN(ERR_SKIP); } - unzFile tmp_unaligned = unzOpen2(unaligned_path.utf8().get_data(), &io); + unzFile tmp_unaligned = unzOpen2(tmp_unaligned_path.utf8().get_data(), &io); if (!tmp_unaligned) { - EditorNode::add_io_error("Could not find temp unaligned APK."); - return ERR_FILE_NOT_FOUND; + EditorNode::add_io_error("Could not unzip temporary unaligned APK."); + CLEANUP_AND_RETURN(ERR_FILE_NOT_FOUND); } ret = unzGoToFirstFile(tmp_unaligned); @@ -2442,7 +2460,7 @@ public: zipClose(final_apk, NULL); unzClose(tmp_unaligned); - return OK; + CLEANUP_AND_RETURN(OK); } virtual void get_platform_features(List<String> *r_features) { diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index ac43392700..a0d6ac9214 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -141,3 +141,6 @@ def configure(env): # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime. env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1']) + + #adding flag due to issue with emscripten 1.38.41 callMain method https://github.com/emscripten-core/emscripten/blob/incoming/ChangeLog.md#v13841-08072019 + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain"]']) diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index c68b420c61..69dd038709 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -362,12 +362,21 @@ int EditorExportPlatformJavaScript::get_device_count() const { Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { - String path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_export.html"); + String basepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); + String path = basepath + ".html"; Error err = export_project(p_preset, true, path, p_debug_flags); - if (err) { + if (err != OK) { + // Export generates several files, clean them up on failure. + DirAccess::remove_file_or_error(basepath + ".html"); + DirAccess::remove_file_or_error(basepath + ".js"); + DirAccess::remove_file_or_error(basepath + ".pck"); + DirAccess::remove_file_or_error(basepath + ".png"); + DirAccess::remove_file_or_error(basepath + ".wasm"); return err; } OS::get_singleton()->shell_open(String("file://") + path); + // FIXME: Find out how to clean up export files after running the successfully + // exported game. Might not be trivial. return OK; } diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 8cabc45250..56b0a44dbc 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -29,9 +29,11 @@ /*************************************************************************/ #include "export.h" + #include "core/io/marshalls.h" #include "core/io/resource_saver.h" #include "core/io/zip_io.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "core/project_settings.h" @@ -244,13 +246,17 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ copy->resize(icon_infos[i].size, icon_infos[i].size); if (icon_infos[i].is_png) { - //encode png icon + // Encode PNG icon. it->create_from_image(copy); String path = EditorSettings::get_singleton()->get_cache_dir().plus_file("icon.png"); ResourceSaver::save(path, it); FileAccess *f = FileAccess::open(path, FileAccess::READ); - ERR_FAIL_COND(!f); + if (!f) { + // Clean up generated file. + DirAccess::remove_file_or_error(path); + ERR_FAIL(); + } int ofs = data.size(); uint32_t len = f->get_len(); @@ -261,6 +267,10 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ len = BSWAP32(len); copymem(&data.write[ofs], icon_infos[i].name, 4); encode_uint32(len, &data.write[ofs + 4]); + + // Clean up generated file. + DirAccess::remove_file_or_error(path); + } else { PoolVector<uint8_t> src_data = copy->get_data(); @@ -561,7 +571,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } } } - //bleh? } if (data.size() > 0) { @@ -687,7 +696,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p // Clean up temporary .app dir OS::get_singleton()->move_to_trash(tmp_app_path_name); - } else { + + } else { // pck String pack_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".pck"); @@ -747,6 +757,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p zipCloseFileInZip(dst_pkg_zip); } } + + // Clean up generated file. + DirAccess::remove_file_or_error(pack_path); } } diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 024e7f1916..662ee49935 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -34,6 +34,7 @@ #include "core/io/zip_io.h" #include "core/math/crypto_core.h" #include "core/object.h" +#include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/project_settings.h" #include "core/version.h" @@ -133,8 +134,6 @@ class AppxPackager { String progress_task; FileAccess *package; - String tmp_blockmap_file_path; - String tmp_content_types_file_path; Set<String> mime_types; @@ -146,8 +145,8 @@ class AppxPackager { String hash_block(const uint8_t *p_block_data, size_t p_block_len); - void make_block_map(); - void make_content_types(); + void make_block_map(const String &p_path); + void make_content_types(const String &p_path); _FORCE_INLINE_ unsigned int buf_put_int16(uint16_t p_val, uint8_t *p_buf) { for (int i = 0; i < 2; i++) { @@ -208,9 +207,9 @@ String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len) return String(base64); } -void AppxPackager::make_block_map() { +void AppxPackager::make_block_map(const String &p_path) { - FileAccess *tmp_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::WRITE); + FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"); tmp_file->store_string("<BlockMap xmlns=\"http://schemas.microsoft.com/appx/2010/blockmap\" HashMethod=\"http://www.w3.org/2001/04/xmlenc#sha256\">"); @@ -253,9 +252,9 @@ String AppxPackager::content_type(String p_extension) { return "application/octet-stream"; } -void AppxPackager::make_content_types() { +void AppxPackager::make_content_types(const String &p_path) { - FileAccess *tmp_file = FileAccess::open(tmp_content_types_file_path, FileAccess::WRITE); + FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE); tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); tmp_file->store_string("<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">"); @@ -458,8 +457,6 @@ void AppxPackager::init(FileAccess *p_fa) { package = p_fa; central_dir_offset = 0; end_of_central_dir_offset = 0; - tmp_blockmap_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); - tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); } Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) { @@ -591,7 +588,9 @@ void AppxPackager::finish() { // Create and add block map file EditorNode::progress_task_step("export", "Creating block map...", 4); - make_block_map(); + const String &tmp_blockmap_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); + make_block_map(tmp_blockmap_file_path); + FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ); Vector<uint8_t> blockmap_buffer; blockmap_buffer.resize(blockmap_file->get_len()); @@ -604,8 +603,11 @@ void AppxPackager::finish() { memdelete(blockmap_file); // Add content types + EditorNode::progress_task_step("export", "Setting content types...", 5); - make_content_types(); + + const String &tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); + make_content_types(tmp_content_types_file_path); FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ); Vector<uint8_t> types_buffer; @@ -618,6 +620,10 @@ void AppxPackager::finish() { types_file->close(); memdelete(types_file); + // Cleanup generated files. + DirAccess::remove_file_or_error(tmp_blockmap_file_path); + DirAccess::remove_file_or_error(tmp_content_types_file_path); + // Pre-process central directory before signing for (int i = 0; i < file_metadata.size(); i++) { store_central_dir_header(file_metadata[i]); @@ -909,7 +915,8 @@ class EditorExportPlatformUWP : public EditorExportPlatform { if (err != OK) { String err_string = "Couldn't open temp logo file."; - + // Cleanup generated file. + DirAccess::remove_file_or_error(tmp_path); EditorNode::add_io_error(err_string); ERR_FAIL_V_MSG(data, err_string); } @@ -919,29 +926,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform { f->close(); memdelete(f); - - // Delete temp file - DirAccess *dir = DirAccess::open(tmp_path.get_base_dir(), &err); - - if (err != OK) { - - String err_string = "Couldn't open temp path to remove temp logo file."; - - EditorNode::add_io_error(err_string); - ERR_FAIL_V_MSG(data, err_string); - } - - err = dir->remove(tmp_path); - - memdelete(dir); - - if (err != OK) { - - String err_string = "Couldn't remove temp logo file."; - - EditorNode::add_io_error(err_string); - ERR_FAIL_V_MSG(data, err_string); - } + DirAccess::remove_file_or_error(tmp_path); return data; } diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 0d1e702d04..ca72393e43 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -1518,9 +1518,12 @@ void OS_X11::set_window_maximized(bool p_enabled) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - if (is_window_maximize_allowed()) { - while (p_enabled && !is_window_maximized()) { - // Wait for effective resizing (so the GLX context is too). + if (p_enabled && is_window_maximize_allowed()) { + // Wait for effective resizing (so the GLX context is too). + // Give up after 0.5s, it's not going to happen on this WM. + // https://github.com/godotengine/godot/issues/19978 + for (int attempt = 0; !is_window_maximized() && attempt < 50; attempt++) { + usleep(10000); } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 60a74066a4..4a763844f8 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1368,18 +1368,28 @@ void LineEdit::set_editable(bool p_editable) { // Reorganize context menu. menu->clear(); - if (editable) + + if (editable) { + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + } + + if (editable) { + menu->add_separator(); menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - if (editable) + + if (editable) { menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + menu->add_separator(); menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); - menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); } update(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index c55ada0ed8..32380b6457 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -207,6 +207,7 @@ bool Popup::is_exclusive() const { void Popup::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_as_minsize"), &Popup::set_as_minsize); ClassDB::bind_method(D_METHOD("popup_centered", "size"), &Popup::popup_centered, DEFVAL(Size2())); ClassDB::bind_method(D_METHOD("popup_centered_ratio", "ratio"), &Popup::popup_centered_ratio, DEFVAL(0.75)); ClassDB::bind_method(D_METHOD("popup_centered_minsize", "minsize"), &Popup::popup_centered_minsize, DEFVAL(Size2())); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index db277d3705..6ada0cba97 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -170,6 +170,10 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_exit() { + // discontinue because the focus_exit was caused by right-click context menu + if (line_edit->get_menu()->is_visible()) + return; + _text_entered(line_edit->get_text()); } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index ff0c723141..6e5f8de3b5 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -4450,18 +4450,28 @@ void TextEdit::set_readonly(bool p_readonly) { // Reorganize context menu. menu->clear(); - if (!readonly) + + if (!readonly) { + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + } + + if (!readonly) { + menu->add_separator(); menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - if (!readonly) + + if (!readonly) { menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + menu->add_separator(); menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + if (!readonly) { menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); - menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); } update(); @@ -6592,8 +6602,6 @@ TextEdit::TextEdit() { indent_size = 4; text.set_indent_size(indent_size); text.clear(); - //text.insert(1,"Mongolia..."); - //text.insert(2,"PAIS GENEROSO!!"); text.set_color_regions(&color_regions); h_scroll = memnew(HScrollBar); diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp index acf27d2bbf..1390ab55c4 100644 --- a/servers/audio/effects/audio_effect_record.cpp +++ b/servers/audio/effects/audio_effect_record.cpp @@ -217,7 +217,7 @@ Ref<AudioStreamSample> AudioEffectRecord::get_recording() const { PoolVector<uint8_t> dst_data; ERR_FAIL_COND_V(current_instance.is_null(), NULL); - ERR_FAIL_COND_V(current_instance->recording_data.size(), NULL); + ERR_FAIL_COND_V(current_instance->recording_data.size() == 0, NULL); if (dst_format == AudioStreamSample::FORMAT_8_BITS) { int data_size = current_instance->recording_data.size(); diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index 5c28705192..1dd6699851 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -961,6 +961,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, const Map<String if (r_type) { *r_type = IDENTIFIER_FUNCTION; } + return true; } } @@ -3861,14 +3862,13 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui return ERR_PARSE_ERROR; } + decl.size = ((uint32_t)tk.constant); tk = _get_token(); if (tk.type != TK_BRACKET_CLOSE) { _set_error("Expected ']'"); return ERR_PARSE_ERROR; } - - decl.size = ((uint32_t)tk.constant); var.array_size = decl.size; } @@ -4021,6 +4021,10 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui _set_error("Expected array initialization"); return ERR_PARSE_ERROR; } + if (is_const) { + _set_error("Expected initialization of constant"); + return ERR_PARSE_ERROR; + } } node->declarations.push_back(decl); @@ -4050,6 +4054,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui tk = _get_token(); node->declarations.push_back(decl); } else { + if (is_const) { + _set_error("Expected initialization of constant"); + return ERR_PARSE_ERROR; + } + VariableDeclarationNode *node = alloc_node<VariableDeclarationNode>(); node->datatype = type; node->precision = precision; @@ -4123,16 +4132,40 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui } else { _set_tkpos(pos); //rollback } - } else if (tk.type == TK_CF_WHILE) { - //if () {} + } else if (tk.type == TK_CF_DO || tk.type == TK_CF_WHILE) { + // do {} while() + // while() {} + bool is_do = tk.type == TK_CF_DO; + + BlockNode *do_block = NULL; + if (is_do) { + + do_block = alloc_node<BlockNode>(); + do_block->parent_block = p_block; + + Error err = _parse_block(do_block, p_builtin_types, true, true, true); + if (err) + return err; + + tk = _get_token(); + if (tk.type != TK_CF_WHILE) { + _set_error("Expected while after do"); + return ERR_PARSE_ERROR; + } + } tk = _get_token(); + if (tk.type != TK_PARENTHESIS_OPEN) { _set_error("Expected '(' after while"); return ERR_PARSE_ERROR; } ControlFlowNode *cf = alloc_node<ControlFlowNode>(); - cf->flow_op = FLOW_OP_WHILE; + if (is_do) { + cf->flow_op = FLOW_OP_DO; + } else { + cf->flow_op = FLOW_OP_WHILE; + } Node *n = _parse_and_reduce_expression(p_block, p_builtin_types); if (!n) return ERR_PARSE_ERROR; @@ -4142,18 +4175,30 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const Map<StringName, Bui _set_error("Expected ')' after expression"); return ERR_PARSE_ERROR; } + if (!is_do) { + BlockNode *block = alloc_node<BlockNode>(); + block->parent_block = p_block; + cf->expressions.push_back(n); + cf->blocks.push_back(block); + p_block->statements.push_back(cf); - BlockNode *block = alloc_node<BlockNode>(); - block->parent_block = p_block; - cf->expressions.push_back(n); - cf->blocks.push_back(block); - p_block->statements.push_back(cf); + Error err = _parse_block(block, p_builtin_types, true, true, true); + if (err) + return err; + } else { - Error err = _parse_block(block, p_builtin_types, true, true, true); - if (err) - return err; + cf->expressions.push_back(n); + cf->blocks.push_back(do_block); + p_block->statements.push_back(cf); + + tk = _get_token(); + if (tk.type != TK_SEMICOLON) { + _set_error("Expected ';'"); + return ERR_PARSE_ERROR; + } + } } else if (tk.type == TK_CF_FOR) { - //if () {} + // for() {} tk = _get_token(); if (tk.type != TK_PARENTHESIS_OPEN) { _set_error("Expected '(' after for"); |