diff options
Diffstat (limited to 'modules')
134 files changed, 3181 insertions, 1683 deletions
diff --git a/modules/SCsub b/modules/SCsub index 5ff4623743..fcc01e2c1b 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -22,36 +22,45 @@ env.CommandNoCache( ), ) -# Header to be included in `tests/test_main.cpp` to run module-specific tests. -if env["tests"]: - env.Depends("modules_tests.gen.h", Value(env.module_list)) - env.CommandNoCache( - "modules_tests.gen.h", - Value(env.module_list), - env.Run( - modules_builders.generate_modules_tests, - "Generating modules tests header.", - # NOTE: No need to run in subprocess since this is still executed serially. - subprocess=False, - ), - ) - env.AlwaysBuild("modules_tests.gen.h") vs_sources = [] +test_headers = [] # libmodule_<name>.a for each active module. for name, path in env.module_list.items(): env.modules_sources = [] - if not os.path.isabs(path): - SConscript(name + "/SCsub") # Built-in. - else: - SConscript(path + "/SCsub") # Custom. + # Name for built-in modules, (absolute) path for custom ones. + base_path = path if os.path.isabs(path) else name + SConscript(base_path + "/SCsub") lib = env_modules.add_library("module_%s" % name, env.modules_sources) env.Prepend(LIBS=[lib]) if env["vsproj"]: vs_sources += env.modules_sources + if env["tests"]: + # Lookup potential headers in `tests` subfolder. + import glob + + module_tests = sorted(glob.glob(os.path.join(base_path, "tests", "*.h"))) + if module_tests != []: + test_headers += module_tests + + +# Generate header to be included in `tests/test_main.cpp` to run module-specific tests. +if env["tests"]: + env.Depends("modules_tests.gen.h", test_headers) + env.CommandNoCache( + "modules_tests.gen.h", + test_headers, + env.Run( + modules_builders.generate_modules_tests, + "Generating modules tests header.", + # NOTE: No need to run in subprocess since this is still executed serially. + subprocess=False, + ), + ) + # libmodules.a with only register_module_types. # Must be last so that all libmodule_<name>.a libraries are on the right side # in the linker command. diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h index 6377f8915d..dffcadbcdc 100644 --- a/modules/bullet/shape_bullet.h +++ b/modules/bullet/shape_bullet.h @@ -105,7 +105,7 @@ private: }; class SphereShapeBullet : public ShapeBullet { - real_t radius; + real_t radius = 0.0; public: SphereShapeBullet(); @@ -137,8 +137,8 @@ private: }; class CapsuleShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CapsuleShapeBullet(); @@ -155,8 +155,8 @@ private: }; class CylinderShapeBullet : public ShapeBullet { - real_t height; - real_t radius; + real_t height = 0.0; + real_t radius = 0.0; public: CylinderShapeBullet(); diff --git a/modules/camera/camera_osx.h b/modules/camera/camera_osx.h index 9d67235839..b0db844599 100644 --- a/modules/camera/camera_osx.h +++ b/modules/camera/camera_osx.h @@ -39,7 +39,6 @@ class CameraOSX : public CameraServer { public: CameraOSX(); - ~CameraOSX(); void update_feeds(); }; diff --git a/modules/camera/camera_osx.mm b/modules/camera/camera_osx.mm index 391006bfc2..d199c31b2f 100644 --- a/modules/camera/camera_osx.mm +++ b/modules/camera/camera_osx.mm @@ -114,18 +114,12 @@ if (output) { [self removeOutput:output]; [output setSampleBufferDelegate:nil queue:nullptr]; - [output release]; output = nullptr; } [self commitConfiguration]; } -- (void)dealloc { - // bye bye - [super dealloc]; -} - - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // This gets called every time our camera has a new image for us to process. // May need to investigate in a way to throttle this if we get more images then we're rendering frames.. @@ -208,7 +202,6 @@ public: AVCaptureDevice *get_device() const; CameraFeedOSX(); - ~CameraFeedOSX(); void set_device(AVCaptureDevice *p_device); @@ -227,7 +220,6 @@ CameraFeedOSX::CameraFeedOSX() { void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { device = p_device; - [device retain]; // get some info NSString *device_name = p_device.localizedName; @@ -240,18 +232,6 @@ void CameraFeedOSX::set_device(AVCaptureDevice *p_device) { }; }; -CameraFeedOSX::~CameraFeedOSX() { - if (capture_session != nullptr) { - [capture_session release]; - capture_session = nullptr; - }; - - if (device != nullptr) { - [device release]; - device = nullptr; - }; -}; - bool CameraFeedOSX::activate_feed() { if (capture_session) { // Already recording! @@ -282,7 +262,6 @@ void CameraFeedOSX::deactivate_feed() { // end camera capture if we have one if (capture_session) { [capture_session cleanup]; - [capture_session release]; capture_session = nullptr; }; }; @@ -317,8 +296,6 @@ void CameraFeedOSX::deactivate_feed() { // remove notifications [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil]; - - [super dealloc]; } @end @@ -376,7 +353,3 @@ CameraOSX::CameraOSX() { // should only have one of these.... device_notifications = [[MyDeviceNotifications alloc] initForServer:this]; }; - -CameraOSX::~CameraOSX() { - [device_notifications release]; -}; diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 41b4682c84..fbddedbe55 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -1451,8 +1451,8 @@ Ref<Material> CSGCylinder3D::get_material() const { CSGCylinder3D::CSGCylinder3D() { // defaults - radius = 1.0; - height = 1.0; + radius = 0.5; + height = 2.0; sides = 8; cone = false; smooth_faces = true; @@ -1671,8 +1671,8 @@ Ref<Material> CSGTorus3D::get_material() const { CSGTorus3D::CSGTorus3D() { // defaults - inner_radius = 2.0; - outer_radius = 3.0; + inner_radius = 0.5; + outer_radius = 1.0; sides = 8; ring_sides = 6; smooth_faces = true; @@ -1694,7 +1694,7 @@ CSGBrush *CSGPolygon3D::_build_brush() { } int shape_sides = shape_polygon.size(); Vector<int> shape_faces = Geometry2D::triangulate_polygon(shape_polygon); - ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon"); + ERR_FAIL_COND_V_MSG(shape_faces.size() < 3, brush, "Failed to triangulate CSGPolygon. Make sure the polygon doesn't have any intersecting edges."); // Get polygon enclosing Rect2. Rect2 shape_rect(shape_polygon[0], Vector2()); diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index eed995a40e..6da9893368 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -239,7 +239,7 @@ class CSGBox3D : public CSGPrimitive3D { virtual CSGBrush *_build_brush() override; Ref<Material> material; - Vector3 size = Vector3(2, 2, 2); + Vector3 size = Vector3(1, 1, 1); protected: static void _bind_methods(); diff --git a/modules/csg/doc_classes/CSGBox3D.xml b/modules/csg/doc_classes/CSGBox3D.xml index d64e58ae4d..64b07a6cbb 100644 --- a/modules/csg/doc_classes/CSGBox3D.xml +++ b/modules/csg/doc_classes/CSGBox3D.xml @@ -5,14 +5,16 @@ </brief_description> <description> This node allows you to create a box for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the box. </member> - <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)"> + <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)"> The box's width, height and depth. </member> </members> diff --git a/modules/csg/doc_classes/CSGCombiner3D.xml b/modules/csg/doc_classes/CSGCombiner3D.xml index 422c5d35b7..16903141de 100644 --- a/modules/csg/doc_classes/CSGCombiner3D.xml +++ b/modules/csg/doc_classes/CSGCombiner3D.xml @@ -5,7 +5,9 @@ </brief_description> <description> For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGCombiner3D node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner3D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner3D node, and then do an operation that takes the two end results as its input to create the final shape. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> </class> diff --git a/modules/csg/doc_classes/CSGCylinder3D.xml b/modules/csg/doc_classes/CSGCylinder3D.xml index 40e989bfb3..fa61957c84 100644 --- a/modules/csg/doc_classes/CSGCylinder3D.xml +++ b/modules/csg/doc_classes/CSGCylinder3D.xml @@ -5,20 +5,22 @@ </brief_description> <description> This node allows you to create a cylinder (or cone) for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false"> If [code]true[/code] a cone is created, the [member radius] will only apply to one side. </member> - <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> + <member name="height" type="float" setter="set_height" getter="get_height" default="2.0"> The height of the cylinder. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the cylinder. </member> - <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0"> + <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5"> The radius of the cylinder. </member> <member name="sides" type="int" setter="set_sides" getter="get_sides" default="8"> diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml index 2810343139..660659a98b 100644 --- a/modules/csg/doc_classes/CSGMesh3D.xml +++ b/modules/csg/doc_classes/CSGMesh3D.xml @@ -4,9 +4,11 @@ A CSG Mesh shape that uses a mesh resource. </brief_description> <description> - This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. + This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> diff --git a/modules/csg/doc_classes/CSGPolygon3D.xml b/modules/csg/doc_classes/CSGPolygon3D.xml index ecbb7962d1..e8ef79a9e5 100644 --- a/modules/csg/doc_classes/CSGPolygon3D.xml +++ b/modules/csg/doc_classes/CSGPolygon3D.xml @@ -4,9 +4,11 @@ Extrudes a 2D polygon shape to create a 3D mesh. </brief_description> <description> - An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. + An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGMesh3D] for using 3D meshes as CSG nodes. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0"> @@ -46,7 +48,8 @@ When [member mode] is [constant MODE_PATH], this is the distance along the path, in meters, the texture coordinates will tile. When set to 0, texture coordinates will match geometry exactly with no tiling. </member> <member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)"> - The point array that defines the 2D polygon that is extruded. + The point array that defines the 2D polygon that is extruded. This can be a convex or concave polygon with 3 or more points. The polygon must [i]not[/i] have any intersecting edges. Otherwise, triangulation will fail and no mesh will be generated. + [b]Note:[/b] If only 1 or 2 points are defined in [member polygon], no mesh will be generated. </member> <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false"> If [code]true[/code], applies smooth shading to the extrusions. diff --git a/modules/csg/doc_classes/CSGPrimitive3D.xml b/modules/csg/doc_classes/CSGPrimitive3D.xml index 8f4c8b9451..7eac85368e 100644 --- a/modules/csg/doc_classes/CSGPrimitive3D.xml +++ b/modules/csg/doc_classes/CSGPrimitive3D.xml @@ -5,8 +5,10 @@ </brief_description> <description> Parent class for various CSG primitives. It contains code and functionality that is common between them. It cannot be used directly. Instead use one of the various classes that inherit from it. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces" default="false"> diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index f5031064d6..b2513703c0 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -5,8 +5,10 @@ </brief_description> <description> This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <methods> <method name="get_collision_layer_value" qualifiers="const"> diff --git a/modules/csg/doc_classes/CSGSphere3D.xml b/modules/csg/doc_classes/CSGSphere3D.xml index b8dfb4cf5f..1ef20bc7ca 100644 --- a/modules/csg/doc_classes/CSGSphere3D.xml +++ b/modules/csg/doc_classes/CSGSphere3D.xml @@ -5,8 +5,10 @@ </brief_description> <description> This node allows you to create a sphere for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> <member name="material" type="Material" setter="set_material" getter="get_material"> diff --git a/modules/csg/doc_classes/CSGTorus3D.xml b/modules/csg/doc_classes/CSGTorus3D.xml index 91ee63a4c9..09256af109 100644 --- a/modules/csg/doc_classes/CSGTorus3D.xml +++ b/modules/csg/doc_classes/CSGTorus3D.xml @@ -5,17 +5,19 @@ </brief_description> <description> This node allows you to create a torus for use with the CSG system. + [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. </description> <tutorials> + <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <members> - <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="2.0"> + <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5"> The inner radius of the torus. </member> <member name="material" type="Material" setter="set_material" getter="get_material"> The material used to render the torus. </member> - <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="3.0"> + <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0"> The outer radius of the torus. </member> <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6"> diff --git a/modules/cvtt/SCsub b/modules/cvtt/SCsub index e56177d6e9..1d5a7ff6a3 100644 --- a/modules/cvtt/SCsub +++ b/modules/cvtt/SCsub @@ -11,7 +11,16 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/cvtt/" thirdparty_sources = [ - "ConvectionKernels.cpp", + "ConvectionKernels_API.cpp", + "ConvectionKernels_ETC.cpp", + "ConvectionKernels_BC67.cpp", + "ConvectionKernels_IndexSelector.cpp", + "ConvectionKernels_BC6H_IO.cpp", + "ConvectionKernels_S3TC.cpp", + "ConvectionKernels_BC7_PrioData.cpp", + "ConvectionKernels_SingleFile.cpp", + "ConvectionKernels_BCCommon.cpp", + "ConvectionKernels_Util.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 9e0579740b..d18340a2c8 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -41,7 +41,7 @@ struct CVTTCompressionJobParams { bool is_hdr = false; bool is_signed = false; int bytes_per_pixel = 0; - + cvtt::BC7EncodingPlan bc7_plan; cvtt::Options options; }; @@ -116,7 +116,7 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options); } } else { - cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options); + cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options, p_job_params.bc7_plan); } unsigned int num_real_blocks = ((w - x_start) + 3) / 4; @@ -141,7 +141,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) { return; //do not compress, already compressed } - int w = p_image->get_width(); int h = p_image->get_height(); @@ -153,22 +152,8 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann } cvtt::Options options; - uint32_t flags = cvtt::Flags::Fastest; - - if (p_lossy_quality > 0.85) { - flags = cvtt::Flags::Ultra; - } else if (p_lossy_quality > 0.75) { - flags = cvtt::Flags::Better; - } else if (p_lossy_quality > 0.55) { - flags = cvtt::Flags::Default; - } else if (p_lossy_quality > 0.35) { - flags = cvtt::Flags::Fast; - } else if (p_lossy_quality > 0.15) { - flags = cvtt::Flags::Faster; - } - + uint32_t flags = cvtt::Flags::Default; flags |= cvtt::Flags::BC7_RespectPunchThrough; - if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normal map flags |= cvtt::Flags::Uniform; } @@ -215,12 +200,15 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann job_queue.job_params.is_signed = is_signed; job_queue.job_params.options = options; job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4; + cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5); -#ifdef NO_THREADS int num_job_threads = 0; -#else - int num_job_threads = OS::get_singleton()->can_use_threads() ? (OS::get_singleton()->get_processor_count() - 1) : 0; -#endif + // Amdahl's law (Wikipedia) + // If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized, + // therefore only the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many threads are devoted + // to a parallelized execution of this program, the minimum execution time cannot be less than one hour. + // + // The number of executions with different inputs can be increased while the latency is the same. Vector<CVTTCompressionRowTask> tasks; @@ -278,7 +266,6 @@ void image_compress_cvtt(Image *p_image, float p_lossy_quality, Image::UsedChann memdelete(threads_wb[i]); } } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } @@ -388,6 +375,5 @@ void image_decompress_cvtt(Image *p_image) { w >>= 1; h >>= 1; } - p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index c79d449d41..7d5557d197 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -132,8 +132,39 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua // Compress image data and (if required) mipmaps. const bool mipmaps = r_img->has_mipmaps(); - const int width = r_img->get_width(); - const int height = r_img->get_height(); + int width = r_img->get_width(); + int height = r_img->get_height(); + + /* + The first mipmap level of a compressed texture must be a multiple of 4. Quote from D3D11.3 spec: + + BC format surfaces are always multiples of full blocks, each block representing 4x4 pixels. + For mipmaps, the top level map is required to be a multiple of 4 size in all dimensions. + The sizes for the lower level maps are computed as they are for all mipmapped surfaces, + and thus may not be a multiple of 4, for example a top level map of 20 results in a second level + map size of 10. For these cases, there is a differing 'physical' size and a 'virtual' size. + The virtual size is that computed for each mip level without adjustment, which is 10 for the example. + The physical size is the virtual size rounded up to the next multiple of 4, which is 12 for the example, + and this represents the actual memory size. The sampling hardware will apply texture address + processing based on the virtual size (using, for example, border color if specified for accesses + beyond 10), and thus for the example case will not access the 11th and 12th row of the resource. + So for mipmap chains when an axis becomes < 4 in size, only texels 'a','b','e','f' + are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from, + the surface pitch, which can encompass additional padding beyond the physical surface size. + */ + int next_width = width <= 2 ? width : (width + 3) & ~3; + int next_height = height <= 2 ? height : (height + 3) & ~3; + if (next_width != width || next_height != height) { + r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS); + width = r_img->get_width(); + height = r_img->get_height(); + } + // ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed. + // Multiple-of-4 should be guaranteed by above. + // However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels, + // which are individually compressed Image objects that violate the above rule. + // Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4. + const uint8_t *src_read = r_img->get_data().ptr(); print_verbose(vformat("ETCPAK: Encoding image size %dx%d to format %s.", width, height, Image::get_format_name(target_format))); @@ -144,24 +175,48 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua uint8_t *dest_write = dest_data.ptrw(); int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + Vector<uint32_t> padded_src; for (int i = 0; i < mip_count + 1; i++) { // Get write mip metrics for target image. - int mip_w, mip_h; - int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, mip_w, mip_h); + int orig_mip_w, orig_mip_h; + int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). ERR_FAIL_COND(mip_ofs % 8 != 0); uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs]; // Block size. Align stride to multiple of 4 (RGBA8). - mip_w = (mip_w + 3) & ~3; - mip_h = (mip_h + 3) & ~3; + int mip_w = (orig_mip_w + 3) & ~3; + int mip_h = (orig_mip_h + 3) & ~3; const uint32_t blocks = mip_w * mip_h / 16; // Get mip data from source image for reading. int src_mip_ofs = r_img->get_mipmap_offset(i); const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; + // Pad textures to nearest block by smearing. + if (mip_w != orig_mip_w || mip_h != orig_mip_h) { + padded_src.resize(mip_w * mip_h); + uint32_t *ptrw = padded_src.ptrw(); + int x = 0, y = 0; + for (y = 0; y < orig_mip_h; y++) { + for (x = 0; x < orig_mip_w; x++) { + ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x]; + } + // First, smear in x. + for (; x < mip_w; x++) { + ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1]; + } + } + // Then, smear in y. + for (; y < mip_h; y++) { + for (x = 0; x < mip_w; x++) { + ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w]; + } + } + // Override the src_mip_read pointer to our temporary Vector. + src_mip_read = padded_src.ptr(); + } if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2 || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index 4cca907bf2..758c47eecc 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -123,7 +123,7 @@ Node3D *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_ bool corrupt = false; // safer to check this way as there can be different formatted headers - if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) { + if (fbx_header_string.contains("Kaydara FBX Binary")) { is_binary = true; print_verbose("[doc] is binary"); diff --git a/modules/fbx/fbx_parser/FBXAnimation.cpp b/modules/fbx/fbx_parser/FBXAnimation.cpp index 8c43aac8f6..8627c95012 100644 --- a/modules/fbx/fbx_parser/FBXAnimation.cpp +++ b/modules/fbx/fbx_parser/FBXAnimation.cpp @@ -130,7 +130,7 @@ AnimationCurve::~AnimationCurve() { AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc, const char *const *target_prop_whitelist /*= nullptr*/, size_t whitelist_size /*= 0*/) : - Object(id, element, name), target(), doc(doc) { + Object(id, element, name), doc(doc) { // find target node const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); diff --git a/modules/fbx/fbx_parser/FBXDeformer.cpp b/modules/fbx/fbx_parser/FBXDeformer.cpp index b888afd90e..a2b216ab09 100644 --- a/modules/fbx/fbx_parser/FBXDeformer.cpp +++ b/modules/fbx/fbx_parser/FBXDeformer.cpp @@ -104,7 +104,7 @@ Constraint::~Constraint() { // ------------------------------------------------------------------------------------------------ Cluster::Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name), valid_transformAssociateModel(false) { + Deformer(id, element, doc, name) { const ScopePtr sc = GetRequiredScope(element); // for( auto element : sc.Elements()) // { @@ -177,7 +177,7 @@ Cluster::~Cluster() { // ------------------------------------------------------------------------------------------------ Skin::Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Deformer(id, element, doc, name), accuracy(0.0f) { + Deformer(id, element, doc, name) { const ScopePtr sc = GetRequiredScope(element); // keep this it is used for debugging and any FBX format changes diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp index d8ccb4179c..e345b7fc18 100644 --- a/modules/fbx/fbx_parser/FBXParser.cpp +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -235,7 +235,7 @@ Scope::~Scope() { // ------------------------------------------------------------------------------------------------ Parser::Parser(const TokenList &tokens, bool is_binary) : - corrupt(false), tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { + tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { root = new_Scope(*this, true); scopes.push_back(root); } diff --git a/modules/fbx/fbx_parser/FBXProperties.cpp b/modules/fbx/fbx_parser/FBXProperties.cpp index 531f0743d6..7cbb3a2eda 100644 --- a/modules/fbx/fbx_parser/FBXProperties.cpp +++ b/modules/fbx/fbx_parser/FBXProperties.cpp @@ -145,8 +145,7 @@ std::string PeekPropertyName(const Element &element) { } // namespace // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable() : - element(nullptr) { +PropertyTable::PropertyTable() { } // Is used when dealing with FBX Objects not metadata. diff --git a/modules/gdnative/gdnative/packed_arrays.cpp b/modules/gdnative/gdnative/packed_arrays.cpp index bb6f0324a8..0c49694e0b 100644 --- a/modules/gdnative/gdnative/packed_arrays.cpp +++ b/modules/gdnative/gdnative/packed_arrays.cpp @@ -32,7 +32,7 @@ #include "core/variant/variant.h" -#include "core/math/vector2.h" +#include "core/math/vector2i.h" #include "core/math/vector3i.h" static_assert(sizeof(godot_packed_byte_array) == sizeof(PackedByteArray), "PackedByteArray size mismatch"); diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp index f4674850e3..7e0ce76c26 100644 --- a/modules/gdnative/gdnative/rect2.cpp +++ b/modules/gdnative/gdnative/rect2.cpp @@ -31,6 +31,8 @@ #include "gdnative/rect2.h" #include "core/math/rect2.h" +#include "core/math/rect2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_rect2) == sizeof(Rect2), "Rect2 size mismatch"); static_assert(sizeof(godot_rect2i) == sizeof(Rect2i), "Rect2i size mismatch"); diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp index 45ba790dc1..7dc07024e5 100644 --- a/modules/gdnative/gdnative/transform2d.cpp +++ b/modules/gdnative/gdnative/transform2d.cpp @@ -31,6 +31,7 @@ #include "gdnative/transform2d.h" #include "core/math/transform_2d.h" +#include "core/os/memory.h" static_assert(sizeof(godot_transform2d) == sizeof(Transform2D), "Transform2D size mismatch"); diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp index eb8ffd74cd..a8d4281d25 100644 --- a/modules/gdnative/gdnative/vector2.cpp +++ b/modules/gdnative/gdnative/vector2.cpp @@ -31,6 +31,8 @@ #include "gdnative/vector2.h" #include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "core/os/memory.h" static_assert(sizeof(godot_vector2) == sizeof(Vector2), "Vector2 size mismatch"); static_assert(sizeof(godot_vector2i) == sizeof(Vector2i), "Vector2i size mismatch"); diff --git a/modules/gdnative/gdnative_library_editor_plugin.cpp b/modules/gdnative/gdnative_library_editor_plugin.cpp index f94464826e..33ea078696 100644 --- a/modules/gdnative/gdnative_library_editor_plugin.cpp +++ b/modules/gdnative/gdnative_library_editor_plugin.cpp @@ -315,7 +315,6 @@ GDNativeLibraryEditor::GDNativeLibraryEditor() { NativePlatformConfig platform_ios; platform_ios.name = "iOS"; - platform_ios.entries.push_back("armv7"); platform_ios.entries.push_back("arm64"); platform_ios.entries.push_back("x86_64"); // iOS can use both Static and Dynamic libraries. diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp index ddde28811c..0309d1d9c7 100644 --- a/modules/gdnative/nativescript/api_generator.cpp +++ b/modules/gdnative/nativescript/api_generator.cpp @@ -288,7 +288,7 @@ List<ClassAPI> generate_c_api_classes() { String type; String name = argument.name; - if (argument.name.find(":") != -1) { + if (argument.name.contains(":")) { type = argument.name.get_slice(":", 1); name = argument.name.get_slice(":", 0); } else { @@ -324,7 +324,7 @@ List<ClassAPI> generate_c_api_classes() { property_api.getter = ClassDB::get_property_getter(class_name, p->get().name); property_api.setter = ClassDB::get_property_setter(class_name, p->get().name); - if (p->get().name.find(":") != -1) { + if (p->get().name.contains(":")) { property_api.type = p->get().name.get_slice(":", 1); property_api.name = p->get().name.get_slice(":", 0); } else { @@ -355,7 +355,7 @@ List<ClassAPI> generate_c_api_classes() { //method name method_api.method_name = method_info.name; //method return type - if (method_api.method_name.find(":") != -1) { + if (method_api.method_name.contains(":")) { method_api.return_type = method_api.method_name.get_slice(":", 1); method_api.method_name = method_api.method_name.get_slice(":", 0); } else { @@ -388,7 +388,7 @@ List<ClassAPI> generate_c_api_classes() { arg_name = arg_info.name; - if (arg_info.name.find(":") != -1) { + if (arg_info.name.contains(":")) { arg_type = arg_info.name.get_slice(":", 1); arg_name = arg_info.name.get_slice(":", 0); } else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index be304a43f0..5d5414c694 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -766,7 +766,7 @@ Variant NativeScriptInstance::call(const StringName &p_method, const Variant **p void NativeScriptInstance::notification(int p_notification) { #ifdef DEBUG_ENABLED if (p_notification == MainLoop::NOTIFICATION_CRASH) { - if (current_method_call != StringName("")) { + if (current_method_call != StringName()) { ERR_PRINT("NativeScriptInstance detected crash on method: " + current_method_call); current_method_call = ""; } diff --git a/modules/gdnative/pluginscript/pluginscript_script.cpp b/modules/gdnative/pluginscript/pluginscript_script.cpp index 71ab8ef0a2..ec3c9eb4ff 100644 --- a/modules/gdnative/pluginscript/pluginscript_script.cpp +++ b/modules/gdnative/pluginscript/pluginscript_script.cpp @@ -93,7 +93,7 @@ Variant PluginScript::_new(const Variant **p_args, int p_argcount, Callable::Cal REF ref; Object *owner = nullptr; - if (get_instance_base_type() == "") { + if (get_instance_base_type() == StringName()) { owner = memnew(RefCounted); } else { owner = ClassDB::instantiate(get_instance_base_type()); diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 5cc295bbab..ac6684a29c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -33,18 +33,6 @@ #include "../gdscript_tokenizer.h" #include "editor/editor_settings.h" -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - -static bool _is_hex_symbol(char32_t c) { - return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); -} - -static bool _is_bin_symbol(char32_t c) { - return (c == '0' || c == '1'); -} - Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; @@ -102,7 +90,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); - bool is_number = (str[j] >= '0' && str[j] <= '9'); + bool is_number = is_digit(str[j]); /* color regions */ if (is_a_symbol || in_region != -1) { @@ -241,14 +229,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } // allow ABCDEF in hex notation - if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { + if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) { is_number = true; } else { is_hex_notation = false; } // disallow anything not a 0 or 1 - if (is_bin_notation && (_is_bin_symbol(str[j]))) { + if (is_bin_notation && (is_binary_digit(str[j]))) { is_number = true; } else if (is_bin_notation) { is_bin_notation = false; @@ -270,7 +258,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } } - if (!in_word && _is_char(str[j]) && !is_number) { + if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) { in_word = true; } @@ -585,7 +573,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (name.find("/") != -1) { + if (name.contains("/")) { continue; } member_keywords[name] = member_variable_color; diff --git a/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd index 0824d894c5..edaccae018 100644 --- a/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor_templates/CharacterBody2D/basic_movement.gd @@ -3,7 +3,7 @@ extends _BASE_ const SPEED: float = 300.0 -const JUMP_FORCE: float = -400.0 +const JUMP_VELOCITY: float = -400.0 # Get the gravity from the project settings to be synced with RigidDynamicBody nodes. var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") @@ -16,7 +16,7 @@ func _physics_process(delta: float) -> void: # Handle Jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): - motion_velocity.y = JUMP_FORCE + motion_velocity.y = JUMP_VELOCITY # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd index ce6d67ae84..e191e5451a 100644 --- a/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor_templates/CharacterBody3D/basic_movement.gd @@ -3,7 +3,7 @@ extends _BASE_ const SPEED: float = 5.0 -const JUMP_FORCE: float = 4.5 +const JUMP_VELOCITY: float = 4.5 # Get the gravity from the project settings to be synced with RigidDynamicBody nodes. var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") @@ -16,7 +16,7 @@ func _physics_process(delta: float) -> void: # Handle Jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): - motion_velocity.y = JUMP_FORCE + motion_velocity.y = JUMP_VELOCITY # Get the input direction and handle the movement/deceleration. # As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd index 27383b878d..d71f2592fe 100644 --- a/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd +++ b/modules/gdscript/editor_templates/VisualShaderNodeCustom/basic.gd @@ -34,8 +34,5 @@ func _get_output_port_name(port: int) -> String: func _get_output_port_type(port: int) -> int: return PORT_TYPE_SCALAR -func _get_global_code(mode: Shader.Mode) -> String: - return "" - func _get_code(input_vars: Array[String], output_vars: Array[String], mode: Shader.Mode, type: VisualShader.Type) -> String: return output_vars[0] + " = 0.0;" diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 84db97625b..58a788e255 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -43,6 +43,7 @@ #include "gdscript_cache.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "gdscript_rpc_callable.h" #include "gdscript_warning.h" #ifdef TESTS_ENABLED @@ -827,7 +828,7 @@ Error GDScript::reload(bool p_keep_state) { return OK; } #else - if (source.find("_BASE_") != -1) { + if (source.contains("_BASE_")) { return OK; } #endif @@ -1375,7 +1376,13 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { while (sl) { const Map<StringName, GDScriptFunction *>::Element *E = sl->member_functions.find(p_name); if (E) { - r_ret = Callable(this->owner, E->key()); + Multiplayer::RPCConfig config; + config.name = p_name; + if (sptr->rpc_functions.find(config) != -1) { + r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key()))); + } else { + r_ret = Callable(this->owner, E->key()); + } return true; //index found } sl = sl->_base; @@ -2022,8 +2029,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "preload", "signal", "super", - "trait", - "yield", // var "const", "enum", @@ -2040,6 +2045,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { "return", "match", "while", + // These keywords are not implemented currently, but reserved for (potential) future use. + // We highlight them as keywords to make errors easier to understand. + "trait", + "namespace", + "yield", nullptr }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 0d295c3a51..94daba4bf6 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::ENUM; - type.builtin_type = Variant::OBJECT; + type.builtin_type = Variant::INT; type.is_constant = true; type.is_meta_type = true; @@ -196,7 +196,7 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C } if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { - if (current_data_type->native_type != StringName("")) { + if (current_data_type->native_type != StringName()) { return check_native_member_name_conflict( p_member_name, p_member_node, @@ -250,7 +250,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "RefCounted"; + result.native_type = SNAME("RefCounted"); } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -438,7 +438,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type StringName first = p_type->type_chain[0]->name; - if (first == "Variant") { + if (first == SNAME("Variant")) { result.kind = GDScriptParser::DataType::VARIANT; if (p_type->type_chain.size() > 1) { push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); @@ -447,9 +447,9 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } - if (first == "Object") { + if (first == SNAME("Object")) { result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Object"; + result.native_type = SNAME("Object"); if (p_type->type_chain.size() > 1) { push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); return GDScriptParser::DataType(); @@ -650,9 +650,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype = specified_type; if (member.variable->initializer != nullptr) { - if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { + if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) { + if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); } else { // TODO: Add warning. @@ -1400,9 +1400,9 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable type.is_meta_type = false; if (p_variable->initializer != nullptr) { - if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { + if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) { + if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) { push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); } else { // TODO: Add warning. @@ -1586,7 +1586,7 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc if (p_match_pattern->dictionary[i].key) { reduce_expression(p_match_pattern->dictionary[i].key); if (!p_match_pattern->dictionary[i].key->is_constant) { - push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression); + push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->dictionary[i].key); } } @@ -1877,11 +1877,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) { if (compatible) { - compatible = is_type_compatible(assignee_type, op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); if (!compatible) { if (assignee_type.is_hard_type()) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, assignee_type, true)) { + if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. @@ -2416,6 +2416,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { + // Enum type is treated as a dictionary value for function calls. + base_type.is_meta_type = false; + } + if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); } else if (!is_self && base_type.is_meta_type && !is_static) { @@ -2474,17 +2479,24 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); if (!cast_type.is_set()) { + mark_node_unsafe(p_cast); return; } - cast_type.is_meta_type = false; // The casted value won't be a type name. + cast_type = type_from_metatype(cast_type); // The casted value won't be a type name. p_cast->set_datatype(cast_type); if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!op_type.is_variant()) { bool valid = false; - if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { + if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are compatible between each other, so it's a safe cast. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { + // Convertint int to enum is always valid. + valid = true; + } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); @@ -2540,7 +2552,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Node"; + result.native_type = SNAME("Node"); result.builtin_type = Variant::OBJECT; if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { @@ -2586,6 +2598,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod const StringName &name = p_identifier->name; + if (base.kind == GDScriptParser::DataType::ENUM) { + if (base.is_meta_type) { + if (base.enum_values.has(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = base.enum_values[name]; + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::ENUM; + result.is_constant = true; + result.builtin_type = Variant::INT; + result.native_type = base.native_type; + result.enum_type = base.enum_type; + p_identifier->set_datatype(result); + return; + } else { + // Consider as a Dictionary, so it can be anything. + // This will be evaluated in the next if block. + base.kind = GDScriptParser::DataType::BUILTIN; + base.builtin_type = Variant::DICTIONARY; + base.is_meta_type = false; + } + } else { + push_error(R"(Cannot get property from enum value.)", p_identifier); + return; + } + } + if (base.kind == GDScriptParser::DataType::BUILTIN) { if (base.is_meta_type) { bool valid = true; @@ -2632,32 +2672,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } - if (base.kind == GDScriptParser::DataType::ENUM) { - if (base.is_meta_type) { - if (base.enum_values.has(name)) { - p_identifier->is_constant = true; - p_identifier->reduced_value = base.enum_values[name]; - - GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::ENUM_VALUE; - result.is_constant = true; - result.builtin_type = Variant::INT; - result.native_type = base.native_type; - result.enum_type = name; - p_identifier->set_datatype(result); - } else { - // Consider as a Dictionary - GDScriptParser::DataType dummy; - dummy.kind = GDScriptParser::DataType::VARIANT; - p_identifier->set_datatype(dummy); - } - } else { - push_error(R"(Cannot get property from enum value.)", p_identifier); - } - return; - } - GDScriptParser::ClassNode *base_class = base.class_type; // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). @@ -2793,7 +2807,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (element.identifier->name == p_identifier->name) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN; + type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN; type.builtin_type = Variant::INT; type.is_constant = true; if (element.parent_enum->identifier) { @@ -3493,6 +3507,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; result.is_constant = false; + if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { + result.builtin_type = Variant::INT; + } return result; } @@ -3507,7 +3524,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name; } else { result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = p_property.type; @@ -3549,6 +3566,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source r_default_arg_count = 0; StringName function_name = p_function; + if (p_base_type.kind == GDScriptParser::DataType::ENUM) { + if (p_base_type.is_meta_type) { + // Enum type can be treated as a dictionary value. + p_base_type.kind = GDScriptParser::DataType::BUILTIN; + p_base_type.builtin_type = Variant::DICTIONARY; + p_base_type.is_meta_type = false; + } else { + push_error("Cannot call function on enum value.", p_source); + return false; + } + } + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. Callable::CallError err; @@ -3799,6 +3828,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; + + if (p_a.kind == GDScriptParser::DataType::ENUM) { + if (p_a.is_meta_type) { + a_type = Variant::DICTIONARY; + } else { + a_type = Variant::INT; + } + } + if (p_b.kind == GDScriptParser::DataType::ENUM) { + if (p_b.is_meta_type) { + b_type = Variant::DICTIONARY; + } else { + b_type = Variant::INT; + } + } + Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); @@ -3828,7 +3873,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } // TODO: Add safe/unsafe return variable (for variant cases) -bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const { +bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -3848,7 +3893,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } - if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM && !p_source.is_meta_type) { // Enum value is also integer. valid = true; } @@ -3869,6 +3914,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM); + } +#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { @@ -3876,11 +3926,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ return true; } } - if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { - if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { - return true; - } - } return false; } @@ -3935,7 +3980,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -3972,7 +4016,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4cee5cb44a..2697a6ec2b 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -112,7 +112,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); - bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; + bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); bool class_exists(const StringName &p_class) const; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 4ac5a4a60e..6ada7d36f5 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -122,6 +122,10 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP } if (singleton->parser_map.has(p_path)) { ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]); + if (ref.is_null()) { + r_error = ERR_INVALID_DATA; + return ref; + } } else { if (!FileAccess::exists(p_path)) { r_error = ERR_FILE_NOT_FOUND; @@ -133,7 +137,6 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP ref->path = p_path; singleton->parser_map[p_path] = ref.ptr(); } - r_error = ref->raise_status(p_status); return ref; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ca125d3a07..108c988add 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -141,10 +141,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: - case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; - result.builtin_type = Variant::INT; + if (p_datatype.is_meta_type) { + result.builtin_type = Variant::DICTIONARY; + } else { + result.builtin_type = Variant::INT; + } break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); @@ -469,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype()); + GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + + if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. + cast_type.kind = GDScriptDataType::BUILTIN; + cast_type.builtin_type = Variant::INT; + } // Create temporary for result first since it will be deleted last. GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index d10e120410..f0dc830ed8 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -486,7 +486,7 @@ struct GDScriptCompletionIdentifier { static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; - if (enum_name.find(".") == -1) { + if (!enum_name.contains(".")) { return enum_name; } return enum_name.get_slice(".", 1); @@ -610,7 +610,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value); if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) { + if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { def_val = sub->get_datatype().to_string(); } else if (sub->reduced) { const Variant::Type vt = sub->reduced_value.get_type(); @@ -652,7 +652,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String } static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_annotation->name == "@export_range") { + if (p_annotation->name == SNAME("@export_range")) { if (p_argument == 3 || p_argument == 4) { // Slider hint. ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); @@ -662,7 +662,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); } - } else if (p_annotation->name == "@export_exp_easing") { + } else if (p_annotation->name == SNAME("@export_exp_easing")) { if (p_argument == 0 || p_argument == 1) { // Easing hint. ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); @@ -672,7 +672,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a hint2.insert_text = hint2.display.quote(p_quote_style); r_result.insert(hint2.display, hint2); } - } else if (p_annotation->name == "@export_node_path") { + } else if (p_annotation->name == SNAME("@export_node_path")) { ScriptCodeCompletionOption node("Node", ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(node.display, node); List<StringName> node_types; @@ -684,7 +684,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(option.display, option); } - } else if (p_annotation->name == "@warning_ignore") { + } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptCodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); r_result.insert(warning.display, warning); @@ -955,7 +955,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { continue; } - if (E.name.find("/") != -1) { + if (E.name.contains("/")) { continue; } ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); @@ -1000,7 +1000,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } for (const PropertyInfo &E : members) { - if (String(E.name).find("/") == -1) { + if (!String(E.name).contains("/")) { ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } @@ -1384,7 +1384,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, Object *baseptr = base.value; - if (all_is_const && String(call->function_name) == "get_node" && ClassDB::is_parent_class(native_type.native_type, "Node") && args.size()) { + if (all_is_const && call->function_name == SNAME("get_node") && ClassDB::is_parent_class(native_type.native_type, SNAME("Node")) && args.size()) { String arg1 = args[0]; if (arg1.begins_with("/root/")) { String which = arg1.get_slice("/", 2); @@ -2085,7 +2085,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex GDScriptParser::DataType base_type = p_base.type; bool is_static = base_type.is_meta_type; - if (is_static && p_method == "new") { + if (is_static && p_method == SNAME("new")) { r_type.type = base_type; r_type.type.is_meta_type = false; r_type.type.is_constant = false; @@ -2182,7 +2182,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_enum_hint.find(".") == -1) { + if (!p_enum_hint.contains(".")) { // Global constant or in the current class. StringName current_enum = p_enum_hint; if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) { @@ -2274,7 +2274,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_arghint = _make_arguments_hint(info, p_argidx); } - if (p_argidx == 0 && ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node")) { + if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (p_method == SNAME("get_node") || p_method == SNAME("has_node"))) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2291,7 +2291,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) { + if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent")) && p_method.operator String().contains("action")) { // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2639,7 +2639,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c ClassDB::get_virtual_methods(class_name, &virtual_methods); for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; - if (method_hint.find(":") != -1) { + if (method_hint.contains(":")) { method_hint = method_hint.get_slice(":", 0); } method_hint += "("; @@ -2650,7 +2650,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c method_hint += ", "; } String arg = mi.arguments[i].name; - if (arg.find(":") != -1) { + if (arg.contains(":")) { arg = arg.substr(0, arg.find(":")); } method_hint += arg; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index f1877df326..9424de9d22 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -248,7 +248,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { // If the return value is a GDScriptFunctionState reference, // then the function did await again after resuming. - if (ret.is_ref()) { + if (ret.is_ref_counted()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { completed = false; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 5e210074ed..8e4e457ec1 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -519,7 +519,7 @@ void GDScriptParser::parse_program() { // Check for @tool annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@tool") { + if (annotation->name == SNAME("@tool")) { // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). _is_tool = true; if (previous.type != GDScriptTokenizer::Token::NEWLINE) { @@ -573,7 +573,7 @@ void GDScriptParser::parse_program() { // Check for @icon annotation. AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == "@icon") { + if (annotation->name == SNAME("@icon")) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after "@icon" annotation.)"); } @@ -2105,7 +2105,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { - if (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) { + if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL)) { return previous_operand; } // Also switch multiline mode on here for infix operators. @@ -2415,6 +2415,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode push_error("Assignment is not allowed inside an expression."); return parse_expression(false); // Return the following expression. } + if (p_previous_operand == nullptr) { + return parse_expression(false); // Return the following expression. + } #ifdef DEBUG_ENABLED VariableNode *source_variable = nullptr; @@ -3500,7 +3503,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); - if (p_annotation->name == "@export") { + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); return false; @@ -3525,7 +3528,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(export_type.native_type, "Resource")) { + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = export_type.native_type; @@ -3737,8 +3740,6 @@ String GDScriptParser::DataType::to_string() const { } case ENUM: return enum_type.operator String() + " (enum)"; - case ENUM_VALUE: - return enum_type.operator String() + " (enum value)"; case UNRESOLVED: return "<unresolved type>"; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index e4311d2d5e..c09b07282f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -106,8 +106,7 @@ public: NATIVE, SCRIPT, CLASS, // GDScript. - ENUM, // Full enumeration. - ENUM_VALUE, // Value from enumeration. + ENUM, // Enumeration. VARIANT, // Can be any type. UNRESOLVED, }; @@ -185,8 +184,6 @@ public: return builtin_type == p_other.builtin_type; case NATIVE: case ENUM: - return native_type == p_other.native_type; - case ENUM_VALUE: return native_type == p_other.native_type && enum_type == p_other.enum_type; case SCRIPT: return script_type == p_other.script_type; diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp new file mode 100644 index 0000000000..07e5ed4171 --- /dev/null +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* gdscript_rpc_callable.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_rpc_callable.h" + +#include "core/templates/hashfuncs.h" +#include "scene/main/node.h" + +bool GDScriptRPCCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() == p_b->hash(); +} + +bool GDScriptRPCCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() < p_b->hash(); +} + +uint32_t GDScriptRPCCallable::hash() const { + return h; +} + +String GDScriptRPCCallable::get_as_text() const { + String class_name = object->get_class(); + Ref<Script> script = object->get_script(); + return class_name + "(" + script->get_path().get_file() + ")::" + String(method) + " (rpc)"; +} + +CallableCustom::CompareEqualFunc GDScriptRPCCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptRPCCallable::get_compare_less_func() const { + return compare_less; +} + +ObjectID GDScriptRPCCallable::get_object() const { + return object->get_instance_id(); +} + +void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + r_return_value = object->call(method, p_arguments, p_argcount, r_call_error); +} + +GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) { + object = p_object; + method = p_method; + h = method.hash(); + h = hash_djb2_one_64(object->get_instance_id(), h); + node = Object::cast_to<Node>(object); + ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node."); +} + +void GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { + if (unlikely(!node)) { + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + r_call_error.error = Callable::CallError::CALL_OK; + node->rpcp(p_peer_id, method, p_arguments, p_argcount); +} diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h new file mode 100644 index 0000000000..2c8734a74b --- /dev/null +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* gdscript_rpc_callable.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_RPC_CALLABLE +#define GDSCRIPT_RPC_CALLABLE + +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class Node; + +class GDScriptRPCCallable : public CallableCustom { + Object *object = nullptr; + Node *node = nullptr; + StringName method; + uint32_t h = 0; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + void rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; + + GDScriptRPCCallable(Object *p_object, const StringName &p_method); + virtual ~GDScriptRPCCallable() = default; +}; + +#endif // GDSCRIPT_RPC_CALLABLE diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 05ea061798..d3287ab345 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -312,22 +312,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() { return error; } -static bool _is_alphanumeric(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} - -static bool _is_digit(char32_t c) { - return (c >= '0' && c <= '9'); -} - -static bool _is_hex_digit(char32_t c) { - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -static bool _is_binary_digit(char32_t c) { - return (c == '0' || c == '1'); -} - GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { Token token(p_type); token.start_line = start_line; @@ -448,10 +432,10 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To } GDScriptTokenizer::Token GDScriptTokenizer::annotation() { - if (!_is_alphanumeric(_peek())) { + if (!is_ascii_identifier_char(_peek())) { push_error("Expected annotation identifier after \"@\"."); } - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { // Consume all identifier characters. _advance(); } @@ -526,7 +510,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #define MAX_KEYWORD_LENGTH 10 // Consume all alphanumeric characters. - while (_is_alphanumeric(_peek())) { + while (is_ascii_identifier_char(_peek())) { _advance(); } @@ -612,7 +596,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_decimal = false; bool has_exponent = false; bool has_error = false; - bool (*digit_check_func)(char32_t) = _is_digit; + bool (*digit_check_func)(char32_t) = is_digit; if (_peek(-1) == '.') { has_decimal = true; @@ -620,20 +604,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { if (_peek() == 'x') { // Hexadecimal. base = 16; - digit_check_func = _is_hex_digit; + digit_check_func = is_hex_digit; _advance(); } else if (_peek() == 'b') { // Binary. base = 2; - digit_check_func = _is_binary_digit; + digit_check_func = is_binary_digit; _advance(); } } // Allow '_' to be used in a number, for readability. bool previous_was_underscore = false; - while (digit_check_func(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (digit_check_func(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -682,7 +666,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); // Consume decimal digits. - while (_is_digit(_peek()) || _peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { _advance(); } } @@ -696,7 +680,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); } // Consume exponent digits. - if (!_is_digit(_peek())) { + if (!is_digit(_peek())) { Token error = make_error(R"(Expected exponent value after "e".)"); error.start_column = column; error.leftmost_column = column; @@ -705,8 +689,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { push_error(error); } previous_was_underscore = false; - while (_is_digit(_peek()) || _peek() == '_') { - if (_peek() == '_') { + while (is_digit(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { if (previous_was_underscore) { Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); error.start_column = column; @@ -733,7 +717,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { error.rightmost_column = column + 1; push_error(error); has_error = true; - } else if (_is_alphanumeric(_peek())) { + } else if (is_ascii_identifier_char(_peek())) { // Letter at the end of the number. push_error("Invalid numeric notation."); } @@ -786,6 +770,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } String result; + char32_t prev = 0; + int prev_pos = 0; for (;;) { // Consume actual string. @@ -852,16 +838,18 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { case '\\': escaped = '\\'; break; - case 'u': + case 'U': + case 'u': { // Hexadecimal sequence. - for (int i = 0; i < 4; i++) { + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { if (_is_at_end()) { return make_error("Unterminated string."); } char32_t digit = _peek(); char32_t value = 0; - if (digit >= '0' && digit <= '9') { + if (is_digit(digit)) { value = digit - '0'; } else if (digit >= 'a' && digit <= 'f') { value = digit - 'a'; @@ -886,7 +874,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - break; + } break; case '\r': if (_peek() != '\n') { // Carriage return without newline in string. (???) @@ -909,11 +897,53 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { valid_escape = false; break; } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } + } if (valid_escape) { result += escaped; } } else if (ch == quote_char) { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } _advance(); if (is_multiline) { if (_peek() == quote_char && _peek(1) == quote_char) { @@ -930,6 +960,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { break; } } else { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } result += ch; _advance(); if (ch == '\n') { @@ -937,6 +974,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } } } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } // Make the literal. Variant string; @@ -1262,9 +1306,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { line_continuation = false; - if (_is_digit(c)) { + if (is_digit(c)) { return number(); - } else if (_is_alphanumeric(c)) { + } else if (is_ascii_identifier_char(c)) { return potential_identifier(); } @@ -1332,7 +1376,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (_peek() == '.') { _advance(); return make_token(Token::PERIOD_PERIOD); - } else if (_is_digit(_peek())) { + } else if (is_digit(_peek())) { // Number starting with '.'. return number(); } else { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index e0facaf61d..95122714f9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -1532,7 +1532,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - if (base->is_ref()) { + if (base->is_ref_counted()) { err_text = "Attempted to free a reference."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { @@ -1620,7 +1620,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - if (base->is_ref()) { + if (base->is_ref_counted()) { err_text = "Attempted to free a reference."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 73536f5f8e..ad96e36640 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -152,6 +152,9 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(3); return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); } + case INT_ASSIGNED_TO_ENUM: { + return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -199,6 +202,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "REDUNDANT_AWAIT", "EMPTY_FILE", "SHADOWED_GLOBAL_IDENTIFIER", + "INT_ASSIGNED_TO_ENUM", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 112b40781a..82efe3568f 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -70,6 +70,7 @@ public: REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). EMPTY_FILE, // A script file is empty. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. + INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting. WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 49f5303ae6..17886181d5 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -541,7 +541,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & for (int c = p_position.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } @@ -550,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & int end_pos = p_position.character; for (int c = p_position.character; c < line.length(); c++) { char32_t ch = line[c]; - bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; + bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { break; } diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index a944844226..d20b243616 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -63,7 +63,7 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr String function_signature = "func " + function; String source = script->get_source_code(); - if (source.find(function_signature) != -1) { + if (source.contains(function_signature)) { return; } @@ -269,7 +269,7 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { Vector<lsp::DocumentedSymbolInformation> script_symbols; E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { - if (query.is_subsequence_ofi(script_symbols[i].name)) { + if (query.is_subsequence_ofn(script_symbols[i].name)) { lsp::DocumentedSymbolInformation symbol = script_symbols[i]; symbol.location.uri = get_file_uri(symbol.location.uri); arr.push_back(symbol.to_json()); @@ -380,7 +380,7 @@ Error GDScriptWorkspace::initialize() { symbol.children.push_back(symbol_arg); } - if (data.qualifiers.find("vararg") != -1) { + if (data.qualifiers.contains("vararg")) { params += params.is_empty() ? "..." : ", ..."; } @@ -585,7 +585,7 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S stack.push_back(owner_scene_node); while (!stack.is_empty()) { - current = stack.pop_back(); + current = Object::cast_to<Node>(stack.pop_back()); Ref<GDScript> script = current->get_script(); if (script.is_valid() && script->get_path() == path) { break; diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 8e503a9df9..a63f9df918 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -1940,7 +1940,7 @@ static String marked_documentation(const String &p_bbcode) { line = "\t" + line.substr(code_block_indent, line.length()); } - if (in_code_block && line.find("[/codeblock]") != -1) { + if (in_code_block && line.contains("[/codeblock]")) { line = "\n"; in_code_block = false; } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 47772b8039..c2bb2caa29 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -73,23 +73,21 @@ void init_autoloads() { RES res = ResourceLoader::load(info.path); ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instantiate(); - } else if (res->is_class("Script")) { - Ref<Script> script_res = res; - StringName ibt = script_res->get_instance_base_type(); + Ref<PackedScene> scn = res; + Ref<Script> script = res; + if (scn.is_valid()) { + n = scn->instantiate(); + } else if (script.is_valid()) { + StringName ibt = script->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "Node"); ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); Object *obj = ClassDB::instantiate(ibt); - ERR_CONTINUE_MSG(obj == nullptr, - "Cannot instance script for autoload, expected 'Node' inheritance, got: " + - String(ibt)); + ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); n = Object::cast_to<Node>(obj); - n->set_script(script_res); + n->set_script(script); } ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); @@ -134,12 +132,14 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l if (do_init_languages) { init_language(p_source_dir); } +#ifdef DEBUG_ENABLED // Enable all warnings for GDScript, so we can test them. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); } +#endif // Enable printing to show results _print_line_enabled = true; @@ -153,6 +153,21 @@ GDScriptTestRunner::~GDScriptTestRunner() { } } +#ifndef DEBUG_ENABLED +static String strip_warnings(const String &p_expected) { + // On release builds we don't have warnings. Here we remove them from the output before comparison + // so it doesn't fail just because of difference in warnings. + String expected_no_warnings; + for (String line : p_expected.split("\n")) { + if (line.begins_with(">> ")) { + continue; + } + expected_no_warnings += line + "\n"; + } + return expected_no_warnings.strip_edges() + "\n"; +} +#endif + int GDScriptTestRunner::run_tests() { if (!make_tests()) { FAIL("An error occurred while making the tests."); @@ -170,6 +185,9 @@ int GDScriptTestRunner::run_tests() { GDScriptTest::TestResult result = test.run_test(); String expected = FileAccess::get_file_as_string(test.get_output_file()); +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif INFO(test.get_source_file()); if (!result.passed) { INFO(expected); @@ -233,6 +251,22 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { } } else { if (next.get_extension().to_lower() == "gd") { +#ifndef DEBUG_ENABLED + // On release builds, skip tests marked as debug only. + Error open_err = OK; + FileAccessRef script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err)); + if (open_err != OK) { + ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next)); + next = dir->get_next(); + continue; + } else { + if (script_file->get_line() == "#debug-only") { + next = dir->get_next(); + continue; + } + } +#endif + String out_file = next.get_basename() + ".out"; if (!is_generating && !dir->file_exists(out_file)) { ERR_FAIL_V_MSG(false, "Could not find output file for " + next); @@ -387,6 +421,10 @@ bool GDScriptTest::check_output(const String &p_output) const { String got = p_output.strip_edges(); // TODO: may be hacky. got += "\n"; // Make sure to insert newline for CI static checks. +#ifndef DEBUG_ENABLED + expected = strip_warnings(expected); +#endif + return got == expected; } @@ -469,6 +507,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { return result; } +#ifdef DEBUG_ENABLED StringBuilder warning_string; for (const GDScriptWarning &E : parser.get_warnings()) { const GDScriptWarning warning = E; @@ -482,6 +521,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { warning_string.append("\n"); } result.output += warning_string.as_string(); +#endif // Test compiling. GDScriptCompiler compiler; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..928c886650 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,10 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..03a1711d7b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +# Different enum types can't be assigned without casting. +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + +func test(): + print(class_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd new file mode 100644 index 0000000000..d08d3dd7b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out new file mode 100644 index 0000000000..fde7e92f8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd new file mode 100644 index 0000000000..ca6d892218 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd @@ -0,0 +1,6 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out new file mode 100644 index 0000000000..b1710c798d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd new file mode 100644 index 0000000000..edb785c8b6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: int = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: int = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd new file mode 100644 index 0000000000..726e4fd413 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = 0 as MyEnum + +func test(): + print(class_var) + class_var = 1 as MyEnum + print(class_var) + + var local_var: MyEnum = 0 as MyEnum + print(local_var) + local_var = 1 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd new file mode 100644 index 0000000000..798912c987 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd @@ -0,0 +1,14 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + +func test(): + print(class_var) + class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(class_var) + + var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum + print(local_var) + local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd new file mode 100644 index 0000000000..2bfb318c3c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd @@ -0,0 +1,13 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +var class_var: MyEnum = MyEnum.ENUM_VALUE_1 + +func test(): + print(class_var) + class_var = MyEnum.ENUM_VALUE_2 + print(class_var) + + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out new file mode 100644 index 0000000000..5f53802c33 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out @@ -0,0 +1,5 @@ +GDTEST_OK +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd new file mode 100644 index 0000000000..7022d14566 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd @@ -0,0 +1,21 @@ +# Enum is equivalent to int for comparisons and operations. +enum MyEnum { + ZERO, + ONE, + TWO, +} + +enum OtherEnum { + ZERO, + ONE, + TWO, +} + +func test(): + print(MyEnum.ZERO == OtherEnum.ZERO) + print(MyEnum.ZERO == 1) + print(MyEnum.ZERO != OtherEnum.ONE) + print(MyEnum.ZERO != 0) + + print(MyEnum.ONE + OtherEnum.TWO) + print(2 - MyEnum.ONE) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out new file mode 100644 index 0000000000..c8f34c11db --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +true +false +3 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd new file mode 100644 index 0000000000..885d70408a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd @@ -0,0 +1,13 @@ +enum MyEnum { + ZERO, + ONE, + TWO, +} + +func test(): + for key in MyEnum.keys(): + prints(key, MyEnum[key]) + + # https://github.com/godotengine/godot/issues/55491 + for key in MyEnum: + prints(key, MyEnum[key]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out new file mode 100644 index 0000000000..d29f53109c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out @@ -0,0 +1,7 @@ +GDTEST_OK +ZERO 0 +ONE 1 +TWO 2 +ZERO 0 +ONE 1 +TWO 2 diff --git a/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd new file mode 100644 index 0000000000..e9690ee93d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.gd @@ -0,0 +1,2 @@ +func test(): + $=$ diff --git a/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/dollar-assignment-bug-53696.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out index 447d7e223c..5b0ea9df43 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -1,2 +1,2 @@ GDTEST_OK -{2:4, a:1, b:2, with spaces:3} +{a:1, b:2, with spaces:3, 2:4} diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd new file mode 100644 index 0000000000..2be1024214 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd @@ -0,0 +1,15 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +# Assigning int value to enum-typed variable without explicit cast causes a warning. +# While it is valid it may be a mistake in the assignment. +var class_var: MyEnum = 0 + +func test(): + print(class_var) + class_var = 1 + print(class_var) + + var local_var: MyEnum = 0 + print(local_var) + local_var = 1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out new file mode 100644 index 0000000000..eef13bbff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 9 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 12 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +>> WARNING +>> Line: 14 +>> INT_ASSIGNED_TO_ENUM +>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type. +0 +1 +0 +1 diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd index 10780b5379..7b3c112fe9 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd @@ -1,3 +1,4 @@ +#debug-only func test(): var node := Node.new() var inside_tree = node.is_inside_tree diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out index e585c374e2..fe48ade26b 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out @@ -2,5 +2,5 @@ GDTEST_RUNTIME_ERROR >> SCRIPT ERROR >> on function: test() >> runtime/errors/callable_call_after_free_object.gd ->> 5 +>> 6 >> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance. diff --git a/modules/glslang/glslang_resource_limits.h b/modules/glslang/glslang_resource_limits.h index 05390f95ad..02d3daff07 100644 --- a/modules/glslang/glslang_resource_limits.h +++ b/modules/glslang/glslang_resource_limits.h @@ -132,15 +132,15 @@ const TBuiltInResource DefaultTBuiltInResource = { /* .maxDualSourceDrawBuffersEXT = */ 1, /* .limits = */ { - /* .nonInductiveForLoops = */ 1, - /* .whileLoops = */ 1, - /* .doWhileLoops = */ 1, - /* .generalUniformIndexing = */ 1, - /* .generalAttributeMatrixVectorIndexing = */ 1, - /* .generalVaryingIndexing = */ 1, - /* .generalSamplerIndexing = */ 1, - /* .generalVariableIndexing = */ 1, - /* .generalConstantMatrixVectorIndexing = */ 1, + /* .nonInductiveForLoops = */ true, + /* .whileLoops = */ true, + /* .doWhileLoops = */ true, + /* .generalUniformIndexing = */ true, + /* .generalAttributeMatrixVectorIndexing = */ true, + /* .generalVaryingIndexing = */ true, + /* .generalSamplerIndexing = */ true, + /* .generalVariableIndexing = */ true, + /* .generalConstantMatrixVectorIndexing = */ true, } }; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 5a931ed839..f555c8912d 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -50,13 +50,13 @@ #include "core/io/file_access.h" #include "core/io/file_access_memory.h" #include "core/io/json.h" +#include "core/io/stream_peer.h" #include "core/math/disjoint_set.h" #include "core/math/vector2.h" #include "core/variant/dictionary.h" #include "core/variant/typed_array.h" #include "core/variant/variant.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "drivers/png/png_driver_common.h" #include "editor/import/resource_importer_scene.h" #include "scene/2d/node_2d.h" @@ -3373,9 +3373,9 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { orm_texture_index = _set_texture(state, orm_texture); } if (has_ao) { - Dictionary ot; - ot["index"] = orm_texture_index; - d["occlusionTexture"] = ot; + Dictionary occt; + occt["index"] = orm_texture_index; + d["occlusionTexture"] = occt; } if (has_roughness || has_metalness) { mrt["index"] = orm_texture_index; @@ -5322,14 +5322,31 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd if (meshes.size() != 2) { return; } - Ref<Material> mat; - if (csg->get_material_override().is_valid()) { - mat = csg->get_material_override(); + + Ref<ImporterMesh> mesh; + mesh.instantiate(); + { + Ref<Mesh> csg_mesh = csg->get_meshes()[1]; + + for (int32_t surface_i = 0; surface_i < csg_mesh->get_surface_count(); surface_i++) { + Array array = csg_mesh->surface_get_arrays(surface_i); + Ref<Material> mat = csg_mesh->surface_get_material(surface_i); + String mat_name; + if (mat.is_valid()) { + mat_name = mat->get_name(); + } else { + // Assign default material when no material is assigned. + mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + } + mesh->add_surface(csg_mesh->surface_get_primitive_type(surface_i), + array, csg_mesh->surface_get_blend_shape_arrays(surface_i), csg_mesh->surface_get_lods(surface_i), mat, + mat_name, csg_mesh->surface_get_format(surface_i)); + } } + Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); - Ref<ImporterMesh> array_mesh = csg->get_meshes()[1]; - gltf_mesh->set_mesh(array_mesh); + gltf_mesh->set_mesh(mesh); GLTFMeshIndex mesh_i = state->meshes.size(); state->meshes.push_back(gltf_mesh); gltf_node->mesh = mesh_i; @@ -6277,7 +6294,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state p_track.rotation_track.values.write[key_i] = rotation; } } else if (track_type == Animation::TYPE_VALUE) { - if (path.find(":position") != -1) { + if (path.contains(":position")) { p_track.position_track.times = times; p_track.position_track.interpolation = gltf_interpolation; @@ -6288,7 +6305,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 position = p_animation->track_get_key_value(p_track_i, key_i); p_track.position_track.values.write[key_i] = position; } - } else if (path.find(":rotation") != -1) { + } else if (path.contains(":rotation")) { p_track.rotation_track.times = times; p_track.rotation_track.interpolation = gltf_interpolation; @@ -6299,7 +6316,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i); p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian); } - } else if (path.find(":scale") != -1) { + } else if (path.contains(":scale")) { p_track.scale_track.times = times; p_track.scale_track.interpolation = gltf_interpolation; @@ -6312,7 +6329,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state } } } else if (track_type == Animation::TYPE_BEZIER) { - if (path.find("/scale") != -1) { + if (path.contains("/scale")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.scale_track.times.size()) { Vector<real_t> new_times; @@ -6333,16 +6350,16 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.scale_track.values[key_i]; - if (path.find("/scale:x") != -1) { + if (path.contains("/scale:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:y") != -1) { + } else if (path.contains("/scale:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/scale:z") != -1) { + } else if (path.contains("/scale:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.scale_track.values.write[key_i] = bezier_track; } - } else if (path.find("/position") != -1) { + } else if (path.contains("/position")) { const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS; if (!p_track.position_track.times.size()) { Vector<real_t> new_times; @@ -6359,11 +6376,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state for (int32_t key_i = 0; key_i < keys; key_i++) { Vector3 bezier_track = p_track.position_track.values[key_i]; - if (path.find("/position:x") != -1) { + if (path.contains("/position:x")) { bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:y") != -1) { + } else if (path.contains("/position:y")) { bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); - } else if (path.find("/position:z") != -1) { + } else if (path.contains("/position:z")) { bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS); } p_track.position_track.values.write[key_i] = bezier_track; @@ -6384,7 +6401,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, continue; } String orig_track_path = animation->track_get_path(track_i); - if (String(orig_track_path).find(":position") != -1) { + if (String(orig_track_path).contains(":position")) { const Vector<String> node_suffix = String(orig_track_path).split(":position"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6400,7 +6417,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":rotation_degrees") != -1) { + } else if (String(orig_track_path).contains(":rotation_degrees")) { const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6416,7 +6433,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":scale") != -1) { + } else if (String(orig_track_path).contains(":scale")) { const Vector<String> node_suffix = String(orig_track_path).split(":scale"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6432,7 +6449,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(node_index, track); } } - } else if (String(orig_track_path).find(":transform") != -1) { + } else if (String(orig_track_path).contains(":transform")) { const Vector<String> node_suffix = String(orig_track_path).split(":transform"); const NodePath path = node_suffix[0]; const Node *node = ap->get_parent()->get_node_or_null(path); @@ -6443,7 +6460,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks().insert(transform_track_i.key, track); } } - } else if (String(orig_track_path).find(":blend_shapes/") != -1) { + } else if (String(orig_track_path).contains(":blend_shapes/")) { const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/"); const NodePath path = node_suffix[0]; const String suffix = node_suffix[1]; @@ -6499,7 +6516,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, } tracks[mesh_index] = track; } - } else if (String(orig_track_path).find(":") != -1) { + } else if (String(orig_track_path).contains(":")) { //Process skeleton const Vector<String> node_suffix = String(orig_track_path).split(":"); const String node = node_suffix[0]; @@ -6529,7 +6546,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, gltf_animation->get_tracks()[node_i] = track; } } - } else if (String(orig_track_path).find(":") == -1) { + } else if (!String(orig_track_path).contains(":")) { ERR_CONTINUE(!ap->get_parent()); Node *godot_node = ap->get_parent()->get_node_or_null(orig_track_path); for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : state->scene_nodes) { @@ -6654,8 +6671,8 @@ Error GLTFDocument::_serialize_version(Ref<GLTFState> state) { Dictionary asset; asset["version"] = version; - String hash = VERSION_HASH; - asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.length() == 0 ? String("unknown") : hash); + String hash = String(VERSION_HASH); + asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash); state->json["asset"] = asset; ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); @@ -6843,6 +6860,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> state, const String &p_pa } Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { + ERR_FAIL_NULL_V(state, nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); GLTFNodeIndex gltf_root = state->root_nodes.write[0]; Node *gltf_root_node = state->get_scene_node(gltf_root); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 67deedf839..6df7835855 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -813,6 +813,7 @@ void GridMap::_update_octants_callback() { } while (to_delete.front()) { + memdelete(octant_map[to_delete.front()->get()]); octant_map.erase(to_delete.front()->get()); to_delete.pop_front(); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 320901787d..84510fc71e 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -889,7 +889,7 @@ void GridMapEditor::update_palette() { name = "#" + itos(id); } - if (!filter.is_empty() && !filter.is_subsequence_ofi(name)) { + if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) { continue; } diff --git a/modules/meshoptimizer/register_types.cpp b/modules/meshoptimizer/register_types.cpp index 57a1b697be..597c12ed23 100644 --- a/modules/meshoptimizer/register_types.cpp +++ b/modules/meshoptimizer/register_types.cpp @@ -38,6 +38,9 @@ void register_meshoptimizer_types() { SurfaceTool::simplify_with_attrib_func = meshopt_simplifyWithAttributes; SurfaceTool::simplify_scale_func = meshopt_simplifyScale; SurfaceTool::simplify_sloppy_func = meshopt_simplifySloppy; + SurfaceTool::generate_remap_func = meshopt_generateVertexRemap; + SurfaceTool::remap_vertex_func = meshopt_remapVertexBuffer; + SurfaceTool::remap_index_func = meshopt_remapIndexBuffer; } void unregister_meshoptimizer_types() { @@ -45,4 +48,7 @@ void unregister_meshoptimizer_types() { SurfaceTool::simplify_func = nullptr; SurfaceTool::simplify_scale_func = nullptr; SurfaceTool::simplify_sloppy_func = nullptr; + SurfaceTool::generate_remap_func = nullptr; + SurfaceTool::remap_vertex_func = nullptr; + SurfaceTool::remap_index_func = nullptr; } diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp index 49044c4afe..8cd23ffb24 100644 --- a/modules/mobile_vr/mobile_vr_interface.cpp +++ b/modules/mobile_vr/mobile_vr_interface.cpp @@ -463,7 +463,7 @@ CameraMatrix MobileVRInterface::get_projection_for_view(uint32_t p_view, double return eye; }; -Vector<BlitToScreen> MobileVRInterface::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { +Vector<BlitToScreen> MobileVRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { _THREAD_SAFE_METHOD_ Vector<BlitToScreen> blit_to_screen; diff --git a/modules/mobile_vr/mobile_vr_interface.h b/modules/mobile_vr/mobile_vr_interface.h index 47dc33c0c7..8ecca3a2ae 100644 --- a/modules/mobile_vr/mobile_vr_interface.h +++ b/modules/mobile_vr/mobile_vr_interface.h @@ -151,7 +151,7 @@ public: virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; - virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; diff --git a/modules/modules_builders.py b/modules/modules_builders.py index 2243162555..13d5a2075a 100644 --- a/modules/modules_builders.py +++ b/modules/modules_builders.py @@ -14,13 +14,10 @@ def generate_modules_enabled(target, source, env): def generate_modules_tests(target, source, env): import os - import glob with open(target[0].path, "w") as f: - for name, path in env.module_list.items(): - headers = glob.glob(os.path.join(path, "tests", "*.h")) - for h in headers: - f.write('#include "%s"\n' % (os.path.normpath(h))) + for header in source: + f.write('#include "%s"\n' % (os.path.normpath(header.path))) if __name__ == "__main__": diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py index 93a66ebf6f..43c1ec8f8a 100644 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ b/modules/mono/build_scripts/mono_reg_utils.py @@ -96,10 +96,10 @@ def find_msbuild_tools_path_reg(): raise ValueError("Cannot find `installationPath` entry") except ValueError as e: print("Error reading output from vswhere: " + e.message) - except OSError: - pass # Fine, vswhere not found - except (subprocess.CalledProcessError, OSError): - pass + except subprocess.CalledProcessError as e: + print(e.output) + except OSError as e: + print(e) # Try to find 14.0 in the Registry diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9d416dcfce..085ab9a467 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1168,8 +1168,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #ifdef TOOLS_ENABLED // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->get_inspector()->update_tree(); - NodeDock::singleton->update_lists(); + InspectorDock::get_inspector_singleton()->update_tree(); + NodeDock::get_singleton()->update_lists(); } #endif } @@ -3245,6 +3245,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { CRASH_COND(!valid); #endif + GD_MONO_SCOPE_THREAD_ATTACH; + if (native) { StringName native_name = NATIVE_GDMONOCLASS_NAME(native); if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { @@ -3257,8 +3259,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { } } - GD_MONO_SCOPE_THREAD_ATTACH; - Callable::CallError unchecked_error; return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 2be588cac4..2de923c125 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -103,8 +103,6 @@ private: bool valid = false; bool reload_invalidated = false; - bool builtin; - GDMonoClass *base = nullptr; GDMonoClass *native = nullptr; GDMonoClass *script_class = nullptr; diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index ce2b378623..7a4641dbbc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -47,9 +47,13 @@ namespace GodotTools.OpenVisualStudio if (dte == null) { // Open a new instance + dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0"); - var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); - dte = (DTE)Activator.CreateInstance(visualStudioDteType); + if (dte == null) + { + // Launch of VS 2022 failed, fallback to 2019 + dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0"); + } dte.UserControl = true; @@ -133,6 +137,21 @@ namespace GodotTools.OpenVisualStudio return 0; } + private static DTE TryVisualStudioLaunch(string version) + { + try + { + var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true); + var dte = (DTE)Activator.CreateInstance(visualStudioDteType); + + return dte; + } + catch (COMException) + { + return null; + } + } + private static DTE FindInstanceEditingSolution(string solutionPath) { if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) @@ -164,7 +183,7 @@ namespace GodotTools.OpenVisualStudio continue; // The digits after the colon are the process ID - if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]")) + if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]")) continue; if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index e9cf7911be..2dbc78ab77 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -147,7 +147,7 @@ namespace GodotTools.Build Icon = GetThemeIcon("StatusError", "EditorIcons"), ExpandIcon = false, ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _errorsBtn.Toggled += ErrorsToggled; @@ -159,7 +159,7 @@ namespace GodotTools.Build Icon = GetThemeIcon("NodeWarning", "EditorIcons"), ExpandIcon = false, ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _warningsBtn.Toggled += WarningsToggled; @@ -169,7 +169,7 @@ namespace GodotTools.Build { Text = "Show Output".TTR(), ToggleMode = true, - Pressed = true, + ButtonPressed = true, FocusMode = FocusModeEnum.None }; _viewLogBtn.Toggled += ViewLogToggled; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index 37e6a34977..ed758cc137 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -537,7 +537,6 @@ MONO_AOT_MODE_LAST = 1000, { var iosArchs = new[] { - "armv7", "arm64" }; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 98c6881166..69960bdbeb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -428,7 +428,7 @@ namespace GodotTools Shortcut = buildSolutionShortcut, ShortcutInTooltip = true }; - _toolBarBuildButton.PressedSignal += BuildSolutionPressed; + _toolBarBuildButton.Pressed += BuildSolutionPressed; AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1b4ab0ef4b..f345dff333 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -279,8 +279,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("["); pos = brk_pos + 1; } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ")) { - String link_target = tag.substr(tag.find(" ") + 1, tag.length()); - String link_tag = tag.substr(0, tag.find(" ")); + const int tag_end = tag.find(" "); + const String link_tag = tag.substr(0, tag_end); + const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); Vector<String> link_target_parts = link_target.split("."); @@ -360,12 +361,38 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } } } else if (link_tag == "signal") { - // We do not declare signals in any way in C#, so there is nothing to reference - xml_output.append("<c>"); - xml_output.append(link_target); - xml_output.append("</c>"); + if (!target_itype || !target_itype->is_object_type) { + if (OS::get_singleton()->is_stdout_verbose()) { + if (target_itype) { + OS::get_singleton()->print("Cannot resolve signal reference for non-Godot.Object type in documentation: %s\n", link_target.utf8().get_data()); + } else { + OS::get_singleton()->print("Cannot resolve type from signal reference in documentation: %s\n", link_target.utf8().get_data()); + } + } + + // TODO Map what we can + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } else { + const SignalInterface *target_isignal = target_itype->find_signal_by_name(target_cname); + + if (target_isignal) { + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); + xml_output.append(target_itype->proxy_name); + xml_output.append("."); + xml_output.append(target_isignal->proxy_name); + xml_output.append("\"/>"); + } else { + ERR_PRINT("Cannot resolve signal reference in documentation: '" + link_target + "'."); + + xml_output.append("<c>"); + xml_output.append(link_target); + xml_output.append("</c>"); + } + } } else if (link_tag == "enum") { - StringName search_cname = !target_itype ? target_cname : StringName(target_itype->name + "." + (String)target_cname); + const StringName search_cname = !target_itype ? target_cname : StringName(target_itype->name + "." + (String)target_cname); const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(search_cname); @@ -401,7 +428,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(link_target); xml_output.append("</c>"); } else if (!target_itype && target_cname == name_cache.type_at_GlobalScope) { - String target_name = (String)target_cname; + const String target_name = (String)target_cname; // Try to find as a global constant const ConstantInterface *target_iconst = find_constant_by_name(target_name, global_constants); @@ -438,7 +465,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } } } else { - String target_name = (String)target_cname; + const String target_name = (String)target_cname; // Try to find the constant in the current class const ConstantInterface *target_iconst = find_constant_by_name(target_name, target_itype->constants); @@ -690,11 +717,11 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI continue; } - if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') { + if (is_digit(parts[curr_prefix_length][0])) { // The name of enum constants may begin with a numeric digit when strip from the enum prefix, // so we make the prefix for this constant one word shorter in those cases. for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) { - if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') { + if (!is_digit(parts[curr_prefix_length][0])) { break; } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 2e6ce3a952..5460f018f0 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -366,6 +366,16 @@ class BindingsGenerator { return nullptr; } + const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { + for (const MethodInterface &E : methods) { + if (E.proxy_name == p_proxy_name) { + return &E; + } + } + + return nullptr; + } + const PropertyInterface *find_property_by_name(const StringName &p_cname) const { for (const PropertyInterface &E : properties) { if (E.cname == p_cname) { @@ -386,8 +396,18 @@ class BindingsGenerator { return nullptr; } - const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { - for (const MethodInterface &E : methods) { + const SignalInterface *find_signal_by_name(const StringName &p_cname) const { + for (const SignalInterface &E : signals_) { + if (E.cname == p_cname) { + return &E; + } + } + + return nullptr; + } + + const SignalInterface *find_signal_by_proxy_name(const String &p_proxy_name) const { + for (const SignalInterface &E : signals_) { if (E.proxy_name == p_proxy_name) { return &E; } diff --git a/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs index 6e9547ce9b..b0ded3133f 100644 --- a/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs +++ b/modules/mono/editor_templates/CharacterBody2D/basic_movement.cs @@ -6,7 +6,7 @@ using System; public partial class _CLASS_ : _BASE_ { public const float Speed = 300.0f; - public const float JumpForce = -400.0f; + public const float JumpVelocity = -400.0f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); @@ -21,7 +21,7 @@ public partial class _CLASS_ : _BASE_ // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) - motionVelocity.y = JumpForce; + motionVelocity.y = JumpVelocity; // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs index 13be4bbcb1..d8c2f67ac8 100644 --- a/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs +++ b/modules/mono/editor_templates/CharacterBody3D/basic_movement.cs @@ -6,7 +6,7 @@ using System; public partial class _CLASS_ : _BASE_ { public const float Speed = 5.0f; - public const float JumpForce = 4.5f; + public const float JumpVelocity = 4.5f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); @@ -21,7 +21,7 @@ public partial class _CLASS_ : _BASE_ // Handle Jump. if (Input.IsActionJustPressed("ui_accept") && IsOnFloor()) - motionVelocity.y = JumpForce; + motionVelocity.y = JumpVelocity; // Get the input direction and handle the movement/deceleration. // As good practice, you should replace UI actions with custom gameplay actions. diff --git a/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs index 00fdc9968e..a1b93e7daa 100644 --- a/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor_templates/VisualShaderNodeCustom/basic.cs @@ -55,11 +55,6 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return 0; } - public override string _GetGlobalCode(Shader.Mode mode) - { - return ""; - } - public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) { return ""; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index a89dca6c34..a1f058ffe5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -266,7 +266,7 @@ namespace Godot /// <returns>The capitalized string.</returns> public static string Capitalize(this string instance) { - string aux = instance.Replace("_", " ").ToLower(); + string aux = instance.CamelcaseToUnderscore(true).Replace("_", " ").Trim(); string cap = string.Empty; for (int i = 0; i < aux.GetSliceCount(" "); i++) @@ -284,6 +284,51 @@ namespace Godot return cap; } + private static string CamelcaseToUnderscore(this string instance, bool lowerCase) + { + string newString = string.Empty; + int startIndex = 0; + + for (int i = 1; i < instance.Length; i++) + { + bool isUpper = char.IsUpper(instance[i]); + bool isNumber = char.IsDigit(instance[i]); + + bool areNext2Lower = false; + bool isNextLower = false; + bool isNextNumber = false; + bool wasPrecedentUpper = char.IsUpper(instance[i - 1]); + bool wasPrecedentNumber = char.IsDigit(instance[i - 1]); + + if (i + 2 < instance.Length) + { + areNext2Lower = char.IsLower(instance[i + 1]) && char.IsLower(instance[i + 2]); + } + + if (i + 1 < instance.Length) + { + isNextLower = char.IsLower(instance[i + 1]); + isNextNumber = char.IsDigit(instance[i + 1]); + } + + bool condA = isUpper && !wasPrecedentUpper && !wasPrecedentNumber; + bool condB = wasPrecedentUpper && isUpper && areNext2Lower; + bool condC = isNumber && !wasPrecedentNumber; + bool canBreakNumberLetter = isNumber && !wasPrecedentNumber && isNextLower; + bool canBreakLetterNumber = !isNumber && wasPrecedentNumber && (isNextLower || isNextNumber); + + bool shouldSplit = condA || condB || condC || canBreakNumberLetter || canBreakLetterNumber; + if (shouldSplit) + { + newString += instance.Substring(startIndex, i - startIndex) + "_"; + startIndex = i; + } + } + + newString += instance.Substring(startIndex, instance.Length - startIndex); + return lowerCase ? newString.ToLower() : newString; + } + /// <summary> /// Performs a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> @@ -727,7 +772,7 @@ namespace Godot /// <summary> /// Check whether this string is a subsequence of the given string. /// </summary> - /// <seealso cref="IsSubsequenceOfI(string, string)"/> + /// <seealso cref="IsSubsequenceOfN(string, string)"/> /// <param name="instance">The subsequence to search.</param> /// <param name="text">The string that contains the subsequence.</param> /// <param name="caseSensitive">If <see langword="true"/>, the check is case sensitive.</param> @@ -779,7 +824,7 @@ namespace Godot /// <param name="instance">The subsequence to search.</param> /// <param name="text">The string that contains the subsequence.</param> /// <returns>If the string is a subsequence of the given string.</returns> - public static bool IsSubsequenceOfI(this string instance, string text) + public static bool IsSubsequenceOfN(this string instance, string text) { return instance.IsSubsequenceOf(text, caseSensitive: false); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 1f5282e88f..fa7838633c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -512,24 +512,24 @@ namespace Godot /// Returns the result of the spherical linear interpolation between /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// - /// Note: Both vectors must be normalized. + /// This method also handles interpolating the lengths if the input vectors have different lengths. + /// For the special case of one or both input vectors having zero length, this method behaves like [method lerp]. /// </summary> - /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> public Vector2 Slerp(Vector2 to, real_t weight) { -#if DEBUG - if (!IsNormalized()) - { - throw new InvalidOperationException("Vector2.Slerp: From vector is not normalized."); + real_t startLengthSquared = LengthSquared(); + real_t endLengthSquared = to.LengthSquared(); + if (startLengthSquared == 0.0 || endLengthSquared == 0.0) { + // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. + return Lerp(to, weight); } - if (!to.IsNormalized()) - { - throw new InvalidOperationException($"Vector2.Slerp: `{nameof(to)}` is not normalized."); - } -#endif - return Rotated(AngleTo(to) * weight); + real_t startLength = Mathf.Sqrt(startLengthSquared); + real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); + real_t angle = AngleTo(to); + return Rotated(angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 433a5d9dc9..0faf13f8b7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -549,25 +549,24 @@ namespace Godot /// Returns the result of the spherical linear interpolation between /// this vector and <paramref name="to"/> by amount <paramref name="weight"/>. /// - /// Note: Both vectors must be normalized. + /// This method also handles interpolating the lengths if the input vectors have different lengths. + /// For the special case of one or both input vectors having zero length, this method behaves like [method lerp]. /// </summary> - /// <param name="to">The destination vector for interpolation. Must be normalized.</param> + /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> public Vector3 Slerp(Vector3 to, real_t weight) { -#if DEBUG - if (!IsNormalized()) - { - throw new InvalidOperationException("Vector3.Slerp: From vector is not normalized."); + real_t startLengthSquared = LengthSquared(); + real_t endLengthSquared = to.LengthSquared(); + if (startLengthSquared == 0.0 || endLengthSquared == 0.0) { + // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. + return Lerp(to, weight); } - if (!to.IsNormalized()) - { - throw new InvalidOperationException($"Vector3.Slerp: `{nameof(to)}` is not normalized."); - } -#endif - real_t theta = AngleTo(to); - return Rotated(Cross(to), theta * weight); + real_t startLength = Mathf.Sqrt(startLengthSquared); + real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); + real_t angle = AngleTo(to); + return Rotated(Cross(to).Normalized(), angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 77fe9cdd19..52d5379e8b 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -140,12 +140,12 @@ void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, cons } } -void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { +void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node); Ref<Mesh> mesh = mesh_instance->get_mesh(); if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * mesh_instance->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices); } } @@ -159,7 +159,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor n = multimesh->get_instance_count(); } for (int i = 0; i < n; i++) { - _add_mesh(mesh, p_accumulated_transform * multimesh->get_instance_transform(i), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices); } } } @@ -171,7 +171,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (!meshes.is_empty()) { Ref<Mesh> mesh = meshes[1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices); } } } @@ -186,7 +186,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (Object::cast_to<CollisionShape3D>(child)) { CollisionShape3D *col_shape = Object::cast_to<CollisionShape3D>(child); - Transform3D transform = p_accumulated_transform * static_body->get_transform() * col_shape->get_transform(); + Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * col_shape->get_transform(); Ref<Mesh> mesh; Ref<Shape3D> s = col_shape->get_shape(); @@ -270,11 +270,11 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (gridmap) { if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { Array meshes = gridmap->get_meshes(); - Transform3D xform = gridmap->get_transform(); + Transform3D xform = gridmap->get_global_transform(); for (int i = 0; i < meshes.size(); i += 2) { Ref<Mesh> mesh = meshes[i + 1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); } } } @@ -364,14 +364,9 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor } #endif - if (Object::cast_to<Node3D>(p_node)) { - Node3D *spatial = Object::cast_to<Node3D>(p_node); - p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); - } - if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); } } } @@ -616,7 +611,7 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); } - Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_transform().affine_inverse(); + Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_global_transform().affine_inverse(); for (Node *E : parse_nodes) { NavigationMesh::ParsedGeometryType geometry_type = p_nav_mesh->get_parsed_geometry_type(); uint32_t collision_mask = p_nav_mesh->get_collision_mask(); diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 1ffdd39aee..21f7a4941b 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -52,7 +52,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); - static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); + static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh( diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h index 7e56b14a24..73e3cb4fff 100644 --- a/modules/ogg/ogg_packet_sequence.h +++ b/modules/ogg/ogg_packet_sequence.h @@ -104,7 +104,7 @@ class OGGPacketSequencePlayback : public RefCounted { mutable ogg_packet *packet; - uint64_t data_version; + uint64_t data_version = 0; mutable int64_t packetno = 0; diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index dd5eef8b2b..2e0d17fb28 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -270,13 +270,14 @@ void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, OccluderInstance &instance = scenario.instances[p_instance]; + bool changed = false; + if (instance.removed) { instance.removed = false; scenario.removed_instances.erase(p_instance); + changed = true; // It was removed and re-added, we might have missed some changes } - bool changed = false; - if (instance.occluder != p_occluder) { Occluder *old_occluder = occluder_owner.get_or_null(instance.occluder); if (old_occluder) { diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index d6a96282f3..6a06619840 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -62,7 +62,6 @@ if env["builtin_harfbuzz"]: #'src/hb-gobject-structs.cc', "src/hb-icu.cc", "src/hb-map.cc", - "src/hb-ms-feature-ranges.cc", "src/hb-number.cc", "src/hb-ot-cff1-table.cc", "src/hb-ot-cff2-table.cc", diff --git a/modules/text_server_adv/script_iterator.cpp b/modules/text_server_adv/script_iterator.cpp index 9e3d9138d0..06e934c67d 100644 --- a/modules/text_server_adv/script_iterator.cpp +++ b/modules/text_server_adv/script_iterator.cpp @@ -82,7 +82,7 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length paren_stack[paren_sp].pair_index = ch; paren_stack[paren_sp].script_code = script_code; } else if (paren_sp >= 0) { - // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be poped. + // If it's a close character, find the matching open on the stack, and use that script code. Any non-matching open characters above it on the stack will be popped. UChar32 paired_ch = u_getBidiPairedBracket(ch); while (paren_sp >= 0 && paren_stack[paren_sp].pair_index != paired_ch) { paren_sp -= 1; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index e0ee809331..c7511f587e 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -303,22 +303,6 @@ _FORCE_INLINE_ bool is_connected_to_prev(char32_t p_chr, char32_t p_pchr) { return (prop != U_JT_RIGHT_JOINING) && (prop != U_JT_NON_JOINING) ? !is_ligature(p_pchr, p_chr) : false; } -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - /*************************************************************************/ String TextServerAdvanced::interface_name = "ICU / HarfBuzz / Graphite"; @@ -2901,7 +2885,7 @@ void TextServerAdvanced::font_set_global_oversampling(float p_oversampling) { List<RID> text_bufs; shaped_owner.get_owned_list(&text_bufs); for (const RID &E : text_bufs) { - invalidate(shaped_owner.get_or_null(E)); + invalidate(shaped_owner.get_or_null(E), false); } } } @@ -2936,7 +2920,7 @@ int TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int return limit; } -void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped) { +void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2951,27 +2935,32 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->glyphs_logical.clear(); p_shaped->overrun_trim_data = TrimData(); p_shaped->utf16 = Char16String(); - if (p_shaped->script_iter != nullptr) { - memdelete(p_shaped->script_iter); - p_shaped->script_iter = nullptr; - } for (int i = 0; i < p_shaped->bidi_iter.size(); i++) { ubidi_close(p_shaped->bidi_iter[i]); } p_shaped->bidi_iter.clear(); + + if (p_text) { + if (p_shaped->script_iter != nullptr) { + memdelete(p_shaped->script_iter); + p_shaped->script_iter = nullptr; + } + p_shaped->break_ops_valid = false; + p_shaped->js_ops_valid = false; + } } void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) { ShapedTextDataAdvanced *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } - for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextDataAdvanced::Span span = parent->spans[k]; + for (int i = 0; i < parent->spans.size(); i++) { + ShapedTextDataAdvanced::Span span = parent->spans[i]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -3004,7 +2993,7 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) { sd->spans.clear(); sd->objects.clear(); sd->bidi_override.clear(); - invalidate(sd); + invalidate(sd, true); } void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) { @@ -3017,7 +3006,7 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir full_copy(sd); } sd->direction = p_direction; - invalidate(sd); + invalidate(sd, false); } } @@ -3047,7 +3036,7 @@ void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const full_copy(sd); } sd->custom_punct = p_punct; - invalidate(sd); + invalidate(sd, false); } } @@ -3070,7 +3059,7 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Array for (int i = 0; i < p_override.size(); i++) { sd->bidi_override.push_back(p_override[i]); } - invalidate(sd); + invalidate(sd, false); } void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { @@ -3083,7 +3072,7 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O full_copy(sd); } sd->orientation = p_orientation; - invalidate(sd); + invalidate(sd, false); } } @@ -3095,7 +3084,7 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e ERR_FAIL_COND(sd->parent != RID()); if (sd->preserve_invalid != p_enabled) { sd->preserve_invalid = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3117,7 +3106,7 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e full_copy(sd); } sd->preserve_control = p_enabled; - invalidate(sd); + invalidate(sd, false); } } @@ -3137,7 +3126,41 @@ TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_sh return sd->orientation; } -bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { +int TextServerAdvanced::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerAdvanced::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerAdvanced::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataAdvanced::Span &span = sd->spans.write[p_index]; + bool changed = (span.font_size != p_size) || (span.features != p_opentype_features) || (p_fonts.size() != span.fonts.size()); + if (!changed) { + for (int i = 0; i < p_fonts.size(); i++) { + changed = changed || (span.fonts[i] != p_fonts[i]); + } + } + if (changed) { + span.fonts = p_fonts; + span.font_size = p_size; + span.features = p_opentype_features; + + invalidate(sd, false); + } +} + +bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); ERR_FAIL_COND_V(p_size <= 0, false); @@ -3162,11 +3185,12 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te span.font_size = p_size; span.language = p_language; span.features = p_opentype_features; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; sd->end += p_text.length(); - invalidate(sd); + invalidate(sd, true); return true; } @@ -3196,13 +3220,13 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con sd->text += String::chr(0xfffc).repeat(p_length); sd->end += p_length; sd->objects[p_key] = obj; - invalidate(sd); + invalidate(sd, true); return true; } bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3222,7 +3246,7 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -3262,75 +3286,78 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { @@ -3423,7 +3450,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -3466,72 +3493,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S } } - // Align embedded objects to baseline. - float full_ascent = p_new_sd->ascent; - float full_descent = p_new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_new_sd->objects) { - if ((E.value.pos >= p_new_sd->start) && (E.value.pos < p_new_sd->end)) { - if (p_sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -p_new_sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-p_new_sd->ascent + p_new_sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = p_new_sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - } - p_new_sd->ascent = full_ascent; - p_new_sd->descent = full_descent; + _realign(p_new_sd); } p_new_sd->valid = true; @@ -3968,40 +3930,43 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { const UChar *data = sd->utf16.ptr(); - HashMap<int, bool> breaks; - UErrorCode err = U_ZERO_ERROR; - int i = 0; - while (i < sd->spans.size()) { - String language = sd->spans[i].language; - int r_start = sd->spans[i].start; - while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { - i++; - } - int r_end = sd->spans[i].end; - UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); - if (U_FAILURE(err)) { - // No data loaded - use fallback. - for (int j = r_start; j < r_end; j++) { - char32_t c = sd->text[j - sd->start]; - if (is_whitespace(c)) { - breaks[j + 1] = false; - } - if (is_linebreak(c)) { - breaks[j + 1] = true; - } + if (!sd->break_ops_valid) { + sd->breaks.clear(); + UErrorCode err = U_ZERO_ERROR; + int i = 0; + while (i < sd->spans.size()) { + String language = sd->spans[i].language; + int r_start = sd->spans[i].start; + while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { + i++; } - } else { - while (ubrk_next(bi) != UBRK_DONE) { - int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; - if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { - breaks[pos] = true; - } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { - breaks[pos] = false; + int r_end = sd->spans[i].end; + UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); + if (U_FAILURE(err)) { + // No data loaded - use fallback. + for (int j = r_start; j < r_end; j++) { + char32_t c = sd->text[j - sd->start]; + if (is_whitespace(c)) { + sd->breaks[j + 1] = false; + } + if (is_linebreak(c)) { + sd->breaks[j + 1] = true; + } + } + } else { + while (ubrk_next(bi) != UBRK_DONE) { + int pos = _convert_pos(sd, ubrk_current(bi)) + r_start; + if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) { + sd->breaks[pos] = true; + } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { + sd->breaks[pos] = false; + } } } + ubrk_close(bi); + i++; } - ubrk_close(bi); - i++; + sd->break_ops_valid = true; } sd->sort_valid = false; @@ -4013,7 +3978,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { int c_punct_size = sd->custom_punct.length(); const char32_t *c_punct = sd->custom_punct.ptr(); - for (i = 0; i < sd_size; i++) { + for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = ch[sd_glyphs[i].start - sd->start]; if (c == 0xfffc) { @@ -4040,8 +4005,8 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { if (is_underscore(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE; } - if (breaks.has(sd_glyphs[i].end)) { - if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) { + if (sd->breaks.has(sd_glyphs[i].end)) { + if (sd->breaks[sd_glyphs[i].end] && (is_linebreak(c))) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD; } else if (is_whitespace(c)) { sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT; @@ -4186,41 +4151,45 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { const UChar *data = sd->utf16.ptr(); int32_t data_size = sd->utf16.length(); - Map<int, bool> jstops; + if (!sd->js_ops_valid) { + sd->jstops.clear(); - // Use ICU word iterator and custom kashida detection. - UErrorCode err = U_ZERO_ERROR; - UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); - if (U_FAILURE(err)) { - // No data - use fallback. - int limit = 0; - for (int i = 0; i < sd->text.length(); i++) { - if (is_whitespace(data[i])) { - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - limit = i + 1; + // Use ICU word iterator and custom kashida detection. + UErrorCode err = U_ZERO_ERROR; + UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); + if (U_FAILURE(err)) { + // No data - use fallback. + int limit = 0; + for (int i = 0; i < sd->text.length(); i++) { + if (is_whitespace(data[i])) { + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; + } + limit = i + 1; + } } - } - int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; - if (ks != -1) { - jstops[ks] = true; - } - } else { - int limit = 0; - while (ubrk_next(bi) != UBRK_DONE) { - if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { - int i = _convert_pos(sd, ubrk_current(bi)); - jstops[i + sd->start] = false; - int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); - if (ks != -1) { - jstops[ks + sd->start] = true; - } - limit = i; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start; + if (ks != -1) { + sd->jstops[ks] = true; + } + } else { + int limit = 0; + while (ubrk_next(bi) != UBRK_DONE) { + if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) { + int i = _convert_pos(sd, ubrk_current(bi)); + sd->jstops[i + sd->start] = false; + int ks = _generate_kashida_justification_opportunies(sd->text, limit, i); + if (ks != -1) { + sd->jstops[ks + sd->start] = true; + } + limit = i; + } } + ubrk_close(bi); } - ubrk_close(bi); + + sd->js_ops_valid = true; } sd->sort_valid = false; @@ -4228,18 +4197,18 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { Glyph *sd_glyphs = sd->glyphs.ptrw(); int sd_size = sd->glyphs.size(); - if (jstops.size() > 0) { + if (sd->jstops.size() > 0) { for (int i = 0; i < sd_size; i++) { if (sd_glyphs[i].count > 0) { char32_t c = sd->text[sd_glyphs[i].start - sd->start]; if (c == 0x0640) { sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION; } - if (jstops.has(sd_glyphs[i].start)) { + if (sd->jstops.has(sd_glyphs[i].start)) { if (c == 0xfffc) { continue; } - if (jstops[sd_glyphs[i].start]) { + if (sd->jstops[sd_glyphs[i].start]) { if (c != 0x0640) { if (sd_glyphs[i].font_rid != RID()) { Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size); @@ -4574,7 +4543,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { return true; } - invalidate(sd); + invalidate(sd, false); if (sd->parent != RID()) { shaped_text_shape(sd->parent); ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); @@ -4733,70 +4702,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) { } } - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); - } - } - sd->ascent = full_ascent; - sd->descent = full_descent; + _realign(sd); sd->valid = true; return sd->valid; } @@ -4863,7 +4769,7 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const { ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index f2ae24cae6..145d740b68 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -242,6 +242,21 @@ class TextServerAdvanced : public TextServer { // Shaped text cache data. struct ShapedTextDataAdvanced : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + /* Intermediate data */ Char16String utf16; Vector<UBiDi *> bidi_iter; @@ -249,6 +264,11 @@ class TextServerAdvanced : public TextServer { ScriptIterator *script_iter = nullptr; hb_buffer_t *hb_buffer = nullptr; + HashMap<int, bool> jstops; + HashMap<int, bool> breaks; + bool break_ops_valid = false; + bool js_ops_valid = false; + ~ShapedTextDataAdvanced() { for (int i = 0; i < bidi_iter.size(); i++) { ubidi_close(bidi_iter[i]); @@ -268,6 +288,7 @@ class TextServerAdvanced : public TextServer { mutable RID_PtrOwner<FontDataAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + void _realign(ShapedTextDataAdvanced *p_sd) const; int _convert_pos(const ShapedTextDataAdvanced *p_sd, int p_pos) const; int _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int p_pos) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int p_start, int p_length) const; @@ -302,7 +323,7 @@ protected: static void _bind_methods(){}; void full_copy(ShapedTextDataAdvanced *p_shaped); - void invalidate(ShapedTextDataAdvanced *p_shaped); + void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false); public: virtual bool has_feature(Feature p_feature) const override; @@ -482,10 +503,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index ffbd2da22d..182d2a02ad 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -44,30 +44,6 @@ #endif /*************************************************************************/ -/* Character properties. */ -/*************************************************************************/ - -_FORCE_INLINE_ bool is_control(char32_t p_char) { - return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F); -} - -_FORCE_INLINE_ bool is_whitespace(char32_t p_char) { - return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085); -} - -_FORCE_INLINE_ bool is_linebreak(char32_t p_char) { - return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029); -} - -_FORCE_INLINE_ bool is_punct(char32_t p_char) { - return (p_char >= 0x0020 && p_char <= 0x002F) || (p_char >= 0x003A && p_char <= 0x0040) || (p_char >= 0x005B && p_char <= 0x005E) || (p_char == 0x0060) || (p_char >= 0x007B && p_char <= 0x007E) || (p_char >= 0x2000 && p_char <= 0x206F) || (p_char >= 0x3000 && p_char <= 0x303F); -} - -_FORCE_INLINE_ bool is_underscore(char32_t p_char) { - return (p_char == 0x005F); -} - -/*************************************************************************/ String TextServerFallback::interface_name = "Fallback"; uint32_t TextServerFallback::interface_features = 0; // Nothing is supported. @@ -91,7 +67,7 @@ void TextServerFallback::free(RID p_rid) { font_owner.free(p_rid); memdelete(fd); } else if (shaped_owner.owns(p_rid)) { - ShapedTextData *sd = shaped_owner.get_or_null(p_rid); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid); shaped_owner.free(p_rid); memdelete(sd); } @@ -127,8 +103,9 @@ _FORCE_INLINE_ int32_t ot_tag_from_string(const char *p_str, int p_len) { char tag[4]; uint32_t i; - if (!p_str || !p_len || !*p_str) + if (!p_str || !p_len || !*p_str) { return OT_TAG(0, 0, 0, 0); + } if (p_len < 0 || p_len > 4) { p_len = 4; @@ -2060,7 +2037,7 @@ void TextServerFallback::font_set_global_oversampling(float p_oversampling) { /* Shaped text buffer interface */ /*************************************************************************/ -void TextServerFallback::invalidate(ShapedTextData *p_shaped) { +void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) { p_shaped->valid = false; p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; @@ -2074,17 +2051,17 @@ void TextServerFallback::invalidate(ShapedTextData *p_shaped) { p_shaped->glyphs_logical.clear(); } -void TextServerFallback::full_copy(ShapedTextData *p_shaped) { - ShapedTextData *parent = shaped_owner.get_or_null(p_shaped->parent); +void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) { + ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) { if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) { p_shaped->objects[E.key] = E.value; } } for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextData::Span span = parent->spans[k]; + ShapedTextDataFallback::Span span = parent->spans[k]; if (span.start >= p_shaped->end || span.end <= p_shaped->start) { continue; } @@ -2098,7 +2075,7 @@ void TextServerFallback::full_copy(ShapedTextData *p_shaped) { RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = memnew(ShapedTextData); + ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback); sd->direction = p_direction; sd->orientation = p_orientation; @@ -2106,7 +2083,7 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te } void TextServerFallback::shaped_text_clear(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2135,7 +2112,7 @@ TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); if (sd->custom_punct != p_punct) { @@ -2149,13 +2126,13 @@ void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String TextServerFallback::shaped_text_get_custom_punctuation(RID p_shaped) const { _THREAD_SAFE_METHOD_ - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, String()); return sd->custom_punct; } void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2173,7 +2150,7 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Array } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); @@ -2181,7 +2158,7 @@ TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_sh } void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); MutexLock lock(sd->mutex); ERR_FAIL_COND(!sd); @@ -2195,7 +2172,7 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2203,7 +2180,7 @@ bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const { } void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -2217,15 +2194,52 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e } bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; } -bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); +int TextServerFallback::shaped_get_span_count(RID p_shaped) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, 0); + return sd->spans.size(); +} + +Variant TextServerFallback::shaped_get_span_meta(RID p_shaped, int p_index) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; +} + +void TextServerFallback::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND(!sd); + ERR_FAIL_INDEX(p_index, sd->spans.size()); + + ShapedTextDataFallback::Span &span = sd->spans.write[p_index]; + span.fonts.clear(); + // Pre-sort fonts, push fonts with the language support first. + Vector<RID> fonts_no_match; + int font_count = p_fonts.size(); + for (int i = 0; i < font_count; i++) { + if (font_is_language_supported(p_fonts[i], span.language)) { + span.fonts.push_back(p_fonts[i]); + } else { + fonts_no_match.push_back(p_fonts[i]); + } + } + span.fonts.append_array(fonts_no_match); + span.font_size = p_size; + span.features = p_opentype_features; + + sd->valid = false; +} + +bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2243,7 +2257,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->text.length(); span.end = span.start + p_text.length(); @@ -2262,6 +2276,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te ERR_FAIL_COND_V(span.fonts.is_empty(), false); span.font_size = p_size; span.language = p_language; + span.meta = p_meta; sd->spans.push_back(span); sd->text += p_text; @@ -2272,7 +2287,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te } bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2283,12 +2298,12 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con full_copy(sd); } - ShapedTextData::Span span; + ShapedTextDataFallback::Span span; span.start = sd->start + sd->text.length(); span.end = span.start + p_length; span.embedded_key = p_key; - ShapedTextData::EmbeddedObject obj; + ShapedTextDataFallback::EmbeddedObject obj; obj.inline_align = p_inline_align; obj.rect.size = p_size; obj.pos = span.start; @@ -2303,7 +2318,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con } bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2323,7 +2338,7 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, Glyph gl = sd->glyphs[i]; Variant key; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { key = E.key; break; @@ -2363,79 +2378,82 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, sd->width += gl.advance * gl.repeat; } } + _realign(sd); + } + return true; +} - // Align embedded objects to baseline. - float full_ascent = sd->ascent; - float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { - if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) { - if (sd->orientation == ORIENTATION_HORIZONTAL) { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.y = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.y = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.y = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.y = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.y -= E.value.rect.size.y; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.y -= E.value.rect.size.y / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.y); - full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); - } else { - switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { - case INLINE_ALIGNMENT_TO_TOP: { - E.value.rect.position.x = -sd->ascent; - } break; - case INLINE_ALIGNMENT_TO_CENTER: { - E.value.rect.position.x = (-sd->ascent + sd->descent) / 2; - } break; - case INLINE_ALIGNMENT_TO_BASELINE: { - E.value.rect.position.x = 0; - } break; - case INLINE_ALIGNMENT_TO_BOTTOM: { - E.value.rect.position.x = sd->descent; - } break; - } - switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { - case INLINE_ALIGNMENT_BOTTOM_TO: { - E.value.rect.position.x -= E.value.rect.size.x; - } break; - case INLINE_ALIGNMENT_CENTER_TO: { - E.value.rect.position.x -= E.value.rect.size.x / 2; - } break; - case INLINE_ALIGNMENT_TOP_TO: { - // NOP - } break; - } - full_ascent = MAX(full_ascent, -E.value.rect.position.x); - full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); +void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const { + // Align embedded objects to baseline. + float full_ascent = p_sd->ascent; + float full_descent = p_sd->descent; + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) { + if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) { + if (p_sd->orientation == ORIENTATION_HORIZONTAL) { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.y = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.y = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.y = p_sd->descent; + } break; } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.y -= E.value.rect.size.y; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.y -= E.value.rect.size.y / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.y); + full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y); + } else { + switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { + case INLINE_ALIGNMENT_TO_TOP: { + E.value.rect.position.x = -p_sd->ascent; + } break; + case INLINE_ALIGNMENT_TO_CENTER: { + E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2; + } break; + case INLINE_ALIGNMENT_TO_BASELINE: { + E.value.rect.position.x = 0; + } break; + case INLINE_ALIGNMENT_TO_BOTTOM: { + E.value.rect.position.x = p_sd->descent; + } break; + } + switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) { + case INLINE_ALIGNMENT_BOTTOM_TO: { + E.value.rect.position.x -= E.value.rect.size.x; + } break; + case INLINE_ALIGNMENT_CENTER_TO: { + E.value.rect.position.x -= E.value.rect.size.x / 2; + } break; + case INLINE_ALIGNMENT_TOP_TO: { + // NOP + } break; + } + full_ascent = MAX(full_ascent, -E.value.rect.position.x); + full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x); } } - sd->ascent = full_ascent; - sd->descent = full_descent; } - return true; + p_sd->ascent = full_ascent; + p_sd->descent = full_descent; } RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2449,7 +2467,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID()); ERR_FAIL_COND_V(sd->end < p_start + p_length, RID()); - ShapedTextData *new_sd = memnew(ShapedTextData); + ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback); new_sd->parent = p_shaped; new_sd->start = p_start; new_sd->end = p_start + p_length; @@ -2475,7 +2493,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng Variant key; bool find_embedded = false; if (gl.count == 1) { - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (E.value.pos == gl.start) { find_embedded = true; key = E.key; @@ -2519,7 +2537,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng // Align embedded objects to baseline. float full_ascent = new_sd->ascent; float full_descent = new_sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : new_sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : new_sd->objects) { if ((E.value.pos >= new_sd->start) && (E.value.pos < new_sd->end)) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { @@ -2589,7 +2607,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng } RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, RID()); MutexLock lock(sd->mutex); @@ -2597,7 +2615,7 @@ RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const { } float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint16_t /*JustificationFlag*/ p_jst_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2706,7 +2724,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, } float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -2762,7 +2780,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat3 } bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2818,7 +2836,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) { } bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -2834,7 +2852,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) { } void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint16_t p_trim_flags) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped_line); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); @@ -2862,9 +2880,9 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl return; } - Vector<ShapedTextData::Span> &spans = sd->spans; + Vector<ShapedTextDataFallback::Span> &spans = sd->spans; if (sd->parent != RID()) { - ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent); + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); ERR_FAIL_COND(!parent_sd->valid); spans = parent_sd->spans; } @@ -2986,39 +3004,39 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } int TextServerFallback::shaped_text_get_trim_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; } int TextServerFallback::shaped_text_get_ellipsis_pos(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; } const Glyph *TextServerFallback::shaped_text_get_ellipsis_glyphs(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); } int TextServerFallback::shaped_text_get_ellipsis_glyph_count(RID p_shaped) const { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextData invalid."); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); } bool TextServerFallback::shaped_text_shape(RID p_shaped) { - ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3045,7 +3063,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // "Shape" string. for (int i = 0; i < sd->spans.size(); i++) { - const ShapedTextData::Span &span = sd->spans[i]; + const ShapedTextDataFallback::Span &span = sd->spans[i]; if (span.embedded_key != Variant()) { // Embedded object. if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -3145,7 +3163,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { // Align embedded objects to baseline. float full_ascent = sd->ascent; float full_descent = sd->descent; - for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { if (sd->orientation == ORIENTATION_HORIZONTAL) { switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) { case INLINE_ALIGNMENT_TO_TOP: { @@ -3211,7 +3229,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) { } bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, false); MutexLock lock(sd->mutex); @@ -3219,7 +3237,7 @@ bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3230,7 +3248,7 @@ const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const { } int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0); MutexLock lock(sd->mutex); @@ -3241,7 +3259,7 @@ int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const { } const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, nullptr); MutexLock lock(sd->mutex); @@ -3253,7 +3271,7 @@ const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) { } Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Vector2i()); MutexLock lock(sd->mutex); @@ -3262,11 +3280,11 @@ Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const { Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { Array ret; - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, ret); MutexLock lock(sd->mutex); - for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) { + for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { ret.push_back(E.key); } @@ -3274,7 +3292,7 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const { } Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Rect2()); MutexLock lock(sd->mutex); @@ -3286,7 +3304,7 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke } Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, Size2()); MutexLock lock(sd->mutex); @@ -3301,7 +3319,7 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const { } float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3312,7 +3330,7 @@ float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3323,7 +3341,7 @@ float TextServerFallback::shaped_text_get_descent(RID p_shaped) const { } float TextServerFallback::shaped_text_get_width(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3334,7 +3352,7 @@ float TextServerFallback::shaped_text_get_width(RID p_shaped) const { } float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); @@ -3346,7 +3364,7 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const } float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const { - const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped); + const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_V(!sd, 0.f); MutexLock lock(sd->mutex); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index b356b90d2c..be944cde58 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -203,17 +203,38 @@ class TextServerFallback : public TextServer { } } + // Shaped text cache data. + + struct ShapedTextDataFallback : public ShapedTextData { + struct Span { + int start = -1; + int end = -1; + + Vector<RID> fonts; + int font_size = 0; + + Variant embedded_key; + + String language; + Dictionary features; + Variant meta; + }; + Vector<Span> spans; + }; + // Common data. float oversampling = 1.f; mutable RID_PtrOwner<FontDataFallback> font_owner; - mutable RID_PtrOwner<ShapedTextData> shaped_owner; + mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + + void _realign(ShapedTextDataFallback *p_sd) const; protected: static void _bind_methods(){}; - void full_copy(ShapedTextData *p_shaped); - void invalidate(ShapedTextData *p_shaped); + void full_copy(ShapedTextDataFallback *p_shaped); + void invalidate(ShapedTextDataFallback *p_shaped); public: virtual bool has_feature(Feature p_feature) const override; @@ -391,10 +412,14 @@ public: virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override; virtual bool shaped_text_get_preserve_control(RID p_shaped) const override; - virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override; + virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override; virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override; virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override; + virtual int shaped_get_span_count(RID p_shaped) const override; + virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override; + virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override; + virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override; virtual RID shaped_text_get_parent(RID p_shaped) const override; diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp index 9d297fcb93..a9652cbba1 100644 --- a/modules/theora/video_stream_theora.cpp +++ b/modules/theora/video_stream_theora.cpp @@ -337,6 +337,7 @@ void VideoStreamPlaybackTheora::set_file(const String &p_file) { Ref<Image> img; img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8); + texture->create_from_image(img); } else { /* tear down the partial theora setup */ diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp index 6d515d11f5..5ea8eaff00 100644 --- a/modules/visual_script/editor/visual_script_editor.cpp +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -42,6 +42,7 @@ #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" +#include "scene/gui/view_panner.h" #include "scene/main/window.h" #ifdef TOOLS_ENABLED @@ -271,7 +272,7 @@ protected: if (String(p_name) == "export") { script->set_variable_export(var, p_value); - EditorNode::get_singleton()->get_inspector()->update_tree(); + InspectorDock::get_inspector_singleton()->update_tree(); return true; } @@ -652,7 +653,6 @@ void VisualScriptEditor::_update_graph(int p_only_id) { List<int> ids; script->get_node_list(&ids); - StringName editor_icons = "EditorIcons"; for (int &E : ids) { if (p_only_id >= 0 && p_only_id != E) { @@ -1366,7 +1366,7 @@ void VisualScriptEditor::_create_function() { } void VisualScriptEditor::_add_node_dialog() { - _generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true); + _generic_search(graph->get_global_position() + Vector2(55, 80), true); } void VisualScriptEditor::_add_func_input() { @@ -1442,7 +1442,7 @@ void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_butt if (p_button == 1) { // Ensure script base exists otherwise use custom base type. ERR_FAIL_COND(script.is_null()); - new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true); + new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), true); return; } else if (p_button == 0) { String name = _validate_name("new_function"); @@ -1948,14 +1948,14 @@ void VisualScriptEditor::_on_nodes_duplicate() { } } -void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) { +void VisualScriptEditor::_generic_search(Vector2 pos, bool node_centered) { if (node_centered) { port_action_pos = graph->get_size() / 2.0f; } else { port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); } - new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text + new_connect_node_select->select_from_visual_script(script, false); // do not reset text // Ensure that the dialog fits inside the graph. Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); @@ -1992,7 +1992,7 @@ void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } } if (is_empty_selection && clipboard->nodes.is_empty()) { - _generic_search(script->get_instance_base_type(), mouse_up_position); + _generic_search(); } else { popup_menu->set_item_disabled(int(EDIT_CUT_NODES), is_empty_selection); popup_menu->set_item_disabled(int(EDIT_COPY_NODES), is_empty_selection); @@ -2446,7 +2446,7 @@ void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da drop_position = pos; drop_node = node; drop_path = sn->get_path_to(node); - new_connect_node_select->select_from_instance(node, "", false, node->get_class()); + new_connect_node_select->select_from_instance(node, false); } undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); @@ -2802,7 +2802,7 @@ void VisualScriptEditor::add_callback(const String &p_function, PackedStringArra String name = p_args[i]; Variant::Type type = Variant::NIL; - if (name.find(":") != -1) { + if (name.contains(":")) { String tt = name.get_slice(":", 1); name = name.get_slice(":", 0); for (int j = 0; j < Variant::VARIANT_MAX; j++) { @@ -3234,19 +3234,34 @@ void VisualScriptEditor::_port_action_menu(int p_option) { n->set_base_type("Object"); } String type_string; + String base_script = ""; if (script->get_node(port_action_node)->get_output_value_port_count() > 0) { type_string = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + VisualScriptFunctionCall *vsfc = Object::cast_to<VisualScriptFunctionCall>(*script->get_node(port_action_node)); + if (vsfc) { + base_script = vsfc->get_base_script(); + } else { + VisualScriptPropertyGet *vspg = Object::cast_to<VisualScriptPropertyGet>(*script->get_node(port_action_node)); + if (vspg) { + base_script = vspg->get_base_script(); + } else { + VisualScriptPropertySet *vsps = Object::cast_to<VisualScriptPropertySet>(*script->get_node(port_action_node)); + if (vsps) { + base_script = vsps->get_base_script(); + } + } + } } if (tg.type == Variant::OBJECT) { if (tg.script.is_valid()) { - new_connect_node_select->select_from_script(tg.script, ""); - } else if (!type_string.is_empty()) { - new_connect_node_select->select_from_base_type(type_string); + new_connect_node_select->select_from_script(tg.script); + } else if (type_string != String()) { + new_connect_node_select->select_from_base_type(type_string, base_script); } else { - new_connect_node_select->select_from_base_type(n->get_base_type()); + new_connect_node_select->select_from_base_type(n->get_base_type(), base_script); } } else if (tg.type == Variant::NIL) { - new_connect_node_select->select_from_base_type(""); + new_connect_node_select->select_from_base_type("", base_script); } else { new_connect_node_select->select_from_basic_type(tg.type); } @@ -3309,66 +3324,54 @@ void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<Visua } void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { +#ifdef OSX_ENABLED + bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool held_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif Vector2 pos = _get_pos_in_graph(port_action_pos); Set<int> vn; + bool port_node_exists = true; if (drop_position != Vector2()) { pos = drop_position; } drop_position = Vector2(); - bool port_node_exists = true; - - // if (func == StringName()) { - // func = default_func; - // port_node_exists = false; - // } - - if (p_category == "visualscript") { - Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text); - Ref<VisualScriptNode> vnode_old; - if (port_node_exists && p_connecting) { - vnode_old = script->get_node(port_action_node); - } - int new_id = script->get_available_id(); + Ref<VisualScriptNode> vnode; + Ref<VisualScriptNode> vnode_old; + if (port_node_exists && p_connecting) { + vnode_old = script->get_node(port_action_node); + } - if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && vnode_old.is_valid()) { - Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; - Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type); - } + if (p_category.begins_with("VisualScriptNode")) { + Ref<VisualScriptNode> n = VisualScriptLanguage::singleton->create_node_from_name(p_text); - if (Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr()) && vnode_old.is_valid()) { + if (Object::cast_to<VisualScriptTypeCast>(n.ptr()) && vnode_old.is_valid()) { Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; String hint_name = vnode_old->get_output_value_port_info(port_action_output).hint_string; if (type == Variant::OBJECT) { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(hint_name); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(hint_name); } else if (type == Variant::NIL) { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(""); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(""); } else { - Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type)); + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(Variant::get_type_name(type)); } } - - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode_new, pos); - if (vnode_old.is_valid() && p_connecting) { - connect_seq(vnode_old, vnode_new, new_id); - connect_data(vnode_old, vnode_new, new_id); - } - - undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); - undo_redo->add_do_method(this, "_update_graph"); - undo_redo->add_undo_method(this, "_update_graph"); - undo_redo->commit_action(); - return; + vnode = n; } - Ref<VisualScriptNode> vnode; - Ref<VisualScriptPropertySet> script_prop_set; - - if (p_category == String("method")) { + if (p_category == String("Class") && !p_connecting) { + Ref<VisualScriptFunctionCall> n; + n.instantiate(); + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SINGLETON); + n->set_singleton("ClassDB"); + n->set_function("instantiate"); + // Did not find a way to edit the input port value + vnode = n; + } else if (p_category == String("class_method")) { Ref<VisualScriptFunctionCall> n; n.instantiate(); if (!drop_path.is_empty()) { @@ -3386,96 +3389,151 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } vnode = n; - } else if (p_category == String("set")) { - Ref<VisualScriptPropertySet> n; - n.instantiate(); - if (!drop_path.is_empty()) { - if (drop_path == ".") { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); - } else { - n->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); - n->set_base_path(drop_path); + } else if (p_category == String("class_property")) { + Vector<String> property_path = p_text.split(":"); + if (held_ctrl) { + Ref<VisualScriptPropertySet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } } - } - if (drop_node) { - n->set_base_type(drop_node->get_class()); - if (drop_node->get_script_instance()) { - n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } } - } - vnode = n; - script_prop_set = n; - } else if (p_category == String("get")) { - Ref<VisualScriptPropertyGet> n; - n.instantiate(); - n->set_property(p_text); - if (!drop_path.is_empty()) { - if (drop_path == ".") { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); - } else { - n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); - n->set_base_path(drop_path); + vnode = n; + } else { + Ref<VisualScriptPropertyGet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } } - } - if (drop_node) { - n->set_base_type(drop_node->get_class()); - if (drop_node->get_script_instance()) { - n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } } - } - vnode = n; - } - drop_path = String(); - drop_node = nullptr; - - if (p_category == String("action")) { - if (p_text == "VisualScriptCondition") { - Ref<VisualScriptCondition> n; - n.instantiate(); vnode = n; } - if (p_text == "VisualScriptSwitch") { - Ref<VisualScriptSwitch> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptSequence") { - Ref<VisualScriptSequence> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptIterator") { - Ref<VisualScriptIterator> n; + } else if (p_category == String("class_constant")) { + Vector<String> property_path = p_text.split(":"); + if (ClassDB::class_exists(property_path[0])) { + Ref<VisualScriptClassConstant> n; n.instantiate(); + n->set_base_type(property_path[0]); + n->set_class_constant(property_path[1]); vnode = n; - } else if (p_text == "VisualScriptWhile") { - Ref<VisualScriptWhile> n; - n.instantiate(); - vnode = n; - } else if (p_text == "VisualScriptReturn") { - Ref<VisualScriptReturn> n; + } else { + Ref<VisualScriptBasicTypeConstant> n; n.instantiate(); + if (property_path[0] == "Nil") { + n->set_basic_type(Variant::NIL); + } else if (property_path[0] == "bool") { + n->set_basic_type(Variant::BOOL); + } else if (property_path[0] == "int") { + n->set_basic_type(Variant::INT); + } else if (property_path[0] == "float") { + n->set_basic_type(Variant::FLOAT); + } else if (property_path[0] == "String") { + n->set_basic_type(Variant::STRING); + } else if (property_path[0] == "Vector2") { + n->set_basic_type(Variant::VECTOR2); + } else if (property_path[0] == "Vector2i") { + n->set_basic_type(Variant::VECTOR2I); + } else if (property_path[0] == "Rect2") { + n->set_basic_type(Variant::RECT2); + } else if (property_path[0] == "Rect2i") { + n->set_basic_type(Variant::RECT2I); + } else if (property_path[0] == "Transform2D") { + n->set_basic_type(Variant::TRANSFORM2D); + } else if (property_path[0] == "Vector3") { + n->set_basic_type(Variant::VECTOR3); + } else if (property_path[0] == "Vector3i") { + n->set_basic_type(Variant::VECTOR3I); + } else if (property_path[0] == "Plane") { + n->set_basic_type(Variant::PLANE); + } else if (property_path[0] == "ABB") { + n->set_basic_type(Variant::AABB); + } else if (property_path[0] == "Quaternion") { + n->set_basic_type(Variant::QUATERNION); + } else if (property_path[0] == "Basis") { + n->set_basic_type(Variant::BASIS); + } else if (property_path[0] == "Transform3D") { + n->set_basic_type(Variant::TRANSFORM3D); + } else if (property_path[0] == "Color") { + n->set_basic_type(Variant::COLOR); + } else if (property_path[0] == "RID") { + n->set_basic_type(Variant::RID); + } else if (property_path[0] == "Object") { + n->set_basic_type(Variant::OBJECT); + } else if (property_path[0] == "Callable") { + n->set_basic_type(Variant::CALLABLE); + } else if (property_path[0] == "Signal") { + n->set_basic_type(Variant::SIGNAL); + } else if (property_path[0] == "StringName") { + n->set_basic_type(Variant::STRING_NAME); + } else if (property_path[0] == "NodePath") { + n->set_basic_type(Variant::NODE_PATH); + } else if (property_path[0] == "Dictionary") { + n->set_basic_type(Variant::DICTIONARY); + } else if (property_path[0] == "Array") { + n->set_basic_type(Variant::ARRAY); + } else if (property_path[0] == "PackedByteArray") { + n->set_basic_type(Variant::PACKED_BYTE_ARRAY); + } else if (property_path[0] == "PackedInt32Array") { + n->set_basic_type(Variant::PACKED_INT32_ARRAY); + } else if (property_path[0] == "PackedInt64Array") { + n->set_basic_type(Variant::PACKED_INT64_ARRAY); + } else if (property_path[0] == "PackedFloat32Array") { + n->set_basic_type(Variant::PACKED_FLOAT32_ARRAY); + } else if (property_path[0] == "PackedStringArray") { + n->set_basic_type(Variant::PACKED_STRING_ARRAY); + } else if (property_path[0] == "PackedVector2Array") { + n->set_basic_type(Variant::PACKED_VECTOR2_ARRAY); + } else if (property_path[0] == "PackedVector3Array") { + n->set_basic_type(Variant::PACKED_VECTOR3_ARRAY); + } else if (property_path[0] == "PackedColorArray") { + n->set_basic_type(Variant::PACKED_COLOR_ARRAY); + } + n->set_basic_type_constant(property_path[1]); vnode = n; } - } - int new_id = script->get_available_id(); - undo_redo->create_action(TTR("Add Node")); - undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); - undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); - undo_redo->add_do_method(this, "_update_graph", new_id); - undo_redo->add_undo_method(this, "_update_graph", new_id); - undo_redo->commit_action(); + } else if (p_category == String("class_signal")) { + Vector<String> property_path = p_text.split(":"); + ERR_FAIL_COND(!(script->has_custom_signal(property_path[1]) || ClassDB::has_signal(script->get_instance_base_type(), property_path[1]))); - if (script_prop_set.is_valid()) { - script_prop_set->set_property(p_text); + Ref<VisualScriptEmitSignal> n; + n.instantiate(); + n->set_signal(property_path[1]); + vnode = n; + } + if (vnode == nullptr) { + print_error("Category not handled: " + p_category.quote()); } - port_action_new_node = new_id; - - Ref<VisualScriptNode> vsn = script->get_node(port_action_new_node); + if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr()) && p_category != "Class") { + Vector<String> property_path = p_text.split(":"); + String class_of_method = property_path[0]; + String method_name = property_path[1]; - if (Object::cast_to<VisualScriptFunctionCall>(vsn.ptr())) { - Ref<VisualScriptFunctionCall> vsfc = vsn; - vsfc->set_function(p_text); + Ref<VisualScriptFunctionCall> vsfc = vnode; + vsfc->set_function(method_name); if (port_node_exists && p_connecting) { VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); @@ -3492,7 +3550,7 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri if (!base_type.is_empty() && hint == PROPERTY_HINT_TYPE_STRING) { vsfc->set_base_type(base_type); } - if (p_text == "call" || p_text == "call_deferred") { + if (method_name == "call" || method_name == "call_deferred") { vsfc->set_function(String("")); } } @@ -3510,8 +3568,8 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } if (port_node_exists && p_connecting) { - if (Object::cast_to<VisualScriptPropertySet>(vsn.ptr())) { - Ref<VisualScriptPropertySet> vsp = vsn; + if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Ref<VisualScriptPropertySet> vsp = vnode; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); if (tg.type == Variant::OBJECT) { @@ -3540,8 +3598,8 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } - if (Object::cast_to<VisualScriptPropertyGet>(vsn.ptr())) { - Ref<VisualScriptPropertyGet> vsp = vsn; + if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Ref<VisualScriptPropertyGet> vsp = vnode; VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); if (tg.type == Variant::OBJECT) { @@ -3569,13 +3627,85 @@ void VisualScriptEditor::_selected_connect_node(const String &p_text, const Stri } } } + if (vnode == nullptr) { + print_error("Not able to create node from category: \"" + p_category + "\" and text \"" + p_text + "\" Not created"); + return; + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); + + port_action_new_node = new_id; + + String base_script = ""; + String base_type = ""; if (port_node_exists) { - Ref<VisualScriptNode> vnode_old = script->get_node(port_action_node); + if (vnode_old.is_valid()) { + if (Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_script(); + } + } + + Vector<String> property_path = p_text.split(":"); + if (ClassDB::is_parent_class(script->get_instance_base_type(), property_path[0]) || script->get_path().ends_with(property_path[0].unquote())) { + if (!p_connecting) { + base_type = script->get_instance_base_type(); + base_script = script->get_path(); + } + } else { + base_type = property_path[0]; + base_script = ""; + } + + if (drop_node) { + Ref<Script> script = drop_node->get_script(); + if (script != nullptr) { + base_script = script->get_path(); + } + } + if (vnode_old.is_valid() && p_connecting) { + if (base_type == "") { + base_type = property_path[0]; + } else if (ClassDB::is_parent_class(property_path[0], base_type)) { + base_type = property_path[0]; + } connect_seq(vnode_old, vnode, port_action_new_node); connect_data(vnode_old, vnode, port_action_new_node); } } + if (Object::cast_to<VisualScriptTypeCast>(vnode.ptr())) { + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())) { + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_script(base_script); + } + + drop_path = String(); + drop_node = nullptr; + _update_graph(port_action_new_node); } @@ -3625,7 +3755,7 @@ void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<Visual } void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting) { - String name = p_text; + String name = p_text.substr(p_text.find_char(':') + 1); if (script->has_function(name)) { EditorNode::get_singleton()->show_warning(vformat(TTR("Script already has function '%s'"), name)); return; @@ -3782,7 +3912,8 @@ void VisualScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - graph->set_panning_scheme((GraphEdit::PanningScheme)EDITOR_GET("interface/editors/sub_editor_panning_scheme").operator int()); + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"))); } break; case NOTIFICATION_READY: { @@ -3901,7 +4032,7 @@ void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_ void VisualScriptEditor::_menu_option(int p_what) { switch (p_what) { case EDIT_ADD_NODE: { - _generic_search(script->get_instance_base_type(), mouse_up_position); + _generic_search(); } break; case EDIT_DELETE_NODES: { _on_nodes_delete(); @@ -3931,7 +4062,7 @@ void VisualScriptEditor::_menu_option(int p_what) { } break; case EDIT_FIND_NODE_TYPE: { - _generic_search(script->get_instance_base_type()); + _generic_search(); } break; case EDIT_COPY_NODES: { _on_nodes_copy(); diff --git a/modules/visual_script/editor/visual_script_editor.h b/modules/visual_script/editor/visual_script_editor.h index 8901ea4006..b01732b2fd 100644 --- a/modules/visual_script/editor/visual_script_editor.h +++ b/modules/visual_script/editor/visual_script_editor.h @@ -85,55 +85,55 @@ class VisualScriptEditor : public ScriptEditorBase { MEMBER_SIGNAL }; - VBoxContainer *members_section; - MenuButton *edit_menu; + VBoxContainer *members_section = nullptr; + MenuButton *edit_menu = nullptr; Ref<VisualScript> script; - Button *base_type_select; + Button *base_type_select = nullptr; - LineEdit *func_name_box; - ScrollContainer *func_input_scroll; - VBoxContainer *func_input_vbox; - ConfirmationDialog *function_create_dialog; + LineEdit *func_name_box = nullptr; + ScrollContainer *func_input_scroll = nullptr; + VBoxContainer *func_input_vbox = nullptr; + ConfirmationDialog *function_create_dialog = nullptr; - GraphEdit *graph; - HBoxContainer *status_bar; - Button *toggle_scripts_button; + GraphEdit *graph = nullptr; + HBoxContainer *status_bar = nullptr; + Button *toggle_scripts_button = nullptr; - VisualScriptEditorSignalEdit *signal_editor; + VisualScriptEditorSignalEdit *signal_editor = nullptr; - AcceptDialog *edit_signal_dialog; - EditorInspector *edit_signal_edit; + AcceptDialog *edit_signal_dialog = nullptr; + EditorInspector *edit_signal_edit = nullptr; - VisualScriptPropertySelector *method_select; - VisualScriptPropertySelector *new_connect_node_select; - VisualScriptPropertySelector *new_virtual_method_select; + VisualScriptPropertySelector *method_select = nullptr; + VisualScriptPropertySelector *new_connect_node_select = nullptr; + VisualScriptPropertySelector *new_virtual_method_select = nullptr; - VisualScriptEditorVariableEdit *variable_editor; + VisualScriptEditorVariableEdit *variable_editor = nullptr; - AcceptDialog *edit_variable_dialog; - EditorInspector *edit_variable_edit; + AcceptDialog *edit_variable_dialog = nullptr; + EditorInspector *edit_variable_edit = nullptr; - CustomPropertyEditor *default_value_edit; + CustomPropertyEditor *default_value_edit = nullptr; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - Tree *members; - AcceptDialog *function_name_edit; - LineEdit *function_name_box; + Tree *members = nullptr; + AcceptDialog *function_name_edit = nullptr; + LineEdit *function_name_box = nullptr; - Label *hint_text; - Timer *hint_text_timer; + Label *hint_text = nullptr; + Timer *hint_text_timer = nullptr; - Label *select_func_text; + Label *select_func_text = nullptr; bool updating_graph = false; void _show_hint(const String &p_hint); void _hide_timer(); - CreateDialog *select_base_type; + CreateDialog *select_base_type = nullptr; struct VirtualInMenu { String name; @@ -173,10 +173,10 @@ class VisualScriptEditor : public ScriptEditorBase { String member_name; PortAction port_action; - int port_action_node; - int port_action_output; + int port_action_node = 0; + int port_action_output = 0; Vector2 port_action_pos; - int port_action_new_node; + int port_action_new_node = 0; bool saved_pos_dirty = false; @@ -196,7 +196,7 @@ class VisualScriptEditor : public ScriptEditorBase { int _create_new_node_from_name(const String &p_text, const Vector2 &p_point); void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); - int error_line; + int error_line = -1; void _node_selected(Node *p_node); void _center_on_node(int p_id); @@ -241,7 +241,7 @@ class VisualScriptEditor : public ScriptEditorBase { bool node_has_sequence_connections(int p_id); - void _generic_search(String p_base_type = "", Vector2 pos = Vector2(), bool node_centered = false); + void _generic_search(Vector2 pos = Vector2(), bool node_centered = false); virtual void input(const Ref<InputEvent> &p_event) override; void _graph_gui_input(const Ref<InputEvent> &p_event); @@ -267,12 +267,12 @@ class VisualScriptEditor : public ScriptEditorBase { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - int editing_id; - int editing_input; + int editing_id = 0; + int editing_input = 0; - bool can_swap; - int data_disconnect_node; - int data_disconnect_port; + bool can_swap = false; + int data_disconnect_node = 0; + int data_disconnect_port = 0; void _default_value_changed(); void _default_value_edited(Node *p_button, int p_id, int p_input_port); diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp index c88d10dabd..cf0111ee7c 100644 --- a/modules/visual_script/editor/visual_script_property_selector.cpp +++ b/modules/visual_script/editor/visual_script_property_selector.cpp @@ -37,13 +37,28 @@ #include "../visual_script_nodes.h" #include "core/os/keyboard.h" #include "editor/doc_tools.h" +#include "editor/editor_feature_profile.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/main/node.h" #include "scene/main/window.h" -void VisualScriptPropertySelector::_text_changed(const String &p_newtext) { - _update_search(); +void VisualScriptPropertySelector::_update_icons() { + search_box->set_right_icon(results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + search_box->set_clear_button_enabled(true); + search_box->add_theme_icon_override("right_icon", results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + + search_visual_script_nodes->set_icon(results_tree->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); + search_classes->set_icon(results_tree->get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + search_methods->set_icon(results_tree->get_theme_icon(SNAME("MemberMethod"), SNAME("EditorIcons"))); + search_operators->set_icon(results_tree->get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + search_signals->set_icon(results_tree->get_theme_icon(SNAME("MemberSignal"), SNAME("EditorIcons"))); + search_constants->set_icon(results_tree->get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + search_properties->set_icon(results_tree->get_theme_icon(SNAME("MemberProperty"), SNAME("EditorIcons"))); + search_theme_items->set_icon(results_tree->get_theme_icon(SNAME("MemberTheme"), SNAME("EditorIcons"))); + + case_sensitive_button->set_icon(results_tree->get_theme_icon(SNAME("MatchCase"), SNAME("EditorIcons"))); + hierarchy_button->set_icon(results_tree->get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); } void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { @@ -55,24 +70,8 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { case Key::DOWN: case Key::PAGEUP: case Key::PAGEDOWN: { - search_options->gui_input(k); + results_tree->gui_input(k); search_box->accept_event(); - - TreeItem *root = search_options->get_root(); - if (!root->get_first_child()) { - break; - } - - TreeItem *current = search_options->get_selected(); - - TreeItem *item = search_options->get_next_selected(root); - while (item) { - item->deselect(0); - item = search_options->get_next_selected(item); - } - - current->select(0); - } break; default: break; @@ -80,654 +79,1189 @@ void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { } } -void VisualScriptPropertySelector::_update_search() { - set_title(TTR("Search VisualScript")); - - search_options->clear(); - help_bit->set_text(""); - - TreeItem *root = search_options->create_item(); - bool found = false; - StringName base = base_type; - List<StringName> base_list; - while (base) { - base_list.push_back(base); - base = ClassDB::get_parent_class_nocheck(base); - } - - for (const StringName &E : base_list) { - List<MethodInfo> methods; - List<PropertyInfo> props; - TreeItem *category = nullptr; - Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = { - vbc->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("String"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector2i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Rect2"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Rect2i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("RID"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("MiniObject"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Callable"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Signal"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Dictionary"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedByteArray"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedInt32Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedInt64Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedFloat32Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedFloat64Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedStringArray"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedVector2Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")), - vbc->get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons")) - }; - { - String b = String(E); - category = search_options->create_item(root); - if (category) { - category->set_text(0, b.replace_first("*", "")); - category->set_selectable(0, false); - Ref<Texture2D> icon; - String rep = b.replace("*", ""); - icon = EditorNode::get_singleton()->get_class_icon(rep); - category->set_icon(0, icon); - } - } - if (properties || seq_connect) { - if (instance) { - instance->get_property_list(&props, true); - } else { - Object *obj = ObjectDB::get_instance(script); - if (Object::cast_to<Script>(obj)) { - Object::cast_to<Script>(obj)->get_script_property_list(&props); - } else { - ClassDB::get_property_list(E, &props, true); - } - } - for (const PropertyInfo &F : props) { - if (!(F.usage & PROPERTY_USAGE_EDITOR) && !(F.usage & PROPERTY_USAGE_SCRIPT_VARIABLE)) { - continue; - } +void VisualScriptPropertySelector::_update_results_i(int p_int) { + _update_results(); +} - if (type_filter.size() && type_filter.find(F.type) == -1) { - continue; - } +void VisualScriptPropertySelector::_update_results_s(String p_string) { + _update_results(); +} - // capitalize() also converts underscore to space, we'll match again both possible styles - String get_text_raw = String(vformat(TTR("Get %s"), F.name)); - String get_text = get_text_raw.capitalize(); - String set_text_raw = String(vformat(TTR("Set %s"), F.name)); - String set_text = set_text_raw.capitalize(); - String input = search_box->get_text().capitalize(); - - if (input.is_empty() || get_text_raw.findn(input) != -1 || get_text.findn(input) != -1) { - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, get_text); - item->set_metadata(0, F.name); - item->set_icon(0, type_icons[F.type]); - item->set_metadata(1, "get"); - item->set_collapsed(true); - item->set_selectable(0, true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); - } +void VisualScriptPropertySelector::_update_results() { + _update_icons(); + search_runner = Ref<SearchRunner>(memnew(SearchRunner(this, results_tree))); + set_process(true); +} + +void VisualScriptPropertySelector::_confirmed() { + TreeItem *ti = results_tree->get_selected(); + if (!ti) { + return; + } + emit_signal(SNAME("selected"), ti->get_metadata(0), ti->get_metadata(1), connecting); + set_visible(false); +} + +void VisualScriptPropertySelector::_item_selected() { + if (results_tree->get_selected()->has_meta("description")) { + help_bit->set_text(results_tree->get_selected()->get_meta("description")); + } else { + help_bit->set_text("No description available"); + } +} - if (input.is_empty() || set_text_raw.findn(input) != -1 || set_text.findn(input) != -1) { - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, set_text); - item->set_metadata(0, F.name); - item->set_icon(0, type_icons[F.type]); - item->set_metadata(1, "set"); - item->set_selectable(0, true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); +void VisualScriptPropertySelector::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + +void VisualScriptPropertySelector::_notification(int p_what) { + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_icons(); + } break; + case NOTIFICATION_ENTER_TREE: { + connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + } break; + case NOTIFICATION_PROCESS: { + // Update background search. + if (search_runner.is_valid()) { + if (search_runner->work()) { + // Search done. + get_ok_button()->set_disabled(!results_tree->get_selected()); + + search_runner = Ref<SearchRunner>(); + set_process(false); } - } - } - { - if (type != Variant::NIL) { - Variant v; - Callable::CallError ce; - Variant::construct(type, v, nullptr, 0, ce); - v.get_method_list(&methods); } else { - Object *obj = ObjectDB::get_instance(script); - if (Object::cast_to<Script>(obj)) { - Object::cast_to<Script>(obj)->get_script_method_list(&methods); - } - - ClassDB::get_method_list(E, &methods, true, true); - } - } - for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { - String name = M->get().name.get_slice(":", 0); - if (name.begins_with("_") && !(M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; + // if one is valid + set_process(false); } + } break; + } +} - if (virtuals_only && !(M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; - } +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select method from base type")); + base_type = p_base; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; - if (!virtuals_only && (M->get().flags & METHOD_FLAG_VIRTUAL)) { - continue; - } + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("._"); // show all _methods + search_box->set_caret_column(2); + } else { + search_box->set_text("."); // show all methods + search_box->set_caret_column(1); + } + } - MethodInfo mi = M->get(); - String desc_arguments; - if (mi.arguments.size() > 0) { - desc_arguments = "("; - for (int i = 0; i < mi.arguments.size(); i++) { - if (i > 0) { - desc_arguments += ", "; - } - if (mi.arguments[i].type == Variant::NIL) { - desc_arguments += "var"; - } else if (mi.arguments[i].name.find(":") != -1) { - desc_arguments += mi.arguments[i].name.get_slice(":", 1); - mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0); - } else { - desc_arguments += Variant::get_type_name(mi.arguments[i].type); - } - } - desc_arguments += ")"; - } - String desc_raw = mi.name + desc_arguments; - String desc = desc_raw.capitalize().replace("( ", "("); + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); - if (!search_box->get_text().is_empty() && - name.findn(search_box->get_text()) == -1 && - desc.findn(search_box->get_text()) == -1 && - desc_raw.findn(search_box->get_text()) == -1) { - continue; - } + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" - TreeItem *item = search_options->create_item(category ? category : root); - item->set_text(0, desc); - item->set_icon(0, vbc->get_theme_icon(SNAME("MemberMethod"), SNAME("EditorIcons"))); - item->set_metadata(0, name); - item->set_selectable(0, true); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); - item->set_metadata(1, "method"); - item->set_collapsed(true); - item->set_selectable(1, false); + _update_results(); +} - item->set_selectable(2, false); - item->set_metadata(2, connecting); - } +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_base_script, bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from base type")); + base_type = p_base; + base_script = p_base_script.lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + connecting = p_connecting; - if (category && category->get_first_child() == nullptr) { - memdelete(category); //old category was unused + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("_"); + } else { + search_box->set_text(" "); } } - if (properties) { - if (!seq_connect && !visual_script_generic) { - get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); - get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); - get_visual_node_names("functions/by_type/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); - get_visual_node_names("functions/deconstruct/" + Variant::get_type_name(type), Set<String>(), found, root, search_box); - get_visual_node_names("operators/compare/", Set<String>(), found, root, search_box); - if (type == Variant::INT) { - get_visual_node_names("operators/bitwise/", Set<String>(), found, root, search_box); - } - if (type == Variant::BOOL) { - get_visual_node_names("operators/logic/", Set<String>(), found, root, search_box); - } - if (type == Variant::BOOL || type == Variant::INT || type == Variant::FLOAT || type == Variant::VECTOR2 || type == Variant::VECTOR3) { - get_visual_node_names("operators/math/", Set<String>(), found, root, search_box); - } - } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(false); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + // When class is Input only show inheritors + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from script")); + ERR_FAIL_COND(p_script.is_null()); + + base_type = p_script->get_instance_base_type(); + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + script = p_script->get_instance_id(); + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(""); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(true); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" - if (seq_connect && !visual_script_generic) { - String text = search_box->get_text(); - create_visualscript_item(String("VisualScriptCondition"), root, text, String("Condition")); - create_visualscript_item(String("VisualScriptSwitch"), root, text, String("Switch")); - create_visualscript_item(String("VisualScriptSequence"), root, text, String("Sequence")); - create_visualscript_item(String("VisualScriptIterator"), root, text, String("Iterator")); - create_visualscript_item(String("VisualScriptWhile"), root, text, String("While")); - create_visualscript_item(String("VisualScriptReturn"), root, text, String("Return")); - get_visual_node_names("flow_control/type_cast", Set<String>(), found, root, search_box); - get_visual_node_names("functions/built_in/print", Set<String>(), found, root, search_box); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from basic type")); + ERR_FAIL_COND(p_type == Variant::NIL); + base_type = Variant::get_type_name(p_type); + base_script = ""; + type = p_type; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(false); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + + _update_results(); +} + +void VisualScriptPropertySelector::select_from_action(const String &p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from action")); + base_type = p_type; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; - if ((properties || seq_connect) && visual_script_generic) { - get_visual_node_names("", Set<String>(), found, root, search_box); + if (clear_text) { + search_box->set_text(""); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(false); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); + + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from instance")); + base_type = p_instance->get_class(); - TreeItem *selected_item = search_options->search_item_text(search_box->get_text()); - if (!found && selected_item != nullptr) { - selected_item->select(0); - found = true; + const Ref<Script> &p_script = p_instance->get_script(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" - get_ok_button()->set_disabled(root->get_first_child() == nullptr); + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); } -void VisualScriptPropertySelector::create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text) { - if (search_input.is_empty() || text.findn(search_input) != -1) { - TreeItem *item = search_options->create_item(root); - item->set_text(0, text); - item->set_icon(0, vbc->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); - item->set_metadata(0, name); - item->set_metadata(1, "action"); - item->set_selectable(0, true); - item->set_collapsed(true); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); +void VisualScriptPropertySelector::select_from_visual_script(const Ref<Script> &p_script, bool clear_text) { + set_title(TTR("Select from visual script")); + base_type = p_script->get_instance_base_type(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + type = Variant::NIL; + connecting = false; + + if (clear_text) { + search_box->set_text(" "); } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::show_window(float p_screen_ratio) { + popup_centered_ratio(p_screen_ratio); } -void VisualScriptPropertySelector::get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box) { - Map<String, TreeItem *> path_cache; +void VisualScriptPropertySelector::_bind_methods() { + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +} - List<String> fnodes; - VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); +VisualScriptPropertySelector::VisualScriptPropertySelector() { + vbox = memnew(VBoxContainer); + add_child(vbox); + + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->set_alignment(hbox->ALIGNMENT_CENTER); + vbox->add_child(hbox); + + case_sensitive_button = memnew(Button); + case_sensitive_button->set_flat(true); + case_sensitive_button->set_tooltip(TTR("Case Sensitive")); + case_sensitive_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + case_sensitive_button->set_toggle_mode(true); + case_sensitive_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(case_sensitive_button); + + hierarchy_button = memnew(Button); + hierarchy_button->set_flat(true); + hierarchy_button->set_tooltip(TTR("Show Hierarchy")); + hierarchy_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + hierarchy_button->set_toggle_mode(true); + hierarchy_button->set_pressed(true); + hierarchy_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(hierarchy_button); + + hbox->add_child(memnew(VSeparator)); + + search_visual_script_nodes = memnew(Button); + search_visual_script_nodes->set_flat(true); + search_visual_script_nodes->set_tooltip(TTR("Search Visual Script Nodes")); + search_visual_script_nodes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_visual_script_nodes->set_toggle_mode(true); + search_visual_script_nodes->set_pressed(true); + search_visual_script_nodes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_visual_script_nodes); + + search_classes = memnew(Button); + search_classes->set_flat(true); + search_classes->set_tooltip(TTR("Search Classes")); + search_classes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_classes->set_toggle_mode(true); + search_classes->set_pressed(true); + search_classes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_classes); + + search_operators = memnew(Button); + search_operators->set_flat(true); + search_operators->set_tooltip(TTR("Search Operators")); + search_operators->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_operators->set_toggle_mode(true); + search_operators->set_pressed(true); + search_operators->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_operators); + + hbox->add_child(memnew(VSeparator)); + + search_methods = memnew(Button); + search_methods->set_flat(true); + search_methods->set_tooltip(TTR("Search Methods")); + search_methods->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_methods->set_toggle_mode(true); + search_methods->set_pressed(true); + search_methods->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_methods); + + search_signals = memnew(Button); + search_signals->set_flat(true); + search_signals->set_tooltip(TTR("Search Signals")); + search_signals->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_signals->set_toggle_mode(true); + search_signals->set_pressed(true); + search_signals->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_signals); + + search_constants = memnew(Button); + search_constants->set_flat(true); + search_constants->set_tooltip(TTR("Search Constants")); + search_constants->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_constants->set_toggle_mode(true); + search_constants->set_pressed(true); + search_constants->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_constants); + + search_properties = memnew(Button); + search_properties->set_flat(true); + search_properties->set_tooltip(TTR("Search Properties")); + search_properties->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_properties->set_toggle_mode(true); + search_properties->set_pressed(true); + search_properties->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_properties); + + search_theme_items = memnew(Button); + search_theme_items->set_flat(true); + search_theme_items->set_tooltip(TTR("Search Theme Items")); + search_theme_items->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_theme_items->set_toggle_mode(true); + search_theme_items->set_pressed(true); + search_theme_items->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_theme_items); + + scope_combo = memnew(OptionButton); + scope_combo->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + scope_combo->set_tooltip(TTR("Select the search limits")); + scope_combo->set_stretch_ratio(0); // Fixed width. + scope_combo->add_item(TTR("Search Related"), SCOPE_RELATED); + scope_combo->add_separator(); + scope_combo->add_item(TTR("Search Base"), SCOPE_BASE); + scope_combo->add_item(TTR("Search Inheriters"), SCOPE_INHERITERS); + scope_combo->add_item(TTR("Search Unrelated"), SCOPE_UNRELATED); + scope_combo->add_item(TTR("Search All"), SCOPE_ALL); + scope_combo->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_update_results_i)); + hbox->add_child(scope_combo); - for (const String &E : fnodes) { - if (!E.begins_with(root_filter)) { - continue; - } - Vector<String> path = E.split("/"); - - // check if the name has the filter - bool in_filter = false; - Vector<String> tx_filters = search_box->get_text().split(" "); - for (int i = 0; i < tx_filters.size(); i++) { - if (tx_filters[i].is_empty()) { - in_filter = true; - } else { - in_filter = false; - } - if (E.findn(tx_filters[i]) != -1) { - in_filter = true; - break; - } - } - if (!in_filter) { - continue; - } + search_box = memnew(LineEdit); + search_box->set_tooltip(TTR("Enter \" \" to show all filtered options\nEnter \".\" to show all filtered methods, operators and constructors\nUse CTRL_KEY to drop property setters")); + search_box->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_update_results_s)); + search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); + register_text_enter(search_box); + vbox->add_child(search_box); + + results_tree = memnew(Tree); + results_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + results_tree->set_hide_root(true); + results_tree->set_hide_folding(false); + results_tree->set_columns(2); + results_tree->set_column_title(0, TTR("Name")); + results_tree->set_column_clip_content(0, true); + results_tree->set_column_title(1, TTR("Member Type")); + results_tree->set_column_expand(1, false); + results_tree->set_column_custom_minimum_width(1, 150 * EDSCALE); + results_tree->set_column_clip_content(1, true); + results_tree->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + results_tree->set_select_mode(Tree::SELECT_ROW); + results_tree->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + results_tree->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_item_selected)); + vbox->add_child(results_tree); - bool in_modifier = p_modifiers.is_empty(); - for (Set<String>::Element *F = p_modifiers.front(); F && in_modifier; F = F->next()) { - if (E.findn(F->get()) != -1) { - in_modifier = true; - } - } - if (!in_modifier) { - continue; - } + help_bit = memnew(EditorHelpBit); + vbox->add_child(help_bit); + help_bit->connect("request_hide", callable_mp(this, &VisualScriptPropertySelector::_hide_requested)); + get_ok_button()->set_text(TTR("Open")); + get_ok_button()->set_disabled(true); + set_hide_on_ok(false); +} - TreeItem *item = search_options->create_item(root); - Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(E); - Ref<VisualScriptOperator> vnode_operator = vnode; - String type_name; - if (vnode_operator.is_valid()) { - String type; - if (path.size() >= 2) { - type = path[1]; - } - type_name = type.capitalize() + " "; +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_feature_profile(const StringName &p_class) { + Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); + if (profile.is_null()) { + return false; + } + + StringName class_name = p_class; + while (class_name != StringName()) { + if (!ClassDB::class_exists(class_name)) { + return false; } - Ref<VisualScriptFunctionCall> vnode_function_call = vnode; - if (vnode_function_call.is_valid()) { - String basic_type = Variant::get_type_name(vnode_function_call->get_basic_type()); - type_name = basic_type.capitalize() + " "; + + if (profile->is_class_disabled(class_name)) { + return true; } - Ref<VisualScriptConstructor> vnode_constructor = vnode; - if (vnode_constructor.is_valid()) { - type_name = "Construct "; + class_name = ClassDB::get_parent_class(class_name); + } + + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_scope(const StringName &p_class) { + bool is_base_script = false; + if (p_class == selector_ui->base_script) { + is_base_script = true; + } + bool is_base = false; + if (selector_ui->base_type == p_class) { + is_base = true; + } + bool is_parent = false; + if ((ClassDB::is_parent_class(selector_ui->base_type, p_class)) && !is_base) { + is_parent = true; + } + + bool is_inheriter = false; + List<StringName> inheriters; + ClassDB::get_inheriters_from_class(selector_ui->base_type, &inheriters); + if (inheriters.find(p_class)) { + is_inheriter = true; + } + + if (scope_flags & SCOPE_BASE) { + if (is_base_script || is_base || is_parent) { + return false; } - Ref<VisualScriptDeconstruct> vnode_deconstruct = vnode; - if (vnode_deconstruct.is_valid()) { - type_name = "Deconstruct "; + } + if (scope_flags & SCOPE_INHERITERS) { + if (is_base_script || is_base || is_inheriter) { + return false; } - Vector<String> desc = path[path.size() - 1].replace("(", " ").replace(")", " ").replace(",", " ").split(" "); - for (int i = 0; i < desc.size(); i++) { - desc.write[i] = desc[i].capitalize(); - if (desc[i].ends_with(",")) { - desc.write[i] = desc[i].replace(",", ", "); - } + } + // if (scope_flags & SCOPE_RELATED) { + // /* code */ + // } + if (scope_flags & SCOPE_UNRELATED) { + if (!is_base_script && !is_base && !is_inheriter) { + return false; } - - item->set_text(0, type_name + String("").join(desc)); - item->set_icon(0, vbc->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); - item->set_selectable(0, true); - item->set_metadata(0, E); - item->set_selectable(0, true); - item->set_metadata(1, "visualscript"); - item->set_selectable(1, false); - item->set_selectable(2, false); - item->set_metadata(2, connecting); } + return true; } -void VisualScriptPropertySelector::_confirmed() { - TreeItem *ti = search_options->get_selected(); - if (!ti) { - return; +bool VisualScriptPropertySelector::SearchRunner::_slice() { + bool phase_done = false; + switch (phase) { + case PHASE_INIT: + phase_done = _phase_init(); + break; + case PHASE_MATCH_CLASSES_INIT: + phase_done = _phase_match_classes_init(); + break; + case PHASE_NODE_CLASSES_INIT: + phase_done = _phase_node_classes_init(); + break; + case PHASE_NODE_CLASSES_BUILD: + phase_done = _phase_node_classes_build(); + break; + case PHASE_MATCH_CLASSES: + phase_done = _phase_match_classes(); + break; + case PHASE_CLASS_ITEMS_INIT: + phase_done = _phase_class_items_init(); + break; + case PHASE_CLASS_ITEMS: + phase_done = _phase_class_items(); + break; + case PHASE_MEMBER_ITEMS_INIT: + phase_done = _phase_member_items_init(); + break; + case PHASE_MEMBER_ITEMS: + phase_done = _phase_member_items(); + break; + case PHASE_SELECT_MATCH: + phase_done = _phase_select_match(); + break; + case PHASE_MAX: + return true; + default: + WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search."); + return true; + }; + + if (phase_done) { + phase++; } - emit_signal(SNAME("selected"), ti->get_metadata(0), ti->get_metadata(1), ti->get_metadata(2)); - set_visible(false); + return false; } -void VisualScriptPropertySelector::_item_selected() { - help_bit->set_text(""); - - TreeItem *item = search_options->get_selected(); - if (!item) { - return; +bool VisualScriptPropertySelector::SearchRunner::_phase_init() { + search_flags = 0; // selector_ui->filter_combo->get_selected_id(); + if (selector_ui->search_visual_script_nodes->is_pressed()) { + search_flags |= SEARCH_VISUAL_SCRIPT_NODES; } - String name = item->get_metadata(0); - - String class_type; - if (type != Variant::NIL) { - class_type = Variant::get_type_name(type); - - } else { - class_type = base_type; + if (selector_ui->search_classes->is_pressed()) { + search_flags |= SEARCH_CLASSES; } + // if (selector_ui->search_constructors->is_pressed()) { + search_flags |= SEARCH_CONSTRUCTORS; + // } + if (selector_ui->search_methods->is_pressed()) { + search_flags |= SEARCH_METHODS; + } + if (selector_ui->search_operators->is_pressed()) { + search_flags |= SEARCH_OPERATORS; + } + if (selector_ui->search_signals->is_pressed()) { + search_flags |= SEARCH_SIGNALS; + } + if (selector_ui->search_constants->is_pressed()) { + search_flags |= SEARCH_CONSTANTS; + } + if (selector_ui->search_properties->is_pressed()) { + search_flags |= SEARCH_PROPERTIES; + } + if (selector_ui->search_theme_items->is_pressed()) { + search_flags |= SEARCH_THEME_ITEMS; + } + if (selector_ui->case_sensitive_button->is_pressed()) { + search_flags |= SEARCH_CASE_SENSITIVE; + } + if (selector_ui->hierarchy_button->is_pressed()) { + search_flags |= SEARCH_SHOW_HIERARCHY; + } + scope_flags = selector_ui->scope_combo->get_selected_id(); - DocTools *dd = EditorHelp::get_doc_data(); - String text; - - String at_class = class_type; + return true; +} - while (!at_class.is_empty()) { - Map<String, DocData::ClassDoc>::Element *E = dd->class_list.find(at_class); - if (E) { - for (int i = 0; i < E->get().properties.size(); i++) { - if (E->get().properties[i].name == name) { - text = DTR(E->get().properties[i].description); +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { + combined_docs = EditorHelp::get_doc_data()->class_list; + matches.clear(); + matched_item = nullptr; + match_highest_score = 0; + + if ( + (selector_ui->base_script.unquote() != "") && + (selector_ui->base_script.unquote() != ".") && + !combined_docs.has(selector_ui->base_script)) { + String file_path = "res://" + selector_ui->base_script.unquote(); // EditorHelp::get_doc_data().name to filepath + Ref<Script> script; + script = ResourceLoader::load(file_path); + if (!script.is_null()) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + + class_doc.name = selector_ui->base_script; + + class_doc.inherits = script->get_instance_base_type(); + class_doc.brief_description = ".vs files not supported by EditorHelp::get_doc_data()"; + class_doc.description = ""; + + Object *obj = ObjectDB::get_instance(script->get_instance_id()); + if (Object::cast_to<Script>(obj)) { + List<MethodInfo> methods; + Object::cast_to<Script>(obj)->get_script_method_list(&methods); + for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { + class_doc.methods.push_back(_get_method_doc(M->get())); } - } - } - at_class = ClassDB::get_parent_class_nocheck(at_class); - } - at_class = class_type; + List<MethodInfo> signals; + Object::cast_to<Script>(obj)->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *S = signals.front(); S; S = S->next()) { + class_doc.signals.push_back(_get_method_doc(S->get())); + } - while (!at_class.is_empty()) { - Map<String, DocData::ClassDoc>::Element *C = dd->class_list.find(at_class); - if (C) { - for (int i = 0; i < C->get().methods.size(); i++) { - if (C->get().methods[i].name == name) { - text = DTR(C->get().methods[i].description); + List<PropertyInfo> properties; + Object::cast_to<Script>(obj)->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *P = properties.front(); P; P = P->next()) { + DocData::PropertyDoc pd = DocData::PropertyDoc(); + pd.name = P->get().name; + pd.type = Variant::get_type_name(P->get().type); + class_doc.properties.push_back(pd); } } + combined_docs.insert(class_doc.name, class_doc); } + } + iterator_doc = combined_docs.front(); + return true; +} - at_class = ClassDB::get_parent_class_nocheck(at_class); +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_init() { + VisualScriptLanguage::singleton->get_registered_node_names(&vs_nodes); + _add_class_doc("functions", "", ""); + _add_class_doc("operators", "", ""); + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_build() { + if (vs_nodes.is_empty()) { + return true; } - Vector<String> functions = name.rsplit("/", false); - at_class = functions.size() > 3 ? functions[functions.size() - 2] : class_type; - Map<String, DocData::ClassDoc>::Element *T = dd->class_list.find(at_class); - if (T) { - for (int i = 0; i < T->get().methods.size(); i++) { - if (T->get().methods[i].name == functions[functions.size() - 1]) { - text = DTR(T->get().methods[i].description); + String registerd_node_name = vs_nodes[0]; + vs_nodes.pop_front(); + + Vector<String> path = registerd_node_name.split("/"); + if (path[0] == "constants") { + _add_class_doc(registerd_node_name, "", "constants"); + } else if (path[0] == "custom") { + _add_class_doc(registerd_node_name, "", "custom"); + } else if (path[0] == "data") { + _add_class_doc(registerd_node_name, "", "data"); + } else if (path[0] == "flow_control") { + _add_class_doc(registerd_node_name, "", "flow_control"); + } else if (path[0] == "functions") { + if (path[1] == "built_in") { + _add_class_doc(registerd_node_name, "functions", "built_in"); + } else if (path[1] == "by_type") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2], "by_type_class"); + } + } else if (path[1] == "constructors") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2].substr(0, path[2].find_char('(')), "constructors_class"); } + } else if (path[1] == "deconstruct") { + _add_class_doc(registerd_node_name, "", "deconstruct"); + } else if (path[1] == "wait") { + _add_class_doc(registerd_node_name, "functions", "yield"); + } else { + _add_class_doc(registerd_node_name, "functions", ""); + } + } else if (path[0] == "index") { + _add_class_doc(registerd_node_name, "", "index"); + } else if (path[0] == "operators") { + if (path[1] == "bitwise") { + _add_class_doc(registerd_node_name, "operators", "bitwise"); + } else if (path[1] == "compare") { + _add_class_doc(registerd_node_name, "operators", "compare"); + } else if (path[1] == "logic") { + _add_class_doc(registerd_node_name, "operators", "logic"); + } else if (path[1] == "math") { + _add_class_doc(registerd_node_name, "operators", "math"); + } else { + _add_class_doc(registerd_node_name, "operators", ""); } } + return false; +} - List<String> *names = memnew(List<String>); - VisualScriptLanguage::singleton->get_registered_node_names(names); - if (names->find(name) != nullptr) { - Ref<VisualScriptOperator> operator_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (operator_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(operator_node->get_class_name()); - if (F) { - text = Variant::get_operator_name(operator_node->get_operator()); +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes() { + DocData::ClassDoc &class_doc = iterator_doc->value(); + if ( + (!_is_class_disabled_by_feature_profile(class_doc.name) && !_is_class_disabled_by_scope(class_doc.name)) || + _match_visual_script(class_doc)) { + if (class_doc.inherits == "VisualScriptCustomNode") { + class_doc.script_path = "res://" + class_doc.name.unquote(); + Ref<Script> script = ResourceLoader::load(class_doc.script_path); + Ref<VisualScriptCustomNode> vsn; + vsn.instantiate(); + vsn->set_script(script); + class_doc.name = vsn->get_caption(); + if (combined_docs.has(vsn->get_category())) { + class_doc.inherits = vsn->get_category(); + } else if (combined_docs.has("VisualScriptNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptNode/" + vsn->get_category(); + } else if (combined_docs.has("VisualScriptCustomNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptCustomNode/" + vsn->get_category(); + } else { + class_doc.inherits = ""; } + class_doc.category = "VisualScriptCustomNode/" + vsn->get_category(); + class_doc.brief_description = ""; + class_doc.constructors.clear(); + class_doc.methods.clear(); + class_doc.operators.clear(); + class_doc.signals.clear(); + class_doc.constants.clear(); + class_doc.enums.clear(); + class_doc.properties.clear(); + class_doc.theme_properties.clear(); } - Ref<VisualScriptTypeCast> typecast_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (typecast_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(typecast_node->get_class_name()); - if (F) { - text = DTR(F->get().description); + + matches[class_doc.name] = ClassMatch(); + ClassMatch &match = matches[class_doc.name]; + + match.category = class_doc.category; + match.doc = &class_doc; + // Match class name. + if (search_flags & SEARCH_CLASSES || _match_visual_script(class_doc)) { + if (term == "") { + match.name = !_match_is_hidden(class_doc); + } else { + match.name = _match_string(term, class_doc.name); } + // match.name = term == "" || _match_string(term, class_doc.name); } - Ref<VisualScriptBuiltinFunc> builtin_node = VisualScriptLanguage::singleton->create_node_from_name(name); - if (builtin_node.is_valid()) { - Map<String, DocData::ClassDoc>::Element *F = dd->class_list.find(builtin_node->get_class_name()); - if (F) { - for (int i = 0; i < F->get().constants.size(); i++) { - if (F->get().constants[i].value.to_int() == int(builtin_node->get_func())) { - text = DTR(F->get().constants[i].description); + // Match members if the term is long enough. + if (term.length() >= 0) { + if (search_flags & SEARCH_CONSTRUCTORS) { + for (int i = 0; i < class_doc.constructors.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.constructors[i].name : class_doc.constructors[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.constructors.push_back(const_cast<DocData::MethodDoc *>(&class_doc.constructors[i])); + } + } + } + if (search_flags & SEARCH_METHODS) { + for (int i = 0; i < class_doc.methods.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.methods[i].name : class_doc.methods[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.methods.push_back(const_cast<DocData::MethodDoc *>(&class_doc.methods[i])); + } + } + } + if (search_flags & SEARCH_OPERATORS) { + for (int i = 0; i < class_doc.operators.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.operators[i].name : class_doc.operators[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.operators.push_back(const_cast<DocData::MethodDoc *>(&class_doc.operators[i])); + } + } + } + if (search_flags & SEARCH_SIGNALS) { + for (int i = 0; i < class_doc.signals.size(); i++) { + if (_match_string(term, class_doc.signals[i].name) || + term == " ") { + match.signals.push_back(const_cast<DocData::MethodDoc *>(&class_doc.signals[i])); + } + } + } + if (search_flags & SEARCH_CONSTANTS) { + for (int i = 0; i < class_doc.constants.size(); i++) { + if (_match_string(term, class_doc.constants[i].name) || + term == " ") { + match.constants.push_back(const_cast<DocData::ConstantDoc *>(&class_doc.constants[i])); + } + } + } + if (search_flags & SEARCH_PROPERTIES) { + for (int i = 0; i < class_doc.properties.size(); i++) { + if (_match_string(term, class_doc.properties[i].name) || + term == " " || + _match_string(term, class_doc.properties[i].getter) || + _match_string(term, class_doc.properties[i].setter)) { + match.properties.push_back(const_cast<DocData::PropertyDoc *>(&class_doc.properties[i])); + } + } + } + if (search_flags & SEARCH_THEME_ITEMS) { + for (int i = 0; i < class_doc.theme_properties.size(); i++) { + if (_match_string(term, class_doc.theme_properties[i].name) || + term == " ") { + match.theme_properties.push_back(const_cast<DocData::ThemeItemDoc *>(&class_doc.theme_properties[i])); } } } } } - memdelete(names); + iterator_doc = iterator_doc->next(); + return !iterator_doc; +} - if (text.is_empty()) { - return; - } +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items_init() { + results_tree->clear(); + iterator_match = matches.front(); - help_bit->set_text(text); -} + root_item = results_tree->create_item(); + class_items.clear(); -void VisualScriptPropertySelector::_hide_requested() { - _cancel_pressed(); // From AcceptDialog. + return true; } -void VisualScriptPropertySelector::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items() { + if (!iterator_match) { + return true; } -} -void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const String &p_current, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = p_current; - type = Variant::NIL; - properties = false; - instance = nullptr; - virtuals_only = p_virtuals_only; + ClassMatch &match = iterator_match->value(); - show_window(.5f); - if (clear_text) { - search_box->set_text(""); + if (search_flags & SEARCH_SHOW_HIERARCHY) { + if (match.required()) { + _create_class_hierarchy(match); + } } else { - search_box->select_all(); + if (match.name) { + _create_class_item(root_item, match.doc, true); + } } - search_box->grab_focus(); - connecting = p_connecting; - _update_search(); + iterator_match = iterator_match->next(); + return !iterator_match; } -void VisualScriptPropertySelector::set_type_filter(const Vector<Variant::Type> &p_type_filter) { - type_filter = p_type_filter; +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items_init() { + iterator_match = matches.front(); + + return true; } -void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_current, bool p_virtuals_only, bool p_seq_connect, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = p_current; - type = Variant::NIL; - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = p_virtuals_only; +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items() { + if (!iterator_match) { + return true; + } - show_window(.5f); - if (clear_text) { - search_box->set_text(""); + ClassMatch &match = iterator_match->value(); + + TreeItem *parent = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item; + bool constructor_created = false; + for (int i = 0; i < match.methods.size(); i++) { + String text = match.methods[i]->name; + if (!constructor_created) { + if (match.doc->name == match.methods[i]->name) { + text += " " + TTR("(constructors)"); + constructor_created = true; + } + } else { + if (match.doc->name == match.methods[i]->name) { + continue; + } + } + _create_method_item(parent, match.doc, text, match.methods[i]); + } + for (int i = 0; i < match.signals.size(); i++) { + _create_signal_item(parent, match.doc, match.signals[i]); + } + for (int i = 0; i < match.constants.size(); i++) { + _create_constant_item(parent, match.doc, match.constants[i]); + } + for (int i = 0; i < match.properties.size(); i++) { + _create_property_item(parent, match.doc, match.properties[i]); + } + for (int i = 0; i < match.theme_properties.size(); i++) { + _create_theme_property_item(parent, match.doc, match.theme_properties[i]); + } + + iterator_match = iterator_match->next(); + return !iterator_match; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_select_match() { + if (matched_item) { + matched_item->select(0); + } + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_match_string(const String &p_term, const String &p_string) const { + if (search_flags & SEARCH_CASE_SENSITIVE) { + return p_string.find(p_term) > -1; } else { - search_box->select_all(); + return p_string.findn(p_term) > -1; } - search_box->grab_focus(); - seq_connect = p_seq_connect; - connecting = p_connecting; +} - _update_search(); +bool VisualScriptPropertySelector::SearchRunner::_match_visual_script(DocData::ClassDoc &class_doc) { + if (class_doc.category.ends_with("_class")) { + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_CLASSES) { + if (matches.has(class_doc.inherits)) { + return true; + } + } + return false; + } + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_VISUAL_SCRIPT_NODES) { + return true; + } + if (class_doc.name.begins_with("operators") && search_flags & SEARCH_OPERATORS) { + return true; + } + if (class_doc.category.begins_with("VisualScriptNode/deconstruct")) { + if (class_doc.name.find(selector_ui->base_type, 0) > -1) { + return true; + } + } + + return false; } -void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const String &p_current, const bool p_connecting, bool clear_text) { - ERR_FAIL_COND(p_script.is_null()); +bool VisualScriptPropertySelector::SearchRunner::_match_is_hidden(DocData::ClassDoc &class_doc) { + if (class_doc.category.begins_with("VisualScript")) { + if (class_doc.name.begins_with("flow_control")) { + return false; + } else if (class_doc.name.begins_with("operators")) { + return !(search_flags & SEARCH_OPERATORS); + } else if (class_doc.name.begins_with("functions/built_in/print")) { + return false; + } + return true; + } + return false; +} - base_type = p_script->get_instance_base_type(); - selected = p_current; - type = Variant::NIL; - script = p_script->get_instance_id(); - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +void VisualScriptPropertySelector::SearchRunner::_match_item(TreeItem *p_item, const String &p_text) { + float inverse_length = 1.f / float(p_text.length()); - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); + // Favor types where search term is a substring close to the start of the type. + float w = 0.5f; + int pos = p_text.findn(term); + float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w); + + // Favor shorter items: they resemble the search term more. + w = 0.1f; + score *= (1 - w) + w * (term.length() * inverse_length); + + if (match_highest_score == 0 || score > match_highest_score) { + matched_item = p_item; + match_highest_score = score; } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; +} - _update_search(); +void VisualScriptPropertySelector::SearchRunner::_add_class_doc(String class_name, String inherits, String category) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + class_doc.name = class_name; + class_doc.inherits = inherits; + class_doc.category = "VisualScriptNode/" + category; + class_doc.brief_description = category; + combined_docs.insert(class_doc.name, class_doc); } -void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const String &p_current, const bool p_connecting, bool clear_text) { - ERR_FAIL_COND(p_type == Variant::NIL); - base_type = ""; - selected = p_current; - type = p_type; - properties = true; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +DocData::MethodDoc VisualScriptPropertySelector::SearchRunner::_get_method_doc(MethodInfo method_info) { + DocData::MethodDoc method_doc = DocData::MethodDoc(); + method_doc.name = method_info.name; + method_doc.return_type = Variant::get_type_name(method_info.return_val.type); + method_doc.description = "No description available"; + for (List<PropertyInfo>::Element *P = method_info.arguments.front(); P; P = P->next()) { + DocData::ArgumentDoc argument_doc = DocData::ArgumentDoc(); + argument_doc.name = P->get().name; + argument_doc.type = Variant::get_type_name(P->get().type); + method_doc.arguments.push_back(argument_doc); + } + return method_doc; +} - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_hierarchy(const ClassMatch &p_match) { + if (class_items.has(p_match.doc->name)) { + return class_items[p_match.doc->name]; } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; - _update_search(); + // Ensure parent nodes are created first. + TreeItem *parent = root_item; + if (p_match.doc->inherits != "") { + if (class_items.has(p_match.doc->inherits)) { + parent = class_items[p_match.doc->inherits]; + } else if (matches.has(p_match.doc->inherits)) { + ClassMatch &base_match = matches[p_match.doc->inherits]; + parent = _create_class_hierarchy(base_match); + } + } + + TreeItem *class_item = _create_class_item(parent, p_match.doc, !p_match.name); + class_items[p_match.doc->name] = class_item; + return class_item; } -void VisualScriptPropertySelector::select_from_action(const String &p_type, const String &p_current, const bool p_connecting, bool clear_text) { - base_type = p_type; - selected = p_current; - type = Variant::NIL; - properties = false; - visual_script_generic = false; - instance = nullptr; - virtuals_only = false; +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray) { + Ref<Texture2D> icon = empty_icon; + String text_0 = p_doc->name; + String text_1 = "Class"; + + String what = "Class"; + String details = p_doc->name; + if (p_doc->category.begins_with("VisualScriptCustomNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); + text_0 = path[path.size() - 1]; + text_1 = "VisualScriptCustomNode"; + what = "VisualScriptCustomNode"; + details = "CustomNode"; + } else if (p_doc->category.begins_with("VisualScriptNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); + text_0 = path[path.size() - 1]; + if (p_doc->category.begins_with("VisualScriptNode/deconstruct")) { + text_0 = "deconstruct " + text_0; + } + text_1 = "VisualScriptNode"; + what = "VisualScriptNode"; + details = p_doc->name; + + if (path.size() == 1) { + if (path[0] == "functions" || path[0] == "operators") { + text_1 = "VisualScript"; + p_gray = true; + what = "no_result"; + details = ""; + } + } - show_window(.5f); - if (clear_text) { - search_box->set_text(""); } else { - search_box->select_all(); + if (p_doc->name.is_quoted()) { + text_0 = p_doc->name.unquote().get_file(); + if (ui_service->has_theme_icon(p_doc->inherits, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->inherits, "EditorIcons"); + } + } else if (ui_service->has_theme_icon(p_doc->name, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->name, "EditorIcons"); + } else if (ClassDB::class_exists(p_doc->name) && ClassDB::is_parent_class(p_doc->name, "Object")) { + icon = ui_service->get_theme_icon(SNAME("Object"), SNAME("EditorIcons")); + } + } + String tooltip = p_doc->brief_description.strip_edges(); + + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text_0); + item->set_text(1, TTR(text_1)); + item->set_tooltip(0, tooltip); + item->set_tooltip(1, tooltip); + item->set_metadata(0, details); + item->set_metadata(1, what); + if (p_gray) { + item->set_custom_color(0, disabled_color); + item->set_custom_color(1, disabled_color); } - search_box->grab_focus(); - seq_connect = true; - connecting = p_connecting; - _update_search(); + _match_item(item, p_doc->name); + + return item; } -void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const String &p_current, const bool p_connecting, const String &p_basetype, bool clear_text) { - base_type = p_basetype; - selected = p_current; - type = Variant::NIL; - properties = true; - visual_script_generic = false; - instance = p_instance; - virtuals_only = false; +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } + } + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_doc->name, p_text, TTRC("Method"), "method", tooltip, p_doc->description); +} - show_window(.5f); - if (clear_text) { - search_box->set_text(""); - } else { - search_box->select_all(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } } - search_box->grab_focus(); - seq_connect = false; - connecting = p_connecting; + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_doc->name, p_doc->name, TTRC("Signal"), "signal", tooltip, p_doc->description); +} - _update_search(); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc) { + String tooltip = p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_doc->name, p_doc->name, TTRC("Constant"), "constant", tooltip, p_doc->description); } -void VisualScriptPropertySelector::select_from_visual_script(const String &p_base, const bool p_connecting, bool clear_text) { - base_type = p_base; - selected = ""; - type = Variant::NIL; - properties = true; - visual_script_generic = true; - instance = nullptr; - virtuals_only = false; - show_window(.5f); - if (clear_text) { - search_box->set_text(""); +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + tooltip += "\n " + p_class_doc->name + "." + p_doc->setter + "(value) setter"; + tooltip += "\n " + p_class_doc->name + "." + p_doc->getter + "() getter"; + return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_doc->name, p_doc->name, TTRC("Property"), "property", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_doc->name, p_doc->name, TTRC("Theme Property"), "theme_item", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description) { + Ref<Texture2D> icon; + String text; + if (search_flags & SEARCH_SHOW_HIERARCHY) { + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_text; } else { - search_box->select_all(); + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_class_name + "." + p_text; } - search_box->grab_focus(); - connecting = p_connecting; - _update_search(); -} + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text); + item->set_text(1, TTRGET(p_type)); + item->set_tooltip(0, p_tooltip); + item->set_tooltip(1, p_tooltip); + item->set_metadata(0, p_class_name + ":" + p_name); + item->set_metadata(1, "class_" + p_metatype); + item->set_meta("description", p_description); -void VisualScriptPropertySelector::show_window(float p_screen_ratio) { - popup_centered_ratio(p_screen_ratio); + _match_item(item, p_name); + + return item; } -void VisualScriptPropertySelector::_bind_methods() { - ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +bool VisualScriptPropertySelector::SearchRunner::work(uint64_t slot) { + // Return true when the search has been completed, otherwise false. + const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot; + while (!_slice()) { + if (OS::get_singleton()->get_ticks_usec() > until) { + return false; + } + } + return true; } -VisualScriptPropertySelector::VisualScriptPropertySelector() { - vbc = memnew(VBoxContainer); - add_child(vbc); - //set_child_rect(vbc); - search_box = memnew(LineEdit); - vbc->add_margin_child(TTR("Search:"), search_box); - search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_text_changed)); - search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); - search_options = memnew(Tree); - vbc->add_margin_child(TTR("Matches:"), search_options, true); - get_ok_button()->set_text(TTR("Open")); - get_ok_button()->set_disabled(true); - register_text_enter(search_box); - set_hide_on_ok(false); - search_options->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); - search_options->connect("cell_selected", callable_mp(this, &VisualScriptPropertySelector::_item_selected)); - search_options->set_hide_root(true); - search_options->set_hide_folding(true); - virtuals_only = false; - seq_connect = false; - help_bit = memnew(EditorHelpBit); - vbc->add_margin_child(TTR("Description:"), help_bit); - help_bit->connect("request_hide", callable_mp(this, &VisualScriptPropertySelector::_hide_requested)); - search_options->set_columns(3); - search_options->set_column_expand(1, false); - search_options->set_column_expand(2, false); +VisualScriptPropertySelector::SearchRunner::SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree) : + selector_ui(p_selector_ui), + ui_service(p_selector_ui->vbox), + results_tree(p_results_tree), + term(p_selector_ui->search_box->get_text()), + empty_icon(ui_service->get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))), + disabled_color(ui_service->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))) { } diff --git a/modules/visual_script/editor/visual_script_property_selector.h b/modules/visual_script/editor/visual_script_property_selector.h index 9e065548a0..6b5112f1af 100644 --- a/modules/visual_script/editor/visual_script_property_selector.h +++ b/modules/visual_script/editor/visual_script_property_selector.h @@ -31,6 +31,7 @@ #ifndef VISUALSCRIPT_PROPERTYSELECTOR_H #define VISUALSCRIPT_PROPERTYSELECTOR_H +#include "../visual_script.h" #include "editor/editor_help.h" #include "editor/property_editor.h" #include "scene/gui/rich_text_label.h" @@ -38,15 +39,56 @@ class VisualScriptPropertySelector : public ConfirmationDialog { GDCLASS(VisualScriptPropertySelector, ConfirmationDialog); + enum SearchFlags { + SEARCH_CLASSES = 1 << 0, + SEARCH_CONSTRUCTORS = 1 << 1, + SEARCH_METHODS = 1 << 2, + SEARCH_OPERATORS = 1 << 3, + SEARCH_SIGNALS = 1 << 4, + SEARCH_CONSTANTS = 1 << 5, + SEARCH_PROPERTIES = 1 << 6, + SEARCH_THEME_ITEMS = 1 << 7, + SEARCH_VISUAL_SCRIPT_NODES = 1 << 8, + SEARCH_ALL = SEARCH_CLASSES | SEARCH_CONSTRUCTORS | SEARCH_METHODS | SEARCH_OPERATORS | SEARCH_SIGNALS | SEARCH_CONSTANTS | SEARCH_PROPERTIES | SEARCH_THEME_ITEMS, + SEARCH_CASE_SENSITIVE = 1 << 29, + SEARCH_SHOW_HIERARCHY = 1 << 30, + }; + + enum ScopeFlags { + SCOPE_BASE = 1 << 0, + SCOPE_INHERITERS = 1 << 1, + SCOPE_UNRELATED = 1 << 2, + SCOPE_RELATED = SCOPE_BASE | SCOPE_INHERITERS, + SCOPE_ALL = SCOPE_BASE | SCOPE_INHERITERS | SCOPE_UNRELATED + }; + LineEdit *search_box; - Tree *search_options; - void _text_changed(const String &p_newtext); - void _sbox_input(const Ref<InputEvent> &p_ie); - void _update_search(); + Button *case_sensitive_button; + Button *hierarchy_button; + + Button *search_visual_script_nodes; + Button *search_classes; + Button *search_operators; + + Button *search_methods; + Button *search_signals; + Button *search_constants; + Button *search_properties; + Button *search_theme_items; - void create_visualscript_item(const String &name, TreeItem *const root, const String &search_input, const String &text); - void get_visual_node_names(const String &root_filter, const Set<String> &p_modifiers, bool &found, TreeItem *const root, LineEdit *const search_box); + OptionButton *scope_combo; + Tree *results_tree; + + class SearchRunner; + Ref<SearchRunner> search_runner; + + void _update_icons(); + + void _sbox_input(const Ref<InputEvent> &p_ie); + void _update_results_i(int p_int); + void _update_results_s(String p_string); + void _update_results(); void _confirmed(); void _item_selected(); @@ -54,38 +96,124 @@ class VisualScriptPropertySelector : public ConfirmationDialog { EditorHelpBit *help_bit; - bool properties; - bool visual_script_generic; - bool connecting; + bool properties = false; + bool visual_script_generic = false; + bool connecting = false; String selected; Variant::Type type; String base_type; + String base_script; ObjectID script; Object *instance; - bool virtuals_only; - bool seq_connect; - VBoxContainer *vbc; - - Vector<Variant::Type> type_filter; + bool virtuals_only = false; + VBoxContainer *vbox; protected: void _notification(int p_what); static void _bind_methods(); public: - void select_method_from_base_type(const String &p_base, const String &p_current = "", const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); - void select_from_base_type(const String &p_base, const String &p_current = "", bool p_virtuals_only = false, bool p_seq_connect = false, const bool p_connecting = true, bool clear_text = true); - void select_from_script(const Ref<Script> &p_script, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_basic_type(Variant::Type p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_action(const String &p_type, const String &p_current = "", const bool p_connecting = true, bool clear_text = true); - void select_from_instance(Object *p_instance, const String &p_current = "", const bool p_connecting = true, const String &p_basetype = "", bool clear_text = true); - void select_from_visual_script(const String &p_base, const bool p_connecting = true, bool clear_text = true); + void select_method_from_base_type(const String &p_base, const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_base_type(const String &p_base, const String &p_base_script = "", bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_script(const Ref<Script> &p_script, const bool p_connecting = true, bool clear_text = true); + void select_from_basic_type(Variant::Type p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_action(const String &p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_instance(Object *p_instance, const bool p_connecting = true, bool clear_text = true); + void select_from_visual_script(const Ref<Script> &p_script, bool clear_text = true); void show_window(float p_screen_ratio); - void set_type_filter(const Vector<Variant::Type> &p_type_filter); - VisualScriptPropertySelector(); }; +class VisualScriptPropertySelector::SearchRunner : public RefCounted { + enum Phase { + PHASE_INIT, + PHASE_MATCH_CLASSES_INIT, + PHASE_NODE_CLASSES_INIT, + PHASE_NODE_CLASSES_BUILD, + PHASE_MATCH_CLASSES, + PHASE_CLASS_ITEMS_INIT, + PHASE_CLASS_ITEMS, + PHASE_MEMBER_ITEMS_INIT, + PHASE_MEMBER_ITEMS, + PHASE_SELECT_MATCH, + PHASE_MAX + }; + int phase = 0; + + struct ClassMatch { + DocData::ClassDoc *doc; + bool name = false; + String category = ""; + Vector<DocData::MethodDoc *> constructors; + Vector<DocData::MethodDoc *> methods; + Vector<DocData::MethodDoc *> operators; + Vector<DocData::MethodDoc *> signals; + Vector<DocData::ConstantDoc *> constants; + Vector<DocData::PropertyDoc *> properties; + Vector<DocData::ThemeItemDoc *> theme_properties; + + bool required() { + return name || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size(); + } + }; + + VisualScriptPropertySelector *selector_ui; + Control *ui_service; + Tree *results_tree; + String term; + int search_flags; + int scope_flags; + + Ref<Texture2D> empty_icon; + Color disabled_color; + + Map<String, DocData::ClassDoc>::Element *iterator_doc = nullptr; + Map<String, ClassMatch> matches; + Map<String, ClassMatch>::Element *iterator_match = nullptr; + TreeItem *root_item = nullptr; + Map<String, TreeItem *> class_items; + TreeItem *matched_item = nullptr; + float match_highest_score = 0; + + Map<String, DocData::ClassDoc> combined_docs; + List<String> vs_nodes; + + bool _is_class_disabled_by_feature_profile(const StringName &p_class); + bool _is_class_disabled_by_scope(const StringName &p_class); + + bool _slice(); + bool _phase_init(); + bool _phase_match_classes_init(); + bool _phase_node_classes_init(); + bool _phase_node_classes_build(); + bool _phase_match_classes(); + bool _phase_class_items_init(); + bool _phase_class_items(); + bool _phase_member_items_init(); + bool _phase_member_items(); + bool _phase_select_match(); + + bool _match_string(const String &p_term, const String &p_string) const; + bool _match_visual_script(DocData::ClassDoc &class_doc); + bool _match_is_hidden(DocData::ClassDoc &class_doc); + void _match_item(TreeItem *p_item, const String &p_text); + void _add_class_doc(String class_name, String inherits, String category); + DocData::MethodDoc _get_method_doc(MethodInfo method_info); + TreeItem *_create_class_hierarchy(const ClassMatch &p_match); + TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray); + TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc); + TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc); + TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc); + TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc); + TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc); + TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description); + +public: + bool work(uint64_t slot = 100000); + + SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree); +}; + #endif // VISUALSCRIPT_PROPERTYSELECTOR_H diff --git a/modules/visual_script/icons/VisualScript.svg b/modules/visual_script/icons/VisualScript.svg index 2352ba5d87..bc698247c9 100644 --- a/modules/visual_script/icons/VisualScript.svg +++ b/modules/visual_script/icons/VisualScript.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><ellipse cx="3" cy="1039.4" fill="#6e6e6e"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm-4 9v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4zm8 0a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><ellipse cx="3" cy="1039.4" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2zm-4 9v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4zm8 0a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg> diff --git a/modules/visual_script/icons/VisualScriptInternal.svg b/modules/visual_script/icons/VisualScriptInternal.svg index ea83047a29..8ab39ad929 100644 --- a/modules/visual_script/icons/VisualScriptInternal.svg +++ b/modules/visual_script/icons/VisualScriptInternal.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="3" cy="3.000024" fill="#6e6e6e" r="0"/><path d="m11 10a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0"/><path d="m3 10v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4z" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2z" fill="none" stroke="#e0e0e0"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><circle cx="3" cy="3.000024" fill="#e0e0e0" r="0"/><path d="m11 10a2 2 0 0 0 -1.7324 1 2 2 0 0 0 0 2 2 2 0 0 0 1.7324 1h-2v2h2a2 2 0 0 0 1.7324-1 2 2 0 0 0 0-2 2 2 0 0 0 -1.7324-1h2v-2z" fill="#e0e0e0"/><path d="m3 10v6h2a3 3 0 0 0 3-3v-3h-2v3a1 1 0 0 1 -1 1v-4z" fill="#e0e0e0"/><path d="m7 1-.56445 2.2578a5 5 0 0 0 -.68945.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -.28516.68555l-2.2539.5625v2h5.2715a2 2 0 0 1 -.27148-1 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1 -.26953 1h5.2695v-2l-2.2578-.56445a5 5 0 0 0 -.2793-.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -.68555-.28516l-.5625-2.2539h-2z" fill="none" stroke="#e0e0e0"/></svg> diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index fbdf3a654b..88445f2f98 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1164,9 +1164,6 @@ void VisualScript::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_custom_signal", "name"), &VisualScript::remove_custom_signal); ClassDB::bind_method(D_METHOD("rename_custom_signal", "name", "new_name"), &VisualScript::rename_custom_signal); - //ClassDB::bind_method(D_METHOD("set_variable_info","name","info"),&VScript::set_variable_info); - //ClassDB::bind_method(D_METHOD("get_variable_info","name"),&VScript::set_variable_info); - ClassDB::bind_method(D_METHOD("set_instance_base_type", "type"), &VisualScript::set_instance_base_type); ClassDB::bind_method(D_METHOD("_set_data", "data"), &VisualScript::_set_data); diff --git a/modules/visual_script/visual_script_builtin_funcs.cpp b/modules/visual_script/visual_script_builtin_funcs.cpp index 99e75f9289..fd55796a66 100644 --- a/modules/visual_script/visual_script_builtin_funcs.cpp +++ b/modules/visual_script/visual_script_builtin_funcs.cpp @@ -964,7 +964,7 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in return; } - if (p_inputs[0]->is_ref()) { + if (p_inputs[0]->is_ref_counted()) { REF r = *p_inputs[0]; if (!r.is_valid()) { return; diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 2abbd19e12..e8942b9788 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -328,6 +328,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { }; case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -364,9 +365,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -374,13 +377,13 @@ Error VisualScriptExpression::_get_token(Token &r_token) { r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + if (!is_hex_digit(c)) { _set_error("Malformed hex constant in string"); r_token.type = TK_ERROR; return ERR_PARSE_ERROR; } char32_t v; - if (c >= '0' && c <= '9') { + if (is_digit(c)) { v = c - '0'; } else if (c >= 'a' && c <= 'f') { v = c - 'a'; @@ -403,12 +406,46 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; @@ -420,7 +457,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { break; } - if (cchar >= '0' && cchar <= '9') { + if (is_digit(cchar)) { //a number String num; @@ -439,7 +476,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { while (true) { switch (reading) { case READING_INT: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { //pass } else if (c == '.') { reading = READING_DEC; @@ -452,7 +489,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_DEC: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { } else if (c == 'e') { reading = READING_EXP; @@ -462,7 +499,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; case READING_EXP: { - if (c >= '0' && c <= '9') { + if (is_digit(c)) { exp_beg = true; } else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) { @@ -495,11 +532,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } return OK; - } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') { + } else if (is_ascii_char(cchar) || cchar == '_') { String id; bool first = true; - while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) { + while (is_ascii_char(cchar) || cchar == '_' || (!first && is_digit(cchar))) { id += String::chr(cchar); cchar = GET_CHAR(); first = false; diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index cc18d48dd8..ef6c1ecdb9 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -912,7 +912,7 @@ int VisualScriptPropertySet::get_output_sequence_port_count() const { } bool VisualScriptPropertySet::has_input_sequence_port() const { - return 1; + return true; } Node *VisualScriptPropertySet::_get_base_node() const { diff --git a/modules/visual_script/visual_script_nodes.cpp b/modules/visual_script/visual_script_nodes.cpp index f3594e5164..e7f4e542c1 100644 --- a/modules/visual_script/visual_script_nodes.cpp +++ b/modules/visual_script/visual_script_nodes.cpp @@ -2495,7 +2495,7 @@ static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const VisualScriptSceneNode::TypeGuess VisualScriptSceneNode::guess_output_type(TypeGuess *p_inputs, int p_output) const { VisualScriptSceneNode::TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "Node"; + tg.gdclass = SNAME("Node"); #ifdef TOOLS_ENABLED Ref<Script> script = get_visual_script(); @@ -2649,7 +2649,7 @@ VisualScriptNodeInstance *VisualScriptSceneTree::instantiate(VisualScriptInstanc VisualScriptSceneTree::TypeGuess VisualScriptSceneTree::guess_output_type(TypeGuess *p_inputs, int p_output) const { TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "SceneTree"; + tg.gdclass = SNAME("SceneTree"); return tg; } @@ -2766,11 +2766,11 @@ PropertyInfo VisualScriptSelf::get_input_value_port_info(int p_idx) const { } PropertyInfo VisualScriptSelf::get_output_value_port_info(int p_idx) const { - String type_name; + StringName type_name; if (get_visual_script().is_valid()) { type_name = get_visual_script()->get_instance_base_type(); } else { - type_name = "instance"; + type_name = SNAME("instance"); } return PropertyInfo(Variant::OBJECT, type_name); @@ -2801,7 +2801,7 @@ VisualScriptNodeInstance *VisualScriptSelf::instantiate(VisualScriptInstance *p_ VisualScriptSelf::TypeGuess VisualScriptSelf::guess_output_type(TypeGuess *p_inputs, int p_output) const { VisualScriptSceneNode::TypeGuess tg; tg.type = Variant::OBJECT; - tg.gdclass = "Object"; + tg.gdclass = SNAME("Object"); Ref<Script> script = get_visual_script(); if (!script.is_valid()) { diff --git a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml index 2f2c2c379d..416a674435 100644 --- a/modules/webrtc/doc_classes/WebRTCPeerConnection.xml +++ b/modules/webrtc/doc_classes/WebRTCPeerConnection.xml @@ -117,7 +117,7 @@ </methods> <signals> <signal name="data_channel_received"> - <argument index="0" name="channel" type="Object" /> + <argument index="0" name="channel" type="WebRTCDataChannel" /> <description> Emitted when a new in-band channel is received, i.e. when the channel was created with [code]negotiated: false[/code] (default). The object will be an instance of [WebRTCDataChannel]. You must keep a reference of it or it will be closed automatically. See [method create_data_channel]. diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 54d4f57eef..e6a8dcc31a 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -38,11 +38,11 @@ #include "webrtc_peer_connection_extension.h" void register_webrtc_types() { -#define _SET_HINT(NAME, _VAL_, _MAX_) \ - GLOBAL_DEF(NAME, _VAL_); \ +#define SET_HINT(NAME, _VAL_, _MAX_) \ + GLOBAL_DEF(NAME, _VAL_); \ ProjectSettings::get_singleton()->set_custom_property_info(NAME, PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater")); - _SET_HINT(WRTC_IN_BUF, 64, 4096); + SET_HINT(WRTC_IN_BUF, 64, 4096); ClassDB::register_custom_instance_class<WebRTCPeerConnection>(); GDREGISTER_CLASS(WebRTCPeerConnectionExtension); @@ -51,6 +51,8 @@ void register_webrtc_types() { GDREGISTER_CLASS(WebRTCDataChannelExtension); GDREGISTER_CLASS(WebRTCMultiplayerPeer); + +#undef SET_HINT } void unregister_webrtc_types() {} diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 96cf518c06..7fdf26d3cd 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -69,7 +69,7 @@ void WebRTCPeerConnection::_bind_methods() { ADD_SIGNAL(MethodInfo("session_description_created", PropertyInfo(Variant::STRING, "type"), PropertyInfo(Variant::STRING, "sdp"))); ADD_SIGNAL(MethodInfo("ice_candidate_created", PropertyInfo(Variant::STRING, "media"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::STRING, "name"))); - ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel"))); + ADD_SIGNAL(MethodInfo("data_channel_received", PropertyInfo(Variant::OBJECT, "channel", PROPERTY_HINT_RESOURCE_TYPE, "WebRTCDataChannel"))); BIND_ENUM_CONSTANT(STATE_NEW); BIND_ENUM_CONSTANT(STATE_CONNECTING); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index bccbf88417..1ef571b6ee 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -124,17 +124,17 @@ bool WSLClient::_verify_headers(String &r_protocol) { } } -#define _WSL_CHECK(NAME, VALUE) \ +#define WSL_CHECK(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define _WSL_CHECK_NC(NAME, VALUE) \ +#define WSL_CHECK_NC(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME] != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); - _WSL_CHECK("connection", "upgrade"); - _WSL_CHECK("upgrade", "websocket"); - _WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); -#undef _WSL_CHECK_NC -#undef _WSL_CHECK + WSL_CHECK("connection", "upgrade"); + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); +#undef WSL_CHECK_NC +#undef WSL_CHECK if (_protocols.size() == 0) { // We didn't request a custom protocol ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); @@ -163,22 +163,24 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, _peer = Ref<WSLPeer>(memnew(WSLPeer)); if (p_host.is_valid_ip_address()) { - ip_candidates.clear(); - ip_candidates.push_back(IPAddress(p_host)); + _ip_candidates.push_back(IPAddress(p_host)); } else { - ip_candidates = IP::get_singleton()->resolve_hostname_addresses(p_host); - } - - ERR_FAIL_COND_V(ip_candidates.is_empty(), ERR_INVALID_PARAMETER); - - String port = ""; - if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { - port = ":" + itos(p_port); + // Queue hostname for resolution. + _resolver_id = IP::get_singleton()->resolve_hostname_queue_item(p_host); + ERR_FAIL_COND_V(_resolver_id == IP::RESOLVER_INVALID_ID, ERR_INVALID_PARAMETER); + // Check if it was found in cache. + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } } - Error err = ERR_BUG; // Should be at least one entry. - while (ip_candidates.size() > 0) { - err = _tcp->connect_to_host(ip_candidates.pop_front(), p_port); + // We assume OK while hostname resolution is pending. + Error err = _resolver_id != IP::RESOLVER_INVALID_ID ? OK : FAILED; + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), p_port); if (err == OK) { break; } @@ -200,8 +202,11 @@ Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, } _key = WSLPeer::generate_key(); - // TODO custom extra headers (allow overriding this too?) String request = "GET " + p_path + " HTTP/1.1\r\n"; + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } request += "Host: " + p_host + port + "\r\n"; request += "Upgrade: websocket\r\n"; request += "Connection: Upgrade\r\n"; @@ -231,6 +236,30 @@ int WSLClient::get_max_packet_size() const { } void WSLClient::poll() { + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::ResolverStatus ip_status = IP::get_singleton()->get_resolve_item_status(_resolver_id); + if (ip_status == IP::RESOLVER_STATUS_WAITING) { + return; + } + // Anything else is either a candidate or a failure. + Error err = FAILED; + if (ip_status == IP::RESOLVER_STATUS_DONE) { + _ip_candidates = IP::get_singleton()->get_resolve_item_addresses(_resolver_id); + while (_ip_candidates.size()) { + err = _tcp->connect_to_host(_ip_candidates.pop_front(), _port); + if (err == OK) { + break; + } + } + } + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + } if (_peer->is_connected_to_host()) { _peer->poll(); if (!_peer->is_connected_to_host()) { @@ -251,7 +280,7 @@ void WSLClient::poll() { _on_error(); break; case StreamPeerTCP::STATUS_CONNECTED: { - ip_candidates.clear(); + _ip_candidates.clear(); Ref<StreamPeerSSL> ssl; if (_use_ssl) { if (_connection == _tcp) { @@ -282,9 +311,9 @@ void WSLClient::poll() { _do_handshake(); } break; case StreamPeerTCP::STATUS_ERROR: - while (ip_candidates.size() > 0) { + while (_ip_candidates.size() > 0) { _tcp->disconnect_from_host(); - if (_tcp->connect_to_host(ip_candidates.pop_front(), _port) == OK) { + if (_tcp->connect_to_host(_ip_candidates.pop_front(), _port) == OK) { return; } } @@ -307,7 +336,7 @@ MultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { return CONNECTION_CONNECTED; } - if (_tcp->is_connected_to_host()) { + if (_tcp->is_connected_to_host() || _resolver_id != IP::RESOLVER_INVALID_ID) { return CONNECTION_CONNECTING; } @@ -330,7 +359,12 @@ void WSLClient::disconnect_from_host(int p_code, String p_reason) { memset(_resp_buf, 0, sizeof(_resp_buf)); _resp_pos = 0; - ip_candidates.clear(); + if (_resolver_id != IP::RESOLVER_INVALID_ID) { + IP::get_singleton()->erase_resolve_item(_resolver_id); + _resolver_id = IP::RESOLVER_INVALID_ID; + } + + _ip_candidates.clear(); } IPAddress WSLClient::get_connected_host() const { diff --git a/modules/websocket/wsl_client.h b/modules/websocket/wsl_client.h index 4839d7ab9b..d846e6be00 100644 --- a/modules/websocket/wsl_client.h +++ b/modules/websocket/wsl_client.h @@ -63,10 +63,11 @@ private: String _key; String _host; - int _port; - Array ip_candidates; + uint16_t _port; + Array _ip_candidates; Vector<String> _protocols; bool _use_ssl = false; + IP::ResolverID _resolver_id = IP::RESOLVER_INVALID_ID; void _do_handshake(); bool _verify_headers(String &r_protocol); diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 31175c5779..eadd7ef7ac 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -58,17 +58,17 @@ bool WSLServer::PendingPeer::_parse_request(const Vector<String> p_protocols, St headers[name] = value; } } -#define _WSL_CHECK(NAME, VALUE) \ +#define WSL_CHECK(NAME, VALUE) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false, \ "Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'."); -#define _WSL_CHECK_EX(NAME) \ +#define WSL_CHECK_EX(NAME) \ ERR_FAIL_COND_V_MSG(!headers.has(NAME), false, "Missing header '" + String(NAME) + "'."); - _WSL_CHECK("upgrade", "websocket"); - _WSL_CHECK("sec-websocket-version", "13"); - _WSL_CHECK_EX("sec-websocket-key"); - _WSL_CHECK_EX("connection"); -#undef _WSL_CHECK_EX -#undef _WSL_CHECK + WSL_CHECK("upgrade", "websocket"); + WSL_CHECK("sec-websocket-version", "13"); + WSL_CHECK_EX("sec-websocket-key"); + WSL_CHECK_EX("connection"); +#undef WSL_CHECK_EX +#undef WSL_CHECK key = headers["sec-websocket-key"]; if (headers.has("sec-websocket-protocol")) { Vector<String> protos = headers["sec-websocket-protocol"].split(","); diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 8eb0d8ff90..86b857f72c 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -385,7 +385,7 @@ CameraMatrix WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p return eye; } -Vector<BlitToScreen> WebXRInterfaceJS::commit_views(RID p_render_target, const Rect2 &p_screen_rect) { +Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; if (!initialized) { diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 8eddfbe484..31858194f6 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -88,7 +88,7 @@ public: virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; - virtual Vector<BlitToScreen> commit_views(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; virtual void process() override; |