diff options
Diffstat (limited to 'modules')
383 files changed, 13791 insertions, 5718 deletions
diff --git a/modules/basis_universal/register_types.cpp b/modules/basis_universal/register_types.cpp index 86f3385ae3..7c0bc4ac82 100644 --- a/modules/basis_universal/register_types.cpp +++ b/modules/basis_universal/register_types.cpp @@ -162,7 +162,7 @@ static Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size const uint8_t *ptr = p_data; int size = p_size; - ERR_FAIL_COND_V_MSG(p_data == nullptr, image, "Cannot unpack invalid basis universal data."); + ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid basis universal data."); basist::transcoder_texture_format format = basist::transcoder_texture_format::cTFTotalTextureFormats; Image::Format imgfmt = Image::FORMAT_MAX; diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 5605255367..72b540496d 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -167,7 +167,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, // Next we apply some color scaling going from a variable value space to a 256 value space. // This may be simplified some but left as is for legibility. - // float scaled_value = unscaled_value * byte_max_value / color_channel_maxium_value + rounding_offset; + // float scaled_value = unscaled_value * byte_max_value / color_channel_maximum_value + rounding_offset; float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f; float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f; float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f; diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 7cafccfdcb..9c178997c5 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -300,7 +300,7 @@ void CSGShape3D::_update_shape() { root_mesh.unref(); //byebye root mesh CSGBrush *n = _get_brush(); - ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush."); + ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); OAHashMap<Vector3, Vector3> vec_map; @@ -458,7 +458,7 @@ void CSGShape3D::_update_shape() { void CSGShape3D::_update_collision_faces() { if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { CSGBrush *n = _get_brush(); - ERR_FAIL_COND_MSG(!n, "Cannot get CSGBrush."); + ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); Vector<Vector3> physics_faces; physics_faces.resize(n->faces.size() * 3); Vector3 *physicsw = physics_faces.ptrw(); @@ -488,7 +488,9 @@ bool CSGShape3D::_is_debug_collision_shape_visible() { } void CSGShape3D::_update_debug_collision_shape() { - // NOTE: This is called only for the root shape with collision, when root_collision_shape is valid. + if (!use_collision || !is_root_shape() || !root_collision_shape.is_valid() || !_is_debug_collision_shape_visible()) { + return; + } ERR_FAIL_NULL(RenderingServer::get_singleton()); @@ -573,6 +575,11 @@ void CSGShape3D::_notification(int p_what) { // Update this node's parent only if its own visibility has changed, not the visibility of parent nodes parent_shape->_make_dirty(); } + if (is_visible()) { + _update_debug_collision_shape(); + } else { + _clear_debug_collision_shape(); + } last_visible = is_visible(); } break; diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp index 2c533cb36d..ebf0f5a91f 100644 --- a/modules/csg/editor/csg_gizmos.cpp +++ b/modules/csg/editor/csg_gizmos.cpp @@ -35,12 +35,15 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" /////////// CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() { + helper.instantiate(); + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15)); create_material("shape_union_material", gizmo_color); create_material("shape_union_solid_material", gizmo_color); @@ -56,6 +59,9 @@ CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() { create_handle_material("handles"); } +CSGShape3DGizmoPlugin::~CSGShape3DGizmoPlugin() { +} + String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); @@ -64,7 +70,7 @@ String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, } if (Object::cast_to<CSGBox3D>(cs)) { - return "Size"; + return helper->box_get_handle_name(p_id); } if (Object::cast_to<CSGCylinder3D>(cs)) { @@ -104,17 +110,15 @@ Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo return Variant(); } +void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { + helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); +} + void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); - Transform3D gt = cs->get_global_transform(); - //gt.orthonormalize(); - Transform3D gi = gt.affine_inverse(); - - Vector3 ray_from = p_camera->project_ray_origin(p_point); - Vector3 ray_dir = p_camera->project_ray_normal(p_point); - - Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) }; + Vector3 sg[2]; + helper->get_segment(p_camera, p_point, sg); if (Object::cast_to<CSGSphere3D>(cs)) { CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); @@ -135,29 +139,11 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i if (Object::cast_to<CSGBox3D>(cs)) { CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); - - Vector3 axis; - axis[p_id] = 1.0; - Vector3 ra, rb; - Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); - float d = ra[p_id]; - - if (Math::is_nan(d)) { - // The handle is perpendicular to the camera. - return; - } - - if (Node3DEditor::get_singleton()->is_snap_enabled()) { - d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); - } - - if (d < 0.001) { - d = 0.001; - } - - Vector3 h = s->get_size(); - h[p_id] = d * 2; - s->set_size(h); + Vector3 size = s->get_size(); + Vector3 position; + helper->box_set_handle(sg, p_id, size, position); + s->set_size(size); + s->set_global_position(position); } if (Object::cast_to<CSGCylinder3D>(cs)) { @@ -225,17 +211,7 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int } if (Object::cast_to<CSGBox3D>(cs)) { - CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); - if (p_cancel) { - s->set_size(p_restore); - return; - } - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Change Box Shape Size")); - ur->add_do_method(s, "set_size", s->get_size()); - ur->add_undo_method(s, "set_size", p_restore); - ur->commit_action(); + helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs); } if (Object::cast_to<CSGCylinder3D>(cs)) { @@ -394,15 +370,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<CSGBox3D>(cs)) { CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); - - Vector<Vector3> handles; - - for (int i = 0; i < 3; i++) { - Vector3 h; - h[i] = s->get_size()[i] / 2; - handles.push_back(h); - } - + Vector<Vector3> handles = helper->box_get_handles(s->get_size()); p_gizmo->add_handles(handles, handles_material); } diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h index deac1d428d..6281db0a21 100644 --- a/modules/csg/editor/csg_gizmos.h +++ b/modules/csg/editor/csg_gizmos.h @@ -38,9 +38,13 @@ #include "editor/editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" +class Gizmo3DHelper; + class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin); + Ref<Gizmo3DHelper> helper; + public: virtual bool has_gizmo(Node3D *p_spatial) override; virtual String get_gizmo_name() const override; @@ -50,10 +54,12 @@ public: virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override; virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override; CSGShape3DGizmoPlugin(); + ~CSGShape3DGizmoPlugin(); }; class EditorPluginCSG : public EditorPlugin { diff --git a/modules/csg/icons/CSGBox3D.svg b/modules/csg/icons/CSGBox3D.svg index bb3a1f357a..d425180cf5 100644 --- a/modules/csg/icons/CSGBox3D.svg +++ b/modules/csg/icons/CSGBox3D.svg @@ -1 +1 @@ -<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> diff --git a/modules/csg/icons/CSGCapsule3D.svg b/modules/csg/icons/CSGCapsule3D.svg index 33a2d4a115..3c2657999c 100644 --- a/modules/csg/icons/CSGCapsule3D.svg +++ b/modules/csg/icons/CSGCapsule3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> diff --git a/modules/csg/icons/CSGCylinder3D.svg b/modules/csg/icons/CSGCylinder3D.svg index 29d658dc46..19e48b4dba 100644 --- a/modules/csg/icons/CSGCylinder3D.svg +++ b/modules/csg/icons/CSGCylinder3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" stroke-width="2" fill="none" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" stroke-width="2" fill="none" stroke="#fc7f7f" mask="url(#a)"/></svg> diff --git a/modules/csg/icons/CSGPolygon3D.svg b/modules/csg/icons/CSGPolygon3D.svg index 8d4b61e039..090047248b 100644 --- a/modules/csg/icons/CSGPolygon3D.svg +++ b/modules/csg/icons/CSGPolygon3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" fill="none" stroke-linejoin="round" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" fill="none" stroke-linejoin="round" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> diff --git a/modules/csg/icons/CSGSphere3D.svg b/modules/csg/icons/CSGSphere3D.svg index 16d45b3c99..a677ffaf5c 100644 --- a/modules/csg/icons/CSGSphere3D.svg +++ b/modules/csg/icons/CSGSphere3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><path d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" fill="none" stroke-width="2" stroke="#fc7f7f" mask="url(#a)"/></svg> diff --git a/modules/csg/icons/CSGTorus3D.svg b/modules/csg/icons/CSGTorus3D.svg index 5672244e5c..60c56bd1ca 100644 --- a/modules/csg/icons/CSGTorus3D.svg +++ b/modules/csg/icons/CSGTorus3D.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fff"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><ellipse cx="8" cy="7.5" fill="none" rx="6" ry="3.5" stroke="#fc7f7f" stroke-width="2" mask="url(#a)"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><mask id="a"><path d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z" fill="#fefefe"/></mask><path d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z" fill="#5fb2ff"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z" stroke-width="2"/><path d="M6.2 7.2a2 1 0 1 0 3.6 0" stroke-width="1.75" stroke-linecap="round"/></g></svg> diff --git a/modules/denoise/SCsub b/modules/denoise/SCsub deleted file mode 100644 index 967a511e1e..0000000000 --- a/modules/denoise/SCsub +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python - -import resource_to_cpp - -Import("env") -Import("env_modules") - -env_oidn = env_modules.Clone() - -# Thirdparty source files - -thirdparty_obj = [] - -thirdparty_dir = "#thirdparty/oidn/" -thirdparty_sources = [ - "core/api.cpp", - "core/device.cpp", - "core/filter.cpp", - "core/network.cpp", - "core/autoencoder.cpp", - "core/transfer_function.cpp", - "weights/rtlightmap_hdr.gen.cpp", - "mkl-dnn/src/common/batch_normalization.cpp", - "mkl-dnn/src/common/concat.cpp", - "mkl-dnn/src/common/convolution.cpp", - "mkl-dnn/src/common/convolution_pd.cpp", - "mkl-dnn/src/common/deconvolution.cpp", - "mkl-dnn/src/common/eltwise.cpp", - "mkl-dnn/src/common/engine.cpp", - "mkl-dnn/src/common/inner_product.cpp", - "mkl-dnn/src/common/inner_product_pd.cpp", - "mkl-dnn/src/common/lrn.cpp", - "mkl-dnn/src/common/memory.cpp", - "mkl-dnn/src/common/memory_desc_wrapper.cpp", - "mkl-dnn/src/common/mkldnn_debug.cpp", - "mkl-dnn/src/common/mkldnn_debug_autogenerated.cpp", - "mkl-dnn/src/common/pooling.cpp", - "mkl-dnn/src/common/primitive.cpp", - "mkl-dnn/src/common/primitive_attr.cpp", - "mkl-dnn/src/common/primitive_desc.cpp", - "mkl-dnn/src/common/primitive_exec_types.cpp", - "mkl-dnn/src/common/primitive_iterator.cpp", - "mkl-dnn/src/common/query.cpp", - "mkl-dnn/src/common/reorder.cpp", - "mkl-dnn/src/common/rnn.cpp", - "mkl-dnn/src/common/scratchpad.cpp", - "mkl-dnn/src/common/shuffle.cpp", - "mkl-dnn/src/common/softmax.cpp", - "mkl-dnn/src/common/stream.cpp", - "mkl-dnn/src/common/sum.cpp", - "mkl-dnn/src/common/utils.cpp", - "mkl-dnn/src/common/verbose.cpp", - "mkl-dnn/src/cpu/cpu_barrier.cpp", - "mkl-dnn/src/cpu/cpu_concat.cpp", - "mkl-dnn/src/cpu/cpu_engine.cpp", - "mkl-dnn/src/cpu/cpu_memory.cpp", - "mkl-dnn/src/cpu/cpu_reducer.cpp", - "mkl-dnn/src/cpu/cpu_reorder.cpp", - "mkl-dnn/src/cpu/cpu_sum.cpp", - "mkl-dnn/src/cpu/jit_avx2_conv_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_avx2_convolution.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_conv_kernel.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_conv_winograd_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_convolution.cpp", - "mkl-dnn/src/cpu/jit_avx512_common_convolution_winograd.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_2x3.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3.cpp", - "mkl-dnn/src/cpu/jit_avx512_core_fp32_wino_conv_4x3_kernel.cpp", - "mkl-dnn/src/cpu/jit_sse42_conv_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_sse42_convolution.cpp", - "mkl-dnn/src/cpu/jit_transpose_src_utils.cpp", - "mkl-dnn/src/cpu/jit_uni_eltwise.cpp", - "mkl-dnn/src/cpu/jit_uni_pool_kernel_f32.cpp", - "mkl-dnn/src/cpu/jit_uni_pooling.cpp", - "mkl-dnn/src/cpu/jit_uni_reorder.cpp", - "mkl-dnn/src/cpu/jit_uni_reorder_utils.cpp", - "mkl-dnn/src/cpu/jit_utils/jit_utils.cpp", - "mkl-dnn/src/cpu/jit_utils/jitprofiling/jitprofiling.c", - "common/platform.cpp", - "common/thread.cpp", - "common/tensor.cpp", -] -thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - -thirdparty_include_dirs = [ - "", - "include", - "mkl-dnn/include", - "mkl-dnn/src", - "mkl-dnn/src/common", - "mkl-dnn/src/cpu/xbyak", - "mkl-dnn/src/cpu", -] -thirdparty_include_dirs = [thirdparty_dir + file for file in thirdparty_include_dirs] - - -env_oidn.Prepend(CPPPATH=thirdparty_include_dirs) -env_oidn.Append( - CPPDEFINES=[ - "MKLDNN_THR=MKLDNN_THR_SEQ", - "OIDN_STATIC_LIB", - "__STDC_CONSTANT_MACROS", - "__STDC_LIMIT_MACROS", - "DISABLE_VERBOSE", - "MKLDNN_ENABLE_CONCURRENT_EXEC", - ] -) -env_oidn.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds. - -env_thirdparty = env_oidn.Clone() -env_thirdparty.disable_warnings() - -if env["disable_exceptions"]: - # OIDN hard-requires exceptions, so we re-enable them here. - if env.msvc and ("_HAS_EXCEPTIONS", 0) in env_thirdparty["CPPDEFINES"]: - env_thirdparty["CPPDEFINES"].remove(("_HAS_EXCEPTIONS", 0)) - env_thirdparty.AppendUnique(CCFLAGS=["/EHsc"]) - elif not env.msvc and "-fno-exceptions" in env_thirdparty["CCFLAGS"]: - env_thirdparty["CCFLAGS"].remove("-fno-exceptions") - -env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) -env.modules_sources += thirdparty_obj - -weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" -weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" - -env_thirdparty.Depends(weights_out_path, weights_in_path) -env_thirdparty.CommandNoCache(weights_out_path, weights_in_path, resource_to_cpp.tza_to_cpp) - -# Godot source files - -module_obj = [] - -env_oidn.add_source_files(module_obj, "*.cpp") -env.modules_sources += module_obj - -# Needed to force rebuilding the module files when the thirdparty library is updated. -env.Depends(module_obj, thirdparty_obj) diff --git a/modules/denoise/config.py b/modules/denoise/config.py deleted file mode 100644 index 27d2ffbf86..0000000000 --- a/modules/denoise/config.py +++ /dev/null @@ -1,12 +0,0 @@ -def can_build(env, platform): - # Thirdparty dependency OpenImage Denoise includes oneDNN library - # and the version we use only supports x86_64. - # It's also only relevant for tools build and desktop platforms, - # as doing lightmap generation and denoising on Android or Web - # would be a bit far-fetched. - desktop_platforms = ["linuxbsd", "macos", "windows"] - return env.editor_build and platform in desktop_platforms and env["arch"] == "x86_64" - - -def configure(env): - pass diff --git a/modules/denoise/denoise_wrapper.h b/modules/denoise/denoise_wrapper.h deleted file mode 100644 index d4bf154a5d..0000000000 --- a/modules/denoise/denoise_wrapper.h +++ /dev/null @@ -1,38 +0,0 @@ -/**************************************************************************/ -/* denoise_wrapper.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 DENOISE_WRAPPER_H -#define DENOISE_WRAPPER_H - -void *oidn_denoiser_init(); -bool oidn_denoise(void *device, float *p_floats, int p_width, int p_height); -void oidn_denoiser_finish(void *device); - -#endif // DENOISE_WRAPPER_H diff --git a/modules/denoise/register_types.cpp b/modules/denoise/register_types.cpp deleted file mode 100644 index a4264b07c5..0000000000 --- a/modules/denoise/register_types.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/**************************************************************************/ -/* register_types.cpp */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 "register_types.h" - -#include "lightmap_denoiser.h" - -#include "core/config/engine.h" - -void initialize_denoise_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } - - LightmapDenoiserOIDN::make_default_denoiser(); -} - -void uninitialize_denoise_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { - return; - } -} diff --git a/modules/denoise/register_types.h b/modules/denoise/register_types.h deleted file mode 100644 index 239877a5c7..0000000000 --- a/modules/denoise/register_types.h +++ /dev/null @@ -1,39 +0,0 @@ -/**************************************************************************/ -/* register_types.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 DENOISE_REGISTER_TYPES_H -#define DENOISE_REGISTER_TYPES_H - -#include "modules/register_module_types.h" - -void initialize_denoise_module(ModuleInitializationLevel p_level); -void uninitialize_denoise_module(ModuleInitializationLevel p_level); - -#endif // DENOISE_REGISTER_TYPES_H diff --git a/modules/denoise/resource_to_cpp.py b/modules/denoise/resource_to_cpp.py deleted file mode 100644 index a89eda9117..0000000000 --- a/modules/denoise/resource_to_cpp.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python - -## ======================================================================== ## -## Copyright 2009-2019 Intel Corporation ## -## ## -## Licensed under the Apache License, Version 2.0 (the "License"); ## -## you may not use this file except in compliance with the License. ## -## You may obtain a copy of the License at ## -## ## -## http://www.apache.org/licenses/LICENSE-2.0 ## -## ## -## Unless required by applicable law or agreed to in writing, software ## -## distributed under the License is distributed on an "AS IS" BASIS, ## -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## -## See the License for the specific language governing permissions and ## -## limitations under the License. ## -## ======================================================================== ## - -import os -from array import array - - -# Generates a C++ file from the specified binary resource file -def generate(in_path, out_path): - namespace = "oidn::weights" - scopes = namespace.split("::") - - file_name = os.path.basename(in_path) - var_name = os.path.splitext(file_name)[0] - - with open(in_path, "rb") as in_file, open(out_path, "w") as out_file: - # Header - out_file.write("// Generated from: %s\n" % file_name) - out_file.write("#include <cstddef>\n\n") - - # Open the namespaces - for s in scopes: - out_file.write("namespace %s {\n" % s) - if scopes: - out_file.write("\n") - - # Read the file - in_data = array("B", in_file.read()) - - # Write the size - out_file.write("//const size_t %s_size = %d;\n\n" % (var_name, len(in_data))) - - # Write the data - out_file.write("unsigned char %s[] = {" % var_name) - for i in range(len(in_data)): - c = in_data[i] - if i > 0: - out_file.write(",") - if (i + 1) % 20 == 1: - out_file.write("\n") - out_file.write("%d" % c) - out_file.write("\n};\n") - - # Close the namespaces - if scopes: - out_file.write("\n") - for scope in reversed(scopes): - out_file.write("} // namespace %s\n" % scope) - - -def tza_to_cpp(target, source, env): - for x in zip(source, target): - generate(str(x[0]), str(x[1])) diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index 1f2964522b..5ca1ec2228 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -186,7 +186,7 @@ A peer has disconnected. This event is generated on a successful completion of a disconnect initiated by [method ENetPacketPeer.peer_disconnect], if a peer has timed out, or if a connection request initialized by [method connect_to_host] has timed out. The array will contain the peer which disconnected. The data field contains user supplied data describing the disconnection, or 0, if none is available. </constant> <constant name="EVENT_RECEIVE" value="3" enum="EventType"> - A packet has been received from a peer. The array will contain the peer which sent the packet, the channel number upon which the packet was received, and the received packet. + A packet has been received from a peer. The array will contain the peer which sent the packet and the channel number upon which the packet was received. The received packet will be queued to the associated [ENetPacketPeer]. </constant> <constant name="HOST_TOTAL_SENT_DATA" value="0" enum="HostStatistic"> Total data sent. diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp index 88aaa006b5..2ccfd5d326 100644 --- a/modules/enet/enet_connection.cpp +++ b/modules/enet/enet_connection.cpp @@ -37,7 +37,7 @@ #include "core/variant/typed_array.h" void ENetConnection::broadcast(enet_uint8 p_channel, ENetPacket *p_packet) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_MSG(p_channel >= host->channelLimit, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)host->channelLimit)); enet_host_broadcast(host, p_channel, p_packet); } @@ -71,7 +71,7 @@ Error ENetConnection::create_host(int p_max_peers, int p_max_channels, int p_in_ } void ENetConnection::destroy() { - ERR_FAIL_COND_MSG(!host, "Host already destroyed"); + ERR_FAIL_NULL_MSG(host, "Host already destroyed."); for (List<Ref<ENetPacketPeer>>::Element *E = peers.front(); E; E = E->next()) { E->get()->_on_disconnect(); } @@ -82,7 +82,7 @@ void ENetConnection::destroy() { Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int p_port, int p_channels, int p_data) { Ref<ENetPacketPeer> out; - ERR_FAIL_COND_V_MSG(!host, out, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, out, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V_MSG(peers.size(), out, "The ENetConnection is already connected to a peer."); ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, out, "The remote port number must be between 1 and 65535 (inclusive)."); @@ -160,7 +160,7 @@ ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event, } ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) { - ERR_FAIL_COND_V_MSG(!host, EVENT_ERROR, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, EVENT_ERROR, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(r_event.peer.is_valid(), EVENT_ERROR); // Drop peers that have already been disconnected. @@ -186,7 +186,7 @@ ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) } int ENetConnection::check_events(EventType &r_type, Event &r_event) { - ERR_FAIL_COND_V_MSG(!host, -1, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, -1, "The ENetConnection instance isn't currently active."); ENetEvent event; int ret = enet_host_check_events(host, &event); if (ret < 0) { @@ -198,32 +198,32 @@ int ENetConnection::check_events(EventType &r_type, Event &r_event) { } void ENetConnection::flush() { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_flush(host); } void ENetConnection::bandwidth_limit(int p_in_bandwidth, int p_out_bandwidth) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_bandwidth_limit(host, p_in_bandwidth, p_out_bandwidth); } void ENetConnection::channel_limit(int p_max_channels) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_channel_limit(host, p_max_channels); } void ENetConnection::bandwidth_throttle() { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_bandwidth_throttle(host); } void ENetConnection::compress(CompressionMode p_mode) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); Compressor::setup(host, p_mode); } double ENetConnection::pop_statistic(HostStatistic p_stat) { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); uint32_t *ptr = nullptr; switch (p_stat) { case HOST_TOTAL_SENT_DATA: @@ -239,20 +239,20 @@ double ENetConnection::pop_statistic(HostStatistic p_stat) { ptr = &(host->totalReceivedPackets); break; } - ERR_FAIL_COND_V_MSG(ptr == nullptr, 0, "Invalid statistic: " + itos(p_stat)); + ERR_FAIL_NULL_V_MSG(ptr, 0, "Invalid statistic: " + itos(p_stat) + "."); uint32_t ret = *ptr; *ptr = 0; return ret; } int ENetConnection::get_max_channels() const { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); return host->channelLimit; } int ENetConnection::get_local_port() const { - ERR_FAIL_COND_V_MSG(!host, 0, "The ENetConnection instance isn't currently active."); - ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound"); + ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound."); ENetAddress address; ERR_FAIL_COND_V_MSG(enet_socket_get_address(host->socket, &address), 0, "Unable to get socket address"); return address.port; @@ -265,7 +265,7 @@ void ENetConnection::get_peers(List<Ref<ENetPacketPeer>> &r_peers) { } TypedArray<ENetPacketPeer> ENetConnection::_get_peers() { - ERR_FAIL_COND_V_MSG(!host, Array(), "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, Array(), "The ENetConnection instance isn't currently active."); TypedArray<ENetPacketPeer> out; for (const Ref<ENetPacketPeer> &I : peers) { out.push_back(I); @@ -275,7 +275,7 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() { Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET - ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER); return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else @@ -285,7 +285,7 @@ Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) { void ENetConnection::refuse_new_connections(bool p_refuse) { #ifdef GODOT_ENET - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); enet_host_refuse_new_connections(host, p_refuse); #else ERR_FAIL_MSG("ENet DTLS support not available in this build."); @@ -294,7 +294,7 @@ void ENetConnection::refuse_new_connections(bool p_refuse) { Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) { #ifdef GODOT_ENET - ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER); return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK; #else @@ -315,7 +315,7 @@ Error ENetConnection::_create(ENetAddress *p_address, int p_max_peers, int p_max p_in_bandwidth /* limit incoming bandwidth if > 0 */, p_out_bandwidth /* limit outgoing bandwidth if > 0 */); - ERR_FAIL_COND_V_MSG(!host, ERR_CANT_CREATE, "Couldn't create an ENet host."); + ERR_FAIL_NULL_V_MSG(host, ERR_CANT_CREATE, "Couldn't create an ENet host."); return OK; } @@ -335,7 +335,7 @@ Array ENetConnection::_service(int p_timeout) { } void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_flags) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); ERR_FAIL_COND_MSG(p_channel < 0 || p_channel > (int)host->channelLimit, "Invalid channel"); ERR_FAIL_COND_MSG(p_flags & ~ENetPacketPeer::FLAG_ALLOWED, "Invalid flags"); ENetPacket *pkt = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags); @@ -343,8 +343,8 @@ void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_f } void ENetConnection::socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet) { - ERR_FAIL_COND_MSG(!host, "The ENetConnection instance isn't currently active."); - ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound"); + ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active."); + ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound."); ERR_FAIL_COND_MSG(p_port < 1 || p_port > 65535, "The remote port number must be between 1 and 65535 (inclusive)."); IPAddress ip; @@ -497,7 +497,7 @@ size_t ENetConnection::Compressor::enet_decompress(void *context, const enet_uin } void ENetConnection::Compressor::setup(ENetHost *p_host, CompressionMode p_mode) { - ERR_FAIL_COND(!p_host); + ERR_FAIL_NULL(p_host); switch (p_mode) { case COMPRESS_NONE: { enet_host_compress(p_host, nullptr); diff --git a/modules/enet/enet_packet_peer.cpp b/modules/enet/enet_packet_peer.cpp index a131841a07..f2bf5337ee 100644 --- a/modules/enet/enet_packet_peer.cpp +++ b/modules/enet/enet_packet_peer.cpp @@ -31,51 +31,51 @@ #include "enet_packet_peer.h" void ENetPacketPeer::peer_disconnect(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect(peer, p_data); } void ENetPacketPeer::peer_disconnect_later(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect_later(peer, p_data); } void ENetPacketPeer::peer_disconnect_now(int p_data) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_disconnect_now(peer, p_data); _on_disconnect(); } void ENetPacketPeer::ping() { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_ping(peer); } void ENetPacketPeer::ping_interval(int p_interval) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); enet_peer_ping_interval(peer, p_interval); } int ENetPacketPeer::send(uint8_t p_channel, ENetPacket *p_packet) { - ERR_FAIL_COND_V(peer == nullptr, -1); - ERR_FAIL_COND_V(p_packet == nullptr, -1); + ERR_FAIL_NULL_V(peer, -1); + ERR_FAIL_NULL_V(p_packet, -1); ERR_FAIL_COND_V_MSG(p_channel >= peer->channelCount, -1, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)peer->channelCount)); return enet_peer_send(peer, p_channel, p_packet); } void ENetPacketPeer::reset() { - ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); + ERR_FAIL_NULL_MSG(peer, "Peer not connected."); enet_peer_reset(peer); _on_disconnect(); } void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int p_deceleration) { - ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); + ERR_FAIL_NULL_MSG(peer, "Peer not connected."); enet_peer_throttle_configure(peer, p_interval, p_acceleration, p_deceleration); } void ENetPacketPeer::set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max) { - ERR_FAIL_COND_MSG(peer == nullptr, "Peer not connected"); + ERR_FAIL_NULL_MSG(peer, "Peer not connected."); ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less than maximum timeout"); enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max); } @@ -89,7 +89,7 @@ int ENetPacketPeer::get_available_packet_count() const { } Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED); ERR_FAIL_COND_V(!packet_queue.size(), ERR_UNAVAILABLE); if (last_packet) { enet_packet_destroy(last_packet); @@ -103,13 +103,13 @@ Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } Error ENetPacketPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - ERR_FAIL_COND_V(!peer, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED); ENetPacket *packet = enet_packet_create(p_buffer, p_buffer_size, ENET_PACKET_FLAG_RELIABLE); return send(0, packet) < 0 ? FAILED : OK; } IPAddress ENetPacketPeer::get_remote_address() const { - ERR_FAIL_COND_V(!peer, IPAddress()); + ERR_FAIL_NULL_V(peer, IPAddress()); IPAddress out; #ifdef GODOT_ENET out.set_ipv6((uint8_t *)&(peer->address.host)); @@ -120,7 +120,7 @@ IPAddress ENetPacketPeer::get_remote_address() const { } int ENetPacketPeer::get_remote_port() const { - ERR_FAIL_COND_V(!peer, 0); + ERR_FAIL_NULL_V(peer, 0); return peer->address.port; } @@ -129,7 +129,7 @@ bool ENetPacketPeer::is_active() const { } double ENetPacketPeer::get_statistic(PeerStatistic p_stat) { - ERR_FAIL_COND_V(!peer, 0); + ERR_FAIL_NULL_V(peer, 0); switch (p_stat) { case PEER_PACKET_LOSS: return peer->packetLoss; @@ -171,7 +171,7 @@ ENetPacketPeer::PeerState ENetPacketPeer::get_state() const { } int ENetPacketPeer::get_channels() const { - ERR_FAIL_COND_V_MSG(!peer, 0, "The ENetConnection instance isn't currently active."); + ERR_FAIL_NULL_V_MSG(peer, 0, "The ENetConnection instance isn't currently active."); return peer->channelCount; } @@ -183,12 +183,12 @@ void ENetPacketPeer::_on_disconnect() { } void ENetPacketPeer::_queue_packet(ENetPacket *p_packet) { - ERR_FAIL_COND(!peer); + ERR_FAIL_NULL(peer); packet_queue.push_back(p_packet); } Error ENetPacketPeer::_send(int p_channel, PackedByteArray p_packet, int p_flags) { - ERR_FAIL_COND_V_MSG(peer == nullptr, ERR_UNCONFIGURED, "Peer not connected"); + ERR_FAIL_NULL_V_MSG(peer, ERR_UNCONFIGURED, "Peer not connected."); ERR_FAIL_COND_V_MSG(p_channel < 0 || p_channel > (int)peer->channelCount, ERR_INVALID_PARAMETER, "Invalid channel"); ERR_FAIL_COND_V_MSG(p_flags & ~FLAG_ALLOWED, ERR_INVALID_PARAMETER, "Invalid flags"); ENetPacket *packet = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags); diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig new file mode 100644 index 0000000000..640c205093 --- /dev/null +++ b/modules/gdscript/.editorconfig @@ -0,0 +1,8 @@ +[*.gd] +indent_style = tab +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.out] +insert_final_newline = true diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 1dc4768186..61accd4fc9 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -19,6 +19,8 @@ if env.editor_build: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + # Also needed in main env to unexpose --lsp-port option. + env.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) if env["tests"]: diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 4f1a256ec9..3da6bcf10c 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -57,11 +57,12 @@ [/codeblock] </description> </method> - <method name="convert"> + <method name="convert" is_deprecated="true"> <return type="Variant" /> <param index="0" name="what" type="Variant" /> <param index="1" name="type" type="int" /> <description> + [i]Deprecated.[/i] Use [method @GlobalScope.type_convert] instead. Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values. [codeblock] var a = [4, 2.5, 1.2] @@ -284,11 +285,36 @@ <description> Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property, use the type hint notation. [codeblock] + extends Node + + enum Direction {LEFT, RIGHT, UP, DOWN} + + # Built-in types. @export var string = "" @export var int_number = 5 @export var float_number: float = 5 + + # Enums. + @export var type: Variant.Type + @export var format: Image.Format + @export var direction: Direction + + # Resources. @export var image: Image + @export var custom_resource: CustomResource + + # Nodes. + @export var node: Node + @export var custom_node: CustomNode + + # Typed arrays. + @export var int_array: Array[int] + @export var direction_array: Array[Direction] + @export var image_array: Array[Image] + @export var node_array: Array[Node] [/codeblock] + [b]Note:[/b] Custom resources and nodes must be registered as global classes using [code]class_name[/code]. + [b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations. </description> </annotation> <annotation name="@export_category"> @@ -556,7 +582,7 @@ <description> Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. - Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. [codeblock] @export_range(0, 20) var number @@ -566,7 +592,7 @@ @export_range(0, 100, 1, "or_greater") var power_percent @export_range(0, 100, 1, "or_greater", "or_less") var health_delta - @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians + @export_range(-180, 180, 0.001, "radians_as_degrees") var angle_radians @export_range(0, 360, 1, "degrees") var angle_degrees @export_range(-8, 8, 2, "suffix:px") var target_offset [/codeblock] @@ -600,8 +626,8 @@ @icon("res://path/to/class/icon.svg") [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. - [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. - [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported). + [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). </description> </annotation> <annotation name="@onready"> @@ -652,7 +678,7 @@ @tool extends Node [/codeblock] - [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 1aecfc6de1..c3979dd290 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -32,14 +32,11 @@ #include "../gdscript.h" -using GDP = GDScriptParser; -using GDType = GDP::DataType; - -static String _get_script_path(const String &p_path) { +String GDScriptDocGen::_get_script_path(const String &p_path) { return p_path.trim_prefix("res://").quote(); } -static String _get_class_name(const GDP::ClassNode &p_class) { +String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) { const GDP::ClassNode *curr_class = &p_class; if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class. return _get_script_path(curr_class->fqcn); @@ -56,7 +53,7 @@ static String _get_class_name(const GDP::ClassNode &p_class) { return full_name; } -static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false) { +void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) { if (!p_gdtype.is_hard_type()) { r_type = "Variant"; return; @@ -82,12 +79,21 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String r_type = Variant::get_type_name(p_gdtype.builtin_type); return; case GDType::NATIVE: + if (p_gdtype.is_meta_type) { + //r_type = GDScriptNativeClass::get_class_static(); + r_type = "Object"; // "GDScriptNativeClass" refers to a blank page. + return; + } r_type = p_gdtype.native_type; return; case GDType::SCRIPT: + if (p_gdtype.is_meta_type) { + r_type = p_gdtype.script_type.is_valid() ? p_gdtype.script_type->get_class() : Script::get_class_static(); + return; + } if (p_gdtype.script_type.is_valid()) { if (p_gdtype.script_type->get_global_name() != StringName()) { - r_type = _get_script_path(p_gdtype.script_type->get_global_name()); + r_type = p_gdtype.script_type->get_global_name(); return; } if (!p_gdtype.script_type->get_path().is_empty()) { @@ -102,9 +108,17 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String r_type = "Object"; return; case GDType::CLASS: + if (p_gdtype.is_meta_type) { + r_type = GDScript::get_class_static(); + return; + } r_type = _get_class_name(*p_gdtype.class_type); return; case GDType::ENUM: + if (p_gdtype.is_meta_type) { + r_type = "Dictionary"; + return; + } r_type = "int"; r_enum = String(p_gdtype.native_type).replace("::", "."); if (r_enum.begins_with("res://")) { @@ -123,16 +137,100 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String } } +String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_recursion_level) { + constexpr int MAX_RECURSION_LEVEL = 2; + + switch (p_variant.get_type()) { + case Variant::STRING: + return String(p_variant).c_escape().quote(); + case Variant::OBJECT: + return "<Object>"; + case Variant::DICTIONARY: { + const Dictionary dict = p_variant; + + if (dict.is_empty()) { + return "{}"; + } + + if (p_recursion_level > MAX_RECURSION_LEVEL) { + return "{...}"; + } + + List<Variant> keys; + dict.get_key_list(&keys); + keys.sort(); + + String data; + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + if (E->prev()) { + data += ", "; + } + data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); + } + + return "{" + data + "}"; + } break; + case Variant::ARRAY: { + const Array array = p_variant; + String result; + + if (array.get_typed_builtin() != Variant::NIL) { + result += "Array["; + + Ref<Script> script = array.get_typed_script(); + if (script.is_valid()) { + if (script->get_global_name() != StringName()) { + result += script->get_global_name(); + } else if (!script->get_path().get_file().is_empty()) { + result += script->get_path().get_file(); + } else { + result += array.get_typed_class_name(); + } + } else if (array.get_typed_class_name() != StringName()) { + result += array.get_typed_class_name(); + } else { + result += Variant::get_type_name((Variant::Type)array.get_typed_builtin()); + } + + result += "]("; + } + + if (array.is_empty()) { + result += "[]"; + } else if (p_recursion_level > MAX_RECURSION_LEVEL) { + result += "[...]"; + } else { + result += "["; + for (int i = 0; i < array.size(); i++) { + if (i > 0) { + result += ", "; + } + result += _docvalue_from_variant(array[i], p_recursion_level + 1); + } + result += "]"; + } + + if (array.get_typed_builtin() != Variant::NIL) { + result += ")"; + } + + return result; + } break; + default: + return p_variant.get_construct_string(); + } +} + void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { p_script->_clear_doc(); DocData::ClassDoc &doc = p_script->doc; doc.script_path = _get_script_path(p_script->get_script_path()); - if (p_script->name.is_empty()) { + if (p_script->local_name == StringName()) { doc.name = doc.script_path; } else { - doc.name = p_script->name; + doc.name = p_script->local_name; } if (p_script->_owner) { @@ -183,7 +281,10 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[const_name] = m_const->start_line; DocData::ConstantDoc const_doc; - DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description); + const_doc.name = const_name; + const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value); + const_doc.is_value_valid = true; + const_doc.description = m_const->doc_data.description; const_doc.is_deprecated = m_const->doc_data.is_deprecated; const_doc.is_experimental = m_const->doc_data.is_experimental; doc.constants.push_back(const_doc); @@ -203,7 +304,11 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c method_doc.qualifiers = m_func->is_static ? "static" : ""; if (m_func->return_type) { - _doctype_from_gdtype(m_func->return_type->get_datatype(), method_doc.return_type, method_doc.return_enum, true); + // `m_func->return_type->get_datatype()` is a metatype. + _doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true); + } else if (!m_func->body->has_return) { + // If no `return` statement, then return type is `void`, not `Variant`. + method_doc.return_type = "void"; } else { method_doc.return_type = "Variant"; } @@ -214,7 +319,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration); if (p->initializer != nullptr) { if (p->initializer->is_constant) { - arg_doc.default_value = p->initializer->reduced_value.get_construct_string().replace("\n", "\\n"); + arg_doc.default_value = _docvalue_from_variant(p->initializer->reduced_value); } else { arg_doc.default_value = "<unknown>"; } @@ -283,7 +388,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c if (m_var->initializer) { if (m_var->initializer->is_constant) { - prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "\\n"); + prop_doc.default_value = _docvalue_from_variant(m_var->initializer->reduced_value); } else { prop_doc.default_value = "<unknown>"; } @@ -309,7 +414,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c for (const GDP::EnumNode::Value &val : m_enum->values) { DocData::ConstantDoc const_doc; const_doc.name = val.identifier->name; - const_doc.value = String(Variant(val.value)); + const_doc.value = _docvalue_from_variant(val.value); const_doc.is_value_valid = true; const_doc.enumeration = name; const_doc.description = val.doc_data.description; @@ -328,8 +433,11 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[name] = m_enum_val.identifier->start_line; DocData::ConstantDoc const_doc; - DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description); + const_doc.name = name; + const_doc.value = _docvalue_from_variant(m_enum_val.value); + const_doc.is_value_valid = true; const_doc.enumeration = "@unnamed_enums"; + const_doc.description = m_enum_val.doc_data.description; const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated; const_doc.is_experimental = m_enum_val.doc_data.is_experimental; doc.constants.push_back(const_doc); diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h index 3357fb680c..a326c02c5f 100644 --- a/modules/gdscript/editor/gdscript_docgen.h +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -36,8 +36,16 @@ #include "core/doc_data.h" class GDScriptDocGen { + using GDP = GDScriptParser; + using GDType = GDP::DataType; + + static String _get_script_path(const String &p_path); + static String _get_class_name(const GDP::ClassNode &p_class); + static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false); + static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1); + public: - static void generate_docs(GDScript *p_script, const GDScriptParser::ClassNode *p_class); + static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); }; #endif // GDSCRIPT_DOCGEN_H diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index e488d6e266..8dbd262b22 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_keyword = false; bool in_word = false; bool in_number = false; + bool in_raw_string = false; bool in_node_path = false; bool in_node_ref = false; bool in_annotation = false; @@ -62,11 +63,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_lambda = false; bool in_function_name = false; - bool in_function_args = false; bool in_variable_declaration = false; bool in_signal_declaration = false; bool expect_type = false; + int in_function_args = 0; + int in_function_arg_dicts = 0; + Color keyword_color; Color color; @@ -146,7 +149,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l // Check if it's the whole line. if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) { // Don't skip comments, for highlighting markers. - if (color_regions[in_region].start_key == "#") { + if (color_regions[in_region].start_key.begins_with("#")) { break; } if (from + end_key_length > line_length) { @@ -168,7 +171,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } // Don't skip comments, for highlighting markers. - if (j == line_length && color_regions[in_region].start_key != "#") { + if (j == line_length && !color_regions[in_region].start_key.begins_with("#")) { continue; } } @@ -190,7 +193,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l highlighter_info["color"] = region_color; color_map[j] = highlighter_info; - if (color_regions[in_region].start_key == "#") { + if (color_regions[in_region].start_key.begins_with("#")) { int marker_start_pos = from; int marker_len = 0; while (from <= line_length) { @@ -234,15 +237,33 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (str[from] == '\\') { - Dictionary escape_char_highlighter_info; - escape_char_highlighter_info["color"] = symbol_color; - color_map[from] = escape_char_highlighter_info; + if (!in_raw_string) { + Dictionary escape_char_highlighter_info; + escape_char_highlighter_info["color"] = symbol_color; + color_map[from] = escape_char_highlighter_info; + } from++; - Dictionary region_continue_highlighter_info; - region_continue_highlighter_info["color"] = region_color; - color_map[from + 1] = region_continue_highlighter_info; + if (!in_raw_string) { + int esc_len = 0; + if (str[from] == 'u') { + esc_len = 4; + } else if (str[from] == 'U') { + esc_len = 6; + } + for (int k = 0; k < esc_len && from < line_length - 1; k++) { + if (!is_hex_digit(str[from + 1])) { + break; + } + from++; + } + + Dictionary region_continue_highlighter_info; + region_continue_highlighter_info["color"] = region_color; + color_map[from + 1] = region_continue_highlighter_info; + } + continue; } @@ -423,7 +444,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (str[k] == '(') { in_function_name = true; - } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) { + } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) { in_variable_declaration = true; } @@ -453,15 +474,27 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (is_a_symbol) { - if (in_function_name) { - in_function_args = true; - } - - if (in_function_args && str[j] == ')') { - in_function_args = false; + if (in_function_args > 0) { + switch (str[j]) { + case '(': + in_function_args += 1; + break; + case ')': + in_function_args -= 1; + break; + case '{': + in_function_arg_dicts += 1; + break; + case '}': + in_function_arg_dicts -= 1; + break; + } + } else if (in_function_name && str[j] == '(') { + in_function_args = 1; + in_function_arg_dicts = 0; } - if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') { + if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') { expect_type = false; } @@ -469,15 +502,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l expect_type = true; } - if (in_variable_declaration || in_function_args) { + if (in_variable_declaration || in_function_args > 0) { int k = j; - // Skip space + // Skip space. while (k < line_length && is_whitespace(str[k])) { k++; } - if (str[k] == ':') { - // has type hint + if (str[k] == ':' && in_function_arg_dicts == 0) { + // Has type hint. expect_type = true; } } @@ -489,6 +522,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } + if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) { + in_raw_string = true; + } else if (in_raw_string && in_region == -1) { + in_raw_string = false; + } + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand. if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { @@ -520,7 +559,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_annotation = false; } - if (in_node_ref) { + if (in_raw_string) { + color = string_color; + } else if (in_node_ref) { next_type = NODE_REF; color = node_ref_color; } else if (in_annotation) { @@ -535,16 +576,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; - } else if (in_member_variable) { - next_type = MEMBER; - color = member_color; } else if (in_signal_declaration) { next_type = SIGNAL; - color = member_color; } else if (in_function_name) { next_type = FUNCTION; - if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { color = function_definition_color; } else { @@ -559,6 +595,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (expect_type) { next_type = TYPE; color = type_color; + } else if (in_member_variable) { + next_type = MEMBER; + color = member_color; } else { next_type = IDENTIFIER; } @@ -656,6 +695,12 @@ void GDScriptSyntaxHighlighter::_update_cache() { for (const String &E : core_types) { class_names[StringName(E)] = basetype_color; } + class_names[SNAME("Variant")] = basetype_color; + class_names[SNAME("void")] = basetype_color; + // `get_core_type_words()` doesn't return primitive types. + class_names[SNAME("bool")] = basetype_color; + class_names[SNAME("int")] = basetype_color; + class_names[SNAME("float")] = basetype_color; /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); @@ -670,6 +715,10 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } + // Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls. + reserved_keywords[SNAME("set")] = function_color; + reserved_keywords[SNAME("get")] = function_color; + /* Global functions. */ List<StringName> global_function_list; GDScriptUtilityFunctions::get_function_list(&global_function_list); @@ -691,8 +740,18 @@ void GDScriptSyntaxHighlighter::_update_cache() { add_color_region(beg, end, comment_color, end.is_empty()); } + /* Doc comments */ + const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color"); + List<String> doc_comments; + gdscript->get_doc_comment_delimiters(&doc_comments); + for (const String &doc_comment : doc_comments) { + String beg = doc_comment.get_slice(" ", 0); + String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String(); + add_color_region(beg, end, doc_comment_color, end.is_empty()); + } + /* Strings */ - const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; gdscript->get_string_delimiters(&strings); for (const String &string : strings) { @@ -848,6 +907,8 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); if (p_start_key.length() < color_regions[i].start_key.length()) { at++; + } else { + break; } } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index fe3b63d713..090857f397 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -78,6 +78,7 @@ private: Color built_in_type_color; Color number_color; Color member_color; + Color string_color; Color node_path_color; Color node_ref_color; Color annotation_color; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 064143f400..9128f104b8 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -40,7 +40,7 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin } Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) { - // Extract all translatable strings using the parsed tree from GDSriptParser. + // Extract all translatable strings using the parsed tree from GDScriptParser. // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc. // Search strings in AssignmentNode -> text = "__", tooltip_text = "__" etc. @@ -225,10 +225,15 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { assignee_name = static_cast<const GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name; } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { - assignee_name = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name; + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee); + if (subscript->is_attribute && subscript->attribute) { + assignee_name = subscript->attribute->name; + } else if (subscript->index && _is_constant_string(subscript->index)) { + assignee_name = subscript->index->reduced_value; + } } - if (assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) { + if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) { // If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string. ids->push_back(p_assignment->assigned_value->reduced_value); } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) { @@ -236,7 +241,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_assignment->assigned_value); - if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + if (!call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]); // Extract the name in "extension ; name" of PackedStringArray. diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd index b8fc8c75dc..28ab080dd2 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y += gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd index 53bc606c9a..9b0e4be4ed 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y -= gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd index b27b3e5655..547943b910 100644 --- a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd +++ b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd @@ -1,6 +1,7 @@ # meta-description: Basic plugin template + @tool -extends EditorPlugin +extends _BASE_ func _enter_tree() -> void: diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd index 556afe994b..6772ea4a26 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template + @tool -extends EditorScenePostImport +extends _BASE_ # Called by the editor when a scene has this script set as the import script in the import tab. diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd index 875afb4fc0..e8f907f43b 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template (no comments) + @tool -extends EditorScenePostImport +extends _BASE_ func _post_import(scene: Node) -> Object: diff --git a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd index fdb8550d43..fee7353f0d 100644 --- a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic editor script template + @tool -extends EditorScript +extends _BASE_ # Called when the script is executed (using File -> Run in Script Editor). diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd index c79eeb91ec..c7a999ef24 100644 --- a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -1,15 +1,16 @@ # meta-description: Base template for rich text effects @tool -class_name _CLASS_ +# Having a class name is handy for picking the effect in the Inspector. +class_name RichText_CLASS_ extends _BASE_ # To use this effect: # - Enable BBCode on a RichTextLabel. # - Register this effect on the label. -# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. -var bbcode := "_CLASS_" +# - Use [_CLASS_SNAKE_CASE_ param=2.0]hello[/_CLASS_SNAKE_CASE_] in text. +var bbcode := "_CLASS_SNAKE_CASE_" func _process_custom_fx(char_fx: CharFXTransform) -> bool: diff --git a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd index 283a95d3b4..458e22dae4 100644 --- a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd +++ b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd @@ -1,6 +1,7 @@ # meta-description: Visual shader's node plugin template @tool +# Having a class name is required for a custom node. class_name VisualShaderNode_CLASS_ extends _BASE_ @@ -17,7 +18,7 @@ func _get_description() -> String: return "" -func _get_return_icon_type() -> int: +func _get_return_icon_type() -> PortType: return PORT_TYPE_SCALAR @@ -29,7 +30,7 @@ func _get_input_port_name(port: int) -> String: return "" -func _get_input_port_type(port: int) -> int: +func _get_input_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR @@ -41,10 +42,10 @@ func _get_output_port_name(port: int) -> String: return "result" -func _get_output_port_type(port: int) -> int: +func _get_output_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR func _get_code(input_vars: Array[String], output_vars: Array[String], - mode: int, type: int) -> 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 ccbcb3ee96..6245cc85a0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -83,7 +83,7 @@ void GDScriptNativeClass::_bind_methods() { Variant GDScriptNativeClass::_new() { Object *o = instantiate(); - ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); + ERR_FAIL_NULL_V_MSG(o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); RefCounted *rc = Object::cast_to<RefCounted>(o); if (rc) { @@ -215,7 +215,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } else { owner = memnew(RefCounted); //by default, no base means use reference } - ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); + ERR_FAIL_NULL_V_MSG(owner, Variant(), "Can't inherit from a virtual class."); RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { @@ -254,7 +254,7 @@ Ref<Script> GDScript::get_base_script() const { } StringName GDScript::get_global_name() const { - return name; + return global_name; } StringName GDScript::get_instance_base_type() const { @@ -278,33 +278,16 @@ struct _GDScriptMemberSort { void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { placeholders.erase(p_placeholder); } + #endif void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) { - GDScriptFunction *func = E.value; - MethodInfo mi; - mi.name = E.key; - - if (func->is_static()) { - mi.flags |= METHOD_FLAG_STATIC; - } - - for (int i = 0; i < func->get_argument_count(); i++) { - PropertyInfo arginfo = func->get_argument_type(i); -#ifdef TOOLS_ENABLED - arginfo.name = func->get_argument_name(i); -#endif - mi.arguments.push_back(arginfo); - } -#ifdef TOOLS_ENABLED - mi.default_arguments.append_array(func->get_default_arg_values()); -#endif - mi.return_val = func->get_return_type(); - r_list->push_back(mi); + r_list->push_back(E.value->get_method_info()); } + if (!p_include_base) { return; } @@ -323,10 +306,12 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; - for (const KeyValue<StringName, PropertyInfo> &E : sptr->member_info) { + for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) { + if (!sptr->members.has(E.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(E.key)); - ms.index = sptr->member_indices[E.key].index; + ms.index = E.value.index; ms.name = E.key; msort.push_back(ms); } @@ -334,7 +319,7 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl msort.sort(); msort.reverse(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); + props.push_front(sptr->member_indices[msort[i].name].property_info); } #ifdef TOOLS_ENABLED @@ -362,21 +347,17 @@ bool GDScript::has_method(const StringName &p_method) const { return member_functions.has(p_method); } +bool GDScript::has_static_method(const StringName &p_method) const { + return member_functions.has(p_method) && member_functions[p_method]->is_static(); +} + MethodInfo GDScript::get_method_info(const StringName &p_method) const { HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method); if (!E) { return MethodInfo(); } - GDScriptFunction *func = E->value; - MethodInfo mi; - mi.name = E->key; - for (int i = 0; i < func->get_argument_count(); i++) { - mi.arguments.push_back(func->get_argument_type(i)); - } - - mi.return_val = func->get_return_type(); - return mi; + return E->value->get_method_info(); } bool GDScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { @@ -488,7 +469,7 @@ String GDScript::get_class_icon_path() const { } #endif -bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update, bool p_base_exports_changed) { #ifdef TOOLS_ENABLED static Vector<GDScript *> base_caches; @@ -497,7 +478,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc } base_caches.append(this); - bool changed = false; + bool changed = p_base_exports_changed; if (source_changed_cache) { source_changed_cache = false; @@ -557,13 +538,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc member_default_values_cache[member.variable->identifier->name] = default_value; } break; case GDScriptParser::ClassNode::Member::SIGNAL: { - // TODO: Cache this in parser to avoid loops like this. - Vector<StringName> parameters_names; - parameters_names.resize(member.signal->parameters.size()); - for (int j = 0; j < member.signal->parameters.size(); j++) { - parameters_names.write[j] = member.signal->parameters[j]->identifier->name; - } - _signals[member.signal->identifier->name] = parameters_names; + _signals[member.signal->identifier->name] = member.signal->method_info; } break; case GDScriptParser::ClassNode::Member::GROUP: { members_cache.push_back(member.annotation->export_info); @@ -630,9 +605,15 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc void GDScript::update_exports() { #ifdef TOOLS_ENABLED + _update_exports_down(false); +#endif +} +#ifdef TOOLS_ENABLED +void GDScript::_update_exports_down(bool p_base_exports_changed) { bool cyclic_error = false; - _update_exports(&cyclic_error); + bool changed = _update_exports(&cyclic_error, false, nullptr, p_base_exports_changed); + if (cyclic_error) { return; } @@ -642,14 +623,14 @@ void GDScript::update_exports() { for (const ObjectID &E : copy) { Object *id = ObjectDB::get_instance(E); GDScript *s = Object::cast_to<GDScript>(id); + if (!s) { continue; } - s->update_exports(); + s->_update_exports_down(p_base_exports_changed || changed); } - -#endif } +#endif String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { @@ -977,22 +958,26 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - List<PropertyInfo> property_list; - + List<const GDScript *> classes; const GDScript *top = this; while (top) { - for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) { - PropertyInfo pi = PropertyInfo(E.value.data_type); - pi.name = E.key; - pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property. - property_list.push_back(pi); - } - + classes.push_back(top); top = top->_base; } - for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) { - p_properties->push_back(E->get()); + for (const List<const GDScript *>::Element *E = classes.back(); E; E = E->prev()) { + Vector<_GDScriptMemberSort> msort; + for (const KeyValue<StringName, MemberInfo> &F : E->get()->static_variables_indices) { + _GDScriptMemberSort ms; + ms.index = F.value.index; + ms.name = F.key; + msort.push_back(ms); + } + msort.sort(); + + for (int i = 0; i < msort.size(); i++) { + p_properties->push_back(E->get()->static_variables_indices[msort[i].name].property_info); + } } } @@ -1110,7 +1095,7 @@ GDScript *GDScript::find_class(const String &p_qualified_name) { Vector<String> class_names; GDScript *result = nullptr; // Empty initial name means start here. - if (first.is_empty() || first == name) { + if (first.is_empty() || first == global_name) { class_names = p_qualified_name.split("::"); result = this; } else if (p_qualified_name.begins_with(get_root_script()->path)) { @@ -1245,15 +1230,8 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { } void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { - for (const KeyValue<StringName, Vector<StringName>> &E : _signals) { - MethodInfo mi; - mi.name = E.key; - for (int i = 0; i < E.value.size(); i++) { - PropertyInfo arg; - arg.name = E.value[i]; - mi.arguments.push_back(arg); - } - r_list->push_back(mi); + for (const KeyValue<StringName, MethodInfo> &E : _signals) { + r_list->push_back(E.value); } if (!p_include_base) { @@ -1274,21 +1252,6 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { _get_script_signal_list(r_signals, true); } -String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) { - ERR_FAIL_NULL_V(p_gdscript, String()); - - String class_name; - while (p_gdscript) { - if (class_name.is_empty()) { - class_name = p_gdscript->get_script_class_name(); - } else { - class_name = p_gdscript->get_script_class_name() + "." + class_name; - } - p_gdscript = p_gdscript->_owner; - } - return class_name; -} - GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { Object *obj = p_variant; if (obj == nullptr || obj->get_instance_id().is_null()) { @@ -1399,29 +1362,13 @@ void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) { } } -void GDScript::_init_rpc_methods_properties() { - // Copy the base rpc methods so we don't mask their IDs. - rpc_config.clear(); - if (base.is_valid()) { - rpc_config = base->rpc_config.duplicate(); - } - - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } -} - #ifdef DEBUG_ENABLED String GDScript::debug_get_script_name(const Ref<Script> &p_script) { if (p_script.is_valid()) { Ref<GDScript> gdscript = p_script; if (gdscript.is_valid()) { - if (!gdscript->get_script_class_name().is_empty()) { - return gdscript->get_script_class_name(); + if (gdscript->get_local_name() != StringName()) { + return gdscript->get_local_name(); } return gdscript->get_fully_qualified_name().get_file(); } @@ -1439,6 +1386,36 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) { } #endif +thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local; + +GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) { + UpdatableFuncPtrElement result = {}; + + { + MutexLock lock(func_ptrs_to_update_thread_local.mutex); + result.element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr); + result.mutex = &func_ptrs_to_update_thread_local.mutex; + + if (likely(func_ptrs_to_update_thread_local.initialized)) { + return result; + } + + func_ptrs_to_update_thread_local.initialized = true; + } + + MutexLock lock(func_ptrs_to_update_mutex); + func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local); + + return result; +} + +void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element) { + ERR_FAIL_NULL(p_func_ptr_element.element); + ERR_FAIL_NULL(p_func_ptr_element.mutex); + MutexLock lock(*p_func_ptr_element.mutex); + p_func_ptr_element.element->erase(); +} + void GDScript::clear(ClearData *p_clear_data) { if (clearing) { return; @@ -1456,6 +1433,16 @@ void GDScript::clear(ClearData *p_clear_data) { is_root = true; } + { + MutexLock outer_lock(func_ptrs_to_update_mutex); + for (UpdatableFuncPtr *updatable : func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + *func_ptr_ptr = nullptr; + } + } + } + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); for (GDScript *E : must_clear_dependencies) { clear_data->scripts.insert(E); @@ -1667,7 +1654,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } { - HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name); + HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name); if (E) { r_ret = Signal(this->owner, E->key); return true; @@ -1715,15 +1702,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - const GDScript *sptr = script.ptr(); - while (sptr) { - if (sptr->member_info.has(p_name)) { - if (r_is_valid) { - *r_is_valid = true; - } - return sptr->member_info[p_name].type; + if (script->member_indices.has(p_name)) { + if (r_is_valid) { + *r_is_valid = true; } - sptr = sptr->_base; + return script->member_indices[p_name].property_info.type; } if (r_is_valid) { @@ -1798,10 +1781,12 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const //instance a fake script for editing the values Vector<_GDScriptMemberSort> msort; - for (const KeyValue<StringName, PropertyInfo> &F : sptr->member_info) { + for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) { + if (!sptr->members.has(F.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(F.key)); - ms.index = sptr->member_indices[F.key].index; + ms.index = F.value.index; ms.name = F.key; msort.push_back(ms); } @@ -1809,7 +1794,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const msort.sort(); msort.reverse(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); + props.push_front(sptr->member_indices[msort[i].name].property_info); } #ifdef TOOLS_ENABLED @@ -1872,12 +1857,7 @@ void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) { - MethodInfo mi; - mi.name = E.key; - for (int i = 0; i < E.value->get_argument_count(); i++) { - mi.arguments.push_back(PropertyInfo(Variant::NIL, "arg" + itos(i))); - } - p_list->push_back(mi); + p_list->push_back(E.value->get_method_info()); } sptr = sptr->_base; } @@ -2439,61 +2419,60 @@ void GDScriptLanguage::frame() { /* EDITOR FUNCTIONS */ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { - // TODO: Add annotations here? + // Please keep alphabetical order within categories. static const char *_reserved_words[] = { - // operators + // Control flow. + "break", + "continue", + "elif", + "else", + "for", + "if", + "match", + "pass", + "return", + "when", + "while", + // Declarations. + "class", + "class_name", + "const", + "enum", + "extends", + "func", + "namespace", // Reserved for potential future use. + "signal", + "static", + "trait", // Reserved for potential future use. + "var", + // Other keywords. + "await", + "breakpoint", + "self", + "super", + "yield", // Reserved for potential future use. + // Operators. "and", + "as", "in", + "is", "not", "or", - // types and values + // Special values (tokenizer treats them as literals, not as tokens). "false", - "float", - "int", - "bool", "null", - "PI", - "TAU", + "true", + // Constants. "INF", "NAN", - "self", - "true", - "void", - // functions - "as", + "PI", + "TAU", + // Functions (highlighter uses global function color instead). "assert", - "await", - "breakpoint", - "class", - "class_name", - "extends", - "is", - "func", "preload", - "signal", - "super", - // var - "const", - "enum", - "static", - "var", - // control flow - "break", - "continue", - "if", - "elif", - "else", - "for", - "pass", - "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 + // Types (highlighter uses type color instead). + "void", + nullptr, }; const char **w = _reserved_words; @@ -2502,25 +2481,20 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { p_words->push_back(*w); w++; } - - List<StringName> functions; - GDScriptUtilityFunctions::get_function_list(&functions); - - for (const StringName &E : functions) { - p_words->push_back(String(E)); - } } bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { + // Please keep alphabetical order. return p_keyword == "break" || p_keyword == "continue" || p_keyword == "elif" || p_keyword == "else" || - p_keyword == "if" || p_keyword == "for" || + p_keyword == "if" || p_keyword == "match" || p_keyword == "pass" || p_keyword == "return" || + p_keyword == "when" || p_keyword == "while"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0ba007683c..04b0a1d786 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -69,6 +69,7 @@ class GDScript : public Script { StringName setter; StringName getter; GDScriptDataType data_type; + PropertyInfo property_info; }; struct ClearData { @@ -85,6 +86,8 @@ class GDScript : public Script { friend class GDScriptAnalyzer; friend class GDScriptCompiler; friend class GDScriptDocGen; + friend class GDScriptLambdaCallable; + friend class GDScriptLambdaSelfCallable; friend class GDScriptLanguage; friend struct GDScriptUtilityFunctionsDefinitions; @@ -93,16 +96,44 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - HashSet<StringName> members; //members are just indices to the instantiated script. - HashMap<StringName, Variant> constants; + // Members are just indices to the instantiated script. + HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes. + HashSet<StringName> members; // Only members of the current class. + + // Only static variables of the current class. HashMap<StringName, MemberInfo> static_variables_indices; - Vector<Variant> static_variables; + Vector<Variant> static_variables; // Static variable values. + + HashMap<StringName, Variant> constants; HashMap<StringName, GDScriptFunction *> member_functions; - HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. HashMap<StringName, Ref<GDScript>> subclasses; - HashMap<StringName, Vector<StringName>> _signals; + HashMap<StringName, MethodInfo> _signals; Dictionary rpc_config; + struct LambdaInfo { + int capture_count; + bool use_self; + }; + + HashMap<GDScriptFunction *, LambdaInfo> lambda_info; + + // List is used here because a ptr to elements are stored, so the memory locations need to be stable + struct UpdatableFuncPtr { + List<GDScriptFunction **> ptrs; + Mutex mutex; + bool initialized = false; + }; + struct UpdatableFuncPtrElement { + List<GDScriptFunction **>::Element *element = nullptr; + Mutex *mutex = nullptr; + }; + static thread_local UpdatableFuncPtr func_ptrs_to_update_thread_local; + List<UpdatableFuncPtr *> func_ptrs_to_update; + Mutex func_ptrs_to_update_mutex; + + UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr); + static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element); + #ifdef TOOLS_ENABLED // For static data storage during hot-reloading. HashMap<StringName, MemberInfo> old_static_variables_indices; @@ -126,8 +157,6 @@ class GDScript : public Script { void _add_doc(const DocData::ClassDoc &p_inner_class); #endif - HashMap<StringName, PropertyInfo> member_info; - GDScriptFunction *implicit_initializer = nullptr; GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate GDScriptFunction *implicit_ready = nullptr; @@ -142,7 +171,8 @@ class GDScript : public Script { //exported members String source; String path; - String name; + StringName local_name; // Inner class identifier or `class_name`. + StringName global_name; // `class_name`. String fully_qualified_name; String simplified_icon_path; SelfList<GDScript> script_list; @@ -159,24 +189,21 @@ class GDScript : public Script { HashSet<PlaceHolderScriptInstance *> placeholders; //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; + void _update_exports_down(bool p_base_exports_changed); #endif #ifdef DEBUG_ENABLED HashMap<ObjectID, List<Pair<StringName, Variant>>> pending_reload_state; #endif - bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr, bool p_base_exports_changed = false); void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data); - void _init_rpc_methods_properties(); void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; - // This method will map the class name from "RefCounted" to "MyClass.InnerClass". - static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); - GDScript *_get_gdscript_from_variant(const Variant &p_variant); void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); @@ -194,9 +221,12 @@ public: static String debug_get_script_name(const Ref<Script> &p_script); #endif + _FORCE_INLINE_ StringName get_local_name() const { return local_name; } + void clear(GDScript::ClearData *p_clear_data = nullptr); virtual bool is_valid() const override { return valid; } + virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes. bool inherits_script(const Ref<Script> &p_script) const override; @@ -214,7 +244,6 @@ public: } const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; } const Ref<GDScriptNativeClass> &get_native() const { return native; } - const String &get_script_class_name() const { return name; } RBSet<GDScript *> get_dependencies(); RBSet<GDScript *> get_inverted_dependencies(); @@ -264,6 +293,7 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const override; virtual bool has_method(const StringName &p_method) const override; + virtual bool has_static_method(const StringName &p_method) const override; virtual MethodInfo get_method_info(const StringName &p_method) const override; virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; @@ -498,13 +528,16 @@ public: virtual void get_reserved_words(List<String> *p_words) const override; virtual bool is_control_flow_keyword(String p_keywords) const override; virtual void get_comment_delimiters(List<String> *p_delimiters) const override; + virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const override; virtual void get_string_delimiters(List<String> *p_delimiters) const override; virtual bool is_using_templates() override; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override; virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override; virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override; virtual Script *create_script() const override; - virtual bool has_named_classes() const override; +#ifndef DISABLE_DEPRECATED + virtual bool has_named_classes() const override { return false; } +#endif virtual bool supports_builtin_mode() const override; virtual bool supports_documentation() const override; virtual bool can_inherit_from_file() const override { return true; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 18c69467dc..983a19470a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me return ERR_PARSE_ERROR; } - if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) { push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -386,6 +386,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = SNAME("RefCounted"); } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -459,7 +460,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { + if (Engine::get_singleton()->has_singleton(name)) { + push_error(vformat(R"(Cannot inherit native class "%s" because it is an engine singleton.)", name), id); + return ERR_PARSE_ERROR; + } base.kind = GDScriptParser::DataType::NATIVE; + base.builtin_type = Variant::OBJECT; base.native_type = name; } else { // Look for other classes in script. @@ -669,11 +675,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; - } else if (first == SNAME("Object")) { - // Object is treated like a native type, not a built-in. - result.kind = GDScriptParser::DataType::NATIVE; - result.builtin_type = Variant::OBJECT; - result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { @@ -909,7 +910,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (GDScriptParser::AnnotationNode *&E : member_node->annotations) { if (E->name == SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { @@ -932,7 +933,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { if (E->name != SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } @@ -984,7 +985,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { resolve_annotation(E); - E->apply(parser, member.constant); + E->apply(parser, member.constant, p_class); } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -1000,15 +1001,21 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, GDScriptParser::ParameterNode *param = member.signal->parameters[j]; GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier)); param->set_datatype(param_type); - mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name)); - // TODO: add signal parameter default values +#ifdef DEBUG_ENABLED + if (param->datatype_specifier == nullptr) { + parser->push_warning(param, GDScriptWarning::UNTYPED_DECLARATION, "Parameter", param->identifier->name); + } +#endif + mi.arguments.push_back(param_type.to_property_info(param->identifier->name)); + // Signals do not support parameter default values. } member.signal->set_datatype(make_signal_type(mi)); + member.signal->method_info = mi; // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { resolve_annotation(E); - E->apply(parser, member.signal); + E->apply(parser, member.signal, p_class); } } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -1056,13 +1063,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { resolve_annotation(E); - E->apply(parser, member.m_enum); + E->apply(parser, member.m_enum, p_class); } } break; case GDScriptParser::ClassNode::Member::FUNCTION: for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_signature(member.function, p_source); break; @@ -1272,23 +1279,21 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_body(member.function); } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) { if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (member.variable->getter != nullptr) { - member.variable->getter->set_datatype(member.variable->datatype); + member.variable->getter->return_type = member.variable->datatype_specifier; + member.variable->getter->set_datatype(member.get_datatype()); resolve_function_body(member.variable->getter); } if (member.variable->setter != nullptr) { - resolve_function_signature(member.variable->setter); - - if (member.variable->setter->parameters.size() > 0) { - member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; - member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); - } + ERR_CONTINUE(member.variable->setter->parameters.is_empty()); + member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; + member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); resolve_function_body(member.variable->setter); } @@ -1296,7 +1301,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) { // Apply annotation (`@export_{category,group,subgroup}`). resolve_annotation(member.annotation); - member.annotation->apply(parser, nullptr); + member.annotation->apply(parser, nullptr, p_class); } } @@ -1411,7 +1416,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo } void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { - ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); + ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node."); switch (p_node->type) { case GDScriptParser::Node::NONE: @@ -1580,7 +1585,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; bool previous_static_context = static_context; - static_context = p_function->is_static; + if (p_is_lambda) { + // For lambdas this is determined from the context, the `static` keyword is not allowed. + p_function->is_static = static_context; + } else { + // For normal functions, this is determined in the parser by the `static` keyword. + static_context = p_function->is_static; + } GDScriptParser::DataType prev_datatype = p_function->get_datatype(); @@ -1592,21 +1603,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_value_count = 0; #endif // TOOLS_ENABLED +#ifdef DEBUG_ENABLED + String function_visible_name = function_name; + if (function_name == StringName()) { + function_visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; + } +#endif + for (int i = 0; i < p_function->parameters.size(); i++) { resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { - String visible_name = function_name; - if (function_name == StringName()) { - visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; - } - parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); #endif // DEBUG_ENABLED -#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->initializer) { +#ifdef TOOLS_ENABLED default_value_count++; +#endif // TOOLS_ENABLED if (p_function->parameters[i]->initializer->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value); @@ -1614,7 +1630,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } -#endif // TOOLS_ENABLED } if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) { @@ -1664,15 +1679,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * StringName native_base; if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); - valid = valid && parent_return_type == p_function->get_datatype(); + + if (p_function->return_type != nullptr) { + // Check return type covariance. + GDScriptParser::DataType return_type = p_function->get_datatype(); + if (return_type.is_variant()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow an explicitly specified `Variant` if the parent return type is narrower. + valid = valid && parent_return_type.is_variant(); + } else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { + // `is_type_compatible()` returns `true` if target is an `Object` and source is `null`. + // Don't allow `void` if the parent return type is a hard non-`void` type. + if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) { + valid = false; + } + } else { + valid = valid && is_type_compatible(parent_return_type, return_type); + } + } int par_count_diff = p_function->parameters.size() - parameters_types.size(); valid = valid && par_count_diff >= 0; valid = valid && default_value_count >= default_par_count + par_count_diff; - int i = 0; - for (const GDScriptParser::DataType &par_type : parameters_types) { - valid = valid && par_type == p_function->parameters[i++]->get_datatype(); + if (valid) { + int i = 0; + for (const GDScriptParser::DataType &parent_par_type : parameters_types) { + // Check parameter type contravariance. + GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype(); + if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow narrowing a hard `Variant`. + valid = valid && current_par_type.is_variant(); + } else { + valid = valid && is_type_compatible(current_par_type, parent_par_type); + } + } } if (!valid) { @@ -1696,7 +1738,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parent_signature += ") -> "; - const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + const String return_type = parent_return_type.to_string_strict(); if (return_type == "null") { parent_signature += "void"; } else { @@ -1714,6 +1756,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * #endif // TOOLS_ENABLED } +#ifdef DEBUG_ENABLED + if (p_function->return_type == nullptr) { + parser->push_warning(p_function, GDScriptWarning::UNTYPED_DECLARATION, "Function", function_visible_name); + } +#endif + if (p_function->get_datatype().is_resolving()) { p_function->set_datatype(prev_datatype); } @@ -1795,7 +1843,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { resolve_annotation(E); - E->apply(parser, stmt); + E->apply(parser, stmt, nullptr); } #ifdef DEBUG_ENABLED @@ -1917,6 +1965,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } } +#ifdef DEBUG_ENABLED + if (!has_specified_type) { + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; + const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); + if (p_assignable->infer_datatype || is_constant) { + parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + } else { + parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); + } + } +#endif + type.is_constant = is_constant; type.is_read_only = false; p_assignable->set_datatype(type); @@ -2121,19 +2181,21 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; -#ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string()); -#endif } -#ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string()); -#endif + if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } } p_for->variable->set_datatype(specified_type); } else { p_for->variable->set_datatype(variable_type); +#ifdef DEBUG_ENABLED + if (variable_type.is_hard_type()) { + parser->push_warning(p_for->variable, GDScriptWarning::INFERRED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } else { + parser->push_warning(p_for->variable, GDScriptWarning::UNTYPED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } +#endif } } @@ -2190,6 +2252,10 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m resolve_match_pattern(p_match_branch->patterns[i], p_match_test); } + if (p_match_branch->guard_body) { + resolve_suite(p_match_branch->guard_body); + } + resolve_suite(p_match_branch->block); decide_suite_type(p_match_branch, p_match_branch->block); @@ -2440,6 +2506,14 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::WHILE: ERR_FAIL_MSG("Reaching unreachable case"); } + + if (p_expression->get_datatype().kind == GDScriptParser::DataType::UNRESOLVED) { + // Prevent `is_type_compatible()` errors for incomplete expressions. + // The error can still occur if `reduce_*()` is called directly. + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_expression->set_datatype(dummy); + } } void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { @@ -2523,28 +2597,31 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. // This function determines which type is that (if any). void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + GDScriptParser::DataType expected_type = p_element_type; + expected_type.unset_container_element_type(); // Nested types (like `Array[Array[int]]`) are not currently supported. + for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; if (element_node->is_constant) { - update_const_expression_builtin_type(element_node, p_element_type, "include"); + update_const_expression_builtin_type(element_node, expected_type, "include"); } - const GDScriptParser::DataType &element_type = element_node->get_datatype(); - if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + const GDScriptParser::DataType &actual_type = element_node->get_datatype(); + if (actual_type.has_no_type() || actual_type.is_variant() || !actual_type.is_hard_type()) { mark_node_unsafe(element_node); continue; } - if (!is_type_compatible(p_element_type, element_type, true, p_array)) { - if (is_type_compatible(element_type, p_element_type)) { + if (!is_type_compatible(expected_type, actual_type, true, p_array)) { + if (is_type_compatible(actual_type, expected_type)) { mark_node_unsafe(element_node); continue; } - push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", actual_type.to_string(), expected_type.to_string()), element_node); return; } } GDScriptParser::DataType array_type = p_array->get_datatype(); - array_type.set_container_element_type(p_element_type); + array_type.set_container_element_type(expected_type); p_array->set_datatype(array_type); } @@ -2591,7 +2668,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); } @@ -2870,19 +2947,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) { // Call to name directly. StringName function_name = p_call->function_name; - Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + if (function_name == SNAME("Object")) { + push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call); + p_call->set_datatype(call_type); + return; + } + + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; call_type.kind = GDScriptParser::DataType::BUILTIN; call_type.builtin_type = builtin_type; - if (builtin_type == Variant::OBJECT) { - call_type.kind = GDScriptParser::DataType::NATIVE; - call_type.native_type = function_name; // "Object". - } - bool safe_to_fold = true; switch (builtin_type) { // Those are stored by reference so not suited for compile-time construction. @@ -2918,7 +2996,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2934,10 +3012,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too many arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: - push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too few arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: @@ -2948,21 +3026,41 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; } } else { - // TODO: Check constructors without constants. - // If there's one argument, try to use copy constructor (those aren't explicitly defined). if (p_call->arguments.size() == 1) { GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); - if (arg_type.is_variant()) { - mark_node_unsafe(p_call->arguments[0]); - } else { + if (arg_type.is_hard_type() && !arg_type.is_variant()) { if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { // Okay. p_call->set_datatype(call_type); return; } + } else { +#ifdef DEBUG_ENABLED + mark_node_unsafe(p_call); + // Constructors support overloads. + Vector<String> types; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) { + types.push_back(Variant::get_type_name((Variant::Type)i)); + } + } + String expected_types = function_name; + if (types.size() == 1) { + expected_types += "\" or \"" + types[0]; + } else if (types.size() >= 2) { + for (int i = 0; i < types.size() - 1; i++) { + expected_types += "\", \"" + types[i]; + } + expected_types += "\", or \"" + types[types.size() - 1]; + } + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -2979,14 +3077,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); - - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (!is_type_compatible(par_type, arg_type, true)) { types_match = false; break; #ifdef DEBUG_ENABLED } else { - if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name); } #endif } @@ -2994,9 +3092,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (types_match) { for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (p_call->arguments[i]->is_constant) { - update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); + } +#ifdef DEBUG_ENABLED + if (!(par_type.is_variant() && par_type.is_hard_type())) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (arg_type.is_variant() || !arg_type.is_hard_type()) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict()); + } } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -3037,12 +3145,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - type_from_property(wrong_arg, true).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); + } else { + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -3084,18 +3196,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - String expected_type_name; - if (err.argument < function_info.arguments.size()) { - expected_type_name = type_from_property(function_info.arguments[err.argument], true).to_string(); + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); } else { - expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); } - - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -3185,11 +3295,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); + if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) { + push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call); + p_call->set_datatype(call_type); + return; + } + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { - // If the function require typed arrays we must make literals be typed. + // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. + // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time. + if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { + push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } + + // If the function requires typed arrays we must make literals be typed. for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; - if (index < par_types.size() && par_types[index].has_container_element_type()) { + if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) { update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } @@ -3201,15 +3323,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (is_self && static_context && !method_flags.has_flag(METHOD_FLAG_STATIC)) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else { - push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call); + push_error(vformat(R"*(Cannot call non-static function "%s()" from a static variable initializer.)*", p_call->function_name), p_call); } } else if (!is_self && base_type.is_meta_type && !method_flags.has_flag(METHOD_FLAG_STATIC)) { base_type.is_meta_type = false; // For `to_string()`. @@ -3290,8 +3413,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a #else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); #endif // SUGGEST_GODOT4_RENAMES - } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); + } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) { + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call); } } @@ -3439,7 +3562,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); return; @@ -3779,6 +3902,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident #endif // Not a local, so check members. + if (!found_source) { reduce_identifier_from_base(p_identifier); if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { @@ -3791,15 +3915,16 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; if ((source_is_variable || source_is_signal) && static_context) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } else { - push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); } } @@ -3831,10 +3956,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. We make an exception for Variant::OBJECT because it's the base class for - // non-builtin types so we allow doing e.g. Object.new() + // Not a local or a member, so check globals. + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); - if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; @@ -3860,8 +3985,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (autoload.is_singleton) { // Singleton exists, so it's at least a Node. GDScriptParser::DataType result; - result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = SNAME("Node"); if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { @@ -4075,7 +4202,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type) { + if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { // Just try to get it. Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (valid) { @@ -4224,7 +4351,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { - push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + push_error(vformat(R"(Only "String" or "StringName" can be used as index for type "%s", but received "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); } } @@ -4563,7 +4690,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D Ref<Script> script_type = p_element_datatype.script_type; if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); return array; @@ -4734,7 +4861,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } else if (class_exists(elem_type_name)) { elem_type.kind = GDScriptParser::DataType::NATIVE; elem_type.builtin_type = Variant::OBJECT; - elem_type.native_type = p_property.hint_string; + elem_type.native_type = elem_type_name; } else if (ScriptServer::is_global_class(elem_type_name)) { // Just load this as it shouldn't be a GDScript. Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name)); @@ -4962,21 +5089,28 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); if (arg_type.is_variant() || !arg_type.is_hard_type()) { +#ifdef DEBUG_ENABLED // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). if (!(par_type.is_hard_type() && par_type.is_variant())) { mark_node_unsafe(p_call->arguments[i]); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); } +#endif } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { - // Supertypes are acceptable for dynamic compliance, but it's unsafe. - mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); +#ifdef DEBUG_ENABLED + } else { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); +#endif } #ifdef DEBUG_ENABLED } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); #endif } } @@ -5002,9 +5136,13 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } else if (ClassDB::class_exists(name)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "native class"); + return; + } else if (ScriptServer::is_global_class(name)) { + String class_path = ScriptServer::get_global_class_path(name).get_file(); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path)); return; - } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } @@ -5329,12 +5467,15 @@ void GDScriptAnalyzer::resolve_pending_lambda_bodies() { } GDScriptParser::LambdaNode *previous_lambda = current_lambda; + bool previous_static_context = static_context; List<GDScriptParser::LambdaNode *> lambdas = pending_body_resolution_lambdas; pending_body_resolution_lambdas.clear(); for (GDScriptParser::LambdaNode *lambda : lambdas) { current_lambda = lambda; + static_context = lambda->function->is_static; + resolve_function_body(lambda->function, true); int captures_amount = lambda->captures.size(); @@ -5363,6 +5504,7 @@ void GDScriptAnalyzer::resolve_pending_lambda_bodies() { } current_lambda = previous_lambda; + static_context = previous_static_context; } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { @@ -5421,7 +5563,7 @@ Error GDScriptAnalyzer::analyze() { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { resolve_annotation(E); - E->apply(parser, parser->head); + E->apply(parser, parser->head, nullptr); } resolve_interface(); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index af7862efc5..25e20c0e76 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -35,9 +35,6 @@ #include "core/debugger/engine_debugger.h" uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) { -#ifdef TOOLS_ENABLED - function->arg_names.push_back(p_name); -#endif function->_argument_count++; function->argument_types.push_back(p_type); if (p_is_optional) { @@ -403,7 +400,6 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { } function->_stack_size = RESERVED_STACK + max_locals + temporaries.size(); function->_instruction_args_size = instr_args_max; - function->_ptrcall_args_size = ptrcall_max; #ifdef DEBUG_ENABLED function->operator_names = operator_names; @@ -1228,75 +1224,35 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, ct.cleanup(); } -void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { -#define CASE_TYPE(m_type) \ - case Variant::m_type: \ - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \ - break - - bool is_ptrcall = true; - - if (p_method->has_return()) { - MethodInfo info; - ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info); - switch (info.return_val.type) { - CASE_TYPE(BOOL); - CASE_TYPE(INT); - CASE_TYPE(FLOAT); - CASE_TYPE(STRING); - CASE_TYPE(VECTOR2); - CASE_TYPE(VECTOR2I); - CASE_TYPE(RECT2); - CASE_TYPE(RECT2I); - CASE_TYPE(VECTOR3); - CASE_TYPE(VECTOR3I); - CASE_TYPE(TRANSFORM2D); - CASE_TYPE(PLANE); - CASE_TYPE(AABB); - CASE_TYPE(BASIS); - CASE_TYPE(TRANSFORM3D); - CASE_TYPE(COLOR); - CASE_TYPE(STRING_NAME); - CASE_TYPE(NODE_PATH); - CASE_TYPE(RID); - CASE_TYPE(QUATERNION); - CASE_TYPE(OBJECT); - CASE_TYPE(CALLABLE); - CASE_TYPE(SIGNAL); - CASE_TYPE(DICTIONARY); - CASE_TYPE(ARRAY); - CASE_TYPE(PACKED_BYTE_ARRAY); - CASE_TYPE(PACKED_INT32_ARRAY); - CASE_TYPE(PACKED_INT64_ARRAY); - CASE_TYPE(PACKED_FLOAT32_ARRAY); - CASE_TYPE(PACKED_FLOAT64_ARRAY); - CASE_TYPE(PACKED_STRING_ARRAY); - CASE_TYPE(PACKED_VECTOR2_ARRAY); - CASE_TYPE(PACKED_VECTOR3_ARRAY); - CASE_TYPE(PACKED_COLOR_ARRAY); - default: - append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); - is_ptrcall = false; - break; +void GDScriptByteCodeGenerator::write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { + Variant::Type return_type = Variant::NIL; + bool has_return = p_method->has_return(); + + if (has_return) { + PropertyInfo return_info = p_method->get_return_info(); + return_type = return_info.type; + } + + CallTarget ct = get_call_target(p_target, return_type); + + if (has_return) { + Variant::Type temp_type = temporaries[ct.target.address].type; + if (temp_type != return_type) { + write_type_adjust(ct.target, return_type); } - } else { - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size()); } + GDScriptFunction::Opcode code = p_method->has_return() ? GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN : GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN; + append_opcode_and_argcount(code, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - CallTarget ct = get_call_target(p_target); append(ct.target); append(p_arguments.size()); append(p_method); ct.cleanup(); - if (is_ptrcall) { - alloc_ptrcall(p_arguments.size()); - } - -#undef CASE_TYPE } void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 671dea5d6d..9bface6136 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -97,7 +97,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { int max_locals = 0; int current_line = 0; int instr_args_max = 0; - int ptrcall_max = 0; #ifdef DEBUG_ENABLED List<int> temp_stack; @@ -346,12 +345,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } - void alloc_ptrcall(int p_params) { - if (p_params >= ptrcall_max) { - ptrcall_max = p_params; - } - } - CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); int address_of(const Address &p_address) { @@ -519,7 +512,7 @@ public: virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; - virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index d191bd0224..76f4e69ab9 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -59,7 +59,7 @@ GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { } Error GDScriptParserRef::raise_status(Status p_new_status) { - ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); + ERR_FAIL_NULL_V(parser, ERR_INVALID_DATA); if (result != OK) { return result; @@ -287,7 +287,8 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (script.is_null()) { script = get_shallow_script(p_path, r_error); - if (r_error) { + // Only exit early if script failed to load, otherwise let reload report errors. + if (script.is_null()) { return script; } } @@ -363,28 +364,33 @@ void GDScriptCache::remove_static_script(const String &p_fqcn) { Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { MutexLock lock(singleton->mutex); - if (singleton->packed_scene_cache.has(p_path)) { - singleton->packed_scene_dependencies[p_path].insert(p_owner); - return singleton->packed_scene_cache[p_path]; + String path = p_path; + if (path.begins_with("uid://")) { + path = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(path)); + } + + if (singleton->packed_scene_cache.has(path)) { + singleton->packed_scene_dependencies[path].insert(p_owner); + return singleton->packed_scene_cache[path]; } - Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + Ref<PackedScene> scene = ResourceCache::get_ref(path); if (scene.is_valid()) { - singleton->packed_scene_cache[p_path] = scene; - singleton->packed_scene_dependencies[p_path].insert(p_owner); + singleton->packed_scene_cache[path] = scene; + singleton->packed_scene_dependencies[path].insert(p_owner); return scene; } scene.instantiate(); r_error = OK; - if (p_path.is_empty()) { + if (path.is_empty()) { r_error = ERR_FILE_BAD_PATH; return scene; } - scene->set_path(p_path); - singleton->packed_scene_cache[p_path] = scene; - singleton->packed_scene_dependencies[p_path].insert(p_owner); + scene->set_path(path); + singleton->packed_scene_cache[path] = scene; + singleton->packed_scene_dependencies[path].insert(p_owner); scene->reload_from_file(); return scene; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index cf17353dec..7ad8f841aa 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -129,7 +129,7 @@ public: virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; - virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 985eb97b29..7980f020b8 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -60,7 +60,7 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa scr = scr->_base; } - ERR_FAIL_COND_V(!nc, false); + ERR_FAIL_NULL_V(nc, false); return ClassDB::has_property(nc->get_name(), p_name); } @@ -84,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype) { if (!p_datatype.is_set() || !p_datatype.is_hard_type() || p_datatype.is_coroutine) { return GDScriptDataType(); } @@ -101,11 +101,36 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = p_datatype.builtin_type; } break; case GDScriptParser::DataType::NATIVE: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + // Fixes GH-82255. `GDScriptNativeClass` is obtainable in GDScript, + // but is not a registered and exposed class, so `GDScriptNativeClass` + // is missing from `GDScriptLanguage::get_singleton()->get_global_map()`. + //result.native_type = GDScriptNativeClass::get_class_static(); + result.native_type = Object::get_class_static(); + break; + } + result.kind = GDScriptDataType::NATIVE; - result.native_type = p_datatype.native_type; result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; + +#ifdef DEBUG_ENABLED + if (unlikely(!GDScriptLanguage::get_singleton()->get_global_map().has(result.native_type))) { + ERR_PRINT(vformat(R"(GDScript bug: Native class "%s" not found.)", result.native_type)); + result.native_type = Object::get_class_static(); + } +#endif } break; case GDScriptParser::DataType::SCRIPT: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = p_datatype.script_type.is_valid() ? p_datatype.script_type->get_class() : Script::get_class_static(); + break; + } + result.kind = GDScriptDataType::SCRIPT; result.builtin_type = p_datatype.builtin_type; result.script_type_ref = p_datatype.script_type; @@ -113,6 +138,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = GDScript::get_class_static(); + break; + } + result.kind = GDScriptDataType::GDSCRIPT; result.builtin_type = p_datatype.builtin_type; result.native_type = p_datatype.native_type; @@ -148,6 +180,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::DICTIONARY; + break; + } + result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; break; @@ -159,7 +197,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner, false)); } return result; @@ -191,13 +229,13 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp } } -static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { +static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { if (p_method->is_vararg()) { - // ptrcall won't work with vararg methods. + // Validated call won't work with vararg methods. return false; } if (p_method->get_argument_count() != p_arguments.size()) { - // ptrcall won't work with default arguments. + // Validated call won't work with default arguments. return false; } MethodInfo info; @@ -402,7 +440,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code String global_class_path = ScriptServer::get_global_class_path(identifier); if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { Error err = OK; - res = GDScriptCache::get_full_script(global_class_path, err); + // Should not need to pass p_owner since analyzer will already have done it. + res = GDScriptCache::get_shallow_script(global_class_path, err); if (err != OK) { _set_error("Can't load global class " + String(identifier), p_expression); r_error = ERR_COMPILATION_FAILED; @@ -533,7 +572,7 @@ 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->get_datatype(), codegen.script); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script, false); GDScriptCodeGenerator::Address result; if (cast_type.has_type) { @@ -573,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { - // Construct a built-in type. - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - gen->write_construct(result, vtype, arguments); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { // Variant utility function. gen->write_call_utility(result, call->function_name, arguments); @@ -600,9 +636,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code self.mode = GDScriptCodeGenerator::Address::SELF; MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); - if (_can_use_ptrcall(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, self, method, arguments); + if (_can_use_validate_call(method, arguments)) { + // Exact arguments, use validated call. + gen->write_call_method_bind_validated(result, self, method, arguments); } else { // Not exact arguments, but still can use method bind call. gen->write_call_method_bind(result, self, method, arguments); @@ -650,9 +686,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { MethodBind *method = ClassDB::get_method(class_name, call->function_name); - if (_can_use_ptrcall(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, base, method, arguments); + if (_can_use_validate_call(method, arguments)) { + // Exact arguments, use validated call. + gen->write_call_method_bind_validated(result, base, method, arguments); } else { // Not exact arguments, but still can use method bind call. gen->write_call_method_bind(result, base, method, arguments); @@ -697,7 +733,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); - gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); + gen->write_call_method_bind_validated(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); return result; } break; @@ -911,7 +947,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand); - GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script); + GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script, false); if (r_error) { return GDScriptCodeGenerator::Address(); } @@ -1335,6 +1371,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } + main_script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { @@ -1889,6 +1926,26 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } + // If there's a guard, check its condition too. + if (branch->guard_body != nullptr) { + // Do this first so the guard does not run unless the pattern matched. + gen->write_and_left_operand(pattern_result); + + // Don't actually use the block for the guard. + // The binds are already in the locals and we don't want to clear the result of the guard condition before we check the actual match. + GDScriptCodeGenerator::Address guard_result = _parse_expression(codegen, err, static_cast<GDScriptParser::ExpressionNode *>(branch->guard_body->statements[0])); + if (err) { + return err; + } + + gen->write_and_right_operand(guard_result); + gen->write_end_and(pattern_result); + + if (guard_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + // Check if pattern did match. gen->write_if(pattern_result); @@ -2165,8 +2222,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } } + MethodInfo method_info; + codegen.function_name = func_name; + method_info.name = func_name; codegen.is_static = is_static; + if (is_static) { + method_info.flags |= METHOD_FLAG_STATIC; + } codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); int optional_parameters = 0; @@ -2178,10 +2241,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type); codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); + method_info.arguments.push_back(parameter->get_datatype().to_property_info(parameter->identifier->name)); + if (parameter->initializer != nullptr) { optional_parameters++; } } + + method_info.default_arguments.append_array(p_func->default_arg_values); } // Parse initializer if applies. @@ -2335,20 +2402,20 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } if (p_func) { - // if no return statement -> return type is void not unresolved Variant + // If no `return` statement, then return type is `void`, not `Variant`. if (p_func->body->has_return) { gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); + method_info.return_val = p_func->get_datatype().to_property_info(String()); } else { gd_function->return_type = GDScriptDataType(); gd_function->return_type.has_type = true; gd_function->return_type.kind = GDScriptDataType::BUILTIN; gd_function->return_type.builtin_type = Variant::NIL; } -#ifdef TOOLS_ENABLED - gd_function->default_arg_values = p_func->default_arg_values; -#endif } + gd_function->method_info = method_info; + if (!is_implicit_initializer && !is_implicit_ready && !p_for_lambda) { p_script->member_functions[func_name] = gd_function; } @@ -2503,7 +2570,10 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +// Prepares given script, and inner class scripts, for compilation. It populates class members and initializes method +// RPC info for its base classes first, then for itself, then for inner classes. +// Warning: this function cannot initiate compilation of other classes, or it will result in cyclic dependency issues. +Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { if (parsed_classes.has(p_script)) { return OK; } @@ -2554,7 +2624,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri p_script->member_functions.clear(); p_script->member_indices.clear(); - p_script->member_info.clear(); p_script->static_variables_indices.clear(); p_script->static_variables.clear(); p_script->_signals.clear(); @@ -2562,19 +2631,21 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; p_script->static_initializer = nullptr; + p_script->rpc_config.clear(); + p_script->lambda_info.clear(); p_script->clearing = false; p_script->tool = parser->is_tool(); - if (!p_script->name.is_empty()) { - if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { - _set_error("The class '" + p_script->name + "' shadows a native class", p_class); + if (p_script->local_name != StringName()) { + if (ClassDB::class_exists(p_script->local_name) && ClassDB::is_class_exposed(p_script->local_name)) { + _set_error(vformat(R"(The class "%s" shadows a native class)", p_script->local_name), p_class); return ERR_ALREADY_EXISTS; } } - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script, false); int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; @@ -2592,7 +2663,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } if (main_script->has_class(base.ptr())) { - Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + Error err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state); if (err) { return err; } @@ -2611,7 +2682,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri return ERR_COMPILATION_FAILED; } - err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state); if (err) { _set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return err; @@ -2628,6 +2699,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } break; } + // Duplicate RPC information from base GDScript + // Base script isn't valid because it should not have been compiled yet, but the reference contains relevant info. + if (base_type.kind == GDScriptDataType::GDSCRIPT && p_script->base.is_valid()) { + p_script->rpc_config = p_script->base->rpc_config.duplicate(); + } + for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; switch (member.type) { @@ -2636,7 +2713,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri StringName name = variable->identifier->name; GDScript::MemberInfo minfo; - minfo.index = p_script->member_indices.size(); switch (variable->property) { case GDScriptParser::VariableNode::PROP_NONE: break; // Nothing to do. @@ -2659,8 +2735,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script); - PropertyInfo prop_info = minfo.data_type; - prop_info.name = name; + PropertyInfo prop_info = variable->get_datatype().to_property_info(name); PropertyInfo export_info = variable->export_info; if (variable->exported) { @@ -2670,16 +2745,16 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; - prop_info.usage = export_info.usage | PROPERTY_USAGE_SCRIPT_VARIABLE; - } else { - prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; + prop_info.usage = export_info.usage; } + prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + minfo.property_info = prop_info; if (variable->is_static) { minfo.index = p_script->static_variables_indices.size(); p_script->static_variables_indices[name] = minfo; } else { - p_script->member_info[name] = prop_info; + minfo.index = p_script->member_indices.size(); p_script->member_indices[name] = minfo; p_script->members.insert(name); } @@ -2712,12 +2787,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - Vector<StringName> parameters_names; - parameters_names.resize(signal->parameters.size()); - for (int j = 0; j < signal->parameters.size(); j++) { - parameters_names.write[j] = signal->parameters[j]->identifier->name; - } - p_script->_signals[name] = parameters_names; + p_script->_signals[name] = signal->method_info; } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -2740,12 +2810,20 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri prop_info.name = annotation->export_info.name; prop_info.usage = annotation->export_info.usage; prop_info.hint_string = annotation->export_info.hint_string; + minfo.property_info = prop_info; - p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; - p_script->members.insert(Variant()); + p_script->members.insert(name); } break; + case GDScriptParser::ClassNode::Member::FUNCTION: { + const GDScriptParser::FunctionNode *function_n = member.function; + + Variant config = function_n->rpc_config; + if (config.get_type() != Variant::NIL) { + p_script->rpc_config[function_n->identifier->name] = config; + } + } break; default: break; // Nothing to do here. } @@ -2756,7 +2834,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri parsed_classes.insert(p_script); parsing_classes.erase(p_script); - // Populate sub-classes. + // Populate inner classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2769,7 +2847,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri // Subclass might still be parsing, just skip it if (!parsing_classes.has(subclass_ptr)) { - Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); + Error err = _prepare_compilation(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } @@ -2905,8 +2983,6 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: has_static_data = has_static_data || inner_class->has_static_data; } - p_script->_init_rpc_methods_properties(); - p_script->valid = true; return OK; } @@ -2927,7 +3003,8 @@ void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDS void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { p_script->fully_qualified_name = p_class->fqcn; - p_script->name = p_class->identifier ? p_class->identifier->name : ""; + p_script->local_name = p_class->identifier ? p_class->identifier->name : StringName(); + p_script->global_name = p_class->get_global_name(); p_script->simplified_icon_path = p_class->simplified_icon_path; HashMap<StringName, Ref<GDScript>> old_subclasses; @@ -2965,6 +3042,128 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl } } +GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement_info(GDScriptFunction *p_func, int p_index, int p_depth, GDScriptFunction *p_parent_func) { + FunctionLambdaInfo info; + info.function = p_func; + info.parent = p_parent_func; + info.script = p_parent_func; + info.name = p_func->get_name(); + info.line = p_func->_initial_line; + info.index = p_index; + info.depth = p_depth; + info.capture_count = 0; + info.use_self = false; + info.arg_count = p_func->_argument_count; + info.default_arg_count = p_func->_default_arg_count; + info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func); + + GDScript::LambdaInfo *extra_info = main_script->lambda_info.getptr(p_func); + if (extra_info != nullptr) { + info.capture_count = extra_info->capture_count; + info.use_self = extra_info->use_self; + } + + return info; +} + +Vector<GDScriptCompiler::FunctionLambdaInfo> GDScriptCompiler::_get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth, GDScriptFunction *p_parent_func) { + Vector<FunctionLambdaInfo> result; + // Only scrape the lambdas inside p_func. + for (int i = 0; i < p_func->lambdas.size(); ++i) { + result.push_back(_get_function_replacement_info(p_func->lambdas[i], i, p_depth + 1, p_func)); + } + return result; +} + +GDScriptCompiler::ScriptLambdaInfo GDScriptCompiler::_get_script_lambda_replacement_info(GDScript *p_script) { + ScriptLambdaInfo info; + + if (p_script->implicit_initializer) { + info.implicit_initializer_info = _get_function_lambda_replacement_info(p_script->implicit_initializer); + } + if (p_script->implicit_ready) { + info.implicit_ready_info = _get_function_lambda_replacement_info(p_script->implicit_ready); + } + if (p_script->static_initializer) { + info.static_initializer_info = _get_function_lambda_replacement_info(p_script->static_initializer); + } + + for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + info.member_function_infos.insert(E.key, _get_function_lambda_replacement_info(E.value)); + } + + for (const KeyValue<StringName, Ref<GDScript>> &KV : p_script->get_subclasses()) { + info.subclass_info.insert(KV.key, _get_script_lambda_replacement_info(KV.value.ptr())); + } + + return info; +} + +bool GDScriptCompiler::_do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + if (p_new_info == nullptr) { + return false; + } + + if (p_new_info->capture_count != p_old_info.capture_count || p_new_info->use_self != p_old_info.use_self) { + return false; + } + + int old_required_arg_count = p_old_info.arg_count - p_old_info.default_arg_count; + int new_required_arg_count = p_new_info->arg_count - p_new_info->default_arg_count; + if (new_required_arg_count > old_required_arg_count || p_new_info->arg_count < old_required_arg_count) { + return false; + } + + return true; +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + ERR_FAIL_COND(r_replacements.has(p_old_info.function)); + if (!_do_function_infos_match(p_old_info, p_new_info)) { + p_new_info = nullptr; + } + + r_replacements.insert(p_old_info.function, p_new_info != nullptr ? p_new_info->function : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.sublambdas, p_new_info != nullptr ? &p_new_info->sublambdas : nullptr); +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos) { + for (int i = 0; i < p_old_infos.size(); ++i) { + const FunctionLambdaInfo &old_info = p_old_infos[i]; + const FunctionLambdaInfo *new_info = nullptr; + if (p_new_infos != nullptr && p_new_infos->size() == p_old_infos.size()) { + // For now only attempt if the size is the same. + new_info = &p_new_infos->get(i); + } + _get_function_ptr_replacements(r_replacements, old_info, new_info); + } +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info) { + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_initializer_info, p_new_info != nullptr ? &p_new_info->implicit_initializer_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_ready_info, p_new_info != nullptr ? &p_new_info->implicit_ready_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.static_initializer_info, p_new_info != nullptr ? &p_new_info->static_initializer_info : nullptr); + + for (const KeyValue<StringName, Vector<FunctionLambdaInfo>> &old_kv : p_old_info.member_function_infos) { + _get_function_ptr_replacements(r_replacements, old_kv.value, p_new_info != nullptr ? p_new_info->member_function_infos.getptr(old_kv.key) : nullptr); + } + for (int i = 0; i < p_old_info.other_function_infos.size(); ++i) { + const FunctionLambdaInfo &old_other_info = p_old_info.other_function_infos[i]; + const FunctionLambdaInfo *new_other_info = nullptr; + if (p_new_info != nullptr && p_new_info->other_function_infos.size() == p_old_info.other_function_infos.size()) { + // For now only attempt if the size is the same. + new_other_info = &p_new_info->other_function_infos[i]; + } + // Needs to be called on all old lambdas, even if there's no replacement. + _get_function_ptr_replacements(r_replacements, old_other_info, new_other_info); + } + for (const KeyValue<StringName, ScriptLambdaInfo> &old_kv : p_old_info.subclass_info) { + const ScriptLambdaInfo &old_subinfo = old_kv.value; + const ScriptLambdaInfo *new_subinfo = p_new_info != nullptr ? p_new_info->subclass_info.getptr(old_kv.key) : nullptr; + _get_function_ptr_replacements(r_replacements, old_subinfo, new_subinfo); + } +} + Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; @@ -2975,11 +3174,13 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); + ScriptLambdaInfo old_lambda_info = _get_script_lambda_replacement_info(p_script); + // Create scripts for subclasses beforehand so they can be referenced make_scripts(p_script, root, p_keep_state); main_script->_owner = nullptr; - Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); + Error err = _prepare_compilation(main_script, parser->get_tree(), p_keep_state); if (err) { return err; @@ -2990,6 +3191,27 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri return err; } + ScriptLambdaInfo new_lambda_info = _get_script_lambda_replacement_info(p_script); + + HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements; + _get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info); + + { + MutexLock outer_lock(main_script->func_ptrs_to_update_mutex); + for (GDScript::UpdatableFuncPtr *updatable : main_script->func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + GDScriptFunction **replacement = func_ptr_replacements.getptr(*func_ptr_ptr); + if (replacement != nullptr) { + *func_ptr_ptr = *replacement; + } else { + // Probably a lambda from another reload, ignore. + *func_ptr_ptr = nullptr; + } + } + } + } + if (has_static_data && !root->annotated_static_unload) { GDScriptCache::add_static_script(p_script); } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 2f522da4ea..fd6b22f527 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -44,6 +44,34 @@ class GDScriptCompiler { HashSet<GDScript *> parsing_classes; GDScript *main_script = nullptr; + struct FunctionLambdaInfo { + GDScriptFunction *function; + GDScriptFunction *parent; + Ref<GDScript> script; + StringName name; + int line; + int index; + int depth; + //uint64_t code_hash; + //int code_size; + int capture_count; + int use_self; + int arg_count; + int default_arg_count; + //Vector<GDScriptDataType> argument_types; + //GDScriptDataType return_type; + Vector<FunctionLambdaInfo> sublambdas; + }; + + struct ScriptLambdaInfo { + Vector<FunctionLambdaInfo> implicit_initializer_info; + Vector<FunctionLambdaInfo> implicit_ready_info; + Vector<FunctionLambdaInfo> static_initializer_info; + HashMap<StringName, Vector<FunctionLambdaInfo>> member_function_infos; + Vector<FunctionLambdaInfo> other_function_infos; + HashMap<StringName, ScriptLambdaInfo> subclass_info; + }; + struct CodeGen { GDScript *script = nullptr; const GDScriptParser::ClassNode *class_node = nullptr; @@ -124,7 +152,7 @@ class GDScriptCompiler { Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -135,8 +163,15 @@ class GDScriptCompiler { GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + Vector<FunctionLambdaInfo> _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script); + bool _do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info); int err_line = 0; int err_column = 0; StringName source; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 438ec02740..26f7cb7537 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -670,10 +670,29 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4 + argc; } break; - case OPCODE_CALL_PTRCALL_NO_RETURN: { + + case OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN: { int instr_var_args = _code_ptr[++ip]; + text += "call method-bind validated (return) "; + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(2 + argc) + " = "; + text += DADDR(1 + argc) + "."; + text += method->get_name(); + text += "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + incr = 5 + argc; + } break; - text += "call-ptrcall (no return) "; + case OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN: { + int instr_var_args = _code_ptr[++ip]; + + text += "call method-bind validated (no return) "; MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; @@ -694,65 +713,6 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; -#define DISASSEMBLE_PTRCALL(m_type) \ - case OPCODE_CALL_PTRCALL_##m_type: { \ - int instr_var_args = _code_ptr[++ip]; \ - text += "call-ptrcall (return "; \ - text += #m_type; \ - text += ") "; \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \ - int argc = _code_ptr[ip + 1 + instr_var_args]; \ - text += DADDR(2 + argc) + " = "; \ - text += DADDR(1 + argc) + "."; \ - text += method->get_name(); \ - text += "("; \ - for (int i = 0; i < argc; i++) { \ - if (i > 0) \ - text += ", "; \ - text += DADDR(1 + i); \ - } \ - text += ")"; \ - incr = 5 + argc; \ - } break - - DISASSEMBLE_PTRCALL(BOOL); - DISASSEMBLE_PTRCALL(INT); - DISASSEMBLE_PTRCALL(FLOAT); - DISASSEMBLE_PTRCALL(STRING); - DISASSEMBLE_PTRCALL(VECTOR2); - DISASSEMBLE_PTRCALL(VECTOR2I); - DISASSEMBLE_PTRCALL(RECT2); - DISASSEMBLE_PTRCALL(RECT2I); - DISASSEMBLE_PTRCALL(VECTOR3); - DISASSEMBLE_PTRCALL(VECTOR3I); - DISASSEMBLE_PTRCALL(TRANSFORM2D); - DISASSEMBLE_PTRCALL(VECTOR4); - DISASSEMBLE_PTRCALL(VECTOR4I); - DISASSEMBLE_PTRCALL(PLANE); - DISASSEMBLE_PTRCALL(AABB); - DISASSEMBLE_PTRCALL(BASIS); - DISASSEMBLE_PTRCALL(TRANSFORM3D); - DISASSEMBLE_PTRCALL(PROJECTION); - DISASSEMBLE_PTRCALL(COLOR); - DISASSEMBLE_PTRCALL(STRING_NAME); - DISASSEMBLE_PTRCALL(NODE_PATH); - DISASSEMBLE_PTRCALL(RID); - DISASSEMBLE_PTRCALL(QUATERNION); - DISASSEMBLE_PTRCALL(OBJECT); - DISASSEMBLE_PTRCALL(CALLABLE); - DISASSEMBLE_PTRCALL(SIGNAL); - DISASSEMBLE_PTRCALL(DICTIONARY); - DISASSEMBLE_PTRCALL(ARRAY); - DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY); - case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: { int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 6cad3b2b90..724715d9e5 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,11 +54,16 @@ void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const p_delimiters->push_back("#"); } +void GDScriptLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const { + p_delimiters->push_back("##"); +} + void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); p_delimiters->push_back("''' '''"); + // NOTE: StringName, NodePath and r-strings are not listed here. } bool GDScriptLanguage::is_using_templates() { @@ -75,19 +80,25 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri #endif if (!type_hints) { processed_template = processed_template.replace(": int", "") + .replace(": Shader.Mode", "") + .replace(": VisualShader.Type", "") + .replace(": float", "") .replace(": String", "") .replace(": Array[String]", "") - .replace(": float", "") + .replace(": Node", "") .replace(": CharFXTransform", "") .replace(":=", "=") - .replace(" -> String", "") - .replace(" -> int", "") + .replace(" -> void", "") .replace(" -> bool", "") - .replace(" -> void", ""); + .replace(" -> int", "") + .replace(" -> PortType", "") + .replace(" -> String", "") + .replace(" -> Object", ""); } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_", p_class_name.to_pascal_case()) + .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier()) + .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -190,10 +201,6 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li return true; } -bool GDScriptLanguage::has_named_classes() const { - return false; -} - bool GDScriptLanguage::supports_builtin_mode() const { return true; } @@ -500,7 +507,7 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na s += p_args[i].get_slice(":", 0); if (th) { String type = p_args[i].get_slice(":", 1); - if (!type.is_empty() && type != "var") { + if (!type.is_empty()) { s += ": " + type; } } @@ -977,7 +984,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, ScriptLanguage::CodeCompletionOption option; switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: - if (p_only_functions || outer || (p_static)) { + if (p_only_functions || outer || (p_static && !member.variable->is_static)) { continue; } option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); @@ -1119,6 +1126,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -1285,7 +1293,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context static const char *_keywords_with_space[] = { "and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await", - "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", + "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while", nullptr }; @@ -1459,8 +1467,13 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (p_expression->is_constant) { // Already has a value, so just use that. r_type = _type_from_variant(p_expression->reduced_value); - if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) { - r_type.type = p_expression->get_datatype(); + switch (p_expression->get_datatype().kind) { + case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::CLASS: + r_type.type = p_expression->get_datatype(); + break; + default: + break; } found = true; } else { @@ -1618,6 +1631,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, native_type.script_type = parent; } else { native_type.kind = GDScriptParser::DataType::NATIVE; + native_type.builtin_type = Variant::OBJECT; native_type.native_type = native_type.script_type->get_instance_base_type(); if (!ClassDB::class_exists(native_type.native_type)) { native_type.kind = GDScriptParser::DataType::UNRESOLVED; @@ -2147,6 +2161,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ClassDB::class_exists(p_identifier->name) && ClassDB::is_class_exposed(p_identifier->name)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.builtin_type = Variant::OBJECT; r_type.type.native_type = p_identifier->name; r_type.type.is_constant = true; if (Engine::get_singleton()->has_singleton(p_identifier->name)) { @@ -2183,7 +2198,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } return true; case GDScriptParser::ClassNode::Member::VARIABLE: - if (!is_static) { + if (!is_static || member.variable->is_static) { if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { r_type.type = member.variable->get_datatype(); return true; @@ -2258,21 +2273,25 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & return true; } - if (!is_static) { - List<PropertyInfo> members; + List<PropertyInfo> members; + if (is_static) { + scr->get_property_list(&members); + } else { scr->get_script_property_list(&members); - for (const PropertyInfo &prop : members) { - if (prop.name == p_identifier) { - r_type = _type_from_property(prop); - return true; - } + } + for (const PropertyInfo &prop : members) { + if (prop.name == p_identifier) { + r_type = _type_from_property(prop); + return true; } } + Ref<Script> parent = scr->get_base_script(); if (parent.is_valid()) { base_type.script_type = parent; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -2442,6 +2461,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -2697,8 +2717,8 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co } r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); r_base_type.builtin_type = Variant::OBJECT; + r_base_type.native_type = node->get_class_name(); return true; } } @@ -3252,6 +3272,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -3388,6 +3409,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } + if ("Variant" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; + r_result.class_name = "Variant"; + return OK; + } + if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index a6b4dc7981..372c212d2b 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -32,14 +32,6 @@ #include "gdscript.h" -const int *GDScriptFunction::get_code() const { - return _code_ptr; -} - -int GDScriptFunction::get_code_size() const { - return _code_size; -} - Variant GDScriptFunction::get_constant(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>"); return constants[p_idx]; @@ -50,32 +42,6 @@ StringName GDScriptFunction::get_global_name(int p_idx) const { return global_names[p_idx]; } -int GDScriptFunction::get_default_argument_count() const { - return _default_arg_count; -} - -int GDScriptFunction::get_default_argument_addr(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), -1); - return default_arguments[p_idx]; -} - -GDScriptDataType GDScriptFunction::get_return_type() const { - return return_type; -} - -GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType()); - return argument_types[p_idx]; -} - -StringName GDScriptFunction::get_name() const { - return name; -} - -int GDScriptFunction::get_max_stack_size() const { - return _stack_size; -} - struct _GDFKC { int order = 0; List<int> pos; @@ -161,9 +127,7 @@ GDScriptFunction::~GDScriptFunction() { return_type.script_type_ref = Ref<Script>(); #ifdef DEBUG_ENABLED - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); - GDScriptLanguage::get_singleton()->function_list.remove(&function_list); #endif } @@ -176,7 +140,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar if (p_argcount == 0) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; + r_error.expected = 1; return Variant(); } else if (p_argcount == 1) { //noooneee @@ -224,7 +188,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { } Variant GDScriptFunctionState::resume(const Variant &p_arg) { - ERR_FAIL_COND_V(!function, Variant()); + ERR_FAIL_NULL_V(function, Variant()); { MutexLock lock(GDScriptLanguage::singleton->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 5230773c13..c9b543fbb9 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -147,39 +147,12 @@ public: return false; } - operator PropertyInfo() const { - PropertyInfo info; - info.usage = PROPERTY_USAGE_NONE; - if (has_type) { - switch (kind) { - case UNINITIALIZED: - break; - case BUILTIN: { - info.type = builtin_type; - } break; - case NATIVE: { - info.type = Variant::OBJECT; - info.class_name = native_type; - } break; - case SCRIPT: - case GDSCRIPT: { - info.type = Variant::OBJECT; - info.class_name = script_type->get_instance_base_type(); - } break; - } - } else { - info.type = Variant::NIL; - info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - return info; - } - void set_container_element_type(const GDScriptDataType &p_element_type) { container_element_type = memnew(GDScriptDataType(p_element_type)); } GDScriptDataType get_container_element_type() const { - ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType()); + ERR_FAIL_NULL_V(container_element_type, GDScriptDataType()); return *container_element_type; } @@ -268,45 +241,8 @@ public: OPCODE_CALL_METHOD_BIND_RET, OPCODE_CALL_BUILTIN_STATIC, OPCODE_CALL_NATIVE_STATIC, - // ptrcall have one instruction per return type. - OPCODE_CALL_PTRCALL_NO_RETURN, - OPCODE_CALL_PTRCALL_BOOL, - OPCODE_CALL_PTRCALL_INT, - OPCODE_CALL_PTRCALL_FLOAT, - OPCODE_CALL_PTRCALL_STRING, - OPCODE_CALL_PTRCALL_VECTOR2, - OPCODE_CALL_PTRCALL_VECTOR2I, - OPCODE_CALL_PTRCALL_RECT2, - OPCODE_CALL_PTRCALL_RECT2I, - OPCODE_CALL_PTRCALL_VECTOR3, - OPCODE_CALL_PTRCALL_VECTOR3I, - OPCODE_CALL_PTRCALL_TRANSFORM2D, - OPCODE_CALL_PTRCALL_VECTOR4, - OPCODE_CALL_PTRCALL_VECTOR4I, - OPCODE_CALL_PTRCALL_PLANE, - OPCODE_CALL_PTRCALL_QUATERNION, - OPCODE_CALL_PTRCALL_AABB, - OPCODE_CALL_PTRCALL_BASIS, - OPCODE_CALL_PTRCALL_TRANSFORM3D, - OPCODE_CALL_PTRCALL_PROJECTION, - OPCODE_CALL_PTRCALL_COLOR, - OPCODE_CALL_PTRCALL_STRING_NAME, - OPCODE_CALL_PTRCALL_NODE_PATH, - OPCODE_CALL_PTRCALL_RID, - OPCODE_CALL_PTRCALL_OBJECT, - OPCODE_CALL_PTRCALL_CALLABLE, - OPCODE_CALL_PTRCALL_SIGNAL, - OPCODE_CALL_PTRCALL_DICTIONARY, - OPCODE_CALL_PTRCALL_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, + OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, + OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, OPCODE_AWAIT, OPCODE_AWAIT_RESUME, OPCODE_CREATE_LAMBDA, @@ -437,59 +373,31 @@ private: friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; + friend class GDScriptLanguage; + StringName name; StringName source; + bool _static = false; + Vector<GDScriptDataType> argument_types; + GDScriptDataType return_type; + MethodInfo method_info; + Variant rpc_config; - mutable Variant nil; - mutable Variant *_constants_ptr = nullptr; - int _constant_count = 0; - const StringName *_global_names_ptr = nullptr; - int _global_names_count = 0; - const int *_default_arg_ptr = nullptr; - int _default_arg_count = 0; - int _operator_funcs_count = 0; - const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; - int _setters_count = 0; - const Variant::ValidatedSetter *_setters_ptr = nullptr; - int _getters_count = 0; - const Variant::ValidatedGetter *_getters_ptr = nullptr; - int _keyed_setters_count = 0; - const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; - int _keyed_getters_count = 0; - const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; - int _indexed_setters_count = 0; - const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; - int _indexed_getters_count = 0; - const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; - int _builtin_methods_count = 0; - const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; - int _constructors_count = 0; - const Variant::ValidatedConstructor *_constructors_ptr = nullptr; - int _utilities_count = 0; - const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; - int _gds_utilities_count = 0; - const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; - int _methods_count = 0; - MethodBind **_methods_ptr = nullptr; - int _lambdas_count = 0; - GDScriptFunction **_lambdas_ptr = nullptr; - int *_code_ptr = nullptr; - int _code_size = 0; + GDScript *_script = nullptr; + int _initial_line = 0; int _argument_count = 0; int _stack_size = 0; int _instruction_args_size = 0; - int _ptrcall_args_size = 0; - - int _initial_line = 0; - bool _static = false; - Variant rpc_config; - GDScript *_script = nullptr; + SelfList<GDScriptFunction> function_list{ this }; + mutable Variant nil; + HashMap<int, Variant::Type> temporary_slots; + List<StackDebug> stack_debug; - StringName name; + Vector<int> code; + Vector<int> default_arguments; Vector<Variant> constants; Vector<StringName> global_names; - Vector<int> default_arguments; Vector<Variant::ValidatedOperatorEvaluator> operator_funcs; Vector<Variant::ValidatedSetter> setters; Vector<Variant::ValidatedGetter> getters; @@ -503,18 +411,47 @@ private: Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; Vector<MethodBind *> methods; Vector<GDScriptFunction *> lambdas; - Vector<int> code; - Vector<GDScriptDataType> argument_types; - GDScriptDataType return_type; - HashMap<int, Variant::Type> temporary_slots; + int _code_size = 0; + int _default_arg_count = 0; + int _constant_count = 0; + int _global_names_count = 0; + int _operator_funcs_count = 0; + int _setters_count = 0; + int _getters_count = 0; + int _keyed_setters_count = 0; + int _keyed_getters_count = 0; + int _indexed_setters_count = 0; + int _indexed_getters_count = 0; + int _builtin_methods_count = 0; + int _constructors_count = 0; + int _utilities_count = 0; + int _gds_utilities_count = 0; + int _methods_count = 0; + int _lambdas_count = 0; -#ifdef TOOLS_ENABLED - Vector<StringName> arg_names; - Vector<Variant> default_arg_values; -#endif + int *_code_ptr = nullptr; + const int *_default_arg_ptr = nullptr; + mutable Variant *_constants_ptr = nullptr; + const StringName *_global_names_ptr = nullptr; + const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; + const Variant::ValidatedSetter *_setters_ptr = nullptr; + const Variant::ValidatedGetter *_getters_ptr = nullptr; + const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; + const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; + const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; + const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; + const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; + const Variant::ValidatedConstructor *_constructors_ptr = nullptr; + const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; + const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; + MethodBind **_methods_ptr = nullptr; + GDScriptFunction **_lambdas_ptr = nullptr; #ifdef DEBUG_ENABLED + CharString func_cname; + const char *_func_cname = nullptr; + Vector<String> operator_names; Vector<String> setter_names; Vector<String> getter_names; @@ -522,20 +459,6 @@ private: Vector<String> constructors_names; Vector<String> utilities_names; Vector<String> gds_utilities_names; -#endif - - List<StackDebug> stack_debug; - - Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); - - _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; - - friend class GDScriptLanguage; - - SelfList<GDScriptFunction> function_list{ this }; -#ifdef DEBUG_ENABLED - CharString func_cname; - const char *_func_cname = nullptr; struct Profile { StringName signature; @@ -549,9 +472,11 @@ private: uint64_t last_frame_self_time = 0; uint64_t last_frame_total_time = 0; } profile; - #endif + _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; + Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); + public: static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow. @@ -571,51 +496,24 @@ public: Variant result; }; + _FORCE_INLINE_ StringName get_name() const { return name; } + _FORCE_INLINE_ StringName get_source() const { return source; } + _FORCE_INLINE_ GDScript *get_script() const { return _script; } _FORCE_INLINE_ bool is_static() const { return _static; } + _FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; } + _FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; } - const int *get_code() const; //used for debug - int get_code_size() const; Variant get_constant(int p_idx) const; StringName get_global_name(int p_idx) const; - StringName get_name() const; - int get_max_stack_size() const; - int get_default_argument_count() const; - int get_default_argument_addr(int p_idx) const; - GDScriptDataType get_return_type() const; - GDScriptDataType get_argument_type(int p_idx) const; - GDScript *get_script() const { return _script; } - StringName get_source() const { return source; } - - void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const; - - _FORCE_INLINE_ bool is_empty() const { return _code_size == 0; } - - int get_argument_count() const { return _argument_count; } - StringName get_argument_name(int p_idx) const { -#ifdef TOOLS_ENABLED - ERR_FAIL_INDEX_V(p_idx, arg_names.size(), StringName()); - return arg_names[p_idx]; -#else - return StringName(); -#endif - } - Variant get_default_argument(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant()); - return default_arguments[p_idx]; - } -#ifdef TOOLS_ENABLED - const Vector<Variant> &get_default_arg_values() const { - return default_arg_values; - } -#endif // TOOLS_ENABLED Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); + void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const; #ifdef DEBUG_ENABLED void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ const Variant get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 3b89f077bd..547f5607d3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -44,11 +44,18 @@ bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const Calla return p_a < p_b; } +bool GDScriptLambdaCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaCallable::hash() const { return h; } String GDScriptLambdaCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -74,29 +81,78 @@ StringName GDScriptLambdaCallable::get_method() const { void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { int captures_amount = captures.size(); + if (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + if (captures_amount > 0) { Vector<const Variant *> args; args.resize(p_argcount + captures_amount); for (int i = 0; i < captures_amount; i++) { args.write[i] = &captures[i]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } for (int i = 0; i < p_argcount; i++) { args.write[i + captures_amount] = p_arguments[i]; } r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); - r_call_error.argument -= captures_amount; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } else { r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error); } } GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_script.ptr()); + ERR_FAIL_NULL(p_function); script = p_script; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + updatable_func_ptr_element = p_script->_add_func_ptr_to_update(&function); +} + +GDScriptLambdaCallable::~GDScriptLambdaCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -109,11 +165,18 @@ bool GDScriptLambdaSelfCallable::compare_less(const CallableCustom *p_a, const C return p_a < p_b; } +bool GDScriptLambdaSelfCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaSelfCallable::hash() const { return h; } String GDScriptLambdaSelfCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -143,36 +206,95 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun int captures_amount = captures.size(); + if (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + if (captures_amount > 0) { Vector<const Variant *> args; args.resize(p_argcount + captures_amount); for (int i = 0; i < captures_amount; i++) { args.write[i] = &captures[i]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } for (int i = 0; i < p_argcount; i++) { args.write[i + captures_amount] = p_arguments[i]; } r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error); - r_call_error.argument -= captures_amount; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } else { r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), p_arguments, p_argcount, r_call_error); } } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self.ptr()); + ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self); + ERR_FAIL_NULL(p_function); object = p_self; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } +} + +GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 1c7a18fb9d..ee7d547544 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -31,12 +31,13 @@ #ifndef GDSCRIPT_LAMBDA_CALLABLE_H #define GDSCRIPT_LAMBDA_CALLABLE_H +#include "gdscript.h" + #include "core/object/ref_counted.h" #include "core/templates/vector.h" #include "core/variant/callable.h" #include "core/variant/variant.h" -class GDScript; class GDScriptFunction; class GDScriptInstance; @@ -44,6 +45,7 @@ class GDScriptLambdaCallable : public CallableCustom { GDScriptFunction *function = nullptr; Ref<GDScript> script; uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -51,6 +53,7 @@ class GDScriptLambdaCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -60,7 +63,7 @@ public: void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaCallable() = default; + virtual ~GDScriptLambdaCallable(); }; // Lambda callable that references a particular object, so it can use `self` in the body. @@ -69,6 +72,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference. Object *object = nullptr; // For non RefCounted objects, use a direct pointer. uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -76,6 +80,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -85,7 +90,7 @@ public: GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaSelfCallable() = default; + virtual ~GDScriptLambdaSelfCallable(); }; #endif // GDSCRIPT_LAMBDA_CALLABLE_H diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a0d02b12b5..db7b3e7ace 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -32,10 +32,6 @@ #include "gdscript.h" -#ifdef DEBUG_ENABLED -#include "gdscript_warning.h" -#endif - #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -52,11 +48,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } @@ -170,7 +173,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { #ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { - ERR_FAIL_COND(p_source == nullptr); + ERR_FAIL_NULL(p_source); if (is_ignoring_warnings) { return; } @@ -383,8 +386,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() { push_error(current.literal); current = tokenizer.scan(); } - for (Node *n : nodes_in_progress) { - update_extents(n); + if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line. + for (Node *n : nodes_in_progress) { + update_extents(n); + } } return previous; } @@ -510,7 +515,7 @@ void GDScriptParser::parse_program() { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { // `@icon` needs to be applied in the parser. See GH-72444. if (annotation->name == SNAME("@icon")) { - annotation->apply(this, head); + annotation->apply(this, head, nullptr); } else { head->annotations.push_back(annotation); } @@ -579,13 +584,14 @@ void GDScriptParser::parse_program() { complete_extents(head); #ifdef TOOLS_ENABLED - for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { - if (E.value.new_line && E.value.comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E.key); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + int line = MIN(max_script_doc_line, head->end_line); + while (line > 0) { + if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { + head->doc_data = parse_class_doc_comment(line); + break; } - } - if (has_comment(class_doc_line, true)) { - head->doc_data = parse_class_doc_comment(class_doc_line, false); + line--; } #endif // TOOLS_ENABLED @@ -747,10 +753,6 @@ template <class T> void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { advance(); -#ifdef TOOLS_ENABLED - int doc_comment_line = previous.start_line - 1; -#endif // TOOLS_ENABLED - // Consume annotations. List<AnnotationNode *> annotations; while (!annotation_stack.is_empty()) { @@ -762,11 +764,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); clear_unused_annotations(); } -#ifdef TOOLS_ENABLED - if (last_annotation->start_line == doc_comment_line) { - doc_comment_line--; - } -#endif // TOOLS_ENABLED } T *member = (this->*p_parse_function)(p_is_static); @@ -774,28 +771,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); +#ifdef TOOLS_ENABLED + if (annotation->start_line <= doc_comment_line) { + doc_comment_line = annotation->start_line - 1; + } +#endif // TOOLS_ENABLED } #ifdef TOOLS_ENABLED - // Consume doc comments. - class_doc_line = MIN(class_doc_line, doc_comment_line - 1); - - // Check whether current line has a doc comment - if (has_comment(previous.start_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(previous.start_line, true, true); - } else { - member->doc_data = parse_doc_comment(previous.start_line, true); + if constexpr (std::is_same_v<T, ClassNode>) { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_class_doc_comment(member->start_line, true); + } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members. + // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice. + member->doc_data = parse_class_doc_comment(doc_comment_line); } - } else if (has_comment(doc_comment_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(doc_comment_line, true); - } else { + } else { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_doc_comment(member->start_line, true); + } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. member->doc_data = parse_doc_comment(doc_comment_line); } } + + min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment. #endif // TOOLS_ENABLED if (member->identifier != nullptr) { @@ -1263,6 +1272,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); +#ifdef TOOLS_ENABLED + int min_enum_value_doc_line = previous.end_line + 1; +#endif HashMap<StringName, int> elements; @@ -1325,43 +1337,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { } } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); - consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - #ifdef TOOLS_ENABLED // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { - int doc_comment_line = enum_node->values[i].line; - bool single_line = false; - - if (has_comment(doc_comment_line, true)) { - single_line = true; - } else if (has_comment(doc_comment_line - 1, true)) { - doc_comment_line--; - } else { - continue; - } - - if (i == enum_node->values.size() - 1) { - // If close bracket is same line as last value. - if (doc_comment_line == previous.start_line) { - break; - } - } else { - // If two values are same line. - if (doc_comment_line == enum_node->values[i + 1].line) { - continue; + int enum_value_line = enum_node->values[i].line; + int doc_comment_line = enum_value_line - 1; + + MemberDocData doc_data; + if (has_comment(enum_value_line, true)) { + // Inline doc comment. + if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) { + doc_data = parse_doc_comment(enum_value_line, true); } + } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. + doc_data = parse_doc_comment(doc_comment_line); } if (named) { - enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + enum_node->values.write[i].doc_data = doc_data; } else { - current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data); } + + min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment. } #endif // TOOLS_ENABLED + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); complete_extents(enum_node); end_statement("enum"); @@ -2034,7 +2038,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + bool has_guard = false; + if (match(GDScriptTokenizer::Token::WHEN)) { + // Pattern guard. + // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope. + branch->guard_body = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { + SuiteNode::Local local(E.value, current_function); + local.type = SuiteNode::Local::PATTERN_BIND; + branch->guard_body->add_local(local); + } + } + + SuiteNode *parent_block = current_suite; + branch->guard_body->parent_block = parent_block; + current_suite = branch->guard_body; + + ExpressionNode *guard = parse_expression(false); + if (guard == nullptr) { + push_error(R"(Expected expression for pattern guard after "when".)"); + } else { + branch->guard_body->statements.append(guard); + } + current_suite = parent_block; + complete_extents(branch->guard_body); + + has_guard = true; + branch->has_wildcard = false; // If it has a guard, the wildcard might still not match. + } + + if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) { complete_extents(branch); return nullptr; } @@ -3454,31 +3488,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { } GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { - MemberDocData result; + ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { + line--; } - line--; } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3488,11 +3512,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + MemberDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3513,35 +3536,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool return result; } -GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { - ClassDocData result; +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) { + ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.brief = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - bool is_in_brief = true; - if (p_inner_class) { - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; - } + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { line--; } } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3551,11 +3561,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + bool is_in_brief = true; + ClassDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3630,14 +3640,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, } } - if (current_class->members.size() > 0) { - const ClassNode::Member &m = current_class->members[0]; - int first_member_line = m.get_line(); - if (first_member_line == line) { - result = ClassDocData(); // Clear result. - } - } - return result; } #endif // TOOLS_ENABLED @@ -3705,6 +3707,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // PASS, { nullptr, nullptr, PREC_NONE }, // RETURN, { nullptr, nullptr, PREC_NONE }, // MATCH, + { nullptr, nullptr, PREC_NONE }, // WHEN, // Keywords { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, { nullptr, nullptr, PREC_NONE }, // ASSERT, @@ -3788,12 +3791,12 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con return empty; } -bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class) { if (is_applied) { return true; } is_applied = true; - return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); + return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target, p_class); } bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { @@ -3839,7 +3842,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return true; } -bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED if (this->_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); @@ -3850,15 +3853,15 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); +bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); - ClassNode *p_class = static_cast<ClassNode *>(p_node); + ClassNode *class_node = static_cast<ClassNode *>(p_target); String path = p_annotation->resolved_arguments[0]; #ifdef DEBUG_ENABLED - if (!p_class->icon_path.is_empty()) { + if (!class_node->icon_path.is_empty()) { push_error(R"("@icon" annotation can only be used once.)", p_annotation); return false; } @@ -3868,27 +3871,27 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p } #endif // DEBUG_ENABLED - p_class->icon_path = path; + class_node->icon_path = path; if (path.is_empty() || path.is_absolute_path()) { - p_class->simplified_icon_path = path.simplify_path(); + class_node->simplified_icon_path = path.simplify_path(); } else if (path.is_relative_path()) { - p_class->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); + class_node->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); } else { - p_class->simplified_icon_path = path; + class_node->simplified_icon_path = path; } return true; } -bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); +bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); } - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation); return false; @@ -3903,10 +3906,11 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node } template <PropertyHint t_hint, Variant::Type t_type> -bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + ERR_FAIL_NULL_V(p_class, false); - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); return false; @@ -4095,27 +4099,38 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } break; case GDScriptParser::DataType::ENUM: { - variable->export_info.type = Variant::INT; - variable->export_info.hint = PROPERTY_HINT_ENUM; - - String enum_hint_string; - bool first = true; - for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { - if (!first) { - enum_hint_string += ","; - } else { - first = false; + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); } - enum_hint_string += E.key.operator String().capitalize().xml_escape(); - enum_hint_string += ":"; - enum_hint_string += String::num_int64(E.value).xml_escape(); - } - variable->export_info.hint_string = enum_hint_string; + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } } break; default: push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); - break; + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), variable); + return false; } if (is_array) { @@ -4160,7 +4175,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); if (annotation->resolved_arguments.is_empty()) { @@ -4192,7 +4207,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation return true; } -bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED bool has_error = false; for (const Variant &warning_name : p_annotation->resolved_arguments) { @@ -4201,7 +4216,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation); has_error = true; } else { - p_node->ignored_warnings.push_back(warning); + p_target->ignored_warnings.push_back(warning); } } @@ -4213,10 +4228,10 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); +bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); - FunctionNode *function = static_cast<FunctionNode *>(p_node); + FunctionNode *function = static_cast<FunctionNode *>(p_target); if (function->rpc_config.get_type() != Variant::NIL) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; @@ -4274,14 +4289,14 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } -bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) { +bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); - ClassNode *p_class = static_cast<ClassNode *>(p_target); - if (p_class->annotated_static_unload) { + ClassNode *class_node = static_cast<ClassNode *>(p_target); + if (class_node->annotated_static_unload) { push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); return false; } - p_class->annotated_static_unload = true; + class_node->annotated_static_unload = true; return true; } @@ -4370,6 +4385,104 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range."); } +PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const { + PropertyInfo result; + result.name = p_name; + result.usage = PROPERTY_USAGE_NONE; + + if (!is_hard_type()) { + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + return result; + } + + switch (kind) { + case BUILTIN: + result.type = builtin_type; + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + const DataType *elem_type = container_element_type; + switch (elem_type->kind) { + case BUILTIN: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = Variant::get_type_name(elem_type->builtin_type); + break; + case NATIVE: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = elem_type->native_type; + break; + case SCRIPT: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) { + result.hint_string = elem_type->script_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case CLASS: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) { + result.hint_string = elem_type->class_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case ENUM: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = String(elem_type->native_type).replace("::", "."); + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + break; + } + } + break; + case NATIVE: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScriptNativeClass::get_class_static(); + } else { + result.class_name = native_type; + } + break; + case SCRIPT: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static(); + } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) { + result.class_name = script_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case CLASS: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScript::get_class_static(); + } else if (class_type != nullptr && class_type->get_global_name() != StringName()) { + result.class_name = class_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case ENUM: + if (is_meta_type) { + result.type = Variant::DICTIONARY; + } else { + result.type = Variant::INT; + result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + result.class_name = String(native_type).replace("::", "."); + } + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + break; + } + + return result; +} + static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { switch (p_type) { case Variant::PACKED_BYTE_ARRAY: @@ -5293,7 +5406,7 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { } void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { - ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree."); + ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree."); if (p_parser.is_tool()) { push_line("@tool"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 9690784cba..4b46b98baa 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -147,14 +147,17 @@ public: _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == RESOLVING || kind == UNRESOLVED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } + String to_string() const; + _FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; } + PropertyInfo to_property_info(const String &p_name) const; _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { container_element_type = memnew(DataType(p_type)); } _FORCE_INLINE_ DataType get_container_element_type() const { - ERR_FAIL_COND_V(container_element_type == nullptr, DataType()); + ERR_FAIL_NULL_V(container_element_type, DataType()); return *container_element_type; } @@ -175,11 +178,11 @@ public: bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { - return true; // Can be consireded equal for parsing purposes. + return true; // Can be considered equal for parsing purposes. } if (type_source == INFERRED || p_other.type_source == INFERRED) { - return true; // Can be consireded equal for parsing purposes. + return true; // Can be considered equal for parsing purposes. } if (kind != p_other.kind) { @@ -360,7 +363,7 @@ public: bool is_resolved = false; bool is_applied = false; - bool apply(GDScriptParser *p_this, Node *p_target); + bool apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class); bool applies_to(uint32_t p_target_kinds) const; AnnotationNode() { @@ -749,6 +752,10 @@ public: bool resolved_interface = false; bool resolved_body = false; + StringName get_global_name() const { + return (outer == nullptr && identifier != nullptr) ? identifier->name : StringName(); + } + Member get_member(const StringName &p_name) const { return members[members_indices[p_name]]; } @@ -831,13 +838,13 @@ public: HashMap<StringName, int> parameters_indices; TypeNode *return_type = nullptr; SuiteNode *body = nullptr; - bool is_static = false; + bool is_static = false; // For lambdas it's determined in the analyzer. bool is_coroutine = false; Variant rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; -#ifdef TOOLS_ENABLED Vector<Variant> default_arg_values; +#ifdef TOOLS_ENABLED MemberDocData doc_data; #endif // TOOLS_ENABLED @@ -942,6 +949,7 @@ public: Vector<PatternNode *> patterns; SuiteNode *block = nullptr; bool has_wildcard = false; + SuiteNode *guard_body = nullptr; MatchBranchNode() { type = MATCH_BRANCH; @@ -1026,6 +1034,7 @@ public: IdentifierNode *identifier = nullptr; Vector<ParameterNode *> parameters; HashMap<StringName, int> parameters_indices; + MethodInfo method_info; #ifdef TOOLS_ENABLED MemberDocData doc_data; #endif // TOOLS_ENABLED @@ -1331,7 +1340,7 @@ private: bool in_lambda = false; bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed. - typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target); + typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); struct AnnotationInfo { enum TargetKind { NONE = 0, @@ -1452,16 +1461,16 @@ private: bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); - bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyHint t_hint, Variant::Type t_type> - bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); + bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyUsageFlags t_usage> - bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target); - bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); // Statements. Node *parse_statement(); VariableNode *parse_variable(bool p_is_static); @@ -1510,10 +1519,11 @@ private: TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED - int class_doc_line = 0x7FFFFFFF; + int max_script_doc_line = INT_MAX; + int min_member_doc_line = 1; bool has_comment(int p_line, bool p_must_be_doc = false); MemberDocData parse_doc_comment(int p_line, bool p_single_line = false); - ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false); + ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false); #endif // TOOLS_ENABLED public: @@ -1522,7 +1532,7 @@ public: bool is_tool() const { return _is_tool; } ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; - static Variant::Type get_builtin_type(const StringName &p_type); + static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index 199ea81330..df014d3cfe 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -73,12 +73,13 @@ void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Vari } GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) { + ERR_FAIL_NULL(p_object); object = p_object; method = p_method; h = method.hash(); h = hash_murmur3_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."); + ERR_FAIL_NULL_MSG(node, "RPC can only be defined on class that extends Node."); } Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 42b983ef45..98a3a1268f 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -99,6 +99,7 @@ static const char *token_names[] = { "pass", // PASS, "return", // RETURN, "match", // MATCH, + "when", // WHEN, // Keywords "as", // AS, "assert", // ASSERT, @@ -187,6 +188,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case WHEN: // New keyword, avoid breaking existing code. // Allow constants to be treated as regular identifiers. case CONST_PI: case CONST_INF: @@ -241,6 +243,7 @@ bool GDScriptTokenizer::Token::is_node_name() const { case VAR: case VOID: case WHILE: + case WHEN: case YIELD: return true; default: @@ -531,6 +534,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() { KEYWORD("void", Token::VOID) \ KEYWORD_GROUP('w') \ KEYWORD("while", Token::WHILE) \ + KEYWORD("when", Token::WHEN) \ KEYWORD_GROUP('y') \ KEYWORD("yield", Token::YIELD) \ KEYWORD_GROUP('I') \ @@ -857,10 +861,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { STRING_NODEPATH, }; + bool is_raw = false; bool is_multiline = false; StringType type = STRING_REGULAR; - if (_peek(-1) == '&') { + if (_peek(-1) == 'r') { + is_raw = true; + _advance(); + } else if (_peek(-1) == '&') { type = STRING_NAME; _advance(); } else if (_peek(-1) == '^') { @@ -890,7 +898,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { char32_t ch = _peek(); if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) { - Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + Token error; + if (is_raw) { + error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string."); + } else { + error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + } error.start_column = column; error.leftmost_column = error.start_column; error.end_column = column + 1; @@ -905,144 +918,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - // Grab escape character. - char32_t code = _peek(); - _advance(); - if (_is_at_end()) { - return make_error("Unterminated string."); - } + if (is_raw) { + if (_peek() == quote_char) { + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += quote_char; + } else if (_peek() == '\\') { // For `\\\"`. + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += '\\'; + } else { + result += '\\'; + } + } else { + // Grab escape character. + char32_t code = _peek(); + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - char32_t escaped = 0; - bool valid_escape = true; + char32_t escaped = 0; + bool valid_escape = true; - switch (code) { - case 'a': - escaped = '\a'; - break; - case 'b': - escaped = '\b'; - break; - case 'f': - escaped = '\f'; - break; - case 'n': - escaped = '\n'; - break; - case 'r': - escaped = '\r'; - break; - case 't': - escaped = '\t'; - break; - case 'v': - escaped = '\v'; - break; - case '\'': - escaped = '\''; - break; - case '\"': - escaped = '\"'; - break; - case '\\': - escaped = '\\'; - break; - case 'U': - case 'u': { - // Hexadecimal sequence. - int hex_len = (code == 'U') ? 6 : 4; - for (int j = 0; j < hex_len; j++) { - if (_is_at_end()) { - return make_error("Unterminated string."); + switch (code) { + case 'a': + escaped = '\a'; + break; + case 'b': + escaped = '\b'; + break; + case 'f': + escaped = '\f'; + break; + case 'n': + escaped = '\n'; + break; + case 'r': + escaped = '\r'; + break; + case 't': + escaped = '\t'; + break; + case 'v': + escaped = '\v'; + break; + case '\'': + escaped = '\''; + break; + case '\"': + escaped = '\"'; + break; + case '\\': + escaped = '\\'; + break; + case 'U': + case 'u': { + // Hexadecimal sequence. + 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 (is_digit(digit)) { + value = digit - '0'; + } else if (digit >= 'a' && digit <= 'f') { + value = digit - 'a'; + value += 10; + } else if (digit >= 'A' && digit <= 'F') { + value = digit - 'A'; + value += 10; + } else { + // Make error, but keep parsing the string. + Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); + error.start_column = column; + error.leftmost_column = error.start_column; + error.end_column = column + 1; + error.rightmost_column = error.end_column; + push_error(error); + valid_escape = false; + break; + } + + escaped <<= 4; + escaped |= value; + + _advance(); } - - char32_t digit = _peek(); - char32_t value = 0; - if (is_digit(digit)) { - value = digit - '0'; - } else if (digit >= 'a' && digit <= 'f') { - value = digit - 'a'; - value += 10; - } else if (digit >= 'A' && digit <= 'F') { - value = digit - 'A'; - value += 10; - } else { - // Make error, but keep parsing the string. - Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); - error.start_column = column; - error.leftmost_column = error.start_column; - error.end_column = column + 1; - error.rightmost_column = error.end_column; - push_error(error); - valid_escape = false; + } break; + case '\r': + if (_peek() != '\n') { + // Carriage return without newline in string. (???) + // Just add it to the string and keep going. + result += ch; + _advance(); break; } - - escaped <<= 4; - escaped |= value; - - _advance(); - } - } break; - case '\r': - if (_peek() != '\n') { - // Carriage return without newline in string. (???) - // Just add it to the string and keep going. - result += ch; - _advance(); + [[fallthrough]]; + case '\n': + // Escaping newline. + newline(false); + valid_escape = false; // Don't add to the string. break; - } - [[fallthrough]]; - case '\n': - // Escaping newline. - newline(false); - valid_escape = false; // Don't add to the string. - break; - default: - Token error = make_error("Invalid escape in string."); - error.start_column = column - 2; - error.leftmost_column = error.start_column; - push_error(error); - 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"); + default: + Token error = make_error("Invalid escape in string."); error.start_column = column - 2; error.leftmost_column = error.start_column; push_error(error); valid_escape = false; - prev = 0; + 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; + } } - } 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; + 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); - 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; + if (valid_escape) { + result += escaped; + } } } else if (ch == quote_char) { if (prev != 0) { @@ -1216,7 +1249,7 @@ void GDScriptTokenizer::check_indent() { if (line_continuation || multiline_mode) { // We cleared up all the whitespace at the beginning of the line. - // But if this is a continuation or multiline mode and we don't want any indentation change. + // If this is a line continuation or we're in multiline mode then we don't want any indentation changes. return; } @@ -1416,6 +1449,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (is_digit(c)) { return number(); + } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) { + // Raw string literals. + return string(); } else if (is_unicode_identifier_start(c)) { return potential_identifier(); } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 068393cee9..6dd8a98652 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -105,6 +105,7 @@ public: PASS, RETURN, MATCH, + WHEN, // Keywords AS, ASSERT, @@ -187,6 +188,8 @@ public: #ifdef TOOLS_ENABLED struct CommentData { String comment; + // true: Comment starts at beginning of line or after indentation. + // false: Inline comment (starts after some code). bool new_line = false; CommentData() {} CommentData(const String &p_comment, bool p_new_line) { diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 030950267d..40c564c36b 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -45,14 +45,12 @@ #define VALIDATE_ARG_COUNT(m_count) \ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ @@ -85,6 +83,7 @@ #endif struct GDScriptUtilityFunctionsDefinitions { +#ifndef DISABLE_DEPRECATED static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(2); VALIDATE_ARG_INT(1); @@ -98,8 +97,12 @@ struct GDScriptUtilityFunctionsDefinitions { } else { Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type))); + } } } +#endif // DISABLE_DEPRECATED static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(1); @@ -117,7 +120,6 @@ struct GDScriptUtilityFunctionsDefinitions { switch (p_arg_count) { case 0: { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; r_error.expected = 1; *r_ret = Variant(); } break; @@ -131,8 +133,8 @@ struct GDScriptUtilityFunctionsDefinitions { } Error err = arr.resize(count); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } @@ -156,8 +158,8 @@ struct GDScriptUtilityFunctionsDefinitions { } Error err = arr.resize(to - from); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } for (int i = from; i < to; i++) { @@ -200,8 +202,8 @@ struct GDScriptUtilityFunctionsDefinitions { Error err = arr.resize(count); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } @@ -221,7 +223,6 @@ struct GDScriptUtilityFunctionsDefinitions { } break; default: { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; r_error.expected = 3; *r_ret = Variant(); @@ -249,6 +250,7 @@ struct GDScriptUtilityFunctionsDefinitions { } else if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; + r_error.expected = Variant::OBJECT; *r_ret = Variant(); } else { Object *obj = *p_args[0]; @@ -277,7 +279,7 @@ struct GDScriptUtilityFunctionsDefinitions { Vector<StringName> sname; while (p->_owner) { - sname.push_back(p->name); + sname.push_back(p->local_name); p = p->_owner; } sname.reverse(); @@ -371,7 +373,7 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); if (r_error.error != Callable::CallError::CALL_OK) { - *r_ret = Variant(); + *r_ret = RTR("Cannot instantiate GDScript class."); return; } @@ -388,13 +390,13 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { if (p_arg_count < 3) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 3; + r_error.expected = 3; *r_ret = Variant(); return; } if (p_arg_count > 4) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; + r_error.expected = 4; *r_ret = Variant(); return; } @@ -703,7 +705,9 @@ static void _register_function(const String &p_name, const MethodInfo &p_method_ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT) void GDScriptUtilityFunctions::register_functions() { +#ifndef DISABLE_DEPRECATED REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); +#endif // DISABLE_DEPRECATED REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); @@ -725,50 +729,50 @@ void GDScriptUtilityFunctions::unregister_functions() { GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, nullptr); + ERR_FAIL_NULL_V(info, nullptr); return info->function; } bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); } Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); return info->info.return_val.type; } StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, StringName()); + ERR_FAIL_NULL_V(info, StringName()); return info->info.return_val.class_name; } Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL); return info->info.arguments[p_arg].type; } int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, 0); + ERR_FAIL_NULL_V(info, 0); return info->info.arguments.size(); } bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return (bool)(info->info.flags & METHOD_FLAG_VARARG); } bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->is_constant; } @@ -784,6 +788,6 @@ void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, MethodInfo()); + ERR_FAIL_NULL_V(info, MethodInfo()); return info->info; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 1ddd54b323..d31411b26b 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -116,6 +116,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { int errorarg = p_err.argument; + ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer."); // Handle the Object to Object case separately as we don't have further class details. #ifdef DEBUG_ENABLED if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { @@ -128,9 +129,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; } } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { err_text = "Invalid call. Nonexistent " + p_where + "."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { @@ -186,191 +187,155 @@ void (*type_init_function_table[])(Variant *) = { }; #if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_OPERATOR_VALIDATED, \ - &&OPCODE_TYPE_TEST_BUILTIN, \ - &&OPCODE_TYPE_TEST_ARRAY, \ - &&OPCODE_TYPE_TEST_NATIVE, \ - &&OPCODE_TYPE_TEST_SCRIPT, \ - &&OPCODE_SET_KEYED, \ - &&OPCODE_SET_KEYED_VALIDATED, \ - &&OPCODE_SET_INDEXED_VALIDATED, \ - &&OPCODE_GET_KEYED, \ - &&OPCODE_GET_KEYED_VALIDATED, \ - &&OPCODE_GET_INDEXED_VALIDATED, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_SET_NAMED_VALIDATED, \ - &&OPCODE_GET_NAMED, \ - &&OPCODE_GET_NAMED_VALIDATED, \ - &&OPCODE_SET_MEMBER, \ - &&OPCODE_GET_MEMBER, \ - &&OPCODE_SET_STATIC_VARIABLE, \ - &&OPCODE_GET_STATIC_VARIABLE, \ - &&OPCODE_ASSIGN, \ - &&OPCODE_ASSIGN_TRUE, \ - &&OPCODE_ASSIGN_FALSE, \ - &&OPCODE_ASSIGN_TYPED_BUILTIN, \ - &&OPCODE_ASSIGN_TYPED_ARRAY, \ - &&OPCODE_ASSIGN_TYPED_NATIVE, \ - &&OPCODE_ASSIGN_TYPED_SCRIPT, \ - &&OPCODE_CAST_TO_BUILTIN, \ - &&OPCODE_CAST_TO_NATIVE, \ - &&OPCODE_CAST_TO_SCRIPT, \ - &&OPCODE_CONSTRUCT, \ - &&OPCODE_CONSTRUCT_VALIDATED, \ - &&OPCODE_CONSTRUCT_ARRAY, \ - &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ - &&OPCODE_CONSTRUCT_DICTIONARY, \ - &&OPCODE_CALL, \ - &&OPCODE_CALL_RETURN, \ - &&OPCODE_CALL_ASYNC, \ - &&OPCODE_CALL_UTILITY, \ - &&OPCODE_CALL_UTILITY_VALIDATED, \ - &&OPCODE_CALL_GDSCRIPT_UTILITY, \ - &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ - &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_CALL_METHOD_BIND, \ - &&OPCODE_CALL_METHOD_BIND_RET, \ - &&OPCODE_CALL_BUILTIN_STATIC, \ - &&OPCODE_CALL_NATIVE_STATIC, \ - &&OPCODE_CALL_PTRCALL_NO_RETURN, \ - &&OPCODE_CALL_PTRCALL_BOOL, \ - &&OPCODE_CALL_PTRCALL_INT, \ - &&OPCODE_CALL_PTRCALL_FLOAT, \ - &&OPCODE_CALL_PTRCALL_STRING, \ - &&OPCODE_CALL_PTRCALL_VECTOR2, \ - &&OPCODE_CALL_PTRCALL_VECTOR2I, \ - &&OPCODE_CALL_PTRCALL_RECT2, \ - &&OPCODE_CALL_PTRCALL_RECT2I, \ - &&OPCODE_CALL_PTRCALL_VECTOR3, \ - &&OPCODE_CALL_PTRCALL_VECTOR3I, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \ - &&OPCODE_CALL_PTRCALL_VECTOR4, \ - &&OPCODE_CALL_PTRCALL_VECTOR4I, \ - &&OPCODE_CALL_PTRCALL_PLANE, \ - &&OPCODE_CALL_PTRCALL_QUATERNION, \ - &&OPCODE_CALL_PTRCALL_AABB, \ - &&OPCODE_CALL_PTRCALL_BASIS, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM3D, \ - &&OPCODE_CALL_PTRCALL_PROJECTION, \ - &&OPCODE_CALL_PTRCALL_COLOR, \ - &&OPCODE_CALL_PTRCALL_STRING_NAME, \ - &&OPCODE_CALL_PTRCALL_NODE_PATH, \ - &&OPCODE_CALL_PTRCALL_RID, \ - &&OPCODE_CALL_PTRCALL_OBJECT, \ - &&OPCODE_CALL_PTRCALL_CALLABLE, \ - &&OPCODE_CALL_PTRCALL_SIGNAL, \ - &&OPCODE_CALL_PTRCALL_DICTIONARY, \ - &&OPCODE_CALL_PTRCALL_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \ - &&OPCODE_AWAIT, \ - &&OPCODE_AWAIT_RESUME, \ - &&OPCODE_CREATE_LAMBDA, \ - &&OPCODE_CREATE_SELF_LAMBDA, \ - &&OPCODE_JUMP, \ - &&OPCODE_JUMP_IF, \ - &&OPCODE_JUMP_IF_NOT, \ - &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ - &&OPCODE_JUMP_IF_SHARED, \ - &&OPCODE_RETURN, \ - &&OPCODE_RETURN_TYPED_BUILTIN, \ - &&OPCODE_RETURN_TYPED_ARRAY, \ - &&OPCODE_RETURN_TYPED_NATIVE, \ - &&OPCODE_RETURN_TYPED_SCRIPT, \ - &&OPCODE_ITERATE_BEGIN, \ - &&OPCODE_ITERATE_BEGIN_INT, \ - &&OPCODE_ITERATE_BEGIN_FLOAT, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ - &&OPCODE_ITERATE_BEGIN_STRING, \ - &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ - &&OPCODE_ITERATE_BEGIN_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_OBJECT, \ - &&OPCODE_ITERATE, \ - &&OPCODE_ITERATE_INT, \ - &&OPCODE_ITERATE_FLOAT, \ - &&OPCODE_ITERATE_VECTOR2, \ - &&OPCODE_ITERATE_VECTOR2I, \ - &&OPCODE_ITERATE_VECTOR3, \ - &&OPCODE_ITERATE_VECTOR3I, \ - &&OPCODE_ITERATE_STRING, \ - &&OPCODE_ITERATE_DICTIONARY, \ - &&OPCODE_ITERATE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_OBJECT, \ - &&OPCODE_STORE_GLOBAL, \ - &&OPCODE_STORE_NAMED_GLOBAL, \ - &&OPCODE_TYPE_ADJUST_BOOL, \ - &&OPCODE_TYPE_ADJUST_INT, \ - &&OPCODE_TYPE_ADJUST_FLOAT, \ - &&OPCODE_TYPE_ADJUST_STRING, \ - &&OPCODE_TYPE_ADJUST_VECTOR2, \ - &&OPCODE_TYPE_ADJUST_VECTOR2I, \ - &&OPCODE_TYPE_ADJUST_RECT2, \ - &&OPCODE_TYPE_ADJUST_RECT2I, \ - &&OPCODE_TYPE_ADJUST_VECTOR3, \ - &&OPCODE_TYPE_ADJUST_VECTOR3I, \ - &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ - &&OPCODE_TYPE_ADJUST_VECTOR4, \ - &&OPCODE_TYPE_ADJUST_VECTOR4I, \ - &&OPCODE_TYPE_ADJUST_PLANE, \ - &&OPCODE_TYPE_ADJUST_QUATERNION, \ - &&OPCODE_TYPE_ADJUST_AABB, \ - &&OPCODE_TYPE_ADJUST_BASIS, \ - &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ - &&OPCODE_TYPE_ADJUST_PROJECTION, \ - &&OPCODE_TYPE_ADJUST_COLOR, \ - &&OPCODE_TYPE_ADJUST_STRING_NAME, \ - &&OPCODE_TYPE_ADJUST_NODE_PATH, \ - &&OPCODE_TYPE_ADJUST_RID, \ - &&OPCODE_TYPE_ADJUST_OBJECT, \ - &&OPCODE_TYPE_ADJUST_CALLABLE, \ - &&OPCODE_TYPE_ADJUST_SIGNAL, \ - &&OPCODE_TYPE_ADJUST_DICTIONARY, \ - &&OPCODE_TYPE_ADJUST_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ - &&OPCODE_ASSERT, \ - &&OPCODE_BREAKPOINT, \ - &&OPCODE_LINE, \ - &&OPCODE_END \ - }; \ +#define OPCODES_TABLE \ + static const void *switch_table_ops[] = { \ + &&OPCODE_OPERATOR, \ + &&OPCODE_OPERATOR_VALIDATED, \ + &&OPCODE_TYPE_TEST_BUILTIN, \ + &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_NATIVE, \ + &&OPCODE_TYPE_TEST_SCRIPT, \ + &&OPCODE_SET_KEYED, \ + &&OPCODE_SET_KEYED_VALIDATED, \ + &&OPCODE_SET_INDEXED_VALIDATED, \ + &&OPCODE_GET_KEYED, \ + &&OPCODE_GET_KEYED_VALIDATED, \ + &&OPCODE_GET_INDEXED_VALIDATED, \ + &&OPCODE_SET_NAMED, \ + &&OPCODE_SET_NAMED_VALIDATED, \ + &&OPCODE_GET_NAMED, \ + &&OPCODE_GET_NAMED_VALIDATED, \ + &&OPCODE_SET_MEMBER, \ + &&OPCODE_GET_MEMBER, \ + &&OPCODE_SET_STATIC_VARIABLE, \ + &&OPCODE_GET_STATIC_VARIABLE, \ + &&OPCODE_ASSIGN, \ + &&OPCODE_ASSIGN_TRUE, \ + &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ + &&OPCODE_CONSTRUCT, \ + &&OPCODE_CONSTRUCT_VALIDATED, \ + &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ + &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CALL, \ + &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ + &&OPCODE_CALL_UTILITY, \ + &&OPCODE_CALL_UTILITY_VALIDATED, \ + &&OPCODE_CALL_GDSCRIPT_UTILITY, \ + &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ + &&OPCODE_CALL_SELF_BASE, \ + &&OPCODE_CALL_METHOD_BIND, \ + &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_BUILTIN_STATIC, \ + &&OPCODE_CALL_NATIVE_STATIC, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \ + &&OPCODE_AWAIT, \ + &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_CREATE_LAMBDA, \ + &&OPCODE_CREATE_SELF_LAMBDA, \ + &&OPCODE_JUMP, \ + &&OPCODE_JUMP_IF, \ + &&OPCODE_JUMP_IF_NOT, \ + &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_JUMP_IF_SHARED, \ + &&OPCODE_RETURN, \ + &&OPCODE_RETURN_TYPED_BUILTIN, \ + &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_NATIVE, \ + &&OPCODE_RETURN_TYPED_SCRIPT, \ + &&OPCODE_ITERATE_BEGIN, \ + &&OPCODE_ITERATE_BEGIN_INT, \ + &&OPCODE_ITERATE_BEGIN_FLOAT, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ + &&OPCODE_ITERATE_BEGIN_STRING, \ + &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ + &&OPCODE_ITERATE_BEGIN_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_OBJECT, \ + &&OPCODE_ITERATE, \ + &&OPCODE_ITERATE_INT, \ + &&OPCODE_ITERATE_FLOAT, \ + &&OPCODE_ITERATE_VECTOR2, \ + &&OPCODE_ITERATE_VECTOR2I, \ + &&OPCODE_ITERATE_VECTOR3, \ + &&OPCODE_ITERATE_VECTOR3I, \ + &&OPCODE_ITERATE_STRING, \ + &&OPCODE_ITERATE_DICTIONARY, \ + &&OPCODE_ITERATE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ + &&OPCODE_STORE_NAMED_GLOBAL, \ + &&OPCODE_TYPE_ADJUST_BOOL, \ + &&OPCODE_TYPE_ADJUST_INT, \ + &&OPCODE_TYPE_ADJUST_FLOAT, \ + &&OPCODE_TYPE_ADJUST_STRING, \ + &&OPCODE_TYPE_ADJUST_VECTOR2, \ + &&OPCODE_TYPE_ADJUST_VECTOR2I, \ + &&OPCODE_TYPE_ADJUST_RECT2, \ + &&OPCODE_TYPE_ADJUST_RECT2I, \ + &&OPCODE_TYPE_ADJUST_VECTOR3, \ + &&OPCODE_TYPE_ADJUST_VECTOR3I, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ + &&OPCODE_TYPE_ADJUST_VECTOR4, \ + &&OPCODE_TYPE_ADJUST_VECTOR4I, \ + &&OPCODE_TYPE_ADJUST_PLANE, \ + &&OPCODE_TYPE_ADJUST_QUATERNION, \ + &&OPCODE_TYPE_ADJUST_AABB, \ + &&OPCODE_TYPE_ADJUST_BASIS, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ + &&OPCODE_TYPE_ADJUST_PROJECTION, \ + &&OPCODE_TYPE_ADJUST_COLOR, \ + &&OPCODE_TYPE_ADJUST_STRING_NAME, \ + &&OPCODE_TYPE_ADJUST_NODE_PATH, \ + &&OPCODE_TYPE_ADJUST_RID, \ + &&OPCODE_TYPE_ADJUST_OBJECT, \ + &&OPCODE_TYPE_ADJUST_CALLABLE, \ + &&OPCODE_TYPE_ADJUST_SIGNAL, \ + &&OPCODE_TYPE_ADJUST_DICTIONARY, \ + &&OPCODE_TYPE_ADJUST_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ + &&OPCODE_ASSERT, \ + &&OPCODE_BREAKPOINT, \ + &&OPCODE_LINE, \ + &&OPCODE_END \ + }; \ static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); #define OPCODE(m_op) \ @@ -397,7 +362,13 @@ void (*type_init_function_table[])(Variant *) = { #define OPCODES_END #define OPCODES_OUT #define DISPATCH_OPCODE continue +#ifdef _MSC_VER +#define OPCODE_SWITCH(m_test) \ + __assume(m_test <= OPCODE_END); \ + switch (m_test) +#else #define OPCODE_SWITCH(m_test) switch (m_test) +#endif #define OPCODE_BREAK break #define OPCODE_OUT break #endif @@ -466,8 +437,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) { - err_func = p_instance->script->name + "." + err_func; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->local_name != StringName()) { + err_func = p_instance->script->local_name.operator String() + "." + err_func; } int err_line = _initial_line; const char *err_text = "Stack overflow. Check for infinite recursion in your script."; @@ -482,7 +453,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Variant retvalue; Variant *stack = nullptr; Variant **instruction_args = nullptr; - const void **call_args_ptr = nullptr; int defarg = 0; #ifdef DEBUG_ENABLED @@ -511,13 +481,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (p_argcount != _argument_count) { if (p_argcount > _argument_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; - + r_err.expected = _argument_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; + r_err.expected = _argument_count - _default_arg_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else { @@ -572,12 +541,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } - if (_ptrcall_args_size) { - call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *)); - } else { - call_args_ptr = nullptr; - } - if (p_instance) { memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner)); script = p_instance->script.ptr(); @@ -699,6 +662,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a uint32_t op_signature = _code_ptr[ip + 5]; uint32_t actual_signature = (a->get_type() << 8) | (b->get_type()); +#ifdef DEBUG_ENABLED + if (op == Variant::OP_DIVIDE || op == Variant::OP_MODULE) { + // Don't optimize division and modulo since there's not check for division by zero with validated calls. + op_signature = 0xFFFF; + _code_ptr[ip + 5] = op_signature; + } +#endif + // Check if this is the first run. If so, store the current signature for the optimized path. if (unlikely(op_signature == 0)) { static Mutex initializer_mutex; @@ -1948,106 +1919,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; -#ifdef DEBUG_ENABLED -#define OPCODE_CALL_PTR(m_type) \ - OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ - LOAD_INSTRUCTION_ARGS \ - CHECK_SPACE(3 + instr_arg_count); \ - ip += instr_arg_count; \ - int argc = _code_ptr[ip + 1]; \ - GD_ERR_BREAK(argc < 0); \ - GET_INSTRUCTION_ARG(base, argc); \ - GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ - bool freed = false; \ - Object *base_obj = base->get_validated_object_with_check(freed); \ - if (freed) { \ - err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \ - OPCODE_BREAK; \ - } else if (!base_obj) { \ - err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \ - OPCODE_BREAK; \ - } \ - const void **argptrs = call_args_ptr; \ - for (int i = 0; i < argc; i++) { \ - GET_INSTRUCTION_ARG(v, i); \ - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ - } \ - uint64_t call_time = 0; \ - if (GDScriptLanguage::get_singleton()->profiling) { \ - call_time = OS::get_singleton()->get_ticks_usec(); \ - } \ - GET_INSTRUCTION_ARG(ret, argc + 1); \ - VariantInternal::initialize(ret, Variant::m_type); \ - void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ - method->ptrcall(base_obj, argptrs, ret_opaque); \ - if (GDScriptLanguage::get_singleton()->profiling) { \ - function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \ - } \ - ip += 3; \ - } \ - DISPATCH_OPCODE -#else -#define OPCODE_CALL_PTR(m_type) \ - OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ - LOAD_INSTRUCTION_ARGS \ - CHECK_SPACE(3 + instr_arg_count); \ - ip += instr_arg_count; \ - int argc = _code_ptr[ip + 1]; \ - GET_INSTRUCTION_ARG(base, argc); \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ - Object *base_obj = *VariantInternal::get_object(base); \ - const void **argptrs = call_args_ptr; \ - for (int i = 0; i < argc; i++) { \ - GET_INSTRUCTION_ARG(v, i); \ - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ - } \ - GET_INSTRUCTION_ARG(ret, argc + 1); \ - VariantInternal::initialize(ret, Variant::m_type); \ - void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ - method->ptrcall(base_obj, argptrs, ret_opaque); \ - ip += 3; \ - } \ - DISPATCH_OPCODE -#endif - - OPCODE_CALL_PTR(BOOL); - OPCODE_CALL_PTR(INT); - OPCODE_CALL_PTR(FLOAT); - OPCODE_CALL_PTR(STRING); - OPCODE_CALL_PTR(VECTOR2); - OPCODE_CALL_PTR(VECTOR2I); - OPCODE_CALL_PTR(RECT2); - OPCODE_CALL_PTR(RECT2I); - OPCODE_CALL_PTR(VECTOR3); - OPCODE_CALL_PTR(VECTOR3I); - OPCODE_CALL_PTR(TRANSFORM2D); - OPCODE_CALL_PTR(VECTOR4); - OPCODE_CALL_PTR(VECTOR4I); - OPCODE_CALL_PTR(PLANE); - OPCODE_CALL_PTR(QUATERNION); - OPCODE_CALL_PTR(AABB); - OPCODE_CALL_PTR(BASIS); - OPCODE_CALL_PTR(TRANSFORM3D); - OPCODE_CALL_PTR(PROJECTION); - OPCODE_CALL_PTR(COLOR); - OPCODE_CALL_PTR(STRING_NAME); - OPCODE_CALL_PTR(NODE_PATH); - OPCODE_CALL_PTR(RID); - OPCODE_CALL_PTR(CALLABLE); - OPCODE_CALL_PTR(SIGNAL); - OPCODE_CALL_PTR(DICTIONARY); - OPCODE_CALL_PTR(ARRAY); - OPCODE_CALL_PTR(PACKED_BYTE_ARRAY); - OPCODE_CALL_PTR(PACKED_INT32_ARRAY); - OPCODE_CALL_PTR(PACKED_INT64_ARRAY); - OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY); - OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY); - OPCODE_CALL_PTR(PACKED_STRING_ARRAY); - OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY); - OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY); - OPCODE_CALL_PTR(PACKED_COLOR_ARRAY); - OPCODE(OPCODE_CALL_PTRCALL_OBJECT) { + OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN) { LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); @@ -2060,6 +1932,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; GET_INSTRUCTION_ARG(base, argc); + #ifdef DEBUG_ENABLED bool freed = false; Object *base_obj = base->get_validated_object_with_check(freed); @@ -2074,12 +1947,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Object *base_obj = *VariantInternal::get_object(base); #endif - const void **argptrs = call_args_ptr; + Variant **argptrs = instruction_args; - for (int i = 0; i < argc; i++) { - GET_INSTRUCTION_ARG(v, i); - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); - } #ifdef DEBUG_ENABLED uint64_t call_time = 0; @@ -2089,16 +1958,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif GET_INSTRUCTION_ARG(ret, argc + 1); - VariantInternal::initialize(ret, Variant::OBJECT); - Object **ret_opaque = VariantInternal::get_object(ret); - method->ptrcall(base_obj, argptrs, ret_opaque); - if (method->is_return_type_raw_object_ptr()) { - // The Variant has to participate in the ref count since the method returns a raw Object *. - VariantInternal::object_assign(ret, *ret_opaque); - } else { - // The method, in case it returns something, returns an already encapsulated object. - VariantInternal::update_object_id(ret); - } + method->validated_call(base_obj, (const Variant **)argptrs, ret); #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { @@ -2108,7 +1968,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ip += 3; } DISPATCH_OPCODE; - OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) { + + OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN) { LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); @@ -2134,12 +1995,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #else Object *base_obj = *VariantInternal::get_object(base); #endif - const void **argptrs = call_args_ptr; - - for (int i = 0; i < argc; i++) { - GET_INSTRUCTION_ARG(v, i); - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); - } + Variant **argptrs = instruction_args; #ifdef DEBUG_ENABLED uint64_t call_time = 0; @@ -2150,7 +2006,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(ret, argc + 1); VariantInternal::initialize(ret, Variant::NIL); - method->ptrcall(base_obj, argptrs, nullptr); + method->validated_call(base_obj, (const Variant **)argptrs, nullptr); #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { @@ -2219,11 +2075,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { String methodstr = function; - if (dst->get_type() == Variant::STRING) { + if (dst->get_type() == Variant::STRING && !dst->operator String().is_empty()) { // Call provided error string. - err_text = "Error calling utility function '" + methodstr + "': " + String(*dst); + err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs); } OPCODE_BREAK; } @@ -2275,13 +2131,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - // TODO: Add this information in debug. - String methodstr = "<unknown function>"; - if (dst->get_type() == Variant::STRING) { + String methodstr = gds_utilities_names[_code_ptr[ip + 2]]; + if (dst->get_type() == Variant::STRING && !dst->operator String().is_empty()) { // Call provided error string. - err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); + err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs); } OPCODE_BREAK; } @@ -3649,8 +3504,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (instance_valid_with_script && !p_instance->script->name.is_empty()) { - err_func = p_instance->script->name + "." + err_func; + if (instance_valid_with_script && p_instance->script->local_name != StringName()) { + err_func = p_instance->script->local_name.operator String() + "." + err_func; } int err_line = line; if (err_text.is_empty()) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 4fec445995..1fe9b0425c 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -88,6 +88,15 @@ String GDScriptWarning::get_message() const { case FUNCTION_USED_AS_PROPERTY: CHECK_SYMBOLS(2); return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)", symbols[0], symbols[1]); + case UNTYPED_DECLARATION: + CHECK_SYMBOLS(2); + if (symbols[0] == "Function") { + return vformat(R"*(%s "%s()" has no static return type.)*", symbols[0], symbols[1]); + } + return vformat(R"(%s "%s" has no static type.)", symbols[0], symbols[1]); + case INFERRED_DECLARATION: + CHECK_SYMBOLS(2); + return vformat(R"(%s "%s" has an implicitly inferred static type.)", symbols[0], symbols[1]); case UNSAFE_PROPERTY_ACCESS: CHECK_SYMBOLS(2); return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]); @@ -98,8 +107,8 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(1); return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: - CHECK_SYMBOLS(4); - return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + CHECK_SYMBOLS(5); + return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); @@ -113,14 +122,6 @@ String GDScriptWarning::get_message() const { return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)"; case REDUNDANT_AWAIT: return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; - case REDUNDANT_FOR_VARIABLE_TYPE: - CHECK_SYMBOLS(3); - if (symbols[1] == symbols[2]) { - return vformat(R"(The for loop iterator "%s" already has inferred type "%s", the specified type is redundant.)", symbols[0], symbols[1]); - } else { - return vformat(R"(The for loop iterator "%s" has inferred type "%s" but its supertype "%s" is specified.)", symbols[0], symbols[1], symbols[2]); - } - break; case ASSERT_ALWAYS_TRUE: return "Assert statement is redundant because the expression is always true."; case ASSERT_ALWAYS_FALSE: @@ -208,6 +209,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "PROPERTY_USED_AS_FUNCTION", "CONSTANT_USED_AS_FUNCTION", "FUNCTION_USED_AS_PROPERTY", + "UNTYPED_DECLARATION", + "INFERRED_DECLARATION", "UNSAFE_PROPERTY_ACCESS", "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", @@ -217,7 +220,6 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "STATIC_CALLED_ON_INSTANCE", "REDUNDANT_STATIC_UNLOAD", "REDUNDANT_AWAIT", - "REDUNDANT_FOR_VARIABLE_TYPE", "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "INTEGER_DIVISION", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 73e12eb20e..1aef6fa81b 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -64,6 +64,8 @@ public: PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name. CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name. FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name. + UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred. + INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. @@ -73,7 +75,6 @@ public: STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). - REDUNDANT_FOR_VARIABLE_TYPE, // The for variable type specifier is a supertype of the inferred type. ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded. @@ -112,6 +113,8 @@ public: WARN, // PROPERTY_USED_AS_FUNCTION WARN, // CONSTANT_USED_AS_FUNCTION WARN, // FUNCTION_USED_AS_PROPERTY + IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings. + IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings. IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. @@ -121,7 +124,6 @@ public: WARN, // STATIC_CALLED_ON_INSTANCE WARN, // REDUNDANT_STATIC_UNLOAD WARN, // REDUNDANT_AWAIT - WARN, // REDUNDANT_FOR_VARIABLE_TYPE WARN, // ASSERT_ALWAYS_TRUE WARN, // ASSERT_ALWAYS_FALSE WARN, // INTEGER_DIVISION diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 3a5a54e275..36806d2f73 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -32,9 +32,94 @@ #include "../gdscript.h" #include "../gdscript_analyzer.h" +#include "editor/editor_settings.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" +int get_indent_size() { + if (EditorSettings::get_singleton()) { + return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); + } else { + return 4; + } +} + +lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const { + lsp::Position res; + + // Special case: `line = 0` -> root class (range covers everything). + if (this->line <= 0) { + return res; + } + // Special case: `line = p_lines.size() + 1` -> root class (range covers everything). + if (this->line >= p_lines.size() + 1) { + res.line = p_lines.size(); + return res; + } + res.line = this->line - 1; + // Note: character outside of `pos_line.length()-1` is valid. + res.character = this->column - 1; + + String pos_line = p_lines[res.line]; + if (pos_line.contains("\t")) { + int tab_size = get_indent_size(); + + int in_col = 1; + int res_char = 0; + + while (res_char < pos_line.size() && in_col < this->column) { + if (pos_line[res_char] == '\t') { + in_col += tab_size; + res_char++; + } else { + in_col++; + res_char++; + } + } + + res.character = res_char; + } + + return res; +} + +GodotPosition GodotPosition::from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines) { + GodotPosition res(p_pos.line + 1, p_pos.character + 1); + + // Line outside of actual text is valid (-> pos/cursor at end of text). + if (res.line > p_lines.size()) { + return res; + } + + String line = p_lines[p_pos.line]; + int tabs_before_char = 0; + for (int i = 0; i < p_pos.character && i < line.length(); i++) { + if (line[i] == '\t') { + tabs_before_char++; + } + } + + if (tabs_before_char > 0) { + int tab_size = get_indent_size(); + res.column += tabs_before_char * (tab_size - 1); + } + + return res; +} + +lsp::Range GodotRange::to_lsp(const Vector<String> &p_lines) const { + lsp::Range res; + res.start = start.to_lsp(p_lines); + res.end = end.to_lsp(p_lines); + return res; +} + +GodotRange GodotRange::from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines) { + GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines); + GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines); + return GodotRange(start, end); +} + void ExtendGDScriptParser::update_diagnostics() { diagnostics.clear(); @@ -90,7 +175,7 @@ void ExtendGDScriptParser::update_symbols() { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.insert(symbol.name, &symbol); - // cache level one inner classes + // Cache level one inner classes. if (symbol.kind == lsp::SymbolKind::Class) { ClassMembers inner_class; for (int j = 0; j < symbol.children.size(); j++) { @@ -126,10 +211,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { String value = const_val; lsp::DocumentLink link; link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path); - link.range.start.line = LINE_NUMBER_TO_INDEX(token.start_line); - link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line); - link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column); - link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column); + link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(this->lines); document_links.push_back(link); } } @@ -137,6 +219,12 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { } } +lsp::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const { + GodotPosition start(p_node->start_line, p_node->start_column); + GodotPosition end(p_node->end_line, p_node->end_column); + return GodotRange(start, end).to_lsp(this->lines); +} + void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); @@ -149,13 +237,30 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = p_class->start_line; - r_symbol.range.start.character = p_class->start_column; - r_symbol.range.end.line = lines.size(); - r_symbol.selectionRange.start.line = r_symbol.range.start.line; + r_symbol.range = range_of_node(p_class); + r_symbol.range.start.line = MAX(r_symbol.range.start.line, 0); + if (p_class->identifier) { + r_symbol.selectionRange = range_of_node(p_class->identifier); + } r_symbol.detail = "class " + r_symbol.name; - bool is_root_class = &r_symbol == &class_symbol; - r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class); + { + String doc = p_class->doc_data.description; + if (!p_class->doc_data.description.is_empty()) { + doc += "\n\n" + p_class->doc_data.description; + } + + if (!p_class->doc_data.tutorials.is_empty()) { + doc += "\n"; + for (const Pair<String, String> &tutorial : p_class->doc_data.tutorials) { + if (tutorial.first.is_empty()) { + doc += vformat("\n@tutorial: %s", tutorial.second); + } else { + doc += vformat("\n@tutorial(%s): %s", tutorial.first, tutorial.second); + } + } + } + r_symbol.documentation = doc; + } for (int i = 0; i < p_class->members.size(); i++) { const ClassNode::Member &m = p_class->members[i]; @@ -166,11 +271,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.name = m.variable->identifier->name; symbol.kind = m.variable->property == VariableNode::PROP_NONE ? lsp::SymbolKind::Variable : lsp::SymbolKind::Property; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; + symbol.range = range_of_node(m.variable); + symbol.selectionRange = range_of_node(m.variable->identifier); if (m.variable->exported) { symbol.detail += "@export "; } @@ -182,10 +284,31 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); + symbol.documentation = m.variable->doc_data.description; symbol.uri = uri; symbol.script_path = path; + if (m.variable->initializer && m.variable->initializer->type == GDScriptParser::Node::LAMBDA) { + GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)m.variable->initializer; + lsp::DocumentSymbol lambda; + parse_function_symbol(lambda_node->function, lambda); + // Merge lambda into current variable. + symbol.children.append_array(lambda.children); + } + + if (m.variable->getter && m.variable->getter->type == GDScriptParser::Node::FUNCTION) { + lsp::DocumentSymbol get_symbol; + parse_function_symbol(m.variable->getter, get_symbol); + get_symbol.local = true; + symbol.children.push_back(get_symbol); + } + if (m.variable->setter && m.variable->setter->type == GDScriptParser::Node::FUNCTION) { + lsp::DocumentSymbol set_symbol; + parse_function_symbol(m.variable->setter, set_symbol); + set_symbol.local = true; + symbol.children.push_back(set_symbol); + } + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::CONSTANT: { @@ -194,12 +317,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.name = m.constant->identifier->name; symbol.kind = lsp::SymbolKind::Constant; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); - symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line)); + symbol.range = range_of_node(m.constant); + symbol.selectionRange = range_of_node(m.constant->identifier); + symbol.documentation = m.constant->doc_data.description; symbol.uri = uri; symbol.script_path = path; @@ -231,36 +351,14 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.children.push_back(symbol); } break; - case ClassNode::Member::ENUM_VALUE: { - lsp::DocumentSymbol symbol; - - symbol.name = m.enum_value.identifier->name; - symbol.kind = lsp::SymbolKind::EnumMember; - symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column); - symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line)); - symbol.uri = uri; - symbol.script_path = path; - - symbol.detail = symbol.name + " = " + itos(m.enum_value.value); - - r_symbol.children.push_back(symbol); - } break; case ClassNode::Member::SIGNAL: { lsp::DocumentSymbol symbol; symbol.name = m.signal->identifier->name; symbol.kind = lsp::SymbolKind::Event; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line)); + symbol.range = range_of_node(m.signal); + symbol.selectionRange = range_of_node(m.signal->identifier); + symbol.documentation = m.signal->doc_data.description; symbol.uri = uri; symbol.script_path = path; symbol.detail = "signal " + String(m.signal->identifier->name) + "("; @@ -272,17 +370,48 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } symbol.detail += ")"; + for (GDScriptParser::ParameterNode *param : m.signal->parameters) { + lsp::DocumentSymbol param_symbol; + param_symbol.name = param->identifier->name; + param_symbol.kind = lsp::SymbolKind::Variable; + param_symbol.deprecated = false; + param_symbol.local = true; + param_symbol.range = range_of_node(param); + param_symbol.selectionRange = range_of_node(param->identifier); + param_symbol.uri = uri; + param_symbol.script_path = path; + param_symbol.detail = "var " + param_symbol.name; + if (param->get_datatype().is_hard_type()) { + param_symbol.detail += ": " + param->get_datatype().to_string(); + } + symbol.children.push_back(param_symbol); + } + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM_VALUE: { + lsp::DocumentSymbol symbol; + + symbol.name = m.enum_value.identifier->name; + symbol.kind = lsp::SymbolKind::EnumMember; + symbol.deprecated = false; + symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.leftmost_column).to_lsp(this->lines); + symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.rightmost_column).to_lsp(this->lines); + symbol.selectionRange = range_of_node(m.enum_value.identifier); + symbol.documentation = m.enum_value.doc_data.description; + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = symbol.name + " = " + itos(m.enum_value.value); + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::ENUM: { lsp::DocumentSymbol symbol; + symbol.name = m.m_enum->identifier->name; symbol.kind = lsp::SymbolKind::Enum; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line)); + symbol.range = range_of_node(m.m_enum); + symbol.selectionRange = range_of_node(m.m_enum->identifier); + symbol.documentation = m.m_enum->doc_data.description; symbol.uri = uri; symbol.script_path = path; @@ -294,6 +423,25 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value); } symbol.detail += "}"; + + for (GDScriptParser::EnumNode::Value value : m.m_enum->values) { + lsp::DocumentSymbol child; + + child.name = value.identifier->name; + child.kind = lsp::SymbolKind::EnumMember; + child.deprecated = false; + child.range.start = GodotPosition(value.line, value.leftmost_column).to_lsp(this->lines); + child.range.end = GodotPosition(value.line, value.rightmost_column).to_lsp(this->lines); + child.selectionRange = range_of_node(value.identifier); + child.documentation = value.doc_data.description; + child.uri = uri; + child.script_path = path; + + child.detail = child.name + " = " + itos(value.value); + + symbol.children.push_back(child); + } + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::FUNCTION: { @@ -317,32 +465,29 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); - r_symbol.name = p_func->identifier->name; - r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; - r_symbol.detail = "func " + String(p_func->identifier->name) + "("; + bool is_named = p_func->identifier != nullptr; + + r_symbol.name = is_named ? p_func->identifier->name : ""; + r_symbol.kind = (p_func->is_static || p_func->source_lambda != nullptr) ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; + r_symbol.detail = "func"; + if (is_named) { + r_symbol.detail += " " + String(p_func->identifier->name); + } + r_symbol.detail += "("; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); - r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column); - r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line); - r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column); - r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line)); + r_symbol.range = range_of_node(p_func); + if (is_named) { + r_symbol.selectionRange = range_of_node(p_func->identifier); + } else { + r_symbol.selectionRange.start = r_symbol.selectionRange.end = r_symbol.range.start; + } + r_symbol.documentation = p_func->doc_data.description; r_symbol.uri = uri; r_symbol.script_path = path; String parameters; for (int i = 0; i < p_func->parameters.size(); i++) { const ParameterNode *parameter = p_func->parameters[i]; - lsp::DocumentSymbol symbol; - symbol.kind = lsp::SymbolKind::Variable; - symbol.name = parameter->identifier->name; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); - symbol.uri = uri; - symbol.script_path = path; - r_symbol.children.push_back(symbol); if (i > 0) { parameters += ", "; } @@ -387,6 +532,13 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN node_stack.push_back(while_node->loop); } break; + case GDScriptParser::TypeNode::MATCH: { + GDScriptParser::MatchNode *match_node = (GDScriptParser::MatchNode *)node; + for (GDScriptParser::MatchBranchNode *branch_node : match_node->branches) { + node_stack.push_back(branch_node); + } + } break; + case GDScriptParser::TypeNode::MATCH_BRANCH: { GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node; node_stack.push_back(match_node->block); @@ -400,20 +552,6 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN } } break; - case GDScriptParser::TypeNode::VARIABLE: { - GDScriptParser::VariableNode *variable_node = (GDScriptParser::VariableNode *)(node); - lsp::DocumentSymbol symbol; - symbol.kind = lsp::SymbolKind::Variable; - symbol.name = variable_node->identifier->name; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(variable_node->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(variable_node->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(variable_node->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(variable_node->end_column); - symbol.uri = uri; - symbol.script_path = path; - r_symbol.children.push_back(symbol); - } break; - default: continue; } @@ -426,10 +564,40 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN lsp::DocumentSymbol symbol; symbol.name = local.name; symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); + switch (local.type) { + case SuiteNode::Local::CONSTANT: + symbol.range = range_of_node(local.constant); + symbol.selectionRange = range_of_node(local.constant->identifier); + break; + case SuiteNode::Local::VARIABLE: + symbol.range = range_of_node(local.variable); + symbol.selectionRange = range_of_node(local.variable->identifier); + if (local.variable->initializer && local.variable->initializer->type == GDScriptParser::Node::LAMBDA) { + GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)local.variable->initializer; + lsp::DocumentSymbol lambda; + parse_function_symbol(lambda_node->function, lambda); + // Merge lambda into current variable. + // -> Only interested in new variables, not lambda itself. + symbol.children.append_array(lambda.children); + } + break; + case SuiteNode::Local::PARAMETER: + symbol.range = range_of_node(local.parameter); + symbol.selectionRange = range_of_node(local.parameter->identifier); + break; + case SuiteNode::Local::FOR_VARIABLE: + case SuiteNode::Local::PATTERN_BIND: + symbol.range = range_of_node(local.bind); + symbol.selectionRange = range_of_node(local.bind); + break; + default: + // Fallback. + symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines()); + symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines()); + symbol.selectionRange = symbol.range; + break; + } + symbol.local = true; symbol.uri = uri; symbol.script_path = path; symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var "; @@ -437,53 +605,19 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN if (local.get_datatype().is_hard_type()) { symbol.detail += ": " + local.get_datatype().to_string(); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); - r_symbol.children.push_back(symbol); - } - } -} - -String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { - ERR_FAIL_INDEX_V(p_line, lines.size(), String()); - - List<String> doc_lines; - - if (!p_docs_down) { // inline comment - String inline_comment = lines[p_line]; - int comment_start = inline_comment.find("##"); - if (comment_start != -1) { - inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges(); - if (inline_comment.length() > 1) { - doc_lines.push_back(inline_comment.substr(2, inline_comment.length())); + switch (local.type) { + case SuiteNode::Local::CONSTANT: + symbol.documentation = local.constant->doc_data.description; + break; + case SuiteNode::Local::VARIABLE: + symbol.documentation = local.variable->doc_data.description; + break; + default: + break; } + r_symbol.children.push_back(symbol); } } - - int step = p_docs_down ? 1 : -1; - int start_line = p_docs_down ? p_line : p_line - 1; - for (int i = start_line; true; i += step) { - if (i < 0 || i >= lines.size()) { - break; - } - - String line_comment = lines[i].strip_edges(true, false); - if (line_comment.begins_with("##")) { - line_comment = line_comment.substr(2, line_comment.length()); - if (p_docs_down) { - doc_lines.push_back(line_comment); - } else { - doc_lines.push_front(line_comment); - } - } else { - break; - } - } - - String doc; - for (const String &E : doc_lines) { - doc += E + "\n"; - } - return doc; } String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const { @@ -492,7 +626,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs for (int i = 0; i < len; i++) { if (i == p_cursor.line) { longthing += lines[i].substr(0, p_cursor.character); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += lines[i].substr(p_cursor.character, lines[i].size()); } else { longthing += lines[i]; @@ -513,7 +647,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c if (i == p_cursor.line) { String line = lines[i]; String first_part = line.substr(0, p_cursor.character); - String last_part = line.substr(p_cursor.character + 1, lines[i].length()); + String last_part = line.substr(p_cursor.character, lines[i].length()); if (!p_symbol.is_empty()) { String left_cursor_text; for (int c = p_cursor.character - 1; c >= 0; c--) { @@ -527,9 +661,9 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c } longthing += first_part; - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. if (p_func_required) { - longthing += "("; // tell the parser this is a function call + longthing += "("; // Tell the parser this is a function call. } longthing += last_part; } else { @@ -544,7 +678,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c return longthing; } -String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { +String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; if (line.is_empty()) { @@ -552,8 +686,32 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); - int start_pos = p_position.character; - for (int c = p_position.character; c >= 0; c--) { + // `p_position` cursor is BETWEEN chars, not ON chars. + // -> + // ```gdscript + // var member| := some_func|(some_variable|) + // ^ ^ ^ + // | | | cursor on `some_variable, position on `)` + // | | + // | | cursor on `some_func`, pos on `(` + // | + // | cursor on `member`, pos on ` ` (space) + // ``` + // -> Move position to previous character if: + // * Position not on valid identifier char. + // * Prev position is valid identifier char. + lsp::Position pos = p_position; + if ( + pos.character >= line.length() // Cursor at end of line. + || (!is_ascii_identifier_char(line[pos.character]) // Not on valid identifier char. + && (pos.character > 0 // Not line start -> there is a prev char. + && is_ascii_identifier_char(line[pos.character - 1]) // Prev is valid identifier char. + ))) { + pos.character--; + } + + int start_pos = pos.character; + for (int c = pos.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; bool valid_char = is_ascii_identifier_char(ch); @@ -562,8 +720,8 @@ 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++) { + int end_pos = pos.character; + for (int c = pos.character; c < line.length(); c++) { char32_t ch = line[c]; bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { @@ -571,9 +729,11 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & } end_pos = c; } + if (start_pos < end_pos) { - p_offset.x = start_pos - p_position.character; - p_offset.y = end_pos - p_position.character; + r_range.start.line = r_range.end.line = pos.line; + r_range.start.character = start_pos + 1; + r_range.end.character = end_pos + 1; return line.substr(start_pos + 1, end_pos - start_pos); } @@ -584,15 +744,15 @@ String ExtendGDScriptParser::get_uri() const { return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); } -const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const { +const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name) const { const lsp::DocumentSymbol *ret = nullptr; if (p_line < p_parent.range.start.line) { return ret; - } else if (p_parent.range.start.line == p_line) { + } else if (p_parent.range.start.line == p_line && (p_symbol_name.is_empty() || p_parent.name == p_symbol_name)) { return &p_parent; } else { for (int i = 0; i < p_parent.children.size(); i++) { - ret = search_symbol_defined_at_line(p_line, p_parent.children[i]); + ret = search_symbol_defined_at_line(p_line, p_parent.children[i], p_symbol_name); if (ret) { break; } @@ -645,11 +805,11 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi return ERR_METHOD_NOT_FOUND; } -const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const { +const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line, const String &p_symbol_name) const { if (p_line <= 0) { return &class_symbol; } - return search_symbol_defined_at_line(p_line, class_symbol); + return search_symbol_defined_at_line(p_line, class_symbol, p_symbol_name); } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { @@ -812,7 +972,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode api["name"] = m.signal->identifier->name; Array pars; for (int j = 0; j < m.signal->parameters.size(); j++) { - pars.append(String(m.signal->parameters[i]->identifier->name)); + pars.append(String(m.signal->parameters[j]->identifier->name)); } api["arguments"] = pars; if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 4fd27de081..a808f19e5b 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -39,6 +39,9 @@ #ifndef LINE_NUMBER_TO_INDEX #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) #endif +#ifndef COLUMN_NUMBER_TO_INDEX +#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1) +#endif #ifndef SYMBOL_SEPERATOR #define SYMBOL_SEPERATOR "::" @@ -50,6 +53,64 @@ typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; +/** + * Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`. + * + * Difference to `lsp::Position`: + * * Line & Char/column: 1-based + * * LSP: both 0-based + * * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`). + * * LSP: tab is single char + * + * Example: + * ```gdscript + * →→var my_value = 42 + * ``` + * `_` is at: + * * Godot: `column=12` + * * using `indent/size=4` + * * Note: counting starts at `1` + * * LSP: `character=8` + * * Note: counting starts at `0` + */ +struct GodotPosition { + int line; + int column; + + GodotPosition(int p_line, int p_column) : + line(p_line), column(p_column) {} + + lsp::Position to_lsp(const Vector<String> &p_lines) const; + static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines); + + bool operator==(const GodotPosition &p_other) const { + return line == p_other.line && column == p_other.column; + } + + String to_string() const { + return vformat("(%d,%d)", line, column); + } +}; + +struct GodotRange { + GodotPosition start; + GodotPosition end; + + GodotRange(GodotPosition p_start, GodotPosition p_end) : + start(p_start), end(p_end) {} + + lsp::Range to_lsp(const Vector<String> &p_lines) const; + static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines); + + bool operator==(const GodotRange &p_other) const { + return start == p_other.start && end == p_other.end; + } + + String to_string() const { + return vformat("[%s:%s]", start.to_string(), end.to_string()); + } +}; + class ExtendGDScriptParser : public GDScriptParser { String path; Vector<String> lines; @@ -60,6 +121,8 @@ class ExtendGDScriptParser : public GDScriptParser { ClassMembers members; HashMap<String, ClassMembers> inner_classes; + lsp::Range range_of_node(const GDScriptParser::Node *p_node) const; + void update_diagnostics(); void update_symbols(); @@ -70,8 +133,7 @@ class ExtendGDScriptParser : public GDScriptParser { Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const; Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const; - String parse_documentation(int p_line, bool p_docs_down = false); - const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const; + const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "") const; Array member_completions; @@ -87,10 +149,18 @@ public: String get_text_for_completion(const lsp::Position &p_cursor) const; String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const; - String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; + String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const; String get_uri() const; - const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; + /** + * `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named. + * + * Necessary when multiple symbols at same line for example with `func`: + * `func handle_arg(arg: int):` + * -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted. + * With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`. + */ + const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "") const; const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; const List<lsp::DocumentLink> &get_document_links() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 112db4df3a..8489fc08c1 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -278,6 +278,11 @@ void GDScriptLanguageProtocol::stop() { } void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) { +#ifdef TESTS_ENABLED + if (clients.is_empty()) { + return; + } +#endif if (p_client_id == -1) { ERR_FAIL_COND_MSG(latest_client_id == -1, "GDScript LSP: Can't notify client as none was connected."); @@ -285,7 +290,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_notification(p_method, p_params); String msg = Variant(message).to_json_string(); @@ -294,6 +299,11 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { +#ifdef TESTS_ENABLED + if (clients.is_empty()) { + return; + } +#endif if (p_client_id == -1) { ERR_FAIL_COND_MSG(latest_client_id == -1, "GDScript LSP: Can't notify client as none was connected."); @@ -301,7 +311,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_request(p_method, p_params, next_server_id); next_server_id++; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 8c44483288..053be7eec2 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -36,6 +36,8 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" +int GDScriptLanguageServer::port_override = -1; + GDScriptLanguageServer::GDScriptLanguageServer() { _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); @@ -62,7 +64,7 @@ void GDScriptLanguageServer::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { String remote_host = String(_EDITOR_GET("network/language_server/remote_host")); - int remote_port = (int)_EDITOR_GET("network/language_server/remote_port"); + int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (remote_host != host || remote_port != port || remote_use_thread != use_thread) { stop(); @@ -84,10 +86,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { host = String(_EDITOR_GET("network/language_server/remote_host")); - port = (int)_EDITOR_GET("network/language_server/remote_port"); + port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (protocol.start(port, IPAddress(host)) == OK) { - EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; thread.start(GDScriptLanguageServer::thread_main, this); diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 75f9403a74..e845d139bf 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -53,6 +53,7 @@ private: void _notification(int p_what); public: + static int port_override; GDScriptLanguageServer(); void start(); void stop(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 92a5f55978..44f605232d 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -50,6 +50,8 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename); + ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename); + ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references); ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange); ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -108,9 +110,11 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { } else { scr->reload(true); } + scr->update_exports(); ScriptEditor::get_singleton()->reload_scripts(true); ScriptEditor::get_singleton()->update_docs_from_script(scr); + ScriptEditor::get_singleton()->trigger_live_script_reload(); } } @@ -161,11 +165,8 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri); Array arr; if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) { - Vector<lsp::DocumentedSymbolInformation> list; - parser->value->get_symbols().symbol_tree_as_list(uri, list); - for (int i = 0; i < list.size(); i++) { - arr.push_back(list[i].to_json()); - } + lsp::DocumentSymbol symbol = parser->value->get_symbols(); + arr.push_back(symbol.to_json(true)); } return arr; } @@ -253,6 +254,48 @@ Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); } +Variant GDScriptTextDocument::prepareRename(const Dictionary &p_params) { + lsp::TextDocumentPositionParams params; + params.load(p_params); + + lsp::DocumentSymbol symbol; + lsp::Range range; + if (GDScriptLanguageProtocol::get_singleton()->get_workspace()->can_rename(params, symbol, range)) { + return Variant(range.to_json()); + } + + // `null` -> rename not valid at current location. + return Variant(); +} + +Array GDScriptTextDocument::references(const Dictionary &p_params) { + Array res; + + lsp::ReferenceParams params; + params.load(p_params); + + const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); + if (symbol) { + Vector<lsp::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_all_usages(*symbol); + res.resize(usages.size()); + int declaration_adjustment = 0; + for (int i = 0; i < usages.size(); i++) { + lsp::Location usage = usages[i]; + if (!params.context.includeDeclaration && usage.range == symbol->range) { + declaration_adjustment++; + continue; + } + res[i - declaration_adjustment] = usages[i].to_json(); + } + + if (declaration_adjustment > 0) { + res.resize(res.size() - declaration_adjustment); + } + } + + return res; +} + Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { lsp::CompletionItem item; item.load(p_params); @@ -305,6 +348,15 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } + if (item.kind == lsp::CompletionItemKind::Method) { + bool is_trigger_character = params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter; + bool is_quote_character = params.context.triggerCharacter == "\"" || params.context.triggerCharacter == "'"; + + if (is_trigger_character && is_quote_character && item.insertText.is_quoted()) { + item.insertText = item.insertText.unquote(); + } + } + return item.to_json(true); } @@ -450,7 +502,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & if (symbol) { lsp::Location location; location.uri = symbol->uri; - location.range = symbol->range; + location.range = symbol->selectionRange; const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); @@ -464,7 +516,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; - location.range = s->range; + location.range = s->selectionRange; arr.push_back(location.to_json()); r_list.push_back(s); } diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 0121101db2..cfd0490f0a 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -65,6 +65,8 @@ public: Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); Dictionary rename(const Dictionary &p_params); + Variant prepareRename(const Dictionary &p_params); + Array references(const Dictionary &p_params); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); Array documentLink(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 9f848b02f5..81933c8c87 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -46,7 +46,6 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal); ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files); - ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script); ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script); ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path); @@ -182,35 +181,33 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::Do return nullptr; } -const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) { - const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); +const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position) { + // Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`. - for (int i = 0; i < class_symbol->children.size(); ++i) { - int kind = class_symbol->children[i].kind; - switch (kind) { - case lsp::SymbolKind::Function: - case lsp::SymbolKind::Method: - case lsp::SymbolKind::Class: { - const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + const lsp::DocumentSymbol *current = &p_parser->get_symbols(); + const lsp::DocumentSymbol *best_match = nullptr; - for (int l = 0; l < function_symbol->children.size(); ++l) { - const lsp::DocumentSymbol *local = &function_symbol->children[l]; - if (!local->detail.is_empty() && local->name == p_symbol_identifier) { - return local; - } - } - } break; + while (current) { + if (current->name == p_symbol_identifier) { + if (current->selectionRange.contains(p_position)) { + // Exact match: pos is ON symbol decl identifier. + return current; + } - case lsp::SymbolKind::Variable: { - const lsp::DocumentSymbol *variable_symbol = &class_symbol->children[i]; - if (variable_symbol->name == p_symbol_identifier) { - return variable_symbol; - } - } break; + best_match = current; + } + + const lsp::DocumentSymbol *parent = current; + current = nullptr; + for (const lsp::DocumentSymbol &child : parent->children) { + if (child.range.contains(p_position)) { + current = &child; + break; + } } } - return nullptr; + return best_match; } void GDScriptWorkspace::reload_all_workspace_scripts() { @@ -275,25 +272,6 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) return nullptr; } -Array GDScriptWorkspace::symbol(const Dictionary &p_params) { - String query = p_params["query"]; - Array arr; - if (!query.is_empty()) { - for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { - 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_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()); - } - } - } - } - return arr; -} - Error GDScriptWorkspace::initialize() { if (initialized) { return OK; @@ -423,7 +401,7 @@ Error GDScriptWorkspace::initialize() { native_members.insert(E.key, members); } - // cache member completions + // Cache member completions. for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { S.value->get_member_completions(); } @@ -458,48 +436,110 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont return err; } -Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { - Error err; - String path = get_file_path(p_doc_pos.textDocument.uri); +static bool is_valid_rename_target(const lsp::DocumentSymbol *p_symbol) { + // Must be valid symbol. + if (!p_symbol) { + return false; + } + + // Cannot rename builtin. + if (!p_symbol->native_class.is_empty()) { + return false; + } + + // Source must be available. + if (p_symbol->script_path.is_empty()) { + return false; + } + return true; +} + +Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { lsp::WorkspaceEdit edit; - List<String> paths; - list_script_files("res://", paths); + const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); + if (is_valid_rename_target(reference_symbol)) { + Vector<lsp::Location> usages = find_all_usages(*reference_symbol); + for (int i = 0; i < usages.size(); ++i) { + lsp::Location loc = usages[i]; + + edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name); + } + } + + return edit.to_json(); +} +bool GDScriptWorkspace::can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range) { const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); - if (reference_symbol) { - String identifier = reference_symbol->name; + if (!is_valid_rename_target(reference_symbol)) { + return false; + } - for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { - PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n"); - for (int i = 0; i < content.size(); ++i) { - String line = content[i]; + String path = get_file_path(p_doc_pos.textDocument.uri); + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + parser->get_identifier_under_position(p_doc_pos.position, r_range); + r_symbol = *reference_symbol; + return true; + } - int character = line.find(identifier); - while (character > -1) { - lsp::TextDocumentPositionParams params; + return false; +} - lsp::TextDocumentIdentifier text_doc; - text_doc.uri = get_file_uri(PE->get()); +Vector<lsp::Location> GDScriptWorkspace::find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path) { + Vector<lsp::Location> usages; - params.textDocument = text_doc; - params.position.line = i; - params.position.character = character; + String identifier = p_symbol.name; + if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) { + const PackedStringArray &content = parser->get_lines(); + for (int i = 0; i < content.size(); ++i) { + String line = content[i]; - const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + int character = line.find(identifier); + while (character > -1) { + lsp::TextDocumentPositionParams params; - if (other_symbol == reference_symbol) { - edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); - } + lsp::TextDocumentIdentifier text_doc; + text_doc.uri = get_file_uri(p_file_path); - character = line.find(identifier, character + 1); + params.textDocument = text_doc; + params.position.line = i; + params.position.character = character; + + const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + + if (other_symbol == &p_symbol) { + lsp::Location loc; + loc.uri = text_doc.uri; + loc.range.start = params.position; + loc.range.end.line = params.position.line; + loc.range.end.character = params.position.character + identifier.length(); + usages.append(loc); } + + character = line.find(identifier, character + 1); } } } - return edit.to_json(); + return usages; +} + +Vector<lsp::Location> GDScriptWorkspace::find_all_usages(const lsp::DocumentSymbol &p_symbol) { + if (p_symbol.local) { + // Only search in current document. + return find_usages_in_file(p_symbol, p_symbol.script_path); + } + // Search in all documents. + List<String> paths; + list_script_files("res://", paths); + + Vector<lsp::Location> usages; + for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { + usages.append_array(find_usages_in_file(p_symbol, PE->get())); + } + return usages; } Error GDScriptWorkspace::parse_local_script(const String &p_path) { @@ -636,9 +676,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu lsp::Position pos = p_doc_pos.position; if (symbol_identifier.is_empty()) { - Vector2i offset; - symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); - pos.character += offset.y; + lsp::Range range; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); + pos.character = range.end.character; } if (!symbol_identifier.is_empty()) { @@ -661,7 +701,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { - symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier); if (symbol) { switch (symbol->kind) { @@ -670,10 +710,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu symbol = get_parameter_symbol(symbol, symbol_identifier); } } break; - - case lsp::SymbolKind::Variable: { - symbol = get_local_symbol(parser, symbol_identifier); - } break; } } } @@ -686,10 +722,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu symbol = get_native_symbol(ret.class_name, member); } } else { - symbol = parser->get_member_symbol(symbol_identifier); - + symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position); if (!symbol) { - symbol = get_local_symbol(parser, symbol_identifier); + symbol = parser->get_member_symbol(symbol_identifier); } } } @@ -703,8 +738,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP String path = get_file_path(p_doc_pos.textDocument.uri); if (const ExtendGDScriptParser *parser = get_parse_result(path)) { String symbol_identifier; - Vector2i offset; - symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); + lsp::Range range; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); for (const KeyValue<StringName, ClassMembers> &E : native_members) { const ClassMembers &members = native_members.get(E.key); diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 80653778fb..0b2d43b817 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -54,7 +54,7 @@ protected: const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const; const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier); - const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier); + const lsp::DocumentSymbol *get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position); void reload_all_workspace_scripts(); @@ -74,9 +74,6 @@ public: HashMap<StringName, ClassMembers> native_members; public: - Array symbol(const Dictionary &p_params); - -public: Error initialize(); Error parse_script(const String &p_path, const String &p_content); @@ -96,6 +93,9 @@ public: Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); void did_delete_files(const Dictionary &p_params); Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name); + bool can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range); + Vector<lsp::Location> find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path); + Vector<lsp::Location> find_all_usages(const lsp::DocumentSymbol &p_symbol); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 3782945e07..e09adb74bd 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -83,6 +83,14 @@ struct Position { */ int character = 0; + _FORCE_INLINE_ bool operator==(const Position &p_other) const { + return line == p_other.line && character == p_other.character; + } + + String to_string() const { + return vformat("(%d,%d)", line, character); + } + _FORCE_INLINE_ void load(const Dictionary &p_params) { line = p_params["line"]; character = p_params["character"]; @@ -112,6 +120,27 @@ struct Range { */ Position end; + _FORCE_INLINE_ bool operator==(const Range &p_other) const { + return start == p_other.start && end == p_other.end; + } + + bool contains(const Position &p_pos) const { + // Inside line range. + if (start.line <= p_pos.line && p_pos.line <= end.line) { + // If on start line: must come after start char. + bool start_ok = p_pos.line == start.line ? start.character <= p_pos.character : true; + // If on end line: must come before end char. + bool end_ok = p_pos.line == end.line ? p_pos.character <= end.character : true; + return start_ok && end_ok; + } else { + return false; + } + } + + String to_string() const { + return vformat("[%s:%s]", start.to_string(), end.to_string()); + } + _FORCE_INLINE_ void load(const Dictionary &p_params) { start.load(p_params["start"]); end.load(p_params["end"]); @@ -203,6 +232,17 @@ struct TextDocumentPositionParams { } }; +struct ReferenceContext { + /** + * Include the declaration of the current symbol. + */ + bool includeDeclaration; +}; + +struct ReferenceParams : TextDocumentPositionParams { + ReferenceContext context; +}; + struct DocumentLinkParams { /** * The document to provide document links for. @@ -343,8 +383,8 @@ struct Command { } }; -// Use namespace instead of enumeration to follow the LSP specifications -// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// Use namespace instead of enumeration to follow the LSP specifications. +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not. namespace TextDocumentSyncKind { /** @@ -436,7 +476,7 @@ struct RenameOptions { /** * Renames should be checked and tested before being executed. */ - bool prepareProvider = false; + bool prepareProvider = true; Dictionary to_json() { Dictionary dict; @@ -794,12 +834,12 @@ static const String Markdown = "markdown"; */ struct MarkupContent { /** - * The type of the Markup + * The type of the Markup. */ String kind; /** - * The content itself + * The content itself. */ String value; @@ -821,8 +861,8 @@ struct MarkupContent { }; // Use namespace instead of enumeration to follow the LSP specifications -// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc. +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not. +// And here C++ compilers are unhappy with our enumeration name like `Color`, `File`, `RefCounted` etc. /** * The kind of a completion entry. */ @@ -854,7 +894,7 @@ static const int Operator = 24; static const int TypeParameter = 25; }; // namespace CompletionItemKind -// Use namespace instead of enumeration to follow the LSP specifications +// Use namespace instead of enumeration to follow the LSP specifications. /** * Defines whether the insert text in a completion item should be interpreted as * plain text or a snippet. @@ -1070,8 +1110,8 @@ struct CompletionList { }; // Use namespace instead of enumeration to follow the LSP specifications -// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not +// And here C++ compilers are unhappy with our enumeration name like `String`, `Array`, `Object` etc /** * A symbol kind. */ @@ -1105,70 +1145,6 @@ static const int TypeParameter = 26; }; // namespace SymbolKind /** - * Represents information about programming constructs like variables, classes, - * interfaces etc. - */ -struct SymbolInformation { - /** - * The name of this symbol. - */ - String name; - - /** - * The kind of this symbol. - */ - int kind = SymbolKind::File; - - /** - * Indicates if this symbol is deprecated. - */ - bool deprecated = false; - - /** - * The location of this symbol. The location's range is used by a tool - * to reveal the location in the editor. If the symbol is selected in the - * tool the range's start information is used to position the cursor. So - * the range usually spans more then the actual symbol's name and does - * normally include things like visibility modifiers. - * - * The range doesn't have to denote a node range in the sense of a abstract - * syntax tree. It can therefore not be used to re-construct a hierarchy of - * the symbols. - */ - Location location; - - /** - * The name of the symbol containing this symbol. This information is for - * user interface purposes (e.g. to render a qualifier in the user interface - * if necessary). It can't be used to re-infer a hierarchy for the document - * symbols. - */ - String containerName; - - _FORCE_INLINE_ Dictionary to_json() const { - Dictionary dict; - dict["name"] = name; - dict["kind"] = kind; - dict["deprecated"] = deprecated; - dict["location"] = location.to_json(); - dict["containerName"] = containerName; - return dict; - } -}; - -struct DocumentedSymbolInformation : public SymbolInformation { - /** - * A human-readable string with additional information - */ - String detail; - - /** - * A human-readable string that represents a doc-comment. - */ - String documentation; -}; - -/** * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range, * e.g. the range of an identifier. @@ -1186,12 +1162,12 @@ struct DocumentSymbol { String detail; /** - * Documentation for this symbol + * Documentation for this symbol. */ String documentation; /** - * Class name for the native symbols + * Class name for the native symbols. */ String native_class; @@ -1206,6 +1182,13 @@ struct DocumentSymbol { bool deprecated = false; /** + * If `true`: Symbol is local to script and cannot be accessed somewhere else. + * + * For example: local variable inside a `func`. + */ + bool local = false; + + /** * The range enclosing this symbol not including leading/trailing whitespace but everything else * like comments. This information is typically used to determine if the clients cursor is * inside the symbol to reveal in the symbol in the UI. @@ -1238,35 +1221,21 @@ struct DocumentSymbol { dict["documentation"] = documentation; dict["native_class"] = native_class; } - Array arr; - arr.resize(children.size()); - for (int i = 0; i < children.size(); i++) { - arr[i] = children[i].to_json(with_doc); + if (!children.is_empty()) { + Array arr; + for (int i = 0; i < children.size(); i++) { + if (children[i].local) { + continue; + } + arr.push_back(children[i].to_json(with_doc)); + } + if (!children.is_empty()) { + dict["children"] = arr; + } } - dict["children"] = arr; return dict; } - void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { - DocumentedSymbolInformation si; - if (p_join_name && !p_container.is_empty()) { - si.name = p_container + ">" + name; - } else { - si.name = name; - } - si.kind = kind; - si.containerName = p_container; - si.deprecated = deprecated; - si.location.uri = p_uri; - si.location.range = range; - si.detail = detail; - si.documentation = documentation; - r_list.push_back(si); - for (int i = 0; i < children.size(); i++) { - children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name); - } - } - _FORCE_INLINE_ MarkupContent render() const { MarkupContent markdown; if (detail.length()) { @@ -1460,6 +1429,17 @@ struct CompletionParams : public TextDocumentPositionParams { TextDocumentPositionParams::load(p_params); context.load(p_params["context"]); } + + Dictionary to_json() { + Dictionary ctx; + ctx["triggerCharacter"] = context.triggerCharacter; + ctx["triggerKind"] = context.triggerKind; + + Dictionary dict; + dict = TextDocumentPositionParams::to_json(); + dict["context"] = ctx; + return dict; + } }; /** @@ -1750,7 +1730,7 @@ struct ServerCapabilities { /** * The server provides find references support. */ - bool referencesProvider = false; + bool referencesProvider = true; /** * The server provides document highlight support. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 874cbc6ee8..f91dc83f2c 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -149,6 +149,10 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l // Set all warning levels to "Warn" in order to test them properly, even the ones that default to error. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) { + // TODO: Add ability for test scripts to specify which warnings to enable/disable for testing. + continue; + } String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i); ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN); } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd new file mode 100644 index 0000000000..87d1b9ea18 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd @@ -0,0 +1,7 @@ +# GH-73283 + +class MyClass: + pass + +func test(): + MyClass.not_existing_method() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out new file mode 100644 index 0000000000..7340058dd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static function "not_existing_method()" not found in base "MyClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd new file mode 100644 index 0000000000..7e1efb8d1b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd @@ -0,0 +1,2 @@ +func test(): + Time.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out new file mode 100644 index 0000000000..bc4875d908 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot construct native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd new file mode 100644 index 0000000000..05aa726a05 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd @@ -0,0 +1,8 @@ +# GH-82809 + +extends Resource + +@export var node: Node + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out new file mode 100644 index 0000000000..9a45bbb515 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "Resource". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd new file mode 100644 index 0000000000..c210e7c043 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd @@ -0,0 +1,9 @@ +# GH-82809 + +extends Node + +class Inner: + @export var node: Node + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out new file mode 100644 index 0000000000..3da6d6d7ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "RefCounted". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd new file mode 100644 index 0000000000..6f2c104643 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd @@ -0,0 +1,8 @@ +# GH-82809 + +extends Resource + +@export var node_array: Array[Node] + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out new file mode 100644 index 0000000000..9a45bbb515 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "Resource". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd new file mode 100644 index 0000000000..a46b6d8658 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd @@ -0,0 +1,6 @@ +# GH-82081 + +extends Time + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out new file mode 100644 index 0000000000..7c26dea56e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot inherit native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd new file mode 100644 index 0000000000..db3f3f4c72 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd @@ -0,0 +1,5 @@ +# GH-82021 + +func test(): + for x: String in [1, 2, 3]: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out new file mode 100644 index 0000000000..0bb654e7e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "int" as "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd new file mode 100644 index 0000000000..fdf22f6843 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Object): + pass + +class B extends A: + func f(_p: Node): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out new file mode 100644 index 0000000000..c6a7e40e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd new file mode 100644 index 0000000000..e4094f1d76 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Variant): + pass + +class B extends A: + func f(_p: Node): # No `is_type_compatible()` misuse. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out new file mode 100644 index 0000000000..52a6efc6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd new file mode 100644 index 0000000000..17663da4f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: int): + pass + +class B extends A: + func f(_p: float): # No implicit conversion. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out new file mode 100644 index 0000000000..7a6207fd45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(int) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd new file mode 100644 index 0000000000..6dfa75ecbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Object: + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd new file mode 100644 index 0000000000..366494b94f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Variant: # No `is_type_compatible()` misuse. + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd new file mode 100644 index 0000000000..2cb4e7c616 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> void: # No `is_type_compatible()` misuse. + return + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd new file mode 100644 index 0000000000..2cabce46f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd @@ -0,0 +1,10 @@ +class A: + func f() -> float: + return 0.0 + +class B extends A: + func f() -> int: # No implicit conversion. + return 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out new file mode 100644 index 0000000000..72f2c493d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> float". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd new file mode 100644 index 0000000000..c06fbd89ff --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd @@ -0,0 +1,2 @@ +func test(): + print(len(Color())) # GDScript utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out new file mode 100644 index 0000000000..9cb04f6240 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "len()" function: Value of type 'Color' can't provide a length. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd new file mode 100644 index 0000000000..1dcb9fc36a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd @@ -0,0 +1,4 @@ +func test(): + match 0: + _ when a == 0: + print("a does not exist") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out new file mode 100644 index 0000000000..c5f0a90d6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "a" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd new file mode 100644 index 0000000000..1600c3001f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd @@ -0,0 +1,4 @@ +# GH-73213 + +func test(): + print(Object()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out new file mode 100644 index 0000000000..27668fcd48 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid constructor "Object()", use "Object.new()" instead. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd new file mode 100644 index 0000000000..a98f69f3ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd @@ -0,0 +1,14 @@ +# GH-83468 + +func non_static_func(): + pass + +static func static_func(): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out new file mode 100644 index 0000000000..b78f131345 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd new file mode 100644 index 0000000000..7af9ff274c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd @@ -0,0 +1,15 @@ +# GH-83468 + +func non_static_func(): + pass + +static func static_func( + f := func (): + var g := func (): + non_static_func() + g.call() +): + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out new file mode 100644 index 0000000000..b78f131345 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd new file mode 100644 index 0000000000..5130973bd2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd @@ -0,0 +1,14 @@ +# GH-83468 + +func non_static_func(): + pass + +static var static_var = func (): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out new file mode 100644 index 0000000000..c0308c81f3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd new file mode 100644 index 0000000000..2d15b4e3e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd @@ -0,0 +1,15 @@ +# GH-83468 + +func non_static_func(): + pass + +static var static_var: + set(_value): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out new file mode 100644 index 0000000000..cdf3ab2aeb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "@static_var_setter()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out index f1e9ec34f2..81554ec707 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static()" for static variable initializer. +Cannot call non-static function "non_static()" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd index 25cde1d40b..7cc5aaf44f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd @@ -1,4 +1,4 @@ func test(): - var unconvertable := 1 - var typed: Array[Object] = [unconvertable] + var unconvertible := 1 + var typed: Array[Object] = [unconvertible] print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd new file mode 100644 index 0000000000..dc6e26e682 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd @@ -0,0 +1,2 @@ +func test(): + print(floor(Color())) # Built-in utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out new file mode 100644 index 0000000000..27d2504dd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "floor()" function: Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd new file mode 100644 index 0000000000..57dfffdbee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd @@ -0,0 +1,5 @@ +func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out new file mode 100644 index 0000000000..e68759223c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call the parent class' virtual function "_init()" because it hasn't been defined. diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd new file mode 100644 index 0000000000..dafd2ec0c8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -0,0 +1,17 @@ +class_name TestExportEnumAsDictionary + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") + +@export var x1 = MyEnum +@export var x2 = MyEnum.A +@export var x3 := MyEnum +@export var x4 := MyEnum.A +@export var x5: MyEnum + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(Utils.get_property_signature(property)) + print(" ", Utils.get_property_additional_info(property)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out new file mode 100644 index 0000000000..f1a13f1045 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out @@ -0,0 +1,11 @@ +GDTEST_OK +@export var x1: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x2: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x3: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x4: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x5: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd new file mode 100644 index 0000000000..a43c233625 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd @@ -0,0 +1,20 @@ +class A: + func int_to_variant(_p: int): pass + func node_to_variant(_p: Node): pass + func node_2d_to_node(_p: Node2D): pass + + func variant_to_untyped(_p: Variant): pass + func int_to_untyped(_p: int): pass + func node_to_untyped(_p: Node): pass + +class B extends A: + func int_to_variant(_p: Variant): pass + func node_to_variant(_p: Variant): pass + func node_2d_to_node(_p: Node): pass + + func variant_to_untyped(_p): pass + func int_to_untyped(_p): pass + func node_to_untyped(_p): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd new file mode 100644 index 0000000000..4de50b6731 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd @@ -0,0 +1,32 @@ +class A: + func variant_to_int() -> Variant: return 0 + func variant_to_node() -> Variant: return null + func node_to_node_2d() -> Node: return null + + func untyped_to_void(): pass + func untyped_to_variant(): pass + func untyped_to_int(): pass + func untyped_to_node(): pass + + func void_to_untyped() -> void: pass + func variant_to_untyped() -> Variant: return null + func int_to_untyped() -> int: return 0 + func node_to_untyped() -> Node: return null + +class B extends A: + func variant_to_int() -> int: return 0 + func variant_to_node() -> Node: return null + func node_to_node_2d() -> Node2D: return null + + func untyped_to_void() -> void: pass + func untyped_to_variant() -> Variant: return null + func untyped_to_int() -> int: return 0 + func untyped_to_node() -> Node: return null + + func void_to_untyped(): pass + func variant_to_untyped(): pass + func int_to_untyped(): pass + func node_to_untyped(): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index b447180ea8..d0f895d784 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -23,6 +23,7 @@ func test() -> void: typed = variant() inferred = variant() + @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown. param_weak(typed) param_typed(typed) param_inferred(typed) diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd index 5a413e2015..08e7dc590e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -6,10 +6,12 @@ var prop = null func check_arg(arg = null) -> void: if arg != null: + @warning_ignore("unsafe_call_argument") print(check(arg)) func check_recur() -> void: if recur != null: + @warning_ignore("unsafe_call_argument") print(check(recur)) else: recur = 1 @@ -22,11 +24,13 @@ func test() -> void: if prop == null: set('prop', 1) + @warning_ignore("unsafe_call_argument") print(check(prop)) set('prop', null) var loop = null while loop != 2: if loop != null: + @warning_ignore("unsafe_call_argument") print(check(loop)) loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd new file mode 100644 index 0000000000..e1a1f07e47 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd @@ -0,0 +1,22 @@ +var _typed_array: Array[int] + +func weak_param_func(weak_param = _typed_array): + weak_param = [11] # Don't treat the literal as typed! + return weak_param + +func hard_param_func(hard_param := _typed_array): + hard_param = [12] + return hard_param + +func test(): + var weak_var = _typed_array + print(weak_var.is_typed()) + weak_var = [21] # Don't treat the literal as typed! + print(weak_var.is_typed()) + print(weak_param_func().is_typed()) + + var hard_var := _typed_array + print(hard_var.is_typed()) + hard_var = [22] + print(hard_var.is_typed()) + print(hard_param_func().is_typed()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out new file mode 100644 index 0000000000..34b18dbe7c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +false +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd new file mode 100644 index 0000000000..a8641e4f3b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd @@ -0,0 +1,21 @@ +class BaseClass: + func _get_property_list(): + return {"property" : "definition"} + +class SuperClassMethodsRecognized extends BaseClass: + func _init(): + # Recognizes super class methods. + var _x = _get_property_list() + +class SuperMethodsRecognized extends BaseClass: + func _get_property_list(): + # Recognizes super method. + var result = super() + result["new"] = "new" + return result + +func test(): + var test1 = SuperClassMethodsRecognized.new() + print(test1._get_property_list()) # Calls base class's method. + var test2 = SuperMethodsRecognized.new() + print(test2._get_property_list()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out new file mode 100644 index 0000000000..ccff660117 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ "property": "definition" } +{ "property": "definition", "new": "new" } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd deleted file mode 100644 index 1b32491e48..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd +++ /dev/null @@ -1,4 +0,0 @@ -func test(): - var a: Array[Node] = [] - for node: Node in a: - print(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out deleted file mode 100644 index 3b3fbd9bd1..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out +++ /dev/null @@ -1,5 +0,0 @@ -GDTEST_OK ->> WARNING ->> Line: 3 ->> REDUNDANT_FOR_VARIABLE_TYPE ->> The for loop iterator "node" already has inferred type "Node", the specified type is redundant. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd deleted file mode 100644 index fcbc13c53d..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd +++ /dev/null @@ -1,4 +0,0 @@ -func test(): - var a: Array[Node2D] = [] - for node: Node in a: - print(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out deleted file mode 100644 index 36d4a161d3..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out +++ /dev/null @@ -1,5 +0,0 @@ -GDTEST_OK ->> WARNING ->> Line: 3 ->> REDUNDANT_FOR_VARIABLE_TYPE ->> The for loop iterator "node" has inferred type "Node2D" but its supertype "Node" is specified. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd index 849df0921e..c1776fe1b4 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -14,4 +14,5 @@ func test(): func do_add_node(): var node = Node.new() node.name = "Node" + @warning_ignore("unsafe_call_argument") add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 29239a19da..939e787ea5 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,3 +1,5 @@ +class_name ShadowedClass + var member: int = 0 var print_debug := 'print_debug' @@ -12,5 +14,6 @@ func test(): var sqrt := 'sqrt' var member := 'member' var reference := 'reference' + var ShadowedClass := 'ShadowedClass' print('warn') diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index accc791d8a..8297eed4b8 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,30 +1,34 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 5 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "print_debug" has the same name as a built-in function. >> WARNING ->> Line: 9 +>> Line: 11 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 10 +>> Line: 12 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable "Node" has the same name as a global class. +>> The variable "Node" has the same name as a native class. >> WARNING ->> Line: 11 +>> Line: 13 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 12 +>> Line: 14 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 13 +>> Line: 15 >> SHADOWED_VARIABLE ->> The local variable "member" is shadowing an already-declared variable at line 1. +>> The local variable "member" is shadowing an already-declared variable at line 3. >> WARNING ->> Line: 14 +>> Line: 16 >> SHADOWED_VARIABLE_BASE_CLASS >> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". +>> WARNING +>> Line: 17 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd new file mode 100644 index 0000000000..c6d9b37485 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd @@ -0,0 +1,54 @@ +func variant_func(x: Variant) -> void: + print(x) + +func int_func(x: int) -> void: + print(x) + +func float_func(x: float) -> void: + print(x) + +func node_func(x: Node) -> void: + print(x) + +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var variant: Variant = null + var untyped_int = 42 + var untyped_string = "abc" + var variant_int: Variant = 42 + var variant_string: Variant = "abc" + var typed_int: int = 42 + + variant_func(untyped_int) # No warning. + variant_func(untyped_string) # No warning. + variant_func(variant_int) # No warning. + variant_func(variant_string) # No warning. + variant_func(typed_int) # No warning. + + int_func(untyped_int) + int_func(untyped_string) + int_func(variant_int) + int_func(variant_string) + int_func(typed_int) # No warning. + + float_func(untyped_int) + float_func(untyped_string) + float_func(variant_int) + float_func(variant_string) + float_func(typed_int) # No warning. + + node_func(variant) + node_func(Object.new()) + node_func(Node.new()) # No warning. + node_func(Node2D.new()) # No warning. + + # GH-82529 + print(Callable(self, "test")) # No warning. + print(Callable(variant, "test")) + + print(Dictionary(variant)) + print(Vector2(variant)) + print(int(variant)) + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out new file mode 100644 index 0000000000..3084515233 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out @@ -0,0 +1,57 @@ +GDTEST_OK +>> WARNING +>> Line: 28 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 29 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 30 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 31 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 34 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 35 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 36 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 37 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 40 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Variant" was provided. +>> WARNING +>> Line: 41 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Object" was provided. +>> WARNING +>> Line: 47 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Callable()" requires the subtype "Object" but the supertype "Variant" was provided. +>> WARNING +>> Line: 49 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Dictionary()" requires the subtype "Dictionary" but the supertype "Variant" was provided. +>> WARNING +>> Line: 50 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Vector2()" requires the subtype "Vector2" or "Vector2i" but the supertype "Variant" was provided. +>> WARNING +>> Line: 51 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided. diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.notest.gd new file mode 100644 index 0000000000..53d0b14d72 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/class.notest.gd @@ -0,0 +1,132 @@ +extends Node + +class Inner1 extends Node: +# ^^^^^^ class1 -> class1 + var member1 := 42 + # ^^^^^^^ class1:member1 -> class1:member1 + var member2 : int = 13 + # ^^^^^^^ class1:member2 -> class1:member2 + var member3 = 1337 + # ^^^^^^^ class1:member3 -> class1:member3 + + signal changed(old, new) + # ^^^^^^^ class1:signal -> class1:signal + func my_func(arg1: int, arg2: String, arg3): + # | | | | | | ^^^^ class1:func:arg3 -> class1:func:arg3 + # | | | | ^^^^ class1:func:arg2 -> class1:func:arg2 + # | | ^^^^ class1:func:arg1 -> class1:func:arg1 + # ^^^^^^^ class1:func -> class1:func + print(arg1, arg2, arg3) + # | | | | ^^^^ -> class1:func:arg3 + # | | ^^^^ -> class1:func:arg2 + # ^^^^ -> class1:func:arg1 + changed.emit(arg1, arg3) + # | | | ^^^^ -> class1:func:arg3 + # | ^^^^ -> class1:func:arg1 + #<^^^^^ -> class1:signal + return arg1 + arg2.length() + arg3 + # | | | | ^^^^ -> class1:func:arg3 + # | | ^^^^ -> class1:func:arg2 + # ^^^^ -> class1:func:arg1 + +class Inner2: +# ^^^^^^ class2 -> class2 + var member1 := 42 + # ^^^^^^^ class2:member1 -> class2:member1 + var member2 : int = 13 + # ^^^^^^^ class2:member2 -> class2:member2 + var member3 = 1337 + # ^^^^^^^ class2:member3 -> class2:member3 + + signal changed(old, new) + # ^^^^^^^ class2:signal -> class2:signal + func my_func(arg1: int, arg2: String, arg3): + # | | | | | | ^^^^ class2:func:arg3 -> class2:func:arg3 + # | | | | ^^^^ class2:func:arg2 -> class2:func:arg2 + # | | ^^^^ class2:func:arg1 -> class2:func:arg1 + # ^^^^^^^ class2:func -> class2:func + print(arg1, arg2, arg3) + # | | | | ^^^^ -> class2:func:arg3 + # | | ^^^^ -> class2:func:arg2 + # ^^^^ -> class2:func:arg1 + changed.emit(arg1, arg3) + # | | | ^^^^ -> class2:func:arg3 + # | ^^^^ -> class2:func:arg1 + #<^^^^^ -> class2:signal + return arg1 + arg2.length() + arg3 + # | | | | ^^^^ -> class2:func:arg3 + # | | ^^^^ -> class2:func:arg2 + # ^^^^ -> class2:func:arg1 + +class Inner3 extends Inner2: +# | | ^^^^^^ -> class2 +# ^^^^^^ class3 -> class3 + var whatever = "foo" + # ^^^^^^^^ class3:whatever -> class3:whatever + + func _init(): + # ^^^^^ class3:init + # Note: no self-ref check here: resolves to `Object._init`. + # usages of `Inner3.new()` DO resolve to this `_init` + pass + + class NestedInInner3: + # ^^^^^^^^^^^^^^ class3:nested1 -> class3:nested1 + var some_value := 42 + # ^^^^^^^^^^ class3:nested1:some_value -> class3:nested1:some_value + + class AnotherNestedInInner3 extends NestedInInner3: + #! | | ^^^^^^^^^^^^^^ -> class3:nested1 + # ^^^^^^^^^^^^^^^^^^^^^ class3:nested2 -> class3:nested2 + var another_value := 13 + # ^^^^^^^^^^^^^ class3:nested2:another_value -> class3:nested2:another_value + +func _ready(): + var inner1 = Inner1.new() + # | | ^^^^^^ -> class1 + # ^^^^^^ func:class1 -> func:class1 + var value1 = inner1.my_func(1,"",3) + # | | | | ^^^^^^^ -> class1:func + # | | ^^^^^^ -> func:class1 + # ^^^^^^ func:class1:value1 -> func:class1:value1 + var value2 = inner1.member3 + # | | | | ^^^^^^^ -> class1:member3 + # | | ^^^^^^ -> func:class1 + # ^^^^^^ func:class1:value2 -> func:class1:value2 + print(value1, value2) + # | | ^^^^^^ -> func:class1:value2 + # ^^^^^^ -> func:class1:value1 + + var inner3 = Inner3.new() + # | | | | ^^^ -> class3:init + # | | ^^^^^^ -> class3 + # ^^^^^^ func:class3 -> func:class3 + print(inner3) + # ^^^^^^ -> func:class3 + + var nested1 = Inner3.NestedInInner3.new() + # | | | | ^^^^^^^^^^^^^^ -> class3:nested1 + # | | ^^^^^^ -> class3 + # ^^^^^^^ func:class3:nested1 -> func:class3:nested1 + var value_nested1 = nested1.some_value + # | | | | ^^^^^^^^^^ -> class3:nested1:some_value + # | | ^^^^^^^ -> func:class3:nested1 + # ^^^^^^^^^^^^^ func:class3:nested1:value + print(value_nested1) + # ^^^^^^^^^^^^^ -> func:class3:nested1:value + + var nested2 = Inner3.AnotherNestedInInner3.new() + # | | | | ^^^^^^^^^^^^^^^^^^^^^ -> class3:nested2 + # | | ^^^^^^ -> class3 + # ^^^^^^^ func:class3:nested2 -> func:class3:nested2 + var value_nested2 = nested2.some_value + # | | | | ^^^^^^^^^^ -> class3:nested1:some_value + # | | ^^^^^^^ -> func:class3:nested2 + # ^^^^^^^^^^^^^ func:class3:nested2:value + var another_value_nested2 = nested2.another_value + # | | | | ^^^^^^^^^^^^^ -> class3:nested2:another_value + # | | ^^^^^^^ -> func:class3:nested2 + # ^^^^^^^^^^^^^^^^^^^^^ func:class3:nested2:another_value_nested + print(value_nested2, another_value_nested2) + # | | ^^^^^^^^^^^^^^^^^^^^^ -> func:class3:nested2:another_value_nested + # ^^^^^^^^^^^^^ -> func:class3:nested2:value diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.notest.gd new file mode 100644 index 0000000000..38b9ec110a --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/enums.notest.gd @@ -0,0 +1,26 @@ +extends Node + +enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} +# | | | | ^^^^^^^^^ enum:unnamed:ally -> enum:unnamed:ally +# | | ^^^^^^^^^^ enum:unnamed:enemy -> enum:unnamed:enemy +# ^^^^^^^^^^^^ enum:unnamed:neutral -> enum:unnamed:neutral +enum Named {THING_1, THING_2, ANOTHER_THING = -1} +# | | | | | | ^^^^^^^^^^^^^ enum:named:thing3 -> enum:named:thing3 +# | | | | ^^^^^^^ enum:named:thing2 -> enum:named:thing2 +# | | ^^^^^^^ enum:named:thing1 -> enum:named:thing1 +# ^^^^^ enum:named -> enum:named + +func f(arg): + match arg: + UNIT_ENEMY: print(UNIT_ENEMY) + # | ^^^^^^^^^^ -> enum:unnamed:enemy + #<^^^^^^^^ -> enum:unnamed:enemy + Named.THING_2: print(Named.THING_2) + #! | | | | | ^^^^^^^ -> enum:named:thing2 + # | | | ^^^^^ -> enum:named + #! | ^^^^^^^ -> enum:named:thing2 + #<^^^ -> enum:named + _: print(UNIT_ENEMY, Named.ANOTHER_THING) + #! | | | | ^^^^^^^^^^^^^ -> enum:named:thing3 + # | | ^^^^^ -> enum:named + # ^^^^^^^^^^ -> enum:unnamed:enemy diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd new file mode 100644 index 0000000000..c25d73a719 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd @@ -0,0 +1,28 @@ +extends Node + +var root = 0 +# ^^^^ 0_indent -> 0_indent + +func a(): + var alpha: int = root + 42 + # | | ^^^^ -> 0_indent + # ^^^^^ 1_indent -> 1_indent + if alpha > 42: + # ^^^^^ -> 1_indent + var beta := alpha + 13 + # | | ^^^^ -> 1_indent + # ^^^^ 2_indent -> 2_indent + if beta > alpha: + # | | ^^^^^ -> 1_indent + # ^^^^ -> 2_indent + var gamma = beta + 1 + # | | ^^^^ -> 2_indent + # ^^^^^ 3_indent -> 3_indent + print(gamma) + # ^^^^^ -> 3_indent + print(beta) + # ^^^^ -> 2_indent + print(alpha) + # ^^^^^ -> 1_indent + print(root) + # ^^^^ -> 0_indent diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd new file mode 100644 index 0000000000..6f5d468eea --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd @@ -0,0 +1,91 @@ +extends Node + +var lambda_member1 := func(alpha: int, beta): return alpha + beta +# | | | | | | | | ^^^^ -> \1:beta +# | | | | | | ^^^^^ -> \1:alpha +# | | | | ^^^^ \1:beta -> \1:beta +# | | ^^^^^ \1:alpha -> \1:alpha +# ^^^^^^^^^^^^^^ \1 -> \1 + +var lambda_member2 := func(alpha, beta: int) -> int: +# | | | | | | +# | | | | | | +# | | | | ^^^^ \2:beta -> \2:beta +# | | ^^^^^ \2:alpha -> \2:alpha +# ^^^^^^^^^^^^^^ \2 -> \2 + return alpha + beta + # | | ^^^^ -> \2:beta + # ^^^^^ -> \2:alpha + +var lambda_member3 := func add_values(alpha, beta): return alpha + beta +# | | | | | | | | ^^^^ -> \3:beta +# | | | | | | ^^^^^ -> \3:alpha +# | | | | ^^^^ \3:beta -> \3:beta +# | | ^^^^^ \3:alpha -> \3:alpha +# ^^^^^^^^^^^^^^ \3 -> \3 + +var lambda_multiline = func(alpha: int, beta: int) -> int: +# | | | | | | +# | | | | | | +# | | | | ^^^^ \multi:beta -> \multi:beta +# | | ^^^^^ \multi:alpha -> \multi:alpha +# ^^^^^^^^^^^^^^^^ \multi -> \multi + print(alpha + beta) + # | | ^^^^ -> \multi:beta + # ^^^^^ -> \multi:alpha + var tmp = alpha + beta + 42 + # | | | | ^^^^ -> \multi:beta + # | | ^^^^^ -> \multi:alpha + # ^^^ \multi:tmp -> \multi:tmp + print(tmp) + # ^^^ -> \multi:tmp + if tmp > 50: + # ^^^ -> \multi:tmp + tmp += alpha + # | ^^^^^ -> \multi:alpha + #<^ -> \multi:tmp + else: + tmp -= beta + # | ^^^^ -> \multi:beta + #<^ -> \multi:tmp + print(tmp) + # ^^^ -> \multi:tmp + return beta + tmp + alpha + # | | | | ^^^^^ -> \multi:alpha + # | | ^^^ -> \multi:tmp + # ^^^^ -> \multi:beta + + +var some_name := "foo bar" +# ^^^^^^^^^ member:some_name -> member:some_name + +func _ready() -> void: + var a = lambda_member1.call(1,2) + # ^^^^^^^^^^^^^^ -> \1 + var b = lambda_member2.call(1,2) + # ^^^^^^^^^^^^^^ -> \2 + var c = lambda_member3.call(1,2) + # ^^^^^^^^^^^^^^ -> \3 + var d = lambda_multiline.call(1,2) + # ^^^^^^^^^^^^^^^^ -> \multi + print(a,b,c,d) + + var lambda_local = func(alpha, beta): return alpha + beta + # | | | | | | | | ^^^^ -> \local:beta + # | | | | | | ^^^^^ -> \local:alpha + # | | | | ^^^^ \local:beta -> \local:beta + # | | ^^^^^ \local:alpha -> \local:alpha + # ^^^^^^^^^^^^ \local -> \local + + var value := 42 + # ^^^^^ local:value -> local:value + var lambda_capture = func(): return value + some_name.length() + # | | | | ^^^^^^^^^ -> member:some_name + # | | ^^^^^ -> local:value + # ^^^^^^^^^^^^^^ \capture -> \capture + + var z = lambda_local.call(1,2) + # ^^^^^^^^^^^^ -> \local + var x = lambda_capture.call() + # ^^^^^^^^^^^^^^ -> \capture + print(z,x) diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd new file mode 100644 index 0000000000..b6cc46f7da --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd @@ -0,0 +1,25 @@ +extends Node + +var member := 2 +# ^^^^^^ member -> member + +func test_member() -> void: + var test := member + 42 + # | | ^^^^^^ -> member + # ^^^^ test -> test + test += 3 + #<^^ -> test + member += 5 + #<^^^^ -> member + test = return_arg(test) + # | ^^^^ -> test + #<^^ -> test + print(test) + # ^^^^ -> test + +func return_arg(arg: int) -> int: +# ^^^ arg -> arg + arg += 2 + #<^ -> arg + return arg + # ^^^ -> arg
\ No newline at end of file diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.notest.gd new file mode 100644 index 0000000000..8dfaee2e5b --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/properties.notest.gd @@ -0,0 +1,65 @@ +extends Node + +var prop1 := 42 +# ^^^^^ prop1 -> prop1 +var prop2 : int = 42 +# ^^^^^ prop2 -> prop2 +var prop3 := 42: +# ^^^^^ prop3 -> prop3 + get: + return prop3 + 13 + # ^^^^^ -> prop3 + set(value): + # ^^^^^ prop3:value -> prop3:value + prop3 = value - 13 + # | ^^^^^ -> prop3:value + #<^^^ -> prop3 +var prop4: int: +# ^^^^^ prop4 -> prop4 + get: + return 42 +var prop5 := 42: +# ^^^^^ prop5 -> prop5 + set(value): + # ^^^^^ prop5:value -> prop5:value + prop5 = value - 13 + # | ^^^^^ -> prop5:value + #<^^^ -> prop5 + +var prop6: +# ^^^^^ prop6 -> prop6 + get = get_prop6, + # ^^^^^^^^^ -> get_prop6 + set = set_prop6 + # ^^^^^^^^^ -> set_prop6 +func get_prop6(): +# ^^^^^^^^^ get_prop6 -> get_prop6 + return 42 +func set_prop6(value): +# | | ^^^^^ set_prop6:value -> set_prop6:value +# ^^^^^^^^^ set_prop6 -> set_prop6 + print(value) + # ^^^^^ -> set_prop6:value + +var prop7: +# ^^^^^ prop7 -> prop7 + get = get_prop7 + # ^^^^^^^^^ -> get_prop7 +func get_prop7(): +# ^^^^^^^^^ get_prop7 -> get_prop7 + return 42 + +var prop8: +# ^^^^^ prop8 -> prop8 + set = set_prop8 + # ^^^^^^^^^ -> set_prop8 +func set_prop8(value): +# | | ^^^^^ set_prop8:value -> set_prop8:value +# ^^^^^^^^^ set_prop8 -> set_prop8 + print(value) + # ^^^^^ -> set_prop8:value + +const const_var := 42 +# ^^^^^^^^^ const_var -> const_var +static var static_var := 42 +# ^^^^^^^^^^ static_var -> static_var diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd new file mode 100644 index 0000000000..20b8fb9bd7 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd @@ -0,0 +1,106 @@ +extends Node + +var member := 2 +# ^^^^^^ public -> public + +signal some_changed(new_value) +# | | ^^^^^^^^^ signal:parameter -> signal:parameter +# ^^^^^^^^^^^^ signal -> signal +var some_value := 42: +# ^^^^^^^^^^ property -> property + get: + return some_value + # ^^^^^^^^^^ -> property + set(value): + # ^^^^^ property:set:value -> property:set:value + some_changed.emit(value) + # | ^^^^^ -> property:set:value + #<^^^^^^^^^^ -> signal + some_value = value + # | ^^^^^ -> property:set:value + #<^^^^^^^^ -> property + +func v(): + var value := member + 2 + # | | ^^^^^^ -> public + # ^^^^^ v:value -> v:value + print(value) + # ^^^^^ -> v:value + if value > 0: + # ^^^^^ -> v:value + var beta := value + 2 + # | | ^^^^^ -> v:value + # ^^^^ v:if:beta -> v:if:beta + print(beta) + # ^^^^ -> v:if:beta + + for counter in beta: + # | | ^^^^ -> v:if:beta + # ^^^^^^^ v:if:counter -> v:if:counter + print (counter) + # ^^^^^^^ -> v:if:counter + + else: + for counter in value: + # | | ^^^^^ -> v:value + # ^^^^^^^ v:else:counter -> v:else:counter + print(counter) + # ^^^^^^^ -> v:else:counter + +func f(): + var func1 = func(value): print(value + 13) + # | | | | ^^^^^ -> f:func1:value + # | | ^^^^^ f:func1:value -> f:func1:value + # ^^^^^ f:func1 -> f:func1 + var func2 = func(value): print(value + 42) + # | | | | ^^^^^ -> f:func2:value + # | | ^^^^^ f:func2:value -> f:func2:value + # ^^^^^ f:func2 -> f:func2 + + func1.call(1) + #<^^^ -> f:func1 + func2.call(2) + #<^^^ -> f:func2 + +func m(): + var value = 42 + # ^^^^^ m:value -> m:value + + match value: + # ^^^^^ -> m:value + 13: + print(value) + # ^^^^^ -> m:value + [var start, _, var end]: + # | | ^^^ m:match:array:end -> m:match:array:end + # ^^^^^ m:match:array:start -> m:match:array:start + print(start + end) + # | | ^^^ -> m:match:array:end + # ^^^^^ -> m:match:array:start + { "name": var name }: + # ^^^^ m:match:dict:var -> m:match:dict:var + print(name) + # ^^^^ -> m:match:dict:var + var whatever: + # ^^^^^^^^ m:match:var -> m:match:var + print(whatever) + # ^^^^^^^^ -> m:match:var + +func m2(): + var value = 42 + # ^^^^^ m2:value -> m2:value + + match value: + # ^^^^^ -> m2:value + { "name": var name }: + # ^^^^ m2:match:dict:var -> m2:match:dict:var + print(name) + # ^^^^ -> m2:match:dict:var + [var name, ..]: + # ^^^^ m2:match:array:var -> m2:match:array:var + print(name) + # ^^^^ -> m2:match:array:var + var name: + # ^^^^ m2:match:var -> m2:match:var + print(name) + # ^^^^ -> m2:match:var diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd new file mode 100644 index 0000000000..338000fa0e --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd @@ -0,0 +1,56 @@ +extends Node + +var value := 42 +# ^^^^^ member:value -> member:value + +func variable(): + var value = value + 42 + #! | | ^^^^^ -> member:value + # ^^^^^ variable:value -> variable:value + print(value) + # ^^^^^ -> variable:value + +func array(): + var value = [1,value,3,value+4] + #! | | | | ^^^^^ -> member:value + #! | | ^^^^^ -> member:value + # ^^^^^ array:value -> array:value + print(value) + # ^^^^^ -> array:value + +func dictionary(): + var value = { + # ^^^^^ dictionary:value -> dictionary:value + "key1": value, + #! ^^^^^ -> member:value + "key2": 1 + value + 3, + #! ^^^^^ -> member:value + } + print(value) + # ^^^^^ -> dictionary:value + +func for_loop(): + for value in value: + # | | ^^^^^ -> member:value + # ^^^^^ for:value -> for:value + print(value) + # ^^^^^ -> for:value + +func for_range(): + for value in range(5, value): + # | | ^^^^^ -> member:value + # ^^^^^ for:range:value -> for:range:value + print(value) + # ^^^^^ -> for:range:value + +func matching(): + match value: + # ^^^^^ -> member:value + 42: print(value) + # ^^^^^ -> member:value + [var value, ..]: print(value) + # | | ^^^^^ -> match:array:value + # ^^^^^ match:array:value -> match:array:value + var value: print(value) + # | | ^^^^^ -> match:var:value + # ^^^^^ match:var:value -> match:var:value diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd new file mode 100644 index 0000000000..e5eecbb819 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd new file mode 100644 index 0000000000..9168b69f86 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\\"") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd new file mode 100644 index 0000000000..37dc910e5f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd @@ -0,0 +1,3 @@ +func test(): + # v + print(r"['"]*") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out new file mode 100644 index 0000000000..dcb5c2f289 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Closing "]" doesn't have an opening counterpart. diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd new file mode 100644 index 0000000000..d88b4a37c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd @@ -0,0 +1,5 @@ +func test(): + var a = 0 + match a: + 0 when a = 1: + print("assignment not allowed on pattern guard") diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd index 7e1982597c..0c8a5d1367 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd @@ -14,3 +14,7 @@ func test(): var TAU = "TAU" print(TAU) + + # New keyword for pattern guards. + var when = "when" + print(when) diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out index aae2ae13d5..8ac8e92ef7 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out @@ -4,3 +4,4 @@ PI INF NAN TAU +when diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd index f04f4de08d..19f6e08285 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd @@ -3,27 +3,32 @@ extends Node func test(): var child = Node.new() child.name = "Child" + @warning_ignore("unsafe_call_argument") add_child(child) child.owner = self var hey = Node.new() hey.name = "Hey" + @warning_ignore("unsafe_call_argument") child.add_child(hey) hey.owner = self hey.unique_name_in_owner = true var fake_hey = Node.new() fake_hey.name = "Hey" + @warning_ignore("unsafe_call_argument") add_child(fake_hey) fake_hey.owner = self var sub_child = Node.new() sub_child.name = "SubChild" + @warning_ignore("unsafe_call_argument") hey.add_child(sub_child) sub_child.owner = self var howdy = Node.new() howdy.name = "Howdy" + @warning_ignore("unsafe_call_argument") sub_child.add_child(howdy) howdy.owner = self howdy.unique_name_in_owner = true diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd index 8ba558e91d..3d9404b20b 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -5,9 +5,11 @@ func test(): # Create the required node structure. var hello = Node.new() hello.name = "Hello" + @warning_ignore("unsafe_call_argument") add_child(hello) var world = Node.new() world.name = "World" + @warning_ignore("unsafe_call_argument") hello.add_child(world) # All the ways of writing node paths below with the `$` operator are valid. diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index acf9ff2e21..c9d05a7e68 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,3 +1,5 @@ +extends Node + @export var example = 99 @export_range(0, 100) var example_range = 100 @export_range(0, 100, 1) var example_range_step = 101 @@ -6,7 +8,8 @@ @export var color: Color @export_color_no_alpha var color_no_alpha: Color @export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello" - +@export var node: Node +@export var node_array: Array[Node] func test(): print(example) @@ -16,3 +19,5 @@ func test(): print(color) print(color_no_alpha) print(nodepath) + print(node) + print(var_to_str(node_array)) diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index bae35e75c6..5430c975f4 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -6,3 +6,5 @@ GDTEST_OK (0, 0, 0, 1) (0, 0, 0, 1) hello +<null> +Array[Node]([]) diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index df6001c7e2..f16c768f7f 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -26,6 +26,7 @@ func test(): if true: (v as Callable).call() print() + @warning_ignore("unsafe_call_argument") other(v) print() diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd index 59cdc7d6c2..31de73813f 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -2,4 +2,5 @@ func foo(x): return x + 1 func test(): + @warning_ignore("unsafe_call_argument") print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd new file mode 100644 index 0000000000..6f546f28be --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd @@ -0,0 +1,22 @@ +func test(): + print(r"test ' \' \" \\ \n \t \u2023 test") + print(r"\n\\[\t ]*(\w+)") + print(r"") + print(r"\"") + print(r"\\\"") + print(r"\\") + print(r"\" \\\" \\\\\"") + print(r"\ \\ \\\ \\\\ \\\\\ \\") + print(r'"') + print(r'"(?:\\.|[^"])*"') + print(r"""""") + print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""") + print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''') + print(r"\t + \t") + print(r"\t \ + \t") + print(r"""\t + \t""") + print(r"""\t \ + \t""") diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out new file mode 100644 index 0000000000..114ef0a6c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out @@ -0,0 +1,22 @@ +GDTEST_OK +test ' \' \" \\ \n \t \u2023 test +\n\\[\t ]*(\w+) + +\" +\\\" +\\ +\" \\\" \\\\\" +\ \\ \\\ \\\\ \\\\\ \\ +" +"(?:\\.|[^"])*" + +test \t "test"="" " \" \\\" \ \\ \\\ test +r"""test \t "test"="" " \" \\\" \ \\ \\\ test""" +\t + \t +\t \ + \t +\t + \t +\t \ + \t diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd index f5ae2a74a7..33accd92a9 100644 --- a/modules/gdscript/tests/scripts/parser/features/super.gd +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -36,6 +36,7 @@ class SayNothing extends Say: print("howdy, see above") func say(name): + @warning_ignore("unsafe_call_argument") super(name + " super'd") print(prefix, " say nothing... or not? ", name) diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd index 523959a016..20cc0cee2e 100644 --- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -29,6 +29,7 @@ func test(): const d = 1.1 _process(d) + @warning_ignore("unsafe_call_argument") print(is_equal_approx(ㄥ, PI + (d * PI))) func _process(Δ: float) -> void: diff --git a/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd new file mode 100644 index 0000000000..340fc8c150 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd @@ -0,0 +1,3 @@ +func test(): + var x = Color() + print(len(x)) # GDScript utility function. diff --git a/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out new file mode 100644 index 0000000000..6d2938dcf3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/gd_utility_function_wrong_arg.gd +>> 3 +>> Error calling GDScript utility function "len()": Value of type 'Color' can't provide a length. diff --git a/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd new file mode 100644 index 0000000000..6568155bae --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd @@ -0,0 +1,3 @@ +func test(): + var x = Color() + print(floor(x)) # Built-in utility function. diff --git a/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out new file mode 100644 index 0000000000..b311bfa38a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/utility_function_wrong_arg.gd +>> 3 +>> Error calling utility function "floor()": Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i". diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index 58b4df5a79..bc899a3a6f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -21,6 +21,12 @@ func test(): var elem := e prints(var_to_str(e), var_to_str(elem)) + # GH-82021 + print("Test implicitly typed array literal.") + for e: float in [100, 200, 300]: + var elem := e + prints(var_to_str(e), var_to_str(elem)) + print("Test String-keys dictionary.") var d1 := {a = 1, b = 2, c = 3} for k: StringName in d1: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index f67f7d89d5..eeebdc4be5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -15,6 +15,10 @@ Test typed int array. 10.0 10.0 20.0 20.0 30.0 30.0 +Test implicitly typed array literal. +100.0 100.0 +200.0 200.0 +300.0 300.0 Test String-keys dictionary. &"a" &"a" &"b" &"b" diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd new file mode 100644 index 0000000000..4cb51f8512 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -0,0 +1,71 @@ +var global := 0 + +func test(): + var a = 0 + var b = 1 + + match a: + 0 when b == 0: + print("does not run" if true else "") + 0 when b == 1: + print("guards work") + _: + print("does not run") + + match a: + var a_bind when b == 0: + prints("a is", a_bind, "and b is 0") + var a_bind when b == 1: + prints("a is", a_bind, "and b is 1") + _: + print("does not run") + + match a: + var a_bind when a_bind < 0: + print("a is less than zero") + var a_bind when a_bind == 0: + print("a is equal to zero") + _: + print("a is more than zero") + + match [1, 2, 3]: + [1, 2, var element] when element == 0: + print("does not run") + [1, 2, var element] when element == 3: + print("3rd element is 3") + + match a: + _ when b == 0: + print("does not run") + _ when b == 1: + print("works with wildcard too.") + _: + print("does not run") + + match a: + 0, 1 when b == 0: + print("does not run") + 0, 1 when b == 1: + print("guard with multiple patterns") + _: + print("does not run") + + match a: + 0 when b == 0: + print("does not run") + 0: + print("regular pattern after guard mismatch") + + match a: + 1 when side_effect(): + print("should not run the side effect call") + 0 when side_effect(): + print("will run the side effect call, but not this") + _: + assert(global == 1) + print("side effect only ran once") + +func side_effect(): + print("side effect") + global += 1 + return false diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out new file mode 100644 index 0000000000..452d1ff4bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out @@ -0,0 +1,10 @@ +GDTEST_OK +guards work +a is 0 and b is 1 +a is equal to zero +3rd element is 3 +works with wildcard too. +guard with multiple patterns +regular pattern after guard mismatch +side effect +side effect only ran once diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd new file mode 100644 index 0000000000..805ea42455 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -0,0 +1,72 @@ +class_name TestMemberInfo + +class MyClass: + pass + +enum MyEnum {} + +const Utils = preload("../../utils.notest.gd") + +static var test_static_var_untyped +static var test_static_var_weak_null = null +static var test_static_var_weak_int = 1 +static var test_static_var_hard_int: int + +var test_var_untyped +var test_var_weak_null = null +var test_var_weak_int = 1 +@export var test_var_weak_int_exported = 1 +var test_var_weak_variant_type = TYPE_NIL +@export var test_var_weak_variant_type_exported = TYPE_NIL +var test_var_hard_variant: Variant +var test_var_hard_int: int +var test_var_hard_variant_type: Variant.Type +@export var test_var_hard_variant_type_exported: Variant.Type +var test_var_hard_node_process_mode: Node.ProcessMode +var test_var_hard_my_enum: MyEnum +var test_var_hard_array: Array +var test_var_hard_array_int: Array[int] +var test_var_hard_array_variant_type: Array[Variant.Type] +var test_var_hard_array_node_process_mode: Array[Node.ProcessMode] +var test_var_hard_array_my_enum: Array[MyEnum] +var test_var_hard_array_resource: Array[Resource] +var test_var_hard_array_this: Array[TestMemberInfo] +var test_var_hard_array_my_class: Array[MyClass] +var test_var_hard_resource: Resource +var test_var_hard_this: TestMemberInfo +var test_var_hard_my_class: MyClass + +static func test_static_func(): pass + +func test_func_implicit_void(): pass +func test_func_explicit_void() -> void: pass +func test_func_weak_null(): return null +func test_func_weak_int(): return 1 +func test_func_hard_variant() -> Variant: return null +func test_func_hard_int() -> int: return 1 +func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass +func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass + +signal test_signal_1() +signal test_signal_2(a: Variant, b) +signal test_signal_3(a: int, b: Array[int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) +signal test_signal_5(a: MyEnum, b: Array[MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) +signal test_signal_8(a: MyClass, b: Array[MyClass]) + +func test(): + var script: Script = get_script() + for property in script.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + for property in get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + for method in get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + for method in get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out new file mode 100644 index 0000000000..3a91507da9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -0,0 +1,45 @@ +GDTEST_OK +static var test_static_var_untyped: Variant +static var test_static_var_weak_null: Variant +static var test_static_var_weak_int: Variant +static var test_static_var_hard_int: int +var test_var_untyped: Variant +var test_var_weak_null: Variant +var test_var_weak_int: Variant +@export var test_var_weak_int_exported: int +var test_var_weak_variant_type: Variant +@export var test_var_weak_variant_type_exported: Variant.Type +var test_var_hard_variant: Variant +var test_var_hard_int: int +var test_var_hard_variant_type: Variant.Type +@export var test_var_hard_variant_type_exported: Variant.Type +var test_var_hard_node_process_mode: Node.ProcessMode +var test_var_hard_my_enum: TestMemberInfo.MyEnum +var test_var_hard_array: Array +var test_var_hard_array_int: Array[int] +var test_var_hard_array_variant_type: Array[Variant.Type] +var test_var_hard_array_node_process_mode: Array[Node.ProcessMode] +var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum] +var test_var_hard_array_resource: Array[Resource] +var test_var_hard_array_this: Array[TestMemberInfo] +var test_var_hard_array_my_class: Array[RefCounted] +var test_var_hard_resource: Resource +var test_var_hard_this: TestMemberInfo +var test_var_hard_my_class: RefCounted +static func test_static_func() -> void +func test_func_implicit_void() -> void +func test_func_explicit_void() -> void +func test_func_weak_null() -> Variant +func test_func_weak_int() -> Variant +func test_func_hard_variant() -> Variant +func test_func_hard_int() -> int +func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void +func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void +signal test_signal_1() +signal test_signal_2(a: Variant, b: Variant) +signal test_signal_3(a: int, b: Array[int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) +signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) +signal test_signal_8(a: RefCounted, b: Array[RefCounted]) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd new file mode 100644 index 0000000000..d0cbeeab85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -0,0 +1,45 @@ +# GH-82169 + +const Utils = preload("../../utils.notest.gd") + +class A: + static var test_static_var_a1 + static var test_static_var_a2 + var test_var_a1 + var test_var_a2 + static func test_static_func_a1(): pass + static func test_static_func_a2(): pass + func test_func_a1(): pass + func test_func_a2(): pass + signal test_signal_a1() + signal test_signal_a2() + +class B extends A: + static var test_static_var_b1 + static var test_static_var_b2 + var test_var_b1 + var test_var_b2 + static func test_static_func_b1(): pass + static func test_static_func_b2(): pass + func test_func_b1(): pass + func test_func_b2(): pass + signal test_signal_b1() + signal test_signal_b2() + +func test(): + var b := B.new() + for property in (B as GDScript).get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + print("---") + for property in b.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + print("---") + for method in b.get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + print("---") + for method in b.get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out new file mode 100644 index 0000000000..f294b66ef9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out @@ -0,0 +1,24 @@ +GDTEST_OK +static var test_static_var_a1: Variant +static var test_static_var_a2: Variant +static var test_static_var_b1: Variant +static var test_static_var_b2: Variant +--- +var test_var_b1: Variant +var test_var_b2: Variant +var test_var_a1: Variant +var test_var_a2: Variant +--- +static func test_static_func_b1() -> void +static func test_static_func_b2() -> void +func test_func_b1() -> void +func test_func_b2() -> void +static func test_static_func_a1() -> void +static func test_static_func_a2() -> void +func test_func_a1() -> void +func test_func_a2() -> void +--- +signal test_signal_b1() +signal test_signal_b2() +signal test_signal_a1() +signal test_signal_a2() diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd new file mode 100644 index 0000000000..6c5df32ffe --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -0,0 +1,36 @@ +class MyClass: + const TEST = 10 + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") +const Other = preload("./metatypes.notest.gd") + +var test_native := JSON +var test_script := Other +var test_class := MyClass +var test_enum := MyEnum + +func check_gdscript_native_class(value: Variant) -> void: + print(var_to_str(value).get_slice(",", 0).trim_prefix("Object(")) + +func check_gdscript(value: GDScript) -> void: + print(value.get_class()) + +func check_enum(value: Dictionary) -> void: + print(value) + +func test(): + for property in get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + + check_gdscript_native_class(test_native) + check_gdscript(test_script) + check_gdscript(test_class) + check_enum(test_enum) + + print(test_native.stringify([])) + print(test_script.TEST) + print(test_class.TEST) + print(test_enum.keys()) diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd new file mode 100644 index 0000000000..e6a591b927 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd @@ -0,0 +1 @@ +const TEST = 100 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.out b/modules/gdscript/tests/scripts/runtime/features/metatypes.out new file mode 100644 index 0000000000..352d1caa59 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.out @@ -0,0 +1,13 @@ +GDTEST_OK +var test_native: GDScriptNativeClass +var test_script: GDScript +var test_class: GDScript +var test_enum: Dictionary +GDScriptNativeClass +GDScript +GDScript +{ "A": 0, "B": 1, "C": 2 } +[] +100 +10 +["A", "B", "C"] diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd new file mode 100644 index 0000000000..b875efef56 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd @@ -0,0 +1,6 @@ +# GH-73213 + +func test(): + var object := Object.new() # Not `Object()`. + print(object.get_class()) + object.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out new file mode 100644 index 0000000000..3673881576 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out @@ -0,0 +1,2 @@ +GDTEST_OK +Object diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index 2f55059334..691b611574 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -7,11 +7,12 @@ func test(): test_builtin_call_validated(Vector2.UP, false) test_object_call(RefCounted.new(), false) test_object_call_method_bind(Resource.new(), false) - test_object_call_ptrcall(RefCounted.new(), false) + test_object_call_method_bind_validated(RefCounted.new(), false) print("end") func test_construct(v, f): + @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. assert(not f) # Test unary operator reading from `nil`. @@ -39,7 +40,7 @@ func test_object_call_method_bind(v: Resource, f): v.duplicate() # Native type method call with MethodBind. assert(not f) # Test unary operator reading from `nil`. -func test_object_call_ptrcall(v: RefCounted, f): +func test_object_call_method_bind_validated(v: RefCounted, f): @warning_ignore("return_value_discarded") - v.get_reference_count() # Native type method call with ptrcall. + v.get_reference_count() # Native type method call with validated MethodBind. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd new file mode 100644 index 0000000000..f6aa58737f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd @@ -0,0 +1,12 @@ +# GH-79521 + +class_name TestStaticMethodAsCallable + +static func static_func() -> String: + return "Test" + +func test(): + var a: Callable = TestStaticMethodAsCallable.static_func + var b: Callable = static_func + prints(a.call(), a.is_valid()) + prints(b.call(), b.is_valid()) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out new file mode 100644 index 0000000000..e6d461b8f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +Test true +Test true diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index 8da8bb7e53..7fa76ca4b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -44,6 +44,7 @@ func test(): @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() + @warning_ignore("unsafe_call_argument") var other = load(path + "/static_variables_load.gd") prints("load.perm:", other.perm) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index fead2df854..0dbb252b0e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -24,7 +24,7 @@ func test(): print(StringName("hello")) print(NodePath("hello/world")) var node := Node.new() - print(RID(node)) + print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented? print(node.get_name) print(node.property_list_changed) node.free() diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd new file mode 100644 index 0000000000..781843b8e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -0,0 +1,199 @@ +static func get_type(property: Dictionary, is_return: bool = false) -> String: + match property.type: + TYPE_NIL: + if property.usage & PROPERTY_USAGE_NIL_IS_VARIANT: + return "Variant" + return "void" if is_return else "null" + TYPE_INT: + if property.usage & PROPERTY_USAGE_CLASS_IS_ENUM: + if property.class_name == &"": + return "<unknown enum>" + return property.class_name + TYPE_ARRAY: + if property.hint == PROPERTY_HINT_ARRAY_TYPE: + if str(property.hint_string).is_empty(): + return "Array[<unknown type>]" + return "Array[%s]" % property.hint_string + TYPE_OBJECT: + if not str(property.class_name).is_empty(): + return property.class_name + return type_string(property.type) + + +static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: + var result: String = "" + if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): + printerr("Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.") + if property.usage & PROPERTY_USAGE_DEFAULT: + result += "@export " + if is_static: + result += "static " + result += "var " + property.name + ": " + get_type(property) + return result + + +static func get_property_additional_info(property: Dictionary) -> String: + return 'hint=%s hint_string="%s" usage=%s' % [ + get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), + str(property.hint_string).c_escape(), + get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), + ] + + +static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String: + var result: String = "" + if method.flags & METHOD_FLAG_STATIC: + result += "static " + result += ("signal " if is_signal else "func ") + method.name + "(" + + var args: Array[Dictionary] = method.args + var default_args: Array = method.default_args + var mandatory_argc: int = args.size() - default_args.size() + for i in args.size(): + if i > 0: + result += ", " + var arg: Dictionary = args[i] + result += arg.name + ": " + get_type(arg) + if i >= mandatory_argc: + result += " = " + var_to_str(default_args[i - mandatory_argc]) + + result += ")" + if is_signal: + if get_type(method.return, true) != "void": + printerr("Signal return type must be `void`.") + else: + result += " -> " + get_type(method.return, true) + return result + + +static func get_property_hint_name(hint: PropertyHint) -> String: + match hint: + PROPERTY_HINT_NONE: + return "PROPERTY_HINT_NONE" + PROPERTY_HINT_RANGE: + return "PROPERTY_HINT_RANGE" + PROPERTY_HINT_ENUM: + return "PROPERTY_HINT_ENUM" + PROPERTY_HINT_ENUM_SUGGESTION: + return "PROPERTY_HINT_ENUM_SUGGESTION" + PROPERTY_HINT_EXP_EASING: + return "PROPERTY_HINT_EXP_EASING" + PROPERTY_HINT_LINK: + return "PROPERTY_HINT_LINK" + PROPERTY_HINT_FLAGS: + return "PROPERTY_HINT_FLAGS" + PROPERTY_HINT_LAYERS_2D_RENDER: + return "PROPERTY_HINT_LAYERS_2D_RENDER" + PROPERTY_HINT_LAYERS_2D_PHYSICS: + return "PROPERTY_HINT_LAYERS_2D_PHYSICS" + PROPERTY_HINT_LAYERS_2D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_2D_NAVIGATION" + PROPERTY_HINT_LAYERS_3D_RENDER: + return "PROPERTY_HINT_LAYERS_3D_RENDER" + PROPERTY_HINT_LAYERS_3D_PHYSICS: + return "PROPERTY_HINT_LAYERS_3D_PHYSICS" + PROPERTY_HINT_LAYERS_3D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_3D_NAVIGATION" + PROPERTY_HINT_LAYERS_AVOIDANCE: + return "PROPERTY_HINT_LAYERS_AVOIDANCE" + PROPERTY_HINT_FILE: + return "PROPERTY_HINT_FILE" + PROPERTY_HINT_DIR: + return "PROPERTY_HINT_DIR" + PROPERTY_HINT_GLOBAL_FILE: + return "PROPERTY_HINT_GLOBAL_FILE" + PROPERTY_HINT_GLOBAL_DIR: + return "PROPERTY_HINT_GLOBAL_DIR" + PROPERTY_HINT_RESOURCE_TYPE: + return "PROPERTY_HINT_RESOURCE_TYPE" + PROPERTY_HINT_MULTILINE_TEXT: + return "PROPERTY_HINT_MULTILINE_TEXT" + PROPERTY_HINT_EXPRESSION: + return "PROPERTY_HINT_EXPRESSION" + PROPERTY_HINT_PLACEHOLDER_TEXT: + return "PROPERTY_HINT_PLACEHOLDER_TEXT" + PROPERTY_HINT_COLOR_NO_ALPHA: + return "PROPERTY_HINT_COLOR_NO_ALPHA" + PROPERTY_HINT_OBJECT_ID: + return "PROPERTY_HINT_OBJECT_ID" + PROPERTY_HINT_TYPE_STRING: + return "PROPERTY_HINT_TYPE_STRING" + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: + return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" + PROPERTY_HINT_OBJECT_TOO_BIG: + return "PROPERTY_HINT_OBJECT_TOO_BIG" + PROPERTY_HINT_NODE_PATH_VALID_TYPES: + return "PROPERTY_HINT_NODE_PATH_VALID_TYPES" + PROPERTY_HINT_SAVE_FILE: + return "PROPERTY_HINT_SAVE_FILE" + PROPERTY_HINT_GLOBAL_SAVE_FILE: + return "PROPERTY_HINT_GLOBAL_SAVE_FILE" + PROPERTY_HINT_INT_IS_OBJECTID: + return "PROPERTY_HINT_INT_IS_OBJECTID" + PROPERTY_HINT_INT_IS_POINTER: + return "PROPERTY_HINT_INT_IS_POINTER" + PROPERTY_HINT_ARRAY_TYPE: + return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_LOCALE_ID: + return "PROPERTY_HINT_LOCALE_ID" + PROPERTY_HINT_LOCALIZABLE_STRING: + return "PROPERTY_HINT_LOCALIZABLE_STRING" + PROPERTY_HINT_NODE_TYPE: + return "PROPERTY_HINT_NODE_TYPE" + PROPERTY_HINT_HIDE_QUATERNION_EDIT: + return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" + PROPERTY_HINT_PASSWORD: + return "PROPERTY_HINT_PASSWORD" + push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + return "<invalid hint>" + + +static func get_property_usage_string(usage: int) -> String: + if usage == PROPERTY_USAGE_NONE: + return "PROPERTY_USAGE_NONE" + + const FLAGS: Array[Array] = [ + [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"], + [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"], + [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"], + [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"], + [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"], + [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"], + [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"], + [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"], + [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"], + [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"], + [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"], + [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"], + [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"], + [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"], + [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"], + [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"], + [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"], + [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"], + [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"], + [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"], + [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"], + [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"], + [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"], + [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"], + [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"], + [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"], + [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"], + [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"], + [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"], + [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"], + ] + + var result: String = "" + + for flag in FLAGS: + if usage & flag[0]: + result += flag[1] + "|" + usage &= ~flag[0] + + if usage != PROPERTY_USAGE_NONE: + push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + return "<invalid usage flags>" + + return result.left(-1) diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 0446a7aad6..467bedc4b2 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -138,12 +138,13 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const for (const KeyValue<StringName, GDScriptFunction *> &E : script->get_member_functions()) { const GDScriptFunction *func = E.value; - String signature = "Disassembling " + func->get_name().operator String() + "("; - for (int i = 0; i < func->get_argument_count(); i++) { + const MethodInfo &mi = func->get_method_info(); + String signature = "Disassembling " + mi.name + "("; + for (int i = 0; i < mi.arguments.size(); i++) { if (i > 0) { signature += ", "; } - signature += func->get_argument_name(i); + signature += mi.arguments[i].name; } print_line(signature + ")"); #ifdef TOOLS_ENABLED @@ -156,7 +157,7 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const for (const KeyValue<StringName, Ref<GDScript>> &F : script->get_subclasses()) { const Ref<GDScript> inner_script = F.value; print_line(""); - print_line(vformat("Inner Class: %s", inner_script->get_script_class_name())); + print_line(vformat("Inner Class: %s", inner_script->get_local_name())); print_line(""); recursively_disassemble_functions(inner_script, p_lines); } @@ -222,6 +223,16 @@ void test(TestType p_type) { // Initialize the language for the test routine. init_language(fa->get_path_absolute().get_base_dir()); + // Load global classes. + TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list(); + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { + continue; + } + ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + Vector<uint8_t> buf; uint64_t flen = fa->get_length(); buf.resize(flen + 1); diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h new file mode 100644 index 0000000000..e57df00e2d --- /dev/null +++ b/modules/gdscript/tests/test_lsp.h @@ -0,0 +1,480 @@ +/**************************************************************************/ +/* test_lsp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 TEST_LSP_H +#define TEST_LSP_H + +#ifdef TOOLS_ENABLED + +#include "tests/test_macros.h" + +#include "../language_server/gdscript_extend_parser.h" +#include "../language_server/gdscript_language_protocol.h" +#include "../language_server/gdscript_workspace.h" +#include "../language_server/godot_lsp.h" + +#include "core/io/dir_access.h" +#include "core/io/file_access_pack.h" +#include "core/os/os.h" +#include "editor/editor_help.h" +#include "editor/editor_node.h" +#include "modules/gdscript/gdscript_analyzer.h" +#include "modules/regex/regex.h" + +#include "thirdparty/doctest/doctest.h" + +template <> +struct doctest::StringMaker<lsp::Position> { + static doctest::String convert(const lsp::Position &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +template <> +struct doctest::StringMaker<lsp::Range> { + static doctest::String convert(const lsp::Range &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +template <> +struct doctest::StringMaker<GodotPosition> { + static doctest::String convert(const GodotPosition &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +namespace GDScriptTests { + +// LSP GDScript test scripts are located inside project of other GDScript tests: +// Cannot reset `ProjectSettings` (singleton) -> Cannot load another workspace and resources in there. +// -> Reuse GDScript test project. LSP specific scripts are then placed inside `lsp` folder. +// Access via `res://lsp/my_script.notest.gd`. +const String root = "modules/gdscript/tests/scripts/"; + +/* + * After use: + * * `memdelete` returned `GDScriptLanguageProtocol`. + * * Call `GDScriptTests::::finish_language`. + */ +GDScriptLanguageProtocol *initialize(const String &p_root) { + Error err = OK; + Ref<DirAccess> dir(DirAccess::open(p_root, &err)); + REQUIRE_MESSAGE(err == OK, "Could not open specified root directory"); + String absolute_root = dir->get_current_dir(); + init_language(absolute_root); + + GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol); + + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + workspace->root = absolute_root; + // On windows: `C:/...` -> `C%3A/...`. + workspace->root_uri = "file:///" + absolute_root.lstrip("/").replace_first(":", "%3A"); + + return proto; +} + +lsp::Position pos(const int p_line, const int p_character) { + lsp::Position p; + p.line = p_line; + p.character = p_character; + return p; +} + +lsp::Range range(const lsp::Position p_start, const lsp::Position p_end) { + lsp::Range r; + r.start = p_start; + r.end = p_end; + return r; +} + +lsp::TextDocumentPositionParams pos_in(const lsp::DocumentUri &p_uri, const lsp::Position p_pos) { + lsp::TextDocumentPositionParams params; + params.textDocument.uri = p_uri; + params.position = p_pos; + return params; +} + +const lsp::DocumentSymbol *test_resolve_symbol_at(const String &p_uri, const lsp::Position p_pos, const String &p_expected_uri, const String &p_expected_name, const lsp::Range &p_expected_range) { + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + + lsp::TextDocumentPositionParams params = pos_in(p_uri, p_pos); + const lsp::DocumentSymbol *symbol = workspace->resolve_symbol(params); + CHECK(symbol); + + if (symbol) { + CHECK_EQ(symbol->uri, p_expected_uri); + CHECK_EQ(symbol->name, p_expected_name); + CHECK_EQ(symbol->selectionRange, p_expected_range); + } + + return symbol; +} + +struct InlineTestData { + lsp::Range range; + String text; + String name; + String ref; + + static bool try_parse(const Vector<String> &p_lines, const int p_line_number, InlineTestData &r_data) { + String line = p_lines[p_line_number]; + + RegEx regex = RegEx("^\\t*#[ |]*(?<range>(?<left><)?\\^+)(\\s+(?<name>(?!->)\\S+))?(\\s+->\\s+(?<ref>\\S+))?"); + Ref<RegExMatch> match = regex.search(line); + if (match.is_null()) { + return false; + } + + // Find first line without leading comment above current line. + int target_line = p_line_number; + while (target_line >= 0) { + String dedented = p_lines[target_line].lstrip("\t"); + if (!dedented.begins_with("#")) { + break; + } + target_line--; + } + if (target_line < 0) { + return false; + } + r_data.range.start.line = r_data.range.end.line = target_line; + + String marker = match->get_string("range"); + int i = line.find(marker); + REQUIRE(i >= 0); + r_data.range.start.character = i; + if (!match->get_string("left").is_empty()) { + // Include `#` (comment char) in range. + r_data.range.start.character--; + } + r_data.range.end.character = i + marker.length(); + + String target = p_lines[target_line]; + r_data.text = target.substr(r_data.range.start.character, r_data.range.end.character - r_data.range.start.character); + + r_data.name = match->get_string("name"); + r_data.ref = match->get_string("ref"); + + return true; + } +}; + +Vector<InlineTestData> read_tests(const String &p_path) { + Error err; + String source = FileAccess::get_file_as_string(p_path, &err); + REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path)); + + // Format: + // ```gdscript + // var foo = bar + baz + // # | | | | ^^^ name -> ref + // # | | ^^^ -> ref + // # ^^^ name + // + // func my_func(): + // # ^^^^^^^ name + // var value = foo + 42 + // # ^^^^^ name + // print(value) + // # ^^^^^ -> ref + // ``` + // + // * `^`: Range marker. + // * `name`: Unique name. Can contain any characters except whitespace chars. + // * `ref`: Reference to unique name. + // + // Notes: + // * If range should include first content-char (which is occupied by `#`): use `<` for next marker. + // -> Range expands 1 to left (-> includes `#`). + // * Note: Means: Range cannot be single char directly marked by `#`, but must be at least two chars (marked with `#<`). + // * Comment must start at same ident as line its marked (-> because of tab alignment...). + // * Use spaces to align after `#`! -> for correct alignment + // * Between `#` and `^` can be spaces or `|` (to better visualize what's marked below). + PackedStringArray lines = source.split("\n"); + + PackedStringArray names; + Vector<InlineTestData> data; + for (int i = 0; i < lines.size(); i++) { + InlineTestData d; + if (InlineTestData::try_parse(lines, i, d)) { + if (!d.name.is_empty()) { + // Safety check: names must be unique. + if (names.find(d.name) != -1) { + FAIL(vformat("Duplicated name '%s' in '%s'. Names must be unique!", d.name, p_path)); + } + names.append(d.name); + } + + data.append(d); + } + } + + return data; +} + +void test_resolve_symbol(const String &p_uri, const InlineTestData &p_test_data, const Vector<InlineTestData> &p_all_data) { + if (p_test_data.ref.is_empty()) { + return; + } + + SUBCASE(vformat("Can resolve symbol '%s' at %s to '%s'", p_test_data.text, p_test_data.range.to_string(), p_test_data.ref).utf8().get_data()) { + const InlineTestData *target = nullptr; + for (int i = 0; i < p_all_data.size(); i++) { + if (p_all_data[i].name == p_test_data.ref) { + target = &p_all_data[i]; + break; + } + } + REQUIRE_MESSAGE(target, vformat("No target for ref '%s'", p_test_data.ref)); + + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + lsp::Position pos = p_test_data.range.start; + + SUBCASE("start of identifier") { + pos.character = p_test_data.range.start.character; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + + SUBCASE("inside identifier") { + pos.character = (p_test_data.range.end.character + p_test_data.range.start.character) / 2; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + + SUBCASE("end of identifier") { + pos.character = p_test_data.range.end.character; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + } +} + +Vector<InlineTestData> filter_ref_towards(const Vector<InlineTestData> &p_data, const String &p_name) { + Vector<InlineTestData> res; + + for (const InlineTestData &d : p_data) { + if (d.ref == p_name) { + res.append(d); + } + } + + return res; +} + +void test_resolve_symbols(const String &p_uri, const Vector<InlineTestData> &p_test_data, const Vector<InlineTestData> &p_all_data) { + for (const InlineTestData &d : p_test_data) { + test_resolve_symbol(p_uri, d, p_all_data); + } +} + +void assert_no_errors_in(const String &p_path) { + Error err; + String source = FileAccess::get_file_as_string(p_path, &err); + REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path)); + + GDScriptParser parser; + err = parser.parse(source, p_path, true); + REQUIRE_MESSAGE(err == OK, vformat("Errors while parsing '%s'", p_path)); + + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + REQUIRE_MESSAGE(err == OK, vformat("Errors while analyzing '%s'", p_path)); +} + +inline lsp::Position lsp_pos(int line, int character) { + lsp::Position p; + p.line = line; + p.character = character; + return p; +} + +void test_position_roundtrip(lsp::Position p_lsp, GodotPosition p_gd, const PackedStringArray &p_lines) { + GodotPosition actual_gd = GodotPosition::from_lsp(p_lsp, p_lines); + CHECK_EQ(p_gd, actual_gd); + lsp::Position actual_lsp = p_gd.to_lsp(p_lines); + CHECK_EQ(p_lsp, actual_lsp); +} + +// Note: +// * Cursor is BETWEEN chars +// * `va|r` -> cursor between `a`&`r` +// * `var` +// ^ +// -> Character on `r` -> cursor between `a`&`r`s for tests: +// * Line & Char: +// * LSP: both 0-based +// * Godot: both 1-based +TEST_SUITE("[Modules][GDScript][LSP]") { + TEST_CASE("Can convert positions to and from Godot") { + String code = R"(extends Node + +var member := 42 + +func f(): + var value := 42 + return value + member)"; + PackedStringArray lines = code.split("\n"); + + SUBCASE("line after end") { + lsp::Position lsp = lsp_pos(7, 0); + GodotPosition gd(8, 1); + test_position_roundtrip(lsp, gd, lines); + } + SUBCASE("first char in first line") { + lsp::Position lsp = lsp_pos(0, 0); + GodotPosition gd(1, 1); + test_position_roundtrip(lsp, gd, lines); + } + + SUBCASE("with tabs") { + // On `v` in `value` in `var value := ...`. + lsp::Position lsp = lsp_pos(5, 6); + GodotPosition gd(6, 13); + test_position_roundtrip(lsp, gd, lines); + } + + SUBCASE("doesn't fail with column outside of character length") { + lsp::Position lsp = lsp_pos(2, 100); + GodotPosition::from_lsp(lsp, lines); + + GodotPosition gd(3, 100); + gd.to_lsp(lines); + } + + SUBCASE("doesn't fail with line outside of line length") { + lsp::Position lsp = lsp_pos(200, 100); + GodotPosition::from_lsp(lsp, lines); + + GodotPosition gd(300, 100); + gd.to_lsp(lines); + } + + SUBCASE("special case: negative line for root class") { + GodotPosition gd(-1, 0); + lsp::Position expected = lsp_pos(0, 0); + lsp::Position actual = gd.to_lsp(lines); + CHECK_EQ(actual, expected); + } + SUBCASE("special case: lines.length() + 1 for root class") { + GodotPosition gd(lines.size() + 1, 0); + lsp::Position expected = lsp_pos(lines.size(), 0); + lsp::Position actual = gd.to_lsp(lines); + CHECK_EQ(actual, expected); + } + } + TEST_CASE("[workspace][resolve_symbol]") { + GDScriptLanguageProtocol *proto = initialize(root); + REQUIRE(proto); + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + + { + String path = "res://lsp/local_variables.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + SUBCASE("Can get correct ranges for public variables") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "member"); + test_resolve_symbols(uri, test_data, all_test_data); + } + SUBCASE("Can get correct ranges for local variables") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "test"); + test_resolve_symbols(uri, test_data, all_test_data); + } + SUBCASE("Can get correct ranges for local parameters") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "arg"); + test_resolve_symbols(uri, test_data, all_test_data); + } + } + + SUBCASE("Can get correct ranges for indented variables") { + String path = "res://lsp/indentation.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for scopes") { + String path = "res://lsp/scopes.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for lambda") { + String path = "res://lsp/lambdas.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for inner class") { + String path = "res://lsp/class.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for inner class") { + String path = "res://lsp/enums.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for shadowing & shadowed variables") { + String path = "res://lsp/shadowing_initializer.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for properties and getter/setter") { + String path = "res://lsp/properties.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + memdelete(proto); + finish_language(); + } +} + +} // namespace GDScriptTests + +#endif // TOOLS_ENABLED + +#endif // TEST_LSP_H diff --git a/modules/glslang/SCsub b/modules/glslang/SCsub index 22ef1b5ea9..a34a97d230 100644 --- a/modules/glslang/SCsub +++ b/modules/glslang/SCsub @@ -12,6 +12,8 @@ thirdparty_obj = [] if env["builtin_glslang"]: thirdparty_dir = "#thirdparty/glslang/" thirdparty_sources = [ + "glslang/GenericCodeGen/CodeGen.cpp", + "glslang/GenericCodeGen/Link.cpp", "glslang/MachineIndependent/attribute.cpp", "glslang/MachineIndependent/Constant.cpp", "glslang/MachineIndependent/glslang_tab.cpp", @@ -40,8 +42,7 @@ if env["builtin_glslang"]: "glslang/MachineIndependent/SpirvIntrinsics.cpp", "glslang/MachineIndependent/SymbolTable.cpp", "glslang/MachineIndependent/Versions.cpp", - "glslang/GenericCodeGen/CodeGen.cpp", - "glslang/GenericCodeGen/Link.cpp", + "glslang/ResourceLimits/ResourceLimits.cpp", "OGLCompilersDLL/InitializeDll.cpp", "SPIRV/disassemble.cpp", "SPIRV/doc.cpp", diff --git a/modules/glslang/glslang_resource_limits.h b/modules/glslang/glslang_resource_limits.h deleted file mode 100644 index 8340e63096..0000000000 --- a/modules/glslang/glslang_resource_limits.h +++ /dev/null @@ -1,156 +0,0 @@ -/**************************************************************************/ -/* glslang_resource_limits.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 GLSLANG_RESOURCE_LIMITS_H -#define GLSLANG_RESOURCE_LIMITS_H - -#include <glslang/Include/ResourceLimits.h> - -// Synchronized with upstream glslang/StandAlone/ResourceLimits.cpp which is not -// part of the public API. - -const TBuiltInResource DefaultTBuiltInResource = { - /* .MaxLights = */ 32, - /* .MaxClipPlanes = */ 6, - /* .MaxTextureUnits = */ 32, - /* .MaxTextureCoords = */ 32, - /* .MaxVertexAttribs = */ 64, - /* .MaxVertexUniformComponents = */ 4096, - /* .MaxVaryingFloats = */ 64, - /* .MaxVertexTextureImageUnits = */ 32, - /* .MaxCombinedTextureImageUnits = */ 80, - /* .MaxTextureImageUnits = */ 32, - /* .MaxFragmentUniformComponents = */ 4096, - /* .MaxDrawBuffers = */ 32, - /* .MaxVertexUniformVectors = */ 128, - /* .MaxVaryingVectors = */ 8, - /* .MaxFragmentUniformVectors = */ 16, - /* .MaxVertexOutputVectors = */ 16, - /* .MaxFragmentInputVectors = */ 15, - /* .MinProgramTexelOffset = */ -8, - /* .MaxProgramTexelOffset = */ 7, - /* .MaxClipDistances = */ 8, - /* .MaxComputeWorkGroupCountX = */ 65535, - /* .MaxComputeWorkGroupCountY = */ 65535, - /* .MaxComputeWorkGroupCountZ = */ 65535, - /* .MaxComputeWorkGroupSizeX = */ 1024, - /* .MaxComputeWorkGroupSizeY = */ 1024, - /* .MaxComputeWorkGroupSizeZ = */ 64, - /* .MaxComputeUniformComponents = */ 1024, - /* .MaxComputeTextureImageUnits = */ 16, - /* .MaxComputeImageUniforms = */ 8, - /* .MaxComputeAtomicCounters = */ 8, - /* .MaxComputeAtomicCounterBuffers = */ 1, - /* .MaxVaryingComponents = */ 60, - /* .MaxVertexOutputComponents = */ 64, - /* .MaxGeometryInputComponents = */ 64, - /* .MaxGeometryOutputComponents = */ 128, - /* .MaxFragmentInputComponents = */ 128, - /* .MaxImageUnits = */ 8, - /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, - /* .MaxCombinedShaderOutputResources = */ 8, - /* .MaxImageSamples = */ 0, - /* .MaxVertexImageUniforms = */ 0, - /* .MaxTessControlImageUniforms = */ 0, - /* .MaxTessEvaluationImageUniforms = */ 0, - /* .MaxGeometryImageUniforms = */ 0, - /* .MaxFragmentImageUniforms = */ 8, - /* .MaxCombinedImageUniforms = */ 8, - /* .MaxGeometryTextureImageUnits = */ 16, - /* .MaxGeometryOutputVertices = */ 256, - /* .MaxGeometryTotalOutputComponents = */ 1024, - /* .MaxGeometryUniformComponents = */ 1024, - /* .MaxGeometryVaryingComponents = */ 64, - /* .MaxTessControlInputComponents = */ 128, - /* .MaxTessControlOutputComponents = */ 128, - /* .MaxTessControlTextureImageUnits = */ 16, - /* .MaxTessControlUniformComponents = */ 1024, - /* .MaxTessControlTotalOutputComponents = */ 4096, - /* .MaxTessEvaluationInputComponents = */ 128, - /* .MaxTessEvaluationOutputComponents = */ 128, - /* .MaxTessEvaluationTextureImageUnits = */ 16, - /* .MaxTessEvaluationUniformComponents = */ 1024, - /* .MaxTessPatchComponents = */ 120, - /* .MaxPatchVertices = */ 32, - /* .MaxTessGenLevel = */ 64, - /* .MaxViewports = */ 16, - /* .MaxVertexAtomicCounters = */ 0, - /* .MaxTessControlAtomicCounters = */ 0, - /* .MaxTessEvaluationAtomicCounters = */ 0, - /* .MaxGeometryAtomicCounters = */ 0, - /* .MaxFragmentAtomicCounters = */ 8, - /* .MaxCombinedAtomicCounters = */ 8, - /* .MaxAtomicCounterBindings = */ 1, - /* .MaxVertexAtomicCounterBuffers = */ 0, - /* .MaxTessControlAtomicCounterBuffers = */ 0, - /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, - /* .MaxGeometryAtomicCounterBuffers = */ 0, - /* .MaxFragmentAtomicCounterBuffers = */ 1, - /* .MaxCombinedAtomicCounterBuffers = */ 1, - /* .MaxAtomicCounterBufferSize = */ 16384, - /* .MaxTransformFeedbackBuffers = */ 4, - /* .MaxTransformFeedbackInterleavedComponents = */ 64, - /* .MaxCullDistances = */ 8, - /* .MaxCombinedClipAndCullDistances = */ 8, - /* .MaxSamples = */ 4, - /* .maxMeshOutputVerticesNV = */ 256, - /* .maxMeshOutputPrimitivesNV = */ 512, - /* .maxMeshWorkGroupSizeX_NV = */ 32, - /* .maxMeshWorkGroupSizeY_NV = */ 1, - /* .maxMeshWorkGroupSizeZ_NV = */ 1, - /* .maxTaskWorkGroupSizeX_NV = */ 32, - /* .maxTaskWorkGroupSizeY_NV = */ 1, - /* .maxTaskWorkGroupSizeZ_NV = */ 1, - /* .maxMeshViewCountNV = */ 4, - /* .maxMeshOutputVerticesEXT = */ 256, - /* .maxMeshOutputPrimitivesEXT = */ 256, - /* .maxMeshWorkGroupSizeX_EXT = */ 128, - /* .maxMeshWorkGroupSizeY_EXT = */ 128, - /* .maxMeshWorkGroupSizeZ_EXT = */ 128, - /* .maxTaskWorkGroupSizeX_EXT = */ 128, - /* .maxTaskWorkGroupSizeY_EXT = */ 128, - /* .maxTaskWorkGroupSizeZ_EXT = */ 128, - /* .maxMeshViewCountEXT = */ 4, - /* .maxDualSourceDrawBuffersEXT = */ 1, - - /* .limits = */ { - /* .nonInductiveForLoops = */ 1, - /* .whileLoops = */ 1, - /* .doWhileLoops = */ 1, - /* .generalUniformIndexing = */ 1, - /* .generalAttributeMatrixVectorIndexing = */ 1, - /* .generalVaryingIndexing = */ 1, - /* .generalSamplerIndexing = */ 1, - /* .generalVariableIndexing = */ 1, - /* .generalConstantMatrixVectorIndexing = */ 1, - } -}; - -#endif // GLSLANG_RESOURCE_LIMITS_H diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index 2b070c24b8..7fe3a57880 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -30,12 +30,11 @@ #include "register_types.h" -#include "glslang_resource_limits.h" - #include "core/config/engine.h" #include "servers/rendering/rendering_device.h" #include <glslang/Include/Types.h> +#include <glslang/Public/ResourceLimits.h> #include <glslang/Public/ShaderLang.h> #include <glslang/SPIRV/GlslangToSpv.h> @@ -133,7 +132,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage const int DefaultVersion = 100; //parse - if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) { + if (!shader.parse(GetDefaultResources(), DefaultVersion, false, messages)) { if (r_error) { (*r_error) = "Failed parse:\n"; (*r_error) += shader.getInfoLog(); diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 9b760a997a..5c10b76e0a 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -5,7 +5,7 @@ </brief_description> <description> GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene. - All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. + All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects. GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported. </description> <tutorials> @@ -87,4 +87,28 @@ </description> </method> </methods> + <members> + <member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default=""PNG""> + The user-friendly name of the export image format. This is used when exporting the GLTF file, including writing to a file and writing to a byte array. + By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes. + </member> + <member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75"> + If [member image_format] is a lossy image format, this determines the lossy quality of the image. On a range of [code]0.0[/code] to [code]1.0[/code], where [code]0.0[/code] is the lowest quality and [code]1.0[/code] is the highest quality. A lossy quality of [code]1.0[/code] is not the same as lossless. + </member> + <member name="root_node_mode" type="int" setter="set_root_node_mode" getter="get_root_node_mode" enum="GLTFDocument.RootNodeMode" default="0"> + How to process the root node during export. See [enum RootNodeMode] for details. The default and recommended value is [constant ROOT_NODE_MODE_SINGLE_ROOT]. + [b]Note:[/b] Regardless of how the glTF file is exported, when importing, the root node type and name can be overridden in the scene import settings tab. + </member> + </members> + <constants> + <constant name="ROOT_NODE_MODE_SINGLE_ROOT" value="0" enum="RootNodeMode"> + Treat the Godot scene's root node as the root node of the glTF file, and mark it as the single root node via the [code]GODOT_single_root[/code] glTF extension. This will be parsed the same as [constant ROOT_NODE_MODE_KEEP_ROOT] if the implementation does not support [code]GODOT_single_root[/code]. + </constant> + <constant name="ROOT_NODE_MODE_KEEP_ROOT" value="1" enum="RootNodeMode"> + Treat the Godot scene's root node as the root node of the glTF file, but do not mark it as anything special. An extra root node will be generated when importing into Godot. This uses only vanilla glTF features. This is equivalent to the behavior in Godot 4.1 and earlier. + </constant> + <constant name="ROOT_NODE_MODE_MULTI_ROOT" value="2" enum="RootNodeMode"> + Treat the Godot scene's root node as the name of the glTF scene, and add all of its children as root nodes of the glTF file. This uses only vanilla glTF features. This avoids an extra root node, but only the name of the Godot scene's root node will be preserved, as it will not be saved as a node. + </constant> + </constants> </class> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index bae980fb56..eee62845ca 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -28,7 +28,7 @@ <param index="2" name="json" type="Dictionary" /> <param index="3" name="node" type="Node" /> <description> - Part of the export process. This method is run after [method _export_preserialize] and before [method _export_post]. + Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json]. This method can be used to modify the final JSON of each node. </description> </method> @@ -53,7 +53,7 @@ <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_node]. + Part of the export process. This method is run after [method _convert_scene_node] and before [method _get_saveable_image_formats]. This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem]. </description> </method> @@ -63,8 +63,9 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_parent" type="Node" /> <description> - Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_post_parse]. + Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node]. Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node. + [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node. </description> </method> <method name="_get_image_file_extension" qualifiers="virtual"> @@ -73,6 +74,13 @@ Returns the file extension to use for saving image data into, for example, [code]".png"[/code]. If defined, when this extension is used to handle images, and the images are saved to a separate file, the image bytes will be copied to a file with this extension. If this is set, there should be a [ResourceImporter] class able to import the file. If not defined or empty, Godot will save the image into a PNG file. </description> </method> + <method name="_get_saveable_image_formats" qualifiers="virtual"> + <return type="PackedStringArray" /> + <description> + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_node]. + Returns an array of the image formats that can be saved/exported by this extension. This extension will only be selected as the image exporter if the [GLTFDocument]'s [member GLTFDocument.image_format] is in this array. If this [GLTFDocumentExtension] is selected as the image exporter, one of the [method _save_image_at_path] or [method _serialize_image_to_bytes] methods will run next, otherwise [method _export_node] will run next. If the format name contains [code]"Lossy"[/code], the lossy quality slider will be displayed. + </description> + </method> <method name="_get_supported_extensions" qualifiers="virtual"> <return type="PackedStringArray" /> <description> @@ -87,7 +95,7 @@ <param index="2" name="json" type="Dictionary" /> <param index="3" name="node" type="Node" /> <description> - Part of the import process. This method is run after [method _import_post_parse] and before [method _import_post]. + Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_post]. This method can be used to make modifications to each of the generated Godot scene nodes. </description> </method> @@ -104,7 +112,7 @@ <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node]. + Part of the import process. This method is run after [method _parse_node_extensions] and before [method _generate_scene_node]. This method can be used to modify any of the data imported so far, including any scene nodes, before running the final per-node import step. </description> </method> @@ -134,7 +142,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="extensions" type="Dictionary" /> <description> - Part of the import process. This method is run after [method _get_supported_extensions] and before [method _generate_scene_node]. + Part of the import process. This method is run after [method _get_supported_extensions] and before [method _import_post_parse]. Runs when parsing the node extensions of a GLTFNode. This method can be used to process the extension JSON data into a format that can be used by [method _generate_scene_node]. The return value should be a member of the [enum Error] enum. </description> </method> @@ -148,5 +156,41 @@ Runs when parsing the texture JSON from the GLTF textures array. This can be used to set the source image index to use as the texture. </description> </method> + <method name="_save_image_at_path" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="image" type="Image" /> + <param index="2" name="file_path" type="String" /> + <param index="3" name="image_format" type="String" /> + <param index="4" name="lossy_quality" type="float" /> + <description> + Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. + This method is run when saving images separately from the GLTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + </description> + </method> + <method name="_serialize_image_to_bytes" qualifiers="virtual"> + <return type="PackedByteArray" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="image" type="Image" /> + <param index="2" name="image_dict" type="Dictionary" /> + <param index="3" name="image_format" type="String" /> + <param index="4" name="lossy_quality" type="float" /> + <description> + Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. + This method is run when embedding images in the GLTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + This method must set the image MIME type in the [param image_dict] with the [code]"mimeType"[/code] key. For example, for a PNG image, it would be set to [code]"image/png"[/code]. The return value must be a [PackedByteArray] containing the image data. + </description> + </method> + <method name="_serialize_texture_json" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="texture_json" type="Dictionary" /> + <param index="2" name="gltf_texture" type="GLTFTexture" /> + <param index="3" name="image_format" type="String" /> + <description> + Part of the export process. This method is run after [method _save_image_at_path] or [method _serialize_image_to_bytes], and before [method _export_node]. Note that this method only runs when this [GLTFDocumentExtension] is selected as the image exporter. + This method can be used to set up the extensions for the texture JSON by editing [param texture_json]. The extension must also be added as used extension with [method GLTFState.add_used_extension], be sure to set [code]required[/code] to [code]true[/code] if you are not providing a fallback. + </description> + </method> </methods> </class> diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 3787d0fe5e..2fad475e92 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -49,6 +49,67 @@ #include <shlwapi.h> #endif +static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { + String path = p_path; +#ifdef WINDOWS_ENABLED + path = path.path_join("blender.exe"); +#else + path = path.path_join("blender"); +#endif + +#if defined(MACOS_ENABLED) + if (!FileAccess::exists(path)) { + path = p_path.path_join("Blender"); + } +#endif + + if (!FileAccess::exists(path)) { + if (r_err) { + *r_err = TTR("Path does not contain a Blender installation."); + } + return false; + } + List<String> args; + args.push_back("--version"); + String pipe; + Error err = OS::get_singleton()->execute(path, args, &pipe); + if (err != OK) { + if (r_err) { + *r_err = TTR("Can't execute Blender binary."); + } + return false; + } + int bl = pipe.find("Blender "); + if (bl == -1) { + if (r_err) { + *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path); + } + return false; + } + pipe = pipe.substr(bl); + pipe = pipe.replace_first("Blender ", ""); + int pp = pipe.find("."); + if (pp == -1) { + if (r_err) { + *r_err = TTR("Path supplied lacks a Blender binary."); + } + return false; + } + String v = pipe.substr(0, pp); + r_major = v.to_int(); + if (r_major < 3) { + if (r_err) { + *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); + } + return false; + } + + int pp2 = pipe.find(".", pp + 1); + r_minor = pp2 > pp ? pipe.substr(pp + 1, pp2 - pp - 1).to_int() : 0; + + return true; +} + uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; } @@ -60,12 +121,18 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { - // Get global paths for source and sink. + String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); + if (blender_major_version == -1 || blender_minor_version == -1) { + _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); + } + + // Get global paths for source and sink. // Escape paths to be valid Python strings to embed in the script. const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); + const String blend_basename = p_path.get_file().get_basename(); const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( - vformat("%s-%s.gltf", p_path.get_file().get_basename(), p_path.md5_text())); + vformat("%s-%s.gltf", blend_basename, p_path.md5_text())); const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); // Handle configuration options. @@ -153,9 +220,17 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ parameters_map["export_tangents"] = false; } if (p_options.has(SNAME("blender/animation/group_tracks")) && p_options[SNAME("blender/animation/group_tracks")]) { - parameters_map["export_nla_strips"] = true; + if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) { + parameters_map["export_animation_mode"] = "ACTIONS"; + } else { + parameters_map["export_nla_strips"] = true; + } } else { - parameters_map["export_nla_strips"] = false; + if (blender_major_version > 3 || (blender_major_version == 3 && blender_minor_version >= 6)) { + parameters_map["export_animation_mode"] = "ACTIVE_ACTIONS"; + } else { + parameters_map["export_nla_strips"] = false; + } } if (p_options.has(SNAME("blender/animation/limit_playback")) && p_options[SNAME("blender/animation/limit_playback")]) { parameters_map["export_frame_range"] = true; @@ -208,6 +283,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { base_dir = sink.get_base_dir(); } + state->set_scene_name(blend_basename); err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { if (r_err) { @@ -269,67 +345,8 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li /////////////////////////// static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { - String path = p_path; -#ifdef WINDOWS_ENABLED - path = path.path_join("blender.exe"); -#else - path = path.path_join("blender"); -#endif - -#if defined(MACOS_ENABLED) - if (!FileAccess::exists(path)) { - path = path.path_join("Blender"); - } -#endif - - if (!FileAccess::exists(path)) { - if (r_err) { - *r_err = TTR("Path does not contain a Blender installation."); - } - return false; - } - List<String> args; - args.push_back("--version"); - String pipe; - Error err = OS::get_singleton()->execute(path, args, &pipe); - if (err != OK) { - if (r_err) { - *r_err = TTR("Can't execute Blender binary."); - } - return false; - } - int bl = pipe.find("Blender "); - if (bl == -1) { - if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s"), path); - } - return false; - } - pipe = pipe.substr(bl); - pipe = pipe.replace_first("Blender ", ""); - int pp = pipe.find("."); - if (pp == -1) { - if (r_err) { - *r_err = TTR("Path supplied lacks a Blender binary."); - } - return false; - } - String v = pipe.substr(0, pp); - int version = v.to_int(); - if (version < 3) { - if (r_err) { - *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); - } - return false; - } - if (version > 3) { - if (r_err) { - *r_err = TTR("This Blender installation is too new for this importer (not 3.x)."); - } - return false; - } - - return true; + int major, minor; + return _get_blender_version(p_path, major, minor, r_err); } bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index c77a23f9f6..ec467db457 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -43,6 +43,9 @@ class ConfirmationDialog; class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterBlend, EditorSceneFormatImporter); + int blender_major_version = -1; + int blender_minor_version = -1; + public: enum { BLEND_VISIBLE_ALL, diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index b63a938e64..e35c0e9b9b 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -51,6 +51,10 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t gltf.instantiate(); Ref<GLTFState> state; state.instantiate(); + if (p_options.has("gltf/naming_version")) { + int naming_version = p_options["gltf/naming_version"]; + gltf->set_naming_version(naming_version); + } if (p_options.has("gltf/embedded_image_handling")) { int32_t enum_option = p_options["gltf/embedded_image_handling"]; state->set_handle_binary_image(enum_option); @@ -77,7 +81,16 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); +} + +void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { + if (!p_import_params.has("gltf/naming_version")) { + // If an existing import file is missing the glTF + // compatibility version, we need to use version 0. + p_import_params["gltf/naming_version"] = 0; + } } #endif // TOOLS_ENABLED diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index ed57ec8cdb..7726c845bf 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -49,6 +49,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; + virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; }; #endif // TOOLS_ENABLED diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 11718ba78a..582bcf466b 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -46,6 +46,10 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_export_preflight, "state", "root"); GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); GDVIRTUAL_BIND(_export_preserialize, "state"); + GDVIRTUAL_BIND(_get_saveable_image_formats); + GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality"); + GDVIRTUAL_BIND(_save_image_at_path, "state", "image", "file_path", "image_format", "lossy_quality"); + GDVIRTUAL_BIND(_serialize_texture_json, "state", "texture_json", "gltf_texture", "image_format"); GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_export_post, "state"); } @@ -97,7 +101,6 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { ERR_FAIL_NULL_V(p_state, nullptr); ERR_FAIL_NULL_V(p_gltf_node, nullptr); - ERR_FAIL_NULL_V(p_scene_parent, nullptr); Node3D *ret_node = nullptr; GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); return ret_node; @@ -149,6 +152,36 @@ Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { return err; } +Vector<String> GLTFDocumentExtension::get_saveable_image_formats() { + Vector<String> ret; + GDVIRTUAL_CALL(_get_saveable_image_formats, ret); + return ret; +} + +PackedByteArray GLTFDocumentExtension::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) { + PackedByteArray ret; + ERR_FAIL_NULL_V(p_state, ret); + ERR_FAIL_NULL_V(p_image, ret); + GDVIRTUAL_CALL(_serialize_image_to_bytes, p_state, p_image, p_image_dict, p_image_format, p_lossy_quality, ret); + return ret; +} + +Error GLTFDocumentExtension::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); + Error ret = OK; + GDVIRTUAL_CALL(_save_image_at_path, p_state, p_image, p_file_path, p_image_format, p_lossy_quality, ret); + return ret; +} + +Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_gltf_texture, ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_serialize_texture_json, p_state, p_texture_json, p_gltf_texture, p_image_format, err); + return err; +} + Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index 0a631bb6c5..512b7aba91 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -47,14 +47,18 @@ public: virtual Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image); virtual String get_image_file_extension(); virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture); - virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_post_parse(Ref<GLTFState> p_state); + virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error import_post(Ref<GLTFState> p_state, Node *p_node); // Export process. virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root); virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); virtual Error export_preserialize(Ref<GLTFState> p_state); + virtual Vector<String> get_saveable_image_formats(); + virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality); + virtual Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality); + virtual Error serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format); virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error export_post(Ref<GLTFState> p_state); @@ -73,6 +77,10 @@ public: GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *); GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>); + GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats); + GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float); + GDVIRTUAL5R(Error, _save_image_at_path, Ref<GLTFState>, Ref<Image>, String, String, float); + GDVIRTUAL4R(Error, _serialize_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>, String); GDVIRTUAL4R(Error, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL1R(Error, _export_post, Ref<GLTFState>); }; diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp index 73c869be3b..f8bd6d57cf 100644 --- a/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp +++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.cpp @@ -68,3 +68,42 @@ Error GLTFDocumentExtensionTextureWebP::parse_texture_json(Ref<GLTFState> p_stat r_gltf_texture->set_src_image(texture_webp["source"]); return OK; } + +Vector<String> GLTFDocumentExtensionTextureWebP::get_saveable_image_formats() { + Vector<String> ret; + ret.push_back("Lossless WebP"); + ret.push_back("Lossy WebP"); + return ret; +} + +PackedByteArray GLTFDocumentExtensionTextureWebP::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) { + if (p_image_format == "Lossless WebP") { + p_image_dict["mimeType"] = "image/webp"; + return p_image->save_webp_to_buffer(false); + } else if (p_image_format == "Lossy WebP") { + p_image_dict["mimeType"] = "image/webp"; + return p_image->save_webp_to_buffer(true, p_lossy_quality); + } + ERR_FAIL_V(PackedByteArray()); +} + +Error GLTFDocumentExtensionTextureWebP::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) { + if (p_image_format == "Lossless WebP") { + p_image->save_webp(p_file_path, false); + return OK; + } else if (p_image_format == "Lossy WebP") { + p_image->save_webp(p_file_path, true, p_lossy_quality); + return OK; + } + return ERR_INVALID_PARAMETER; +} + +Error GLTFDocumentExtensionTextureWebP::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) { + Dictionary ext_texture_webp; + ext_texture_webp["source"] = p_gltf_texture->get_src_image(); + Dictionary texture_extensions; + texture_extensions["EXT_texture_webp"] = ext_texture_webp; + p_texture_json["extensions"] = texture_extensions; + p_state->add_used_extension("EXT_texture_webp", true); + return OK; +} diff --git a/modules/gltf/extensions/gltf_document_extension_texture_webp.h b/modules/gltf/extensions/gltf_document_extension_texture_webp.h index d2654aae8c..2113bd4768 100644 --- a/modules/gltf/extensions/gltf_document_extension_texture_webp.h +++ b/modules/gltf/extensions/gltf_document_extension_texture_webp.h @@ -43,6 +43,11 @@ public: Error parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) override; String get_image_file_extension() override; Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) override; + // Export process. + Vector<String> get_saveable_image_formats() override; + PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) override; + Error save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_full_path, const String &p_image_format, float p_lossy_quality) override; + Error serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) override; }; #endif // GLTF_DOCUMENT_EXTENSION_TEXTURE_WEBP_H diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index c60b522168..435a1260ba 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -111,7 +111,7 @@ void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { Ref<GLTFLight> GLTFLight::from_node(const Light3D *p_light) { Ref<GLTFLight> l; l.instantiate(); - ERR_FAIL_COND_V_MSG(!p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_light, l, "Tried to create a GLTFLight from a Light3D node, but the given node was null."); l->color = p_light->get_color(); if (cast_to<DirectionalLight3D>(p_light)) { l->light_type = "directional"; diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index 49a6edd2e3..b80f4348c2 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -112,7 +112,7 @@ void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) { Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) { Ref<GLTFPhysicsBody> physics_body; physics_body.instantiate(); - ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); if (cast_to<CharacterBody3D>(p_body_node)) { physics_body->body_type = "character"; } else if (cast_to<AnimatableBody3D>(p_body_node)) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 984052d2ca..4060f7f626 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -42,7 +42,6 @@ #include "core/io/stream_peer.h" #include "core/math/disjoint_set.h" #include "core/version.h" -#include "drivers/png/png_driver_common.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" @@ -67,8 +66,10 @@ #endif // MODULE_GRIDMAP_ENABLED // FIXME: Hardcoded to avoid editor dependency. +#define GLTF_IMPORT_GENERATE_TANGENT_ARRAYS 8 #define GLTF_IMPORT_USE_NAMED_SKIN_BINDS 16 #define GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS 32 +#define GLTF_IMPORT_FORCE_DISABLE_MESH_COMPRESSION 64 #include <stdio.h> #include <stdlib.h> @@ -572,9 +573,12 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) { // Determine what to use for the scene name. if (scene_dict.has("name") && !String(scene_dict["name"]).is_empty() && !((String)scene_dict["name"]).begins_with("Scene")) { p_state->scene_name = scene_dict["name"]; - } else { + } else if (p_state->scene_name.is_empty()) { p_state->scene_name = p_state->filename; } + if (_naming_version == 0) { + p_state->scene_name = _gen_unique_name(p_state, p_state->scene_name); + } } return OK; @@ -2209,7 +2213,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { } Array array = import_mesh->get_surface_arrays(surface_i); - uint32_t format = import_mesh->get_surface_format(surface_i); + uint64_t format = import_mesh->get_surface_format(surface_i); int32_t vertex_num = 0; Dictionary attributes; { @@ -2569,7 +2573,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name))); for (int j = 0; j < primitives.size(); j++) { - uint32_t flags = 0; + uint64_t flags = RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES; Dictionary p = primitives[j]; Array array; @@ -2796,7 +2800,28 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { array[Mesh::ARRAY_INDEX] = indices; } - bool generate_tangents = (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("TEXCOORD_0") && a.has("NORMAL")); + bool generate_tangents = p_state->force_generate_tangents && (primitive == Mesh::PRIMITIVE_TRIANGLES && !a.has("TANGENT") && a.has("NORMAL")); + + if (generate_tangents && !a.has("TEXCOORD_0")) { + // If we don't have UVs we provide a dummy tangent array. + Vector<float> tangents; + tangents.resize(vertex_num * 4); + float *tangentsw = tangents.ptrw(); + + Vector<Vector3> normals = array[Mesh::ARRAY_NORMAL]; + for (int k = 0; k < vertex_num; k++) { + Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[k]); + tangentsw[k * 4 + 0] = tan.x; + tangentsw[k * 4 + 1] = tan.y; + tangentsw[k * 4 + 2] = tan.z; + tangentsw[k * 4 + 3] = 1.0; + } + array[Mesh::ARRAY_TANGENT] = tangents; + } + + if (p_state->force_disable_compression || !a.has("POSITION") || !a.has("NORMAL") || p.has("targets") || (a.has("JOINTS_0") || a.has("JOINTS_1"))) { + flags &= ~RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES; + } Ref<SurfaceTool> mesh_surface_tool; mesh_surface_tool.instantiate(); @@ -2805,7 +2830,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { mesh_surface_tool->set_skin_weight_count(SurfaceTool::SKIN_8_WEIGHTS); } mesh_surface_tool->index(); - if (generate_tangents) { + if (generate_tangents && a.has("TEXCOORD_0")) { //must generate mikktspace tangents.. ergh.. mesh_surface_tool->generate_tangents(); } @@ -2936,7 +2961,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { // Enforce blend shape mask array format for (int l = 0; l < Mesh::ARRAY_MAX; l++) { - if (!(Mesh::ARRAY_FORMAT_BLEND_SHAPE_MASK & (1 << l))) { + if (!(Mesh::ARRAY_FORMAT_BLEND_SHAPE_MASK & (1ULL << l))) { array_copy[l] = Variant(); } } @@ -3001,8 +3026,43 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { return OK; } +void GLTFDocument::set_naming_version(int p_version) { + _naming_version = p_version; +} + +int GLTFDocument::get_naming_version() const { + return _naming_version; +} + +void GLTFDocument::set_image_format(const String &p_image_format) { + _image_format = p_image_format; +} + +String GLTFDocument::get_image_format() const { + return _image_format; +} + +void GLTFDocument::set_lossy_quality(float p_lossy_quality) { + _lossy_quality = p_lossy_quality; +} + +float GLTFDocument::get_lossy_quality() const { + return _lossy_quality; +} + Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { Array images; + // Check if any extension wants to be the image saver. + _image_save_extension = Ref<GLTFDocumentExtension>(); + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + Vector<String> image_formats = ext->get_saveable_image_formats(); + if (image_formats.has(_image_format)) { + _image_save_extension = ext; + break; + } + } + // Serialize every image in the state's images array. for (int i = 0; i < p_state->images.size(); i++) { Dictionary image_dict; @@ -3010,6 +3070,10 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { Ref<Image> image = p_state->images[i]->get_image(); ERR_CONTINUE(image.is_null()); + if (image->is_compressed()) { + image->decompress(); + ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed."); + } if (p_state->filename.to_lower().ends_with("gltf")) { String img_name = p_state->images[i]->get_name(); @@ -3017,14 +3081,28 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { img_name = itos(i); } img_name = _gen_unique_name(p_state, img_name); - img_name = img_name.pad_zeros(3) + ".png"; + img_name = img_name.pad_zeros(3); String relative_texture_dir = "textures"; String full_texture_dir = p_state->base_path.path_join(relative_texture_dir); Ref<DirAccess> da = DirAccess::open(p_state->base_path); + ERR_FAIL_COND_V(da.is_null(), FAILED); + if (!da->dir_exists(full_texture_dir)) { da->make_dir(full_texture_dir); } - image->save_png(full_texture_dir.path_join(img_name)); + if (_image_save_extension.is_valid()) { + img_name = img_name + _image_save_extension->get_image_file_extension(); + Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality); + ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file."); + } else if (_image_format == "PNG") { + img_name = img_name + ".png"; + image->save_png(full_texture_dir.path_join(img_name)); + } else if (_image_format == "JPEG") { + img_name = img_name + ".jpg"; + image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality); + } else { + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + } image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode(); } else { GLTFBufferViewIndex bvi; @@ -3042,8 +3120,20 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { if (img_tex.is_valid()) { image = img_tex->get_image(); } - Error err = PNGDriverCommon::image_to_png(image, buffer); - ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG."); + // Save in various image formats. Note that if the format is "None", + // the state's images will be empty, so this code will not be reached. + if (_image_save_extension.is_valid()) { + buffer = _image_save_extension->serialize_image_to_bytes(p_state, image, image_dict, _image_format, _lossy_quality); + } else if (_image_format == "PNG") { + buffer = image->save_png_to_buffer(); + image_dict["mimeType"] = "image/png"; + } else if (_image_format == "JPEG") { + buffer = image->save_jpg_to_buffer(_lossy_quality); + image_dict["mimeType"] = "image/jpeg"; + } else { + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + } + ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format."); bv->byte_length = buffer.size(); p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length); @@ -3053,7 +3143,6 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { p_state->buffer_views.push_back(bv); bvi = p_state->buffer_views.size() - 1; image_dict["bufferView"] = bvi; - image_dict["mimeType"] = "image/png"; } images.push_back(image_dict); } @@ -3332,9 +3421,13 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) { for (int32_t i = 0; i < p_state->textures.size(); i++) { Dictionary texture_dict; Ref<GLTFTexture> gltf_texture = p_state->textures[i]; - ERR_CONTINUE(gltf_texture->get_src_image() == -1); - texture_dict["source"] = gltf_texture->get_src_image(); - + if (_image_save_extension.is_valid()) { + Error err = _image_save_extension->serialize_texture_json(p_state, texture_dict, gltf_texture, _image_format); + ERR_FAIL_COND_V(err != OK, err); + } else { + ERR_CONTINUE(gltf_texture->get_src_image() == -1); + texture_dict["source"] = gltf_texture->get_src_image(); + } GLTFTextureSamplerIndex sampler_index = gltf_texture->get_sampler(); if (sampler_index != -1) { texture_dict["sampler"] = sampler_index; @@ -3543,7 +3636,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { arr.push_back(c.a); mr["baseColorFactor"] = arr; } - { + if (_image_format != "None") { Dictionary bct; Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); GLTFTextureIndex gltf_texture_index = -1; @@ -3820,7 +3913,6 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } else { material->set_name(vformat("material_%s", itos(i))); } - material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); Dictionary material_extensions; if (material_dict.has("extensions")) { material_extensions = material_dict["extensions"]; @@ -4276,7 +4368,7 @@ Error GLTFDocument::_expand_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) { } Error GLTFDocument::_verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) { - // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) + // This may seem duplicated from expand_skins, but this is really a safety check! (so it kinda is) // In case additional interpolating logic is added to the skins, this will help ensure that you // do not cause it to self implode into a fiery blaze @@ -5277,12 +5369,22 @@ void GLTFDocument::_assign_node_names(Ref<GLTFState> p_state) { } String gltf_node_name = gltf_node->get_name(); if (gltf_node_name.is_empty()) { - if (gltf_node->mesh >= 0) { - gltf_node_name = "Mesh"; - } else if (gltf_node->camera >= 0) { - gltf_node_name = "Camera3D"; + if (_naming_version == 0) { + if (gltf_node->mesh >= 0) { + gltf_node_name = _gen_unique_name(p_state, "Mesh"); + } else if (gltf_node->camera >= 0) { + gltf_node_name = _gen_unique_name(p_state, "Camera3D"); + } else { + gltf_node_name = _gen_unique_name(p_state, "Node"); + } } else { - gltf_node_name = "Node"; + if (gltf_node->mesh >= 0) { + gltf_node_name = "Mesh"; + } else if (gltf_node->camera >= 0) { + gltf_node_name = "Camera"; + } else { + gltf_node_name = "Node"; + } } } gltf_node->set_name(_gen_unique_name(p_state, gltf_node_name)); @@ -5536,7 +5638,7 @@ void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_paren } void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_COND(!p_animation_player); + ERR_FAIL_NULL(p_animation_player); p_state->animation_players.push_back(p_animation_player); print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name()); } @@ -5555,7 +5657,7 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) { } void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { - ERR_FAIL_COND(!camera); + ERR_FAIL_NULL(camera); GLTFCameraIndex camera_index = _convert_camera(p_state, camera); if (camera_index != -1) { p_gltf_node->camera = camera_index; @@ -5563,7 +5665,7 @@ void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_st } void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { - ERR_FAIL_COND(!light); + ERR_FAIL_NULL(light); GLTFLightIndex light_index = _convert_light(p_state, light); if (light_index != -1) { p_gltf_node->light = light_index; @@ -5605,7 +5707,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf( GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) { - ERR_FAIL_COND(!p_multi_mesh_instance); + ERR_FAIL_NULL(p_multi_mesh_instance); Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh(); if (multi_mesh.is_null()) { return; @@ -5782,6 +5884,10 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent); p_scene_parent->add_child(bone_attachment, true); + + // Find the correct bone_idx so we can properly serialize it. + bone_attachment->set_bone_idx(active_skeleton->find_bone(gltf_node->get_name())); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name @@ -5821,15 +5927,22 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn current_node = _generate_spatial(p_state, p_node_index); } } - // Add the node we generated and set the owner to the scene root. - p_scene_parent->add_child(current_node, true); - if (current_node != p_scene_root) { + String gltf_node_name = gltf_node->get_name(); + if (!gltf_node_name.is_empty()) { + current_node->set_name(gltf_node_name); + } + // Note: p_scene_parent and p_scene_root must either both be null or both be valid. + if (p_scene_root == nullptr) { + // If the root node argument is null, this is the root node. + p_scene_root = current_node; + } else { + // Add the node we generated and set the owner to the scene root. + p_scene_parent->add_child(current_node, true); Array args; args.append(p_scene_root); current_node->propagate_call(StringName("set_owner"), args); + current_node->set_transform(gltf_node->xform); } - current_node->set_transform(gltf_node->xform); - current_node->set_name(gltf_node->get_name()); p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { @@ -5878,6 +5991,10 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, p_node_index); p_scene_parent->add_child(bone_attachment, true); + + // Find the correct bone_idx so we can properly serialize it. + bone_attachment->set_bone_idx(active_skeleton->find_bone(gltf_node->get_name())); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name @@ -6080,7 +6197,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key]; Node *root = p_animation_player->get_parent(); - ERR_FAIL_COND(root == nullptr); + ERR_FAIL_NULL(root); HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index); ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index)); node_path = root->get_path_to(node_element->value); @@ -6093,7 +6210,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ if (gltf_node->skeleton >= 0) { const Skeleton3D *sk = p_state->skeletons[gltf_node->skeleton]->godot_skeleton; - ERR_FAIL_COND(sk == nullptr); + ERR_FAIL_NULL(sk); const String path = p_animation_player->get_parent()->get_path_to(sk); const String bone = gltf_node->get_name(); @@ -6153,7 +6270,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ if (p_remove_immutable_tracks) { Vector3 base_pos = p_state->nodes[track_i.key]->position; for (int i = 0; i < track.position_track.times.size(); i++) { - Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; + int value_index = track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i; + ERR_FAIL_COND_MSG(value_index >= track.position_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements."); + Vector3 value = track.position_track.values[value_index]; if (!value.is_equal_approx(base_pos)) { is_default = false; break; @@ -6173,7 +6292,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ if (p_remove_immutable_tracks) { Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized(); for (int i = 0; i < track.rotation_track.times.size(); i++) { - Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized(); + int value_index = track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i; + ERR_FAIL_COND_MSG(value_index >= track.rotation_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements."); + Quaternion value = track.rotation_track.values[value_index].normalized(); if (!value.is_equal_approx(base_rot)) { is_default = false; break; @@ -6193,7 +6314,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ if (p_remove_immutable_tracks) { Vector3 base_scale = p_state->nodes[track_i.key]->scale; for (int i = 0; i < track.scale_track.times.size(); i++) { - Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; + int value_index = track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i; + ERR_FAIL_COND_MSG(value_index >= track.scale_track.values.size(), "Animation sampler output accessor with 'CUBICSPLINE' interpolation doesn't have enough elements."); + Vector3 value = track.scale_track.values[value_index]; if (!value.is_equal_approx(base_scale)) { is_default = false; break; @@ -6443,7 +6566,7 @@ float GLTFDocument::get_max_component(const Color &p_color) { return MAX(MAX(r, g), b); } -void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) { +void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state) { for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) { Ref<GLTFNode> node = p_state->nodes[node_i]; @@ -7158,6 +7281,21 @@ void GLTFDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"), &GLTFDocument::write_to_filesystem); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_SINGLE_ROOT); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_KEEP_ROOT); + BIND_ENUM_CONSTANT(ROOT_NODE_MODE_MULTI_ROOT); + + ClassDB::bind_method(D_METHOD("set_image_format", "image_format"), &GLTFDocument::set_image_format); + ClassDB::bind_method(D_METHOD("get_image_format"), &GLTFDocument::get_image_format); + ClassDB::bind_method(D_METHOD("set_lossy_quality", "lossy_quality"), &GLTFDocument::set_lossy_quality); + ClassDB::bind_method(D_METHOD("get_lossy_quality"), &GLTFDocument::get_lossy_quality); + ClassDB::bind_method(D_METHOD("set_root_node_mode", "root_node_mode"), &GLTFDocument::set_root_node_mode); + ClassDB::bind_method(D_METHOD("get_root_node_mode"), &GLTFDocument::get_root_node_mode); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_format"), "set_image_format", "get_image_format"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lossy_quality"), "set_lossy_quality", "get_lossy_quality"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "root_node_mode"), "set_root_node_mode", "get_root_node_mode"); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"), &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"), @@ -7266,15 +7404,44 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_ return OK; } +Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { + // Generate the skeletons and skins (if any). + Error err = _create_skeletons(p_state); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons."); + err = _create_skins(p_state); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skins."); + // Generate the node tree. + Node *single_root; + if (p_state->extensions_used.has("GODOT_single_root")) { + _generate_scene_node(p_state, 0, nullptr, nullptr); + single_root = p_state->scene_nodes[0]; + } else { + single_root = memnew(Node3D); + for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { + _generate_scene_node(p_state, p_state->root_nodes[root_i], single_root, single_root); + } + } + // Assign the scene name and single root name to each other + // if one is missing, or do nothing if both are already set. + if (unlikely(p_state->scene_name.is_empty())) { + p_state->scene_name = single_root->get_name(); + } else if (single_root->get_name() == StringName()) { + if (_naming_version == 0) { + single_root->set_name(p_state->scene_name); + } else { + single_root->set_name(_gen_unique_name(p_state, p_state->scene_name)); + } + } + return single_root; +} + Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { ERR_FAIL_NULL_V(p_state, nullptr); ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr); Error err = OK; - GLTFNodeIndex gltf_root = p_state->root_nodes.write[0]; - Node *gltf_root_node = p_state->get_scene_node(gltf_root); - Node *root = gltf_root_node->get_parent(); + Node *root = _generate_scene_node_tree(p_state); ERR_FAIL_NULL_V(root, nullptr); - _process_mesh_instances(p_state, root); + _process_mesh_instances(p_state); if (p_state->get_create_animations() && p_state->animations.size()) { AnimationPlayer *ap = memnew(AnimationPlayer); root->add_child(ap, true); @@ -7310,6 +7477,8 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint ERR_FAIL_COND_V(p_state.is_null(), FAILED); p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS; + p_state->force_disable_compression = p_flags & GLTF_IMPORT_FORCE_DISABLE_MESH_COMPRESSION; if (!p_state->buffers.size()) { p_state->buffers.push_back(Vector<uint8_t>()); } @@ -7324,6 +7493,19 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint } } // Add the root node(s) and their descendants to the state. + if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_MULTI_ROOT) { + const int child_count = p_node->get_child_count(); + if (child_count > 0) { + for (int i = 0; i < child_count; i++) { + _convert_scene_node(p_state, p_node->get_child(i), -1, -1); + } + p_state->scene_name = p_node->get_name(); + return OK; + } + } + if (_root_node_mode == RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT) { + p_state->extensions_used.append("GODOT_single_root"); + } _convert_scene_node(p_state, p_node, -1, -1); return OK; } @@ -7334,6 +7516,8 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa Error err = FAILED; p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS; + p_state->force_disable_compression = p_flags & GLTF_IMPORT_FORCE_DISABLE_MESH_COMPRESSION; Ref<FileAccessMemory> file_access; file_access.instantiate(); @@ -7427,14 +7611,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se err = _determine_skeletons(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - /* CREATE SKELETONS */ - err = _create_skeletons(p_state); - ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - - /* CREATE SKINS */ - err = _create_skins(p_state); - ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - /* PARSE MESHES (we have enough info now) */ err = _parse_meshes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); @@ -7454,11 +7630,6 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se /* ASSIGN SCENE NAMES */ _assign_node_names(p_state); - Node3D *root = memnew(Node3D); - for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { - _generate_scene_node(p_state, p_state->root_nodes[root_i], root, root); - } - return OK; } @@ -7470,6 +7641,9 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint p_state->filename = p_path.get_file().get_basename(); p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->force_generate_tangents = p_flags & GLTF_IMPORT_GENERATE_TANGENT_ARRAYS; + p_state->force_disable_compression = p_flags & GLTF_IMPORT_FORCE_DISABLE_MESH_COMPRESSION; + Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); @@ -7521,3 +7695,11 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { } return ret; } + +void GLTFDocument::set_root_node_mode(GLTFDocument::RootNodeMode p_root_node_mode) { + _root_node_mode = p_root_node_mode; +} + +GLTFDocument::RootNodeMode GLTFDocument::get_root_node_mode() const { + return _root_node_mode; +} diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index f2e36a0457..7e378fe94d 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -40,9 +40,6 @@ class GLTFDocument : public Resource { static Vector<Ref<GLTFDocumentExtension>> all_document_extensions; Vector<Ref<GLTFDocumentExtension>> document_extensions; -private: - const float BAKE_FPS = 30.0f; - public: const int32_t JOINT_GROUP_SIZE = 4; @@ -68,6 +65,19 @@ public: TEXTURE_TYPE_GENERIC = 0, TEXTURE_TYPE_NORMAL = 1, }; + enum RootNodeMode { + ROOT_NODE_MODE_SINGLE_ROOT, + ROOT_NODE_MODE_KEEP_ROOT, + ROOT_NODE_MODE_MULTI_ROOT, + }; + +private: + const float BAKE_FPS = 30.0f; + int _naming_version = 1; + String _image_format = "PNG"; + float _lossy_quality = 0.75f; + Ref<GLTFDocumentExtension> _image_save_extension; + RootNodeMode _root_node_mode = RootNodeMode::ROOT_NODE_MODE_SINGLE_ROOT; protected: static void _bind_methods(); @@ -77,6 +87,15 @@ public: static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension); static void unregister_all_gltf_document_extensions(); + void set_naming_version(int p_version); + int get_naming_version() const; + void set_image_format(const String &p_image_format); + String get_image_format() const; + void set_lossy_quality(float p_lossy_quality); + float get_lossy_quality() const; + void set_root_node_mode(RootNodeMode p_root_node_mode); + RootNodeMode get_root_node_mode() const; + private: void _build_parent_hierachy(Ref<GLTFState> p_state); double _filter_number(double p_float); @@ -306,7 +325,8 @@ public: Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path); Error _parse_asset_header(Ref<GLTFState> p_state); Error _parse_gltf_extensions(Ref<GLTFState> p_state); - void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root); + void _process_mesh_instances(Ref<GLTFState> p_state); + Node *_generate_scene_node_tree(Ref<GLTFState> p_state); void _generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _generate_skeleton_bone_node(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root); void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, @@ -370,4 +390,6 @@ public: Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file); }; +VARIANT_ENUM_CAST(GLTFDocument::RootNodeMode); + #endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index c0ec004fd6..766fe41257 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -124,7 +124,7 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector<Ref<GLTFSkeleton>> ADD_PROPERTY(PropertyInfo(Variant::BOOL, "create_animations"), "set_create_animations", "get_create_animations"); // bool ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector<Ref<GLTFAnimation>> - ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum + ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum BIND_CONSTANT(HANDLE_BINARY_DISCARD_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EXTRACT_TEXTURES); diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index 91af8f91a4..1ed8ce3629 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -58,7 +58,9 @@ class GLTFState : public Resource { bool use_named_skin_binds = false; bool use_khr_texture_transform = false; bool discard_meshes_and_materials = false; + bool force_generate_tangents = false; bool create_animations = true; + bool force_disable_compression = false; int handle_binary_image = HANDLE_BINARY_EXTRACT_TEXTURES; diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index 630b34c270..d56f67a092 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -60,7 +60,7 @@ void GLTFCamera::_bind_methods() { Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { Ref<GLTFCamera> c; c.instantiate(); - ERR_FAIL_COND_V_MSG(!p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); + ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. c->set_fov(Math::deg_to_rad(p_camera->get_fov())); diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index f96cc86142..f7c01ff840 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -749,6 +749,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D for (int i = 0; i < options->get_popup()->get_item_count(); ++i) { const Ref<Shortcut> &shortcut = options->get_popup()->get_item_shortcut(i); if (shortcut.is_valid() && shortcut->matches_event(p_event)) { + accept_event(); _menu_option(options->get_popup()->get_item_id(i)); return EditorPlugin::AFTER_GUI_INPUT_STOP; } @@ -1290,14 +1291,14 @@ GridMapEditor::GridMapEditor() { search_box->connect("gui_input", callable_mp(this, &GridMapEditor::_sbox_input)); mode_thumbnail = memnew(Button); - mode_thumbnail->set_flat(true); + mode_thumbnail->set_theme_type_variation("FlatButton"); mode_thumbnail->set_toggle_mode(true); mode_thumbnail->set_pressed(true); hb->add_child(mode_thumbnail); mode_thumbnail->connect("pressed", callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_THUMBNAIL)); mode_list = memnew(Button); - mode_list->set_flat(true); + mode_list->set_theme_type_variation("FlatButton"); mode_list->set_toggle_mode(true); mode_list->set_pressed(false); hb->add_child(mode_list); @@ -1411,6 +1412,7 @@ GridMapEditor::GridMapEditor() { inner_mat.instantiate(); inner_mat->set_albedo(Color(0.7, 0.7, 1.0, 0.2)); inner_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + inner_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); inner_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); d[RS::ARRAY_VERTEX] = triangles; @@ -1423,11 +1425,13 @@ GridMapEditor::GridMapEditor() { outer_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); outer_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + outer_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); selection_floor_mat.instantiate(); selection_floor_mat->set_albedo(Color(0.80, 0.80, 1.0, 1)); selection_floor_mat->set_on_top_of_alpha(); selection_floor_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_floor_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); d[RS::ARRAY_VERTEX] = lines; RenderingServer::get_singleton()->mesh_add_surface_from_arrays(selection_mesh, RS::PRIMITIVE_LINES, d); @@ -1456,6 +1460,7 @@ GridMapEditor::GridMapEditor() { indicator_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); indicator_mat->set_albedo(Color(0.8, 0.5, 0.1)); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 4ac143c7ff..6f493f48e3 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -267,6 +267,7 @@ void GridMap::set_mesh_library(const Ref<MeshLibrary> &p_mesh_library) { } _recreate_octant_data(); + emit_signal(CoreStringNames::get_singleton()->changed); } Ref<MeshLibrary> GridMap::get_mesh_library() const { diff --git a/modules/lightmapper_rd/config.py b/modules/lightmapper_rd/config.py index d22f9454ed..ecc61c2d7e 100644 --- a/modules/lightmapper_rd/config.py +++ b/modules/lightmapper_rd/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return True + return env.editor_build and platform not in ["android", "ios"] def configure(env): diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 748e8ae50c..fe919953c1 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -35,7 +35,10 @@ #include "lm_raster.glsl.gen.h" #include "core/config/project_settings.h" +#include "core/io/dir_access.h" #include "core/math/geometry_2d.h" +#include "editor/editor_paths.h" +#include "editor/editor_settings.h" #include "servers/rendering/rendering_device_binds.h" //uncomment this if you want to see textures from all the process saved @@ -52,7 +55,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) { mesh_instances.push_back(mi); } -void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) { +void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_DIRECTIONAL; l.direction[0] = p_direction.x; @@ -62,13 +65,14 @@ void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direct l.color[1] = p_color.g; l.color[2] = p_color.b; l.energy = p_energy; + l.indirect_energy = p_indirect_energy; l.static_bake = p_static; l.size = Math::tan(Math::deg_to_rad(p_angular_distance)); l.shadow_blur = p_shadow_blur; lights.push_back(l); } -void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) { +void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_OMNI; l.position[0] = p_position.x; @@ -80,13 +84,14 @@ void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, con l.color[1] = p_color.g; l.color[2] = p_color.b; l.energy = p_energy; + l.indirect_energy = p_indirect_energy; l.static_bake = p_static; l.size = p_size; l.shadow_blur = p_shadow_blur; lights.push_back(l); } -void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) { +void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_SPOT; l.position[0] = p_position.x; @@ -103,6 +108,7 @@ void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, con l.color[1] = p_color.g; l.color[2] = p_color.b; l.energy = p_energy; + l.indirect_energy = p_indirect_energy; l.static_bake = p_static; l.size = p_size; l.shadow_blur = p_shadow_blur; @@ -118,7 +124,7 @@ void LightmapperRD::add_probe(const Vector3 &p_position) { probe_positions.push_back(probe); } -void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size) { +void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &p_triangles_sort, uint32_t p_grid_size) { int half_size = p_size / 2; for (int i = 0; i < 8; i++) { @@ -153,13 +159,69 @@ void LightmapperRD::_plot_triangle_into_triangle_index_list(int p_size, const Ve TriangleSort ts; ts.cell_index = n.x + (n.y * p_grid_size) + (n.z * p_grid_size * p_grid_size); ts.triangle_index = p_triangle_index; - triangles.push_back(ts); + ts.triangle_aabb.position = p_points[0]; + ts.triangle_aabb.size = Vector3(); + ts.triangle_aabb.expand_to(p_points[1]); + ts.triangle_aabb.expand_to(p_points[2]); + p_triangles_sort.push_back(ts); } else { - _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, triangles, p_grid_size); + _plot_triangle_into_triangle_index_list(half_size, n, aabb, p_points, p_triangle_index, p_triangles_sort, p_grid_size); } } } +void LightmapperRD::_sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_cluster_index, uint32_t p_index_start, uint32_t p_count, LocalVector<TriangleSort> &p_triangle_sort, LocalVector<ClusterAABB> &p_cluster_aabb) { + if (p_count == 0) { + return; + } + + // Compute AABB for all triangles in the range. + SortArray<TriangleSort, TriangleSortAxis<0>> triangle_sorter_x; + SortArray<TriangleSort, TriangleSortAxis<1>> triangle_sorter_y; + SortArray<TriangleSort, TriangleSortAxis<2>> triangle_sorter_z; + AABB cluster_aabb = p_triangle_sort[p_index_start].triangle_aabb; + for (uint32_t i = 1; i < p_count; i++) { + cluster_aabb.merge_with(p_triangle_sort[p_index_start + i].triangle_aabb); + } + + if (p_count > p_cluster_size) { + int longest_axis_index = cluster_aabb.get_longest_axis_index(); + switch (longest_axis_index) { + case 0: + triangle_sorter_x.sort(&p_triangle_sort[p_index_start], p_count); + break; + case 1: + triangle_sorter_y.sort(&p_triangle_sort[p_index_start], p_count); + break; + case 2: + triangle_sorter_z.sort(&p_triangle_sort[p_index_start], p_count); + break; + default: + DEV_ASSERT(false && "Invalid axis returned by AABB."); + break; + } + + uint32_t left_cluster_count = next_power_of_2(p_count / 2); + left_cluster_count = MAX(left_cluster_count, p_cluster_size); + left_cluster_count = MIN(left_cluster_count, p_count); + _sort_triangle_clusters(p_cluster_size, p_cluster_index, p_index_start, left_cluster_count, p_triangle_sort, p_cluster_aabb); + + if (left_cluster_count < p_count) { + uint32_t cluster_index_right = p_cluster_index + (left_cluster_count / p_cluster_size); + _sort_triangle_clusters(p_cluster_size, cluster_index_right, p_index_start + left_cluster_count, p_count - left_cluster_count, p_triangle_sort, p_cluster_aabb); + } + } else { + ClusterAABB &aabb = p_cluster_aabb[p_cluster_index]; + Vector3 aabb_end = cluster_aabb.get_end(); + aabb.min_bounds[0] = cluster_aabb.position.x; + aabb.min_bounds[1] = cluster_aabb.position.y; + aabb.min_bounds[2] = cluster_aabb.position.z; + aabb.max_bounds[0] = aabb_end.x; + aabb.max_bounds[1] = aabb_end.y; + aabb.max_bounds[2] = aabb_end.z; + } +} + Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) { Vector<Size2i> sizes; @@ -275,7 +337,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ return BAKE_OK; } -void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { +void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &p_probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) { HashMap<Vertex, uint32_t, VertexHash> vertex_map; //fill triangles array and vertex array @@ -427,31 +489,70 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i //sort it triangle_sort.sort(); + LocalVector<uint32_t> cluster_indices; + LocalVector<ClusterAABB> cluster_aabbs; Vector<uint32_t> triangle_indices; triangle_indices.resize(triangle_sort.size()); Vector<uint32_t> grid_indices; grid_indices.resize(grid_size * grid_size * grid_size * 2); memset(grid_indices.ptrw(), 0, grid_indices.size() * sizeof(uint32_t)); - Vector<bool> solid; - solid.resize(grid_size * grid_size * grid_size); - memset(solid.ptrw(), 0, solid.size() * sizeof(bool)); { - uint32_t *tiw = triangle_indices.ptrw(); + // Fill grid with cell indices. uint32_t last_cell = 0xFFFFFFFF; uint32_t *giw = grid_indices.ptrw(); - bool *solidw = solid.ptrw(); + uint32_t cluster_count = 0; + uint32_t solid_cell_count = 0; for (uint32_t i = 0; i < triangle_sort.size(); i++) { uint32_t cell = triangle_sort[i].cell_index; if (cell != last_cell) { - //cell changed, update pointer to indices - giw[cell * 2 + 1] = i; - solidw[cell] = true; + giw[cell * 2 + 1] = solid_cell_count; + solid_cell_count++; } - tiw[i] = triangle_sort[i].triangle_index; - giw[cell * 2]++; //update counter + + if ((giw[cell * 2] % p_cluster_size) == 0) { + // Add an extra cluster every time the triangle counter reaches a multiple of the cluster size. + cluster_count++; + } + + giw[cell * 2]++; last_cell = cell; } + + // Build fixed-size triangle clusters for all the cells to speed up the traversal. A cell can hold multiple clusters that each contain a fixed + // amount of triangles and an AABB. The tracer will check against the AABBs first to know whether it needs to visit the cell's triangles. + // + // The building algorithm will divide the triangles recursively contained inside each cell, sorting by the longest axis of the AABB on each step. + // + // - If the amount of triangles is less or equal to the cluster size, the AABB will be stored and the algorithm stops. + // + // - The division by two is increased to the next power of two of half the amount of triangles (with cluster size as the minimum value) to + // ensure the first half always fills the cluster. + + cluster_indices.resize(solid_cell_count * 2); + cluster_aabbs.resize(cluster_count); + + uint32_t i = 0; + uint32_t cluster_index = 0; + uint32_t solid_cell_index = 0; + uint32_t *tiw = triangle_indices.ptrw(); + while (i < triangle_sort.size()) { + cluster_indices[solid_cell_index * 2] = cluster_index; + cluster_indices[solid_cell_index * 2 + 1] = i; + + uint32_t cell = triangle_sort[i].cell_index; + uint32_t triangle_count = giw[cell * 2]; + uint32_t cell_cluster_count = (triangle_count + p_cluster_size - 1) / p_cluster_size; + _sort_triangle_clusters(p_cluster_size, cluster_index, i, triangle_count, triangle_sort, cluster_aabbs); + + for (uint32_t j = 0; j < triangle_count; j++) { + tiw[i + j] = triangle_sort[i + j].triangle_index; + } + + i += triangle_count; + cluster_index += cell_cluster_count; + solid_cell_index++; + } } #if 0 for (int i = 0; i < grid_size; i++) { @@ -501,7 +602,13 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i triangle_buffer = rd->storage_buffer_create(tb.size(), tb); Vector<uint8_t> tib = triangle_indices.to_byte_array(); - triangle_cell_indices_buffer = rd->storage_buffer_create(tib.size(), tib); + r_triangle_indices_buffer = rd->storage_buffer_create(tib.size(), tib); + + Vector<uint8_t> cib = cluster_indices.to_byte_array(); + r_cluster_indices_buffer = rd->storage_buffer_create(cib.size(), cib); + + Vector<uint8_t> cab = cluster_aabbs.to_byte_array(); + r_cluster_aabbs_buffer = rd->storage_buffer_create(cab.size(), cab); Vector<uint8_t> lb = lights.to_byte_array(); if (lb.size() == 0) { @@ -589,8 +696,12 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int raster_push_constant.grid_size[0] = grid_size; raster_push_constant.grid_size[1] = grid_size; raster_push_constant.grid_size[2] = grid_size; - raster_push_constant.uv_offset[0] = 0; - raster_push_constant.uv_offset[1] = 0; + + // Half pixel offset is required so the rasterizer doesn't output face edges directly aligned into pixels. + // This fixes artifacts where the pixel would be traced from the edge of a face, causing half the rays to + // be outside of the boundaries of the geometry. See <https://github.com/godotengine/godot/issues/69126>. + raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x); + raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y); RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); //draw opaque @@ -610,25 +721,29 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int } } -LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { +static Vector<RD::Uniform> dilate_or_denoise_common_uniforms(RID &p_source_light_tex, RID &p_dest_light_tex) { Vector<RD::Uniform> uniforms; { - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 0; - u.append_id(dest_light_tex); - uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 1; - u.append_id(source_light_tex); - uniforms.push_back(u); - } + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 0; + u.append_id(p_dest_light_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 1; + u.append_id(p_source_light_tex); + uniforms.push_back(u); } + return uniforms; +} + +LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex); + RID compute_shader_dilate = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("dilate")); ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen RID compute_shader_dilate_pipeline = rd->compute_pipeline_create(compute_shader_dilate); @@ -663,7 +778,219 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade return BAKE_OK; } -LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { +Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) { + Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index); + Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data); + img->convert(Image::FORMAT_RGBF); + Vector<uint8_t> data_float = img->get_data(); + + Error err = OK; + Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save PFN at path: '%s'.", p_name)); + file->store_line("PF"); + file->store_line(vformat("%d %d", img->get_width(), img->get_height())); +#ifdef BIG_ENDIAN_ENABLED + file->store_line("1.0"); +#else + file->store_line("-1.0"); +#endif + file->store_buffer(data_float); + file->close(); + + return OK; +} + +Ref<Image> LightmapperRD::_read_pfm(const String &p_name) { + Error err = OK; + Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name)); + ERR_FAIL_COND_V(file->get_line() != "PF", Ref<Image>()); + + Vector<String> new_size = file->get_line().split(" "); + ERR_FAIL_COND_V(new_size.size() != 2, Ref<Image>()); + int new_width = new_size[0].to_int(); + int new_height = new_size[1].to_int(); + + float endian = file->get_line().to_float(); + Vector<uint8_t> new_data = file->get_buffer(file->get_length() - file->get_position()); + file->close(); + +#ifdef BIG_ENDIAN_ENABLED + if (unlikely(endian < 0.0)) { + uint32_t count = new_data.size() / 4; + uint16_t *dst = (uint16_t *)new_data.ptrw(); + for (uint32_t j = 0; j < count; j++) { + dst[j * 4] = BSWAP32(dst[j * 4]); + } + } +#else + if (unlikely(endian > 0.0)) { + uint32_t count = new_data.size() / 4; + uint16_t *dst = (uint16_t *)new_data.ptrw(); + for (uint32_t j = 0; j < count; j++) { + dst[j * 4] = BSWAP32(dst[j * 4]); + } + } +#endif + Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data); + img->convert(Image::FORMAT_RGBAH); + return img; +} + +LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe) { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + for (int i = 0; i < p_atlas_slices; i++) { + String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i)); + _store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in); + + for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) { + int index = i * (p_bake_sh ? 4 : 1) + j; + String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index)); + String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index)); + + _store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in); + + List<String> args; + args.push_back("--device"); + args.push_back("default"); + + args.push_back("--filter"); + args.push_back("RTLightmap"); + + args.push_back("--hdr"); + args.push_back(fname_light_in); + + args.push_back("--nrm"); + args.push_back(fname_norm_in); + + args.push_back("--output"); + args.push_back(fname_out); + + String str; + int exitcode = 0; + + Error err = OS::get_singleton()->execute(p_exe, args, &str, &exitcode, true); + + da->remove(fname_light_in); + + if (err != OK || exitcode != 0) { + da->remove(fname_out); + print_verbose(str); + ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat(TTR("OIDN denoiser failed, return code: %d"), exitcode)); + } + + Ref<Image> img = _read_pfm(fname_out); + da->remove(fname_out); + + ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + Vector<uint8_t> old_data = p_rd->texture_get_data(p_source_light_tex, index); + Vector<uint8_t> new_data = img->get_data(); + img.unref(); // Avoid copy on write. + + uint32_t count = old_data.size() / 2; + const uint16_t *src = (const uint16_t *)old_data.ptr(); + uint16_t *dst = (uint16_t *)new_data.ptrw(); + for (uint32_t k = 0; k < count; k += 4) { + dst[k + 3] = src[k + 3]; + } + + p_rd->texture_update(p_dest_light_tex, index, new_data); + } + da->remove(fname_norm_in); + } + return BAKE_OK; +} + +LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) { + RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams)); + DenoiseParams denoise_params; + denoise_params.spatial_bandwidth = 5.0f; + denoise_params.light_bandwidth = p_denoiser_strength; + denoise_params.albedo_bandwidth = 1.0f; + denoise_params.normal_bandwidth = 0.1f; + denoise_params.filter_strength = 10.0f; + p_rd->buffer_update(denoise_params_buffer, 0, sizeof(DenoiseParams), &denoise_params); + + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(p_source_light_tex, p_dest_light_tex); + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; + u.binding = 2; + u.append_id(p_source_normal_tex); + uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.binding = 3; + u.append_id(denoise_params_buffer); + uniforms.push_back(u); + } + + RID compute_shader_denoise = p_rd->shader_create_from_spirv(p_compute_shader->get_spirv_stages("denoise")); + ERR_FAIL_COND_V(compute_shader_denoise.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); + + RID compute_shader_denoise_pipeline = p_rd->compute_pipeline_create(compute_shader_denoise); + RID denoise_uniform_set = p_rd->uniform_set_create(uniforms, compute_shader_denoise, 1); + + // We denoise in fixed size regions and synchronize execution to avoid GPU timeouts. + // We use a region with 1/4 the amount of pixels if we're denoising SH lightmaps, as + // all four of them are denoised in the shader in one dispatch. + const int max_region_size = p_bake_sh ? 512 : 1024; + int x_regions = (p_atlas_size.width - 1) / max_region_size + 1; + int y_regions = (p_atlas_size.height - 1) / max_region_size + 1; + for (int s = 0; s < p_atlas_slices; s++) { + p_push_constant.atlas_slice = s; + + for (int i = 0; i < x_regions; i++) { + for (int j = 0; j < y_regions; j++) { + int x = i * max_region_size; + int y = j * max_region_size; + int w = MIN((i + 1) * max_region_size, p_atlas_size.width) - x; + int h = MIN((j + 1) * max_region_size, p_atlas_size.height) - y; + p_push_constant.region_ofs[0] = x; + p_push_constant.region_ofs[1] = y; + + RD::ComputeListID compute_list = p_rd->compute_list_begin(); + p_rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_denoise_pipeline); + p_rd->compute_list_bind_uniform_set(compute_list, p_compute_base_uniform_set, 0); + p_rd->compute_list_bind_uniform_set(compute_list, denoise_uniform_set, 1); + p_rd->compute_list_set_push_constant(compute_list, &p_push_constant, sizeof(PushConstant)); + p_rd->compute_list_dispatch(compute_list, (w - 1) / 8 + 1, (h - 1) / 8 + 1, 1); + p_rd->compute_list_end(); + + p_rd->submit(); + p_rd->sync(); + } + } + } + + p_rd->free(compute_shader_denoise); + p_rd->free(denoise_params_buffer); + + return BAKE_OK; +} + +LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { + int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser"); + String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path"); + + if (p_use_denoiser && denoiser == 1) { + // OIDN (external). + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + if (da->dir_exists(oidn_path)) { + if (OS::get_singleton()->get_name() == "Windows") { + oidn_path = oidn_path.path_join("oidnDenoise.exe"); + } else { + oidn_path = oidn_path.path_join("oidnDenoise"); + } + } + ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings.")); + } + if (p_step_function) { p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true); } @@ -701,19 +1028,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RID light_dest_tex; RID light_accum_tex; RID light_accum_tex2; - RID light_primary_dynamic_tex; RID light_environment_tex; -#define FREE_TEXTURES \ - rd->free(albedo_array_tex); \ - rd->free(emission_array_tex); \ - rd->free(normal_tex); \ - rd->free(position_tex); \ - rd->free(unocclude_tex); \ - rd->free(light_source_tex); \ - rd->free(light_accum_tex2); \ - rd->free(light_accum_tex); \ - rd->free(light_primary_dynamic_tex); \ +#define FREE_TEXTURES \ + rd->free(albedo_array_tex); \ + rd->free(emission_array_tex); \ + rd->free(normal_tex); \ + rd->free(position_tex); \ + rd->free(unocclude_tex); \ + rd->free(light_source_tex); \ + rd->free(light_accum_tex2); \ + rd->free(light_accum_tex); \ rd->free(light_environment_tex); { // create all textures @@ -751,8 +1076,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d light_source_tex = rd->texture_create(tf, RD::TextureView()); rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); - light_primary_dynamic_tex = rd->texture_create(tf, RD::TextureView()); - rd->texture_clear(light_primary_dynamic_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); if (p_bake_sh) { tf.array_layers *= 4; @@ -794,26 +1117,68 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d /* STEP 2: create the acceleration structure for the GPU*/ Vector<int> slice_triangle_count; + RID bake_parameters_buffer; RID vertex_buffer; RID triangle_buffer; RID lights_buffer; - RID triangle_cell_indices_buffer; + RID triangle_indices_buffer; + RID cluster_indices_buffer; + RID cluster_aabbs_buffer; RID grid_texture; RID seams_buffer; RID probe_positions_buffer; Vector<int> slice_seam_count; -#define FREE_BUFFERS \ - rd->free(vertex_buffer); \ - rd->free(triangle_buffer); \ - rd->free(lights_buffer); \ - rd->free(triangle_cell_indices_buffer); \ - rd->free(grid_texture); \ - rd->free(seams_buffer); \ +#define FREE_BUFFERS \ + rd->free(bake_parameters_buffer); \ + rd->free(vertex_buffer); \ + rd->free(triangle_buffer); \ + rd->free(lights_buffer); \ + rd->free(triangle_indices_buffer); \ + rd->free(cluster_indices_buffer); \ + rd->free(cluster_aabbs_buffer); \ + rd->free(grid_texture); \ + rd->free(seams_buffer); \ rd->free(probe_positions_buffer); - _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); + const uint32_t cluster_size = 16; + _create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, cluster_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, lights_buffer, triangle_indices_buffer, cluster_indices_buffer, cluster_aabbs_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata); + + // Create global bake parameters buffer. + BakeParameters bake_parameters; + bake_parameters.world_size[0] = bounds.size.x; + bake_parameters.world_size[1] = bounds.size.y; + bake_parameters.world_size[2] = bounds.size.z; + bake_parameters.bias = p_bias; + bake_parameters.to_cell_offset[0] = bounds.position.x; + bake_parameters.to_cell_offset[1] = bounds.position.y; + bake_parameters.to_cell_offset[2] = bounds.position.z; + bake_parameters.grid_size = grid_size; + bake_parameters.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size); + bake_parameters.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size); + bake_parameters.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size); + bake_parameters.light_count = lights.size(); + bake_parameters.env_transform[0] = p_environment_transform.rows[0][0]; + bake_parameters.env_transform[1] = p_environment_transform.rows[1][0]; + bake_parameters.env_transform[2] = p_environment_transform.rows[2][0]; + bake_parameters.env_transform[3] = 0.0f; + bake_parameters.env_transform[4] = p_environment_transform.rows[0][1]; + bake_parameters.env_transform[5] = p_environment_transform.rows[1][1]; + bake_parameters.env_transform[6] = p_environment_transform.rows[2][1]; + bake_parameters.env_transform[7] = 0.0f; + bake_parameters.env_transform[8] = p_environment_transform.rows[0][2]; + bake_parameters.env_transform[9] = p_environment_transform.rows[1][2]; + bake_parameters.env_transform[10] = p_environment_transform.rows[2][2]; + bake_parameters.env_transform[11] = 0.0f; + bake_parameters.atlas_size[0] = atlas_size.width; + bake_parameters.atlas_size[1] = atlas_size.height; + bake_parameters.exposure_normalization = p_exposure_normalization; + bake_parameters.bounces = p_bounces; + bake_parameters.bounce_indirect_energy = p_bounce_indirect_energy; + + bake_parameters_buffer = rd->uniform_buffer_create(sizeof(BakeParameters)); + rd->buffer_update(bake_parameters_buffer, 0, sizeof(BakeParameters), &bake_parameters); if (p_step_function) { p_step_function(0.47, RTR("Preparing shaders"), p_bake_userdata, true); @@ -851,6 +1216,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d { { RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_UNIFORM_BUFFER; + u.binding = 0; + u.append_id(bake_parameters_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.binding = 1; u.append_id(vertex_buffer); @@ -867,7 +1239,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; u.binding = 3; - u.append_id(triangle_cell_indices_buffer); + u.append_id(triangle_indices_buffer); base_uniforms.push_back(u); } { @@ -919,6 +1291,20 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(sampler); base_uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 11; + u.append_id(cluster_indices_buffer); + base_uniforms.push_back(u); + } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; + u.binding = 12; + u.append_id(cluster_aabbs_buffer); + base_uniforms.push_back(u); + } } RID raster_base_uniform = rd->uniform_set_create(base_uniforms, rasterize_shader, 0); @@ -963,8 +1349,19 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d /* Plot direct light */ Ref<RDShaderFile> compute_shader; + String defines = ""; + defines += "\n#define CLUSTER_SIZE " + uitos(cluster_size) + "\n"; + + if (p_bake_sh) { + defines += "\n#define USE_SH_LIGHTMAPS\n"; + } + + if (p_texture_for_bounces) { + defines += "\n#define USE_LIGHT_TEXTURE_FOR_BOUNCES\n"; + } + compute_shader.instantiate(); - err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : ""); + err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, defines); if (err != OK) { FREE_TEXTURES FREE_BUFFERS @@ -1002,40 +1399,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d rd->free(compute_shader_secondary); \ rd->free(compute_shader_light_probes); - PushConstant push_constant; - { - //set defaults - push_constant.atlas_size[0] = atlas_size.width; - push_constant.atlas_size[1] = atlas_size.height; - push_constant.world_size[0] = bounds.size.x; - push_constant.world_size[1] = bounds.size.y; - push_constant.world_size[2] = bounds.size.z; - push_constant.to_cell_offset[0] = bounds.position.x; - push_constant.to_cell_offset[1] = bounds.position.y; - push_constant.to_cell_offset[2] = bounds.position.z; - push_constant.bias = p_bias; - push_constant.to_cell_size[0] = (1.0 / bounds.size.x) * float(grid_size); - push_constant.to_cell_size[1] = (1.0 / bounds.size.y) * float(grid_size); - push_constant.to_cell_size[2] = (1.0 / bounds.size.z) * float(grid_size); - push_constant.light_count = lights.size(); - push_constant.grid_size = grid_size; - push_constant.atlas_slice = 0; - push_constant.region_ofs[0] = 0; - push_constant.region_ofs[1] = 0; - push_constant.environment_xform[0] = p_environment_transform.rows[0][0]; - push_constant.environment_xform[1] = p_environment_transform.rows[1][0]; - push_constant.environment_xform[2] = p_environment_transform.rows[2][0]; - push_constant.environment_xform[3] = 0; - push_constant.environment_xform[4] = p_environment_transform.rows[0][1]; - push_constant.environment_xform[5] = p_environment_transform.rows[1][1]; - push_constant.environment_xform[6] = p_environment_transform.rows[2][1]; - push_constant.environment_xform[7] = 0; - push_constant.environment_xform[8] = p_environment_transform.rows[0][2]; - push_constant.environment_xform[9] = p_environment_transform.rows[1][2]; - push_constant.environment_xform[10] = p_environment_transform.rows[2][2]; - push_constant.environment_xform[11] = 0; - } - Vector3i group_size((atlas_size.x - 1) / 8 + 1, (atlas_size.y - 1) / 8 + 1, 1); rd->submit(); rd->sync(); @@ -1044,6 +1407,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d p_step_function(0.49, RTR("Un-occluding geometry"), p_bake_userdata, true); } + PushConstant push_constant; + /* UNOCCLUDE */ { Vector<RD::Uniform> uniforms; @@ -1084,6 +1449,24 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d p_step_function(0.5, RTR("Plot direct lighting"), p_bake_userdata, true); } + // Set ray count to the quality used for direct light and bounces. + switch (p_quality) { + case BAKE_QUALITY_LOW: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count"); + } break; + } + + push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u); + /* PRIMARY (direct) LIGHT PASS */ { Vector<RD::Uniform> uniforms; @@ -1123,41 +1506,15 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(light_accum_tex); uniforms.push_back(u); } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 5; - u.append_id(light_primary_dynamic_tex); - uniforms.push_back(u); - } } RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1); - switch (p_quality) { - case BAKE_QUALITY_LOW: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count"); - } break; - case BAKE_QUALITY_MEDIUM: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count"); - } break; - case BAKE_QUALITY_HIGH: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count"); - } break; - case BAKE_QUALITY_ULTRA: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count"); - } break; - } - - push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u); - RD::ComputeListID compute_list = rd->compute_list_begin(); rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline); rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); rd->compute_list_bind_uniform_set(compute_list, light_uniform_set, 1); - push_constant.environment_xform[11] = p_exposure_normalization; - for (int i = 0; i < atlas_slices; i++) { push_constant.atlas_slice = i; rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); @@ -1165,8 +1522,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d //no barrier, let them run all together } rd->compute_list_end(); //done - - push_constant.environment_xform[11] = 0.0; } #ifdef DEBUG_TEXTURES @@ -1187,6 +1542,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Vector<RD::Uniform> uniforms; { { + // Unused. RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_IMAGE; u.binding = 0; @@ -1223,93 +1579,69 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } { RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_IMAGE; - u.binding = 5; - u.append_id(unocclude_tex); //reuse unocclude tex - uniforms.push_back(u); - } - { - RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 6; + u.binding = 5; u.append_id(light_environment_tex); uniforms.push_back(u); } } - RID secondary_uniform_set[2]; - secondary_uniform_set[0] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); - uniforms.write[0].set_id(0, light_source_tex); - uniforms.write[1].set_id(0, light_dest_tex); - secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); + RID secondary_uniform_set; + secondary_uniform_set = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size"))); int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass"); int x_regions = (atlas_size.width - 1) / max_region_size + 1; int y_regions = (atlas_size.height - 1) / max_region_size + 1; + int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1; rd->submit(); rd->sync(); - for (int b = 0; b < p_bounces; b++) { - int count = 0; - if (b > 0) { - SWAP(light_source_tex, light_dest_tex); - SWAP(secondary_uniform_set[0], secondary_uniform_set[1]); - } - - for (int s = 0; s < atlas_slices; s++) { - push_constant.atlas_slice = s; - - for (int i = 0; i < x_regions; i++) { - for (int j = 0; j < y_regions; j++) { - int x = i * max_region_size; - int y = j * max_region_size; - int w = MIN((i + 1) * max_region_size, atlas_size.width) - x; - int h = MIN((j + 1) * max_region_size, atlas_size.height) - y; - - push_constant.region_ofs[0] = x; - push_constant.region_ofs[1] = y; - - group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1); - - for (int k = 0; k < ray_iterations; k++) { - RD::ComputeListID compute_list = rd->compute_list_begin(); - rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline); - rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); - rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set[0], 1); - - push_constant.ray_from = k * max_rays; - push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count)); - rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); - rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); - - rd->compute_list_end(); //done - rd->submit(); - rd->sync(); - - count++; - if (p_step_function) { - int total = (atlas_slices * x_regions * y_regions * ray_iterations); - int percent = count * 100 / total; - float p = float(count) / total * 0.1; - p_step_function(0.6 + p, vformat(RTR("Bounce %d/%d: Integrate indirect lighting %d%%"), b + 1, p_bounces, percent), p_bake_userdata, false); - } + int count = 0; + for (int s = 0; s < atlas_slices; s++) { + push_constant.atlas_slice = s; + + for (int i = 0; i < x_regions; i++) { + for (int j = 0; j < y_regions; j++) { + int x = i * max_region_size; + int y = j * max_region_size; + int w = MIN((i + 1) * max_region_size, atlas_size.width) - x; + int h = MIN((j + 1) * max_region_size, atlas_size.height) - y; + + push_constant.region_ofs[0] = x; + push_constant.region_ofs[1] = y; + + group_size = Vector3i((w - 1) / 8 + 1, (h - 1) / 8 + 1, 1); + + for (int k = 0; k < ray_iterations; k++) { + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_secondary_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, secondary_uniform_set, 1); + + push_constant.ray_from = k * max_rays; + push_constant.ray_to = MIN((k + 1) * max_rays, int32_t(push_constant.ray_count)); + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + + rd->compute_list_end(); + rd->submit(); + rd->sync(); + + count++; + if (p_step_function) { + int total = (atlas_slices * x_regions * y_regions * ray_iterations); + int percent = count * 100 / total; + float p = float(count) / total * 0.1; + p_step_function(0.6 + p, vformat(RTR("Integrate indirect lighting %d%%"), percent), p_bake_userdata, false); } } } } - - if (b == 0) { - // This disables the environment for subsequent bounces - push_constant.environment_xform[3] = -99.0f; - } } - - // Restore the correct environment transform - push_constant.environment_xform[3] = 0.0f; } /* LIGHTPROBES */ @@ -1336,20 +1668,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; u.binding = 1; - u.append_id(light_dest_tex); + u.append_id(light_source_tex); uniforms.push_back(u); } { RD::Uniform u; u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; u.binding = 2; - u.append_id(light_primary_dynamic_tex); - uniforms.push_back(u); - } - { - RD::Uniform u; - u.uniform_type = RD::UNIFORM_TYPE_TEXTURE; - u.binding = 3; u.append_id(light_environment_tex); uniforms.push_back(u); } @@ -1371,8 +1696,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } break; } - push_constant.atlas_size[0] = probe_positions.size(); push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u); + push_constant.probe_count = probe_positions.size(); int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_probe_pass"); int ray_iterations = (push_constant.ray_count - 1) / max_rays + 1; @@ -1398,8 +1723,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d p_step_function(0.7 + p, vformat(RTR("Integrating light probes %d%%"), percent), p_bake_userdata, false); } } - - push_constant.atlas_size[0] = atlas_size.x; //restore } #if 0 @@ -1415,14 +1738,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } #endif - { - SWAP(light_accum_tex, light_accum_tex2); - BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); - if (unlikely(error != BAKE_OK)) { - return error; - } - } - /* DENOISE */ if (p_use_denoiser) { @@ -1430,39 +1745,30 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d p_step_function(0.8, RTR("Denoising"), p_bake_userdata, true); } - Ref<LightmapDenoiser> denoiser = LightmapDenoiser::create(); - if (denoiser.is_valid()) { - for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { - Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); - Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); - - Ref<Image> denoised = denoiser->denoise_image(img); - if (denoised != img) { - denoised->convert(Image::FORMAT_RGBAH); - Vector<uint8_t> ds = denoised->get_data(); - denoised.unref(); //avoid copy on write - { //restore alpha - uint32_t count = s.size() / 2; //uint16s - const uint16_t *src = (const uint16_t *)s.ptr(); - uint16_t *dst = (uint16_t *)ds.ptrw(); - for (uint32_t j = 0; j < count; j += 4) { - dst[j + 3] = src[j + 3]; - } - } - rd->texture_update(light_accum_tex, i, ds); - } - } - } - { - SWAP(light_accum_tex, light_accum_tex2); - BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); + BakeError error; + if (denoiser == 1) { + // OIDN (external). + error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path); + } else { + // JNLM (built-in). + SWAP(light_accum_tex, light_accum_tex2); + error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function); + } if (unlikely(error != BAKE_OK)) { return error; } } } + { + SWAP(light_accum_tex, light_accum_tex2); + BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1)); + if (unlikely(error != BAKE_OK)) { + return error; + } + } + #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { @@ -1579,8 +1885,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d { seams_push_constant.base_index = seam_offset; rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline); - seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width); - seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height); + seams_push_constant.uv_offset[0] = (uv_offsets[0].x - 0.5f) / float(atlas_size.width); + seams_push_constant.uv_offset[1] = (uv_offsets[0].y - 0.5f) / float(atlas_size.height); seams_push_constant.blend = uv_offsets[0].z; rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); @@ -1603,8 +1909,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d for (int j = 1; j < uv_offset_count; j++) { seams_push_constant.base_index = seam_offset; - seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width); - seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height); + seams_push_constant.uv_offset[0] = (uv_offsets[j].x - 0.5f) / float(atlas_size.width); + seams_push_constant.uv_offset[1] = (uv_offsets[j].y - 0.5f) / float(atlas_size.height); seams_push_constant.blend = uv_offsets[0].z; rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant)); diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 061c9ba000..5414048ddc 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -40,6 +40,26 @@ class RDShaderFile; class LightmapperRD : public Lightmapper { GDCLASS(LightmapperRD, Lightmapper) + struct BakeParameters { + float world_size[3] = {}; + float bias = 0.0; + + float to_cell_offset[3] = {}; + int32_t grid_size = 0; + + float to_cell_size[3] = {}; + uint32_t light_count = 0; + + float env_transform[12] = {}; + + int32_t atlas_size[2] = {}; + float exposure_normalization = 0.0f; + uint32_t bounces = 0; + + float bounce_indirect_energy = 0.0f; + uint32_t pad[3] = {}; + }; + struct MeshInstance { MeshData data; int slice = 0; @@ -57,9 +77,10 @@ class LightmapperRD : public Lightmapper { float attenuation = 0.0; float cos_spot_angle = 0.0; float inv_spot_attenuation = 0.0; + float indirect_energy = 0.0; float shadow_blur = 0.0; uint32_t static_bake = 0; - uint32_t pad[2] = {}; + uint32_t pad = 0; bool operator<(const Light &p_light) const { return type < p_light.type; @@ -171,6 +192,13 @@ class LightmapperRD : public Lightmapper { } }; + struct ClusterAABB { + float min_bounds[3]; + float pad0 = 0.0f; + float max_bounds[3]; + float pad1 = 0.0f; + }; + Vector<MeshInstance> mesh_instances; Vector<Light> lights; @@ -178,12 +206,22 @@ class LightmapperRD : public Lightmapper { struct TriangleSort { uint32_t cell_index = 0; uint32_t triangle_index = 0; + AABB triangle_aabb; + bool operator<(const TriangleSort &p_triangle_sort) const { return cell_index < p_triangle_sort.cell_index; //sorting by triangle index in this case makes no sense } }; + template <int T> + struct TriangleSortAxis { + bool operator()(const TriangleSort &p_a, const TriangleSort &p_b) const { + return p_a.triangle_aabb.get_center()[T] < p_b.triangle_aabb.get_center()[T]; + } + }; + void _plot_triangle_into_triangle_index_list(int p_size, const Vector3i &p_ofs, const AABB &p_bounds, const Vector3 p_points[3], uint32_t p_triangle_index, LocalVector<TriangleSort> &triangles, uint32_t p_grid_size); + void _sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_cluster_index, uint32_t p_index_start, uint32_t p_count, LocalVector<TriangleSort> &p_triangle_sort, LocalVector<ClusterAABB> &p_cluster_aabb); struct RasterPushConstant { float atlas_size[2] = {}; @@ -206,42 +244,46 @@ class LightmapperRD : public Lightmapper { }; struct PushConstant { - int32_t atlas_size[2] = {}; + uint32_t atlas_slice = 0; uint32_t ray_count = 0; - uint32_t ray_to = 0; - - float world_size[3] = {}; - float bias = 0.0; - - float to_cell_offset[3] = {}; uint32_t ray_from = 0; - - float to_cell_size[3] = {}; - uint32_t light_count = 0; - - int32_t grid_size = 0; - int32_t atlas_slice = 0; - int32_t region_ofs[2] = {}; - - float environment_xform[12] = {}; + uint32_t ray_to = 0; + uint32_t region_ofs[2] = {}; + uint32_t probe_count = 0; + uint32_t pad = 0; }; Vector<Ref<Image>> bake_textures; Vector<Color> probe_values; + struct DenoiseParams { + float spatial_bandwidth; + float light_bandwidth; + float albedo_bandwidth; + float normal_bandwidth; + + float filter_strength; + float pad[3]; + }; + BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata); - void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); + void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata); void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); + BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function); + + Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name); + Ref<Image> _read_pfm(const String &p_name); + BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe); public: virtual void add_mesh(const MeshData &p_mesh) override; - virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) override; - virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; - virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; + virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) override; + virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; + virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; virtual void add_probe(const Vector3 &p_position) override; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; int get_bake_texture_count() const override; Ref<Image> get_bake_texture(int p_index) const override; diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index e0e8000f56..98d11b9e69 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -1,6 +1,26 @@ /* SET 0, static data that does not change between any call */ +layout(set = 0, binding = 0) uniform BakeParameters { + vec3 world_size; + float bias; + + vec3 to_cell_offset; + int grid_size; + + vec3 to_cell_size; + uint light_count; + + mat3x4 env_transform; + + ivec2 atlas_size; + float exposure_normalization; + uint bounces; + + float bounce_indirect_energy; +} +bake_params; + struct Vertex { vec3 position; float normal_z; @@ -22,15 +42,22 @@ struct Triangle { uint pad1; }; +struct ClusterAABB { + vec3 min_bounds; + uint pad0; + vec3 max_bounds; + uint pad1; +}; + layout(set = 0, binding = 2, std430) restrict readonly buffer Triangles { Triangle data[]; } triangles; -layout(set = 0, binding = 3, std430) restrict readonly buffer GridIndices { +layout(set = 0, binding = 3, std430) restrict readonly buffer TriangleIndices { uint data[]; } -grid_indices; +triangle_indices; #define LIGHT_TYPE_DIRECTIONAL 0 #define LIGHT_TYPE_OMNI 1 @@ -51,9 +78,10 @@ struct Light { float cos_spot_angle; float inv_spot_attenuation; + float indirect_energy; float shadow_blur; bool static_bake; - uint pad[2]; + uint pad; }; layout(set = 0, binding = 4, std430) restrict readonly buffer Lights { @@ -83,6 +111,16 @@ layout(set = 0, binding = 9) uniform texture2DArray emission_tex; layout(set = 0, binding = 10) uniform sampler linear_sampler; +layout(set = 0, binding = 11, std430) restrict readonly buffer ClusterIndices { + uint data[]; +} +cluster_indices; + +layout(set = 0, binding = 12, std430) restrict readonly buffer ClusterAABBs { + ClusterAABB data[]; +} +cluster_aabbs; + // Fragment action constants const uint FA_NONE = 0; const uint FA_SMOOTHEN_POSITION = 1; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index c2557dfed3..a2a480043a 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -5,6 +5,7 @@ secondary = "#define MODE_BOUNCE_LIGHT"; dilate = "#define MODE_DILATE"; unocclude = "#define MODE_UNOCCLUDE"; light_probes = "#define MODE_LIGHT_PROBES"; +denoise = "#define MODE_DENOISE"; #[compute] @@ -36,8 +37,7 @@ layout(set = 1, binding = 0, std430) restrict buffer LightProbeData { light_probes; layout(set = 1, binding = 1) uniform texture2DArray source_light; -layout(set = 1, binding = 2) uniform texture2DArray source_direct_light; //also need the direct light, which was omitted -layout(set = 1, binding = 3) uniform texture2D environment; +layout(set = 1, binding = 2) uniform texture2D environment; #endif #ifdef MODE_UNOCCLUDE @@ -58,37 +58,35 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light; #endif #ifdef MODE_BOUNCE_LIGHT -layout(rgba32f, set = 1, binding = 5) uniform restrict image2DArray bounce_accum; -layout(set = 1, binding = 6) uniform texture2D environment; -#endif -#ifdef MODE_DIRECT_LIGHT -layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic; +layout(set = 1, binding = 5) uniform texture2D environment; #endif -#ifdef MODE_DILATE +#if defined(MODE_DILATE) || defined(MODE_DENOISE) layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; layout(set = 1, binding = 1) uniform texture2DArray source_light; #endif -layout(push_constant, std430) uniform Params { - ivec2 atlas_size; // x used for light probe mode total probes - uint ray_count; - uint ray_to; +#ifdef MODE_DENOISE +layout(set = 1, binding = 2) uniform texture2DArray source_normal; +layout(set = 1, binding = 3) uniform DenoiseParams { + float spatial_bandwidth; + float light_bandwidth; + float albedo_bandwidth; + float normal_bandwidth; - vec3 world_size; - float bias; + float filter_strength; +} +denoise_params; +#endif - vec3 to_cell_offset; +layout(push_constant, std430) uniform Params { + uint atlas_slice; + uint ray_count; uint ray_from; + uint ray_to; - vec3 to_cell_size; - uint light_count; - - int grid_size; - int atlas_slice; ivec2 region_ofs; - - mat3x4 env_transform; + uint probe_count; } params; @@ -113,7 +111,7 @@ bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, ve r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y); r_distance = dot(triangle_normal, e2); - return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0))); + return (r_distance > bake_params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0))); } const uint RAY_MISS = 0; @@ -121,104 +119,145 @@ const uint RAY_FRONT = 1; const uint RAY_BACK = 2; const uint RAY_ANY = 3; -uint trace_ray(vec3 p_from, vec3 p_to -#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) - , - out uint r_triangle, out vec3 r_barycentric -#endif -#if defined(MODE_UNOCCLUDE) - , - out float r_distance, out vec3 r_normal -#endif -) { +bool ray_box_test(vec3 p_from, vec3 p_inv_dir, vec3 p_box_min, vec3 p_box_max) { + vec3 t0 = (p_box_min - p_from) * p_inv_dir; + vec3 t1 = (p_box_max - p_from) * p_inv_dir; + vec3 tmin = min(t0, t1), tmax = max(t0, t1); + return max(tmin.x, max(tmin.y, tmin.z)) <= min(tmax.x, min(tmax.y, tmax.z)); +} - /* world coords */ +#if CLUSTER_SIZE > 32 +#define CLUSTER_TRIANGLE_ITERATION +#endif +uint trace_ray(vec3 p_from, vec3 p_to, bool p_any_hit, out float r_distance, out vec3 r_normal, out uint r_triangle, out vec3 r_barycentric) { + // World coordinates. vec3 rel = p_to - p_from; float rel_len = length(rel); vec3 dir = normalize(rel); vec3 inv_dir = 1.0 / dir; - /* cell coords */ - - vec3 from_cell = (p_from - params.to_cell_offset) * params.to_cell_size; - vec3 to_cell = (p_to - params.to_cell_offset) * params.to_cell_size; + // Cell coordinates. + vec3 from_cell = (p_from - bake_params.to_cell_offset) * bake_params.to_cell_size; + vec3 to_cell = (p_to - bake_params.to_cell_offset) * bake_params.to_cell_size; - //prepare DDA + // Prepare DDA. vec3 rel_cell = to_cell - from_cell; ivec3 icell = ivec3(from_cell); ivec3 iendcell = ivec3(to_cell); vec3 dir_cell = normalize(rel_cell); - vec3 delta = min(abs(1.0 / dir_cell), params.grid_size); // use params.grid_size as max to prevent infinity values + vec3 delta = min(abs(1.0 / dir_cell), bake_params.grid_size); // Use bake_params.grid_size as max to prevent infinity values. ivec3 step = ivec3(sign(rel_cell)); vec3 side = (sign(rel_cell) * (vec3(icell) - from_cell) + (sign(rel_cell) * 0.5) + 0.5) * delta; uint iters = 0; - while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(params.grid_size))) && iters < 1000) { + while (all(greaterThanEqual(icell, ivec3(0))) && all(lessThan(icell, ivec3(bake_params.grid_size))) && (iters < 1000)) { uvec2 cell_data = texelFetch(usampler3D(grid, linear_sampler), icell, 0).xy; - if (cell_data.x > 0) { //triangles here + uint triangle_count = cell_data.x; + if (triangle_count > 0) { uint hit = RAY_MISS; float best_distance = 1e20; - - for (uint i = 0; i < cell_data.x; i++) { - uint tidx = grid_indices.data[cell_data.y + i]; - - //Ray-Box test - Triangle triangle = triangles.data[tidx]; - vec3 t0 = (triangle.min_bounds - p_from) * inv_dir; - vec3 t1 = (triangle.max_bounds - p_from) * inv_dir; - vec3 tmin = min(t0, t1), tmax = max(t0, t1); - - if (max(tmin.x, max(tmin.y, tmin.z)) > min(tmax.x, min(tmax.y, tmax.z))) { - continue; //ray box failed - } - - //prepare triangle vertices - vec3 vtx0 = vertices.data[triangle.indices.x].position; - vec3 vtx1 = vertices.data[triangle.indices.y].position; - vec3 vtx2 = vertices.data[triangle.indices.z].position; -#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) - vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); - - bool backface = dot(normal, dir) >= 0.0; -#endif - - float distance; - vec3 barycentric; - - if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) { -#ifdef MODE_DIRECT_LIGHT - return RAY_ANY; //any hit good -#endif - -#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) - if (!backface) { - // the case of meshes having both a front and back face in the same plane is more common than - // expected, so if this is a front-face, bias it closer to the ray origin, so it always wins over the back-face - distance = max(params.bias, distance - params.bias); + uint cluster_start = cluster_indices.data[cell_data.y * 2]; + uint cell_triangle_start = cluster_indices.data[cell_data.y * 2 + 1]; + uint cluster_count = (triangle_count + CLUSTER_SIZE - 1) / CLUSTER_SIZE; + uint cluster_base_index = 0; + while (cluster_base_index < cluster_count) { + // To minimize divergence, all Ray-AABB tests on the clusters contained in the cell are performed + // before checking against the triangles. We do this 32 clusters at a time and store the intersected + // clusters on each bit of the 32-bit integer. + uint cluster_test_count = min(32, cluster_count - cluster_base_index); + uint cluster_hits = 0; + for (uint i = 0; i < cluster_test_count; i++) { + uint cluster_index = cluster_start + cluster_base_index + i; + ClusterAABB cluster_aabb = cluster_aabbs.data[cluster_index]; + if (ray_box_test(p_from, inv_dir, cluster_aabb.min_bounds, cluster_aabb.max_bounds)) { + cluster_hits |= (1 << i); } + } - if (distance < best_distance) { - hit = backface ? RAY_BACK : RAY_FRONT; - best_distance = distance; -#if defined(MODE_UNOCCLUDE) - r_distance = distance; - r_normal = normal; + // Check the triangles in any of the clusters that were intersected by toggling off the bits in the + // 32-bit integer counter until no bits are left. + while (cluster_hits > 0) { + uint cluster_index = findLSB(cluster_hits); + cluster_hits &= ~(1 << cluster_index); + cluster_index += cluster_base_index; + + // Do the same divergence execution trick with triangles as well. + uint triangle_base_index = 0; +#ifdef CLUSTER_TRIANGLE_ITERATION + while (triangle_base_index < triangle_count) #endif -#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) - r_triangle = tidx; - r_barycentric = barycentric; + { + uint triangle_start_index = cell_triangle_start + cluster_index * CLUSTER_SIZE + triangle_base_index; + uint triangle_test_count = min(CLUSTER_SIZE, triangle_count - triangle_base_index); + uint triangle_hits = 0; + for (uint i = 0; i < triangle_test_count; i++) { + uint triangle_index = triangle_indices.data[triangle_start_index + i]; + if (ray_box_test(p_from, inv_dir, triangles.data[triangle_index].min_bounds, triangles.data[triangle_index].max_bounds)) { + triangle_hits |= (1 << i); + } + } + + while (triangle_hits > 0) { + uint cluster_triangle_index = findLSB(triangle_hits); + triangle_hits &= ~(1 << cluster_triangle_index); + cluster_triangle_index += triangle_start_index; + + uint triangle_index = triangle_indices.data[cluster_triangle_index]; + Triangle triangle = triangles.data[triangle_index]; + + // Gather the triangle vertex positions. + vec3 vtx0 = vertices.data[triangle.indices.x].position; + vec3 vtx1 = vertices.data[triangle.indices.y].position; + vec3 vtx2 = vertices.data[triangle.indices.z].position; + vec3 normal = -normalize(cross((vtx0 - vtx1), (vtx0 - vtx2))); + bool backface = dot(normal, dir) >= 0.0; + float distance; + vec3 barycentric; + if (ray_hits_triangle(p_from, dir, rel_len, vtx0, vtx1, vtx2, distance, barycentric)) { + if (p_any_hit) { + // Return early if any hit was requested. + return RAY_ANY; + } + + vec3 position = p_from + dir * distance; + vec3 hit_cell = (position - bake_params.to_cell_offset) * bake_params.to_cell_size; + if (icell != ivec3(hit_cell)) { + // It's possible for the ray to hit a triangle in a position outside the bounds of the cell + // if it's large enough to cover multiple ones. The hit must be ignored if this is the case. + continue; + } + + if (!backface) { + // The case of meshes having both a front and back face in the same plane is more common than + // expected, so if this is a front-face, bias it closer to the ray origin, so it always wins + // over the back-face. + distance = max(bake_params.bias, distance - bake_params.bias); + } + + if (distance < best_distance) { + hit = backface ? RAY_BACK : RAY_FRONT; + best_distance = distance; + r_distance = distance; + r_normal = normal; + r_triangle = triangle_index; + r_barycentric = barycentric; + } + } + } + +#ifdef CLUSTER_TRIANGLE_ITERATION + triangle_base_index += CLUSTER_SIZE; #endif } -#endif } + + cluster_base_index += 32; } -#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) if (hit != RAY_MISS) { return hit; } -#endif } if (icell == iendcell) { @@ -228,13 +267,32 @@ uint trace_ray(vec3 p_from, vec3 p_to bvec3 mask = lessThanEqual(side.xyz, min(side.yzx, side.zxy)); side += vec3(mask) * delta; icell += ivec3(vec3(mask)) * step; - iters++; } return RAY_MISS; } +uint trace_ray_closest_hit_triangle(vec3 p_from, vec3 p_to, out uint r_triangle, out vec3 r_barycentric) { + float distance; + vec3 normal; + return trace_ray(p_from, p_to, false, distance, normal, r_triangle, r_barycentric); +} + +uint trace_ray_closest_hit_distance(vec3 p_from, vec3 p_to, out float r_distance, out vec3 r_normal) { + uint triangle; + vec3 barycentric; + return trace_ray(p_from, p_to, false, r_distance, r_normal, triangle, barycentric); +} + +uint trace_ray_any_hit(vec3 p_from, vec3 p_to) { + float distance; + vec3 normal; + uint triangle; + vec3 barycentric; + return trace_ray(p_from, p_to, true, distance, normal, triangle, barycentric); +} + // https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/ uint hash(uint value) { uint state = value * 747796405u + 2891336453u; @@ -255,21 +313,31 @@ float randomize(inout uint value) { const float PI = 3.14159265f; // http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.4.pdf (chapter 15) -vec3 generate_hemisphere_uniform_direction(inout uint noise) { +vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) { float noise1 = randomize(noise); float noise2 = randomize(noise) * 2.0 * PI; - float factor = sqrt(1 - (noise1 * noise1)); - return vec3(factor * cos(noise2), factor * sin(noise2), noise1); + return vec3(sqrt(noise1) * cos(noise2), sqrt(noise1) * sin(noise2), sqrt(1.0 - noise1)); } -vec3 generate_hemisphere_cosine_weighted_direction(inout uint noise) { - float noise1 = randomize(noise); - float noise2 = randomize(noise) * 2.0 * PI; +// Distribution generation adapted from "Generating uniformly distributed numbers on a sphere" +// <http://corysimon.github.io/articles/uniformdistn-on-sphere/> +vec3 generate_sphere_uniform_direction(inout uint noise) { + float theta = 2.0 * PI * randomize(noise); + float phi = acos(1.0 - 2.0 * randomize(noise)); + return vec3(sin(phi) * cos(theta), sin(phi) * sin(theta), cos(phi)); +} - return vec3(sqrt(noise1) * cos(noise2), sqrt(noise1) * sin(noise2), sqrt(1.0 - noise1)); +vec3 generate_ray_dir_from_normal(vec3 normal, inout uint noise) { + vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); + vec3 tangent = normalize(cross(v0, normal)); + vec3 bitangent = normalize(cross(tangent, normal)); + mat3 normal_mat = mat3(tangent, bitangent, normal); + return normal_mat * generate_hemisphere_cosine_weighted_direction(noise); } +#if defined(MODE_DIRECT_LIGHT) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) + float get_omni_attenuation(float distance, float inv_range, float decay) { float nd = distance * inv_range; nd *= nd; @@ -279,16 +347,202 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { return nd * pow(max(distance, 0.0001), -decay); } +void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) { + r_light = vec3(0.0f); + + vec3 light_pos; + float dist; + float attenuation; + float soft_shadowing_disk_size; + Light light_data = lights.data[p_light_index]; + if (light_data.type == LIGHT_TYPE_DIRECTIONAL) { + vec3 light_vec = light_data.direction; + light_pos = p_position - light_vec * length(bake_params.world_size); + r_light_dir = normalize(light_pos - p_position); + dist = length(bake_params.world_size); + attenuation = 1.0; + soft_shadowing_disk_size = light_data.size; + } else { + light_pos = light_data.position; + r_light_dir = normalize(light_pos - p_position); + dist = distance(p_position, light_pos); + if (dist > light_data.range) { + return; + } + + soft_shadowing_disk_size = light_data.size / dist; + + attenuation = get_omni_attenuation(dist, 1.0 / light_data.range, light_data.attenuation); + + if (light_data.type == LIGHT_TYPE_SPOT) { + vec3 rel = normalize(p_position - light_pos); + float cos_spot_angle = light_data.cos_spot_angle; + float cos_angle = dot(rel, light_data.direction); + + if (cos_angle < cos_spot_angle) { + return; + } + + float scos = max(cos_angle, cos_spot_angle); + float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle)); + attenuation *= 1.0 - pow(spot_rim, light_data.inv_spot_attenuation); + } + } + + attenuation *= max(0.0, dot(p_normal, r_light_dir)); + if (attenuation <= 0.0001) { + return; + } + + float penumbra = 0.0; + if ((light_data.size > 0.0) && p_soft_shadowing) { + vec3 light_to_point = -r_light_dir; + vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 light_to_point_tan = normalize(cross(light_to_point, aux)); + vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan)); + + const uint shadowing_rays_check_penumbra_denom = 2; + uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1; + + uint hits = 0; + vec3 light_disk_to_point = light_to_point; + for (uint j = 0; j < shadowing_ray_count; j++) { + // Optimization: + // Once already traced an important proportion of rays, if all are hits or misses, + // assume we're not in the penumbra so we can infer the rest would have the same result + if (p_soft_shadowing) { + if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { + if (hits == j) { + // Assume totally lit + hits = shadowing_ray_count; + break; + } else if (hits == 0) { + // Assume totally dark + hits = 0; + break; + } + } + } + + float r = randomize(r_noise); + float a = randomize(r_noise) * 2.0 * PI; + vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur; + light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan); + + if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) { + hits++; + } + } + + penumbra = float(hits) / float(shadowing_ray_count); + } else { + if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) { + penumbra = 1.0; + } + } + + r_light = light_data.color * light_data.energy * attenuation * penumbra; +} + +#endif + +#if defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES) + +vec3 trace_environment_color(vec3 ray_dir) { + vec3 sky_dir = normalize(mat3(bake_params.env_transform) * ray_dir); + vec2 st = vec2(atan(sky_dir.x, sky_dir.z), acos(sky_dir.y)); + if (st.x < 0.0) { + st.x += PI * 2.0; + } + + return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb; +} + +vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) { + // The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested. + vec3 position = p_position; + vec3 ray_dir = p_ray_dir; + uint max_depth = max(bake_params.bounces, 1); + vec3 throughput = vec3(1.0); + vec3 light = vec3(0.0); + for (uint depth = 0; depth < max_depth; depth++) { + uint tidx; + vec3 barycentric; + uint trace_result = trace_ray_closest_hit_triangle(position + ray_dir * bake_params.bias, position + ray_dir * length(bake_params.world_size), tidx, barycentric); + if (trace_result == RAY_FRONT) { + Vertex vert0 = vertices.data[triangles.data[tidx].indices.x]; + Vertex vert1 = vertices.data[triangles.data[tidx].indices.y]; + Vertex vert2 = vertices.data[triangles.data[tidx].indices.z]; + vec3 uvw = vec3(barycentric.x * vert0.uv + barycentric.y * vert1.uv + barycentric.z * vert2.uv, float(triangles.data[tidx].slice)); + position = barycentric.x * vert0.position + barycentric.y * vert1.position + barycentric.z * vert2.position; + + vec3 norm0 = vec3(vert0.normal_xy, vert0.normal_z); + vec3 norm1 = vec3(vert1.normal_xy, vert1.normal_z); + vec3 norm2 = vec3(vert2.normal_xy, vert2.normal_z); + vec3 normal = barycentric.x * norm0 + barycentric.y * norm1 + barycentric.z * norm2; + + vec3 direct_light = vec3(0.0f); +#ifdef USE_LIGHT_TEXTURE_FOR_BOUNCES + direct_light += textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb; +#else + // Trace the lights directly. Significantly more expensive but more accurate in scenarios + // where the lightmap texture isn't reliable. + for (uint i = 0; i < bake_params.light_count; i++) { + vec3 light; + vec3 light_dir; + trace_direct_light(position, normal, i, false, light, light_dir, r_noise); + direct_light += light * lights.data[i].indirect_energy; + } + + direct_light *= bake_params.exposure_normalization; +#endif + + vec3 albedo = textureLod(sampler2DArray(albedo_tex, linear_sampler), uvw, 0).rgb; + vec3 emissive = textureLod(sampler2DArray(emission_tex, linear_sampler), uvw, 0).rgb; + emissive *= bake_params.exposure_normalization; + + light += throughput * emissive; + throughput *= albedo; + light += throughput * direct_light * bake_params.bounce_indirect_energy; + + // Use Russian Roulette to determine a probability to terminate the bounce earlier as an optimization. + // <https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer> + float p = max(max(throughput.x, throughput.y), throughput.z); + if (randomize(r_noise) > p) { + break; + } + + // Boost the throughput from the probability of the ray being terminated early. + throughput *= 1.0 / p; + + // Generate a new ray direction for the next bounce from this surface's normal. + ray_dir = generate_ray_dir_from_normal(normal, r_noise); + } else if (trace_result == RAY_MISS) { + // Look for the environment color and stop bouncing. + light += throughput * trace_environment_color(ray_dir); + break; + } else { + // Ignore any other trace results. + break; + } + } + + return light; +} + +#endif + void main() { + // Check if invocation is out of bounds. #ifdef MODE_LIGHT_PROBES int probe_index = int(gl_GlobalInvocationID.x); - if (probe_index >= params.atlas_size.x) { //too large, do nothing + if (probe_index >= params.probe_count) { return; } #else ivec2 atlas_pos = ivec2(gl_GlobalInvocationID.xy) + params.region_ofs; - if (any(greaterThanEqual(atlas_pos, params.atlas_size))) { //too large, do nothing + if (any(greaterThanEqual(atlas_pos, bake_params.atlas_size))) { return; } #endif @@ -300,11 +554,8 @@ void main() { return; //empty texel, no process } vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; - - //go through all lights - //start by own light (emissive) - vec3 static_light = vec3(0.0); - vec3 dynamic_light = vec3(0.0); + vec3 light_for_texture = vec3(0.0); + vec3 light_for_bounces = vec3(0.0); #ifdef USE_SH_LIGHTMAPS vec4 sh_accum[4] = vec4[]( @@ -314,99 +565,17 @@ void main() { vec4(0.0, 0.0, 0.0, 1.0)); #endif - for (uint i = 0; i < params.light_count; i++) { - vec3 light_pos; - float dist; - float attenuation; - float soft_shadowing_disk_size; - if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) { - vec3 light_vec = lights.data[i].direction; - light_pos = position - light_vec * length(params.world_size); - dist = length(params.world_size); - attenuation = 1.0; - soft_shadowing_disk_size = lights.data[i].size; - } else { - light_pos = lights.data[i].position; - dist = distance(position, light_pos); - if (dist > lights.data[i].range) { - continue; - } - soft_shadowing_disk_size = lights.data[i].size / dist; - - attenuation = get_omni_attenuation(dist, 1.0 / lights.data[i].range, lights.data[i].attenuation); - - if (lights.data[i].type == LIGHT_TYPE_SPOT) { - vec3 rel = normalize(position - light_pos); - float cos_spot_angle = lights.data[i].cos_spot_angle; - float cos_angle = dot(rel, lights.data[i].direction); - - if (cos_angle < cos_spot_angle) { - continue; //invisible, dont try - } - - float scos = max(cos_angle, cos_spot_angle); - float spot_rim = max(0.0001, (1.0 - scos) / (1.0 - cos_spot_angle)); - attenuation *= 1.0 - pow(spot_rim, lights.data[i].inv_spot_attenuation); - } - } - - vec3 light_dir = normalize(light_pos - position); - attenuation *= max(0.0, dot(normal, light_dir)); - - if (attenuation <= 0.0001) { - continue; //no need to do anything - } - - float penumbra = 0.0; - if (lights.data[i].size > 0.0) { - vec3 light_to_point = -light_dir; - vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); - vec3 light_to_point_tan = normalize(cross(light_to_point, aux)); - vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan)); - - const uint shadowing_rays_check_penumbra_denom = 2; - uint shadowing_ray_count = params.ray_count; - - uint hits = 0; - uint noise = random_seed(ivec3(atlas_pos, 43573547 /* some prime */)); - vec3 light_disk_to_point = light_to_point; - for (uint j = 0; j < shadowing_ray_count; j++) { - // Optimization: - // Once already traced an important proportion of rays, if all are hits or misses, - // assume we're not in the penumbra so we can infer the rest would have the same result - if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { - if (hits == j) { - // Assume totally lit - hits = shadowing_ray_count; - break; - } else if (hits == 0) { - // Assume totally dark - hits = 0; - break; - } - } - - float r = randomize(noise); - float a = randomize(noise) * 2.0 * PI; - vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * lights.data[i].shadow_blur; - light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan); - - if (trace_ray(position - light_disk_to_point * params.bias, position - light_disk_to_point * dist) == RAY_MISS) { - hits++; - } - } - penumbra = float(hits) / float(shadowing_ray_count); - } else { - if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) { - penumbra = 1.0; - } - } + // Use atlas position and a prime number as the seed. + uint noise = random_seed(ivec3(atlas_pos, 43573547)); + for (uint i = 0; i < bake_params.light_count; i++) { + vec3 light; + vec3 light_dir; + trace_direct_light(position, normal, i, true, light, light_dir, noise); - vec3 light = lights.data[i].color * lights.data[i].energy * attenuation * penumbra; if (lights.data[i].static_bake) { - static_light += light; -#ifdef USE_SH_LIGHTMAPS + light_for_texture += light; +#ifdef USE_SH_LIGHTMAPS float c[4] = float[]( 0.282095, //l0 0.488603 * light_dir.y, //l1n1 @@ -415,106 +584,56 @@ void main() { ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * (1.0 / 3.0); + sh_accum[j].rgb += light * c[j] * 8.0; } #endif - - } else { - dynamic_light += light; } - } - vec3 albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; - vec3 emissive = texelFetch(sampler2DArray(emission_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; - - dynamic_light *= albedo; //if it will bounce, must multiply by albedo - dynamic_light += emissive; - - //keep for lightprobes - imageStore(primary_dynamic, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0)); + light_for_bounces += light * lights.data[i].indirect_energy; + } - dynamic_light += static_light * albedo; //send for bounces - dynamic_light *= params.env_transform[2][3]; // exposure_normalization - imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(dynamic_light, 1.0)); + light_for_bounces *= bake_params.exposure_normalization; + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_bounces, 1.0)); #ifdef USE_SH_LIGHTMAPS - //keep for adding at the end + // Keep for adding at the end. imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 0), sh_accum[0]); imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 1), sh_accum[1]); imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 2), sh_accum[2]); imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + 3), sh_accum[3]); - #else - static_light *= params.env_transform[2][3]; // exposure_normalization - imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0)); + light_for_texture *= bake_params.exposure_normalization; + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_texture, 1.0)); #endif #endif #ifdef MODE_BOUNCE_LIGHT - vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; - if (length(normal) < 0.5) { - return; //empty texel, no process - } - - vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; - - vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); - vec3 tangent = normalize(cross(v0, normal)); - vec3 bitangent = normalize(cross(tangent, normal)); - mat3 normal_mat = mat3(tangent, bitangent, normal); - #ifdef USE_SH_LIGHTMAPS vec4 sh_accum[4] = vec4[]( vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 1.0), vec4(0.0, 0.0, 0.0, 1.0)); +#else + vec3 light_accum = vec3(0.0); #endif - vec3 light_average = vec3(0.0); - float active_rays = 0.0; - uint noise = random_seed(ivec3(params.ray_from, atlas_pos)); - for (uint i = params.ray_from; i < params.ray_to; i++) { - vec3 ray_dir = normal_mat * generate_hemisphere_cosine_weighted_direction(noise); - - uint tidx; - vec3 barycentric; - - vec3 light = vec3(0.0); - uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric); - if (trace_result == RAY_FRONT) { - //hit a triangle - vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv; - vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv; - vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv; - vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice)); - - light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb; - active_rays += 1.0; - } else if (trace_result == RAY_MISS) { - if (params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce - // Did not hit a triangle, reach out for the sky - vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir); - - vec2 st = vec2( - atan(sky_dir.x, sky_dir.z), - acos(sky_dir.y)); - - if (st.x < 0.0) - st.x += PI * 2.0; - - st /= vec2(PI * 2.0, PI); - light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb; - } - active_rays += 1.0; - } + // Retrieve starting normal and position. + vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + if (length(normal) < 0.5) { + // The pixel is empty, skip processing it. + return; + } - light_average += light; + vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + uint noise = random_seed(ivec3(params.ray_from, atlas_pos)); + for (uint i = params.ray_from; i < params.ray_to; i++) { + vec3 ray_dir = generate_ray_dir_from_normal(normal, noise); + vec3 light = trace_indirect_light(position, ray_dir, noise); #ifdef USE_SH_LIGHTMAPS - float c[4] = float[]( 0.282095, //l0 0.488603 * ray_dir.y, //l1n1 @@ -523,44 +642,25 @@ void main() { ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * (8.0 / float(params.ray_count)); + sh_accum[j].rgb += light * c[j] * 8.0; } +#else + light_accum += light; #endif } - vec3 light_total; - if (params.ray_from == 0) { - light_total = vec3(0.0); - } else { - vec4 accum = imageLoad(bounce_accum, ivec3(atlas_pos, params.atlas_slice)); - light_total = accum.rgb; - active_rays += accum.a; - } - - light_total += light_average; - + // Add the averaged result to the accumulated light texture. #ifdef USE_SH_LIGHTMAPS - for (int i = 0; i < 4; i++) { vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i)); - accum.rgb += sh_accum[i].rgb; + accum.rgb += sh_accum[i].rgb / float(params.ray_count); imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), accum); } - -#endif - if (params.ray_to == params.ray_count) { - if (active_rays > 0) { - light_total /= active_rays; - } - imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, 1.0)); -#ifndef USE_SH_LIGHTMAPS - vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice)); - accum.rgb += light_total; - imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum); +#else + vec4 accum = imageLoad(accum_light, ivec3(atlas_pos, params.atlas_slice)); + accum.rgb += light_accum / float(params.ray_count); + imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), accum); #endif - } else { - imageStore(bounce_accum, ivec3(atlas_pos, params.atlas_slice), vec4(light_total, active_rays)); - } #endif @@ -583,7 +683,7 @@ void main() { vec3 v0 = abs(face_normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0); vec3 tangent = normalize(cross(v0, face_normal)); vec3 bitangent = normalize(cross(tangent, face_normal)); - vec3 base_pos = vertex_pos + face_normal * params.bias; //raise a bit + vec3 base_pos = vertex_pos + face_normal * bake_params.bias; // Raise a bit. vec3 rays[4] = vec3[](tangent, bitangent, -tangent, -bitangent); float min_d = 1e20; @@ -592,9 +692,10 @@ void main() { float d; vec3 norm; - if (trace_ray(base_pos, ray_to, d, norm) == RAY_BACK) { + if (trace_ray_closest_hit_distance(base_pos, ray_to, d, norm) == RAY_BACK) { if (d < min_d) { - vertex_pos = base_pos + rays[i] * d + norm * params.bias * 10.0; //this bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back. + // This bias needs to be greater than the regular bias, because otherwise later, rays will go the other side when pointing back. + vertex_pos = base_pos + rays[i] * d + norm * bake_params.bias * 10.0; min_d = d; } } @@ -623,58 +724,24 @@ void main() { uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */)); for (uint i = params.ray_from; i < params.ray_to; i++) { - vec3 ray_dir = generate_hemisphere_uniform_direction(noise); - if (bool(i & 1)) { - //throw to both sides, so alternate them - ray_dir.z *= -1.0; - } - - uint tidx; - vec3 barycentric; - vec3 light; - - uint trace_result = trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric); - if (trace_result == RAY_FRONT) { - vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv; - vec2 uv1 = vertices.data[triangles.data[tidx].indices.y].uv; - vec2 uv2 = vertices.data[triangles.data[tidx].indices.z].uv; - vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice)); - - light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb; - light += textureLod(sampler2DArray(source_direct_light, linear_sampler), uvw, 0.0).rgb; - } else if (trace_result == RAY_MISS) { - //did not hit a triangle, reach out for the sky - vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir); - - vec2 st = vec2( - atan(sky_dir.x, sky_dir.z), - acos(sky_dir.y)); - - if (st.x < 0.0) - st.x += PI * 2.0; - - st /= vec2(PI * 2.0, PI); - - light = textureLod(sampler2D(environment, linear_sampler), st, 0.0).rgb; - } + vec3 ray_dir = generate_sphere_uniform_direction(noise); + vec3 light = trace_indirect_light(position, ray_dir, noise); - { - float c[9] = float[]( - 0.282095, //l0 - 0.488603 * ray_dir.y, //l1n1 - 0.488603 * ray_dir.z, //l1n0 - 0.488603 * ray_dir.x, //l1p1 - 1.092548 * ray_dir.x * ray_dir.y, //l2n2 - 1.092548 * ray_dir.y * ray_dir.z, //l2n1 - //0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20 - 0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20 - 1.092548 * ray_dir.x * ray_dir.z, //l2p1 - 0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2 - ); + float c[9] = float[]( + 0.282095, //l0 + 0.488603 * ray_dir.y, //l1n1 + 0.488603 * ray_dir.z, //l1n0 + 0.488603 * ray_dir.x, //l1p1 + 1.092548 * ray_dir.x * ray_dir.y, //l2n2 + 1.092548 * ray_dir.y * ray_dir.z, //l2n1 + //0.315392 * (ray_dir.x * ray_dir.x + ray_dir.y * ray_dir.y + 2.0 * ray_dir.z * ray_dir.z), //l20 + 0.315392 * (3.0 * ray_dir.z * ray_dir.z - 1.0), //l20 + 1.092548 * ray_dir.x * ray_dir.z, //l2p1 + 0.546274 * (ray_dir.x * ray_dir.x - ray_dir.y * ray_dir.y) //l2p2 + ); - for (uint j = 0; j < 9; j++) { - probe_sh_accum[j].rgb += light * c[j]; - } + for (uint j = 0; j < 9; j++) { + probe_sh_accum[j].rgb += light * c[j]; } } @@ -735,4 +802,153 @@ void main() { imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice), c); #endif + +#ifdef MODE_DENOISE + // Joint Non-local means (JNLM) denoiser. + // + // Based on YoctoImageDenoiser's JNLM implementation with corrections from "Nonlinearly Weighted First-order Regression for Denoising Monte Carlo Renderings". + // + // <https://github.com/ManuelPrandini/YoctoImageDenoiser/blob/06e19489dd64e47792acffde536393802ba48607/libs/yocto_extension/yocto_extension.cpp#L207> + // <https://benedikt-bitterli.me/nfor/nfor.pdf> + // + // MIT License + // + // Copyright (c) 2020 ManuelPrandini + // + // 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. + // + // Most of the constants below have been hand-picked to fit the common scenarios lightmaps + // are generated with, but they can be altered freely to experiment and achieve better results. + + // Half the size of the patch window around each pixel that is weighted to compute the denoised pixel. + // A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc. + const int HALF_PATCH_WINDOW = 4; + + // Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel. + const int HALF_SEARCH_WINDOW = 10; + + // For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance + // in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also + // cause less features to be erased in the process. + + // Controls how much the spatial distance of the pixels influences the denoising weight. + const float SIGMA_SPATIAL = denoise_params.spatial_bandwidth; + + // Controls how much the light color distance of the pixels influences the denoising weight. + const float SIGMA_LIGHT = denoise_params.light_bandwidth; + + // Controls how much the albedo color distance of the pixels influences the denoising weight. + const float SIGMA_ALBEDO = denoise_params.albedo_bandwidth; + + // Controls how much the normal vector distance of the pixels influences the denoising weight. + const float SIGMA_NORMAL = denoise_params.normal_bandwidth; + + // Strength of the filter. The original paper recommends values around 10 to 15 times the Sigma parameter. + const float FILTER_VALUE = denoise_params.filter_strength * SIGMA_LIGHT; + + // Formula constants. + const int PATCH_WINDOW_DIMENSION = (HALF_PATCH_WINDOW * 2 + 1); + const int PATCH_WINDOW_DIMENSION_SQUARE = (PATCH_WINDOW_DIMENSION * PATCH_WINDOW_DIMENSION); + const float TWO_SIGMA_SPATIAL_SQUARE = 2.0f * SIGMA_SPATIAL * SIGMA_SPATIAL; + const float TWO_SIGMA_LIGHT_SQUARE = 2.0f * SIGMA_LIGHT * SIGMA_LIGHT; + const float TWO_SIGMA_ALBEDO_SQUARE = 2.0f * SIGMA_ALBEDO * SIGMA_ALBEDO; + const float TWO_SIGMA_NORMAL_SQUARE = 2.0f * SIGMA_NORMAL * SIGMA_NORMAL; + const float FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE = FILTER_VALUE * FILTER_VALUE * TWO_SIGMA_LIGHT_SQUARE; + const float EPSILON = 1e-6f; + +#ifdef USE_SH_LIGHTMAPS + const uint slice_count = 4; + const uint slice_base = params.atlas_slice * slice_count; +#else + const uint slice_count = 1; + const uint slice_base = params.atlas_slice; +#endif + + for (uint i = 0; i < slice_count; i++) { + uint lightmap_slice = slice_base + i; + vec3 denoised_rgb = vec3(0.0f); + vec4 input_light = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, lightmap_slice), 0); + vec3 input_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).rgb; + vec3 input_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + if (length(input_normal) > EPSILON) { + // Compute the denoised pixel if the normal is valid. + float sum_weights = 0.0f; + vec3 input_rgb = input_light.rgb; + for (int search_y = -HALF_SEARCH_WINDOW; search_y <= HALF_SEARCH_WINDOW; search_y++) { + for (int search_x = -HALF_SEARCH_WINDOW; search_x <= HALF_SEARCH_WINDOW; search_x++) { + ivec2 search_pos = atlas_pos + ivec2(search_x, search_y); + vec3 search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(search_pos, lightmap_slice), 0).rgb; + vec3 search_albedo = texelFetch(sampler2DArray(albedo_tex, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).rgb; + vec3 search_normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(search_pos, params.atlas_slice), 0).xyz; + float patch_square_dist = 0.0f; + for (int offset_y = -HALF_PATCH_WINDOW; offset_y <= HALF_PATCH_WINDOW; offset_y++) { + for (int offset_x = -HALF_PATCH_WINDOW; offset_x <= HALF_PATCH_WINDOW; offset_x++) { + ivec2 offset_input_pos = atlas_pos + ivec2(offset_x, offset_y); + ivec2 offset_search_pos = search_pos + ivec2(offset_x, offset_y); + vec3 offset_input_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_input_pos, lightmap_slice), 0).rgb; + vec3 offset_search_rgb = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(offset_search_pos, lightmap_slice), 0).rgb; + vec3 offset_delta_rgb = offset_input_rgb - offset_search_rgb; + patch_square_dist += dot(offset_delta_rgb, offset_delta_rgb) - TWO_SIGMA_LIGHT_SQUARE; + } + } + + patch_square_dist = max(0.0f, patch_square_dist / (3.0f * PATCH_WINDOW_DIMENSION_SQUARE)); + + float weight = 1.0f; + + // Ignore weight if search position is out of bounds. + weight *= step(0, search_pos.x) * step(search_pos.x, bake_params.atlas_size.x - 1); + weight *= step(0, search_pos.y) * step(search_pos.y, bake_params.atlas_size.y - 1); + + // Ignore weight if normal is zero length. + weight *= step(EPSILON, length(search_normal)); + + // Weight with pixel distance. + vec2 pixel_delta = vec2(search_x, search_y); + float pixel_square_dist = dot(pixel_delta, pixel_delta); + weight *= exp(-pixel_square_dist / TWO_SIGMA_SPATIAL_SQUARE); + + // Weight with patch. + weight *= exp(-patch_square_dist / FILTER_SQUARE_TWO_SIGMA_LIGHT_SQUARE); + + // Weight with albedo. + vec3 albedo_delta = input_albedo - search_albedo; + float albedo_square_dist = dot(albedo_delta, albedo_delta); + weight *= exp(-albedo_square_dist / TWO_SIGMA_ALBEDO_SQUARE); + + // Weight with normal. + vec3 normal_delta = input_normal - search_normal; + float normal_square_dist = dot(normal_delta, normal_delta); + weight *= exp(-normal_square_dist / TWO_SIGMA_NORMAL_SQUARE); + + denoised_rgb += weight * search_rgb; + sum_weights += weight; + } + } + + denoised_rgb /= sum_weights; + } else { + // Ignore pixels where the normal is empty, just copy the light color. + denoised_rgb = input_light.rgb; + } + + imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a)); + } +#endif } diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp index 7ec4a40766..8103799974 100644 --- a/modules/lightmapper_rd/register_types.cpp +++ b/modules/lightmapper_rd/register_types.cpp @@ -46,10 +46,10 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) { return; } - GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 16); - GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 64); - GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 256); - GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 1024); + GLOBAL_DEF("rendering/lightmapping/bake_quality/low_quality_ray_count", 32); + GLOBAL_DEF("rendering/lightmapping/bake_quality/medium_quality_ray_count", 128); + GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_ray_count", 512); + GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_ray_count", 2048); GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_pass", 32); GLOBAL_DEF("rendering/lightmapping/bake_performance/region_size", 512); @@ -58,7 +58,8 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) { GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512); GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048); GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64); - GLOBAL_DEF("rendering/lightmapping/primitive_meshes/texel_size", 0.2); + + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/denoising/denoiser", PROPERTY_HINT_ENUM, "JNLM,OIDN"), 0); #ifndef _3D_DISABLED GDREGISTER_CLASS(LightmapperRD); Lightmapper::create_gpu = create_lightmapper_rd; diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index 47c0dc9bb6..381ed42fe1 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -256,7 +256,7 @@ Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, PackedByte } Error HMACContextMbedTLS::update(PackedByteArray p_data) { - ERR_FAIL_COND_V_MSG(ctx == nullptr, ERR_INVALID_DATA, "Start must be called before update."); + ERR_FAIL_NULL_V_MSG(ctx, ERR_INVALID_DATA, "Start must be called before update."); ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_PARAMETER, "Src must not be empty."); @@ -265,7 +265,7 @@ Error HMACContextMbedTLS::update(PackedByteArray p_data) { } PackedByteArray HMACContextMbedTLS::finish() { - ERR_FAIL_COND_V_MSG(ctx == nullptr, PackedByteArray(), "Start must be called before finish."); + ERR_FAIL_NULL_V_MSG(ctx, PackedByteArray(), "Start must be called before finish."); ERR_FAIL_COND_V_MSG(hash_len == 0, PackedByteArray(), "Unsupported hash type."); PackedByteArray out; @@ -342,7 +342,7 @@ void CryptoMbedTLS::load_default_certificates(String p_path) { ERR_FAIL_COND(default_certs != nullptr); default_certs = memnew(X509CertificateMbedTLS); - ERR_FAIL_COND(default_certs == nullptr); + ERR_FAIL_NULL(default_certs); if (!p_path.is_empty()) { // Use certs defined in project settings. @@ -419,9 +419,19 @@ Ref<X509Certificate> CryptoMbedTLS::generate_self_signed_certificate(Ref<CryptoK } PackedByteArray CryptoMbedTLS::generate_random_bytes(int p_bytes) { + ERR_FAIL_COND_V(p_bytes < 0, PackedByteArray()); PackedByteArray out; out.resize(p_bytes); - mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw(), p_bytes); + int left = p_bytes; + int pos = 0; + // Ensure we generate random in chunks of no more than MBEDTLS_CTR_DRBG_MAX_REQUEST bytes or mbedtls_ctr_drbg_random will fail. + while (left > 0) { + int to_read = MIN(left, MBEDTLS_CTR_DRBG_MAX_REQUEST); + int ret = mbedtls_ctr_drbg_random(&ctr_drbg, out.ptrw() + pos, to_read); + ERR_FAIL_COND_V_MSG(ret != 0, PackedByteArray(), vformat("Failed to generate %d random bytes(s). Error: %d.", p_bytes, ret)); + left -= to_read; + pos += to_read; + } return out; } diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index ed1a97cc2c..c7373481ca 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -40,7 +40,7 @@ int PacketPeerMbedDTLS::bio_send(void *ctx, const unsigned char *buf, size_t len PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); Error err = sp->base->put_packet((const uint8_t *)buf, len); if (err == ERR_BUSY) { @@ -58,7 +58,7 @@ int PacketPeerMbedDTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { PacketPeerMbedDTLS *sp = static_cast<PacketPeerMbedDTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int pc = sp->base->get_available_packet_count(); if (pc == 0) { diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index a9d187bd64..a359b42041 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -40,7 +40,7 @@ int StreamPeerMbedTLS::bio_send(void *ctx, const unsigned char *buf, size_t len) StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int sent; Error err = sp->base->put_partial_data((const uint8_t *)buf, len, sent); @@ -60,7 +60,7 @@ int StreamPeerMbedTLS::bio_recv(void *ctx, unsigned char *buf, size_t len) { StreamPeerMbedTLS *sp = static_cast<StreamPeerMbedTLS *>(ctx); - ERR_FAIL_COND_V(sp == nullptr, 0); + ERR_FAIL_NULL_V(sp, 0); int got; Error err = sp->base->get_partial_data((uint8_t *)buf, len, got); diff --git a/modules/minimp3/SCsub b/modules/minimp3/SCsub index 20e3165f38..09e84f71e9 100644 --- a/modules/minimp3/SCsub +++ b/modules/minimp3/SCsub @@ -13,5 +13,8 @@ if not env.msvc: else: env_minimp3.Prepend(CPPPATH=[thirdparty_dir]) +if not env["minimp3_extra_formats"]: + env_minimp3.Append(CPPDEFINES=["MINIMP3_ONLY_MP3"]) + # Godot source files env_minimp3.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 6af86a96dc..4efa4d329e 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -28,7 +28,6 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#define MINIMP3_ONLY_MP3 #define MINIMP3_FLOAT_OUTPUT #define MINIMP3_IMPLEMENTATION #define MINIMP3_NO_STDIO diff --git a/modules/minimp3/config.py b/modules/minimp3/config.py index e6bdcb2a83..115b376fc8 100644 --- a/modules/minimp3/config.py +++ b/modules/minimp3/config.py @@ -2,6 +2,14 @@ def can_build(env, platform): return True +def get_opts(platform): + from SCons.Variables import BoolVariable + + return [ + BoolVariable("minimp3_extra_formats", "Build minimp3 with MP1/MP2 decoding support", False), + ] + + def configure(env): pass diff --git a/modules/minimp3/resource_importer_mp3.cpp b/modules/minimp3/resource_importer_mp3.cpp index 4e56120ec6..d60b979c3f 100644 --- a/modules/minimp3/resource_importer_mp3.cpp +++ b/modules/minimp3/resource_importer_mp3.cpp @@ -47,6 +47,10 @@ String ResourceImporterMP3::get_visible_name() const { } void ResourceImporterMP3::get_recognized_extensions(List<String> *p_extensions) const { +#ifndef MINIMP3_ONLY_MP3 + p_extensions->push_back("mp1"); + p_extensions->push_back("mp2"); +#endif p_extensions->push_back("mp3"); } diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index 580f51c973..90a517cc40 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -316,9 +316,35 @@ def generate_sdk_package_versions(): f.write(props) f.close() + # Also write the versioned docs URL to a constant for the Source Generators. + + constants = """namespace Godot.SourceGenerators +{{ + partial class Common + {{ + public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}"; + }} +}} +""".format( + **version_info + ) + + generators_dir = os.path.join( + dirname(script_path), + "editor", + "Godot.NET.Sdk", + "Godot.SourceGenerators", + "Generated", + ) + os.makedirs(generators_dir, exist_ok=True) + + with open(os.path.join(generators_dir, "Common.Constants.cs"), "w") as f: + f.write(constants) + f.close() + def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): - # Generate SdkPackageVersions.props + # Generate SdkPackageVersions.props and VersionDocsUrl constant generate_sdk_package_versions() # Godot API diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index 733f1dbe34..c4aba577db 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -47,7 +47,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { for (const StringName &E : class_list) { ClassDB::ClassInfo *t = ClassDB::classes.getptr(E); - ERR_FAIL_COND(!t); + ERR_FAIL_NULL(t); if (t->api != p_api || !t->exposed) { continue; } diff --git a/modules/mono/config.py b/modules/mono/config.py index 9846d60c33..859d77b262 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,6 +1,6 @@ # Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "web", "ios"] # Eventually support for each them should be added back. -supported_platforms = ["windows", "macos", "linuxbsd", "android"] +supported_platforms = ["windows", "macos", "linuxbsd", "android", "ios"] def can_build(env, platform): diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9e23a27093..56e4fa53d0 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -121,16 +121,16 @@ void CSharpLanguage::init() { GLOBAL_DEF(PropertyInfo(Variant::INT, "dotnet/project/assembly_reload_attempts", PROPERTY_HINT_RANGE, "1,16,1,or_greater"), 3); #endif - gdmono = memnew(GDMono); - gdmono->initialize(); - #ifdef TOOLS_ENABLED - if (gdmono->is_runtime_initialized()) { - gdmono->initialize_load_assemblies(); - } - EditorNode::add_init_callback(&_editor_init_callback); #endif + + gdmono = memnew(GDMono); + + // Initialize only if the project uses C#. + if (gdmono->should_initialize()) { + gdmono->initialize(); + } } void CSharpLanguage::finish() { @@ -142,6 +142,10 @@ void CSharpLanguage::finalize() { return; } + if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown(); + } + finalizing = true; // Make sure all script binding gchandles are released before finalizing GDMono @@ -328,6 +332,11 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("/* */"); // delimited comment } +void CSharpLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const { + p_delimiters->push_back("///"); // single-line doc comment + p_delimiters->push_back("/** */"); // delimited doc comment +} + void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("' '"); // character literal p_delimiters->push_back("\" \""); // regular string literal @@ -392,10 +401,6 @@ Script *CSharpLanguage::create_script() const { return memnew(CSharpScript); } -bool CSharpLanguage::has_named_classes() const { - return false; -} - bool CSharpLanguage::supports_builtin_mode() const { return false; } @@ -435,6 +440,11 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { return "Collections.Dictionary"; } + if (p_var_type_name.begins_with(Variant::get_type_name(Variant::ARRAY) + "[")) { + String element_type = p_var_type_name.trim_prefix(Variant::get_type_name(Variant::ARRAY) + "[").trim_suffix("]"); + return "Collections.Array<" + variant_type_to_managed_name(element_type) + ">"; + } + if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) { return "Collections.Array"; } @@ -549,13 +559,13 @@ bool CSharpLanguage::handles_global_class_type(const String &p_type) const { String CSharpLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { Ref<CSharpScript> scr = ResourceLoader::load(p_path, get_type()); - if (!scr.is_valid() || !scr->valid || !scr->global_class) { - // Invalid script or the script is not a global class. - return String(); - } + // Always assign r_base_type and r_icon_path, even if the script + // is not a global one. In the case that it is not a global script, + // return an empty string AFTER assigning the return parameters. + // See GDScriptLanguage::get_global_class_name() in modules/gdscript/gdscript.cpp - String name = scr->class_name; - if (unlikely(name.is_empty())) { + if (!scr.is_valid() || !scr->valid) { + // Invalid script. return String(); } @@ -582,7 +592,8 @@ String CSharpLanguage::get_global_class_name(const String &p_path, String *r_bas *r_base_type = scr->get_instance_base_type(); } } - return name; + + return scr->global_class ? scr->class_name : String(); } String CSharpLanguage::debug_get_error() const { @@ -1978,24 +1989,31 @@ const Variant CSharpInstance::get_rpc_config() const { void CSharpInstance::notification(int p_notification, bool p_reversed) { if (p_notification == Object::NOTIFICATION_PREDELETE) { - // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). - // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed + if (base_ref_counted) { + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted wouldn't have reached 0 otherwise, since the managed side + // references it and Dispose() needs to be called to release it. + // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but + // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + return; + } + } else if (p_notification == Object::NOTIFICATION_PREDELETE_CLEANUP) { + // When NOTIFICATION_PREDELETE_CLEANUP is sent, we also take the chance to call Dispose(). + // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE_CLEANUP is guaranteed // to be sent at least once, which happens right before the call to the destructor. predelete_notified = true; if (base_ref_counted) { - // It's not safe to proceed if the owner derives RefCounted and the refcount reached 0. - // At this point, Dispose() was already called (manually or from the finalizer) so - // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the - // managed side references it and Dispose() needs to be called to release it. - // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but - // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted wouldn't have reached 0 otherwise, since the managed side + // references it and Dispose() needs to be called to release it. return; } - _call_notification(p_notification, p_reversed); - + // NOTIFICATION_PREDELETE_CLEANUP is not sent to scripts. + // After calling Dispose() the C# instance can no longer be used, + // so it should be the last thing we do. GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( gchandle.get_intptr(), /* okIfNull */ false); @@ -2295,6 +2313,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { bool tool = false; bool global_class = false; + bool abstract_class = false; // TODO: Use GDExtension godot_dictionary Array methods_array; @@ -2308,12 +2327,13 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { String icon_path; Ref<CSharpScript> base_script; GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( - p_script.ptr(), &class_name, &tool, &global_class, &icon_path, + p_script.ptr(), &class_name, &tool, &global_class, &abstract_class, &icon_path, &methods_array, &rpc_functions_dict, &signals_dict, &base_script); p_script->class_name = class_name; p_script->tool = tool; p_script->global_class = global_class; + p_script->abstract_class = abstract_class; p_script->icon_path = icon_path; p_script->rpc_config.clear(); @@ -2348,6 +2368,8 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { mi.arguments.push_back(arg_info); } + mi.flags = (uint32_t)method_info_dict["flags"]; + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } @@ -2401,7 +2423,7 @@ bool CSharpScript::can_instantiate() const { ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'. Make sure the script exists and contains a class definition with a name that matches the filename of the script exactly (it's case-sensitive)."); } - return valid && extra_cond; + return valid && !abstract_class && extra_cond; } StringName CSharpScript::get_instance_base_type() const { @@ -2597,6 +2619,18 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { return MethodInfo(); } +Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + ERR_FAIL_COND_V(!valid, Variant()); + + Variant ret; + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CallStatic(this, &p_method, p_args, p_argcount, &r_error, &ret); + if (ok) { + return ret; + } + + return Script::callp(p_method, p_args, p_argcount, r_error); +} + Error CSharpScript::reload(bool p_keep_state) { if (!reload_invalidated) { return OK; diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 33862016a4..e381f0e840 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -63,6 +63,7 @@ class CSharpScript : public Script { bool tool = false; bool global_class = false; + bool abstract_class = false; bool valid = false; bool reload_invalidated = false; @@ -188,6 +189,9 @@ public: bool is_valid() const override { return valid; } + bool is_abstract() const override { + return abstract_class; + } bool inherits_script(const Ref<Script> &p_script) const override; @@ -199,6 +203,7 @@ public: void get_script_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; MethodInfo get_method_info(const StringName &p_method) const override; + Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; int get_member_line(const StringName &p_member) const override; @@ -414,6 +419,7 @@ public: void get_reserved_words(List<String> *p_words) const override; bool is_control_flow_keyword(String p_keyword) const override; void get_comment_delimiters(List<String> *p_delimiters) const override; + void get_doc_comment_delimiters(List<String> *p_delimiters) const override; void get_string_delimiters(List<String> *p_delimiters) const override; bool is_using_templates() override; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override; @@ -424,7 +430,9 @@ public: } String validate_path(const String &p_path) const override; Script *create_script() const override; - bool has_named_classes() const override; +#ifndef DISABLE_DEPRECATED + virtual bool has_named_classes() const override { return false; } +#endif bool supports_builtin_mode() const override; /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; diff --git a/modules/mono/editor/Godot.NET.Sdk/.gitignore b/modules/mono/editor/Godot.NET.Sdk/.gitignore new file mode 100644 index 0000000000..55ec4bcc64 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/.gitignore @@ -0,0 +1,2 @@ +# Generated sources directories +Godot.SourceGenerators/Generated diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 663eb14f07..ad3a10ba49 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -29,5 +29,7 @@ <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk"> <Link>Sdk\SdkPackageVersions.props</Link> </None> + <None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" /> + <None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 6677d77559..b6c72bce9d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -4,8 +4,6 @@ <PropertyGroup> <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. --> <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk> - - <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid> </PropertyGroup> <PropertyGroup> @@ -62,6 +60,18 @@ <!-- Auto-detect the target Godot platform if it was not specified. --> <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' "> + <GodotTargetPlatform Condition=" $(RuntimeIdentifier.StartsWith('ios')) ">ios</GodotTargetPlatform> + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('android')) ">android</GodotTargetPlatform> + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('browser')) ">web</GodotTargetPlatform> + + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('linux')) ">linuxbsd</GodotTargetPlatform> + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('freebsd')) ">linuxbsd</GodotTargetPlatform> + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('osx')) ">macos</GodotTargetPlatform> + <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('win')) ">windows</GodotTargetPlatform> + </PropertyGroup> + + <!-- Auto-detect the target Godot platform if it was not specified and there's no runtime identifier information. --> + <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' "> <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform> <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform> <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">macos</GodotTargetPlatform> @@ -99,4 +109,6 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> + + <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " /> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index 859ea52c93..29ef76a5e8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -2,11 +2,6 @@ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " /> <PropertyGroup> - <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid> - <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids> - </PropertyGroup> - - <PropertyGroup> <!-- Define constant to determine whether the real_t type in Godot is double precision or not. By default this is false, like the official Godot builds. If someone is using a custom @@ -25,4 +20,8 @@ <PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" /> <PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " /> </ItemGroup> + + <!-- iOS-specific build targets --> + <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.targets" Condition=" '$(GodotTargetPlatform)' == 'ios' " /> + </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props new file mode 100644 index 0000000000..e3c953ccac --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props @@ -0,0 +1,8 @@ +<Project> + <PropertyGroup> + <PublishAot>true</PublishAot> + <PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack> + <UseNativeAOTRuntime>true</UseNativeAOTRuntime> + <TrimmerSingleWarn>false</TrimmerSingleWarn> + </PropertyGroup> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets new file mode 100644 index 0000000000..d8129a6652 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets @@ -0,0 +1,58 @@ +<Project> + <ItemGroup> + <TrimmerRootAssembly Include="GodotSharp" /> + <TrimmerRootAssembly Include="$(TargetName)" /> + <LinkerArg Include="-install_name '@rpath/$(TargetName)$(NativeBinaryExt)'" /> + </ItemGroup> + + <PropertyGroup> + <LinkStandardCPlusPlusLibrary>true</LinkStandardCPlusPlusLibrary> + <FindXCode Condition=" '$(XCodePath)' == '' and '$([MSBuild]::IsOsPlatform(OSX))' ">true</FindXCode> + <XCodePath Condition=" '$(XCodePath)' == '' ">/Applications/Xcode.app/Contents/Developer</XCodePath> + <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath> + </PropertyGroup> + + <Target Name="PrepareBeforeIlcCompile" + BeforeTargets="IlcCompile"> + + <Copy SourceFiles="%(ResolvedRuntimePack.PackageDirectory)/runtimes/$(RuntimeIdentifier)/native/icudt.dat" DestinationFolder="$(PublishDir)"/> + + <!-- We need to find the path to Xcode so we can set manual linker args to the correct SDKs + Once https://github.com/dotnet/runtime/issues/88737 is released, we can take this out + --> + + <Exec Command="xcrun xcode-select -p" ConsoleToMSBuild="true" Condition=" '$(FindXCode)' == 'true' "> + <Output TaskParameter="ConsoleOutput" PropertyName="XcodeSelect" /> + </Exec> + + <PropertyGroup Condition=" '$(FindXCode)' == 'true' "> + <XCodePath>$(XcodeSelect)</XCodePath> + <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath> + </PropertyGroup> + + <Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/> + + <ItemGroup> + <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22" + Condition=" $(RuntimeIdentifier.Contains('simulator')) "/> + <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22" + Condition=" !$(RuntimeIdentifier.Contains('simulator')) "/> + </ItemGroup> + + </Target> + + <Target Name="FixSymbols" + AfterTargets="Publish"> + + <RemoveDir Directories="$(PublishDir)$(TargetName).framework.dSYM"/> + + <!-- create-xcframework (called from the export plugin wants the symbol files in a directory + with a slightly different name from the one created by dotnet publish, so we copy them over + to the correctly-named directory --> + <ItemGroup> + <SymbolFiles Include="$(NativeBinary).dsym\**\*.*"/> + </ItemGroup> + <Copy SourceFiles="@(SymbolFiles)" DestinationFolder="$(PublishDir)$(TargetName).framework.dSYM"/> + </Target> + +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs new file mode 100644 index 0000000000..3021f57115 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/NestedClass.cs @@ -0,0 +1,22 @@ +using System; + +namespace Godot.SourceGenerators.Sample; + +public partial class NestedClass : GodotObject +{ + public partial class NestedClass2 : GodotObject + { + public partial class NestedClass3 : GodotObject + { + [Signal] + public delegate void MySignalEventHandler(string str, int num); + + [Export] private String field_String = "foo"; + [Export] private String property_String { get; set; } = "foo"; + + private void Method() + { + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs new file mode 100644 index 0000000000..0c374169b9 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllReadOnly.cs @@ -0,0 +1,10 @@ +namespace Godot.SourceGenerators.Sample +{ + public partial class AllReadOnly : GodotObject + { + public readonly string readonly_field = "foo"; + public string readonly_auto_property { get; } = "foo"; + public string readonly_property { get => "foo"; } + public string initonly_auto_property { get; init; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs new file mode 100644 index 0000000000..14a1802330 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/AllWriteOnly.cs @@ -0,0 +1,10 @@ +using System; + +namespace Godot.SourceGenerators.Sample +{ + public partial class AllWriteOnly : GodotObject + { + bool writeonly_backing_field = false; + public bool writeonly_property { set => writeonly_backing_field = value; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs new file mode 100644 index 0000000000..f556bdc7e4 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/OneWayProperties/MixedReadOnlyWriteOnly.cs @@ -0,0 +1,13 @@ +namespace Godot.SourceGenerators.Sample +{ + public partial class MixedReadonlyWriteOnly : GodotObject + { + public readonly string readonly_field = "foo"; + public string readonly_auto_property { get; } = "foo"; + public string readonly_property { get => "foo"; } + public string initonly_auto_property { get; init; } + + bool writeonly_backing_field = false; + public bool writeonly_property { set => writeonly_backing_field = value; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 72614dd7e0..147ef852b3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -5,8 +5,10 @@ using Microsoft.CodeAnalysis.Diagnostics; namespace Godot.SourceGenerators { - public static class Common + public static partial class Common { + private static readonly string _helpLinkFormat = $"{VersionDocsUrl}/tutorials/scripting/c_sharp/diagnostics/{{0}}.html"; + public static void ReportNonPartialGodotScriptClass( GeneratorExecutionContext context, ClassDeclarationSyntax cds, INamedTypeSymbol symbol @@ -14,9 +16,9 @@ namespace Godot.SourceGenerators { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedNameOmitGlobal()}' which is a subclass of '{GodotClasses.GodotObject}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' that derives from '{GodotClasses.GodotObject}'"; - string description = $"{message}. Subclasses of '{GodotClasses.GodotObject}' " + + string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' " + "must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( @@ -26,7 +28,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0001")), cds.GetLocation(), cds.SyntaxTree.FilePath)); } @@ -46,9 +49,9 @@ namespace Godot.SourceGenerators string message = $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + - $"which contains one or more subclasses of '{GodotClasses.GodotObject}'"; + $"which contains nested classes that derive from '{GodotClasses.GodotObject}'"; - string description = $"{message}. Subclasses of '{GodotClasses.GodotObject}' and their " + + string description = $"{message}. Classes that derive from '{GodotClasses.GodotObject}' and their " + "containing types must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( @@ -58,7 +61,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0002")), outerTypeDeclSyntax.GetLocation(), outerTypeDeclSyntax.SyntaxTree.FilePath)); } @@ -85,7 +89,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0101")), location, location?.SourceTree?.FilePath)); } @@ -111,7 +116,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0102")), location, location?.SourceTree?.FilePath)); } @@ -139,7 +145,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD1003")), location, location?.SourceTree?.FilePath)); } @@ -163,7 +170,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0104")), location, location?.SourceTree?.FilePath)); } @@ -189,7 +197,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0105")), location, location?.SourceTree?.FilePath)); } @@ -215,6 +224,32 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0106")), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportOnlyNodesShouldExportNodes( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"Types not derived from Node should not export Node {(isField ? "fields" : "properties")}"; + + string description = $"{message}. Node export is only supported in Node-derived classes."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0107", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, description), location, location?.SourceTree?.FilePath)); @@ -240,7 +275,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0201")), location, location?.SourceTree?.FilePath)); } @@ -264,7 +300,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0202")), location, location?.SourceTree?.FilePath)); } @@ -288,7 +325,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0203")), location, location?.SourceTree?.FilePath)); } @@ -300,7 +338,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument."); + "The generic type argument must be a Variant compatible type. Use a Variant compatible type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0301")); public static void ReportGenericTypeArgumentMustBeVariant( SyntaxNodeAnalysisContext context, @@ -319,7 +358,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0301")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -331,7 +371,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0302")); public static void ReportGenericTypeParameterMustBeVariantAnnotated( SyntaxNodeAnalysisContext context, @@ -349,7 +390,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0302")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -361,7 +403,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The generic type argument must be a Variant type. Use a Variant type as the generic type argument."); + "The generic type argument must be a Variant type. Use a Variant type as the generic type argument.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0303")); public static void ReportTypeArgumentParentSymbolUnhandled( SyntaxNodeAnalysisContext context, @@ -380,7 +423,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0303")), typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } @@ -388,11 +432,12 @@ namespace Godot.SourceGenerators public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = new DiagnosticDescriptor(id: "GD0401", title: "The class must derive from GodotObject or a derived class", - messageFormat: "The class '{0}' must derive from GodotObject or a derived class.", + messageFormat: "The class '{0}' must derive from GodotObject or a derived class", category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute."); + "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); public static void ReportGlobalClassMustDeriveFromGodotObject( SyntaxNodeAnalysisContext context, @@ -410,7 +455,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")), classSyntax.GetLocation(), classSyntax.SyntaxTree.FilePath)); } @@ -422,7 +468,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute."); + "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0401")); public static void ReportGlobalClassMustNotBeGeneric( SyntaxNodeAnalysisContext context, @@ -440,7 +487,8 @@ namespace Godot.SourceGenerators category: "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, - description), + description, + helpLinkUri: string.Format(_helpLinkFormat, "GD0402")), classSyntax.GetLocation(), classSyntax.SyntaxTree.FilePath)); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index b6ea4b8e88..c7fd45238d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -32,7 +32,7 @@ namespace Godot.SourceGenerators disabledGenerators != null && disabledGenerators.Split(';').Contains(generatorName)); - public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName) + public static bool InheritsFrom(this ITypeSymbol? symbol, string assemblyName, string typeFullName) { while (symbol != null) { @@ -303,11 +303,6 @@ namespace Godot.SourceGenerators { foreach (var property in properties) { - // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. - // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable. - if (property.IsWriteOnly || property.IsReadOnly || property.SetMethod!.IsInitOnly) - continue; - var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache); if (marshalType == null) @@ -325,10 +320,6 @@ namespace Godot.SourceGenerators foreach (var field in fields) { // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. - // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. - if (field.IsReadOnly) - continue; - var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache); if (marshalType == null) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index 5ea0ca53c3..7232e4d7d7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -105,16 +105,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -125,7 +129,7 @@ namespace Godot.SourceGenerators var members = symbol.GetMembers(); var methodSymbols = members - .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) + .Where(s => s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) .Cast<IMethodSymbol>() .Where(m => m.MethodKind == MethodKind.Ordinary); @@ -221,6 +225,29 @@ namespace Godot.SourceGenerators source.Append(" }\n"); } + // Generate InvokeGodotClassStaticMethod + + var godotClassStaticMethods = godotClassMethods.Where(m => m.Method.IsStatic).ToArray(); + + if (godotClassStaticMethods.Length > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" internal new static bool InvokeGodotClassStaticMethod(in godot_string_name method, "); + source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n"); + + foreach (var method in godotClassStaticMethods) + { + GenerateMethodInvoker(method, source); + } + + source.Append(" ret = default;\n"); + source.Append(" return false;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + // Generate HasGodotClassMethod if (distinctMethodNames.Length > 0) @@ -356,7 +383,14 @@ namespace Godot.SourceGenerators arguments = null; } - return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments, + MethodFlags flags = MethodFlags.Default; + + if (method.Method.IsStatic) + { + flags |= MethodFlags.Static; + } + + return new MethodInfo(method.Method.Name, returnVal, flags, arguments, defaultArguments: null); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 94d8696717..de44ada6de 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -91,16 +91,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -212,31 +216,37 @@ namespace Godot.SourceGenerators } // Generate GetGodotClassPropertyValue + bool allPropertiesAreWriteOnly = godotClassFields.Length == 0 && godotClassProperties.All(pi => pi.PropertySymbol.IsWriteOnly); - source.Append(" /// <inheritdoc/>\n"); - source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); - source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); - source.Append("out godot_variant value)\n {\n"); - - isFirstEntry = true; - foreach (var property in godotClassProperties) + if (!allPropertiesAreWriteOnly) { - GeneratePropertyGetter(property.PropertySymbol.Name, - property.PropertySymbol.Type, property.Type, source, isFirstEntry); - isFirstEntry = false; - } + source.Append(" /// <inheritdoc/>\n"); + source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n"); + source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("out godot_variant value)\n {\n"); - foreach (var field in godotClassFields) - { - GeneratePropertyGetter(field.FieldSymbol.Name, - field.FieldSymbol.Type, field.Type, source, isFirstEntry); - isFirstEntry = false; - } + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + if (property.PropertySymbol.IsWriteOnly) + continue; - source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + GeneratePropertyGetter(property.PropertySymbol.Name, + property.PropertySymbol.Type, property.Type, source, isFirstEntry); + isFirstEntry = false; + } - source.Append(" }\n"); + foreach (var field in godotClassFields) + { + GeneratePropertyGetter(field.FieldSymbol.Name, + field.FieldSymbol.Type, field.Type, source, isFirstEntry); + isFirstEntry = false; + } + source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + + source.Append(" }\n"); + } // Generate GetGodotPropertyList const string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 4df16d05f0..253e24f092 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -88,16 +88,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -166,6 +170,15 @@ namespace Godot.SourceGenerators continue; } + if (marshalType == MarshalType.GodotObjectOrDerived) + { + if (!symbol.InheritsFrom("GodotSharp", "Godot.Node") && + propertyType.InheritsFrom("GodotSharp", "Godot.Node")) + { + Common.ReportOnlyNodesShouldExportNodes(context, property); + } + } + var propertyDeclarationSyntax = property.DeclaringSyntaxReferences .Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault(); @@ -261,6 +274,15 @@ namespace Godot.SourceGenerators continue; } + if (marshalType == MarshalType.GodotObjectOrDerived) + { + if (!symbol.InheritsFrom("GodotSharp", "Godot.Node") && + fieldType.InheritsFrom("GodotSharp", "Godot.Node")) + { + Common.ReportOnlyNodesShouldExportNodes(context, field); + } + } + EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences .Select(r => r.GetSyntax()) .OfType<VariableDeclaratorSyntax>() diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 231a7be021..0bc58c2b47 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -91,16 +91,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -119,8 +123,14 @@ namespace Godot.SourceGenerators .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) .Cast<IFieldSymbol>(); - var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); - var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable. + var godotClassProperties = propertySymbols.Where(property => !(property.IsReadOnly || property.IsWriteOnly || property.SetMethod!.IsInitOnly)) + .WhereIsGodotCompatibleType(typeCache) + .ToArray(); + var godotClassFields = fieldSymbols.Where(property => !property.IsReadOnly) + .WhereIsGodotCompatibleType(typeCache) + .ToArray(); var signalDelegateSymbols = members .Where(s => s.Kind == SymbolKind.NamedType) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 8f2774d5ae..5409d1a961 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -100,16 +100,20 @@ namespace Godot.SourceGenerators if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index f3c8e89dff..04ea46e9ef 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -23,6 +23,13 @@ namespace GodotTools.ProjectEditor var mainGroup = root.AddPropertyGroup(); mainGroup.AddProperty("TargetFramework", "net6.0"); + + var net7 = mainGroup.AddProperty("TargetFramework", "net7.0"); + net7.Condition = " '$(GodotTargetPlatform)' == 'android' "; + + var net8 = mainGroup.AddProperty("TargetFramework", "net8.0"); + net8.Condition = " '$(GodotTargetPlatform)' == 'ios' "; + mainGroup.AddProperty("EnableDynamicLoading", "true"); string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 7b1d5c228a..1d17f3d4f5 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using Microsoft.Build.Construction; +using Microsoft.Build.Locator; namespace GodotTools.ProjectEditor { @@ -19,15 +21,18 @@ namespace GodotTools.ProjectEditor public static class ProjectUtils { - public static void MSBuildLocatorRegisterDefaults(out Version version, out string path) + public static void MSBuildLocatorRegisterLatest(out Version version, out string path) { - var instance = Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults(); + var instance = MSBuildLocator.QueryVisualStudioInstances() + .OrderByDescending(x => x.Version) + .First(); + MSBuildLocator.RegisterInstance(instance); version = instance.Version; path = instance.MSBuildPath; } public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath) - => Microsoft.Build.Locator.MSBuildLocator.RegisterMSBuildPath(msbuildPath); + => MSBuildLocator.RegisterMSBuildPath(msbuildPath); public static MSBuildProject Open(string path) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs new file mode 100644 index 0000000000..6e0c63dd43 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace GodotTools.Build +{ + public class BuildDiagnostic + { + public enum DiagnosticType + { + Hidden, + Info, + Warning, + Error, + } + + public DiagnosticType Type { get; set; } + public string? File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string? Code { get; set; } + public string Message { get; set; } = ""; + public string? ProjectFile { get; set; } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 312c65e364..7cf98b8f1f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; @@ -40,9 +41,6 @@ namespace GodotTools.Build plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel); } - public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); - private static string GetLogFilePath(BuildInfo buildInfo) { return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); @@ -70,7 +68,7 @@ namespace GodotTools.Build { BuildStarted?.Invoke(buildInfo); - // Required in order to update the build tasks list + // Required in order to update the build tasks list. Internal.GodotMainIteration(); try @@ -165,7 +163,7 @@ namespace GodotTools.Build { BuildStarted?.Invoke(buildInfo); - // Required in order to update the build tasks list + // Required in order to update the build tasks list. Internal.GodotMainIteration(); try @@ -209,17 +207,19 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Project)) return true; // No project to build. - using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1); - - pr.Step("Building project", 0); + bool success; + using (var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1)) + { + pr.Step("Building project", 0); + success = Build(buildInfo); + } - if (!Build(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to build project"); - return false; } - return true; + return success; } private static bool CleanProjectBlocking(BuildInfo buildInfo) @@ -227,32 +227,36 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Project)) return true; // No project to clean. - using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); - - pr.Step("Cleaning project", 0); + bool success; + using (var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1)) + { + pr.Step("Cleaning project", 0); + success = Build(buildInfo); + } - if (!Build(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to clean project"); - return false; } - return true; + return success; } private static bool PublishProjectBlocking(BuildInfo buildInfo) { - using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1); - - pr.Step("Running dotnet publish", 0); + bool success; + using (var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1)) + { + pr.Step("Running dotnet publish", 0); + success = Publish(buildInfo); + } - if (!Publish(buildInfo)) + if (!success) { ShowBuildErrorDialog("Failed to publish .NET project"); - return false; } - return true; + return success; } private static BuildInfo CreateBuildInfo( @@ -320,6 +324,45 @@ namespace GodotTools.Build ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration, platform, runtimeIdentifier, publishOutputDir, includeDebugSymbols)); + public static bool GenerateXCFrameworkBlocking( + List<string> outputPaths, + string xcFrameworkPath) + { + using var pr = new EditorProgress("generate_xcframework", "Generating XCFramework...", 1); + + pr.Step("Running xcodebuild -create-xcframework", 0); + + if (!GenerateXCFramework(outputPaths, xcFrameworkPath)) + { + ShowBuildErrorDialog("Failed to generate XCFramework"); + return false; + } + + return true; + } + + private static bool GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath) + { + // Required in order to update the build tasks list. + Internal.GodotMainIteration(); + + try + { + int exitCode = BuildSystem.GenerateXCFramework(outputPaths, xcFrameworkPath, StdOutputReceived, StdErrorReceived); + + if (exitCode != 0) + PrintVerbose( + $"xcodebuild create-xcframework exited with code: {exitCode}."); + + return exitCode == 0; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return false; + } + } + public static bool EditorBuildCallback() { if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index 54f7ed02f5..f9e85c36e5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,425 +1,150 @@ using Godot; -using System; -using System.Diagnostics.CodeAnalysis; -using GodotTools.Internals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; +using static GodotTools.Internals.Globals; + +#nullable enable namespace GodotTools.Build { - public partial class BuildOutputView : VBoxContainer, ISerializationListener + public partial class BuildOutputView : HBoxContainer { - [Serializable] - private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization - { - public bool Warning { get; set; } - public string File { get; set; } - public int Line { get; set; } - public int Column { get; set; } - public string Code { get; set; } - public string Message { get; set; } - public string ProjectFile { get; set; } - } - - [Signal] - public delegate void BuildStateChangedEventHandler(); - - public bool HasBuildExited { get; private set; } = false; +#nullable disable + private RichTextLabel _log; - public BuildResult? BuildResult { get; private set; } = null; + private Button _clearButton; + private Button _copyButton; +#nullable enable - public int ErrorCount { get; private set; } = 0; - - public int WarningCount { get; private set; } = 0; - - public bool ErrorsVisible { get; set; } = true; - public bool WarningsVisible { get; set; } = true; - - public Texture2D BuildStateIcon + public void Append(string text) { - get - { - if (!HasBuildExited) - return GetThemeIcon("Stop", "EditorIcons"); - - if (BuildResult == Build.BuildResult.Error) - return GetThemeIcon("Error", "EditorIcons"); - - if (WarningCount > 1) - return GetThemeIcon("Warning", "EditorIcons"); - - return null; - } + _log.AddText(text); } - public bool LogVisible + public void Clear() { - set => _buildLog.Visible = value; + _log.Clear(); } - // TODO Use List once we have proper serialization. - private Godot.Collections.Array<BuildIssue> _issues = new(); - private ItemList _issuesList; - private PopupMenu _issuesListContextMenu; - private TextEdit _buildLog; - private BuildInfo _buildInfo; - - private readonly object _pendingBuildLogTextLock = new object(); - [NotNull] private string _pendingBuildLogText = string.Empty; - - private void LoadIssuesFromFile(string csvFile) + private void CopyRequested() { - using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); - - if (file == null) - return; + string text = _log.GetSelectedText(); - while (!file.EofReached()) - { - string[] csvColumns = file.GetCsvLine(); + if (string.IsNullOrEmpty(text)) + text = _log.GetParsedText(); - if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) - return; - - if (csvColumns.Length != 7) - { - GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); - continue; - } - - var issue = new BuildIssue - { - Warning = csvColumns[0] == "warning", - File = csvColumns[1], - Line = int.Parse(csvColumns[2]), - Column = int.Parse(csvColumns[3]), - Code = csvColumns[4], - Message = csvColumns[5], - ProjectFile = csvColumns[6] - }; - - if (issue.Warning) - WarningCount += 1; - else - ErrorCount += 1; - - _issues.Add(issue); - } + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); } - private void IssueActivated(long idx) + public override void _Ready() { - if (idx < 0 || idx >= _issuesList.ItemCount) - throw new ArgumentOutOfRangeException(nameof(idx), "Item list index out of range."); - - // Get correct issue idx from issue list - int issueIndex = (int)_issuesList.GetItemMetadata((int)idx); - - if (issueIndex < 0 || issueIndex >= _issues.Count) - throw new InvalidOperationException("Issue index out of range."); - - BuildIssue issue = _issues[issueIndex]; + Name = "Output".TTR(); - if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) - return; - - string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ? - issue.ProjectFile.GetBaseDir() : - _buildInfo.Solution.GetBaseDir(); - - string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); - - if (!File.Exists(file)) - return; - - file = ProjectSettings.LocalizePath(file); - - if (file.StartsWith("res://")) + var vbLeft = new VBoxContainer { - var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); - - // Godot's ScriptEditor.Edit is 0-based but the issue lines are 1-based. - if (script != null && Internal.ScriptEditorEdit(script, issue.Line - 1, issue.Column - 1)) - Internal.EditorNodeShowScriptScreen(); - } - } - - public void UpdateIssuesList() - { - _issuesList.Clear(); + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); - using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) - using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) + // Log - Rich Text Label. + _log = new RichTextLabel { - for (int i = 0; i < _issues.Count; i++) - { - BuildIssue issue = _issues[i]; - - if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) - continue; - - string tooltip = string.Empty; - tooltip += $"Message: {issue.Message}"; - - if (!string.IsNullOrEmpty(issue.Code)) - tooltip += $"\nCode: {issue.Code}"; - - tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; - - string text = string.Empty; - - if (!string.IsNullOrEmpty(issue.File)) - { - text += $"{issue.File}({issue.Line},{issue.Column}): "; - - tooltip += $"\nFile: {issue.File}"; - tooltip += $"\nLine: {issue.Line}"; - tooltip += $"\nColumn: {issue.Column}"; - } - - if (!string.IsNullOrEmpty(issue.ProjectFile)) - tooltip += $"\nProject: {issue.ProjectFile}"; - - text += issue.Message; - - int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); - string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); - _issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); - - int index = _issuesList.ItemCount - 1; - _issuesList.SetItemTooltip(index, tooltip); - _issuesList.SetItemMetadata(index, i); - } - } - } - - private void BuildLaunchFailed(BuildInfo buildInfo, string cause) - { - HasBuildExited = true; - BuildResult = Build.BuildResult.Error; - - _issuesList.Clear(); - - var issue = new BuildIssue { Message = cause, Warning = false }; - - ErrorCount += 1; - _issues.Add(issue); - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildStarted(BuildInfo buildInfo) - { - _buildInfo = buildInfo; - HasBuildExited = false; - - _issues.Clear(); - WarningCount = 0; - ErrorCount = 0; - _buildLog.Text = string.Empty; - - UpdateIssuesList(); - - EmitSignal(nameof(BuildStateChanged)); - } - - private void BuildFinished(BuildResult result) - { - HasBuildExited = true; - BuildResult = result; - - LoadIssuesFromFile(Path.Combine(_buildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + BbcodeEnabled = true, + ScrollFollowing = true, + SelectionEnabled = true, + ContextMenuEnabled = true, + FocusMode = FocusModeEnum.Click, + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + DeselectOnFocusLossEnabled = false, - UpdateIssuesList(); + }; + vbLeft.AddChild(_log); - EmitSignal(nameof(BuildStateChanged)); - } + var vbRight = new VBoxContainer(); + AddChild(vbRight); - private void UpdateBuildLogText() - { - lock (_pendingBuildLogTextLock) + // Tools grid + var hbTools = new HBoxContainer { - _buildLog.Text += _pendingBuildLogText; - _pendingBuildLogText = string.Empty; - ScrollToLastNonEmptyLogLine(); - } - } - - private void StdOutputReceived(string text) - { - lock (_pendingBuildLogTextLock) - { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); - private void StdErrorReceived(string text) - { - lock (_pendingBuildLogTextLock) + // Clear. + _clearButton = new Button { - if (_pendingBuildLogText.Length == 0) - CallDeferred(nameof(UpdateBuildLogText)); - _pendingBuildLogText += text + "\n"; - } - } + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); - private void ScrollToLastNonEmptyLogLine() - { - int line; - for (line = _buildLog.GetLineCount(); line > 0; line--) + // Copy. + _copyButton = new Button { - string lineText = _buildLog.GetLine(line); - - if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) - break; - } - - _buildLog.SetCaretLine(line); - } - - public void RestartBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build already started."); - - BuildManager.RestartBuild(this); - } - - public void StopBuild() - { - if (!HasBuildExited) - throw new InvalidOperationException("Build is not in progress."); + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopyRequested; + hbTools.AddChild(_copyButton); - BuildManager.StopBuild(this); + UpdateTheme(); } - private enum IssuesContextMenuOption + public override void _Notification(int what) { - Copy - } + base._Notification(what); - private void IssuesListContextOptionPressed(long id) - { - switch ((IssuesContextMenuOption)id) + if (what == NotificationThemeChanged) { - case IssuesContextMenuOption.Copy: - { - // We don't allow multi-selection but just in case that changes later... - string text = null; - - foreach (int issueIndex in _issuesList.GetSelectedItems()) - { - if (text != null) - text += "\n"; - text += _issuesList.GetItemText(issueIndex); - } - - if (text != null) - DisplayServer.ClipboardSet(text); - break; - } - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option"); + UpdateTheme(); } } - private void IssuesListClicked(long index, Vector2 atPosition, long mouseButtonIndex) + private void UpdateTheme() { - if (mouseButtonIndex != (long)MouseButton.Right) - { + // Nodes will be null until _Ready is called. + if (_log == null) return; - } - - _ = index; // Unused - - _issuesListContextMenu.Clear(); - _issuesListContextMenu.Size = new Vector2I(1, 1); - - if (_issuesList.IsAnythingSelected()) - { - // Add menu entries for the selected item - _issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), - label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); - } - - if (_issuesListContextMenu.ItemCount > 0) - { - _issuesListContextMenu.Position = (Vector2I)(_issuesList.GlobalPosition + atPosition); - _issuesListContextMenu.Popup(); - } - } - - public override void _Ready() - { - base._Ready(); - SizeFlagsVertical = SizeFlags.ExpandFill; + var normalFont = GetThemeFont("output_source", "EditorFonts"); + if (normalFont != null) + _log.AddThemeFontOverride("normal_font", normalFont); - var hsc = new HSplitContainer - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - SizeFlagsVertical = SizeFlags.ExpandFill - }; - AddChild(hsc); + var boldFont = GetThemeFont("output_source_bold", "EditorFonts"); + if (boldFont != null) + _log.AddThemeFontOverride("bold_font", boldFont); - _issuesList = new ItemList - { - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the build log - }; - _issuesList.ItemActivated += IssueActivated; - _issuesList.AllowRmbSelect = true; - _issuesList.ItemClicked += IssuesListClicked; - hsc.AddChild(_issuesList); + var italicsFont = GetThemeFont("output_source_italic", "EditorFonts"); + if (italicsFont != null) + _log.AddThemeFontOverride("italics_font", italicsFont); - _issuesListContextMenu = new PopupMenu(); - _issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; - _issuesList.AddChild(_issuesListContextMenu); + var boldItalicsFont = GetThemeFont("output_source_bold_italic", "EditorFonts"); + if (boldItalicsFont != null) + _log.AddThemeFontOverride("bold_italics_font", boldItalicsFont); - _buildLog = new TextEdit - { - Editable = false, - SizeFlagsVertical = SizeFlags.ExpandFill, - SizeFlagsHorizontal = SizeFlags.ExpandFill // Avoid being squashed by the issues list - }; - hsc.AddChild(_buildLog); + var monoFont = GetThemeFont("output_source_mono", "EditorFonts"); + if (monoFont != null) + _log.AddThemeFontOverride("mono_font", monoFont); - AddBuildEventListeners(); - } - - private void AddBuildEventListeners() - { - BuildManager.BuildLaunchFailed += BuildLaunchFailed; - BuildManager.BuildStarted += BuildStarted; - BuildManager.BuildFinished += BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived += StdOutputReceived; - BuildManager.StdErrorReceived += StdErrorReceived; - } + // Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines. + // This also better matches terminal output, which does not use any form of padding. + _log.AddThemeConstantOverride("text_highlight_h_padding", 0); + _log.AddThemeConstantOverride("text_highlight_v_padding", 0); - public void OnBeforeSerialize() - { - // In case it didn't update yet. We don't want to have to serialize any pending output. - UpdateBuildLogText(); - - // NOTE: - // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. - // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. - BuildManager.BuildLaunchFailed -= BuildLaunchFailed; - BuildManager.BuildStarted -= BuildStarted; - BuildManager.BuildFinished -= BuildFinished; - // StdOutput/Error can be received from different threads, so we need to use CallDeferred - BuildManager.StdOutputReceived -= StdOutputReceived; - BuildManager.StdErrorReceived -= StdErrorReceived; - } + int font_size = GetThemeFontSize("output_source_size", "EditorFonts"); + _log.AddThemeFontSizeOverride("normal_font_size", font_size); + _log.AddThemeFontSizeOverride("bold_font_size", font_size); + _log.AddThemeFontSizeOverride("italics_font_size", font_size); + _log.AddThemeFontSizeOverride("mono_font_size", font_size); - public void OnAfterDeserialize() - { - AddBuildEventListeners(); // Re-add them + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs new file mode 100644 index 0000000000..9c165e5767 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs @@ -0,0 +1,40 @@ +using Godot; + +#nullable enable + +namespace GodotTools.Build +{ + public class BuildProblemsFilter + { + public BuildDiagnostic.DiagnosticType Type { get; } + + public Button ToggleButton { get; } + + private int _problemsCount; + + public int ProblemsCount + { + get => _problemsCount; + set + { + _problemsCount = value; + ToggleButton.Text = _problemsCount.ToString(); + } + } + + public bool IsActive => ToggleButton.ButtonPressed; + + public BuildProblemsFilter(BuildDiagnostic.DiagnosticType type) + { + Type = type; + ToggleButton = new Button + { + ToggleMode = true, + ButtonPressed = true, + Text = "0", + FocusMode = Control.FocusModeEnum.None, + ThemeTypeVariation = "EditorLogFilterButton", + }; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs new file mode 100644 index 0000000000..b23b3f42ef --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs @@ -0,0 +1,694 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Godot; +using GodotTools.Internals; +using static GodotTools.Internals.Globals; +using FileAccess = Godot.FileAccess; + +#nullable enable + +namespace GodotTools.Build +{ + public partial class BuildProblemsView : HBoxContainer + { +#nullable disable + private Button _clearButton; + private Button _copyButton; + + private Button _toggleLayoutButton; + + private Button _showSearchButton; + private LineEdit _searchBox; +#nullable enable + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, BuildProblemsFilter> _filtersByType = new(); + +#nullable disable + private Tree _problemsTree; + private PopupMenu _problemsContextMenu; +#nullable enable + + public enum ProblemsLayout { List, Tree } + private ProblemsLayout _layout = ProblemsLayout.Tree; + + private readonly List<BuildDiagnostic> _diagnostics = new(); + + public int TotalDiagnosticCount => _diagnostics.Count; + + private readonly Dictionary<BuildDiagnostic.DiagnosticType, int> _problemCountByType = new(); + + public int WarningCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Warning); + + public int ErrorCount => + GetProblemCountForType(BuildDiagnostic.DiagnosticType.Error); + + private int GetProblemCountForType(BuildDiagnostic.DiagnosticType type) + { + if (!_problemCountByType.TryGetValue(type, out int count)) + { + count = _diagnostics.Count(d => d.Type == type); + _problemCountByType[type] = count; + } + + return count; + } + + private static IEnumerable<BuildDiagnostic> ReadDiagnosticsFromFile(string csvFile) + { + using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); + + if (file == null) + yield break; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) + yield break; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var diagnostic = new BuildDiagnostic + { + Type = csvColumns[0] switch + { + "warning" => BuildDiagnostic.DiagnosticType.Warning, + "error" or _ => BuildDiagnostic.DiagnosticType.Error, + }, + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6], + }; + + // If there's no ProjectFile but the File is a csproj, then use that. + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && + !string.IsNullOrEmpty(diagnostic.File) && + diagnostic.File.EndsWith(".csproj")) + { + diagnostic.ProjectFile = diagnostic.File; + } + + yield return diagnostic; + } + } + + public void SetDiagnosticsFromFile(string csvFile) + { + var diagnostics = ReadDiagnosticsFromFile(csvFile); + SetDiagnostics(diagnostics); + } + + public void SetDiagnostics(IEnumerable<BuildDiagnostic> diagnostics) + { + _diagnostics.Clear(); + _problemCountByType.Clear(); + + _diagnostics.AddRange(diagnostics); + UpdateProblemsView(); + } + + public void Clear() + { + _problemsTree.Clear(); + _diagnostics.Clear(); + _problemCountByType.Clear(); + + UpdateProblemsView(); + } + + private void CopySelectedProblems() + { + var selectedItem = _problemsTree.GetNextSelected(null); + if (selectedItem == null) + return; + + var selectedIdxs = new List<int>(); + while (selectedItem != null) + { + int selectedIdx = (int)selectedItem.GetMetadata(0); + selectedIdxs.Add(selectedIdx); + + selectedItem = _problemsTree.GetNextSelected(selectedItem); + } + + if (selectedIdxs.Count == 0) + return; + + var selectedDiagnostics = selectedIdxs.Select(i => _diagnostics[i]); + + var sb = new StringBuilder(); + + foreach (var diagnostic in selectedDiagnostics) + { + if (!string.IsNullOrEmpty(diagnostic.Code)) + sb.Append($"{diagnostic.Code}: "); + + sb.AppendLine($"{diagnostic.Message} {diagnostic.File}({diagnostic.Line},{diagnostic.Column})"); + } + + string text = sb.ToString(); + + if (!string.IsNullOrEmpty(text)) + DisplayServer.ClipboardSet(text); + } + + private void ToggleLayout(bool pressed) + { + _layout = pressed ? ProblemsLayout.List : ProblemsLayout.Tree; + + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); + editorSettings.SetSetting(GodotSharpEditor.Settings.ProblemsLayout, Variant.From(_layout)); + + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _toggleLayoutButton.TooltipText = GetToggleLayoutTooltipText(); + + UpdateProblemsView(); + } + + private bool GetToggleLayoutPressedState() + { + // If pressed: List layout. + // If not pressed: Tree layout. + return _layout == ProblemsLayout.List; + } + + private Texture2D? GetToggleLayoutIcon() + { + return _layout switch + { + ProblemsLayout.List => GetThemeIcon("FileList", "EditorIcons"), + ProblemsLayout.Tree or _ => GetThemeIcon("FileTree", "EditorIcons"), + }; + } + + private string GetToggleLayoutTooltipText() + { + return _layout switch + { + ProblemsLayout.List => "View as a Tree".TTR(), + ProblemsLayout.Tree or _ => "View as a List".TTR(), + }; + } + + private void ToggleSearchBoxVisibility(bool pressed) + { + _searchBox.Visible = pressed; + if (pressed) + { + _searchBox.GrabFocus(); + } + } + + private void SearchTextChanged(string text) + { + UpdateProblemsView(); + } + + private void ToggleFilter(bool pressed) + { + UpdateProblemsView(); + } + + private void GoToSelectedProblem() + { + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem == null) + throw new InvalidOperationException("Item tree has no selected items."); + + // Get correct diagnostic index from problems tree. + int diagnosticIndex = (int)selectedItem.GetMetadata(0); + + if (diagnosticIndex < 0 || diagnosticIndex >= _diagnostics.Count) + throw new InvalidOperationException("Diagnostic index out of range."); + + var diagnostic = _diagnostics[diagnosticIndex]; + + if (string.IsNullOrEmpty(diagnostic.ProjectFile) && string.IsNullOrEmpty(diagnostic.File)) + return; + + string? projectDir = !string.IsNullOrEmpty(diagnostic.ProjectFile) ? + diagnostic.ProjectFile.GetBaseDir() : + GodotSharpEditor.Instance.MSBuildPanel.LastBuildInfo?.Solution.GetBaseDir(); + if (string.IsNullOrEmpty(projectDir)) + return; + + string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()); + + if (!File.Exists(file)) + return; + + file = ProjectSettings.LocalizePath(file); + + if (file.StartsWith("res://")) + { + var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); + + // Godot's ScriptEditor.Edit is 0-based but the diagnostic lines are 1-based. + if (script != null && Internal.ScriptEditorEdit(script, diagnostic.Line - 1, diagnostic.Column - 1)) + Internal.EditorNodeShowScriptScreen(); + } + } + + private void ShowProblemContextMenu(Vector2 position, long mouseButtonIndex) + { + if (mouseButtonIndex != (long)MouseButton.Right) + return; + + _problemsContextMenu.Clear(); + _problemsContextMenu.Size = new Vector2I(1, 1); + + var selectedItem = _problemsTree.GetSelected(); + if (selectedItem != null) + { + // Add menu entries for the selected item. + _problemsContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + label: "Copy Error".TTR(), (int)ProblemContextMenuOption.Copy); + } + + if (_problemsContextMenu.ItemCount > 0) + { + _problemsContextMenu.Position = (Vector2I)(_problemsTree.GlobalPosition + position); + _problemsContextMenu.Popup(); + } + } + + private enum ProblemContextMenuOption + { + Copy, + } + + private void ProblemContextOptionPressed(long id) + { + switch ((ProblemContextMenuOption)id) + { + case ProblemContextMenuOption.Copy: + CopySelectedProblems(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid problem context menu option."); + } + } + + private bool ShouldDisplayDiagnostic(BuildDiagnostic diagnostic) + { + if (!_filtersByType[diagnostic.Type].IsActive) + return false; + + string searchText = _searchBox.Text; + if (!string.IsNullOrEmpty(searchText) && + (!diagnostic.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase) || + !(diagnostic.File?.Contains(searchText, StringComparison.OrdinalIgnoreCase) ?? false))) + { + return false; + } + + return true; + } + + private Color? GetProblemItemColor(BuildDiagnostic diagnostic) + { + return diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeColor("warning_color", "Editor"), + BuildDiagnostic.DiagnosticType.Error => GetThemeColor("error_color", "Editor"), + _ => null, + }; + } + + public void UpdateProblemsView() + { + switch (_layout) + { + case ProblemsLayout.List: + UpdateProblemsList(); + break; + + case ProblemsLayout.Tree: + default: + UpdateProblemsTree(); + break; + } + + foreach (var (type, filter) in _filtersByType) + { + int count = _diagnostics.Count(d => d.Type == type); + filter.ProblemsCount = count; + } + + if (_diagnostics.Count == 0) + Name = "Problems".TTR(); + else + Name = $"{"Problems".TTR()} ({_diagnostics.Count})"; + } + + private void UpdateProblemsList() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + for (int i = 0; i < _diagnostics.Count; i++) + { + var diagnostic = _diagnostics[i]; + + if (!ShouldDisplayDiagnostic(diagnostic)) + continue; + + var item = CreateProblemItem(diagnostic, includeFileInText: true); + + var problemItem = _problemsTree.CreateItem(root); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, i); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + + private void UpdateProblemsTree() + { + _problemsTree.Clear(); + + var root = _problemsTree.CreateItem(); + + var groupedDiagnostics = _diagnostics.Select((d, i) => (Diagnostic: d, Index: i)) + .Where(x => ShouldDisplayDiagnostic(x.Diagnostic)) + .GroupBy(x => x.Diagnostic.ProjectFile) + .Select(g => (ProjectFile: g.Key, Diagnostics: g.GroupBy(x => x.Diagnostic.File) + .Select(x => (File: x.Key, Diagnostics: x.ToArray())))) + .ToArray(); + + if (groupedDiagnostics.Length == 0) + return; + + foreach (var (projectFile, projectDiagnostics) in groupedDiagnostics) + { + TreeItem projectItem; + + if (groupedDiagnostics.Length == 1) + { + // Don't create a project item if there's only one project. + projectItem = root; + } + else + { + string projectFilePath = !string.IsNullOrEmpty(projectFile) + ? projectFile + : "Unknown project".TTR(); + projectItem = _problemsTree.CreateItem(root); + projectItem.SetText(0, projectFilePath); + projectItem.SetSelectable(0, false); + } + + foreach (var (file, fileDiagnostics) in projectDiagnostics) + { + if (fileDiagnostics.Length == 0) + continue; + + string? projectDir = Path.GetDirectoryName(projectFile); + string relativeFilePath = !string.IsNullOrEmpty(file) && !string.IsNullOrEmpty(projectDir) + ? Path.GetRelativePath(projectDir, file) + : "Unknown file".TTR(); + + string fileItemText = string.Format("{0} ({1} issues)".TTR(), relativeFilePath, fileDiagnostics.Length); + + var fileItem = _problemsTree.CreateItem(projectItem); + fileItem.SetText(0, fileItemText); + fileItem.SetSelectable(0, false); + + foreach (var (diagnostic, index) in fileDiagnostics) + { + var item = CreateProblemItem(diagnostic); + + var problemItem = _problemsTree.CreateItem(fileItem); + problemItem.SetIcon(0, item.Icon); + problemItem.SetText(0, item.Text); + problemItem.SetTooltipText(0, item.TooltipText); + problemItem.SetMetadata(0, index); + + var color = GetProblemItemColor(diagnostic); + if (color.HasValue) + problemItem.SetCustomColor(0, color.Value); + } + } + } + } + + private class ProblemItem + { + public string? Text { get; set; } + public string? TooltipText { get; set; } + public Texture2D? Icon { get; set; } + } + + private ProblemItem CreateProblemItem(BuildDiagnostic diagnostic, bool includeFileInText = false) + { + var text = new StringBuilder(); + var tooltip = new StringBuilder(); + + ReadOnlySpan<char> shortMessage = diagnostic.Message.AsSpan(); + int lineBreakIdx = shortMessage.IndexOf('\n'); + if (lineBreakIdx != -1) + shortMessage = shortMessage[..lineBreakIdx]; + text.Append(shortMessage); + + tooltip.Append($"Message: {diagnostic.Message}"); + + if (!string.IsNullOrEmpty(diagnostic.Code)) + tooltip.Append($"\nCode: {diagnostic.Code}"); + + string type = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Hidden => "hidden", + BuildDiagnostic.DiagnosticType.Info => "info", + BuildDiagnostic.DiagnosticType.Warning => "warning", + BuildDiagnostic.DiagnosticType.Error => "error", + _ => "unknown", + }; + tooltip.Append($"\nType: {type}"); + + if (!string.IsNullOrEmpty(diagnostic.File)) + { + text.Append(' '); + if (includeFileInText) + { + text.Append(diagnostic.File); + } + + text.Append($"({diagnostic.Line},{diagnostic.Column})"); + + tooltip.Append($"\nFile: {diagnostic.File}"); + tooltip.Append($"\nLine: {diagnostic.Line}"); + tooltip.Append($"\nColumn: {diagnostic.Column}"); + } + + if (!string.IsNullOrEmpty(diagnostic.ProjectFile)) + tooltip.Append($"\nProject: {diagnostic.ProjectFile}"); + + return new ProblemItem() + { + Text = text.ToString(), + TooltipText = tooltip.ToString(), + Icon = diagnostic.Type switch + { + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("Warning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("Error", "EditorIcons"), + _ => null, + }, + }; + } + + public override void _Ready() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + + Name = "Problems".TTR(); + + var vbLeft = new VBoxContainer + { + CustomMinimumSize = new Vector2(0, 180 * EditorScale), + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + AddChild(vbLeft); + + // Problem Tree. + _problemsTree = new Tree + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + AllowRmbSelect = true, + HideRoot = true, + }; + _problemsTree.ItemActivated += GoToSelectedProblem; + _problemsTree.ItemMouseSelected += ShowProblemContextMenu; + vbLeft.AddChild(_problemsTree); + + // Problem context menu. + _problemsContextMenu = new PopupMenu(); + _problemsContextMenu.IdPressed += ProblemContextOptionPressed; + _problemsTree.AddChild(_problemsContextMenu); + + // Search box. + _searchBox = new LineEdit + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + PlaceholderText = "Filter Problems".TTR(), + ClearButtonEnabled = true, + }; + _searchBox.TextChanged += SearchTextChanged; + vbLeft.AddChild(_searchBox); + + var vbRight = new VBoxContainer(); + AddChild(vbRight); + + // Tools grid. + var hbTools = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ExpandFill, + }; + vbRight.AddChild(hbTools); + + // Clear. + _clearButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/clear_output", "Clear Output".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | (Key)KeyModifierMask.MaskShift | Key.K), + ShortcutContext = this, + }; + _clearButton.Pressed += Clear; + hbTools.AddChild(_clearButton); + + // Copy. + _copyButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + Shortcut = EditorDefShortcut("editor/copy_output", "Copy Selection".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.C), + ShortcutContext = this, + }; + _copyButton.Pressed += CopySelectedProblems; + hbTools.AddChild(_copyButton); + + // A second hbox to make a 2x2 grid of buttons. + var hbTools2 = new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.ShrinkCenter, + }; + vbRight.AddChild(hbTools2); + + // Toggle List/Tree. + _toggleLayoutButton = new Button + { + Flat = true, + FocusMode = FocusModeEnum.None, + TooltipText = GetToggleLayoutTooltipText(), + ToggleMode = true, + ButtonPressed = GetToggleLayoutPressedState(), + }; + // Don't tint the icon even when in "pressed" state. + _toggleLayoutButton.AddThemeColorOverride("icon_pressed_color", Colors.White); + _toggleLayoutButton.Toggled += ToggleLayout; + hbTools2.AddChild(_toggleLayoutButton); + + // Show Search. + _showSearchButton = new Button + { + ThemeTypeVariation = "FlatButton", + FocusMode = FocusModeEnum.None, + ToggleMode = true, + ButtonPressed = true, + Shortcut = EditorDefShortcut("editor/open_search", "Focus Search/Filter Bar".TTR(), (Key)KeyModifierMask.MaskCmdOrCtrl | Key.F), + ShortcutContext = this, + }; + _showSearchButton.Toggled += ToggleSearchBoxVisibility; + hbTools2.AddChild(_showSearchButton); + + // Diagnostic Type Filters. + vbRight.AddChild(new HSeparator()); + + var infoFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Info); + infoFilter.ToggleButton.TooltipText = "Toggle visibility of info diagnostics.".TTR(); + infoFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(infoFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Info] = infoFilter; + + var errorFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Error); + errorFilter.ToggleButton.TooltipText = "Toggle visibility of errors.".TTR(); + errorFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(errorFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Error] = errorFilter; + + var warningFilter = new BuildProblemsFilter(BuildDiagnostic.DiagnosticType.Warning); + warningFilter.ToggleButton.TooltipText = "Toggle visibility of warnings.".TTR(); + warningFilter.ToggleButton.Toggled += ToggleFilter; + vbRight.AddChild(warningFilter.ToggleButton); + _filtersByType[BuildDiagnostic.DiagnosticType.Warning] = warningFilter; + + UpdateTheme(); + + UpdateProblemsView(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + switch ((long)what) + { + case EditorSettings.NotificationEditorSettingsChanged: + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + _layout = editorSettings.GetSetting(GodotSharpEditor.Settings.ProblemsLayout).As<ProblemsLayout>(); + _toggleLayoutButton.ButtonPressed = GetToggleLayoutPressedState(); + UpdateProblemsView(); + break; + + case NotificationThemeChanged: + UpdateTheme(); + break; + } + } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_clearButton == null) + return; + + foreach (var (type, filter) in _filtersByType) + { + filter.ToggleButton.Icon = type switch + { + BuildDiagnostic.DiagnosticType.Info => GetThemeIcon("Popup", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Warning => GetThemeIcon("StatusWarning", "EditorIcons"), + BuildDiagnostic.DiagnosticType.Error => GetThemeIcon("StatusError", "EditorIcons"), + _ => null, + }; + } + + _clearButton.Icon = GetThemeIcon("Clear", "EditorIcons"); + _copyButton.Icon = GetThemeIcon("ActionCopy", "EditorIcons"); + _toggleLayoutButton.Icon = GetToggleLayoutIcon(); + _showSearchButton.Icon = GetThemeIcon("Search", "EditorIcons"); + _searchBox.RightIcon = GetThemeIcon("Search", "EditorIcons"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 8a292fd73a..57b5598a78 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -9,7 +9,9 @@ using System.Text; using System.Threading.Tasks; using Godot; using GodotTools.BuildLogger; +using GodotTools.Internals; using GodotTools.Utils; +using Directory = GodotTools.Utils.Directory; namespace GodotTools.Build { @@ -293,5 +295,81 @@ namespace GodotTools.Build foreach (string env in platformEnvironmentVariables) environmentVariables.Remove(env); } + + private static Process DoGenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, + Action<string> stdOutHandler, Action<string> stdErrHandler) + { + if (Directory.Exists(xcFrameworkPath)) + { + Directory.Delete(xcFrameworkPath, true); + } + + var startInfo = new ProcessStartInfo("xcrun"); + + BuildXCFrameworkArguments(outputPaths, xcFrameworkPath, startInfo.ArgumentList); + + string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Packaging: ")).ToString(); + stdOutHandler?.Invoke(launchMessage); + if (Godot.OS.IsStdOutVerbose()) + Console.WriteLine(launchMessage); + + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + + if (OperatingSystem.IsWindows()) + { + startInfo.StandardOutputEncoding = Encoding.UTF8; + startInfo.StandardErrorEncoding = Encoding.UTF8; + } + + // Needed when running from Developer Command Prompt for VS. + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process { StartInfo = startInfo }; + + if (stdOutHandler != null) + process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data); + if (stdErrHandler != null) + process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data); + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + return process; + } + + public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string> stdOutHandler, Action<string> stdErrHandler) + { + using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } + + private static void BuildXCFrameworkArguments(List<string> outputPaths, + string xcFrameworkPath, Collection<string> arguments) + { + var baseDylib = $"{GodotSharpDirs.ProjectAssemblyName}.dylib"; + var baseSym = $"{GodotSharpDirs.ProjectAssemblyName}.framework.dSYM"; + + arguments.Add("xcodebuild"); + arguments.Add("-create-xcframework"); + + foreach (var outputPath in outputPaths) + { + arguments.Add("-library"); + arguments.Add(Path.Combine(outputPath, baseDylib)); + arguments.Add("-debug-symbols"); + arguments.Add(Path.Combine(outputPath, baseSym)); + } + + arguments.Add("-output"); + arguments.Add(xcFrameworkPath); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index cc11132a55..bae87dd1dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -5,28 +5,73 @@ using GodotTools.Internals; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; +#nullable enable + namespace GodotTools.Build { - public partial class MSBuildPanel : VBoxContainer + public partial class MSBuildPanel : MarginContainer, ISerializationListener { - public BuildOutputView BuildOutputView { get; private set; } + [Signal] + public delegate void BuildStateChangedEventHandler(); + +#nullable disable + private MenuButton _buildMenuButton; + private Button _openLogsFolderButton; + + private BuildProblemsView _problemsView; + private BuildOutputView _outputView; +#nullable enable + + public BuildInfo? LastBuildInfo { get; private set; } + public bool IsBuildingOngoing { get; private set; } + public BuildResult? BuildResult { get; private set; } - private MenuButton _buildMenuBtn; - private Button _errorsBtn; - private Button _warningsBtn; - private Button _viewLogBtn; - private Button _openLogsFolderBtn; + private readonly object _pendingBuildLogTextLock = new object(); + private string _pendingBuildLogText = string.Empty; - private void WarningsToggled(bool pressed) + public Texture2D? GetBuildStateIcon() { - BuildOutputView.WarningsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + if (IsBuildingOngoing) + return GetThemeIcon("Stop", "EditorIcons"); + + if (_problemsView.WarningCount > 0 && _problemsView.ErrorCount > 0) + return GetThemeIcon("ErrorWarning", "EditorIcons"); + + if (_problemsView.WarningCount > 0) + return GetThemeIcon("Warning", "EditorIcons"); + + if (_problemsView.ErrorCount > 0) + return GetThemeIcon("Error", "EditorIcons"); + + return null; } - private void ErrorsToggled(bool pressed) + private enum BuildMenuOptions { - BuildOutputView.ErrorsVisible = pressed; - BuildOutputView.UpdateIssuesList(); + BuildProject, + RebuildProject, + CleanProject, + } + + private void BuildMenuOptionPressed(long id) + { + switch ((BuildMenuOptions)id) + { + case BuildMenuOptions.BuildProject: + BuildProject(); + break; + + case BuildMenuOptions.RebuildProject: + RebuildProject(); + break; + + case BuildMenuOptions.CleanProject: + CleanProject(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + } } public void BuildProject() @@ -73,108 +118,136 @@ namespace GodotTools.Build _ = BuildManager.CleanProjectBlocking("Debug"); } - private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; - - private void OpenLogsFolderPressed() => OS.ShellOpen( + private void OpenLogsFolder() => OS.ShellOpen( $"file://{GodotSharpDirs.LogsDirPathFor("Debug")}" ); - private void BuildMenuOptionPressed(long id) + private void BuildLaunchFailed(BuildInfo buildInfo, string cause) { - switch ((BuildMenuOptions)id) + IsBuildingOngoing = false; + BuildResult = Build.BuildResult.Error; + + _problemsView.Clear(); + _outputView.Clear(); + + var diagnostic = new BuildDiagnostic { - case BuildMenuOptions.BuildProject: - BuildProject(); - break; - case BuildMenuOptions.RebuildProject: - RebuildProject(); - break; - case BuildMenuOptions.CleanProject: - CleanProject(); - break; - default: - throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + Type = BuildDiagnostic.DiagnosticType.Error, + Message = cause, + }; + + _problemsView.SetDiagnostics(new[] { diagnostic }); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildStarted(BuildInfo buildInfo) + { + LastBuildInfo = buildInfo; + IsBuildingOngoing = true; + BuildResult = null; + + _problemsView.Clear(); + _outputView.Clear(); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void BuildFinished(BuildResult result) + { + IsBuildingOngoing = false; + BuildResult = result; + + string csvFile = Path.Combine(LastBuildInfo!.LogsDirPath, BuildManager.MsBuildIssuesFileName); + _problemsView.SetDiagnosticsFromFile(csvFile); + + _problemsView.UpdateProblemsView(); + + EmitSignal(SignalName.BuildStateChanged); + } + + private void UpdateBuildLogText() + { + lock (_pendingBuildLogTextLock) + { + _outputView.Append(_pendingBuildLogText); + _pendingBuildLogText = string.Empty; } } - private enum BuildMenuOptions + private void StdOutputReceived(string text) { - BuildProject, - RebuildProject, - CleanProject + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } + } + + private void StdErrorReceived(string text) + { + lock (_pendingBuildLogTextLock) + { + if (_pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + _pendingBuildLogText += text + "\n"; + } } public override void _Ready() { base._Ready(); - CustomMinimumSize = new Vector2(0, 228 * EditorScale); - SizeFlagsVertical = SizeFlags.ExpandFill; + var bottomPanelStylebox = EditorInterface.Singleton.GetBaseControl().GetThemeStylebox("BottomPanel", "EditorStyles"); + AddThemeConstantOverride("margin_top", -(int)bottomPanelStylebox.ContentMarginTop); + AddThemeConstantOverride("margin_left", -(int)bottomPanelStylebox.ContentMarginLeft); + AddThemeConstantOverride("margin_right", -(int)bottomPanelStylebox.ContentMarginRight); + + var tabs = new TabContainer(); + AddChild(tabs); - var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; - AddChild(toolBarHBox); + var tabActions = new HBoxContainer + { + SizeFlagsVertical = SizeFlags.ExpandFill, + SizeFlagsHorizontal = SizeFlags.ExpandFill, + Alignment = BoxContainer.AlignmentMode.End, + }; + tabActions.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect); + tabs.GetTabBar().AddChild(tabActions); - _buildMenuBtn = new MenuButton { Text = "Build", Icon = GetThemeIcon("BuildCSharp", "EditorIcons") }; - toolBarHBox.AddChild(_buildMenuBtn); + _buildMenuButton = new MenuButton + { + TooltipText = "Build".TTR(), + Flat = true, + }; + tabActions.AddChild(_buildMenuButton); - var buildMenu = _buildMenuBtn.GetPopup(); + var buildMenu = _buildMenuButton.GetPopup(); buildMenu.AddItem("Build Project".TTR(), (int)BuildMenuOptions.BuildProject); buildMenu.AddItem("Rebuild Project".TTR(), (int)BuildMenuOptions.RebuildProject); buildMenu.AddItem("Clean Project".TTR(), (int)BuildMenuOptions.CleanProject); buildMenu.IdPressed += BuildMenuOptionPressed; - _errorsBtn = new Button + _openLogsFolderButton = new Button { - TooltipText = "Show Errors".TTR(), - Icon = GetThemeIcon("StatusError", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None + TooltipText = "Show Logs in File Manager".TTR(), + Flat = true, }; - _errorsBtn.Toggled += ErrorsToggled; - toolBarHBox.AddChild(_errorsBtn); + _openLogsFolderButton.Pressed += OpenLogsFolder; + tabActions.AddChild(_openLogsFolderButton); - _warningsBtn = new Button - { - TooltipText = "Show Warnings".TTR(), - Icon = GetThemeIcon("NodeWarning", "EditorIcons"), - ExpandIcon = false, - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _warningsBtn.Toggled += WarningsToggled; - toolBarHBox.AddChild(_warningsBtn); + _problemsView = new BuildProblemsView(); + tabs.AddChild(_problemsView); - _viewLogBtn = new Button - { - Text = "Show Output".TTR(), - ToggleMode = true, - ButtonPressed = true, - FocusMode = FocusModeEnum.None - }; - _viewLogBtn.Toggled += ViewLogToggled; - toolBarHBox.AddChild(_viewLogBtn); - - // Horizontal spacer, push everything to the right. - toolBarHBox.AddChild(new Control - { - SizeFlagsHorizontal = SizeFlags.ExpandFill, - }); + _outputView = new BuildOutputView(); + tabs.AddChild(_outputView); - _openLogsFolderBtn = new Button - { - Text = "Show Logs in File Manager".TTR(), - Icon = GetThemeIcon("Filesystem", "EditorIcons"), - ExpandIcon = false, - FocusMode = FocusModeEnum.None, - }; - _openLogsFolderBtn.Pressed += OpenLogsFolderPressed; - toolBarHBox.AddChild(_openLogsFolderBtn); + UpdateTheme(); - BuildOutputView = new BuildOutputView(); - AddChild(BuildOutputView); + AddBuildEventListeners(); } public override void _Notification(int what) @@ -183,13 +256,49 @@ namespace GodotTools.Build if (what == NotificationThemeChanged) { - if (_buildMenuBtn != null) - _buildMenuBtn.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); - if (_errorsBtn != null) - _errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); - if (_warningsBtn != null) - _warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + UpdateTheme(); } } + + private void UpdateTheme() + { + // Nodes will be null until _Ready is called. + if (_buildMenuButton == null) + return; + + _buildMenuButton.Icon = GetThemeIcon("BuildCSharp", "EditorIcons"); + _openLogsFolderButton.Icon = GetThemeIcon("Filesystem", "EditorIcons"); + } + + private void AddBuildEventListeners() + { + BuildManager.BuildLaunchFailed += BuildLaunchFailed; + BuildManager.BuildStarted += BuildStarted; + BuildManager.BuildFinished += BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred. + BuildManager.StdOutputReceived += StdOutputReceived; + BuildManager.StdErrorReceived += StdErrorReceived; + } + + public void OnBeforeSerialize() + { + // In case it didn't update yet. We don't want to have to serialize any pending output. + UpdateBuildLogText(); + + // NOTE: + // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. + // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. + BuildManager.BuildLaunchFailed -= BuildLaunchFailed; + BuildManager.BuildStarted -= BuildStarted; + BuildManager.BuildFinished -= BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived -= StdOutputReceived; + BuildManager.StdErrorReceived -= StdErrorReceived; + } + + public void OnAfterDeserialize() + { + AddBuildEventListeners(); // Re-add them. + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index b98df190ca..91e5118990 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -6,9 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using GodotTools.Build; -using GodotTools.Core; using GodotTools.Internals; -using static GodotTools.Internals.Globals; using Directory = GodotTools.Utils.Directory; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; @@ -22,6 +20,19 @@ namespace GodotTools.Export private List<string> _tempFolders = new List<string>(); + private static bool ProjectContainsDotNet() + { + return File.Exists(GodotSharpDirs.ProjectSlnPath); + } + + public override string[] _GetExportFeatures(EditorExportPlatform platform, bool debug) + { + if (!ProjectContainsDotNet()) + return Array.Empty<string>(); + + return new string[] { "dotnet" }; + } + public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetExportOptions(EditorExportPlatform platform) { return new Godot.Collections.Array<Godot.Collections.Dictionary>() @@ -77,7 +88,7 @@ namespace GodotTools.Export $"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); - // TODO What if the source file is not part of the game's C# project + // TODO: What if the source file is not part of the game's C# project? bool includeScriptsContent = (bool)GetOption("dotnet/include_scripts_content"); @@ -89,7 +100,7 @@ namespace GodotTools.Export // Because of this, we add a file which contains a line break. AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false); - // Tell the Godot exporter that we already took care of the file + // Tell the Godot exporter that we already took care of the file. Skip(); } } @@ -119,123 +130,269 @@ namespace GodotTools.Export private void _ExportBeginImpl(string[] features, bool isDebug, string path, long flags) { - _ = flags; // Unused + _ = flags; // Unused. - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + if (!ProjectContainsDotNet()) return; if (!DeterminePlatformFromFeatures(features, out string platform)) throw new NotSupportedException("Target platform not supported."); - if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android } + if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS } .Contains(platform)) { throw new NotImplementedException("Target platform not yet implemented."); } - string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - - bool includeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols"); + PublishConfig publishConfig = new() + { + BuildConfig = isDebug ? "ExportDebug" : "ExportRelease", + IncludeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols"), + RidOS = DetermineRuntimeIdentifierOS(platform), + Archs = new List<string>(), + UseTempDir = platform != OS.Platforms.iOS, // xcode project links directly to files in the publish dir, so use one that sticks around. + BundleOutputs = true, + }; - var archs = new List<string>(); if (features.Contains("x86_64")) { - archs.Add("x86_64"); + publishConfig.Archs.Add("x86_64"); } + if (features.Contains("x86_32")) { - archs.Add("x86_32"); + publishConfig.Archs.Add("x86_32"); } + if (features.Contains("arm64")) { - archs.Add("arm64"); + publishConfig.Archs.Add("arm64"); } + if (features.Contains("arm32")) { - archs.Add("arm32"); + publishConfig.Archs.Add("arm32"); } + if (features.Contains("universal")) { if (platform == OS.Platforms.MacOS) { - archs.Add("x86_64"); - archs.Add("arm64"); + publishConfig.Archs.Add("x86_64"); + publishConfig.Archs.Add("arm64"); } } - bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android"); + var targets = new List<PublishConfig> { publishConfig }; - foreach (var arch in archs) + if (platform == OS.Platforms.iOS) { - string ridOS = DetermineRuntimeIdentifierOS(platform); - string ridArch = DetermineRuntimeIdentifierArch(arch); - string runtimeIdentifier = $"{ridOS}-{ridArch}"; - string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}"; - if (platform == OS.Platforms.MacOS) + targets.Add(new PublishConfig { - projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName); - } + BuildConfig = publishConfig.BuildConfig, + Archs = new List<string> { "arm64", "x86_64" }, + BundleOutputs = false, + IncludeDebugSymbols = publishConfig.IncludeDebugSymbols, + RidOS = OS.DotNetOS.iOSSimulator, + UseTempDir = true, + }); + } + + List<string> outputPaths = new(); - // Create temporary publish output directory + bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || platform == OS.Platforms.Android; - string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet", - $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}"); + foreach (PublishConfig config in targets) + { + string ridOS = config.RidOS; + string buildConfig = config.BuildConfig; + bool includeDebugSymbols = config.IncludeDebugSymbols; - _tempFolders.Add(publishOutputTempDir); + foreach (string arch in config.Archs) + { + string ridArch = DetermineRuntimeIdentifierArch(arch); + string runtimeIdentifier = $"{ridOS}-{ridArch}"; + string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}"; + if (platform == OS.Platforms.MacOS) + { + projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName); + } - if (!Directory.Exists(publishOutputTempDir)) - Directory.CreateDirectory(publishOutputTempDir); + // Create temporary publish output directory. + string publishOutputDir; - // Execute dotnet publish + if (config.UseTempDir) + { + publishOutputDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet", + $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}"); + _tempFolders.Add(publishOutputDir); + } + else + { + publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet", + $"{buildConfig}-{runtimeIdentifier}"); - if (!BuildManager.PublishProjectBlocking(buildConfig, platform, - runtimeIdentifier, publishOutputTempDir, includeDebugSymbols)) - { - throw new InvalidOperationException("Failed to build project."); - } + } - string soExt = ridOS switch - { - OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll", - OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib", - _ => "so" - }; - - if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll")) - // NativeAOT shared library output - && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}"))) - { - throw new NotSupportedException( - "Publish succeeded but project assembly not found in the output directory"); - } + outputPaths.Add(publishOutputDir); - var manifest = new StringBuilder(); + if (!Directory.Exists(publishOutputDir)) + Directory.CreateDirectory(publishOutputDir); - // Add to the exported project shared object list or packed resources. - foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) - { - if (embedBuildResults) + // Execute dotnet publish. + if (!BuildManager.PublishProjectBlocking(buildConfig, platform, + runtimeIdentifier, publishOutputDir, includeDebugSymbols)) { - var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file)); - var fileData = File.ReadAllBytes(file); - var hash = Convert.ToBase64String(SHA512.HashData(fileData)); + throw new InvalidOperationException("Failed to build project."); + } - manifest.Append($"{filePath}\t{hash}\n"); + string soExt = ridOS switch + { + OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll", + OS.DotNetOS.OSX or OS.DotNetOS.iOS or OS.DotNetOS.iOSSimulator => "dylib", + _ => "so" + }; - AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false); + string assemblyPath = Path.Combine(publishOutputDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"); + string nativeAotPath = Path.Combine(publishOutputDir, + $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}"); + + if (!File.Exists(assemblyPath) && !File.Exists(nativeAotPath)) + { + throw new NotSupportedException( + $"Publish succeeded but project assembly not found at '{assemblyPath}' or '{nativeAotPath}'."); } - else + + // For ios simulator builds, skip packaging the build outputs. + if (!config.BundleOutputs) + continue; + + var manifest = new StringBuilder(); + + // Add to the exported project shared object list or packed resources. + RecursePublishContents(publishOutputDir, + filterDir: dir => + { + if (platform == OS.Platforms.iOS) + { + // Exclude dsym folders. + return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase); + } + + return true; + }, + filterFile: file => + { + if (platform == OS.Platforms.iOS) + { + // Exclude the dylib artifact, since it's included separately as an xcframework. + return Path.GetFileName(file) != $"{GodotSharpDirs.ProjectAssemblyName}.dylib"; + } + + return true; + }, + recurseDir: dir => + { + if (platform == OS.Platforms.iOS) + { + // Don't recurse into dsym folders. + return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase); + } + + return true; + }, + addEntry: (path, isFile) => + { + // We get called back for both directories and files, but we only package files for now. + if (isFile) + { + if (embedBuildResults) + { + string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path)); + byte[] fileData = File.ReadAllBytes(path); + string hash = Convert.ToBase64String(SHA512.HashData(fileData)); + + manifest.Append($"{filePath}\t{hash}\n"); + + AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false); + } + else + { + if (platform == OS.Platforms.iOS && path.EndsWith(".dat")) + { + AddIosBundleFile(path); + } + else + { + AddSharedObject(path, tags: null, + Path.Join(projectDataDirName, + Path.GetRelativePath(publishOutputDir, + Path.GetDirectoryName(path)))); + } + } + } + }); + + if (embedBuildResults) { - AddSharedObject(file, tags: null, - Path.Join(projectDataDirName, - Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file)))); + byte[] fileData = Encoding.Default.GetBytes(manifest.ToString()); + AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false); } } + } - if (embedBuildResults) + if (platform == OS.Platforms.iOS) + { + if (outputPaths.Count > 2) { - var fileData = Encoding.Default.GetBytes(manifest.ToString()); - AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false); + // lipo the simulator binaries together + // TODO: Move this to the native lipo implementation we have in the macos export plugin. + var lipoArgs = new List<string>(); + lipoArgs.Add("-create"); + lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))); + lipoArgs.Add("-output"); + lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib")); + + int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs); + if (lipoExitCode != 0) + throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}."); + + outputPaths.RemoveRange(2, outputPaths.Count - 2); + } + + var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, + $"{GodotSharpDirs.ProjectAssemblyName}.xcframework"); + if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, + Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath))) + { + throw new InvalidOperationException("Failed to generate xcframework."); + } + + AddIosEmbeddedFramework(xcFrameworkPath); + } + } + + private static void RecursePublishContents(string path, Func<string, bool> filterDir, + Func<string, bool> filterFile, Func<string, bool> recurseDir, + Action<string, bool> addEntry) + { + foreach (string file in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly)) + { + if (filterFile(file)) + { + addEntry(file, true); + } + } + + foreach (string dir in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly)) + { + if (filterDir(dir)) + { + addEntry(dir, false); + } + else if (recurseDir(dir)) + { + RecursePublishContents(dir, filterDir, filterFile, recurseDir, addEntry); } } } @@ -304,5 +461,15 @@ namespace GodotTools.Export platform = null; return false; } + + private struct PublishConfig + { + public bool UseTempDir; + public bool BundleOutputs; + public string RidOS; + public List<string> Archs; + public string BuildConfig; + public bool IncludeDebugSymbols; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index f01fbbd1b9..650afb4cdf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -30,6 +30,7 @@ namespace GodotTools public const string VerbosityLevel = "dotnet/build/verbosity_level"; public const string NoConsoleLogging = "dotnet/build/no_console_logging"; public const string CreateBinaryLog = "dotnet/build/create_binary_log"; + public const string ProblemsLayout = "dotnet/build/problems_layout"; } private EditorSettings _editorSettings; @@ -64,6 +65,7 @@ namespace GodotTools private bool CreateProjectSolution() { + string errorMessage = null; using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) { pr.Step("Generating C# project...".TTR()); @@ -95,22 +97,23 @@ namespace GodotTools } catch (IOException e) { - ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message); - return false; + errorMessage = "Failed to save solution. Exception message: ".TTR() + e.Message; } - - pr.Step("Done".TTR()); - - // Here, after all calls to progress_task_step - CallDeferred(nameof(_ShowDotnetFeatures)); } else { - ShowErrorDialog("Failed to create C# project.".TTR()); + errorMessage = "Failed to create C# project.".TTR(); } + } - return true; + if (!string.IsNullOrEmpty(errorMessage)) + { + ShowErrorDialog(errorMessage); + return false; } + + _ShowDotnetFeatures(); + return true; } private void _ShowDotnetFeatures() @@ -160,14 +163,14 @@ namespace GodotTools { _errorDialog.Title = title; _errorDialog.DialogText = message; - _errorDialog.PopupCentered(); + EditorInterface.Singleton.PopupDialogCentered(_errorDialog); } public void ShowConfirmCreateSlnDialog() { _confirmCreateSlnDialog.Title = "C# solution already exists. This will override the existing C# project file, any manual changes will be lost.".TTR(); _confirmCreateSlnDialog.DialogText = "Create C# solution".TTR(); - _confirmCreateSlnDialog.PopupCentered(); + EditorInterface.Singleton.PopupDialogCentered(_confirmCreateSlnDialog); } private static string _vsCodePath = string.Empty; @@ -190,6 +193,9 @@ namespace GodotTools case ExternalEditorId.CustomEditor: { string file = ProjectSettings.GlobalizePath(script.ResourcePath); + string project = ProjectSettings.GlobalizePath("res://"); + // Since ProjectSettings.GlobalizePath replaces only "res:/", leaving a trailing slash, it is removed here. + project = project[..^1]; var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>(); var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>(); var args = new List<string>(); @@ -226,6 +232,7 @@ namespace GodotTools hasFileFlag = true; } + arg = arg.ReplaceN("{project}", project); arg = arg.ReplaceN("{file}", file); args.Add(arg); @@ -433,7 +440,7 @@ namespace GodotTools private void BuildStateChanged() { if (_bottomPanelBtn != null) - _bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + _bottomPanelBtn.Icon = MSBuildPanel.GetBuildStateIcon(); } public override void _EnablePlugin() @@ -449,7 +456,7 @@ namespace GodotTools var dotNetSdkSearchVersion = Environment.Version; // First we try to find the .NET Sdk ourselves to make sure we get the - // correct version first (`RegisterDefaults` always picks the latest). + // correct version first, otherwise pick the latest. if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath)) { if (Godot.OS.IsStdOutVerbose()) @@ -461,7 +468,7 @@ namespace GodotTools { try { - ProjectUtils.MSBuildLocatorRegisterDefaults(out sdkVersion, out sdkPath); + ProjectUtils.MSBuildLocatorRegisterLatest(out sdkVersion, out sdkPath); if (Godot.OS.IsStdOutVerbose()) Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); } @@ -478,20 +485,22 @@ namespace GodotTools _editorSettings = EditorInterface.Singleton.GetEditorSettings(); _errorDialog = new AcceptDialog(); - editorBaseControl.AddChild(_errorDialog); + _errorDialog.SetUnparentWhenInvisible(true); _confirmCreateSlnDialog = new ConfirmationDialog(); + _confirmCreateSlnDialog.SetUnparentWhenInvisible(true); _confirmCreateSlnDialog.Confirmed += () => CreateProjectSolution(); - editorBaseControl.AddChild(_confirmCreateSlnDialog); MSBuildPanel = new MSBuildPanel(); - MSBuildPanel.Ready += () => - MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; + MSBuildPanel.BuildStateChanged += BuildStateChanged; _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); - _menuPopup = new PopupMenu(); + _menuPopup = new PopupMenu + { + Name = "CSharpTools", + }; _menuPopup.Hide(); AddToolSubmenuItem("C#", _menuPopup); @@ -499,7 +508,7 @@ namespace GodotTools _toolBarBuildButton = new Button { Flat = true, - Icon = editorBaseControl.GetThemeIcon("BuildCSharp", "EditorIcons"), + Icon = EditorInterface.Singleton.GetEditorTheme().GetIcon("BuildCSharp", "EditorIcons"), FocusMode = Control.FocusModeEnum.None, Shortcut = EditorDefShortcut("mono/build_solution", "Build Project".TTR(), (Key)KeyModifierMask.MaskAlt | Key.B), ShortcutInTooltip = true, @@ -531,6 +540,7 @@ namespace GodotTools EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal)); EditorDef(Settings.NoConsoleLogging, false); EditorDef(Settings.CreateBinaryLog, false); + EditorDef(Settings.ProblemsLayout, Variant.From(BuildProblemsView.ProblemsLayout.Tree)); string settingsHintStr = "Disabled"; @@ -589,6 +599,14 @@ namespace GodotTools ["hint_string"] = string.Join(",", verbosityLevels), }); + _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = (int)Variant.Type.Int, + ["name"] = Settings.ProblemsLayout, + ["hint"] = (int)PropertyHint.Enum, + ["hint_string"] = "View as List,View as Tree", + }); + OnSettingsChanged(); _editorSettings.SettingsChanged += OnSettingsChanged; @@ -611,6 +629,12 @@ namespace GodotTools _editorSettings.SettingsChanged -= OnSettingsChanged; } + public override void _ExitTree() + { + _errorDialog?.QueueFree(); + _confirmCreateSlnDialog?.QueueFree(); + } + private void OnSettingsChanged() { // We want to force NoConsoleLogging to true when the VerbosityLevel is at Detailed or above. diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index fbbd01dafd..a0ab381b9b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -5,10 +5,14 @@ using Godot; using GodotTools.Internals; using JetBrains.Rider.PathLocator; +#nullable enable + namespace GodotTools.Ides.Rider { public static class RiderPathManager { + private const string EditorPathSettingName = "dotnet/editor/editor_path_optional"; + private static readonly RiderPathLocator RiderPathLocator; private static readonly RiderFileOpener RiderFileOpener; @@ -19,13 +23,14 @@ namespace GodotTools.Ides.Rider RiderFileOpener = new RiderFileOpener(riderLocatorEnvironment); } - public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional"; - - private static string GetRiderPathFromSettings() + private static string? GetRiderPathFromSettings() { var editorSettings = EditorInterface.Singleton.GetEditorSettings(); if (editorSettings.HasSetting(EditorPathSettingName)) + { return (string)editorSettings.GetSetting(EditorPathSettingName); + } + return null; } @@ -37,7 +42,7 @@ namespace GodotTools.Ides.Rider { if (!editorSettings.HasSetting(EditorPathSettingName)) { - Globals.EditorDef(EditorPathSettingName, "Optional"); + Globals.EditorDef(EditorPathSettingName, ""); editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { ["type"] = (int)Variant.Type.String, @@ -55,9 +60,10 @@ namespace GodotTools.Ides.Rider } var paths = RiderPathLocator.GetAllRiderPaths(); - - if (!paths.Any()) + if (paths.Length == 0) + { return; + } string newPath = paths.Last().Path; Globals.EditorDef(EditorPathSettingName, newPath); @@ -67,18 +73,16 @@ namespace GodotTools.Ides.Rider public static bool IsRider(string path) { - if (string.IsNullOrEmpty(path)) - return false; - if (path.IndexOfAny(Path.GetInvalidPathChars()) != -1) + { return false; + } var fileInfo = new FileInfo(path); - string filename = fileInfo.Name.ToLowerInvariant(); - return filename.StartsWith("rider", StringComparison.Ordinal); + return fileInfo.Name.StartsWith("rider", StringComparison.OrdinalIgnoreCase); } - private static string CheckAndUpdatePath(string riderPath) + private static string? CheckAndUpdatePath(string? riderPath) { if (File.Exists(riderPath)) { @@ -87,11 +91,14 @@ namespace GodotTools.Ides.Rider var allInfos = RiderPathLocator.GetAllRiderPaths(); if (allInfos.Length == 0) + { return null; - var riderInfos = allInfos.Where(info => IsRider(info.Path)).ToArray(); - string newPath = riderInfos.Length > 0 - ? riderInfos[riderInfos.Length - 1].Path - : allInfos[allInfos.Length - 1].Path; + } + + // RiderPathLocator includes Rider and Fleet locations, prefer Rider when available. + var preferredInfo = allInfos.LastOrDefault(info => IsRider(info.Path), allInfos[allInfos.Length - 1]); + string newPath = preferredInfo.Path; + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); editorSettings.SetSetting(EditorPathSettingName, newPath); Globals.EditorDef(EditorPathSettingName, newPath); @@ -100,8 +107,14 @@ namespace GodotTools.Ides.Rider public static void OpenFile(string slnPath, string scriptPath, int line, int column) { - string pathFromSettings = GetRiderPathFromSettings(); - string path = CheckAndUpdatePath(pathFromSettings); + string? pathFromSettings = GetRiderPathFromSettings(); + string? path = CheckAndUpdatePath(pathFromSettings); + if (string.IsNullOrEmpty(path)) + { + GD.PushError($"Error when trying to run code editor: JetBrains Rider or Fleet. Could not find path to the editor."); + return; + } + RiderFileOpener.OpenFile(path, slnPath, scriptPath, line, column); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 55b413453d..67891a0594 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -118,6 +118,16 @@ namespace GodotTools.Internals } } + public static string ProjectBaseOutputPath + { + get + { + if (_projectCsProjPath == null) + DetermineProjectLocation(); + return Path.Combine(Path.GetDirectoryName(_projectCsProjPath)!, ".godot", "mono", "temp", "bin"); + } + } + public static string LogsDirPathFor(string solution, string configuration) => Path.Combine(BuildLogsDirs, $"{solution.Md5Text()}_{configuration}"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index bff0c0df7c..c24b730c89 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -56,6 +56,7 @@ namespace GodotTools.Utils public const string Win10 = "win10"; public const string Android = "android"; public const string iOS = "ios"; + public const string iOSSimulator = "iossimulator"; public const string Browser = "browser"; } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 9b41f9cd1b..36fdda4625 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -94,6 +94,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define ICALL_PREFIX "godot_icall_" #define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility" #define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" @@ -263,7 +264,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf } else if (code_tag) { 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 ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) { + } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ") || tag.begins_with("param ")) { 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(" "); @@ -297,6 +298,12 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf if (link_tag == "method") { _append_xml_method(xml_output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "constructor") { + // TODO: Support constructors? + _append_xml_undeclared(xml_output, link_target); + } else if (link_tag == "operator") { + // TODO: Support operators? + _append_xml_undeclared(xml_output, link_target); } else if (link_tag == "member") { _append_xml_member(xml_output, target_itype, target_cname, link_target, link_target_parts); } else if (link_tag == "signal") { @@ -400,29 +407,29 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf pos = brk_end + 1; tag_stack.push_front(tag); - } else if (tag == "code") { + } else if (tag == "code" || tag.begins_with("code ")) { xml_output.append("<c>"); code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); - } else if (tag == "codeblock") { + tag_stack.push_front("code"); + } else if (tag == "codeblock" || tag.begins_with("codeblock ")) { xml_output.append("<code>"); code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); + tag_stack.push_front("codeblock"); } else if (tag == "codeblocks") { line_del = true; pos = brk_end + 1; tag_stack.push_front(tag); - } else if (tag == "csharp") { + } else if (tag == "csharp" || tag.begins_with("csharp ")) { xml_output.append("<code>"); line_del = false; code_tag = true; pos = brk_end + 1; - tag_stack.push_front(tag); + tag_stack.push_front("csharp"); } else if (tag == "kbd") { // keyboard combinations are not supported in xml comments pos = brk_end + 1; @@ -989,9 +996,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); - p_output.append("\n#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n"); - p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); for (const ConstantInterface &iconstant : global_constants) { @@ -1089,8 +1093,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append(CLOSE_BLOCK); } } - - p_output.append("\n#pragma warning restore CS1591\n"); } Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { @@ -1403,15 +1405,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); output.append("using System;\n"); // IntPtr + output.append("using System.ComponentModel;\n"); // EditorBrowsable output.append("using System.Diagnostics;\n"); // DebuggerBrowsable output.append("using Godot.NativeInterop;\n"); - output.append("\n" - "#pragma warning disable CS1591 // Disable warning: " - "'Missing XML comment for publicly visible type or member'\n" - "#pragma warning disable CS1573 // Disable warning: " - "'Parameter has no matching param tag in the XML comment'\n"); - output.append("\n#nullable disable\n"); const DocData::ClassDoc *class_doc = itype.class_doc; @@ -1876,7 +1873,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output << "\n" << INDENT1 "{\n"; + HashMap<String, StringName> method_names; for (const MethodInterface &imethod : itype.methods) { + if (method_names.has(imethod.proxy_name)) { + ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value."); + continue; + } + method_names[imethod.proxy_name] = imethod.cname; output << INDENT2 "/// <summary>\n" << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" << INDENT2 "/// </summary>\n" @@ -1904,10 +1907,6 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(CLOSE_BLOCK /* class */); - output.append("\n" - "#pragma warning restore CS1591\n" - "#pragma warning restore CS1573\n"); - return _save_file(p_output_file, output); } @@ -2129,7 +2128,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf arguments_sig += iarg.name; - if (iarg.default_argument.size()) { + if (!p_imethod.is_compat && iarg.default_argument.size()) { if (iarg.def_param_mode != ArgumentInterface::CONSTANT) { arguments_sig += " = null"; } else { @@ -2217,8 +2216,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output << "GodotObject."; } - p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." - << p_imethod.proxy_name + p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." + << p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul" << ");\n"; } @@ -2257,6 +2256,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } + if (p_imethod.is_compat) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN); p_output.append(p_imethod.is_internal ? "internal " : "public "); @@ -2973,6 +2976,12 @@ bool method_has_ptr_parameter(MethodInfo p_method_info) { return false; } +struct SortMethodWithHashes { + _FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const { + return p_a.first < p_b.first; + } +}; + bool BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -3100,11 +3109,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { List<MethodInfo> virtual_method_list; ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true); - List<MethodInfo> method_list; - ClassDB::get_method_list(type_cname, &method_list, true); - method_list.sort(); + List<Pair<MethodInfo, uint32_t>> method_list_with_hashes; + ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true); + method_list_with_hashes.sort_custom_inplace<SortMethodWithHashes>(); + + List<MethodInterface> compat_methods; + for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) { + const MethodInfo &method_info = E.first; + const uint32_t hash = E.second; - for (const MethodInfo &method_info : method_list) { int argc = method_info.arguments.size(); if (method_info.name.is_empty()) { @@ -3126,6 +3139,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { MethodInterface imethod; imethod.name = method_info.name; imethod.cname = cname; + imethod.hash = hash; if (method_info.flags & METHOD_FLAG_STATIC) { imethod.is_static = true; @@ -3138,7 +3152,17 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = nullptr; + + if (!imethod.is_virtual) { + bool method_exists = false; + m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat); + + if (unlikely(!method_exists)) { + ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false, + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + } + } imethod.is_vararg = m && m->is_vararg(); @@ -3259,6 +3283,14 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); + // Compat methods aren't added to the type yet, they need to be checked for conflicts + // after all the non-compat methods have been added. The compat methods are added in + // reverse so the most recently added ones take precedence over older compat methods. + if (imethod.is_compat) { + compat_methods.push_front(imethod); + continue; + } + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { @@ -3273,6 +3305,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + // Add compat methods that don't conflict with other methods in the type. + for (const MethodInterface &imethod : compat_methods) { + if (_method_has_conflicting_signature(imethod, itype)) { + WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored."); + continue; + } + itype.methods.push_back(imethod); + } + // Populate signals const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; @@ -4054,6 +4095,50 @@ void BindingsGenerator::_populate_global_constants() { } } +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) { + // Compare p_imethod with all the methods already registered in p_itype. + for (const MethodInterface &method : p_itype.methods) { + if (method.proxy_name == p_imethod.proxy_name) { + if (_method_has_conflicting_signature(p_imethod, method)) { + return true; + } + } + } + + return false; +} + +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) { + // Check if a method already exists in p_itype with a method signature that would conflict with p_imethod. + // The return type is ignored because only changing the return type is not enough to avoid conflicts. + // The const keyword is also ignored since it doesn't generate different C# code. + + if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) { + // Different argument count, so no conflict. + return false; + } + + for (int i = 0; i < p_imethod_left.arguments.size(); i++) { + const ArgumentInterface &iarg_left = p_imethod_left.arguments[i]; + const ArgumentInterface &iarg_right = p_imethod_right.arguments[i]; + + if (iarg_left.type.cname != iarg_right.type.cname) { + // Different types for arguments in the same position, so no conflict. + return false; + } + + if (iarg_left.def_param_mode != iarg_right.def_param_mode) { + // If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T' + // and will not create a conflict. + if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + return false; + } + } + } + + return true; +} + void BindingsGenerator::_initialize_blacklisted_methods() { blacklisted_methods["Object"].push_back("to_string"); // there is already ToString blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 6118576bb6..aa4e5ea093 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -134,6 +134,11 @@ class BindingsGenerator { String proxy_name; /** + * Hash of the ClassDB method + */ + uint64_t hash = 0; + + /** * [TypeInterface::name] of the return type */ TypeReference return_type; @@ -168,6 +173,12 @@ class BindingsGenerator { */ bool is_internal = false; + /** + * Determines if the method is a compatibility method added to avoid breaking binary compatibility. + * These methods will be generated but hidden and are considered deprecated. + */ + bool is_compat = false; + List<ArgumentInterface> arguments; const DocData::MethodDoc *method_doc = nullptr; @@ -787,6 +798,9 @@ class BindingsGenerator { void _populate_global_constants(); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right); + Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index 04bd6a9207..4f15335c1e 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -320,7 +320,12 @@ bool get_dotnet_root_from_env(String &r_dotnet_root) { bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { String fxr_dir = path::join(p_dotnet_root, "host", "fxr"); - ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir); + if (!DirAccess::exists(fxr_dir)) { + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT("The host fxr folder does not exist: " + fxr_dir + "."); + } + return false; + } return get_latest_fxr(fxr_dir, r_fxr_path); } diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig index d4e71b1bd9..df4a6c2d0d 100644 --- a/modules/mono/glue/GodotSharp/.editorconfig +++ b/modules/mono/glue/GodotSharp/.editorconfig @@ -1,8 +1,17 @@ [**/Generated/**.cs] -# Validate parameter is non-null before using it +# CA1062: Validate parameter is non-null before using it # Useful for generated code, as it disables nullable dotnet_diagnostic.CA1062.severity = error # CA1069: Enums should not have duplicate values dotnet_diagnostic.CA1069.severity = none # CA1708: Identifiers should differ by more than case dotnet_diagnostic.CA1708.severity = none +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = none +# CS1573: Parameter has no matching param tag in the XML comment +dotnet_diagnostic.CS1573.severity = none + +[GodotSharp/Core/**.cs] +# CS1591: Missing XML comment for publicly visible type or member +# TODO: Temporary change to not pollute the warnings, but we need to document public APIs +dotnet_diagnostic.CS1591.severity = suggestion diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs index 6b000cc89b..750e11777d 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -128,16 +128,20 @@ using Godot.NativeInterop; if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } @@ -303,16 +307,20 @@ using Godot.NativeInterop; if (isInnerClass) { var containingType = symbol.ContainingType; + AppendPartialContainingTypeDeclarations(containingType); - while (containingType != null) + void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType) { + if (containingType == null) + return; + + AppendPartialContainingTypeDeclarations(containingType.ContainingType); + source.Append("partial "); source.Append(containingType.GetDeclarationKeyword()); source.Append(" "); source.Append(containingType.NameWithTypeParameters()); source.Append("\n{\n"); - - containingType = containingType.ContainingType; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs index 48b47b166a..9126495a27 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs @@ -7,6 +7,8 @@ using System.ComponentModel; namespace Godot; +#pragma warning disable CS1734 // XML comment on 'X' has a paramref tag for 'Y', but there is no parameter by that name. + partial class AnimationNode { /// <inheritdoc cref="BlendInput(int, double, bool, bool, float, FilterAction, bool, bool)"/> @@ -24,6 +26,44 @@ partial class AnimationNode } } +partial class AnimationPlayer +{ + /// <inheritdoc cref="AnimationMixer.CallbackModeMethod"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationMethodCallMode MethodCallMode + { + get => (AnimationMethodCallMode)CallbackModeMethod; + set => CallbackModeMethod = (AnimationCallbackModeMethod)value; + } + + /// <inheritdoc cref="AnimationMixer.Active"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool PlaybackActive + { + get => Active; + set => Active = value; + } + + /// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationProcessCallback PlaybackProcessMode + { + get => (AnimationProcessCallback)CallbackModeProcess; + set => CallbackModeProcess = (AnimationCallbackModeProcess)value; + } +} + +partial class AnimationTree +{ + /// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public AnimationProcessCallback ProcessCallback + { + get => (AnimationProcessCallback)CallbackModeProcess; + set => CallbackModeProcess = (AnimationCallbackModeProcess)value; + } +} + partial class CodeEdit { /// <inheritdoc cref="AddCodeCompletionOption(CodeCompletionKind, string, string, Nullable{Color}, Resource, Nullable{Variant}, int)"/> @@ -36,7 +76,7 @@ partial class CodeEdit partial class Geometry3D { - /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Collections.Array{Plane})"/> + /// <inheritdoc cref="SegmentIntersectsConvex(Vector3, Vector3, Godot.Collections.Array{Plane})"/> [EditorBrowsable(EditorBrowsableState.Never)] public static Vector3[] SegmentIntersectsConvex(Vector3 from, Vector3 to, Godot.Collections.Array planes) { @@ -44,6 +84,61 @@ partial class Geometry3D } } +partial class GraphEdit +{ + /// <inheritdoc cref="ShowArrangeButton"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool ArrangeNodesButtonHidden + { + get => !ShowArrangeButton; + set => ShowArrangeButton = !value; + } + + /// <inheritdoc cref="GetMenuHBox()"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public HBoxContainer GetZoomHBox() + { + return GetMenuHBox(); + } + + /// <inheritdoc cref="SnappingDistance"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public int SnapDistance + { + get => SnappingDistance; + set => SnappingDistance = value; + } + + /// <inheritdoc cref="SnappingEnabled"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public bool UseSnap + { + get => SnappingEnabled; + set => SnappingEnabled = value; + } +} + +partial class GraphNode +{ + /// <inheritdoc cref="GraphElement.DeleteRequest"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public event Action CloseRequest + { + add => DeleteRequest += value; + remove => DeleteRequest -= value; + } +} +partial class ImporterMesh +{ + /// <inheritdoc cref="AddSurface(Mesh.PrimitiveType, Godot.Collections.Array, Godot.Collections.Array{Godot.Collections.Array}, Godot.Collections.Dictionary, Material, string, ulong)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public void AddSurface(Mesh.PrimitiveType primitive, Godot.Collections.Array arrays, Godot.Collections.Array<Godot.Collections.Array> blendShapes, Godot.Collections.Dictionary lods, Material material, string name, uint flags) + { + AddSurface(primitive, arrays, blendShapes, lods, material, name, (ulong)flags); + } +} + + partial class MeshInstance3D { /// <inheritdoc cref="CreateMultipleConvexCollisions(MeshConvexDecompositionSettings)"/> @@ -106,6 +201,26 @@ partial class SurfaceTool { AddTriangleFan(vertices, uvs, colors, uv2S, normals, new Godot.Collections.Array<Plane>(tangents)); } + + /// <inheritdoc cref="Commit(ArrayMesh, ulong)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public ArrayMesh Commit(ArrayMesh existing, uint flags) + { + return Commit(existing, (ulong)flags); + } +} + +partial class TileMap +{ + /// <summary> + /// The TileMap's quadrant size. Optimizes drawing by batching, using chunks of this size. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Never)] + public int CellQuadrantSize + { + get => RenderingQuadrantSize; + set => RenderingQuadrantSize = value; + } } partial class Tree @@ -127,3 +242,5 @@ partial class UndoRedo CreateAction(name, mergeMode, backwardUndoOps: false); } } + +#pragma warning restore CS1734 diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index d25944dceb..cc99225a33 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -723,7 +723,7 @@ namespace Godot /// <returns>A hash code for this AABB.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 10aeeae995..13c0cde1ef 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -36,6 +36,9 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Array"/> from the given collection's elements. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> /// <param name="collection">The collection of elements to construct from.</param> /// <returns>A new Godot Array.</returns> public Array(IEnumerable<Variant> collection) : this() @@ -50,6 +53,9 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Array"/> from the given objects. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> /// <param name="array">The objects to put in the new array.</param> /// <returns>A new Godot Array.</returns> public Array(Variant[] array) @@ -68,6 +74,13 @@ namespace Godot.Collections this[i] = array[i]; } + /// <summary> + /// Constructs a new <see cref="Array"/> from the given span's elements. + /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <returns>A new Godot Array.</returns> public Array(Span<StringName> array) { if (array == null) @@ -84,6 +97,13 @@ namespace Godot.Collections this[i] = array[i]; } + /// <summary> + /// Constructs a new <see cref="Array"/> from the given span's elements. + /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <returns>A new Godot Array.</returns> public Array(Span<NodePath> array) { if (array == null) @@ -100,6 +120,13 @@ namespace Godot.Collections this[i] = array[i]; } + /// <summary> + /// Constructs a new <see cref="Array"/> from the given span's elements. + /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <returns>A new Godot Array.</returns> public Array(Span<Rid> array) { if (array == null) @@ -121,6 +148,13 @@ namespace Godot.Collections // fine as long as the array is not mutated. However, Span does this type checking at // instantiation, so it's not possible to use it even when not mutating anything. // ReSharper disable once RedundantNameQualifier + /// <summary> + /// Constructs a new <see cref="Array"/> from the given ReadOnlySpan's elements. + /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <returns>A new Godot Array.</returns> public Array(ReadOnlySpan<GodotObject> array) { if (array == null) @@ -861,9 +895,15 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Array"/> to the given /// <see cref="Variant"/> C# array, starting at the given index. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. /// </exception> + /// <exception cref="ArgumentException"> + /// The destination array was not long enough. + /// </exception> /// <param name="array">The array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> public void CopyTo(Variant[] array, int arrayIndex) @@ -1031,6 +1071,7 @@ namespace Godot.Collections /// <summary> /// Constructs a new empty <see cref="Array{T}"/>. /// </summary> + /// <returns>A new Godot Array.</returns> public Array() { _underlyingArray = new Array(); @@ -1039,6 +1080,9 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Array{T}"/> from the given collection's elements. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> /// <param name="collection">The collection of elements to construct from.</param> /// <returns>A new Godot Array.</returns> public Array(IEnumerable<T> collection) @@ -1055,6 +1099,9 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Array{T}"/> from the given items. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> /// <param name="array">The items to put in the new array.</param> /// <returns>A new Godot Array.</returns> public Array(T[] array) @@ -1071,9 +1118,16 @@ namespace Godot.Collections /// <summary> /// Constructs a typed <see cref="Array{T}"/> from an untyped <see cref="Array"/>. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> /// <param name="array">The untyped array to construct from.</param> + /// <returns>A new Godot Array.</returns> public Array(Array array) { + if (array == null) + throw new ArgumentNullException(nameof(array)); + _underlyingArray = array; } @@ -1085,6 +1139,7 @@ namespace Godot.Collections /// Converts this typed <see cref="Array{T}"/> to an untyped <see cref="Array"/>. /// </summary> /// <param name="from">The typed array to convert.</param> + /// <returns>A new Godot Array, or <see langword="null"/> if <see paramref="from"/> was null.</returns> public static explicit operator Array(Array<T> from) { return from?._underlyingArray; @@ -1695,9 +1750,15 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Array{T}"/> to the given /// C# array, starting at the given index. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. /// </exception> + /// <exception cref="ArgumentException"> + /// The destination array was not long enough. + /// </exception> /// <param name="array">The C# array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> public void CopyTo(T[] array, int arrayIndex) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index d53dd9a9af..b4f7b82f60 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -239,7 +239,7 @@ namespace Godot /// <summary> /// Returns the basis's rotation in the form of Euler angles. - /// The Euler order depends on the [param order] parameter, + /// The Euler order depends on the <paramref name="order"/> parameter, /// by default it uses the YXZ convention: when decomposing, /// first Z, then X, and Y last. The returned vector contains /// the rotation angles in the format (X angle, Y angle, Z angle). @@ -1037,10 +1037,11 @@ namespace Godot } /// <summary> - /// Returns a Vector3 transformed (multiplied) by the transposed basis matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// basis matrix only if it represents a rotation-reflection. + /// Returns a Vector3 transformed (multiplied) by the inverse basis matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * basis</c> is equivalent to <c>basis.Transposed() * vector</c>. See <see cref="Transposed"/>. + /// For transforming by inverse of a non-orthonormal basis (e.g. with scaling) <c>basis.Inverse() * vector</c> can be used instead. See <see cref="Inverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely transform.</param> /// <param name="basis">The basis matrix transformation to apply.</param> @@ -1123,7 +1124,7 @@ namespace Godot /// <returns>A hash code for this basis.</returns> public override readonly int GetHashCode() { - return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); + return HashCode.Combine(Row0, Row1, Row2); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index 109643c2d4..a78cb0bba9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -25,10 +25,11 @@ namespace Godot.Bridge public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath; public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge; public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass; - public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, godot_bool*, godot_bool*, godot_string*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList; public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> ScriptManagerBridge_CallStatic; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set; public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get; @@ -70,6 +71,7 @@ namespace Godot.Bridge ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType, ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList, ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues, + ScriptManagerBridge_CallStatic = &ScriptManagerBridge.CallStatic, CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call, CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set, CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 0fe4bcdfce..80c26e5708 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -90,7 +91,7 @@ namespace Godot.Bridge internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, IntPtr godotObject) { - // TODO: Optimize with source generators and delegate pointers + // TODO: Optimize with source generators and delegate pointers. try { @@ -124,13 +125,15 @@ namespace Godot.Bridge IntPtr godotObject, godot_variant** args, int argCount) { - // TODO: Optimize with source generators and delegate pointers + // TODO: Optimize with source generators and delegate pointers. try { // Performance is not critical here as this will be replaced with source generators. Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + Debug.Assert(!scriptType.IsAbstract, $"Cannot create script instance. The class '{scriptType.FullName}' is abstract."); + var ctor = scriptType .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(c => c.GetParameters().Length == argCount) @@ -146,7 +149,7 @@ namespace Godot.Bridge else { throw new MissingMemberException( - $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters."); + $"The class '{scriptType.FullName}' does not define a constructor that takes {argCount} parameters."); } } @@ -277,7 +280,7 @@ namespace Godot.Bridge if (wrapperType != null && IsStatic(wrapperType)) { // A static class means this is a Godot singleton class. Try to get the Instance proxy type. - wrapperType = TypeGetProxyClass($"{nativeTypeNameStr}Instance"); + wrapperType = TypeGetProxyClass($"{wrapperType.Name}Instance"); if (wrapperType == null) { // Otherwise, fallback to GodotObject. @@ -597,7 +600,7 @@ namespace Godot.Bridge [UnmanagedCallersOnly] internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_string* outClassName, - godot_bool* outTool, godot_bool* outGlobal, godot_string* outIconPath, + godot_bool* outTool, godot_bool* outGlobal, godot_bool* outAbstract, godot_string* outIconPath, godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript) { @@ -631,9 +634,10 @@ namespace Godot.Bridge var iconAttr = scriptType.GetCustomAttributes(inherit: false) .OfType<IconAttribute>() .FirstOrDefault(); - *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path); + *outAbstract = scriptType.IsAbstract.ToGodotBool(); + // Methods // Performance is not critical here as this will be replaced with source generators. @@ -677,6 +681,8 @@ namespace Godot.Bridge methodInfo.Add("params", methodParams); + methodInfo.Add("flags", (int)method.Flags); + methods.Add(methodInfo); } } @@ -797,6 +803,7 @@ namespace Godot.Bridge *outClassName = default; *outTool = godot_bool.False; *outGlobal = godot_bool.False; + *outAbstract = godot_bool.False; *outIconPath = default; *outMethodsDest = NativeFuncs.godotsharp_array_new(); *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new(); @@ -958,6 +965,54 @@ namespace Godot.Bridge public godot_variant Value; // Not owned } + private delegate bool InvokeGodotClassStaticMethodDelegate(in godot_string_name method, NativeVariantPtrArgs args, out godot_variant ret); + + [UnmanagedCallersOnly] + internal static unsafe godot_bool CallStatic(IntPtr scriptPtr, godot_string_name* method, + godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret) + { + // TODO: Optimize with source generators and delegate pointers. + + try + { + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + + Type? top = scriptType; + Type native = GodotObject.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var invokeGodotClassStaticMethod = top.GetMethod( + "InvokeGodotClassStaticMethod", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (invokeGodotClassStaticMethod != null) + { + var invoked = invokeGodotClassStaticMethod.CreateDelegate<InvokeGodotClassStaticMethodDelegate>()( + CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args, argCount), out godot_variant retValue); + if (invoked) + { + *ret = retValue; + return godot_bool.True; + } + } + + top = top.BaseType; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *ret = default; + return godot_bool.False; + } + + *ret = default; + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD; + return godot_bool.False; + } + [UnmanagedCallersOnly] internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr, delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 5dddb38055..293e680067 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -1308,7 +1308,7 @@ namespace Godot /// <returns>A hash code for this color.</returns> public override readonly int GetHashCode() { - return R.GetHashCode() ^ G.GetHashCode() ^ B.GetHashCode() ^ A.GetHashCode(); + return HashCode.Combine(R, G, B, A); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 923b2adafd..2a72ebc32b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -385,6 +385,15 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Dictionary"/> to the given untyped /// <see cref="KeyValuePair{TKey, TValue}"/> array, starting at the given index. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </exception> + /// <exception cref="ArgumentException"> + /// The destination array was not long enough. + /// </exception> /// <param name="array">The array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> void ICollection<KeyValuePair<Variant, Variant>>.CopyTo(KeyValuePair<Variant, Variant>[] array, int arrayIndex) @@ -499,6 +508,7 @@ namespace Godot.Collections /// <summary> /// Constructs a new empty <see cref="Dictionary{TKey, TValue}"/>. /// </summary> + /// <returns>A new Godot Dictionary.</returns> public Dictionary() { _underlyingDict = new Dictionary(); @@ -507,6 +517,9 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="dictionary"/> is <see langword="null"/>. + /// </exception> /// <param name="dictionary">The dictionary to construct from.</param> /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) @@ -523,10 +536,16 @@ namespace Godot.Collections /// <summary> /// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="dictionary"/> is <see langword="null"/>. + /// </exception> /// <param name="dictionary">The dictionary to construct from.</param> /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { + if (dictionary == null) + throw new ArgumentNullException(nameof(dictionary)); + _underlyingDict = dictionary; } @@ -539,6 +558,7 @@ namespace Godot.Collections /// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>. /// </summary> /// <param name="from">The typed dictionary to convert.</param> + /// <returns>A new Godot Dictionary, or <see langword="null"/> if <see paramref="from"/> was null.</returns> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { return from?._underlyingDict; @@ -555,6 +575,8 @@ namespace Godot.Collections /// elements will be shallow copied regardless of the <paramref name="deep"/> /// setting. /// </summary> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { return new Dictionary<TKey, TValue>(_underlyingDict.Duplicate(deep)); @@ -688,6 +710,9 @@ namespace Godot.Collections /// <exception cref="InvalidOperationException"> /// The dictionary is read-only. /// </exception> + /// <exception cref="ArgumentException"> + /// An element with the same <paramref name="key"/> already exists. + /// </exception> /// <param name="key">The key at which to add the object.</param> /// <param name="value">The object to add.</param> public void Add(TKey key, TValue value) @@ -810,6 +835,15 @@ namespace Godot.Collections /// Copies the elements of this <see cref="Dictionary{TKey, TValue}"/> to the given /// untyped C# array, starting at the given index. /// </summary> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="array"/> is <see langword="null"/>. + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </exception> + /// <exception cref="ArgumentException"> + /// The destination array was not long enough. + /// </exception> /// <param name="array">The array to copy to.</param> /// <param name="arrayIndex">The index to start at.</param> void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index c6337e56ef..43598ca84d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -247,6 +247,18 @@ namespace Godot return methodBind; } + internal static IntPtr ClassDB_get_method_with_compatibility(StringName type, StringName method, ulong hash) + { + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method_with_compatibility(typeSelf, methodSelf, hash); + + if (methodBind == IntPtr.Zero) + throw new NativeMethodBindNotFoundException(type + "." + method); + + return methodBind; + } + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 81b2ffef34..09269508b7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -134,6 +134,38 @@ namespace Godot } /// <summary> + /// Returns the difference between the two angles, + /// in range of -<see cref="Pi"/>, <see cref="Pi"/>. + /// When <paramref name="from"/> and <paramref name="to"/> are opposite, + /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>, + /// or <see cref="Pi"/> otherwise. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The destination angle.</param> + /// <returns>The difference between the two angles.</returns> + public static float AngleDifference(float from, float to) + { + float difference = (to - from) % MathF.Tau; + return ((2.0f * difference) % MathF.Tau) - difference; + } + + /// <summary> + /// Returns the difference between the two angles, + /// in range of -<see cref="Pi"/>, <see cref="Pi"/>. + /// When <paramref name="from"/> and <paramref name="to"/> are opposite, + /// returns -<see cref="Pi"/> if <paramref name="from"/> is smaller than <paramref name="to"/>, + /// or <see cref="Pi"/> otherwise. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The destination angle.</param> + /// <returns>The difference between the two angles.</returns> + public static double AngleDifference(double from, double to) + { + double difference = (to - from) % Math.Tau; + return ((2.0 * difference) % Math.Tau) - difference; + } + + /// <summary> /// Returns the arc sine of <paramref name="s"/> in radians. /// Use to get the angle of sine <paramref name="s"/>. /// </summary> @@ -1093,9 +1125,7 @@ namespace Godot /// <returns>The resulting angle of the interpolation.</returns> public static float LerpAngle(float from, float to, float weight) { - float difference = (to - from) % MathF.Tau; - float distance = ((2 * difference) % MathF.Tau) - difference; - return from + (distance * weight); + return from + AngleDifference(from, to) * weight; } /// <summary> @@ -1110,9 +1140,7 @@ namespace Godot /// <returns>The resulting angle of the interpolation.</returns> public static double LerpAngle(double from, double to, double weight) { - double difference = (to - from) % Math.Tau; - double distance = ((2 * difference) % Math.Tau) - difference; - return from + (distance * weight); + return from + AngleDifference(from, to) * weight; } /// <summary> @@ -1429,6 +1457,38 @@ namespace Godot } /// <summary> + /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>. + /// Similar to <see cref="MoveToward(float, float, float)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>. + /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The angle to move towards.</param> + /// <param name="delta">The amount to move by.</param> + /// <returns>The angle after moving.</returns> + public static float RotateToward(float from, float to, float delta) + { + float difference = AngleDifference(from, to); + float absDifference = Math.Abs(difference); + return from + Math.Clamp(delta, absDifference - MathF.PI, absDifference) * (difference >= 0.0f ? 1.0f : -1.0f); + } + + /// <summary> + /// Rotates <paramref name="from"/> toward <paramref name="to"/> by the <paramref name="delta"/> amount. Will not go past <paramref name="to"/>. + /// Similar to <see cref="MoveToward(double, double, double)"/> but interpolates correctly when the angles wrap around <see cref="Tau"/>. + /// If <paramref name="delta"/> is negative, this function will rotate away from <paramref name="to"/>, toward the opposite angle, and will not go past the opposite angle. + /// </summary> + /// <param name="from">The start angle.</param> + /// <param name="to">The angle to move towards.</param> + /// <param name="delta">The amount to move by.</param> + /// <returns>The angle after moving.</returns> + public static double RotateToward(double from, double to, double delta) + { + double difference = AngleDifference(from, to); + double absDifference = Math.Abs(difference); + return from + Math.Clamp(delta, absDifference - Math.PI, absDifference) * (difference >= 0.0 ? 1.0 : -1.0); + } + + /// <summary> /// Rounds <paramref name="s"/> to the nearest whole number, /// with halfway cases rounded towards the nearest multiple of two. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index a656c5de90..dc53e48bd0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -206,7 +206,7 @@ namespace Godot.NativeInterop } case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS: case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS: - return $"Invalid call to {where}. Expected {error.Argument} arguments."; + return $"Invalid call to {where}. Expected {error.Expected} arguments."; case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD: return $"Invalid call. Nonexistent {where}."; case godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL: diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index d42ee15657..d181bf2c0f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -42,6 +42,9 @@ namespace Godot.NativeInterop public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, in godot_string_name p_methodname); + public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility( + in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash); + public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( in godot_string_name p_classname); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 3c7455a76c..85b2b02c45 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -414,7 +414,7 @@ namespace Godot /// <returns>A hash code for this plane.</returns> public override readonly int GetHashCode() { - return _normal.GetHashCode() ^ _d.GetHashCode(); + return HashCode.Combine(_normal, _d); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 998a2786a7..155e90ff24 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -905,7 +905,8 @@ namespace Godot } /// <summary> - /// Returns a Vector4 transformed (multiplied) by the inverse projection. + /// Returns a Vector4 transformed (multiplied) by the transpose of the projection. + /// For transforming by inverse of a projection <c>projection.Inverse() * vector</c> can be used instead. See <see cref="Inverse"/>. /// </summary> /// <param name="proj">The projection to apply.</param> /// <param name="vector">A Vector4 to transform.</param> @@ -1001,7 +1002,7 @@ namespace Godot /// <returns>A hash code for this projection.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 2e282447bd..3d45913586 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -644,6 +644,7 @@ namespace Godot /// <summary> /// Returns a Vector3 rotated (multiplied) by the inverse quaternion. + /// <c>vector * quaternion</c> is equivalent to <c>quaternion.Inverse() * vector</c>. See <see cref="Inverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely rotate.</param> /// <param name="quaternion">The quaternion to rotate by.</param> @@ -800,7 +801,7 @@ namespace Godot /// <returns>A hash code for this quaternion.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 458802f95d..babb26960b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -459,7 +459,7 @@ namespace Godot /// <returns>A hash code for this rect.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs index 2099d0abca..49fba02b54 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs @@ -419,7 +419,7 @@ namespace Godot /// <returns>A hash code for this rect.</returns> public override readonly int GetHashCode() { - return _position.GetHashCode() ^ _size.GetHashCode(); + return HashCode.Combine(_position, _size); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 618c892681..386f587464 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -126,7 +126,7 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation, scaling, and translation. + /// the basis is invertible (must have non-zero determinant). /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> @@ -180,11 +180,12 @@ namespace Godot } /// <summary> - /// Returns a vector transformed (multiplied) by the inverse basis matrix. + /// Returns a vector transformed (multiplied) by the inverse basis matrix, + /// under the assumption that the basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). /// This method does not account for translation (the <see cref="Origin"/> vector). - /// - /// Note: This results in a multiplication by the inverse of the - /// basis matrix only if it represents a rotation-reflection. + /// <c>transform.BasisXformInv(vector)</c> is equivalent to <c>transform.Inverse().BasisXform(vector)</c>. See <see cref="Inverse"/>. + /// For non-orthonormal transforms (e.g. with scaling) <c>transform.AffineInverse().BasisXform(vector)</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <seealso cref="BasisXform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> @@ -213,8 +214,9 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation and translation - /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for + /// non-orthonormal transforms (e.g. with scaling). /// </summary> /// <returns>The inverse matrix.</returns> public readonly Transform2D Inverse() @@ -480,7 +482,11 @@ namespace Godot } /// <summary> - /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix. + /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="vector">A Vector2 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -507,7 +513,11 @@ namespace Godot } /// <summary> - /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix. + /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>rect * transform</c> is equivalent to <c>transform.Inverse() * rect</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * rect</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="rect">A Rect2 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -541,7 +551,11 @@ namespace Godot } /// <summary> - /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix. + /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="array">A Vector2[] to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -626,7 +640,7 @@ namespace Godot /// <returns>A hash code for this transform.</returns> public override readonly int GetHashCode() { - return X.GetHashCode() ^ Y.GetHashCode() ^ Origin.GetHashCode(); + return HashCode.Combine(X, Y, Origin); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index b16e6e592e..2d09259dcb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -105,7 +105,7 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation, scaling, and translation. + /// the basis is invertible (must have non-zero determinant). /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> @@ -144,8 +144,9 @@ namespace Godot /// <summary> /// Returns the inverse of the transform, under the assumption that - /// the transformation is composed of rotation and translation - /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). + /// the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for + /// non-orthonormal transforms (e.g. with scaling). /// </summary> /// <returns>The inverse matrix.</returns> public readonly Transform3D Inverse() @@ -426,10 +427,11 @@ namespace Godot } /// <summary> - /// Returns a Vector3 transformed (multiplied) by the transposed transformation matrix. - /// - /// Note: This results in a multiplication by the inverse of the - /// transformation matrix only if it represents a rotation-reflection. + /// Returns a Vector3 transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="vector">A Vector3 to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -482,7 +484,11 @@ namespace Godot } /// <summary> - /// Returns an AABB transformed (multiplied) by the inverse transformation matrix. + /// Returns an AABB transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>aabb * transform</c> is equivalent to <c>transform.Inverse() * aabb</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * aabb</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="aabb">An AABB to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -523,6 +529,7 @@ namespace Godot /// <summary> /// Returns a Plane transformed (multiplied) by the inverse transformation matrix. + /// <c>plane * transform</c> is equivalent to <c>transform.AffineInverse() * plane</c>. See <see cref="AffineInverse"/>. /// </summary> /// <param name="plane">A Plane to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -568,7 +575,11 @@ namespace Godot } /// <summary> - /// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix. + /// Returns a copy of the given Vector3[] transformed (multiplied) by the inverse transformation matrix, + /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection + /// is fine, scaling/skew is not). + /// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>. + /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>. /// </summary> /// <param name="array">A Vector3[] to inversely transform.</param> /// <param name="transform">The transformation to apply.</param> @@ -653,7 +664,7 @@ namespace Godot /// <returns>A hash code for this transform.</returns> public override readonly int GetHashCode() { - return Basis.GetHashCode() ^ Origin.GetHashCode(); + return HashCode.Combine(Basis, Origin); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 642ef231f3..4842dbc9af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -1000,7 +1000,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode(); + return HashCode.Combine(X, Y); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 231e791904..215bb4df8c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -182,6 +182,9 @@ namespace Godot } // Constants + private static readonly Vector2I _minValue = new Vector2I(int.MinValue, int.MinValue); + private static readonly Vector2I _maxValue = new Vector2I(int.MaxValue, int.MaxValue); + private static readonly Vector2I _zero = new Vector2I(0, 0); private static readonly Vector2I _one = new Vector2I(1, 1); @@ -191,6 +194,17 @@ namespace Godot private static readonly Vector2I _left = new Vector2I(-1, 0); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector2.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector2I(int.MinValue, int.MinValue)</c>.</value> + public static Vector2I MinValue { get { return _minValue; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector2.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector2I(int.MaxValue, int.MaxValue)</c>.</value> + public static Vector2I MaxValue { get { return _maxValue; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector2I(0, 0)</c>.</value> @@ -542,7 +556,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode(); + return HashCode.Combine(X, Y); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 7d548f1d10..d26d4662a0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -265,7 +265,7 @@ namespace Godot return new Vector3( Mathf.BezierDerivative(X, control1.X, control2.X, end.X, t), Mathf.BezierDerivative(Y, control1.Y, control2.Y, end.Y, t), - Mathf.BezierDerivative(Z, control1.Z, control2.Z, end.Y, t) + Mathf.BezierDerivative(Z, control1.Z, control2.Z, end.Z, t) ); } @@ -1102,7 +1102,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode(); + return HashCode.Combine(X, Y, Z); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index 8543052f56..fe74ec8884 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -193,6 +193,9 @@ namespace Godot } // Constants + private static readonly Vector3I _minValue = new Vector3I(int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector3I _maxValue = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector3I _zero = new Vector3I(0, 0, 0); private static readonly Vector3I _one = new Vector3I(1, 1, 1); @@ -204,6 +207,17 @@ namespace Godot private static readonly Vector3I _back = new Vector3I(0, 0, 1); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector3.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector3I(int.MinValue, int.MinValue, int.MinValue)</c>.</value> + public static Vector3I MinValue { get { return _minValue; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector3.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> + public static Vector3I MaxValue { get { return _maxValue; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector3I(0, 0, 0)</c>.</value> @@ -597,7 +611,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode(); + return HashCode.Combine(X, Y, Z); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 10a0b14162..eeaef5e46e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -884,7 +884,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index f813903177..a0a4393523 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -228,10 +228,24 @@ namespace Godot } // Constants + private static readonly Vector4I _minValue = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector4I _maxValue = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector4I _zero = new Vector4I(0, 0, 0, 0); private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1); /// <summary> + /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector4.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue)</c>.</value> + public static Vector4I MinValue { get { return _minValue; } } + /// <summary> + /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector4.Inf"/>. + /// </summary> + /// <value>Equivalent to <c>new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> + public static Vector4I MaxValue { get { return _maxValue; } } + + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> /// <value>Equivalent to <c>new Vector4I(0, 0, 0, 0)</c>.</value> @@ -618,7 +632,7 @@ namespace Godot /// <returns>A hash code for this vector.</returns> public override readonly int GetHashCode() { - return Y.GetHashCode() ^ X.GetHashCode() ^ Z.GetHashCode() ^ W.GetHashCode(); + return HashCode.Combine(X, Y, Z, W); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 8a36b3e514..a55b8d693b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -11,9 +11,6 @@ <LangVersion>10</LangVersion> <AnalysisMode>Recommended</AnalysisMode> - - <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. --> - <NoWarn>CS1591</NoWarn> </PropertyGroup> <PropertyGroup> <Description>Godot C# Core API.</Description> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 24a9d4030a..3518507f8c 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -68,6 +68,10 @@ MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, con return ClassDB::get_method(*p_classname, *p_methodname); } +MethodBind *godotsharp_method_bind_get_method_with_compatibility(const StringName *p_classname, const StringName *p_methodname, uint64_t p_hash) { + return ClassDB::get_method_with_compatibility(*p_classname, *p_methodname, p_hash); +} + godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) { ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname); if (class_info) { @@ -1416,6 +1420,7 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { static const void *unmanaged_callbacks[]{ (void *)godotsharp_dotnet_module_is_initialized, (void *)godotsharp_method_bind_get_method, + (void *)godotsharp_method_bind_get_method_with_compatibility, (void *)godotsharp_get_class_constructor, (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index faf3ae7b04..c55c5d8111 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -89,7 +89,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better r_return_value = Variant(); - ERR_FAIL_COND(delegate_handle.value == nullptr); + ERR_FAIL_NULL(delegate_handle.value); GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs( delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 247968e251..3eb746677d 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -322,7 +322,7 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) #if defined(WINDOWS_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll"); -#elif defined(MACOS_ENABLED) +#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib"); #elif defined(UNIX_ENABLED) String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so"); @@ -330,28 +330,33 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) #error "Platform not supported (yet?)" #endif - if (FileAccess::exists(native_aot_so_path)) { - Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle); - - if (err != OK) { - return nullptr; - } + Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle); - void *lib = r_aot_dll_handle; + if (err != OK) { + return nullptr; + } - void *symbol = nullptr; + void *lib = r_aot_dll_handle; - err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol); - ERR_FAIL_COND_V(err != OK, nullptr); - return (godot_plugins_initialize_fn)symbol; - } + void *symbol = nullptr; - return nullptr; + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol); + ERR_FAIL_COND_V(err != OK, nullptr); + return (godot_plugins_initialize_fn)symbol; } #endif } // namespace +bool GDMono::should_initialize() { +#ifdef TOOLS_ENABLED + // The editor always needs to initialize the .NET module for now. + return true; +#else + return OS::get_singleton()->has_feature("dotnet"); +#endif +} + static bool _on_core_api_assembly_loaded() { if (!GDMonoCache::godot_api_cache_updated) { return false; @@ -376,11 +381,13 @@ void GDMono::initialize() { godot_plugins_initialize_fn godot_plugins_initialize = nullptr; +#if !defined(IOS_ENABLED) // Check that the .NET assemblies directory exists before trying to use it. if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) { OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found")); ERR_FAIL_MSG(".NET: Assemblies not found"); } +#endif if (!load_hostfxr(hostfxr_dll_handle)) { #if !defined(TOOLS_ENABLED) @@ -437,11 +444,15 @@ void GDMono::initialize() { _on_core_api_assembly_loaded(); +#ifdef TOOLS_ENABLED + _try_load_project_assembly(); +#endif + initialized = true; } #ifdef TOOLS_ENABLED -void GDMono::initialize_load_assemblies() { +void GDMono::_try_load_project_assembly() { if (Engine::get_singleton()->is_project_manager_hint()) { return; } @@ -545,12 +556,6 @@ GDMono::GDMono() { GDMono::~GDMono() { finalizing_scripts_domain = true; - if (is_runtime_initialized()) { - if (GDMonoCache::godot_api_cache_updated) { - GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown(); - } - } - if (hostfxr_dll_handle) { OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle); } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index c629ab2eff..530936cfb4 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -72,6 +72,7 @@ class GDMono { #ifdef TOOLS_ENABLED bool _load_project_assembly(); + void _try_load_project_assembly(); #endif uint64_t api_core_hash = 0; @@ -149,10 +150,9 @@ public: Error reload_project_assemblies(); #endif + bool should_initialize(); + void initialize(); -#ifdef TOOLS_ENABLED - void initialize_load_assemblies(); -#endif GDMono(); ~GDMono(); diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 8fdf163b26..ef4e32e4a7 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -42,7 +42,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { #define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \ { \ - ERR_FAIL_COND_MSG(m_var == nullptr, \ + ERR_FAIL_NULL_MSG(m_var, \ "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \ checked_count += 1; \ } @@ -70,6 +70,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList); CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CallStatic); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index f604e4d681..dac8cdcaef 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -91,10 +91,11 @@ struct ManagedCallbacks { using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *); using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *); using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *); - using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, String *, bool *, bool *, bool *, String *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool); using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add); using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add); + using FuncScriptManagerBridge_CallStatic = bool(GD_CLR_STDCALL *)(const CSharpScript *, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *); using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *); @@ -130,6 +131,7 @@ struct ManagedCallbacks { FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType; FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList; FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues; + FuncScriptManagerBridge_CallStatic ScriptManagerBridge_CallStatic; FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call; FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set; FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get; diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h deleted file mode 100644 index cb397c8b46..0000000000 --- a/modules/mono/mono_gd/support/ios_support.h +++ /dev/null @@ -1,50 +0,0 @@ -/**************************************************************************/ -/* ios_support.h */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 IOS_SUPPORT_H -#define IOS_SUPPORT_H - -#if defined(IOS_ENABLED) - -#include "core/string/ustring.h" - -namespace gdmono { -namespace ios { -namespace support { - -void initialize(); -void cleanup(); -} // namespace support -} // namespace ios -} // namespace gdmono - -#endif // IOS_ENABLED - -#endif // IOS_SUPPORT_H diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm deleted file mode 100644 index df8b3e2626..0000000000 --- a/modules/mono/mono_gd/support/ios_support.mm +++ /dev/null @@ -1,150 +0,0 @@ -/**************************************************************************/ -/* ios_support.mm */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* 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 "ios_support.h" - -#if defined(IOS_ENABLED) - -#include "../gd_mono_marshal.h" - -#include "core/ustring.h" - -#import <Foundation/Foundation.h> -#include <os/log.h> - -// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m - -// Definition generated by the Godot exporter -extern "C" void gd_mono_setup_aot(); - -namespace gdmono { -namespace ios { -namespace support { - -void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) { - os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message); - if (fatal) { - os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1); - exit(1); - } -} - -void initialize() { - mono_dllmap_insert(nullptr, "System.Native", nullptr, "__Internal", nullptr); - mono_dllmap_insert(nullptr, "System.IO.Compression.Native", nullptr, "__Internal", nullptr); - mono_dllmap_insert(nullptr, "System.Security.Cryptography.Native.Apple", nullptr, "__Internal", nullptr); - -#ifdef IOS_DEVICE - // This function is defined in an auto-generated source file - gd_mono_setup_aot(); -#endif - - mono_set_signal_chaining(true); - mono_set_crash_chaining(true); -} - -void cleanup() { -} -} // namespace support -} // namespace ios -} // namespace gdmono - -// The following are P/Invoke functions required by the monotouch profile of the BCL. -// These are P/Invoke functions and not internal calls, hence why they use -// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'. - -#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default"))) - -GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() { - NSLocale *locale = [NSLocale currentLocale]; - NSString *countryCode = [locale objectForKey:NSLocaleCountryCode]; - if (countryCode == nullptr) { - return strdup("US"); - } - return strdup([countryCode UTF8String]); -} - -GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) { - int length = 0; - const uint16_t *ptr = p_unicode_message; - while (*ptr++) { - length += sizeof(uint16_t); - } - NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding]; - - os_log_info(OS_LOG_DEFAULT, "%{public}@", msg); -} - -GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) { - NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder; - NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject]; - NSString *path = [url path]; - return strdup([path UTF8String]); -} - -GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() { - NSTimeZone *tz = nil; - tz = [NSTimeZone localTimeZone]; - NSString *name = [tz name]; - return (name != nil) ? strdup([name UTF8String]) : strdup("Local"); -} - -GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) { - NSArray *array = [NSTimeZone knownTimeZoneNames]; - *p_count = array.count; - char **result = (char **)malloc(sizeof(char *) * (*p_count)); - for (uint32_t i = 0; i < *p_count; i++) { - NSString *s = [array objectAtIndex:i]; - result[i] = strdup(s.UTF8String); - } - return result; -} - -GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before - NSTimeZone *tz = nil; - if (p_name) { - NSString *n = [[NSString alloc] initWithUTF8String:p_name]; - tz = [[NSTimeZone alloc] initWithName:n]; - } else { - tz = [NSTimeZone localTimeZone]; - } - NSData *data = [tz data]; - *p_size = [data length]; - void *result = malloc(*p_size); - memcpy(result, data.bytes, *p_size); - return result; -} - -GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) { - // FIXME: What's this for? No idea how to implement. - os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'"); -} - -#endif // IOS_ENABLED diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index 0051d82e99..eab1f5d51d 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -112,9 +112,9 @@ void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) { void ReplicationEditor::_pick_node_selected(NodePath p_path) { Node *root = current->get_node(current->get_root_path()); - ERR_FAIL_COND(!root); + ERR_FAIL_NULL(root); Node *node = get_node(p_path); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); NodePath path_to = root->get_path_to(node); adding_node_path = path_to; prop_selector->select_property_from_instance(node); @@ -245,24 +245,29 @@ ReplicationEditor::ReplicationEditor() { add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property)); add_pick_button->set_text(TTR("Add property to sync...")); hb->add_child(add_pick_button); + VSeparator *vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); hb->add_child(vs); hb->add_child(memnew(Label(TTR("Path:")))); + np_line_edit = memnew(LineEdit); np_line_edit->set_placeholder(":property"); np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); np_line_edit->connect("text_submitted", callable_mp(this, &ReplicationEditor::_np_text_submitted)); hb->add_child(np_line_edit); + add_from_path_button = memnew(Button); add_from_path_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed)); add_from_path_button->set_text(TTR("Add from path")); hb->add_child(add_from_path_button); + vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); hb->add_child(vs); + pin = memnew(Button); - pin->set_flat(true); + pin->set_theme_type_variation("FlatButton"); pin->set_toggle_mode(true); hb->add_child(pin); @@ -359,7 +364,7 @@ void ReplicationEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel"))); add_pick_button->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons))); pin->set_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons))); } break; diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp index ea52741601..a4d2aed2d6 100644 --- a/modules/multiplayer/multiplayer_debugger.cpp +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -237,10 +237,10 @@ void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_proces // ReplicationProfiler MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) { - ERR_FAIL_COND(!p_sync); + ERR_FAIL_NULL(p_sync); synchronizer = p_sync->get_instance_id(); - if (p_sync->get_replication_config().is_valid()) { - config = p_sync->get_replication_config()->get_instance_id(); + if (p_sync->get_replication_config_ptr()) { + config = p_sync->get_replication_config_ptr()->get_instance_id(); } if (p_sync->get_root_node()) { root_node = p_sync->get_root_node()->get_instance_id(); @@ -305,7 +305,7 @@ void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) { const ObjectID id = p_data[1]; const uint64_t size = p_data[2]; MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id)); - ERR_FAIL_COND(!sync); + ERR_FAIL_NULL(sync); if (!sync_data.has(id)) { sync_data[id] = SyncInfo(sync); } diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 6c6aa28344..264a2e9c8e 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -268,7 +268,7 @@ void MultiplayerSpawner::_spawn_notify(ObjectID p_id) { void MultiplayerSpawner::_node_exit(ObjectID p_id) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); if (tracked_nodes.has(p_id)) { tracked_nodes.erase(p_id); get_multiplayer()->object_configuration_remove(node, this); @@ -323,10 +323,10 @@ Node *MultiplayerSpawner::spawn(const Variant &p_data) { ERR_FAIL_COND_V_MSG(!spawn_function.is_valid(), nullptr, "Custom spawn requires the 'spawn_function' property to be a valid callable."); Node *parent = get_spawn_node(); - ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); + ERR_FAIL_NULL_V_MSG(parent, nullptr, "Cannot find spawn node."); Node *node = instantiate_custom(p_data); - ERR_FAIL_COND_V_MSG(!node, nullptr, "The 'spawn_function' callable must return a valid node."); + ERR_FAIL_NULL_V_MSG(node, nullptr, "The 'spawn_function' callable must return a valid node."); _track(node, p_data); parent->add_child(node, true); diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp index 9c2d281f72..12b4ac540d 100644 --- a/modules/multiplayer/multiplayer_synchronizer.cpp +++ b/modules/multiplayer/multiplayer_synchronizer.cpp @@ -107,6 +107,9 @@ void MultiplayerSynchronizer::reset() { net_id = 0; last_sync_usec = 0; last_inbound_sync = 0; + last_watch_usec = 0; + sync_started = false; + watchers.clear(); } uint32_t MultiplayerSynchronizer::get_net_id() const { @@ -131,7 +134,9 @@ bool MultiplayerSynchronizer::update_outbound_sync_time(uint64_t p_usec) { } bool MultiplayerSynchronizer::update_inbound_sync_time(uint16_t p_network_time) { - if (p_network_time <= last_inbound_sync && last_inbound_sync - p_network_time < 32767) { + if (!sync_started) { + sync_started = true; + } else if (p_network_time <= last_inbound_sync && last_inbound_sync - p_network_time < 32767) { return false; } last_inbound_sync = p_network_time; @@ -149,14 +154,14 @@ PackedStringArray MultiplayerSynchronizer::get_configuration_warnings() const { } Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER); r_variant.resize(p_properties.size()); r_variant_ptrs.resize(r_variant.size()); int i = 0; for (const NodePath &prop : p_properties) { bool valid = false; const Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); + ERR_FAIL_NULL_V(obj, FAILED); r_variant.write[i] = obj->get_indexed(prop.get_subnames(), &valid); r_variant_ptrs.write[i] = &r_variant[i]; ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop)); @@ -166,11 +171,11 @@ Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Obj } Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) { - ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_obj, ERR_INVALID_PARAMETER); int i = 0; for (const NodePath &prop : p_properties) { Object *obj = _get_prop_target(p_obj, prop); - ERR_FAIL_COND_V(!obj, FAILED); + ERR_FAIL_NULL_V(obj, FAILED); obj->set_indexed(prop.get_subnames(), p_state[i]); i += 1; } @@ -343,6 +348,9 @@ void MultiplayerSynchronizer::update_visibility(int p_for_peer) { } void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) { + if (p_path == root_path) { + return; + } _stop(); root_path = p_path; _start(); @@ -353,15 +361,12 @@ NodePath MultiplayerSynchronizer::get_root_path() const { } void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) { - Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr; - if (!node || get_multiplayer_authority() == p_peer_id) { - Node::set_multiplayer_authority(p_peer_id, p_recursive); + if (get_multiplayer_authority() == p_peer_id) { return; } - - get_multiplayer()->object_configuration_remove(node, this); + _stop(); Node::set_multiplayer_authority(p_peer_id, p_recursive); - get_multiplayer()->object_configuration_add(node, this); + _start(); } Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) { @@ -374,7 +379,7 @@ Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) { return OK; } Node *node = get_root_node(); - ERR_FAIL_COND_V(!node, FAILED); + ERR_FAIL_NULL_V(node, FAILED); int idx = -1; Watcher *ptr = watchers.ptrw(); for (const NodePath &prop : props) { @@ -382,7 +387,7 @@ Error MultiplayerSynchronizer::_watch_changes(uint64_t p_usec) { bool valid = false; const Object *obj = _get_prop_target(node, prop); ERR_CONTINUE_MSG(!obj, vformat("Node not found for property '%s'.", prop)); - Variant v = obj->get(prop.get_concatenated_subnames(), &valid); + Variant v = obj->get_indexed(prop.get_subnames(), &valid); ERR_CONTINUE_MSG(!valid, vformat("Property '%s' not found.", prop)); Watcher &w = ptr[idx]; if (w.prop != prop) { @@ -441,6 +446,10 @@ List<NodePath> MultiplayerSynchronizer::get_delta_properties(uint64_t p_indexes) return out; } +SceneReplicationConfig *MultiplayerSynchronizer::get_replication_config_ptr() const { + return replication_config.ptr(); +} + MultiplayerSynchronizer::MultiplayerSynchronizer() { // Publicly visible by default. peer_visibility.insert(0); diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h index 7b77e691d1..192d7a5920 100644 --- a/modules/multiplayer/multiplayer_synchronizer.h +++ b/modules/multiplayer/multiplayer_synchronizer.h @@ -66,6 +66,7 @@ private: uint64_t last_sync_usec = 0; uint16_t last_inbound_sync = 0; uint32_t net_id = 0; + bool sync_started = false; static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop); void _start(); @@ -118,6 +119,7 @@ public: List<Variant> get_delta_state(uint64_t p_cur_usec, uint64_t p_last_usec, uint64_t &r_indexes); List<NodePath> get_delta_properties(uint64_t p_indexes); + SceneReplicationConfig *get_replication_config_ptr() const; MultiplayerSynchronizer(); }; diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 90e6ac7c2c..56cd0bec18 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -44,16 +44,15 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { path_get_cache.erase(p_id); // Cleanup sent cache. // Some refactoring is needed to make this faster and do paths GC. - for (const KeyValue<NodePath, PathSentCache> &E : path_send_cache) { - PathSentCache *psc = path_send_cache.getptr(E.key); - psc->confirmed_peers.erase(p_id); + for (KeyValue<ObjectID, PathSentCache> &E : path_send_cache) { + E.value.confirmed_peers.erase(p_id); } } } void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_packet, int p_packet_len) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND(!root_node); + ERR_FAIL_NULL(root_node); ERR_FAIL_COND_MSG(p_packet_len < 38, "Invalid packet received. Size too small."); int ofs = 1; @@ -67,21 +66,21 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac String paths; paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs); - NodePath path = paths; + const NodePath path = paths; if (!path_get_cache.has(p_from)) { path_get_cache[p_from] = PathGetCache(); } Node *node = root_node->get_node(path); - ERR_FAIL_COND(node == nullptr); + ERR_FAIL_NULL(node); const bool valid_rpc_checksum = multiplayer->get_rpc_md5(node) == methods_md5; if (valid_rpc_checksum == false) { ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); } PathGetCache::NodeInfo ni; - ni.path = path; + ni.path = node->get_path(); path_get_cache[p_from].nodes[id] = ni; @@ -106,29 +105,34 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) { ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small."); + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_NULL(root_node); const bool valid_rpc_checksum = p_packet[1]; String paths; paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2); - NodePath path = paths; + const NodePath path = paths; if (valid_rpc_checksum == false) { ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); } - PathSentCache *psc = path_send_cache.getptr(path); - ERR_FAIL_COND_MSG(!psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); + Node *node = root_node->get_node(path); + ERR_FAIL_NULL(node); + + PathSentCache *psc = path_send_cache.getptr(node->get_instance_id()); + ERR_FAIL_NULL_MSG(psc, "Invalid packet received. Tries to confirm a path which was not found in cache."); HashMap<int, bool>::Iterator E = psc->confirmed_peers.find(p_from); ERR_FAIL_COND_MSG(!E, "Invalid packet received. Source peer was not found in cache for the given path."); E->value = true; } -Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers) { +Error SceneCacheInterface::_send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers) { // Encode function name. - const CharString path = String(p_path).utf8(); + const CharString path = String(multiplayer->get_root_path().rel_path_to(p_node->get_path())).utf8(); const int path_len = encode_cstring(path.get_data(), nullptr); // Extract MD5 from rpc methods list. @@ -163,9 +167,10 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat return err; } -bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { - const PathSentCache *psc = path_send_cache.getptr(p_path); - ERR_FAIL_COND_V(!psc, false); +bool SceneCacheInterface::is_cache_confirmed(Node *p_node, int p_peer) { + ERR_FAIL_NULL_V(p_node, false); + const PathSentCache *psc = path_send_cache.getptr(p_node->get_instance_id()); + ERR_FAIL_NULL_V(psc, false); HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer); ERR_FAIL_COND_V(!F, false); // Should never happen. return F->value; @@ -173,14 +178,14 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) { int SceneCacheInterface::make_object_cache(Object *p_obj) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, -1); - NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); + ERR_FAIL_NULL_V(node, -1); + const ObjectID oid = node->get_instance_id(); // See if the path is cached. - PathSentCache *psc = path_send_cache.getptr(for_path); + PathSentCache *psc = path_send_cache.getptr(oid); if (!psc) { // Path is not cached, create. - path_send_cache[for_path] = PathSentCache(); - psc = path_send_cache.getptr(for_path); + path_send_cache[oid] = PathSentCache(); + psc = path_send_cache.getptr(oid); psc->id = last_send_cache_id++; } return psc->id; @@ -188,12 +193,17 @@ int SceneCacheInterface::make_object_cache(Object *p_obj) { bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) { Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, false); - - r_id = make_object_cache(p_obj); - ERR_FAIL_COND_V(r_id < 0, false); - NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path()); - PathSentCache *psc = path_send_cache.getptr(for_path); + ERR_FAIL_NULL_V(node, false); + const ObjectID oid = node->get_instance_id(); + // See if the path is cached. + PathSentCache *psc = path_send_cache.getptr(oid); + if (!psc) { + // Path is not cached, create. + path_send_cache[oid] = PathSentCache(); + psc = path_send_cache.getptr(oid); + psc->id = last_send_cache_id++; + } + r_id = psc->id; bool has_all_peers = true; List<int> peers_to_add; // If one is missing, take note to add it. @@ -225,7 +235,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r } if (peers_to_add.size()) { - _send_confirm_path(node, for_path, psc, peers_to_add); + _send_confirm_path(node, psc, peers_to_add); } return has_all_peers; @@ -233,7 +243,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND_V(!root_node, nullptr); + ERR_FAIL_NULL_V(root_node, nullptr); HashMap<int, PathGetCache>::Iterator E = path_get_cache.find(p_from); ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("No cache found for peer %d.", p_from)); diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h index 7a7304fde8..e63beb5f84 100644 --- a/modules/multiplayer/scene_cache_interface.h +++ b/modules/multiplayer/scene_cache_interface.h @@ -58,12 +58,12 @@ private: HashMap<int, NodeInfo> nodes; }; - HashMap<NodePath, PathSentCache> path_send_cache; + HashMap<ObjectID, PathSentCache> path_send_cache; HashMap<int, PathGetCache> path_get_cache; int last_send_cache_id = 1; protected: - Error _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers); + Error _send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers); public: void clear(); @@ -75,7 +75,7 @@ public: bool send_object_cache(Object *p_obj, int p_target, int &p_id); int make_object_cache(Object *p_obj); Object *get_cached_object(int p_from, uint32_t p_cache_id); - bool is_cache_confirmed(NodePath p_path, int p_peer); + bool is_cache_confirmed(Node *p_path, int p_peer); SceneCacheInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; } }; diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 3e3118b1cd..04de3dfb7f 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -680,12 +680,16 @@ void SceneMultiplayer::_bind_methods() { SceneMultiplayer::SceneMultiplayer() { relay_buffer.instantiate(); - replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this))); - rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this))); cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); + replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this, cache.ptr()))); + rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this, cache.ptr(), replicator.ptr()))); set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer))); } SceneMultiplayer::~SceneMultiplayer() { clear(); + // Ensure unref in reverse order for safety (we shouldn't use those pointers in the deconstructors anyway). + rpc.unref(); + replicator.unref(); + cache.unref(); } diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index a61e505689..e799abeb48 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -201,9 +201,6 @@ public: void set_max_delta_packet_size(int p_size); int get_max_delta_packet_size() const; - Ref<SceneCacheInterface> get_path_cache() { return cache; } - Ref<SceneReplicationInterface> get_replicator() { return replicator; } - SceneMultiplayer(); ~SceneMultiplayer(); }; diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index 7f12fea4bf..c95e4ff9c9 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -89,6 +89,10 @@ void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) { } } +bool SceneReplicationInterface::_has_authority(const Node *p_node) { + return multiplayer->has_multiplayer_peer() && p_node->get_multiplayer_authority() == multiplayer->get_unique_id(); +} + void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) { if (p_connected) { peers_info[p_id] = PeerInfo(); @@ -155,7 +159,7 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(spawner, ERR_INVALID_PARAMETER); // Track node. const ObjectID oid = node->get_instance_id(); TrackedNode &tobj = _track(oid); @@ -184,7 +188,7 @@ void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) { ERR_CONTINUE(!spawner); spawned_nodes.insert(oid); - if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { + if (_has_authority(spawner)) { if (tobj.net_id == 0) { tobj.net_id = ++last_net_id; } @@ -226,7 +230,7 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER); // Add to synchronizer list. TrackedNode &tobj = _track(p_obj->get_instance_id()); @@ -248,9 +252,9 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c // Try to apply spawn state (before ready). if (pending_buffer_size > 0) { - ERR_FAIL_COND_V(!node || sync->get_replication_config().is_null(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(!node || !sync->get_replication_config_ptr(), ERR_UNCONFIGURED); int consumed = 0; - const List<NodePath> props = sync->get_replication_config()->get_spawn_properties(); + const List<NodePath> props = sync->get_replication_config_ptr()->get_spawn_properties(); Vector<Variant> vars; vars.resize(props.size()); Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed); @@ -270,7 +274,7 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co Node *node = Object::cast_to<Node>(p_obj); ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER); MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object()); - ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(sync, ERR_INVALID_PARAMETER); sync->disconnect("visibility_changed", callable_mp(this, &SceneReplicationInterface::_visibility_changed)); // Untrack synchronizer. const ObjectID oid = node->get_instance_id(); @@ -291,9 +295,9 @@ Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_co void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(p_sid); - ERR_FAIL_COND(!sync); // Bug. + ERR_FAIL_NULL(sync); // Bug. Node *node = sync->get_root_node(); - ERR_FAIL_COND(!node); // Bug. + ERR_FAIL_NULL(node); // Bug. const ObjectID oid = node->get_instance_id(); if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) { _update_spawn_visibility(p_peer, oid); @@ -341,8 +345,8 @@ bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer } Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) { - ERR_FAIL_COND_V(!p_sync, ERR_BUG); - if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) { + ERR_FAIL_NULL_V(p_sync, ERR_BUG); + if (!_has_authority(p_sync) || p_peer == multiplayer->get_unique_id()) { return OK; } @@ -380,17 +384,19 @@ Error SceneReplicationInterface::_update_sync_visibility(int p_peer, Multiplayer Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const ObjectID &p_oid) { const TrackedNode *tnode = tracked_nodes.getptr(p_oid); - ERR_FAIL_COND_V(!tnode, ERR_BUG); + ERR_FAIL_NULL_V(tnode, ERR_BUG); MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner); Node *node = get_id_as<Node>(p_oid); - ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG); + ERR_FAIL_NULL_V(node, ERR_BUG); + ERR_FAIL_NULL_V(spawner, ERR_BUG); + ERR_FAIL_COND_V(!_has_authority(spawner), ERR_BUG); ERR_FAIL_COND_V(!tracked_nodes.has(p_oid), ERR_BUG); const HashSet<ObjectID> synchronizers = tracked_nodes[p_oid].synchronizers; bool is_visible = true; for (const ObjectID &sid : synchronizers) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); ERR_CONTINUE(!sync); - if (!sync->is_multiplayer_authority()) { + if (!_has_authority(sync)) { continue; } // Spawn visibility is composed using OR when multiple synchronizers are present. @@ -435,7 +441,7 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje for (int pid : to_spawn) { ERR_CONTINUE(!peers_info.has(pid)); int path_id; - multiplayer->get_path_cache()->send_object_cache(spawner, pid, path_id); + multiplayer_cache->send_object_cache(spawner, pid, path_id); _send_raw(packet_cache.ptr(), len, pid, true); peers_info[pid].spawn_nodes.insert(p_oid); } @@ -454,9 +460,9 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); + ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED); peer->set_transfer_channel(0); peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); return multiplayer->send_command(p_peer, p_buffer, p_size); @@ -467,7 +473,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa const ObjectID oid = p_node->get_instance_id(); const TrackedNode *tnode = tracked_nodes.getptr(oid); - ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER); uint32_t nid = tnode->net_id; ERR_FAIL_COND_V(!nid, ERR_UNCONFIGURED); @@ -488,12 +494,12 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa const HashSet<ObjectID> synchronizers = tnode->synchronizers; for (const ObjectID &sid : synchronizers) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); - if (!sync->is_multiplayer_authority()) { + if (!_has_authority(sync)) { continue; } ERR_CONTINUE(!sync); - ERR_FAIL_COND_V(sync->get_replication_config().is_null(), ERR_BUG); - for (const NodePath &prop : sync->get_replication_config()->get_spawn_properties()) { + ERR_FAIL_NULL_V(sync->get_replication_config_ptr(), ERR_BUG); + for (const NodePath &prop : sync->get_replication_config_ptr()->get_spawn_properties()) { state_props.push_back(prop); } // Ensure the synchronizer has an ID. @@ -513,7 +519,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa } // Encode scene ID, path ID, net ID, node name. - int path_id = multiplayer->get_path_cache()->make_object_cache(p_spawner); + int path_id = multiplayer_cache->make_object_cache(p_spawner); CharString cname = p_node->get_name().operator String().utf8(); int nlen = encode_cstring(cname.get_data(), nullptr); MAKE_ROOM(1 + 1 + 4 + 4 + 4 + 4 * sync_ids.size() + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size); @@ -549,7 +555,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa Error SceneReplicationInterface::_make_despawn_packet(Node *p_node, int &r_len) { const ObjectID oid = p_node->get_instance_id(); const TrackedNode *tnode = tracked_nodes.getptr(oid); - ERR_FAIL_COND_V(!tnode, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(tnode, ERR_INVALID_PARAMETER); MAKE_ROOM(5); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = (uint8_t)SceneMultiplayer::NETWORK_COMMAND_DESPAWN; @@ -567,8 +573,8 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b ofs += 1; uint32_t node_target = decode_uint32(&p_buffer[ofs]); ofs += 4; - MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_path_cache()->get_cached_object(p_from, node_target)); - ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); + MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer_cache->get_cached_object(p_from, node_target)); + ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); uint32_t net_id = decode_uint32(&p_buffer[ofs]); @@ -592,7 +598,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b // Check that we can spawn. Node *parent = spawner->get_node_or_null(spawner->get_spawn_path()); - ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED); + ERR_FAIL_NULL_V(parent, ERR_UNCONFIGURED); ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA); Node *node = nullptr; @@ -611,7 +617,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b // Scene based spawn. node = spawner->instantiate_scene(scene_id); } - ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED); + ERR_FAIL_NULL_V(node, ERR_UNAUTHORIZED); node->set_name(name); // Add and track remote @@ -656,13 +662,13 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p PeerInfo &pinfo = peers_info[p_from]; ERR_FAIL_COND_V(!pinfo.recv_nodes.has(net_id), ERR_UNAUTHORIZED); Node *node = get_id_as<Node>(pinfo.recv_nodes[net_id]); - ERR_FAIL_COND_V(!node, ERR_BUG); + ERR_FAIL_NULL_V(node, ERR_BUG); pinfo.recv_nodes.erase(net_id); const ObjectID oid = node->get_instance_id(); ERR_FAIL_COND_V(!tracked_nodes.has(oid), ERR_BUG); MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tracked_nodes[oid].spawner); - ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST); + ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST); ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED); if (node->get_parent() != nullptr) { @@ -678,7 +684,7 @@ bool SceneReplicationInterface::_verify_synchronizer(int p_peer, MultiplayerSync r_net_id = p_sync->get_net_id(); if (r_net_id == 0 || (r_net_id & 0x80000000)) { int path_id = 0; - bool verified = multiplayer->get_path_cache()->send_object_cache(p_sync, p_peer, path_id); + bool verified = multiplayer_cache->send_object_cache(p_sync, p_peer, path_id); ERR_FAIL_COND_V_MSG(path_id < 0, false, "This should never happen!"); if (r_net_id == 0) { // First time path based ID. @@ -693,7 +699,7 @@ bool SceneReplicationInterface::_verify_synchronizer(int p_peer, MultiplayerSync MultiplayerSynchronizer *SceneReplicationInterface::_find_synchronizer(int p_peer, uint32_t p_net_id) { MultiplayerSynchronizer *sync = nullptr; if (p_net_id & 0x80000000) { - sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_peer, p_net_id & 0x7FFFFFFF)); + sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer_cache->get_cached_object(p_peer, p_net_id & 0x7FFFFFFF)); } else if (peers_info[p_peer].recv_sync_ids.has(p_net_id)) { const ObjectID &sid = peers_info[p_peer].recv_sync_ids[p_net_id]; sync = get_id_as<MultiplayerSynchronizer>(sid); @@ -708,7 +714,7 @@ void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID> int ofs = 1; for (const ObjectID &oid : p_synchronizers) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid); - ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority()); + ERR_CONTINUE(!sync || !sync->get_replication_config_ptr() || !_has_authority(sync)); uint32_t net_id; if (!_verify_synchronizer(p_peer, sync, net_id)) { continue; @@ -803,7 +809,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p // This is a lazy implementation, we could optimize much more here with by grouping by replication config. for (const ObjectID &oid : p_synchronizers) { MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid); - ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority()); + ERR_CONTINUE(!sync || !sync->get_replication_config_ptr() || !_has_authority(sync)); if (!sync->update_outbound_sync_time(p_usec)) { continue; // nothing to sync. } @@ -818,7 +824,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p int size; Vector<Variant> vars; Vector<const Variant *> varp; - const List<NodePath> props = sync->get_replication_config()->get_sync_properties(); + const List<NodePath> props = sync->get_replication_config_ptr()->get_sync_properties(); Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp); ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state."); err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size); @@ -877,7 +883,7 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu ofs += size; continue; } - const List<NodePath> props = sync->get_replication_config()->get_sync_properties(); + const List<NodePath> props = sync->get_replication_config_ptr()->get_sync_properties(); Vector<Variant> vars; vars.resize(props.size()); int consumed; diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index 267d329ca7..3b3ec6a9ef 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -37,6 +37,7 @@ #include "core/object/ref_counted.h" class SceneMultiplayer; +class SceneCacheInterface; class SceneReplicationInterface : public RefCounted { GDCLASS(SceneReplicationInterface, RefCounted); @@ -87,6 +88,7 @@ private: // Replicator config. SceneMultiplayer *multiplayer = nullptr; + SceneCacheInterface *multiplayer_cache = nullptr; PackedByteArray packet_cache; int sync_mtu = 1350; // Highly dependent on underlying protocol. int delta_mtu = 65535; @@ -95,6 +97,7 @@ private: void _untrack(const ObjectID &p_id); void _node_ready(const ObjectID &p_oid); + bool _has_authority(const Node *p_node); bool _verify_synchronizer(int p_peer, MultiplayerSynchronizer *p_sync, uint32_t &r_net_id); MultiplayerSynchronizer *_find_synchronizer(int p_peer, uint32_t p_net_ida); @@ -143,8 +146,9 @@ public: void set_max_delta_packet_size(int p_size); int get_max_delta_packet_size() const; - SceneReplicationInterface(SceneMultiplayer *p_multiplayer) { + SceneReplicationInterface(SceneMultiplayer *p_multiplayer, SceneCacheInterface *p_cache) { multiplayer = p_multiplayer; + multiplayer_cache = p_cache; } }; diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index da1a044c9d..1463598ddc 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -116,25 +116,9 @@ const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(con return rpc_cache[oid]; } -_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) { - switch (mode) { - case MultiplayerAPI::RPC_MODE_DISABLED: { - return false; - } break; - case MultiplayerAPI::RPC_MODE_ANY_PEER: { - return true; - } break; - case MultiplayerAPI::RPC_MODE_AUTHORITY: { - return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority(); - } break; - } - - return false; -} - String SceneRPCInterface::get_rpc_md5(const Object *p_obj) { const Node *node = Object::cast_to<Node>(p_obj); - ERR_FAIL_COND_V(!node, ""); + ERR_FAIL_NULL_V(node, ""); const RPCConfigCache cache = _get_node_config(node); String rpc_list; for (const KeyValue<uint16_t, RPCConfig> &config : cache.configs) { @@ -145,7 +129,7 @@ String SceneRPCInterface::get_rpc_md5(const Object *p_obj) { Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, uint32_t p_node_target, int p_packet_len) { Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_COND_V(!root_node, nullptr); + ERR_FAIL_NULL_V(root_node, nullptr); Node *node = nullptr; if (p_node_target & 0x80000000) { @@ -167,7 +151,7 @@ Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet, return node; } else { // Use cached path. - return Object::cast_to<Node>(multiplayer->get_path_cache()->get_cached_object(p_from, p_node_target)); + return Object::cast_to<Node>(multiplayer_cache->get_cached_object(p_from, p_node_target)); } } @@ -225,7 +209,7 @@ void SceneRPCInterface::process_rpc(int p_from, const uint8_t *p_packet, int p_p } Node *node = _process_get_node(p_from, p_packet, node_target, p_packet_len); - ERR_FAIL_COND_MSG(node == nullptr, "Invalid packet received. Requested node was not found."); + ERR_FAIL_NULL_MSG(node, "Invalid packet received. Requested node was not found."); uint16_t name_id = 0; switch (name_id_compression) { @@ -252,7 +236,19 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i ERR_FAIL_COND(!cache_config.configs.has(p_rpc_method_id)); const RPCConfig &config = cache_config.configs[p_rpc_method_id]; - bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from); + bool can_call = false; + switch (config.rpc_mode) { + case MultiplayerAPI::RPC_MODE_DISABLED: { + can_call = false; + } break; + case MultiplayerAPI::RPC_MODE_ANY_PEER: { + can_call = true; + } break; + case MultiplayerAPI::RPC_MODE_AUTHORITY: { + can_call = p_from == p_node->get_multiplayer_authority(); + } break; + } + ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + "."); int argc = 0; @@ -313,25 +309,24 @@ void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, con // See if all peers have cached path (if so, call can be fast) while building the RPC target list. HashSet<int> targets; - Ref<SceneCacheInterface> cache = multiplayer->get_path_cache(); int psc_id = -1; bool has_all_peers = true; const ObjectID oid = p_node->get_instance_id(); if (p_to > 0) { - ERR_FAIL_COND_MSG(!multiplayer->get_replicator()->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to)); + ERR_FAIL_COND_MSG(!multiplayer_replicator->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to)); targets.insert(p_to); - has_all_peers = cache->send_object_cache(p_node, p_to, psc_id); + has_all_peers = multiplayer_cache->send_object_cache(p_node, p_to, psc_id); } else { - bool restricted = !multiplayer->get_replicator()->is_rpc_visible(oid, 0); + bool restricted = !multiplayer_replicator->is_rpc_visible(oid, 0); for (const int &P : multiplayer->get_connected_peers()) { if (p_to < 0 && P == -p_to) { continue; // Excluded peer. } - if (restricted && !multiplayer->get_replicator()->is_rpc_visible(oid, P)) { + if (restricted && !multiplayer_replicator->is_rpc_visible(oid, P)) { continue; // Not visible to this peer. } targets.insert(P); - bool has_peer = cache->send_object_cache(p_node, P, psc_id); + bool has_peer = multiplayer_cache->send_object_cache(p_node, P, psc_id); has_all_peers = has_all_peers && has_peer; } } @@ -443,15 +438,14 @@ void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, con // Not all verified path, so send one by one. // Append path at the end, since we will need it for some packets. - NodePath from_path = multiplayer->get_root_path().rel_path_to(p_node->get_path()); - CharString pname = String(from_path).utf8(); + CharString pname = String(multiplayer->get_root_path().rel_path_to(p_node->get_path())).utf8(); int path_len = encode_cstring(pname.get_data(), nullptr); MAKE_ROOM(ofs + path_len); encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); // Not all verified path, so check which needs the longer packet. for (const int P : targets) { - bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P); + bool confirmed = multiplayer_cache->is_cache_confirmed(p_node, P); if (confirmed) { // This one confirmed path, so use id. encode_uint32(psc_id, &(packet_cache.write[1])); diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h index b40169a63b..5c9b66d5f5 100644 --- a/modules/multiplayer/scene_rpc_interface.h +++ b/modules/multiplayer/scene_rpc_interface.h @@ -35,6 +35,8 @@ #include "scene/main/multiplayer_api.h" class SceneMultiplayer; +class SceneCacheInterface; +class SceneReplicationInterface; class Node; class SceneRPCInterface : public RefCounted { @@ -77,6 +79,9 @@ private: }; SceneMultiplayer *multiplayer = nullptr; + SceneCacheInterface *multiplayer_cache = nullptr; + SceneReplicationInterface *multiplayer_replicator = nullptr; + Vector<uint8_t> packet_cache; HashMap<ObjectID, RPCConfigCache> rpc_cache; @@ -99,7 +104,11 @@ public: void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len); String get_rpc_md5(const Object *p_obj); - SceneRPCInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; } + SceneRPCInterface(SceneMultiplayer *p_multiplayer, SceneCacheInterface *p_cache, SceneReplicationInterface *p_replicator) { + multiplayer = p_multiplayer; + multiplayer_cache = p_cache; + multiplayer_replicator = p_replicator; + } }; #endif // SCENE_RPC_INTERFACE_H diff --git a/modules/navigation/config.py b/modules/navigation/config.py index d22f9454ed..a42f27fbe1 100644 --- a/modules/navigation/config.py +++ b/modules/navigation/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return True + return not env["disable_3d"] def configure(env): diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index 85948e7547..18d66c7b69 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -64,7 +64,7 @@ void NavigationMeshEditor::_notification(int p_what) { void NavigationMeshEditor::_bake_pressed() { button_bake->set_pressed(false); - ERR_FAIL_COND(!node); + ERR_FAIL_NULL(node); Ref<NavigationMesh> navmesh = node->get_navigation_mesh(); if (!navmesh.is_valid()) { err_dialog->set_text(TTR("A NavigationMesh resource must be set or created for this node to work.")); @@ -134,7 +134,7 @@ NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); button_bake = memnew(Button); - button_bake->set_flat(true); + button_bake->set_theme_type_variation("FlatButton"); bake_hbox->add_child(button_bake); button_bake->set_toggle_mode(true); button_bake->set_text(TTR("Bake NavigationMesh")); @@ -142,7 +142,7 @@ NavigationMeshEditor::NavigationMeshEditor() { button_bake->connect("pressed", callable_mp(this, &NavigationMeshEditor::_bake_pressed)); button_reset = memnew(Button); - button_reset->set_flat(true); + button_reset->set_theme_type_variation("FlatButton"); bake_hbox->add_child(button_reset); button_reset->set_text(TTR("Clear NavigationMesh")); button_reset->set_tooltip_text(TTR("Clears the internal NavigationMesh vertices and polygons.")); diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 9162fcf171..6a3bf6793e 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -116,7 +116,7 @@ RID GodotNavigationServer::map_create() { COMMAND_2(map_set_active, RID, p_map, bool, p_active) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); if (p_active) { if (!map_is_active(p_map)) { @@ -133,126 +133,126 @@ COMMAND_2(map_set_active, RID, p_map, bool, p_active) { bool GodotNavigationServer::map_is_active(RID p_map) const { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, false); + ERR_FAIL_NULL_V(map, false); return active_maps.find(map) >= 0; } COMMAND_2(map_set_up, RID, p_map, Vector3, p_up) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_up(p_up); } Vector3 GodotNavigationServer::map_get_up(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_up(); } COMMAND_2(map_set_cell_size, RID, p_map, real_t, p_cell_size) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_cell_size(p_cell_size); } real_t GodotNavigationServer::map_get_cell_size(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_cell_size(); } COMMAND_2(map_set_cell_height, RID, p_map, real_t, p_cell_height) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_cell_height(p_cell_height); } real_t GodotNavigationServer::map_get_cell_height(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_cell_height(); } COMMAND_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_use_edge_connections(p_enabled); } bool GodotNavigationServer::map_get_use_edge_connections(RID p_map) const { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, false); + ERR_FAIL_NULL_V(map, false); return map->get_use_edge_connections(); } COMMAND_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_edge_connection_margin(p_connection_margin); } real_t GodotNavigationServer::map_get_edge_connection_margin(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_edge_connection_margin(); } COMMAND_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); map->set_link_connection_radius(p_connection_radius); } real_t GodotNavigationServer::map_get_link_connection_radius(RID p_map) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, 0); + ERR_FAIL_NULL_V(map, 0); return map->get_link_connection_radius(); } Vector<Vector3> GodotNavigationServer::map_get_path(RID p_map, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector<Vector3>()); + ERR_FAIL_NULL_V(map, Vector<Vector3>()); return map->get_path(p_origin, p_destination, p_optimize, p_navigation_layers, nullptr, nullptr, nullptr); } Vector3 GodotNavigationServer::map_get_closest_point_to_segment(RID p_map, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point_to_segment(p_from, p_to, p_use_collision); } Vector3 GodotNavigationServer::map_get_closest_point(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point(p_point); } Vector3 GodotNavigationServer::map_get_closest_point_normal(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); return map->get_closest_point_normal(p_point); } RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 &p_point) const { const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, RID()); + ERR_FAIL_NULL_V(map, RID()); return map->get_closest_point_owner(p_point); } @@ -260,7 +260,7 @@ RID GodotNavigationServer::map_get_closest_point_owner(RID p_map, const Vector3 TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const { TypedArray<RID> link_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, link_rids); + ERR_FAIL_NULL_V(map, link_rids); const LocalVector<NavLink *> &links = map->get_links(); link_rids.resize(links.size()); @@ -274,7 +274,7 @@ TypedArray<RID> GodotNavigationServer::map_get_links(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const { TypedArray<RID> regions_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, regions_rids); + ERR_FAIL_NULL_V(map, regions_rids); const LocalVector<NavRegion *> ®ions = map->get_regions(); regions_rids.resize(regions.size()); @@ -288,7 +288,7 @@ TypedArray<RID> GodotNavigationServer::map_get_regions(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { TypedArray<RID> agents_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, agents_rids); + ERR_FAIL_NULL_V(map, agents_rids); const LocalVector<NavAgent *> &agents = map->get_agents(); agents_rids.resize(agents.size()); @@ -302,7 +302,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const { TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { TypedArray<RID> obstacles_rids; const NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND_V(map == nullptr, obstacles_rids); + ERR_FAIL_NULL_V(map, obstacles_rids); const LocalVector<NavObstacle *> obstacles = map->get_obstacles(); obstacles_rids.resize(obstacles.size()); for (uint32_t i = 0; i < obstacles.size(); i++) { @@ -313,7 +313,7 @@ TypedArray<RID> GodotNavigationServer::map_get_obstacles(RID p_map) const { RID GodotNavigationServer::region_get_map(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, RID()); + ERR_FAIL_NULL_V(region, RID()); if (region->get_map()) { return region->get_map()->get_self(); @@ -323,7 +323,7 @@ RID GodotNavigationServer::region_get_map(RID p_region) const { RID GodotNavigationServer::agent_get_map(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, RID()); + ERR_FAIL_NULL_V(agent, RID()); if (agent->get_map()) { return agent->get_map()->get_self(); @@ -342,35 +342,35 @@ RID GodotNavigationServer::region_create() { COMMAND_2(region_set_enabled, RID, p_region, bool, p_enabled) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_enabled(p_enabled); } bool GodotNavigationServer::region_get_enabled(RID p_region) const { const NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, false); + ERR_FAIL_NULL_V(region, false); return region->get_enabled(); } COMMAND_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_use_edge_connections(p_enabled); } bool GodotNavigationServer::region_get_use_edge_connections(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, false); + ERR_FAIL_NULL_V(region, false); return region->get_use_edge_connections(); } COMMAND_2(region_set_map, RID, p_region, RID, p_map) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); NavMap *map = map_owner.get_or_null(p_map); @@ -379,14 +379,14 @@ COMMAND_2(region_set_map, RID, p_region, RID, p_map) { COMMAND_2(region_set_transform, RID, p_region, Transform3D, p_transform) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_transform(p_transform); } COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); ERR_FAIL_COND(p_enter_cost < 0.0); region->set_enter_cost(p_enter_cost); @@ -394,14 +394,14 @@ COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost) { real_t GodotNavigationServer::region_get_enter_cost(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_enter_cost(); } COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); ERR_FAIL_COND(p_travel_cost < 0.0); region->set_travel_cost(p_travel_cost); @@ -409,28 +409,28 @@ COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost) { real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_travel_cost(); } COMMAND_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_owner_id(p_owner_id); } ObjectID GodotNavigationServer::region_get_owner_id(RID p_region) const { const NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, ObjectID()); + ERR_FAIL_NULL_V(region, ObjectID()); return region->get_owner_id(); } bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const { const NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, false); + ERR_FAIL_NULL_V(region, false); if (region->get_map()) { RID closest_point_owner = map_get_closest_point_owner(region->get_map()->get_self(), p_point); @@ -441,21 +441,21 @@ bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_poi COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_navigation_layers(p_navigation_layers); } uint32_t GodotNavigationServer::region_get_navigation_layers(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(region == nullptr, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_navigation_layers(); } COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navigation_mesh) { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND(region == nullptr); + ERR_FAIL_NULL(region); region->set_mesh(p_navigation_mesh); } @@ -463,7 +463,7 @@ COMMAND_2(region_set_navigation_mesh, RID, p_region, Ref<NavigationMesh>, p_navi #ifndef DISABLE_DEPRECATED void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh, Node *p_root_node) { ERR_FAIL_COND(p_navigation_mesh.is_null()); - ERR_FAIL_COND(p_root_node == nullptr); + ERR_FAIL_NULL(p_root_node); WARN_PRINT_ONCE("NavigationServer3D::region_bake_navigation_mesh() is deprecated due to core threading changes. To upgrade existing code, first create a NavigationMeshSourceGeometryData3D resource. Use this resource with method parse_source_geometry_data() to parse the SceneTree for nodes that should contribute to the navigation mesh baking. The SceneTree parsing needs to happen on the main thread. After the parsing is finished use the resource with method bake_from_source_geometry_data() to bake a navigation mesh.."); @@ -479,21 +479,21 @@ void GodotNavigationServer::region_bake_navigation_mesh(Ref<NavigationMesh> p_na int GodotNavigationServer::region_get_connections_count(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, 0); + ERR_FAIL_NULL_V(region, 0); return region->get_connections_count(); } Vector3 GodotNavigationServer::region_get_connection_pathway_start(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, Vector3()); + ERR_FAIL_NULL_V(region, Vector3()); return region->get_connection_pathway_start(p_connection_id); } Vector3 GodotNavigationServer::region_get_connection_pathway_end(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); - ERR_FAIL_COND_V(!region, Vector3()); + ERR_FAIL_NULL_V(region, Vector3()); return region->get_connection_pathway_end(p_connection_id); } @@ -509,7 +509,7 @@ RID GodotNavigationServer::link_create() { COMMAND_2(link_set_map, RID, p_link, RID, p_map) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); NavMap *map = map_owner.get_or_null(p_map); @@ -518,7 +518,7 @@ COMMAND_2(link_set_map, RID, p_link, RID, p_map) { RID GodotNavigationServer::link_get_map(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, RID()); + ERR_FAIL_NULL_V(link, RID()); if (link->get_map()) { return link->get_map()->get_self(); @@ -528,112 +528,112 @@ RID GodotNavigationServer::link_get_map(const RID p_link) const { COMMAND_2(link_set_enabled, RID, p_link, bool, p_enabled) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_enabled(p_enabled); } bool GodotNavigationServer::link_get_enabled(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, false); + ERR_FAIL_NULL_V(link, false); return link->get_enabled(); } COMMAND_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_bidirectional(p_bidirectional); } bool GodotNavigationServer::link_is_bidirectional(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, false); + ERR_FAIL_NULL_V(link, false); return link->is_bidirectional(); } COMMAND_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_navigation_layers(p_navigation_layers); } uint32_t GodotNavigationServer::link_get_navigation_layers(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_navigation_layers(); } COMMAND_2(link_set_start_position, RID, p_link, Vector3, p_position) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_start_position(p_position); } Vector3 GodotNavigationServer::link_get_start_position(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, Vector3()); + ERR_FAIL_NULL_V(link, Vector3()); return link->get_start_position(); } COMMAND_2(link_set_end_position, RID, p_link, Vector3, p_position) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_end_position(p_position); } Vector3 GodotNavigationServer::link_get_end_position(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, Vector3()); + ERR_FAIL_NULL_V(link, Vector3()); return link->get_end_position(); } COMMAND_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_enter_cost(p_enter_cost); } real_t GodotNavigationServer::link_get_enter_cost(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_enter_cost(); } COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_travel_cost(p_travel_cost); } real_t GodotNavigationServer::link_get_travel_cost(const RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, 0); + ERR_FAIL_NULL_V(link, 0); return link->get_travel_cost(); } COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id) { NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND(link == nullptr); + ERR_FAIL_NULL(link); link->set_owner_id(p_owner_id); } ObjectID GodotNavigationServer::link_get_owner_id(RID p_link) const { const NavLink *link = link_owner.get_or_null(p_link); - ERR_FAIL_COND_V(link == nullptr, ObjectID()); + ERR_FAIL_NULL_V(link, ObjectID()); return link->get_owner_id(); } @@ -649,35 +649,35 @@ RID GodotNavigationServer::agent_create() { COMMAND_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_enabled(p_enabled); } bool GodotNavigationServer::agent_get_avoidance_enabled(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->is_avoidance_enabled(); } COMMAND_2(agent_set_use_3d_avoidance, RID, p_agent, bool, p_enabled) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_use_3d_avoidance(p_enabled); } bool GodotNavigationServer::agent_get_use_3d_avoidance(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->get_use_3d_avoidance(); } COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); NavMap *map = map_owner.get_or_null(p_map); @@ -686,28 +686,28 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) { COMMAND_2(agent_set_paused, RID, p_agent, bool, p_paused) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_paused(p_paused); } bool GodotNavigationServer::agent_get_paused(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->get_paused(); } COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_neighbor_distance(p_distance); } COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_max_neighbors(p_count); } @@ -715,7 +715,7 @@ COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) { COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_time_horizon_agents(p_time_horizon); } @@ -723,7 +723,7 @@ COMMAND_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon) { COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon) { ERR_FAIL_COND_MSG(p_time_horizon < 0.0, "Time horizion must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_time_horizon_obstacles(p_time_horizon); } @@ -731,7 +731,7 @@ COMMAND_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_radius(p_radius); } @@ -739,7 +739,7 @@ COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) { COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { ERR_FAIL_COND_MSG(p_height < 0.0, "Height must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_height(p_height); } @@ -747,42 +747,42 @@ COMMAND_2(agent_set_height, RID, p_agent, real_t, p_height) { COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) { ERR_FAIL_COND_MSG(p_max_speed < 0.0, "Max speed must be positive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_max_speed(p_max_speed); } COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_velocity(p_velocity); } COMMAND_2(agent_set_velocity_forced, RID, p_agent, Vector3, p_velocity) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_velocity_forced(p_velocity); } COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_position(p_position); } bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND_V(agent == nullptr, false); + ERR_FAIL_NULL_V(agent, false); return agent->is_map_changed(); } COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_callback(p_callback); @@ -797,13 +797,13 @@ COMMAND_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback) { COMMAND_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_layers(p_layers); } COMMAND_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask) { NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_mask(p_mask); } @@ -811,7 +811,7 @@ COMMAND_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority) { ERR_FAIL_COND_MSG(p_priority < 0.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); ERR_FAIL_COND_MSG(p_priority > 1.0, "Avoidance priority must be between 0.0 and 1.0 inclusive."); NavAgent *agent = agent_owner.get_or_null(p_agent); - ERR_FAIL_COND(agent == nullptr); + ERR_FAIL_NULL(agent); agent->set_avoidance_priority(p_priority); } @@ -833,35 +833,35 @@ RID GodotNavigationServer::obstacle_create() { COMMAND_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_avoidance_enabled(p_enabled); } bool GodotNavigationServer::obstacle_get_avoidance_enabled(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->is_avoidance_enabled(); } COMMAND_2(obstacle_set_use_3d_avoidance, RID, p_obstacle, bool, p_enabled) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_use_3d_avoidance(p_enabled); } bool GodotNavigationServer::obstacle_get_use_3d_avoidance(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->get_use_3d_avoidance(); } COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); NavMap *map = map_owner.get_or_null(p_map); @@ -870,7 +870,7 @@ COMMAND_2(obstacle_set_map, RID, p_obstacle, RID, p_map) { RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, RID()); + ERR_FAIL_NULL_V(obstacle, RID()); if (obstacle->get_map()) { return obstacle->get_map()->get_self(); } @@ -879,14 +879,14 @@ RID GodotNavigationServer::obstacle_get_map(RID p_obstacle) const { COMMAND_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_paused(p_paused); } bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND_V(obstacle == nullptr, false); + ERR_FAIL_NULL_V(obstacle, false); return obstacle->get_paused(); } @@ -894,39 +894,39 @@ bool GodotNavigationServer::obstacle_get_paused(RID p_obstacle) const { COMMAND_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius) { ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive."); NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_radius(p_radius); } COMMAND_2(obstacle_set_height, RID, p_obstacle, real_t, p_height) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_height(p_height); } COMMAND_2(obstacle_set_velocity, RID, p_obstacle, Vector3, p_velocity) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_velocity(p_velocity); } COMMAND_2(obstacle_set_position, RID, p_obstacle, Vector3, p_position) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_position(p_position); } void GodotNavigationServer::obstacle_set_vertices(RID p_obstacle, const Vector<Vector3> &p_vertices) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_vertices(p_vertices); } COMMAND_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers) { NavObstacle *obstacle = obstacle_owner.get_or_null(p_obstacle); - ERR_FAIL_COND(obstacle == nullptr); + ERR_FAIL_NULL(obstacle); obstacle->set_avoidance_layers(p_layers); } @@ -934,7 +934,7 @@ void GodotNavigationServer::parse_source_geometry_data(const Ref<NavigationMesh> #ifndef _3D_DISABLED ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); - ERR_FAIL_COND_MSG(p_root_node == nullptr, "No parsing root node specified."); + ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified."); ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); ERR_FAIL_NULL(NavMeshGenerator3D::get_singleton()); @@ -1079,13 +1079,21 @@ void GodotNavigationServer::flush_queries() { void GodotNavigationServer::map_force_update(RID p_map) { NavMap *map = map_owner.get_or_null(p_map); - ERR_FAIL_COND(map == nullptr); + ERR_FAIL_NULL(map); flush_queries(); map->sync(); } +void GodotNavigationServer::sync() { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + navmesh_generator_3d->sync(); + } +#endif // _3D_DISABLED +} + void GodotNavigationServer::process(real_t p_delta_time) { flush_queries(); @@ -1093,16 +1101,6 @@ void GodotNavigationServer::process(real_t p_delta_time) { return; } -#ifndef _3D_DISABLED - // Sync finished navmesh bakes before doing NavMap updates. - if (navmesh_generator_3d) { - navmesh_generator_3d->sync(); - // Finished bakes emit callbacks and users might have reacted to those. - // Flush queue again so users do not have to wait for the next sync. - flush_queries(); - } -#endif // _3D_DISABLED - int _new_pm_region_count = 0; int _new_pm_agent_count = 0; int _new_pm_link_count = 0; @@ -1168,7 +1166,7 @@ PathQueryResult GodotNavigationServer::_query_path(const PathQueryParameters &p_ PathQueryResult r_query_result; const NavMap *map = map_owner.get_or_null(p_parameters.map); - ERR_FAIL_COND_V(map == nullptr, r_query_result); + ERR_FAIL_NULL_V(map, r_query_result); // run the pathfinding diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index c12605bc7a..4ead4fc398 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -243,6 +243,7 @@ public: void flush_queries(); virtual void process(real_t p_delta_time) override; virtual void init() override; + virtual void sync() override; virtual void finish() override; virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override; diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp new file mode 100644 index 0000000000..b54729e06f --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.cpp @@ -0,0 +1,369 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "godot_navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +#include "nav_mesh_generator_2d.h" +#endif // CLIPPER2_ENABLED + +#include "servers/navigation_server_3d.h" + +#define FORWARD_0(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_0_C(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \ + } + +#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \ + } + +#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \ + } + +static RID rid_to_rid(const RID d) { + return d; +} + +static bool bool_to_bool(const bool d) { + return d; +} + +static int int_to_int(const int d) { + return d; +} + +static uint32_t uint32_to_uint32(const uint32_t d) { + return d; +} + +static real_t real_to_real(const real_t d) { + return d; +} + +static Vector3 v2_to_v3(const Vector2 d) { + return Vector3(d.x, 0.0, d.y); +} + +static Vector2 v3_to_v2(const Vector3 &d) { + return Vector2(d.x, d.z); +} + +static Vector<Vector3> vector_v2_to_v3(const Vector<Vector2> &d) { + Vector<Vector3> nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v2_to_v3(d[i]); + } + return nd; +} + +static Vector<Vector2> vector_v3_to_v2(const Vector<Vector3> &d) { + Vector<Vector2> nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v3_to_v2(d[i]); + } + return nd; +} + +static Transform3D trf2_to_trf3(const Transform2D &d) { + Vector3 o(v2_to_v3(d.get_origin())); + Basis b; + b.rotate(Vector3(0, -1, 0), d.get_rotation()); + b.scale(v2_to_v3(d.get_scale())); + return Transform3D(b, o); +} + +static ObjectID id_to_id(const ObjectID &id) { + return id; +} + +static Callable callable_to_callable(const Callable &c) { + return c; +} + +static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) { + if (d.is_valid()) { + return d->get_navigation_mesh(); + } else { + return Ref<NavigationMesh>(); + } +} + +void GodotNavigationServer2D::init() { +#ifdef CLIPPER2_ENABLED + navmesh_generator_2d = memnew(NavMeshGenerator2D); + ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D."); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::sync() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->sync(); + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::finish() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->finish(); + memdelete(navmesh_generator_2d); + navmesh_generator_2d = nullptr; + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified."); + ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +GodotNavigationServer2D::GodotNavigationServer2D() {} + +GodotNavigationServer2D::~GodotNavigationServer2D() {} + +TypedArray<RID> FORWARD_0_C(get_maps); + +TypedArray<RID> FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); + +TypedArray<RID> FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid); + +RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid); + +RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid); + +RID FORWARD_0(map_create); + +void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool); + +bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid); + +void GodotNavigationServer2D::map_force_update(RID p_map) { + NavigationServer3D::get_singleton()->map_force_update(p_map); +} + +void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid); + +Vector<Vector2> FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32); + +Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); +RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +RID FORWARD_0(region_create); + +void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid); + +void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid); +bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3); + +void GodotNavigationServer2D::region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) { + NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon)); +} + +int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); + +RID FORWARD_0(link_create); + +void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid); + +RID GodotNavigationServer2D::agent_create() { + RID agent = NavigationServer3D::get_singleton()->agent_create(); + return agent; +} + +void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int); +void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3); + +bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); + +void FORWARD_1(free, RID, p_object, rid_to_rid); +void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); + +void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real); + +RID GodotNavigationServer2D::obstacle_create() { + RID obstacle = NavigationServer3D::get_singleton()->obstacle_create(); + return obstacle; +} +void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); + +void GodotNavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) { + NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices)); +} + +void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const { + ERR_FAIL_COND(!p_query_parameters.is_valid()); + ERR_FAIL_COND(!p_query_result.is_valid()); + + const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters()); + + p_query_result->set_path(vector_v3_to_v2(_query_result.path)); + p_query_result->set_path_types(_query_result.path_types); + p_query_result->set_path_rids(_query_result.path_rids); + p_query_result->set_path_owner_ids(_query_result.path_owner_ids); +} diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h new file mode 100644 index 0000000000..337f5f40d8 --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.h @@ -0,0 +1,234 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 GODOT_NAVIGATION_SERVER_2D_H +#define GODOT_NAVIGATION_SERVER_2D_H + +#include "nav_agent.h" +#include "nav_link.h" +#include "nav_map.h" +#include "nav_obstacle.h" +#include "nav_region.h" + +#include "servers/navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +class NavMeshGenerator2D; +#endif // CLIPPER2_ENABLED + +// This server exposes the `NavigationServer3D` features in the 2D world. +class GodotNavigationServer2D : public NavigationServer2D { + GDCLASS(GodotNavigationServer2D, NavigationServer2D); + +#ifdef CLIPPER2_ENABLED + NavMeshGenerator2D *navmesh_generator_2d = nullptr; +#endif // CLIPPER2_ENABLED + +public: + GodotNavigationServer2D(); + virtual ~GodotNavigationServer2D(); + + virtual TypedArray<RID> get_maps() const override; + + virtual RID map_create() override; + virtual void map_set_active(RID p_map, bool p_active) override; + virtual bool map_is_active(RID p_map) const override; + virtual void map_set_cell_size(RID p_map, real_t p_cell_size) override; + virtual real_t map_get_cell_size(RID p_map) const override; + virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) override; + virtual bool map_get_use_edge_connections(RID p_map) const override; + virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override; + virtual real_t map_get_edge_connection_margin(RID p_map) const override; + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override; + virtual real_t map_get_link_connection_radius(RID p_map) const override; + virtual Vector<Vector2> map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override; + virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override; + virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override; + virtual TypedArray<RID> map_get_links(RID p_map) const override; + virtual TypedArray<RID> map_get_regions(RID p_map) const override; + virtual TypedArray<RID> map_get_agents(RID p_map) const override; + virtual TypedArray<RID> map_get_obstacles(RID p_map) const override; + virtual void map_force_update(RID p_map) override; + + virtual RID region_create() override; + virtual void region_set_enabled(RID p_region, bool p_enabled) override; + virtual bool region_get_enabled(RID p_region) const override; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) override; + virtual bool region_get_use_edge_connections(RID p_region) const override; + virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) override; + virtual real_t region_get_enter_cost(RID p_region) const override; + virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) override; + virtual real_t region_get_travel_cost(RID p_region) const override; + virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) override; + virtual ObjectID region_get_owner_id(RID p_region) const override; + virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const override; + virtual void region_set_map(RID p_region, RID p_map) override; + virtual RID region_get_map(RID p_region) const override; + virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override; + virtual uint32_t region_get_navigation_layers(RID p_region) const override; + virtual void region_set_transform(RID p_region, Transform2D p_transform) override; + virtual void region_set_navigation_polygon(RID p_region, Ref<NavigationPolygon> p_navigation_polygon) override; + virtual int region_get_connections_count(RID p_region) const override; + virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; + virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + + virtual RID link_create() override; + + /// Set the map of this link. + virtual void link_set_map(RID p_link, RID p_map) override; + virtual RID link_get_map(RID p_link) const override; + + virtual void link_set_enabled(RID p_link, bool p_enabled) override; + virtual bool link_get_enabled(RID p_link) const override; + + /// Set whether this link travels in both directions. + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) override; + virtual bool link_is_bidirectional(RID p_link) const override; + + /// Set the link's layers. + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override; + virtual uint32_t link_get_navigation_layers(RID p_link) const override; + + /// Set the start position of the link. + virtual void link_set_start_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_start_position(RID p_link) const override; + + /// Set the end position of the link. + virtual void link_set_end_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_end_position(RID p_link) const override; + + /// Set the enter cost of the link. + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) override; + virtual real_t link_get_enter_cost(RID p_link) const override; + + /// Set the travel cost of the link. + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) override; + virtual real_t link_get_travel_cost(RID p_link) const override; + + /// Set the node which manages this link. + virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) override; + virtual ObjectID link_get_owner_id(RID p_link) const override; + + /// Creates the agent. + virtual RID agent_create() override; + + /// Put the agent in the map. + virtual void agent_set_map(RID p_agent, RID p_map) override; + virtual RID agent_get_map(RID p_agent) const override; + + virtual void agent_set_paused(RID p_agent, bool p_paused) override; + virtual bool agent_get_paused(RID p_agent) const override; + + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override; + virtual bool agent_get_avoidance_enabled(RID p_agent) const override; + + /// The maximum distance (center point to + /// center point) to other agents this agent + /// takes into account in the navigation. The + /// larger this number, the longer the running + /// time of the simulation. If the number is too + /// low, the simulation will not be safe. + /// Must be non-negative. + virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override; + + /// The maximum number of other agents this + /// agent takes into account in the navigation. + /// The larger this number, the longer the + /// running time of the simulation. If the + /// number is too low, the simulation will not + /// be safe. + virtual void agent_set_max_neighbors(RID p_agent, int p_count) override; + + /// The minimal amount of time for which this + /// agent's velocities that are computed by the + /// simulation are safe with respect to other + /// agents. The larger this number, the sooner + /// this agent will respond to the presence of + /// other agents, but the less freedom this + /// agent has in choosing its velocities. + /// Must be positive. + + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override; + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override; + + /// The radius of this agent. + /// Must be non-negative. + virtual void agent_set_radius(RID p_agent, real_t p_radius) override; + + /// The maximum speed of this agent. + /// Must be non-negative. + virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) override; + + /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly + virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override; + + /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. + /// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. + virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) override; + + /// Position of the agent in world space. + virtual void agent_set_position(RID p_agent, Vector2 p_position) override; + + /// Returns true if the map got changed the previous frame. + virtual bool agent_is_map_changed(RID p_agent) const override; + + /// Callback called at the end of the RVO process + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override; + + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override; + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override; + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override; + + virtual RID obstacle_create() override; + virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override; + virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const override; + virtual void obstacle_set_map(RID p_obstacle, RID p_map) override; + virtual RID obstacle_get_map(RID p_obstacle) const override; + virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) override; + virtual bool obstacle_get_paused(RID p_obstacle) const override; + virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) override; + virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override; + virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override; + virtual void obstacle_set_vertices(RID p_obstacle, const Vector<Vector2> &p_vertices) override; + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override; + + virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const override; + + virtual void init() override; + virtual void sync() override; + virtual void finish() override; + virtual void free(RID p_object) override; + + virtual void parse_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; +}; + +#endif // GODOT_NAVIGATION_SERVER_2D_H diff --git a/modules/navigation/nav_agent.cpp b/modules/navigation/nav_agent.cpp index 010bd2d7c0..cb219ac6c0 100644 --- a/modules/navigation/nav_agent.cpp +++ b/modules/navigation/nav_agent.cpp @@ -145,12 +145,7 @@ void NavAgent::dispatch_avoidance_callback() { } // Invoke the callback with the new velocity. - Variant args[] = { new_velocity }; - const Variant *args_p[] = { &args[0] }; - Variant return_value; - Callable::CallError call_error; - - avoidance_callback.callp(args_p, 1, return_value, call_error); + avoidance_callback.call(new_velocity); } void NavAgent::set_neighbor_distance(real_t p_neighbor_distance) { diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 737ccaf3cd..5ef0298eb4 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -953,6 +953,9 @@ void NavMap::sync() { // Search for polygons within range of a nav link. for (const NavLink *link : links) { + if (!link->get_enabled()) { + continue; + } const Vector3 start = link->get_start_position(); const Vector3 end = link->get_end_position(); diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp new file mode 100644 index 0000000000..f8c12935b4 --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.cpp @@ -0,0 +1,851 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "nav_mesh_generator_2d.h" + +#include "core/config/project_settings.h" +#include "scene/2d/mesh_instance_2d.h" +#include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/physics_body_2d.h" +#include "scene/2d/polygon_2d.h" +#include "scene/2d/tile_map.h" +#include "scene/resources/capsule_shape_2d.h" +#include "scene/resources/circle_shape_2d.h" +#include "scene/resources/concave_polygon_shape_2d.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/navigation_polygon.h" +#include "scene/resources/rectangle_shape_2d.h" + +#include "thirdparty/clipper2/include/clipper2/clipper.h" +#include "thirdparty/misc/polypartition.h" + +NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; +Mutex NavMeshGenerator2D::baking_navmesh_mutex; +Mutex NavMeshGenerator2D::generator_task_mutex; +bool NavMeshGenerator2D::use_threads = true; +bool NavMeshGenerator2D::baking_use_multiple_threads = true; +bool NavMeshGenerator2D::baking_use_high_priority_threads = true; +HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes; +HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks; + +NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { + return singleton; +} + +NavMeshGenerator2D::NavMeshGenerator2D() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + + baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads"); + baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads"); + + // Using threads might cause problems on certain exports or with the Editor on certain devices. + // This is the main switch to turn threaded navmesh baking off should the need arise. + use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint(); +} + +NavMeshGenerator2D::~NavMeshGenerator2D() { + cleanup(); +} + +void NavMeshGenerator2D::sync() { + if (generator_tasks.size() == 0) { + return; + } + + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); + + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); + } + } + + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::cleanup() { + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + baking_navmeshes.clear(); + + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::finish() { + cleanup(); +} + +void NavMeshGenerator2D::parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND(!Thread::is_main_thread()); + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_NULL(p_root_node); + ERR_FAIL_COND(!p_root_node->is_inside_tree()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data); + + baking_navmesh_mutex.lock(); + baking_navmeshes.erase(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + if (!use_threads) { + bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_task_mutex.lock(); + NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); + generator_task->navigation_mesh = p_navigation_mesh; + generator_task->source_geometry_data = p_source_geometry_data; + generator_task->callback = p_callback; + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); + generator_tasks.insert(generator_task->thread_task_id, generator_task); + generator_task_mutex.unlock(); +} + +void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { + NavMeshGeneratorTask2D *generator_task = static_cast<NavMeshGeneratorTask2D *>(p_arg); + + generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data); + + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED; +} + +void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children) { + generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); + + if (p_recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); + } + } +} + +void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + MeshInstance2D *mesh_instance = Object::cast_to<MeshInstance2D>(p_node); + + if (mesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref<Mesh> mesh = mesh_instance->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform(); + + using namespace Clipper2Lib; + + Paths64 subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + subject_paths.push_back(subject_path); + } + + Paths64 path_solution; + + path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + Vector<Vector<Vector2>> polypaths; + + for (const Path64 &scaled_path : path_solution) { + Vector<Vector2> shape_outline; + for (const Point64 &scaled_point : scaled_path) { + shape_outline.push_back(Point2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); + } + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + MultiMeshInstance2D *multimesh_instance = Object::cast_to<MultiMeshInstance2D>(p_node); + + if (multimesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref<MultiMesh> multimesh = multimesh_instance->get_multimesh(); + if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) { + return; + } + + Ref<Mesh> mesh = multimesh->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + using namespace Clipper2Lib; + + Paths64 mesh_subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector<Vector2> mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector<int> mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + mesh_subject_paths.push_back(subject_path); + } + + Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + int multimesh_instance_count = multimesh->get_visible_instance_count(); + if (multimesh_instance_count == -1) { + multimesh_instance_count = multimesh->get_instance_count(); + } + + const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform(); + + for (int i = 0; i < multimesh_instance_count; i++) { + const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i); + + for (const Path64 &mesh_path : mesh_path_solution) { + Vector<Vector2> shape_outline; + + for (const Point64 &mesh_path_point : mesh_path) { + shape_outline.push_back(Point2(static_cast<real_t>(mesh_path_point.x), static_cast<real_t>(mesh_path_point.y))); + } + + for (int j = 0; j < shape_outline.size(); j++) { + shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]); + } + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } +} + +void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + Polygon2D *polygon_2d = Object::cast_to<Polygon2D>(p_node); + + if (polygon_2d == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) { + const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform(); + + Vector<Vector2> shape_outline = polygon_2d->get_polygon(); + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + StaticBody2D *static_body = Object::cast_to<StaticBody2D>(p_node); + + if (static_body == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + if (!(static_body->get_collision_layer() & parsed_collision_mask)) { + return; + } + + List<uint32_t> shape_owners; + static_body->get_shape_owners(&shape_owners); + + for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } + + const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); + + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + Ref<Shape2D> s = static_body->shape_owner_get_shape(shape_owner, shape_index); + + if (s.is_null()) { + continue; + } + + const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); + + RectangleShape2D *rectangle_shape = Object::cast_to<RectangleShape2D>(*s); + if (rectangle_shape) { + Vector<Vector2> shape_outline; + + const Vector2 &rectangle_size = rectangle_shape->get_size(); + + shape_outline.resize(5); + shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5); + shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5); + shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5); + shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5); + shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5); + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CapsuleShape2D *capsule_shape = Object::cast_to<CapsuleShape2D>(*s); + if (capsule_shape) { + const real_t capsule_height = capsule_shape->get_height(); + const real_t capsule_radius = capsule_shape->get_radius(); + + Vector<Vector2> shape_outline; + const real_t turn_step = Math_TAU / 12.0; + shape_outline.resize(14); + int shape_outline_inx = 0; + for (int i = 0; i < 12; i++) { + Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius); + + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs); + shape_outline_inx += 1; + if (i == 3 || i == 9) { + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs); + shape_outline_inx += 1; + } + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CircleShape2D *circle_shape = Object::cast_to<CircleShape2D>(*s); + if (circle_shape) { + const real_t circle_radius = circle_shape->get_radius(); + + Vector<Vector2> shape_outline; + int circle_edge_count = 12; + shape_outline.resize(circle_edge_count); + + const real_t turn_step = Math_TAU / real_t(circle_edge_count); + for (int i = 0; i < circle_edge_count; i++) { + shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to<ConcavePolygonShape2D>(*s); + if (concave_polygon_shape) { + Vector<Vector2> shape_outline = concave_polygon_shape->get_segments(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to<ConvexPolygonShape2D>(*s); + if (convex_polygon_shape) { + Vector<Vector2> shape_outline = convex_polygon_shape->get_points(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + TileMap *tilemap = Object::cast_to<TileMap>(p_node); + + if (tilemap == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + + if (tilemap->get_layers_count() <= 0) { + return; + } + + int tilemap_layer = 0; // only main tile map layer is supported + + Ref<TileSet> tile_set = tilemap->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int physics_layers_count = tile_set->get_physics_layers_count(); + int navigation_layers_count = tile_set->get_navigation_layers_count(); + + if (physics_layers_count <= 0 && navigation_layers_count <= 0) { + return; + } + + const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform(); + TypedArray<Vector2i> used_cells = tilemap->get_used_cells(tilemap_layer); + + for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) { + const Vector2i &cell = used_cells[used_cell_index]; + + const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false); + if (tile_data == nullptr) { + continue; + } + + Transform2D tile_transform; + tile_transform.set_origin(tilemap->map_to_local(cell)); + + const Transform2D tile_transform_offset = tilemap_xform * tile_transform; + + if (navigation_layers_count > 0) { + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer); + if (navigation_polygon.is_valid()) { + for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) { + const Vector<Vector2> &navigation_polygon_outline = navigation_polygon->get_outline(outline_index); + if (navigation_polygon_outline.size() == 0) { + continue; + } + + Vector<Vector2> traversable_outline; + traversable_outline.resize(navigation_polygon_outline.size()); + + const Vector2 *navigation_polygon_outline_ptr = navigation_polygon_outline.ptr(); + Vector2 *traversable_outline_ptrw = traversable_outline.ptrw(); + + for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) { + traversable_outline_ptrw[traversable_outline_index] = tile_transform_offset.xform(navigation_polygon_outline_ptr[traversable_outline_index]); + } + + p_source_geometry_data->_add_traversable_outline(traversable_outline); + } + } + } + + if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) { + for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) { + const Vector<Vector2> &collision_polygon_points = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index); + if (collision_polygon_points.size() == 0) { + continue; + } + + Vector<Vector2> obstruction_outline; + obstruction_outline.resize(collision_polygon_points.size()); + + const Vector2 *collision_polygon_points_ptr = collision_polygon_points.ptr(); + Vector2 *obstruction_outline_ptrw = obstruction_outline.ptrw(); + + for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) { + obstruction_outline_ptrw[obstruction_outline_index] = tile_transform_offset.xform(collision_polygon_points_ptr[obstruction_outline_index]); + } + + p_source_geometry_data->_add_obstruction_outline(obstruction_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) { + List<Node *> parse_nodes; + + if (p_navigation_mesh->get_source_geometry_mode() == NavigationPolygon::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { + parse_nodes.push_back(p_root_node); + } else { + p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_geometry_group_name(), &parse_nodes); + } + + Transform2D root_node_transform = Transform2D(); + if (Object::cast_to<Node2D>(p_root_node)) { + root_node_transform = Object::cast_to<Node2D>(p_root_node)->get_global_transform().affine_inverse(); + } + + p_source_geometry_data->clear(); + p_source_geometry_data->root_node_transform = root_node_transform; + + bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + + for (Node *E : parse_nodes) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children); + } +}; + +static void generator_recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) { + using namespace Clipper2Lib; + + Vector<Vector2> polygon_vertices; + + for (const Point64 &polypath_point : p_polypath_item->Polygon()) { + polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y))); + } + + TPPLPoly tp; + tp.Init(polygon_vertices.size()); + for (int j = 0; j < polygon_vertices.size(); j++) { + tp[j] = polygon_vertices[j]; + } + + if (p_polypath_item->IsHole()) { + tp.SetOrientation(TPPL_ORIENTATION_CW); + tp.SetHole(true); + } else { + tp.SetOrientation(TPPL_ORIENTATION_CCW); + } + p_tppl_in_polygon.push_back(tp); + + for (size_t i = 0; i < p_polypath_item->Count(); i++) { + const PolyPath64 *polypath_item = p_polypath_item->Child(i); + generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item); + } +} + +bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) { + ERR_FAIL_COND_V(!p_callback.is_valid(), false); + + Callable::CallError ce; + Variant result; + p_callback.callp(nullptr, 0, result, ce); + + return ce.error == Callable::CallError::CALL_OK; +} + +void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) { + if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { + return; + } + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + return; + } + + int outline_count = p_navigation_mesh->get_outline_count(); + const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines(); + const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines(); + + if (outline_count == 0 && traversable_outlines.size() == 0) { + return; + } + + using namespace Clipper2Lib; + + Paths64 traversable_polygon_paths; + Paths64 obstruction_polygon_paths; + + for (int i = 0; i < outline_count; i++) { + const Vector<Vector2> &traversable_outline = p_navigation_mesh->get_outline(i); + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector<Vector2> &traversable_outline : traversable_outlines) { + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) { + Path64 clip_path; + for (const Vector2 &obstruction_point : obstruction_outline) { + const Point64 &point = Point64(obstruction_point.x, obstruction_point.y); + clip_path.push_back(point); + } + obstruction_polygon_paths.push_back(clip_path); + } + + Paths64 path_solution; + + // first merge all traversable polygons according to user specified fill rule + Paths64 dummy_clip_path; + traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, FillRule::NonZero); + // merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry + obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero); + + path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero); + + real_t agent_radius_offset = p_navigation_mesh->get_agent_radius(); + if (agent_radius_offset > 0.0) { + path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); + } + //path_solution = RamerDouglasPeucker(path_solution, 0.025); // + + Vector<Vector<Vector2>> new_baked_outlines; + + for (const Path64 &scaled_path : path_solution) { + Vector<Vector2> polypath; + for (const Point64 &scaled_point : scaled_path) { + polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y))); + } + new_baked_outlines.push_back(polypath); + } + + if (new_baked_outlines.size() == 0) { + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + + Paths64 polygon_paths; + + for (const Vector<Vector2> &baked_outline : new_baked_outlines) { + Path64 polygon_path; + for (const Vector2 &baked_outline_point : baked_outline) { + const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y); + polygon_path.push_back(point); + } + polygon_paths.push_back(polygon_path); + } + + ClipType clipper_cliptype = ClipType::Union; + + List<TPPLPoly> tppl_in_polygon, tppl_out_polygon; + + PolyTree64 polytree; + Clipper64 clipper_64; + + clipper_64.AddSubject(polygon_paths); + clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree); + + for (size_t i = 0; i < polytree.Count(); i++) { + const PolyPath64 *polypath_item = polytree[i]; + generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item); + } + + TPPLPartition tpart; + if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed! + ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + + Vector<Vector2> new_vertices; + Vector<Vector<int>> new_polygons; + + HashMap<Vector2, int> points; + for (List<TPPLPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) { + TPPLPoly &tp = I->get(); + + Vector<int> new_polygon; + + for (int64_t i = 0; i < tp.GetNumPoints(); i++) { + HashMap<Vector2, int>::Iterator E = points.find(tp[i]); + if (!E) { + E = points.insert(tp[i], new_vertices.size()); + new_vertices.push_back(tp[i]); + } + new_polygon.push_back(E->value); + } + + new_polygons.push_back(new_polygon); + } + + p_navigation_mesh->set_vertices(new_vertices); + p_navigation_mesh->clear_polygons(); + for (int i = 0; i < new_polygons.size(); i++) { + p_navigation_mesh->add_polygon(new_polygons[i]); + } +} diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h new file mode 100644 index 0000000000..763ad24636 --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.h @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 NAV_MESH_GENERATOR_2D_H +#define NAV_MESH_GENERATOR_2D_H + +#include "core/object/class_db.h" +#include "core/object/worker_thread_pool.h" + +class Node; +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + +class NavMeshGenerator2D : public Object { + static NavMeshGenerator2D *singleton; + + static Mutex baking_navmesh_mutex; + static Mutex generator_task_mutex; + + static bool use_threads; + static bool baking_use_multiple_threads; + static bool baking_use_high_priority_threads; + + struct NavMeshGeneratorTask2D { + enum TaskStatus { + BAKING_STARTED, + BAKING_FINISHED, + BAKING_FAILED, + CALLBACK_DISPATCHED, + CALLBACK_FAILED, + }; + + Ref<NavigationPolygon> navigation_mesh; + Ref<NavigationMeshSourceGeometryData2D> source_geometry_data; + Callable callback; + WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID; + NavMeshGeneratorTask2D::TaskStatus status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + }; + + static HashMap<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> generator_tasks; + + static void generator_thread_bake(void *p_arg); + + static HashSet<Ref<NavigationPolygon>> baking_navmeshes; + + static void generator_parse_geometry_node(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node, bool p_recurse_children); + static void generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node); + static void generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data); + + static void generator_parse_meshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node); + + static bool generator_emit_callback(const Callable &p_callback); + +public: + static NavMeshGenerator2D *get_singleton(); + + static void sync(); + static void cleanup(); + static void finish(); + + static void parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); + + NavMeshGenerator2D(); + ~NavMeshGenerator2D(); +}; + +#endif // NAV_MESH_GENERATOR_2D_H diff --git a/modules/navigation/nav_mesh_generator_3d.cpp b/modules/navigation/nav_mesh_generator_3d.cpp index 7c27417e5f..8719801c72 100644 --- a/modules/navigation/nav_mesh_generator_3d.cpp +++ b/modules/navigation/nav_mesh_generator_3d.cpp @@ -149,7 +149,7 @@ void NavMeshGenerator3D::finish() { void NavMeshGenerator3D::parse_source_geometry_data(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { ERR_FAIL_COND(!Thread::is_main_thread()); ERR_FAIL_COND(!p_navigation_mesh.is_valid()); - ERR_FAIL_COND(p_root_node == nullptr); + ERR_FAIL_NULL(p_root_node); ERR_FAIL_COND(!p_root_node->is_inside_tree()); ERR_FAIL_COND(!p_source_geometry_data.is_valid()); @@ -384,33 +384,23 @@ void NavMeshGenerator3D::generator_parse_staticbody3d_node(const Ref<NavigationM const Vector<real_t> &map_data = heightmap_shape->get_map_data(); Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); - Vector2 start = heightmap_gridsize * -0.5; + Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; Vector<Vector3> vertex_array; vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); - int map_data_current_index = 0; - - for (int d = 0; d < heightmap_depth; d++) { - for (int w = 0; w < heightmap_width; w++) { - if (map_data_current_index + 1 + heightmap_depth < map_data.size()) { - float top_left_height = map_data[map_data_current_index]; - float top_right_height = map_data[map_data_current_index + 1]; - float bottom_left_height = map_data[map_data_current_index + heightmap_depth]; - float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth]; - - Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d); - Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d); - Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0); - Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0); - - vertex_array.push_back(top_right); - vertex_array.push_back(bottom_left); - vertex_array.push_back(top_left); - vertex_array.push_back(top_right); - vertex_array.push_back(bottom_right); - vertex_array.push_back(bottom_left); - } - map_data_current_index += 1; + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + const real_t *map_data_ptr = map_data.ptr(); + int vertex_index = 0; + + for (int d = 0; d < heightmap_depth - 1; d++) { + for (int w = 0; w < heightmap_width - 1; w++) { + vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); + vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); + vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_index += 6; } } if (vertex_array.size() > 0) { @@ -540,33 +530,23 @@ void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref<NavigationMesh> const Vector<real_t> &map_data = dict["heights"]; Vector2 heightmap_gridsize(heightmap_width - 1, heightmap_depth - 1); - Vector2 start = heightmap_gridsize * -0.5; + Vector3 start = Vector3(heightmap_gridsize.x, 0, heightmap_gridsize.y) * -0.5; Vector<Vector3> vertex_array; vertex_array.resize((heightmap_depth - 1) * (heightmap_width - 1) * 6); - int map_data_current_index = 0; - - for (int d = 0; d < heightmap_depth; d++) { - for (int w = 0; w < heightmap_width; w++) { - if (map_data_current_index + 1 + heightmap_depth < map_data.size()) { - float top_left_height = map_data[map_data_current_index]; - float top_right_height = map_data[map_data_current_index + 1]; - float bottom_left_height = map_data[map_data_current_index + heightmap_depth]; - float bottom_right_height = map_data[map_data_current_index + 1 + heightmap_depth]; - - Vector3 top_left = Vector3(start.x + w, top_left_height, start.y + d); - Vector3 top_right = Vector3(start.x + w + 1.0, top_right_height, start.y + d); - Vector3 bottom_left = Vector3(start.x + w, bottom_left_height, start.y + d + 1.0); - Vector3 bottom_right = Vector3(start.x + w + 1.0, bottom_right_height, start.y + d + 1.0); - - vertex_array.push_back(top_right); - vertex_array.push_back(bottom_left); - vertex_array.push_back(top_left); - vertex_array.push_back(top_right); - vertex_array.push_back(bottom_right); - vertex_array.push_back(bottom_left); - } - map_data_current_index += 1; + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + const real_t *map_data_ptr = map_data.ptr(); + int vertex_index = 0; + + for (int d = 0; d < heightmap_depth - 1; d++) { + for (int w = 0; w < heightmap_width - 1; w++) { + vertex_array_ptrw[vertex_index] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + w], d); + vertex_array_ptrw[vertex_index + 1] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 2] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_array_ptrw[vertex_index + 3] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + w + 1], d); + vertex_array_ptrw[vertex_index + 4] = start + Vector3(w + 1, map_data_ptr[(heightmap_width * d) + heightmap_width + w + 1], d + 1); + vertex_array_ptrw[vertex_index + 5] = start + Vector3(w, map_data_ptr[(heightmap_width * d) + heightmap_width + w], d + 1); + vertex_index += 6; } } if (vertex_array.size() > 0) { @@ -714,7 +694,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation bake_state = "Creating heightfield..."; // step #3 hf = rcAllocHeightfield(); - ERR_FAIL_COND(!hf); + ERR_FAIL_NULL(hf); ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)); bake_state = "Marking walkable triangles..."; // step #4 @@ -744,7 +724,7 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation chf = rcAllocCompactHeightfield(); - ERR_FAIL_COND(!chf); + ERR_FAIL_NULL(chf); ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf)); rcFreeHeightField(hf); @@ -769,17 +749,17 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation cset = rcAllocContourSet(); - ERR_FAIL_COND(!cset); + ERR_FAIL_NULL(cset); ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset)); bake_state = "Creating polymesh..."; // step #9 poly_mesh = rcAllocPolyMesh(); - ERR_FAIL_COND(!poly_mesh); + ERR_FAIL_NULL(poly_mesh); ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh)); detail_mesh = rcAllocPolyMeshDetail(); - ERR_FAIL_COND(!detail_mesh); + ERR_FAIL_NULL(detail_mesh); ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh)); rcFreeCompactHeightfield(chf); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 4e7964ed76..09697c7be0 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -89,13 +89,13 @@ int NavRegion::get_connections_count() const { } Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const { - ERR_FAIL_COND_V(!map, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); return connections[p_connection_id].pathway_start; } Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const { - ERR_FAIL_COND_V(!map, Vector3()); + ERR_FAIL_NULL_V(map, Vector3()); ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); return connections[p_connection_id].pathway_end; } @@ -125,11 +125,11 @@ void NavRegion::update_polygons() { #ifdef DEBUG_ENABLED if (!Math::is_equal_approx(double(map->get_cell_size()), double(mesh->get_cell_size()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_size()), double(mesh->get_cell_size()))); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_size()), double(map->get_cell_size()))); } if (!Math::is_equal_approx(double(map->get_cell_height()), double(mesh->get_cell_height()))) { - ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(map->get_cell_height()), double(mesh->get_cell_height()))); + ERR_PRINT_ONCE(vformat("Navigation map synchronization error. Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(mesh->get_cell_height()), double(map->get_cell_height()))); } if (map && Math::rad_to_deg(map->get_up().angle_to(transform.basis.get_column(1))) >= 90.0f) { diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp index 1548ff4b9c..525fe71134 100644 --- a/modules/navigation/register_types.cpp +++ b/modules/navigation/register_types.cpp @@ -31,6 +31,7 @@ #include "register_types.h" #include "godot_navigation_server.h" +#include "godot_navigation_server_2d.h" #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED @@ -43,6 +44,7 @@ #endif #include "core/config/engine.h" +#include "servers/navigation_server_2d.h" #include "servers/navigation_server_3d.h" #ifndef DISABLE_DEPRECATED @@ -55,9 +57,14 @@ NavigationServer3D *new_server() { return memnew(GodotNavigationServer); } +NavigationServer2D *new_navigation_server_2d() { + return memnew(GodotNavigationServer2D); +} + void initialize_navigation_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { NavigationServer3DManager::set_default_server(new_server); + NavigationServer2DManager::set_default_server(new_navigation_server_2d); #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED diff --git a/modules/noise/fastnoise_lite.cpp b/modules/noise/fastnoise_lite.cpp index 4aea98c4de..1b0ef6506b 100644 --- a/modules/noise/fastnoise_lite.cpp +++ b/modules/noise/fastnoise_lite.cpp @@ -416,8 +416,8 @@ void FastNoiseLite::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "noise_type", PROPERTY_HINT_ENUM, "Simplex,Simplex Smooth,Cellular,Perlin,Value Cubic,Value"), "set_noise_type", "get_noise_type"); ADD_PROPERTY(PropertyInfo(Variant::INT, "seed"), "set_seed", "get_seed"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frequency", PROPERTY_HINT_RANGE, ".0001,1,.0001"), "set_frequency", "get_frequency"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "offset", PROPERTY_HINT_RANGE, "-999999999,999999999,0.01"), "set_offset", "get_offset"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "frequency", PROPERTY_HINT_RANGE, ".0001,1,.0001,exp"), "set_frequency", "get_frequency"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "offset", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_offset", "get_offset"); ADD_GROUP("Fractal", "fractal_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fractal_type", PROPERTY_HINT_ENUM, "None,FBM,Ridged,Ping-Pong"), "set_fractal_type", "get_fractal_type"); diff --git a/modules/noise/noise.cpp b/modules/noise/noise.cpp index 1115d92f58..9b9fd640f4 100644 --- a/modules/noise/noise.cpp +++ b/modules/noise/noise.cpp @@ -54,6 +54,9 @@ Vector<Ref<Image>> Noise::_get_seamless_image(int p_width, int p_height, int p_d Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const { Vector<Ref<Image>> images = _get_seamless_image(p_width, p_height, 1, p_invert, p_in_3d_space, p_blend_skirt, p_normalize); + if (images.size() == 0) { + return Ref<Image>(); + } return images[0]; } @@ -163,6 +166,9 @@ Vector<Ref<Image>> Noise::_get_image(int p_width, int p_height, int p_depth, boo Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, bool p_normalize) const { Vector<Ref<Image>> images = _get_image(p_width, p_height, 1, p_invert, p_in_3d_space, p_normalize); + if (images.is_empty()) { + return Ref<Image>(); + } return images[0]; } diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp index fb29493ca9..1100367f03 100644 --- a/modules/ogg/ogg_packet_sequence.cpp +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -159,7 +159,9 @@ bool OggPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { *p_packet = packet; - packet_cursor++; + if (!packet->e_o_s) { // Added this so it doesn't try to go to the next packet if it's the last packet of the file. + packet_cursor++; + } return true; } @@ -216,6 +218,20 @@ bool OggPacketSequencePlayback::seek_page(int64_t p_granule_pos) { return true; } +int64_t OggPacketSequencePlayback::get_page_number() const { + return page_cursor; +} + +bool OggPacketSequencePlayback::set_page_number(int64_t p_page_number) { + if (p_page_number >= 0 && p_page_number < ogg_packet_sequence->page_data.size()) { + page_cursor = p_page_number; + packet_cursor = 0; + packetno = 0; + return true; + } + return false; +} + OggPacketSequencePlayback::OggPacketSequencePlayback() { packet = new ogg_packet(); } diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h index 7085504500..922a16560c 100644 --- a/modules/ogg/ogg_packet_sequence.h +++ b/modules/ogg/ogg_packet_sequence.h @@ -120,6 +120,13 @@ public: // Returns true on success, false on failure. bool seek_page(int64_t p_granule_pos); + // Gets the current page number. + int64_t get_page_number() const; + + // Moves to a specific page in the stream. + // Returns true on success, false if the page number is out of bounds. + bool set_page_number(int64_t p_page_number); + OggPacketSequencePlayback(); virtual ~OggPacketSequencePlayback(); }; diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 1bd10f1009..c64b6de78c 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -70,7 +70,7 @@ if env["builtin_openxr"]: # On Android the openxr_loader is provided by separate plugins for each device # Build the engine using object files khrloader_obj = [] - env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/xr_generated_dispatch_table_core.c") env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") env_thirdparty.add_source_files(khrloader_obj, thirdparty_dir + "/src/common/object_info.cpp") @@ -106,10 +106,13 @@ if env["opengl3"] and env["platform"] != "macos": env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_foveation_extension.cpp") +env_openxr.add_source_files(module_obj, "extensions/openxr_fb_update_swapchain_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp") diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp index 652e5fc407..72866f1cf7 100644 --- a/modules/openxr/action_map/openxr_action_map.cpp +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -206,7 +206,8 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/waist," "/user/vive_tracker_htcx/role/chest," "/user/vive_tracker_htcx/role/camera," - "/user/vive_tracker_htcx/role/keyboard"); + "/user/vive_tracker_htcx/role/keyboard," + "/user/eyes_ext"); Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); Ref<OpenXRAction> palm_pose = action_set->add_new_action("palm_pose", "Palm pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); @@ -470,6 +471,7 @@ void OpenXRActionMap::create_default_action_sets() { profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); // Create our HTC Vive tracker profile profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_tracker_htcx"); @@ -502,6 +504,11 @@ void OpenXRActionMap::create_default_action_sets() { "/user/vive_tracker_htcx/role/camera/output/haptic," "/user/vive_tracker_htcx/role/keyboard/output/haptic"); add_interaction_profile(profile); + + // Create our eye gaze interaction profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction"); + profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose"); + add_interaction_profile(profile); } void OpenXRActionMap::create_editor_action_sets() { diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 8d8cbf1a29..6d1c215ffc 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -31,6 +31,14 @@ If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]! </description> </method> + <method name="get_hand_joint_flags" qualifiers="const"> + <return type="int" enum="OpenXRInterface.HandJointFlags" is_bitfield="true" /> + <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> + <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> + <description> + If handtracking is enabled, returns flags that inform us of the validity of the tracking data. + </description> + </method> <method name="get_hand_joint_linear_velocity" qualifiers="const"> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> @@ -77,6 +85,27 @@ Returns [code]true[/code] if the given action set is active. </description> </method> + <method name="is_eye_gaze_interaction_supported"> + <return type="bool" /> + <description> + Returns the capabilities of the eye gaze interaction extension. + [b]Note:[/b] This only returns a valid value after OpenXR has been initialized. + </description> + </method> + <method name="is_foveation_supported" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if OpenXR's foveation extension is supported, the interface must be initialized before this returns a valid value. + [b]Note:[/b] This feature is only available on the compatibility renderer and currently only available on some stand alone headsets. For Vulkan set [member Viewport.vrs_mode] to [code]VRS_XR[/code] on desktop. + </description> + </method> + <method name="is_hand_tracking_supported"> + <return type="bool" /> + <description> + Returns [code]true[/code] if OpenXR's hand tracking is supported and enabled. + [b]Note:[/b] This only returns a valid value after OpenXR has been initialized. + </description> + </method> <method name="set_action_set_active"> <return type="void" /> <param index="0" name="name" type="String" /> @@ -98,6 +127,12 @@ <member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0"> The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized. </member> + <member name="foveation_dynamic" type="bool" setter="set_foveation_dynamic" getter="get_foveation_dynamic" default="false"> + Enable dynamic foveation adjustment, the interface must be initialized before this is accessible. If enabled foveation will automatically adjusted between low and [member foveation_level]. + </member> + <member name="foveation_level" type="int" setter="set_foveation_level" getter="get_foveation_level" default="0"> + Set foveation level from 0 (off) to 3 (high), the interface must be initialized before this is accessible. + </member> <member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0"> The render size multiplier for the current HMD. Must be set before the interface has been initialized. </member> @@ -226,5 +261,26 @@ <constant name="HAND_JOINT_MAX" value="26" enum="HandJoints"> Maximum value for the hand joint enum. </constant> + <constant name="HAND_JOINT_NONE" value="0" enum="HandJointFlags" is_bitfield="true"> + No flags are set. + </constant> + <constant name="HAND_JOINT_ORIENTATION_VALID" value="1" enum="HandJointFlags" is_bitfield="true"> + If set, the orientation data is valid, otherwise, the orientation data is unreliable and should not be used. + </constant> + <constant name="HAND_JOINT_ORIENTATION_TRACKED" value="2" enum="HandJointFlags" is_bitfield="true"> + If set, the orientation data comes from tracking data, otherwise, the orientation data contains predicted data. + </constant> + <constant name="HAND_JOINT_POSITION_VALID" value="4" enum="HandJointFlags" is_bitfield="true"> + If set, the positional data is valid, otherwise, the positional data is unreliable and should not be used. + </constant> + <constant name="HAND_JOINT_POSITION_TRACKED" value="8" enum="HandJointFlags" is_bitfield="true"> + If set, the positional data comes from tracking data, otherwise, the positional data contains predicted data. + </constant> + <constant name="HAND_JOINT_LINEAR_VELOCITY_VALID" value="16" enum="HandJointFlags" is_bitfield="true"> + If set, our linear velocity data is valid, otherwise, the linear velocity data is unreliable and should not be used. + </constant> + <constant name="HAND_JOINT_ANGULAR_VELOCITY_VALID" value="32" enum="HandJointFlags" is_bitfield="true"> + If set, our angular velocity data is valid, otherwise, the angular velocity data is unreliable and should not be used. + </constant> </constants> </class> diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp index 7bccabf936..da6a9eed70 100644 --- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp +++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp @@ -45,7 +45,6 @@ void OpenXRInteractionProfileEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("_add_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_add_binding); ClassDB::bind_method(D_METHOD("_remove_binding", "action", "path"), &OpenXRInteractionProfileEditorBase::_remove_binding); - ClassDB::bind_method(D_METHOD("_update_interaction_profile"), &OpenXRInteractionProfileEditorBase::_update_interaction_profile); } void OpenXRInteractionProfileEditorBase::_notification(int p_what) { @@ -63,7 +62,7 @@ void OpenXRInteractionProfileEditorBase::_notification(int p_what) { void OpenXRInteractionProfileEditorBase::_do_update_interaction_profile() { if (!is_dirty) { is_dirty = true; - call_deferred("_update_interaction_profile"); + callable_mp(this, &OpenXRInteractionProfileEditorBase::_update_interaction_profile).call_deferred(); } } diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp new file mode 100644 index 0000000000..59bdec5c8e --- /dev/null +++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* openxr_eye_gaze_interaction.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "openxr_eye_gaze_interaction.h" + +#include "core/os/os.h" + +#include "../action_map/openxr_interaction_profile_metadata.h" + +OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::singleton = nullptr; + +OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::get_singleton() { + ERR_FAIL_NULL_V(singleton, nullptr); + return singleton; +} + +OpenXREyeGazeInteractionExtension::OpenXREyeGazeInteractionExtension() { + singleton = this; +} + +OpenXREyeGazeInteractionExtension::~OpenXREyeGazeInteractionExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXREyeGazeInteractionExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME] = &available; + + return request_extensions; +} + +void *OpenXREyeGazeInteractionExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) { + if (!available) { + return p_next_pointer; + } + + properties.type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT; + properties.next = p_next_pointer; + properties.supportsEyeGazeInteraction = false; + + return &properties; +} + +bool OpenXREyeGazeInteractionExtension::is_available() { + return available; +} + +bool OpenXREyeGazeInteractionExtension::supports_eye_gaze_interaction() { + // The extension being available only means that the OpenXR Runtime supports the extension. + // The `supportsEyeGazeInteraction` is set to true if the device also supports this. + // Thus both need to be true. + // In addition, on mobile runtimes, the proper permission needs to be granted. + if (available && properties.supportsEyeGazeInteraction) { + return !OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature("PERMISSION_XR_EXT_eye_gaze_interaction"); + } + + return false; +} + +void OpenXREyeGazeInteractionExtension::on_register_metadata() { + OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Eyes top path + metadata->register_top_level_path("Eye gaze tracker", "/user/eyes_ext", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME); + + // Eye gaze interaction + metadata->register_interaction_profile("Eye gaze", "/interaction_profiles/ext/eye_gaze_interaction", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME); + metadata->register_io_path("/interaction_profiles/ext/eye_gaze_interaction", "Gaze pose", "/user/eyes_ext", "/user/eyes_ext/input/gaze_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE); +} diff --git a/modules/denoise/lightmap_denoiser.h b/modules/openxr/extensions/openxr_eye_gaze_interaction.h index 8f658ab096..704940ad26 100644 --- a/modules/denoise/lightmap_denoiser.h +++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* lightmap_denoiser.h */ +/* openxr_eye_gaze_interaction.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef LIGHTMAP_DENOISER_H -#define LIGHTMAP_DENOISER_H +#ifndef OPENXR_EYE_GAZE_INTERACTION_H +#define OPENXR_EYE_GAZE_INTERACTION_H -#include "core/object/class_db.h" -#include "scene/3d/lightmapper.h" +#include "openxr_extension_wrapper.h" -struct OIDNDeviceImpl; +class OpenXREyeGazeInteractionExtension : public OpenXRExtensionWrapper { +public: + static OpenXREyeGazeInteractionExtension *get_singleton(); -class LightmapDenoiserOIDN : public LightmapDenoiser { - GDCLASS(LightmapDenoiserOIDN, LightmapDenoiser); + OpenXREyeGazeInteractionExtension(); + ~OpenXREyeGazeInteractionExtension(); -protected: - void *device = nullptr; + virtual HashMap<String, bool *> get_requested_extensions() override; + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override; -public: - static LightmapDenoiser *create_oidn_denoiser(); + bool is_available(); + bool supports_eye_gaze_interaction(); - Ref<Image> denoise_image(const Ref<Image> &p_image) override; + virtual void on_register_metadata() override; - static void make_default_denoiser(); +private: + static OpenXREyeGazeInteractionExtension *singleton; - LightmapDenoiserOIDN(); - ~LightmapDenoiserOIDN(); + bool available = false; + XrSystemEyeGazeInteractionPropertiesEXT properties; }; -#endif // LIGHTMAP_DENOISER_H +#endif // OPENXR_EYE_GAZE_INTERACTION_H diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.cpp b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp new file mode 100644 index 0000000000..bbdd2e3c8a --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.cpp @@ -0,0 +1,168 @@ +/**************************************************************************/ +/* openxr_fb_foveation_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "openxr_fb_foveation_extension.h" +#include "core/config/project_settings.h" + +OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::singleton = nullptr; + +OpenXRFBFoveationExtension *OpenXRFBFoveationExtension::get_singleton() { + return singleton; +} + +OpenXRFBFoveationExtension::OpenXRFBFoveationExtension(const String &p_rendering_driver) { + singleton = this; + rendering_driver = p_rendering_driver; + swapchain_update_state_ext = OpenXRFBUpdateSwapchainExtension::get_singleton(); + int fov_level = GLOBAL_GET("xr/openxr/foveation_level"); + if (fov_level >= 0 && fov_level < 4) { + foveation_level = XrFoveationLevelFB(fov_level); + } + bool fov_dyn = GLOBAL_GET("xr/openxr/foveation_dynamic"); + foveation_dynamic = fov_dyn ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB; + + swapchain_create_info_foveation_fb.type = XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB; + swapchain_create_info_foveation_fb.next = nullptr; + swapchain_create_info_foveation_fb.flags = 0; +} + +OpenXRFBFoveationExtension::~OpenXRFBFoveationExtension() { + singleton = nullptr; + swapchain_update_state_ext = nullptr; +} + +HashMap<String, bool *> OpenXRFBFoveationExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + if (rendering_driver == "vulkan") { + // This is currently only supported on OpenGL, but we may add Vulkan support in the future... + + } else if (rendering_driver == "opengl3") { + request_extensions[XR_FB_FOVEATION_EXTENSION_NAME] = &fb_foveation_ext; + request_extensions[XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME] = &fb_foveation_configuration_ext; + } + + return request_extensions; +} + +void OpenXRFBFoveationExtension::on_instance_created(const XrInstance p_instance) { + if (fb_foveation_ext) { + EXT_INIT_XR_FUNC(xrCreateFoveationProfileFB); + EXT_INIT_XR_FUNC(xrDestroyFoveationProfileFB); + } + + if (fb_foveation_configuration_ext) { + // nothing to register here... + } +} + +void OpenXRFBFoveationExtension::on_instance_destroyed() { + fb_foveation_ext = false; + fb_foveation_configuration_ext = false; +} + +bool OpenXRFBFoveationExtension::is_enabled() const { + return swapchain_update_state_ext != nullptr && swapchain_update_state_ext->is_enabled() && fb_foveation_ext && fb_foveation_configuration_ext; +} + +void *OpenXRFBFoveationExtension::set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { + if (is_enabled()) { + swapchain_create_info_foveation_fb.next = p_next_pointer; + return &swapchain_create_info_foveation_fb; + } else { + return p_next_pointer; + } +} + +void OpenXRFBFoveationExtension::on_state_ready() { + update_profile(); +} + +XrFoveationLevelFB OpenXRFBFoveationExtension::get_foveation_level() const { + return foveation_level; +} + +void OpenXRFBFoveationExtension::set_foveation_level(XrFoveationLevelFB p_foveation_level) { + foveation_level = p_foveation_level; + + // Update profile will do nothing if we're not yet initialized. + update_profile(); +} + +XrFoveationDynamicFB OpenXRFBFoveationExtension::get_foveation_dynamic() const { + return foveation_dynamic; +} + +void OpenXRFBFoveationExtension::set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic) { + foveation_dynamic = p_foveation_dynamic; + + // Update profile will do nothing if we're not yet initialized. + update_profile(); +} + +void OpenXRFBFoveationExtension::update_profile() { + if (!is_enabled()) { + return; + } + + XrFoveationLevelProfileCreateInfoFB level_profile_create_info; + level_profile_create_info.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB; + level_profile_create_info.next = nullptr; + level_profile_create_info.level = foveation_level; + level_profile_create_info.verticalOffset = 0.0f; + level_profile_create_info.dynamic = foveation_dynamic; + + XrFoveationProfileCreateInfoFB profile_create_info; + profile_create_info.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB; + profile_create_info.next = &level_profile_create_info; + + XrFoveationProfileFB foveation_profile; + XrResult result = xrCreateFoveationProfileFB(OpenXRAPI::get_singleton()->get_session(), &profile_create_info, &foveation_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to create the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + return; + } + + XrSwapchainStateFoveationFB foveation_update_state; + foveation_update_state.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB; + foveation_update_state.profile = foveation_profile; + + result = swapchain_update_state_ext->xrUpdateSwapchainFB(OpenXRAPI::get_singleton()->get_color_swapchain(), (XrSwapchainStateBaseHeaderFB *)&foveation_update_state); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to update the swapchain [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + + // We still want to destroy our profile so keep going... + } + + result = xrDestroyFoveationProfileFB(foveation_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to destroy the foveation profile [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + } +} diff --git a/modules/openxr/extensions/openxr_fb_foveation_extension.h b/modules/openxr/extensions/openxr_fb_foveation_extension.h new file mode 100644 index 0000000000..1c5e722731 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_foveation_extension.h @@ -0,0 +1,96 @@ +/**************************************************************************/ +/* openxr_fb_foveation_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 OPENXR_FB_FOVEATION_EXTENSION_H +#define OPENXR_FB_FOVEATION_EXTENSION_H + +// This extension implements the FB Foveation extension. +// This is an extension Meta added due to VRS being unavailable on Android. +// Other Android based devices are implementing this as well, see: +// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_foveation + +// Note: Currently we only support this for OpenGL. +// This extension works on enabling foveated rendering on the swapchain. +// Vulkan does not render 3D content directly to the swapchain image +// hence this extension can't be used. + +#include "../openxr_api.h" +#include "../util.h" +#include "openxr_extension_wrapper.h" +#include "openxr_fb_update_swapchain_extension.h" + +class OpenXRFBFoveationExtension : public OpenXRExtensionWrapper { +public: + static OpenXRFBFoveationExtension *get_singleton(); + + OpenXRFBFoveationExtension(const String &p_rendering_driver); + virtual ~OpenXRFBFoveationExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) override; + + virtual void on_state_ready() override; + + bool is_enabled() const; + + XrFoveationLevelFB get_foveation_level() const; + void set_foveation_level(XrFoveationLevelFB p_foveation_level); + + XrFoveationDynamicFB get_foveation_dynamic() const; + void set_foveation_dynamic(XrFoveationDynamicFB p_foveation_dynamic); + +private: + static OpenXRFBFoveationExtension *singleton; + + // Setup + String rendering_driver; + bool fb_foveation_ext = false; + bool fb_foveation_configuration_ext = false; + + // Configuration + XrFoveationLevelFB foveation_level = XR_FOVEATION_LEVEL_NONE_FB; + XrFoveationDynamicFB foveation_dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB; + + void update_profile(); + + // Enable foveation on this swapchain + XrSwapchainCreateInfoFoveationFB swapchain_create_info_foveation_fb; + OpenXRFBUpdateSwapchainExtension *swapchain_update_state_ext = nullptr; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateFoveationProfileFB, (XrSession), session, (const XrFoveationProfileCreateInfoFB *), create_info, (XrFoveationProfileFB *), profile); + EXT_PROTO_XRRESULT_FUNC1(xrDestroyFoveationProfileFB, (XrFoveationProfileFB), profile); +}; + +#endif // OPENXR_FB_FOVEATION_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp new file mode 100644 index 0000000000..1289183ea4 --- /dev/null +++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp @@ -0,0 +1,102 @@ +/**************************************************************************/ +/* openxr_fb_update_swapchain_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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 "openxr_fb_update_swapchain_extension.h" + +// always include this as late as possible +#include "../openxr_platform_inc.h" + +OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr; + +OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::get_singleton() { + return singleton; +} + +OpenXRFBUpdateSwapchainExtension::OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver) { + singleton = this; + rendering_driver = p_rendering_driver; +} + +OpenXRFBUpdateSwapchainExtension::~OpenXRFBUpdateSwapchainExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRFBUpdateSwapchainExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME] = &fb_swapchain_update_state_ext; + + if (rendering_driver == "vulkan") { +#ifdef XR_USE_GRAPHICS_API_VULKAN + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME] = &fb_swapchain_update_state_vulkan_ext; +#endif + } else if (rendering_driver == "opengl3") { +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + request_extensions[XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME] = &fb_swapchain_update_state_opengles_ext; +#endif + } + + return request_extensions; +} + +void OpenXRFBUpdateSwapchainExtension::on_instance_created(const XrInstance p_instance) { + if (fb_swapchain_update_state_ext) { + EXT_INIT_XR_FUNC(xrUpdateSwapchainFB); + EXT_INIT_XR_FUNC(xrGetSwapchainStateFB); + } + + if (fb_swapchain_update_state_vulkan_ext) { + // nothing to register here... + } + + if (fb_swapchain_update_state_opengles_ext) { + // nothing to register here... + } +} + +void OpenXRFBUpdateSwapchainExtension::on_instance_destroyed() { + fb_swapchain_update_state_ext = false; + fb_swapchain_update_state_vulkan_ext = false; + fb_swapchain_update_state_opengles_ext = false; +} + +bool OpenXRFBUpdateSwapchainExtension::is_enabled() const { + if (rendering_driver == "vulkan") { + return fb_swapchain_update_state_ext && fb_swapchain_update_state_vulkan_ext; + } else if (rendering_driver == "opengl3") { +#ifdef XR_USE_GRAPHICS_API_OPENGL_ES + return fb_swapchain_update_state_ext && fb_swapchain_update_state_opengles_ext; +#else + return fb_swapchain_update_state_ext; +#endif + } + + return false; +} diff --git a/modules/denoise/denoise_wrapper.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h index 87f02cb4c6..a02b550e58 100644 --- a/modules/denoise/denoise_wrapper.cpp +++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* denoise_wrapper.cpp */ +/* openxr_fb_update_swapchain_extension.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,39 +28,46 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "denoise_wrapper.h" +#ifndef OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H +#define OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H -#include <OpenImageDenoise/oidn.h> +// This extension implements the FB update swapchain extension. +// This is an extension Meta added to further configure the swapchain. +// Other Android based devices are implementing this as well, see: +// https://github.khronos.org/OpenXR-Inventory/extension_support.html#XR_FB_swapchain_update_state -#include <stdio.h> +#include "../openxr_api.h" +#include "../util.h" +#include "openxr_extension_wrapper.h" -void *oidn_denoiser_init() { - OIDNDeviceImpl *device = oidnNewDevice(OIDN_DEVICE_TYPE_CPU); - oidnCommitDevice(device); - return device; -} +class OpenXRFBUpdateSwapchainExtension : public OpenXRExtensionWrapper { + friend class OpenXRFBFoveationExtension; -bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { - OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr; - OIDNFilter filter = oidnNewFilter(device, "RTLightmap"); - oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); - oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); - oidnSetFilter1b(filter, "hdr", true); - //oidnSetFilter1f(filter, "hdrScale", 1.0f); - oidnCommitFilter(filter); - oidnExecuteFilter(filter); +public: + static OpenXRFBUpdateSwapchainExtension *get_singleton(); - const char *msg; - bool success = true; - if (oidnGetDeviceError(device, &msg) != OIDN_ERROR_NONE) { - printf("LightmapDenoiser: %s\n", msg); - success = false; - } + OpenXRFBUpdateSwapchainExtension(const String &p_rendering_driver); + virtual ~OpenXRFBUpdateSwapchainExtension() override; - oidnReleaseFilter(filter); - return success; -} + virtual HashMap<String, bool *> get_requested_extensions() override; -void oidn_denoiser_finish(void *device) { - oidnReleaseDevice((OIDNDeviceImpl *)device); -} + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + bool is_enabled() const; + +private: + static OpenXRFBUpdateSwapchainExtension *singleton; + + // Setup + String rendering_driver; + bool fb_swapchain_update_state_ext = false; + bool fb_swapchain_update_state_vulkan_ext = false; + bool fb_swapchain_update_state_opengles_ext = false; + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC2(xrUpdateSwapchainFB, (XrSwapchain), swapchain, (const XrSwapchainStateBaseHeaderFB *), state); + EXT_PROTO_XRRESULT_FUNC2(xrGetSwapchainStateFB, (XrSwapchain), swapchain, (XrSwapchainStateBaseHeaderFB *), state); +}; + +#endif // OPENXR_FB_UPDATE_SWAPCHAIN_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index c92b2b08d0..0d667b56c6 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -32,6 +32,7 @@ #include "../openxr_api.h" +#include "core/config/project_settings.h" #include "core/string/print_string.h" #include "servers/xr_server.h" @@ -59,7 +60,6 @@ HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext; request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext; - request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &hand_tracking_aim_state_ext; return request_extensions; } @@ -106,17 +106,11 @@ void OpenXRHandTrackingExtension::on_state_ready() { } // Setup our hands and reset data - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { // we'll do this later hand_trackers[i].is_initialized = false; hand_trackers[i].hand_tracker = XR_NULL_HANDLE; - hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; - hand_trackers[i].aimState.pinchStrengthIndex = 0.0; - hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; - hand_trackers[i].aimState.pinchStrengthRing = 0.0; - hand_trackers[i].aimState.pinchStrengthLittle = 0.0; - hand_trackers[i].locations.isActive = false; for (int j = 0; j < XR_HAND_JOINT_COUNT_EXT; j++) { @@ -141,7 +135,7 @@ void OpenXRHandTrackingExtension::on_process() { XrResult result; - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { if (hand_trackers[i].hand_tracker == XR_NULL_HANDLE) { XrHandTrackerCreateInfoEXT createInfo = { XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, // type @@ -157,18 +151,6 @@ void OpenXRHandTrackingExtension::on_process() { hand_trackers[i].is_initialized = false; } else { void *next_pointer = nullptr; - if (hand_tracking_aim_state_ext) { - hand_trackers[i].aimState.type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB; - hand_trackers[i].aimState.next = next_pointer; - hand_trackers[i].aimState.status = 0; - hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } }; - hand_trackers[i].aimState.pinchStrengthIndex = 0.0; - hand_trackers[i].aimState.pinchStrengthMiddle = 0.0; - hand_trackers[i].aimState.pinchStrengthRing = 0.0; - hand_trackers[i].aimState.pinchStrengthLittle = 0.0; - - next_pointer = &hand_trackers[i].aimState; - } hand_trackers[i].velocities.type = XR_TYPE_HAND_JOINT_VELOCITIES_EXT; hand_trackers[i].velocities.next = next_pointer; @@ -219,20 +201,6 @@ void OpenXRHandTrackingExtension::on_process() { !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive } - - /* TODO change this to managing the controller from openxr_interface - if (hand_tracking_aim_state_ext && hand_trackers[i].locations.isActive && check_bit(XR_HAND_TRACKING_AIM_VALID_BIT_FB, hand_trackers[i].aimState.status)) { - // Controllers are updated based on the aim state's pose and pinches' strength - if (hand_trackers[i].aim_state_godot_controller == -1) { - hand_trackers[i].aim_state_godot_controller = - arvr_api->godot_arvr_add_controller( - const_cast<char *>(hand_controller_names[i]), - i + HAND_CONTROLLER_ID_OFFSET, - true, - true); - } - } - */ } } } @@ -246,7 +214,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); - for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) { + for (int i = 0; i < OPENXR_MAX_TRACKED_HANDS; i++) { if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) { xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker); @@ -260,25 +228,37 @@ bool OpenXRHandTrackingExtension::get_active() { return handTrackingSystemProperties.supportsHandTracking; } -const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(uint32_t p_hand) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, nullptr); +const OpenXRHandTrackingExtension::HandTracker *OpenXRHandTrackingExtension::get_hand_tracker(HandTrackedHands p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, nullptr); return &hand_trackers[p_hand]; } -XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(uint32_t p_hand) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT); +XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTrackedHands p_hand) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XR_HAND_JOINTS_MOTION_RANGE_MAX_ENUM_EXT); return hand_trackers[p_hand].motion_range; } -void OpenXRHandTrackingExtension::set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range) { - ERR_FAIL_UNSIGNED_INDEX(p_hand, MAX_OPENXR_TRACKED_HANDS); +void OpenXRHandTrackingExtension::set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range) { + ERR_FAIL_UNSIGNED_INDEX(p_hand, OPENXR_MAX_TRACKED_HANDS); hand_trackers[p_hand].motion_range = p_motion_range; } -Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Quaternion()); +XrSpaceLocationFlags OpenXRHandTrackingExtension::get_hand_joint_location_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XrSpaceLocationFlags(0)); + ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, XrSpaceLocationFlags(0)); + + if (!hand_trackers[p_hand].is_initialized) { + return XrSpaceLocationFlags(0); + } + + const XrHandJointLocationEXT &location = hand_trackers[p_hand].joint_locations[p_joint]; + return location.locationFlags; +} + +Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Quaternion()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Quaternion()); if (!hand_trackers[p_hand].is_initialized) { @@ -289,8 +269,8 @@ Quaternion OpenXRHandTrackingExtension::get_hand_joint_rotation(uint32_t p_hand, return Quaternion(location.pose.orientation.x, location.pose.orientation.y, location.pose.orientation.z, location.pose.orientation.w); } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { @@ -301,8 +281,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_position(uint32_t p_hand, Xr return Vector3(location.pose.position.x, location.pose.position.y, location.pose.position.z); } -float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, 0.0); +float OpenXRHandTrackingExtension::get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, 0.0); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, 0.0); if (!hand_trackers[p_hand].is_initialized) { @@ -312,8 +292,20 @@ float OpenXRHandTrackingExtension::get_hand_joint_radius(uint32_t p_hand, XrHand return hand_trackers[p_hand].joint_locations[p_joint].radius; } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +XrSpaceVelocityFlags OpenXRHandTrackingExtension::get_hand_joint_velocity_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, XrSpaceVelocityFlags(0)); + ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, XrSpaceVelocityFlags(0)); + + if (!hand_trackers[p_hand].is_initialized) { + return XrSpaceVelocityFlags(0); + } + + const XrHandJointVelocityEXT &velocity = hand_trackers[p_hand].joint_velocities[p_joint]; + return velocity.velocityFlags; +} + +Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { @@ -324,8 +316,8 @@ Vector3 OpenXRHandTrackingExtension::get_hand_joint_linear_velocity(uint32_t p_h return Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z); } -Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const { - ERR_FAIL_UNSIGNED_INDEX_V(p_hand, MAX_OPENXR_TRACKED_HANDS, Vector3()); +Vector3 OpenXRHandTrackingExtension::get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, Vector3()); ERR_FAIL_UNSIGNED_INDEX_V(p_joint, XR_HAND_JOINT_COUNT_EXT, Vector3()); if (!hand_trackers[p_hand].is_initialized) { diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h index 99d315c525..f9b26fd604 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.h +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -35,10 +35,14 @@ #include "core/math/quaternion.h" #include "openxr_extension_wrapper.h" -#define MAX_OPENXR_TRACKED_HANDS 2 - class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper { public: + enum HandTrackedHands { + OPENXR_TRACKED_LEFT_HAND, + OPENXR_TRACKED_RIGHT_HAND, + OPENXR_MAX_TRACKED_HANDS + }; + struct HandTracker { bool is_initialized = false; XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; @@ -47,7 +51,6 @@ public: XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; XrHandJointVelocityEXT joint_velocities[XR_HAND_JOINT_COUNT_EXT]; - XrHandTrackingAimStateFB aimState; XrHandJointVelocitiesEXT velocities; XrHandJointLocationsEXT locations; }; @@ -69,29 +72,30 @@ public: virtual void on_state_stopping() override; bool get_active(); - const HandTracker *get_hand_tracker(uint32_t p_hand) const; + const HandTracker *get_hand_tracker(HandTrackedHands p_hand) const; - XrHandJointsMotionRangeEXT get_motion_range(uint32_t p_hand) const; - void set_motion_range(uint32_t p_hand, XrHandJointsMotionRangeEXT p_motion_range); + XrHandJointsMotionRangeEXT get_motion_range(HandTrackedHands p_hand) const; + void set_motion_range(HandTrackedHands p_hand, XrHandJointsMotionRangeEXT p_motion_range); - Quaternion get_hand_joint_rotation(uint32_t p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_position(uint32_t p_hand, XrHandJointEXT p_joint) const; - float get_hand_joint_radius(uint32_t p_hand, XrHandJointEXT p_joint) const; + XrSpaceLocationFlags get_hand_joint_location_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Quaternion get_hand_joint_rotation(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_position(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + float get_hand_joint_radius(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_linear_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const; - Vector3 get_hand_joint_angular_velocity(uint32_t p_hand, XrHandJointEXT p_joint) const; + XrSpaceVelocityFlags get_hand_joint_velocity_flags(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_linear_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; + Vector3 get_hand_joint_angular_velocity(HandTrackedHands p_hand, XrHandJointEXT p_joint) const; private: static OpenXRHandTrackingExtension *singleton; // state XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties; - HandTracker hand_trackers[MAX_OPENXR_TRACKED_HANDS]; // Fixed for left and right hand + HandTracker hand_trackers[OPENXR_MAX_TRACKED_HANDS]; // Fixed for left and right hand // related extensions bool hand_tracking_ext = false; bool hand_motion_range_ext = false; - bool hand_tracking_aim_state_ext = false; // functions void cleanup_hand_tracking(); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index 598d3415ad..3b0aa0bce9 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -39,39 +39,8 @@ #include "core/templates/vector.h" -#ifdef ANDROID_ENABLED -#define XR_USE_GRAPHICS_API_OPENGL_ES -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES3/gl3.h> -#include <GLES3/gl3ext.h> -#else -#define XR_USE_GRAPHICS_API_OPENGL -#endif - -#ifdef WINDOWS_ENABLED -// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform -// however due to the way the openxr headers are put together, we have no choice. -#include <windows.h> -#endif - -#ifdef X11_ENABLED -#include OPENGL_INCLUDE_H -#define GL_GLEXT_PROTOTYPES 1 -#define GL3_PROTOTYPES 1 -#include "thirdparty/glad/glad/gl.h" -#include "thirdparty/glad/glad/glx.h" - -#include <X11/Xlib.h> -#endif - -#ifdef ANDROID_ENABLED -// The jobject type from jni.h is used by openxr_platform.h on Android. -#include <jni.h> -#endif - -// Include platform dependent structs. -#include <openxr/openxr_platform.h> +// always include this as late as possible +#include "../openxr_platform_inc.h" class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h index 4add6f6fa2..f31621fda0 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -36,24 +36,9 @@ #include "openxr_extension_wrapper.h" #include "core/templates/vector.h" -#include "drivers/vulkan/vulkan_context.h" -// Need to include Vulkan so we know of type definitions. -#define XR_USE_GRAPHICS_API_VULKAN - -#ifdef WINDOWS_ENABLED -// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform -// however due to the way the openxr headers are put together, we have no choice. -#include <windows.h> -#endif - -#ifdef ANDROID_ENABLED -// The jobject type from jni.h is used by openxr_platform.h on Android. -#include <jni.h> -#endif - -// Include platform dependent structs. -#include <openxr/openxr_platform.h> +// always include this as late as possible +#include "../openxr_platform_inc.h" class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { public: diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index a66afee1c5..dc3ccccd08 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -43,31 +43,7 @@ #include "editor/editor_settings.h" #endif -// We need to have all the graphics API defines before the Vulkan or OpenGL -// extensions are included, otherwise we'll only get one graphics API. -#ifdef VULKAN_ENABLED -#define XR_USE_GRAPHICS_API_VULKAN -#endif -#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -#ifdef ANDROID_ENABLED -#define XR_USE_GRAPHICS_API_OPENGL_ES -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES3/gl3.h> -#include <GLES3/gl3ext.h> -#else -#define XR_USE_GRAPHICS_API_OPENGL -#endif // ANDROID_ENABLED -#ifdef X11_ENABLED -#include OPENGL_INCLUDE_H -#define GL_GLEXT_PROTOTYPES 1 -#define GL3_PROTOTYPES 1 -#include "thirdparty/glad/glad/gl.h" -#include "thirdparty/glad/glad/glx.h" - -#include <X11/Xlib.h> -#endif // X11_ENABLED -#endif // GLES_ENABLED +#include "openxr_platform_inc.h" #ifdef VULKAN_ENABLED #include "extensions/openxr_vulkan_extension.h" @@ -79,7 +55,9 @@ #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" +#include "extensions/openxr_fb_foveation_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" +#include "extensions/openxr_fb_update_swapchain_extension.h" #ifdef ANDROID_ENABLED #define OPENXR_LOADER_NAME "libopenxr_loader.so" @@ -481,11 +459,19 @@ bool OpenXRAPI::load_supported_view_configuration_types() { result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations"); + ERR_FAIL_COND_V_MSG(num_view_configuration_types == 0, false, "OpenXR: Failed to enumerateview configurations"); // JIC there should be at least 1! for (uint32_t i = 0; i < num_view_configuration_types; i++) { print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); } + // Check value we loaded at startup... + if (!is_view_configuration_supported(view_configuration)) { + print_verbose(String("OpenXR: ") + OpenXRUtil::get_view_configuration_name(view_configuration) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[0])); + + view_configuration = supported_view_configuration_types[0]; + } + return true; } @@ -512,11 +498,19 @@ bool OpenXRAPI::load_supported_environmental_blend_modes() { result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, num_supported_environment_blend_modes, &num_supported_environment_blend_modes, supported_environment_blend_modes); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate environmental blend modes"); + ERR_FAIL_COND_V_MSG(num_supported_environment_blend_modes == 0, false, "OpenXR: Failed to enumerate environmental blend modes"); // JIC there should be at least 1! for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) { print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i])); } + // Check value we loaded at startup... + if (!is_environment_blend_mode_supported(environment_blend_mode)) { + print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0])); + + environment_blend_mode = supported_environment_blend_modes[0]; + } + return true; } @@ -665,11 +659,19 @@ bool OpenXRAPI::load_supported_reference_spaces() { result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces"); + ERR_FAIL_COND_V_MSG(num_reference_spaces == 0, false, "OpenXR: Failed to enumerate reference spaces"); for (uint32_t i = 0; i < num_reference_spaces; i++) { print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); } + // Check value we loaded at startup... + if (!is_reference_space_supported(reference_space)) { + print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0])); + + reference_space = supported_reference_spaces[0]; + } + return true; } @@ -802,6 +804,7 @@ bool OpenXRAPI::create_swapchains() { */ Size2 recommended_size = get_recommended_target_size(); + uint32_t sample_count = 1; // We start with our color swapchain... { @@ -825,7 +828,7 @@ bool OpenXRAPI::create_swapchains() { print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); } - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { return false; } } @@ -861,7 +864,7 @@ bool OpenXRAPI::create_swapchains() { // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning. - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { return false; } @@ -1317,6 +1320,10 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) { ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); } + // Also register our rendering extensions + register_extension_wrapper(memnew(OpenXRFBUpdateSwapchainExtension(p_rendering_driver))); + register_extension_wrapper(memnew(OpenXRFBFoveationExtension(p_rendering_driver))); + // initialize for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_before_instance_created(); @@ -1434,7 +1441,9 @@ Size2 OpenXRAPI::get_recommended_target_size() { XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { XrResult result; - ERR_FAIL_COND_V(!running, XRPose::XR_TRACKING_CONFIDENCE_NONE); + if (!running) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } // xrWaitFrame not run yet if (frame_state.predictedDisplayTime == 0) { @@ -1487,7 +1496,9 @@ XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, } bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { - ERR_FAIL_COND_V(!running, false); + if (!running) { + return false; + } // xrWaitFrame not run yet if (frame_state.predictedDisplayTime == 0) { @@ -1506,9 +1517,12 @@ bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { } bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) { - ERR_FAIL_COND_V(!running, false); ERR_FAIL_NULL_V(graphics_extension, false); + if (!running) { + return false; + } + // xrWaitFrame not run yet if (frame_state.predictedDisplayTime == 0) { return false; @@ -1670,14 +1684,27 @@ bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse... XrResult result; - XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { - XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type - nullptr // next - }; - result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); - return false; + + if (!p_swapchain.skip_acquire_swapchain) { + XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type + nullptr // next + }; + + result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + frame_state.shouldRender = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); + return false; + } else { + // In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain. + return false; + } + } } XrSwapchainImageWaitInfo swapchain_image_wait_info = { @@ -1687,9 +1714,21 @@ bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { }; result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info); - if (XR_FAILED(result)) { - print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); - return false; + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + frame_state.shouldRender = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); + return false; + } else { + // Make sure to skip trying to acquire the swapchain image in the next frame + p_swapchain.skip_acquire_swapchain = true; + return false; + } + } else { + p_swapchain.skip_acquire_swapchain = false; } return true; @@ -1829,6 +1868,10 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { return true; } +XrSwapchain OpenXRAPI::get_color_swapchain() { + return swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; +} + RID OpenXRAPI::get_color_texture() { if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index); @@ -1917,10 +1960,15 @@ void OpenXRAPI::end_frame() { } } + XrCompositionLayerFlags layer_flags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT; + if (layers_list.size() > 0 || environment_blend_mode != XR_ENVIRONMENT_BLEND_MODE_OPAQUE) { + layer_flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; + } + XrCompositionLayerProjection projection_layer = { XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type nullptr, // next - layers_list.size() > 0 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags + layer_flags, // layerFlags play_space, // space view_count, // viewCount projection_views, // views @@ -1975,6 +2023,55 @@ void OpenXRAPI::set_render_target_size_multiplier(double multiplier) { render_target_size_multiplier = multiplier; } +bool OpenXRAPI::is_foveation_supported() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + return fov_ext != nullptr && fov_ext->is_enabled(); +} + +int OpenXRAPI::get_foveation_level() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + switch (fov_ext->get_foveation_level()) { + case XR_FOVEATION_LEVEL_NONE_FB: + return 0; + case XR_FOVEATION_LEVEL_LOW_FB: + return 1; + case XR_FOVEATION_LEVEL_MEDIUM_FB: + return 2; + case XR_FOVEATION_LEVEL_HIGH_FB: + return 3; + default: + return 0; + } + } + + return 0; +} + +void OpenXRAPI::set_foveation_level(int p_foveation_level) { + ERR_FAIL_UNSIGNED_INDEX(p_foveation_level, 4); + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + XrFoveationLevelFB levels[] = { XR_FOVEATION_LEVEL_NONE_FB, XR_FOVEATION_LEVEL_LOW_FB, XR_FOVEATION_LEVEL_MEDIUM_FB, XR_FOVEATION_LEVEL_HIGH_FB }; + fov_ext->set_foveation_level(levels[p_foveation_level]); + } +} + +bool OpenXRAPI::get_foveation_dynamic() const { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + return fov_ext->get_foveation_dynamic() == XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB; + } + return false; +} + +void OpenXRAPI::set_foveation_dynamic(bool p_foveation_dynamic) { + OpenXRFBFoveationExtension *fov_ext = OpenXRFBFoveationExtension::get_singleton(); + if (fov_ext != nullptr && fov_ext->is_enabled()) { + fov_ext->set_foveation_dynamic(p_foveation_dynamic ? XR_FOVEATION_DYNAMIC_LEVEL_ENABLED_FB : XR_FOVEATION_DYNAMIC_DISABLED_FB); + } +} + OpenXRAPI::OpenXRAPI() { // OpenXRAPI is only constructed if OpenXR is enabled. singleton = this; @@ -1984,8 +2081,8 @@ OpenXRAPI::OpenXRAPI() { } else { // Load settings from project settings - int ff = GLOBAL_GET("xr/openxr/form_factor"); - switch (ff) { + int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor"); + switch (form_factor_setting) { case 0: { form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; } break; @@ -1996,8 +2093,8 @@ OpenXRAPI::OpenXRAPI() { break; } - int vc = GLOBAL_GET("xr/openxr/view_configuration"); - switch (vc) { + int view_configuration_setting = GLOBAL_GET("xr/openxr/view_configuration"); + switch (view_configuration_setting) { case 0: { view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO; } break; @@ -2016,8 +2113,8 @@ OpenXRAPI::OpenXRAPI() { break; } - int rs = GLOBAL_GET("xr/openxr/reference_space"); - switch (rs) { + int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space"); + switch (reference_space_setting) { case 0: { reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; } break; @@ -2028,6 +2125,21 @@ OpenXRAPI::OpenXRAPI() { break; } + int environment_blend_mode_setting = GLOBAL_GET("xr/openxr/environment_blend_mode"); + switch (environment_blend_mode_setting) { + case 0: { + environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + } break; + case 1: { + environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE; + } break; + case 2: { + environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND; + } break; + default: + break; + } + submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } @@ -2857,12 +2969,24 @@ const XrEnvironmentBlendMode *OpenXRAPI::get_supported_environment_blend_modes(u return supported_environment_blend_modes; } -bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode mode) { +bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const { + ERR_FAIL_NULL_V(supported_environment_blend_modes, false); + for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) { - if (supported_environment_blend_modes[i] == mode) { - environment_blend_mode = mode; + if (supported_environment_blend_modes[i] == p_blend_mode) { return true; } } + + return false; +} + +bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) { + // We allow setting this when not initialized and will check if it is supported when initializing. + // After OpenXR is initialized we verify we're setting a supported blend mode. + if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) { + environment_blend_mode = p_blend_mode; + return true; + } return false; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 2498cd1eb4..64769b244c 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -54,7 +54,6 @@ // Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set. // forward declarations, we don't want to include these fully -class OpenXRVulkanExtension; class OpenXRInterface; class OpenXRAPI { @@ -140,6 +139,7 @@ private: void *swapchain_graphics_data = nullptr; uint32_t image_index = 0; bool image_acquired = false; + bool skip_acquire_swapchain = false; }; OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; @@ -289,7 +289,7 @@ private: bool on_state_loss_pending(); bool on_state_exiting(); - // convencience + // convenience void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); public: @@ -356,6 +356,7 @@ public: void pre_render(); bool pre_draw_viewport(RID p_render_target); + XrSwapchain get_color_swapchain(); RID get_color_texture(); RID get_depth_texture(); void post_draw_viewport(RID p_render_target); @@ -370,6 +371,15 @@ public: double get_render_target_size_multiplier() const; void set_render_target_size_multiplier(double multiplier); + // Foveation settings + bool is_foveation_supported() const; + + int get_foveation_level() const; + void set_foveation_level(int p_foveation_level); + + bool get_foveation_dynamic() const; + void set_foveation_dynamic(bool p_foveation_dynamic); + // action map String get_default_action_map_resource_name(); @@ -405,7 +415,9 @@ public: void unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider); const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count); - bool set_environment_blend_mode(XrEnvironmentBlendMode mode); + bool is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const; + bool set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode); + XrEnvironmentBlendMode get_environment_blend_mode() const { return environment_blend_mode; } OpenXRAPI(); ~OpenXRAPI(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 4dda51147b..8ce76a5fad 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -34,7 +34,7 @@ #include "core/io/resource_saver.h" #include "servers/rendering/rendering_server_globals.h" -#include "extensions/openxr_hand_tracking_extension.h" +#include "extensions/openxr_eye_gaze_interaction.h" void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -54,16 +54,31 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier", "multiplier"), &OpenXRInterface::set_render_target_size_multiplier); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier"), "set_render_target_size_multiplier", "get_render_target_size_multiplier"); + // Foveation level + ClassDB::bind_method(D_METHOD("is_foveation_supported"), &OpenXRInterface::is_foveation_supported); + + ClassDB::bind_method(D_METHOD("get_foveation_level"), &OpenXRInterface::get_foveation_level); + ClassDB::bind_method(D_METHOD("set_foveation_level", "foveation_level"), &OpenXRInterface::set_foveation_level); + ADD_PROPERTY(PropertyInfo(Variant::INT, "foveation_level"), "set_foveation_level", "get_foveation_level"); + + ClassDB::bind_method(D_METHOD("get_foveation_dynamic"), &OpenXRInterface::get_foveation_dynamic); + ClassDB::bind_method(D_METHOD("set_foveation_dynamic", "foveation_dynamic"), &OpenXRInterface::set_foveation_dynamic); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "foveation_dynamic"), "set_foveation_dynamic", "get_foveation_dynamic"); + + // Action sets ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active); ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active); ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets); + // Refresh rates ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); // Hand tracking. ClassDB::bind_method(D_METHOD("set_motion_range", "hand", "motion_range"), &OpenXRInterface::set_motion_range); ClassDB::bind_method(D_METHOD("get_motion_range", "hand"), &OpenXRInterface::get_motion_range); + ClassDB::bind_method(D_METHOD("get_hand_joint_flags", "hand", "joint"), &OpenXRInterface::get_hand_joint_flags); + ClassDB::bind_method(D_METHOD("get_hand_joint_rotation", "hand", "joint"), &OpenXRInterface::get_hand_joint_rotation); ClassDB::bind_method(D_METHOD("get_hand_joint_position", "hand", "joint"), &OpenXRInterface::get_hand_joint_position); ClassDB::bind_method(D_METHOD("get_hand_joint_radius", "hand", "joint"), &OpenXRInterface::get_hand_joint_radius); @@ -71,6 +86,9 @@ void OpenXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_hand_joint_linear_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_linear_velocity); ClassDB::bind_method(D_METHOD("get_hand_joint_angular_velocity", "hand", "joint"), &OpenXRInterface::get_hand_joint_angular_velocity); + ClassDB::bind_method(D_METHOD("is_hand_tracking_supported"), &OpenXRInterface::is_hand_tracking_supported); + ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported); + BIND_ENUM_CONSTANT(HAND_LEFT); BIND_ENUM_CONSTANT(HAND_RIGHT); BIND_ENUM_CONSTANT(HAND_MAX); @@ -106,6 +124,14 @@ void OpenXRInterface::_bind_methods() { BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL); BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP); BIND_ENUM_CONSTANT(HAND_JOINT_MAX); + + BIND_BITFIELD_FLAG(HAND_JOINT_NONE); + BIND_BITFIELD_FLAG(HAND_JOINT_ORIENTATION_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_ORIENTATION_TRACKED); + BIND_BITFIELD_FLAG(HAND_JOINT_POSITION_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_POSITION_TRACKED); + BIND_BITFIELD_FLAG(HAND_JOINT_LINEAR_VELOCITY_VALID); + BIND_BITFIELD_FLAG(HAND_JOINT_ANGULAR_VELOCITY_VALID); } StringName OpenXRInterface::get_name() const { @@ -139,7 +165,9 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { "/user/vive_tracker_htcx/role/waist", "/user/vive_tracker_htcx/role/chest", "/user/vive_tracker_htcx/role/camera", - "/user/vive_tracker_htcx/role/keyboard" + "/user/vive_tracker_htcx/role/keyboard", + + "/user/eyes_ext", }; return arr; @@ -692,6 +720,36 @@ Array OpenXRInterface::get_available_display_refresh_rates() const { } } +bool OpenXRInterface::is_hand_tracking_supported() { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); + if (hand_tracking_ext == nullptr) { + return false; + } else { + return hand_tracking_ext->get_active(); + } + } +} + +bool OpenXRInterface::is_eye_gaze_interaction_supported() { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + OpenXREyeGazeInteractionExtension *eye_gaze_ext = OpenXREyeGazeInteractionExtension::get_singleton(); + if (eye_gaze_ext == nullptr) { + return false; + } else { + return eye_gaze_ext->supports_eye_gaze_interaction(); + } + } +} + bool OpenXRInterface::is_action_set_active(const String &p_action_set) const { for (ActionSet *action_set : action_sets) { if (action_set->action_set_name == p_action_set) { @@ -740,6 +798,46 @@ void OpenXRInterface::set_render_target_size_multiplier(double multiplier) { } } +bool OpenXRInterface::is_foveation_supported() const { + if (openxr_api == nullptr) { + return false; + } else { + return openxr_api->is_foveation_supported(); + } +} + +int OpenXRInterface::get_foveation_level() const { + if (openxr_api == nullptr) { + return 0; + } else { + return openxr_api->get_foveation_level(); + } +} + +void OpenXRInterface::set_foveation_level(int p_foveation_level) { + if (openxr_api == nullptr) { + return; + } else { + openxr_api->set_foveation_level(p_foveation_level); + } +} + +bool OpenXRInterface::get_foveation_dynamic() const { + if (openxr_api == nullptr) { + return false; + } else { + return openxr_api->get_foveation_dynamic(); + } +} + +void OpenXRInterface::set_foveation_dynamic(bool p_foveation_dynamic) { + if (openxr_api == nullptr) { + return; + } else { + openxr_api->set_foveation_dynamic(p_foveation_dynamic); + } +} + Size2 OpenXRInterface::get_render_target_size() { if (openxr_api == nullptr) { return Size2(); @@ -835,6 +933,48 @@ RID OpenXRInterface::get_depth_texture() { } } +void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand) { + OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); + if (hand_tracking_ext && hand_tracking_ext->get_active()) { + OpenXRInterface::Tracker *tracker = find_tracker(p_path); + if (tracker && tracker->positional_tracker.is_valid()) { + XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT); + + if (location_flags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT)) { + static const XrSpaceLocationFlags all_location_flags = XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT + XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT + XR_SPACE_LOCATION_POSITION_TRACKED_BIT; + XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + Transform3D transform; + Vector3 linear_velocity; + Vector3 angular_velocity; + + if ((location_flags & all_location_flags) == all_location_flags) { + // All flags set? confidence is high! + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } + + if (location_flags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + transform.basis = Basis(hand_tracking_ext->get_hand_joint_rotation(p_hand, XR_HAND_JOINT_PALM_EXT)); + } + if (location_flags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + transform.origin = hand_tracking_ext->get_hand_joint_position(p_hand, XR_HAND_JOINT_PALM_EXT); + } + + XrSpaceVelocityFlags velocity_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT); + if (velocity_flags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + linear_velocity = hand_tracking_ext->get_hand_joint_linear_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); + } + if (velocity_flags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); + } + + tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence); + } else { + tracker->positional_tracker->invalidate_pose("skeleton"); + } + } + } +} + void OpenXRInterface::process() { if (openxr_api) { // do our normal process @@ -842,8 +982,8 @@ void OpenXRInterface::process() { Transform3D t; Vector3 linear_velocity; Vector3 angular_velocity; - XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); - if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + head_confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); + if (head_confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { // Only update our transform if we have one to update it with // note that poses are stored without world scale and reference frame applied! head_transform = t; @@ -865,14 +1005,14 @@ void OpenXRInterface::process() { handle_tracker(trackers[i]); } } + + // Handle hand tracking + handle_hand_tracking("/user/hand/left", OpenXRHandTrackingExtension::OPENXR_TRACKED_LEFT_HAND); + handle_hand_tracking("/user/hand/right", OpenXRHandTrackingExtension::OPENXR_TRACKED_RIGHT_HAND); } if (head.is_valid()) { - // TODO figure out how to get our velocities - - head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity); - - // TODO set confidence on pose once we support tracking this.. + head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity, head_confidence); } } @@ -985,6 +1125,27 @@ Array OpenXRInterface::get_supported_environment_blend_modes() { return modes; } +XRInterface::EnvironmentBlendMode OpenXRInterface::get_environment_blend_mode() const { + if (openxr_api) { + XrEnvironmentBlendMode oxr_blend_mode = openxr_api->get_environment_blend_mode(); + switch (oxr_blend_mode) { + case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: { + return XR_ENV_BLEND_MODE_OPAQUE; + } break; + case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE: { + return XR_ENV_BLEND_MODE_ADDITIVE; + } break; + case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND: { + return XR_ENV_BLEND_MODE_ALPHA_BLEND; + } break; + default: + break; + } + } + + return XR_ENV_BLEND_MODE_OPAQUE; +} + bool OpenXRInterface::set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) { if (openxr_api) { XrEnvironmentBlendMode oxr_blend_mode; @@ -1048,7 +1209,7 @@ void OpenXRInterface::set_motion_range(const Hand p_hand, const HandMotionRange break; } - hand_tracking_ext->set_motion_range(uint32_t(p_hand), xr_motion_range); + hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), xr_motion_range); } } @@ -1057,7 +1218,7 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_ OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(uint32_t(p_hand)); + XrHandJointsMotionRangeEXT xr_motion_range = hand_tracking_ext->get_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(p_hand)); switch (xr_motion_range) { case XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT: @@ -1072,10 +1233,41 @@ OpenXRInterface::HandMotionRange OpenXRInterface::get_motion_range(const Hand p_ return HAND_MOTION_RANGE_MAX; } +BitField<OpenXRInterface::HandJointFlags> OpenXRInterface::get_hand_joint_flags(Hand p_hand, HandJoints p_joint) const { + BitField<OpenXRInterface::HandJointFlags> bits; + + OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); + if (hand_tracking_ext && hand_tracking_ext->get_active()) { + XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); + if (location_flags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + bits.set_flag(HAND_JOINT_ORIENTATION_VALID); + } + if (location_flags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) { + bits.set_flag(HAND_JOINT_ORIENTATION_TRACKED); + } + if (location_flags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + bits.set_flag(HAND_JOINT_POSITION_VALID); + } + if (location_flags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) { + bits.set_flag(HAND_JOINT_POSITION_TRACKED); + } + + XrSpaceVelocityFlags velocity_flags = hand_tracking_ext->get_hand_joint_velocity_flags(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); + if (velocity_flags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + bits.set_flag(HAND_JOINT_LINEAR_VELOCITY_VALID); + } + if (velocity_flags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + bits.set_flag(HAND_JOINT_ANGULAR_VELOCITY_VALID); + } + } + + return bits; +} + Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_rotation(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_rotation(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Quaternion(); @@ -1084,7 +1276,7 @@ Quaternion OpenXRInterface::get_hand_joint_rotation(Hand p_hand, HandJoints p_jo Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_position(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_position(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); @@ -1093,7 +1285,7 @@ Vector3 OpenXRInterface::get_hand_joint_position(Hand p_hand, HandJoints p_joint float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_radius(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_radius(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return 0.0; @@ -1102,7 +1294,7 @@ float OpenXRInterface::get_hand_joint_radius(Hand p_hand, HandJoints p_joint) co Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_linear_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_linear_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); @@ -1111,7 +1303,7 @@ Vector3 OpenXRInterface::get_hand_joint_linear_velocity(Hand p_hand, HandJoints Vector3 OpenXRInterface::get_hand_joint_angular_velocity(Hand p_hand, HandJoints p_joint) const { OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { - return hand_tracking_ext->get_hand_joint_angular_velocity(uint32_t(p_hand), XrHandJointEXT(p_joint)); + return hand_tracking_ext->get_hand_joint_angular_velocity(OpenXRHandTrackingExtension::HandTrackedHands(p_hand), XrHandJointEXT(p_joint)); } return Vector3(); diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 09e1c31728..51ef4ea228 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -33,6 +33,7 @@ #include "action_map/openxr_action_map.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" +#include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" #include "servers/xr/xr_interface.h" @@ -55,6 +56,7 @@ private: Transform3D head_transform; Vector3 head_linear_velocity; Vector3 head_angular_velocity; + XRPose::TrackingConfidence head_confidence; Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet void _load_action_map(); @@ -97,6 +99,8 @@ private: void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); + void handle_hand_tracking(const String &p_path, OpenXRHandTrackingExtension::HandTrackedHands p_hand); + protected: static void _bind_methods(); @@ -107,6 +111,9 @@ public: virtual PackedStringArray get_suggested_tracker_names() const override; virtual TrackingStatus get_tracking_status() const override; + bool is_hand_tracking_supported(); + bool is_eye_gaze_interaction_supported(); + bool initialize_on_startup() const; virtual bool is_initialized() const override; virtual bool initialize() override; @@ -130,6 +137,14 @@ public: double get_render_target_size_multiplier() const; void set_render_target_size_multiplier(double multiplier); + bool is_foveation_supported() const; + + int get_foveation_level() const; + void set_foveation_level(int p_foveation_level); + + bool get_foveation_dynamic() const; + void set_foveation_dynamic(bool p_foveation_dynamic); + virtual Size2 get_render_target_size() override; virtual uint32_t get_view_count() override; virtual Transform3D get_camera_transform() override; @@ -152,6 +167,7 @@ public: /** environment blend mode. */ virtual Array get_supported_environment_blend_modes() override; + virtual XRInterface::EnvironmentBlendMode get_environment_blend_mode() const override; virtual bool set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) override; void on_state_ready(); @@ -207,6 +223,17 @@ public: HAND_JOINT_MAX = 26, }; + enum HandJointFlags { + HAND_JOINT_NONE = 0, + HAND_JOINT_ORIENTATION_VALID = 1, + HAND_JOINT_ORIENTATION_TRACKED = 2, + HAND_JOINT_POSITION_VALID = 4, + HAND_JOINT_POSITION_TRACKED = 8, + HAND_JOINT_LINEAR_VELOCITY_VALID = 16, + HAND_JOINT_ANGULAR_VELOCITY_VALID = 32, + }; + + BitField<HandJointFlags> get_hand_joint_flags(Hand p_hand, HandJoints p_joint) const; Quaternion get_hand_joint_rotation(Hand p_hand, HandJoints p_joint) const; Vector3 get_hand_joint_position(Hand p_hand, HandJoints p_joint) const; float get_hand_joint_radius(Hand p_hand, HandJoints p_joint) const; @@ -221,5 +248,6 @@ public: VARIANT_ENUM_CAST(OpenXRInterface::Hand) VARIANT_ENUM_CAST(OpenXRInterface::HandMotionRange) VARIANT_ENUM_CAST(OpenXRInterface::HandJoints) +VARIANT_BITFIELD_CAST(OpenXRInterface::HandJointFlags) #endif // OPENXR_INTERFACE_H diff --git a/modules/denoise/lightmap_denoiser.cpp b/modules/openxr/openxr_platform_inc.h index 72764036e1..6288d1e380 100644 --- a/modules/denoise/lightmap_denoiser.cpp +++ b/modules/openxr/openxr_platform_inc.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* lightmap_denoiser.cpp */ +/* openxr_platform_inc.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,38 +28,51 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "lightmap_denoiser.h" +#ifndef OPENXR_PLATFORM_INC_H +#define OPENXR_PLATFORM_INC_H -#include "denoise_wrapper.h" +// In various places we need to include platform definitions but we can't +// include these in our normal header files as we'll end up with issues. -#include "core/io/image.h" +#ifdef VULKAN_ENABLED +#define XR_USE_GRAPHICS_API_VULKAN +#include "drivers/vulkan/vulkan_context.h" +#endif // VULKAN_ENABLED -LightmapDenoiser *LightmapDenoiserOIDN::create_oidn_denoiser() { - return memnew(LightmapDenoiserOIDN); -} +#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) +#ifdef ANDROID_ENABLED +#define XR_USE_GRAPHICS_API_OPENGL_ES +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl3ext.h> +#else +#define XR_USE_GRAPHICS_API_OPENGL +#endif // ANDROID_ENABLED +#ifdef X11_ENABLED +#define GL_GLEXT_PROTOTYPES 1 +#define GL3_PROTOTYPES 1 +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" +#endif // X11_ENABLED +#endif // defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -void LightmapDenoiserOIDN::make_default_denoiser() { - create_function = create_oidn_denoiser; -} +#ifdef X11_ENABLED +#include <X11/Xlib.h> +#endif // X11_ENABLED -Ref<Image> LightmapDenoiserOIDN::denoise_image(const Ref<Image> &p_image) { - Ref<Image> img = p_image->duplicate(); +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif // WINDOWS_ENABLED - img->convert(Image::FORMAT_RGBF); +#ifdef ANDROID_ENABLED +// The jobject type from jni.h is used by openxr_platform.h on Android. +#include <jni.h> +#endif // ANDROID_ENABLED - Vector<uint8_t> data = img->get_data(); - if (!oidn_denoise(device, (float *)data.ptrw(), img->get_width(), img->get_height())) { - return p_image; - } +// Include platform dependent structs. +#include <openxr/openxr_platform.h> - img->set_data(img->get_width(), img->get_height(), false, img->get_format(), data); - return img; -} - -LightmapDenoiserOIDN::LightmapDenoiserOIDN() { - device = oidn_denoiser_init(); -} - -LightmapDenoiserOIDN::~LightmapDenoiserOIDN() { - oidn_denoiser_finish(device); -} +#endif // OPENXR_PLATFORM_INC_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index d69c803502..544932bdeb 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -42,6 +42,7 @@ #include "scene/openxr_hand.h" #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_passthrough_extension_wrapper.h" #include "extensions/openxr_hand_tracking_extension.h" @@ -110,11 +111,18 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCViveTrackerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHuaweiControllerExtension)); - OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension)); + + // register gated extensions + if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension)); + } + if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); + } } if (OpenXRAPI::openxr_is_enabled()) { diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp index bedc8874d6..c48fac8055 100644 --- a/modules/openxr/scene/openxr_hand.cpp +++ b/modules/openxr/scene/openxr_hand.cpp @@ -113,7 +113,7 @@ void OpenXRHand::_set_motion_range() { break; } - hand_tracking_ext->set_motion_range(hand, xr_motion_range); + hand_tracking_ext->set_motion_range(OpenXRHandTrackingExtension::HandTrackedHands(hand), xr_motion_range); } Skeleton3D *OpenXRHand::get_skeleton() { @@ -204,7 +204,7 @@ void OpenXRHand::_update_skeleton() { Quaternion inv_quaternions[XR_HAND_JOINT_COUNT_EXT]; Vector3 positions[XR_HAND_JOINT_COUNT_EXT]; - const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand); + const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(OpenXRHandTrackingExtension::HandTrackedHands(hand)); const float ws = XRServer::get_singleton()->get_world_scale(); if (hand_tracker->is_initialized && hand_tracker->locations.isActive) { @@ -243,26 +243,27 @@ void OpenXRHand::_update_skeleton() { // Get our target quaternion Quaternion q = quaternions[i]; + // Get our target position + Vector3 p = positions[i]; + // get local translation, parent should already be processed if (parent == -1) { // use our palm location here, that is what we are tracking q = inv_quaternions[XR_HAND_JOINT_PALM_EXT] * q; + p = inv_quaternions[XR_HAND_JOINT_PALM_EXT].xform(p - positions[XR_HAND_JOINT_PALM_EXT]); } else { int found = false; for (int b = 0; b < XR_HAND_JOINT_COUNT_EXT && !found; b++) { if (bones[b] == parent) { q = inv_quaternions[b] * q; + p = inv_quaternions[b].xform(p - positions[b]); found = true; } } } - // And get the movement from our rest position - // Transform3D rest = skeleton->get_bone_rest(bones[i]); - // q = rest.basis.get_quaternion().inverse() * q; - // and set our pose - // skeleton->set_bone_pose_position(bones[i], v); + skeleton->set_bone_pose_position(bones[i], p); skeleton->set_bone_pose_rotation(bones[i], q); } } diff --git a/modules/openxr/util.h b/modules/openxr/util.h index d95bc3bb8e..7488b5cf8e 100644 --- a/modules/openxr/util.h +++ b/modules/openxr/util.h @@ -61,13 +61,13 @@ #define GDEXTENSION_INIT_XR_FUNC(name) \ do { \ name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \ - ERR_FAIL_COND(name##_ptr == nullptr); \ + ERR_FAIL_NULL(name##_ptr); \ } while (0) #define GDEXTENSION_INIT_XR_FUNC_V(name) \ do { \ name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \ - ERR_FAIL_COND_V(name##_ptr == nullptr, false); \ + ERR_FAIL_NULL_V(name##_ptr, false); \ } while (0) #define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \ diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp index 69fbf87483..5005000eae 100644 --- a/modules/raycast/raycast_occlusion_cull.cpp +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -221,7 +221,7 @@ void RaycastOcclusionCull::occluder_initialize(RID p_occluder) { void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); occluder->vertices = p_vertices; occluder->indices = p_indices; @@ -242,7 +242,7 @@ void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3 void RaycastOcclusionCull::free_occluder(RID p_occluder) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); memdelete(occluder); occluder_owner.free(p_occluder); } @@ -250,17 +250,15 @@ void RaycastOcclusionCull::free_occluder(RID p_occluder) { //////////////////////////////////////////////////////// void RaycastOcclusionCull::add_scenario(RID p_scenario) { - if (scenarios.has(p_scenario)) { - scenarios[p_scenario].removed = false; - } else { - scenarios[p_scenario] = Scenario(); - } + ERR_FAIL_COND(scenarios.has(p_scenario)); + scenarios[p_scenario] = Scenario(); } void RaycastOcclusionCull::remove_scenario(RID p_scenario) { - ERR_FAIL_COND(!scenarios.has(p_scenario)); - Scenario &scenario = scenarios[p_scenario]; - scenario.removed = true; + Scenario *scenario = scenarios.getptr(p_scenario); + ERR_FAIL_NULL(scenario); + scenario->free(); + scenarios.erase(p_scenario); } void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform3D &p_xform, bool p_enabled) { @@ -291,7 +289,7 @@ void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, if (p_occluder.is_valid()) { Occluder *occluder = occluder_owner.get_or_null(p_occluder); - ERR_FAIL_COND(!occluder); + ERR_FAIL_NULL(occluder); occluder->users.insert(InstanceID(p_scenario, p_instance)); } changed = true; @@ -390,6 +388,23 @@ void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_ } } +void RaycastOcclusionCull::Scenario::free() { + if (commit_thread) { + if (commit_thread->is_started()) { + commit_thread->wait_to_finish(); + } + memdelete(commit_thread); + commit_thread = nullptr; + } + + for (int i = 0; i < 2; i++) { + if (ebr_scene[i]) { + rtcReleaseScene(ebr_scene[i]); + ebr_scene[i] = nullptr; + } + } +} + void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { Scenario *scenario = (Scenario *)p_ud; int commit_idx = 1 - (scenario->current_scene_idx); @@ -397,8 +412,8 @@ void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { scenario->commit_done = true; } -bool RaycastOcclusionCull::Scenario::update() { - ERR_FAIL_COND_V(singleton == nullptr, false); +void RaycastOcclusionCull::Scenario::update() { + ERR_FAIL_NULL(singleton); if (commit_thread == nullptr) { commit_thread = memnew(Thread); @@ -409,22 +424,12 @@ bool RaycastOcclusionCull::Scenario::update() { commit_thread->wait_to_finish(); current_scene_idx = 1 - current_scene_idx; } else { - return false; - } - } - - if (removed) { - if (ebr_scene[0]) { - rtcReleaseScene(ebr_scene[0]); + return; } - if (ebr_scene[1]) { - rtcReleaseScene(ebr_scene[1]); - } - return true; } if (!dirty && removed_instances.is_empty() && dirty_instances_array.is_empty()) { - return false; + return; } for (const RID &scenario : removed_instances) { @@ -480,7 +485,6 @@ bool RaycastOcclusionCull::Scenario::update() { dirty = false; commit_done = false; commit_thread->start(&Scenario::_commit_scene, this); - return false; } void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThreadData *p_raycast_data) const { @@ -492,7 +496,7 @@ void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThrea } void RaycastOcclusionCull::Scenario::raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const { - ERR_FAIL_COND(singleton == nullptr); + ERR_FAIL_NULL(singleton); if (raycast_singleton->ebr_device == nullptr) { return; // Embree is initialized on demand when there is some scenario with occluders in it. } @@ -544,13 +548,7 @@ void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform3D &p_cam_ } Scenario &scenario = scenarios[buffer.scenario_rid]; - - bool removed = scenario.update(); - - if (removed) { - scenarios.erase(buffer.scenario_rid); - return; - } + scenario.update(); buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal); @@ -603,19 +601,7 @@ RaycastOcclusionCull::RaycastOcclusionCull() { RaycastOcclusionCull::~RaycastOcclusionCull() { for (KeyValue<RID, Scenario> &K : scenarios) { - Scenario &scenario = K.value; - if (scenario.commit_thread) { - if (scenario.commit_thread->is_started()) { - scenario.commit_thread->wait_to_finish(); - } - memdelete(scenario.commit_thread); - } - - for (int i = 0; i < 2; i++) { - if (scenario.ebr_scene[i]) { - rtcReleaseScene(scenario.ebr_scene[i]); - } - } + K.value.free(); } if (ebr_device != nullptr) { diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h index c4e733b664..ab5eb4eaf0 100644 --- a/modules/raycast/raycast_occlusion_cull.h +++ b/modules/raycast/raycast_occlusion_cull.h @@ -132,7 +132,6 @@ private: Thread *commit_thread = nullptr; bool commit_done = true; bool dirty = false; - bool removed = false; RTCScene ebr_scene[2] = { nullptr, nullptr }; int current_scene_idx = 0; @@ -147,7 +146,8 @@ private: void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data); void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform3D &p_xform, int p_from, int p_to); static void _commit_scene(void *p_ud); - bool update(); + void free(); + void update(); void _raycast(uint32_t p_thread, const RaycastThreadData *p_raycast_data) const; void raycast(CameraRayTile *r_rays, const uint32_t *p_valid_masks, uint32_t p_tile_count) const; diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml index 5770e7155e..ab74fce3a9 100644 --- a/modules/regex/doc_classes/RegEx.xml +++ b/modules/regex/doc_classes/RegEx.xml @@ -10,7 +10,7 @@ var regex = RegEx.new() regex.compile("\\w-(\\d+)") [/codeblock] - The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. + The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. In GDScript, you can also use raw string literals (r-strings). For example, [code]compile(r'"(?:\\.|[^"])*"')[/code] would be read the same. Using [method search], you can find the pattern within the given text. If a pattern is found, [RegExMatch] is returned and you can retrieve details of the results using methods such as [method RegExMatch.get_string] and [method RegExMatch.get_start]. [codeblock] var regex = RegEx.new() diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h index 6515d5d130..3e4d769377 100644 --- a/modules/regex/tests/test_regex.h +++ b/modules/regex/tests/test_regex.h @@ -83,9 +83,16 @@ TEST_CASE("[RegEx] Searching") { REQUIRE(match != nullptr); CHECK(match->get_string(0) == "ea"); + match = re.search(s, 1, 2); + REQUIRE(match != nullptr); + CHECK(match->get_string(0) == "e"); match = re.search(s, 2, 4); REQUIRE(match != nullptr); CHECK(match->get_string(0) == "a"); + match = re.search(s, 3, 5); + CHECK(match == nullptr); + match = re.search(s, 6, 2); + CHECK(match == nullptr); const Array all_results = re.search_all(s); CHECK(all_results.size() == 2); @@ -103,11 +110,45 @@ TEST_CASE("[RegEx] Searching") { } TEST_CASE("[RegEx] Substitution") { - String s = "Double all the vowels."; + const String s1 = "Double all the vowels."; - RegEx re("(?<vowel>[aeiou])"); - REQUIRE(re.is_valid()); - CHECK(re.sub(s, "$0$vowel", true) == "Doouublee aall thee vooweels."); + RegEx re1("(?<vowel>[aeiou])"); + REQUIRE(re1.is_valid()); + CHECK(re1.sub(s1, "$0$vowel", true) == "Doouublee aall thee vooweels."); + + const String s2 = "Substitution with group."; + + RegEx re2("Substitution (.+)"); + REQUIRE(re2.is_valid()); + CHECK(re2.sub(s2, "Test ${1}") == "Test with group."); + + const String s3 = "Useless substitution"; + + RegEx re3("Anything"); + REQUIRE(re3.is_valid()); + CHECK(re3.sub(s3, "Something") == "Useless substitution"); + + const String s4 = "acacac"; + + RegEx re4("(a)(b){0}(c)"); + REQUIRE(re4.is_valid()); + CHECK(re4.sub(s4, "${1}.${3}.", true) == "a.c.a.c.a.c."); +} + +TEST_CASE("[RegEx] Substitution with empty input and/or replacement") { + const String s1 = ""; + const String s2 = "gogogo"; + + RegEx re1(""); + REQUIRE(re1.is_valid()); + CHECK(re1.sub(s1, "") == ""); + CHECK(re1.sub(s1, "a") == "a"); + CHECK(re1.sub(s2, "") == "gogogo"); + + RegEx re2("go"); + REQUIRE(re2.is_valid()); + CHECK(re2.sub(s2, "") == "gogo"); + CHECK(re2.sub(s2, "", true) == ""); } TEST_CASE("[RegEx] Uninitialized use") { @@ -150,6 +191,37 @@ TEST_CASE("[RegEx] Invalid end position") { CHECK(re.sub(s, "", true, 0, 10) == "Gdt"); } + +TEST_CASE("[RegEx] Get match string list") { + const String s = "Godot Engine"; + + RegEx re("(Go)(dot)"); + Ref<RegExMatch> match = re.search(s); + REQUIRE(match != nullptr); + PackedStringArray result; + result.append("Godot"); + result.append("Go"); + result.append("dot"); + CHECK(match->get_strings() == result); +} + +TEST_CASE("[RegEx] Match start and end positions") { + const String s = "Whole pattern"; + + RegEx re1("pattern"); + REQUIRE(re1.is_valid()); + Ref<RegExMatch> match = re1.search(s); + REQUIRE(match != nullptr); + CHECK(match->get_start(0) == 6); + CHECK(match->get_end(0) == 13); + + RegEx re2("(?<vowel>[aeiou])"); + REQUIRE(re2.is_valid()); + match = re2.search(s); + REQUIRE(match != nullptr); + CHECK(match->get_start("vowel") == 2); + CHECK(match->get_end("vowel") == 3); +} } // namespace TestRegEx #endif // TEST_REGEX_H diff --git a/modules/svg/SCsub b/modules/svg/SCsub index c4d7671fb3..d0c6250b11 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -11,40 +11,48 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/thorvg/" thirdparty_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] @@ -58,15 +66,15 @@ env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() env_thirdparty.Prepend( CPPPATH=[ - thirdparty_dir + "src/lib", - thirdparty_dir + "src/lib/sw_engine", - thirdparty_dir + "src/loaders/raw", + thirdparty_dir + "src/common", thirdparty_dir + "src/loaders/svg", + thirdparty_dir + "src/renderer", + thirdparty_dir + "src/renderer/sw_engine", + thirdparty_dir + "src/loaders/raw", + thirdparty_dir + "src/loaders/external_png", + thirdparty_dir + "src/loaders/jpg", ] ) -# Also requires libpng headers -if env["builtin_libpng"]: - env_thirdparty.Prepend(CPPPATH=["#thirdparty/libpng"]) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) env.modules_sources += thirdparty_obj diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 360741363a..3c468e61d7 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -39,7 +39,13 @@ freetype_enabled = "freetype" in env.module_list msdfgen_enabled = "msdfgen" in env.module_list if "svg" in env.module_list: - env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + env_text_server_adv.Prepend( + CPPPATH=[ + "#thirdparty/thorvg/inc", + "#thirdparty/thorvg/src/common", + "#thirdparty/thorvg/src/renderer", + ] + ) # Enable ThorVG static object linking. env_text_server_adv.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 38fd5f6403..4093842650 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -42,58 +42,74 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] env_tvg.Append( CPPPATH=[ "../../../thirdparty/thorvg/inc", - "../../../thirdparty/thorvg/src/lib", - "../../../thirdparty/thorvg/src/lib/sw_engine", - "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + "../../../thirdparty/thorvg/src/renderer/sw_engine", "../../../thirdparty/thorvg/src/loaders/svg", - "../../../thirdparty/libpng", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", ] ) # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index fd8b2caa0c..6d0a398218 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -338,6 +338,7 @@ _FORCE_INLINE_ bool is_connected_to_prev(char32_t p_chr, char32_t p_pchr) { /*************************************************************************/ bool TextServerAdvanced::icu_data_loaded = false; +PackedByteArray TextServerAdvanced::icu_data; bool TextServerAdvanced::_has_feature(Feature p_feature) const { switch (p_feature) { @@ -397,6 +398,14 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) { font_owner.free(p_rid); } memdelete(fd); + } else if (font_var_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_rid); + { + font_var_owner.free(p_rid); + } + memdelete(fdv); } else if (shaped_owner.owns(p_rid)) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_rid); { @@ -409,7 +418,7 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) { bool TextServerAdvanced::_has(const RID &p_rid) { _THREAD_SAFE_METHOD_ - return font_owner.owns(p_rid) || shaped_owner.owns(p_rid); + return font_owner.owns(p_rid) || font_var_owner.owns(p_rid) || shaped_owner.owns(p_rid); } bool TextServerAdvanced::_load_support_data(const String &p_filename) { @@ -430,7 +439,7 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) { return false; } uint64_t len = f->get_length(); - PackedByteArray icu_data = f->get_buffer(len); + icu_data = f->get_buffer(len); UErrorCode err = U_ZERO_ERROR; udata_setCommonData(icu_data.ptr(), &err); @@ -468,10 +477,10 @@ bool TextServerAdvanced::_save_support_data(const String &p_filename) const { return false; } - PackedByteArray icu_data; - icu_data.resize(U_ICUDATA_SIZE); - memcpy(icu_data.ptrw(), U_ICUDATA_ENTRY_POINT, U_ICUDATA_SIZE); - f->store_buffer(icu_data); + PackedByteArray icu_data_static; + icu_data_static.resize(U_ICUDATA_SIZE); + memcpy(icu_data_static.ptrw(), U_ICUDATA_ENTRY_POINT, U_ICUDATA_SIZE); + f->store_buffer(icu_data_static); return true; #else @@ -1809,8 +1818,8 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d } hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, nullptr); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, nullptr); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1828,9 +1837,25 @@ RID TextServerAdvanced::_create_font() { return font_owner.make_rid(fd); } +RID TextServerAdvanced::_create_font_linked_variation(const RID &p_font_rid) { + _THREAD_SAFE_METHOD_ + + RID rid = p_font_rid; + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + ERR_FAIL_COND_V(!font_owner.owns(rid), RID()); + + FontAdvancedLinkedVariation *new_fdv = memnew(FontAdvancedLinkedVariation); + new_fdv->base_font = rid; + + return font_var_owner.make_rid(new_fdv); +} + void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -1840,8 +1865,8 @@ void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteA } void TextServerAdvanced::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -1854,8 +1879,8 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f ERR_FAIL_COND(p_face_index < 0); ERR_FAIL_COND(p_face_index >= 0x7FFF); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->face_index != p_face_index) { @@ -1865,16 +1890,16 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f } int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->face_index; } int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); int face_count = 0; @@ -1919,8 +1944,8 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1929,8 +1954,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty } BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1939,8 +1964,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p } void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const String &p_name) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1949,8 +1974,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin } String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1959,8 +1984,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1969,8 +1994,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh } int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 400); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 400); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1979,8 +2004,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1989,8 +2014,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre } int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 100); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 100); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1999,8 +2024,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { } void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2009,8 +2034,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n } String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2019,8 +2044,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { } Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2132,8 +2157,8 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) } void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->antialiasing != p_antialiasing) { @@ -2143,16 +2168,16 @@ void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServe } TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE); MutexLock lock(fd->mutex); return fd->antialiasing; } void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->mipmaps != p_generate_mipmaps) { @@ -2167,16 +2192,16 @@ void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool } bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->mipmaps; } void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf != p_msdf) { @@ -2186,16 +2211,16 @@ void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID } bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf; } void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_range != p_msdf_pixel_range) { @@ -2205,16 +2230,16 @@ void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64 } int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_range; } void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_source_size != p_msdf_size) { @@ -2224,48 +2249,64 @@ void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms } int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->msdf_source_size; } void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->fixed_size = p_fixed_size; } int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->fixed_size; } +void TextServerAdvanced::_font_set_fixed_size_scale_mode(const RID &p_font_rid, TextServer::FixedSizeScaleMode p_fixed_size_scale_mode) { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); + + MutexLock lock(fd->mutex); + fd->fixed_size_scale_mode = p_fixed_size_scale_mode; +} + +TextServer::FixedSizeScaleMode TextServerAdvanced::_font_get_fixed_size_scale_mode(const RID &p_font_rid) const { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, FIXED_SIZE_SCALE_DISABLE); + + MutexLock lock(fd->mutex); + return fd->fixed_size_scale_mode; +} + void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->allow_system_fallback = p_allow_system_fallback; } bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->allow_system_fallback; } void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->force_autohinter != p_force_autohinter) { @@ -2275,16 +2316,16 @@ void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool } bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->force_autohinter; } void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->hinting != p_hinting) { @@ -2294,32 +2335,32 @@ void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hi } TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, HINTING_NONE); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, HINTING_NONE); MutexLock lock(fd->mutex); return fd->hinting; } void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->subpixel_positioning = p_subpixel; } TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioning(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED); MutexLock lock(fd->mutex); return fd->subpixel_positioning; } void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->embolden != p_strength) { @@ -2329,8 +2370,8 @@ void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_stre } double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->embolden; @@ -2338,30 +2379,39 @@ double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const { void TextServerAdvanced::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + if (fdv->extra_spacing[p_spacing] != p_value) { + fdv->extra_spacing[p_spacing] = p_value; + } + } else { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL(fd); - MutexLock lock(fd->mutex); - if (fd->extra_spacing[p_spacing] != p_value) { - _font_clear_cache(fd); - fd->extra_spacing[p_spacing] = p_value; + MutexLock lock(fd->mutex); + if (fd->extra_spacing[p_spacing] != p_value) { + fd->extra_spacing[p_spacing] = p_value; + } } } int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const { ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + return fdv->extra_spacing[p_spacing]; + } else { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); - - MutexLock lock(fd->mutex); - - return fd->extra_spacing[p_spacing]; + MutexLock lock(fd->mutex); + return fd->extra_spacing[p_spacing]; + } } void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->transform != p_transform) { @@ -2371,33 +2421,35 @@ void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transf } Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Transform2D()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Transform2D()); MutexLock lock(fd->mutex); return fd->transform; } void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); - _font_clear_cache(fd); - fd->variation_coordinates = p_variation_coordinates; + if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) { + _font_clear_cache(fd); + fd->variation_coordinates = p_variation_coordinates.duplicate(); + } } Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->variation_coordinates; } void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->oversampling != p_oversampling) { @@ -2407,16 +2459,16 @@ void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_ } double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->oversampling; } TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); TypedArray<Vector2i> ret; @@ -2427,8 +2479,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_ } void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -2439,8 +2491,8 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) { } void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -2451,8 +2503,8 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve } void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2462,8 +2514,8 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, } double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2472,14 +2524,20 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz if (fd->msdf) { return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->ascent; } } void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); Vector2i size = _get_size(fd, p_size); @@ -2488,8 +2546,8 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size } double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2498,14 +2556,20 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si if (fd->msdf) { return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->descent; } } void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2515,8 +2579,8 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int } double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2525,14 +2589,20 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i if (fd->msdf) { return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->underline_position; } } void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2542,8 +2612,8 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in } double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2552,14 +2622,20 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, if (fd->msdf) { return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->underline_thickness; } } void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2574,8 +2650,8 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, } double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2584,14 +2660,20 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size if (fd->msdf) { return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->scale / fd->cache[size]->oversampling; } } int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2602,8 +2684,8 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const } void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2612,8 +2694,8 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto } void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2624,8 +2706,8 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto } void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND(p_image.is_null()); MutexLock lock(fd->mutex); @@ -2653,8 +2735,8 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve } Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Ref<Image>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Ref<Image>()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2667,8 +2749,8 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) { ERR_FAIL_COND(p_offsets.size() % 4 != 0); - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2686,8 +2768,8 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const } PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2711,8 +2793,8 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font } PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2727,8 +2809,8 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, } void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2738,8 +2820,8 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2749,8 +2831,8 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2 } double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const { - const FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + const FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_font_size); @@ -2763,8 +2845,8 @@ double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) c } Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2793,6 +2875,12 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + } else { + return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { return (gl[p_glyph | mod].advance + ea).round(); } else { @@ -2801,8 +2889,8 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 } void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2816,8 +2904,8 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t } Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2840,14 +2928,20 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const if (fd->msdf) { return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + } else { + return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + } } else { return gl[p_glyph | mod].rect.position; } } void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2861,8 +2955,8 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec } Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2885,14 +2979,20 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve if (fd->msdf) { return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + } else { + return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + } } else { return gl[p_glyph | mod].rect.size; } } void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2906,8 +3006,8 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto } Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Rect2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Rect2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2931,8 +3031,8 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V } void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2946,8 +3046,8 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve } int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, -1); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, -1); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2971,8 +3071,8 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c } void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2986,8 +3086,8 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons } RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, RID()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, RID()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3032,8 +3132,8 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const } Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Size2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Size2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3078,8 +3178,8 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co } Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3108,6 +3208,12 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + scale = scale * (double)p_size / (double)fd->fixed_size; + } else { + scale = scale * Math::round((double)p_size / (double)fd->fixed_size); + } } for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); @@ -3128,8 +3234,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i } TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3144,8 +3250,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon } void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3155,8 +3261,8 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t } void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3166,8 +3272,8 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s } void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3177,8 +3283,8 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size } Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3190,6 +3296,12 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s if (kern.has(p_glyph_pair)) { if (fd->msdf) { return kern[p_glyph_pair] * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return kern[p_glyph_pair] * (double)p_size / (double)fd->fixed_size; + } else { + return kern[p_glyph_pair] * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return kern[p_glyph_pair]; } @@ -3200,6 +3312,12 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->fixed_size; + } else { + return Vector2(delta.x, delta.y) * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return Vector2(delta.x, delta.y); } @@ -3210,8 +3328,8 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s } int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); ERR_FAIL_COND_V_MSG((p_variation_selector >= 0xd800 && p_variation_selector <= 0xdfff) || (p_variation_selector > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_variation_selector, 16) + "."); @@ -3235,8 +3353,8 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t } int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3266,7 +3384,7 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri } bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + FontAdvanced *fd = _get_font_data(p_font_rid); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); if (!fd) { return false; @@ -3287,8 +3405,8 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c } String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); if (fd->cache.is_empty()) { @@ -3320,8 +3438,8 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons } void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + "."); ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + "."); @@ -3355,8 +3473,8 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -3386,8 +3504,8 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2 } void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -3464,8 +3582,20 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - cpos += gl.rect.position; + Vector2 gpos = gl.rect.position; Size2 csize = gl.rect.size; + if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + double gl_scale = (double)p_size / (double)fd->fixed_size; + gpos *= gl_scale; + csize *= gl_scale; + } else { + double gl_scale = Math::round((double)p_size / (double)fd->fixed_size); + gpos *= gl_scale; + csize *= gl_scale; + } + } + cpos += gpos; if (lcd_aa) { RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); } else { @@ -3478,8 +3608,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); @@ -3556,8 +3686,20 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - cpos += gl.rect.position; + Vector2 gpos = gl.rect.position; Size2 csize = gl.rect.size; + if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + double gl_scale = (double)p_size / (double)fd->fixed_size; + gpos *= gl_scale; + csize *= gl_scale; + } else { + double gl_scale = Math::round((double)p_size / (double)fd->fixed_size); + gpos *= gl_scale; + csize *= gl_scale; + } + } + cpos += gpos; if (lcd_aa) { RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); } else { @@ -3570,8 +3712,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->language_support_overrides.has(p_language)) { @@ -3582,32 +3724,32 @@ bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, cons } void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides[p_language] = p_supported; } bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->language_support_overrides[p_language]; } void TextServerAdvanced::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides.erase(p_language); } PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -3618,8 +3760,8 @@ PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const } bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { @@ -3632,32 +3774,32 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const } void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides.erase(p_script); } PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const RID &p_font_rid) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -3668,8 +3810,8 @@ PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const R } void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3678,16 +3820,16 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_ } Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->feature_overrides; } Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3696,8 +3838,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri } Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_rid) const { - FontAdvanced *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -3841,7 +3983,7 @@ RID TextServerAdvanced::_create_shaped_text(TextServer::Direction p_direction, T void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); sd->parent = RID(); @@ -3857,7 +3999,7 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction."); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->direction != p_direction) { @@ -3871,7 +4013,7 @@ void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextSer TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR); + ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR); MutexLock lock(sd->mutex); return sd->direction; @@ -3879,7 +4021,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_direction(const RID & TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR); + ERR_FAIL_NULL_V(sd, TextServer::DIRECTION_LTR); MutexLock lock(sd->mutex); return sd->para_direction; @@ -3888,7 +4030,7 @@ TextServer::Direction TextServerAdvanced::_shaped_text_get_inferred_direction(co void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->custom_punct != p_punct) { if (sd->parent != RID()) { @@ -3902,13 +4044,13 @@ void TextServerAdvanced::_shaped_text_set_custom_punctuation(const RID &p_shaped String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shaped) const { _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, String()); + ERR_FAIL_NULL_V(sd, String()); return sd->custom_punct; } void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -3929,7 +4071,7 @@ void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, con void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { @@ -3943,7 +4085,7 @@ void TextServerAdvanced::_shaped_text_set_orientation(const RID &p_shaped, TextS void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); ERR_FAIL_COND(sd->parent != RID()); @@ -3955,7 +4097,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_invalid; @@ -3963,7 +4105,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_invalid(const RID &p_shaped) void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { @@ -3977,7 +4119,7 @@ void TextServerAdvanced::_shaped_text_set_preserve_control(const RID &p_shaped, bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; @@ -3986,7 +4128,7 @@ bool TextServerAdvanced::_shaped_text_get_preserve_control(const RID &p_shaped) void TextServerAdvanced::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->extra_spacing[p_spacing] != p_value) { @@ -4002,7 +4144,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); return sd->extra_spacing[p_spacing]; @@ -4010,7 +4152,7 @@ int64_t TextServerAdvanced::_shaped_text_get_spacing(const RID &p_shaped, Spacin TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); return sd->orientation; @@ -4018,20 +4160,20 @@ TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const R int64_t TextServerAdvanced::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); return sd->spans.size(); } Variant TextServerAdvanced::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_NULL_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(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataAdvanced::Span &span = sd->spans.ptrw()[p_index]; @@ -4044,12 +4186,12 @@ void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64 bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t 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_NULL_V(sd, false); ERR_FAIL_COND_V(p_size <= 0, false); MutexLock lock(sd->mutex); for (int i = 0; i < p_fonts.size(); i++) { - ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false); + ERR_FAIL_NULL_V(_get_font_data(p_fonts[i]), false); } if (p_text.is_empty()) { @@ -4080,7 +4222,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); ERR_FAIL_COND_V(p_key == Variant(), false); ERR_FAIL_COND_V(sd->objects.has(p_key), false); @@ -4110,7 +4252,7 @@ bool TextServerAdvanced::_shaped_text_add_object(const RID &p_shaped, const Vari bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); @@ -4170,6 +4312,8 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V sd->width += gl.advance * gl.repeat; } } + sd->sort_valid = false; + sd->glyphs_logical.clear(); _realign(sd); } return true; @@ -4253,7 +4397,7 @@ void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const { RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const { _THREAD_SAFE_METHOD_ const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -4437,7 +4581,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); return sd->parent; @@ -4445,7 +4589,7 @@ RID TextServerAdvanced::_shaped_text_get_parent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<TextServer::JustificationFlag> p_jst_flags) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4602,7 +4746,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4658,7 +4802,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line); - ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4825,7 +4969,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; @@ -4833,7 +4977,7 @@ int64_t TextServerAdvanced::_shaped_text_get_trim_pos(const RID &p_shaped) const int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; @@ -4841,7 +4985,7 @@ int64_t TextServerAdvanced::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); @@ -4849,7 +4993,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_ellipsis_glyphs(const RID &p_s int64_t TextServerAdvanced::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataAdvanced invalid."); + ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); @@ -4912,7 +5056,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, PackedInt32Array()); + ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4926,7 +5070,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -5191,7 +5335,7 @@ _FORCE_INLINE_ int64_t _generate_kashida_justification_opportunies(const String bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -5345,7 +5489,7 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size); double scale = _font_get_scale(p_font, p_font_size); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); - ERR_FAIL_COND_V(hb_font == nullptr, Glyph()); + ERR_FAIL_NULL_V(hb_font, Glyph()); hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_set_direction(p_sd->hb_buffer, p_direction); @@ -5631,8 +5775,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star return; } - FontAdvanced *fd = font_owner.get_or_null(f); - ERR_FAIL_COND(!fd); + FontAdvanced *fd = _get_font_data(f); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i fss = _get_size(fd, fs); @@ -5643,7 +5787,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star bool last_run = (p_sd->end == p_end); double ea = _get_extra_advance(f, fs); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(f) == SUBPIXEL_POSITIONING_AUTO && fs <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); - ERR_FAIL_COND(hb_font == nullptr); + ERR_FAIL_NULL(hb_font); hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_set_direction(p_sd->hb_buffer, p_direction); @@ -5834,7 +5978,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (sd->valid) { @@ -6050,7 +6194,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->valid; @@ -6058,7 +6202,7 @@ bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6069,7 +6213,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6080,7 +6224,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6098,7 +6242,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Vector2i()); + ERR_FAIL_NULL_V(sd, Vector2i()); MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); @@ -6107,7 +6251,7 @@ Vector2i TextServerAdvanced::_shaped_text_get_range(const RID &p_shaped) const { Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const { Array ret; const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, ret); + ERR_FAIL_NULL_V(sd, ret); MutexLock lock(sd->mutex); for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) { @@ -6119,7 +6263,7 @@ Array TextServerAdvanced::_shaped_text_get_objects(const RID &p_shaped) const { Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Rect2()); + ERR_FAIL_NULL_V(sd, Rect2()); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); @@ -6131,7 +6275,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Size2()); + ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6146,7 +6290,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6157,7 +6301,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6168,7 +6312,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6179,7 +6323,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -6191,7 +6335,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 7445becfae..cbd2911aaf 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -158,6 +158,7 @@ class TextServerAdvanced : public TextServerExtension { // ICU support data. static bool icu_data_loaded; + static PackedByteArray icu_data; mutable USet *allowed = nullptr; mutable USpoofChecker *sc_spoof = nullptr; mutable USpoofChecker *sc_conf = nullptr; @@ -293,6 +294,11 @@ class TextServerAdvanced : public TextServerExtension { } }; + struct FontAdvancedLinkedVariation { + RID base_font; + int extra_spacing[4] = { 0, 0, 0, 0 }; + }; + struct FontAdvanced { Mutex mutex; @@ -300,6 +306,7 @@ class TextServerAdvanced : public TextServerExtension { bool mipmaps = false; bool msdf = false; int msdf_range = 14; + FixedSizeScaleMode fixed_size_scale_mode = FIXED_SIZE_SCALE_DISABLE; int msdf_source_size = 48; int fixed_size = 0; bool allow_system_fallback = true; @@ -534,9 +541,19 @@ class TextServerAdvanced : public TextServerExtension { // Common data. double oversampling = 1.0; + mutable RID_PtrOwner<FontAdvancedLinkedVariation> font_var_owner; mutable RID_PtrOwner<FontAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + _FORCE_INLINE_ FontAdvanced *_get_font_data(const RID &p_font_rid) const { + RID rid = p_font_rid; + FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + return font_owner.get_or_null(rid); + } + struct SystemFontKey { String font_name; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; @@ -664,11 +681,7 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ bool operator()(const Glyph &l, const Glyph &r) const { if (l.start == r.start) { if (l.count == r.count) { - if ((l.flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL) { - return false; - } else { - return true; - } + return (l.flags & TextServer::GRAPHEME_IS_VIRTUAL) < (r.flags & TextServer::GRAPHEME_IS_VIRTUAL); } return l.count > r.count; // Sort first glyph with count & flags, order of the rest are irrelevant. } else { @@ -704,6 +717,7 @@ public: /* Font interface */ MODBIND0R(RID, create_font); + MODBIND1R(RID, create_font_linked_variation, const RID &); MODBIND2(font_set_data, const RID &, const PackedByteArray &); MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t); @@ -747,6 +761,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_fixed_size_scale_mode, const RID &, FixedSizeScaleMode); + MODBIND1RC(FixedSizeScaleMode, font_get_fixed_size_scale_mode, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 0da2a54bc2..e808864512 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -9,7 +9,9 @@ msdfgen_enabled = "msdfgen" in env.module_list env_text_server_fb = env_modules.Clone() if "svg" in env.module_list: - env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + env_text_server_fb.Prepend( + CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/common", "#thirdparty/thorvg/src/renderer"] + ) # Enable ThorVG static object linking. env_text_server_fb.Append(CPPDEFINES=["TVG_STATIC"]) diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 20e1afa2e5..0d2fbd97fd 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -37,58 +37,74 @@ if env["thorvg_enabled"] and env["freetype_enabled"]: thirdparty_tvg_dir = "../../../thirdparty/thorvg/" thirdparty_tvg_sources = [ - "src/lib/sw_engine/tvgSwFill.cpp", - "src/lib/sw_engine/tvgSwImage.cpp", - "src/lib/sw_engine/tvgSwMath.cpp", - "src/lib/sw_engine/tvgSwMemPool.cpp", - "src/lib/sw_engine/tvgSwRaster.cpp", - "src/lib/sw_engine/tvgSwRenderer.cpp", - "src/lib/sw_engine/tvgSwRle.cpp", - "src/lib/sw_engine/tvgSwShape.cpp", - "src/lib/sw_engine/tvgSwStroke.cpp", - "src/lib/tvgAccessor.cpp", - "src/lib/tvgBezier.cpp", - "src/lib/tvgCanvas.cpp", - "src/lib/tvgFill.cpp", - "src/lib/tvgGlCanvas.cpp", - "src/lib/tvgInitializer.cpp", - "src/lib/tvgLinearGradient.cpp", - "src/lib/tvgLoader.cpp", - "src/lib/tvgLzw.cpp", - "src/lib/tvgPaint.cpp", - "src/lib/tvgPicture.cpp", - "src/lib/tvgRadialGradient.cpp", - "src/lib/tvgRender.cpp", - "src/lib/tvgSaver.cpp", - "src/lib/tvgScene.cpp", - "src/lib/tvgShape.cpp", - "src/lib/tvgSwCanvas.cpp", - "src/lib/tvgTaskScheduler.cpp", - "src/loaders/raw/tvgRawLoader.cpp", + # common + "src/common/tvgBezier.cpp", + "src/common/tvgCompressor.cpp", + "src/common/tvgMath.cpp", + "src/common/tvgStr.cpp", + # SVG parser "src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgPath.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + # renderer common + "src/renderer/tvgAccessor.cpp", + # "src/renderer/tvgAnimation.cpp", + "src/renderer/tvgCanvas.cpp", + "src/renderer/tvgFill.cpp", + # "src/renderer/tvgGlCanvas.cpp", + "src/renderer/tvgInitializer.cpp", + "src/renderer/tvgLoader.cpp", + "src/renderer/tvgPaint.cpp", + "src/renderer/tvgPicture.cpp", + "src/renderer/tvgRender.cpp", + # "src/renderer/tvgSaver.cpp", + "src/renderer/tvgScene.cpp", + "src/renderer/tvgShape.cpp", + "src/renderer/tvgSwCanvas.cpp", + "src/renderer/tvgTaskScheduler.cpp", + # renderer sw_engine + "src/renderer/sw_engine/tvgSwFill.cpp", + "src/renderer/sw_engine/tvgSwImage.cpp", + "src/renderer/sw_engine/tvgSwMath.cpp", + "src/renderer/sw_engine/tvgSwMemPool.cpp", + "src/renderer/sw_engine/tvgSwRaster.cpp", + "src/renderer/sw_engine/tvgSwRenderer.cpp", + "src/renderer/sw_engine/tvgSwRle.cpp", + "src/renderer/sw_engine/tvgSwShape.cpp", + "src/renderer/sw_engine/tvgSwStroke.cpp", ] thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] env_tvg.Append( CPPPATH=[ "../../../thirdparty/thorvg/inc", - "../../../thirdparty/thorvg/src/lib", - "../../../thirdparty/thorvg/src/lib/sw_engine", - "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + "../../../thirdparty/thorvg/src/renderer/sw_engine", "../../../thirdparty/thorvg/src/loaders/svg", - "../../../thirdparty/libpng", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", ] ) # Enable ThorVG static object linking. env_tvg.Append(CPPDEFINES=["TVG_STATIC"]) - env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/common", + "../../../thirdparty/thorvg/src/renderer", + ] + ) env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) lib = env_tvg.Library( diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index d346f54827..f12275d10c 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -123,6 +123,14 @@ void TextServerFallback::_free_rid(const RID &p_rid) { font_owner.free(p_rid); } memdelete(fd); + } else if (font_var_owner.owns(p_rid)) { + MutexLock ftlock(ft_mutex); + + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_rid); + { + font_var_owner.free(p_rid); + } + memdelete(fdv); } else if (shaped_owner.owns(p_rid)) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid); { @@ -935,9 +943,25 @@ RID TextServerFallback::_create_font() { return font_owner.make_rid(fd); } +RID TextServerFallback::_create_font_linked_variation(const RID &p_font_rid) { + _THREAD_SAFE_METHOD_ + + RID rid = p_font_rid; + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + ERR_FAIL_COND_V(!font_owner.owns(rid), RID()); + + FontFallbackLinkedVariation *new_fdv = memnew(FontFallbackLinkedVariation); + new_fdv->base_font = rid; + + return font_var_owner.make_rid(new_fdv); +} + void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -947,8 +971,8 @@ void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteA } void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); _font_clear_cache(fd); @@ -958,8 +982,8 @@ void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t } void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -971,8 +995,8 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f ERR_FAIL_COND(p_face_index < 0); ERR_FAIL_COND(p_face_index >= 0x7FFF); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->face_index != p_face_index) { @@ -982,16 +1006,16 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f } int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->face_index; } int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); int face_count = 0; @@ -1036,8 +1060,8 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { } BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1046,8 +1070,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p } void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1056,8 +1080,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin } String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1066,8 +1090,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { } void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1076,8 +1100,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh } int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 400); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 400); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1086,8 +1110,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { } void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1096,8 +1120,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre } int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 100); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 100); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1106,8 +1130,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { } void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1116,8 +1140,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n } String TextServerFallback::_font_get_name(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -1126,8 +1150,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const { } void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->antialiasing != p_antialiasing) { @@ -1137,16 +1161,16 @@ void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServe } TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE); MutexLock lock(fd->mutex); return fd->antialiasing; } void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->mipmaps != p_generate_mipmaps) { @@ -1161,16 +1185,16 @@ void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool } bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->mipmaps; } void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf != p_msdf) { @@ -1180,16 +1204,16 @@ void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID } bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf; } void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_range != p_msdf_pixel_range) { @@ -1199,16 +1223,16 @@ void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64 } int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->msdf_range; } void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->msdf_source_size != p_msdf_size) { @@ -1218,48 +1242,64 @@ void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms } int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->msdf_source_size; } void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->fixed_size = p_fixed_size; } int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); return fd->fixed_size; } +void TextServerFallback::_font_set_fixed_size_scale_mode(const RID &p_font_rid, TextServer::FixedSizeScaleMode p_fixed_size_scale_mode) { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); + + MutexLock lock(fd->mutex); + fd->fixed_size_scale_mode = p_fixed_size_scale_mode; +} + +TextServer::FixedSizeScaleMode TextServerFallback::_font_get_fixed_size_scale_mode(const RID &p_font_rid) const { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, FIXED_SIZE_SCALE_DISABLE); + + MutexLock lock(fd->mutex); + return fd->fixed_size_scale_mode; +} + void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->allow_system_fallback = p_allow_system_fallback; } bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->allow_system_fallback; } void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->force_autohinter != p_force_autohinter) { @@ -1269,16 +1309,16 @@ void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool } bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->force_autohinter; } void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->hinting != p_hinting) { @@ -1288,32 +1328,32 @@ void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hi } TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, HINTING_NONE); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, HINTING_NONE); MutexLock lock(fd->mutex); return fd->hinting; } void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->subpixel_positioning = p_subpixel; } TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED); MutexLock lock(fd->mutex); return fd->subpixel_positioning; } void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->embolden != p_strength) { @@ -1323,8 +1363,8 @@ void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_stre } double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->embolden; @@ -1332,29 +1372,40 @@ double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const { void TextServerFallback::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + if (fdv->extra_spacing[p_spacing] != p_value) { + fdv->extra_spacing[p_spacing] = p_value; + } + } else { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL(fd); - MutexLock lock(fd->mutex); - if (fd->extra_spacing[p_spacing] != p_value) { - _font_clear_cache(fd); - fd->extra_spacing[p_spacing] = p_value; + MutexLock lock(fd->mutex); + if (fd->extra_spacing[p_spacing] != p_value) { + _font_clear_cache(fd); + fd->extra_spacing[p_spacing] = p_value; + } } } int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const { ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid); + if (fdv) { + return fdv->extra_spacing[p_spacing]; + } else { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); - - MutexLock lock(fd->mutex); - return fd->extra_spacing[p_spacing]; + MutexLock lock(fd->mutex); + return fd->extra_spacing[p_spacing]; + } } void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->transform != p_transform) { @@ -1364,33 +1415,35 @@ void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transf } Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Transform2D()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Transform2D()); MutexLock lock(fd->mutex); return fd->transform; } void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); - _font_clear_cache(fd); - fd->variation_coordinates = p_variation_coordinates; + if (!fd->variation_coordinates.recursive_equal(p_variation_coordinates, 1)) { + _font_clear_cache(fd); + fd->variation_coordinates = p_variation_coordinates.duplicate(); + } } Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->variation_coordinates; } void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); if (fd->oversampling != p_oversampling) { @@ -1400,16 +1453,16 @@ void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_ } double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); return fd->oversampling; } TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); TypedArray<Vector2i> ret; @@ -1420,8 +1473,8 @@ TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_ } void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -1432,8 +1485,8 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) { } void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); MutexLock ftlock(ft_mutex); @@ -1444,8 +1497,8 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve } void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1455,8 +1508,8 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, } double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1465,14 +1518,20 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz if (fd->msdf) { return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->ascent; } } void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); Vector2i size = _get_size(fd, p_size); @@ -1481,8 +1540,8 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size } double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1491,14 +1550,20 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si if (fd->msdf) { return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->descent; } } void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1508,8 +1573,8 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int } double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1518,14 +1583,20 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i if (fd->msdf) { return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->underline_position; } } void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1535,8 +1606,8 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in } double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1545,14 +1616,20 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, if (fd->msdf) { return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->underline_thickness; } } void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1567,8 +1644,8 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, } double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0.0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0.0); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1577,14 +1654,20 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size if (fd->msdf) { return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + } else { + return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return fd->cache[size]->scale / fd->cache[size]->oversampling; } } int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, 0); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, 0); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1595,8 +1678,8 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const } void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1605,8 +1688,8 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto } void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1617,8 +1700,8 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto } void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND(p_image.is_null()); MutexLock lock(fd->mutex); @@ -1646,8 +1729,8 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve } Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Ref<Image>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Ref<Image>()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1660,8 +1743,8 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) { ERR_FAIL_COND(p_offsets.size() % 4 != 0); - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1679,8 +1762,8 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const } PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1704,8 +1787,8 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font } PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedInt32Array()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1720,8 +1803,8 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, } void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1731,8 +1814,8 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1742,8 +1825,8 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2 } Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1772,6 +1855,12 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + } else { + return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { return (gl[p_glyph | mod].advance + ea).round(); } else { @@ -1780,8 +1869,8 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 } void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -1795,8 +1884,8 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t } Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1819,14 +1908,20 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const if (fd->msdf) { return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + } else { + return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + } } else { return gl[p_glyph | mod].rect.position; } } void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1840,8 +1935,8 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec } Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1864,14 +1959,20 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve if (fd->msdf) { return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + } else { + return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + } } else { return gl[p_glyph | mod].rect.size; } } void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1885,8 +1986,8 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto } Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Rect2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Rect2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1910,8 +2011,8 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V } void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1925,8 +2026,8 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve } int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, -1); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, -1); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1950,8 +2051,8 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c } void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -1965,8 +2066,8 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons } RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, RID()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, RID()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2011,8 +2112,8 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const } Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Size2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Size2()); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2057,8 +2158,8 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co } Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2087,6 +2188,12 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + scale = scale * (double)p_size / (double)fd->fixed_size; + } else { + scale = scale * Math::round((double)p_size / (double)fd->fixed_size); + } } for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); @@ -2107,8 +2214,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i } TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2123,8 +2230,8 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon } void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2134,8 +2241,8 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t } void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2145,8 +2252,8 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s } void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2156,8 +2263,8 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size } Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Vector2()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Vector2()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2169,6 +2276,12 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s if (kern.has(p_glyph_pair)) { if (fd->msdf) { return kern[p_glyph_pair] * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return kern[p_glyph_pair] * (double)p_size / (double)fd->fixed_size; + } else { + return kern[p_glyph_pair] * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return kern[p_glyph_pair]; } @@ -2181,6 +2294,12 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; + } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->fixed_size; + } else { + return Vector2(delta.x, delta.y) * Math::round((double)p_size / (double)fd->fixed_size); + } } else { return Vector2(delta.x, delta.y); } @@ -2200,7 +2319,7 @@ int64_t TextServerFallback::_font_get_char_from_glyph_index(const RID &p_font_ri } bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); + FontFallback *fd = _get_font_data(p_font_rid); ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + "."); if (!fd) { return false; @@ -2221,8 +2340,8 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c } String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, String()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); if (fd->cache.is_empty()) { @@ -2254,8 +2373,8 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons } void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + "."); ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + "."); @@ -2289,8 +2408,8 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); @@ -2320,8 +2439,8 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2 } void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); @@ -2398,8 +2517,20 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - cpos += gl.rect.position; + Vector2 gpos = gl.rect.position; Size2 csize = gl.rect.size; + if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + double gl_scale = (double)p_size / (double)fd->fixed_size; + gpos *= gl_scale; + csize *= gl_scale; + } else { + double gl_scale = Math::round((double)p_size / (double)fd->fixed_size); + gpos *= gl_scale; + csize *= gl_scale; + } + } + cpos += gpos; if (lcd_aa) { RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); } else { @@ -2412,8 +2543,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); @@ -2490,8 +2621,20 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - cpos += gl.rect.position; + Vector2 gpos = gl.rect.position; Size2 csize = gl.rect.size; + if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { + if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { + double gl_scale = (double)p_size / (double)fd->fixed_size; + gpos *= gl_scale; + csize *= gl_scale; + } else { + double gl_scale = Math::round((double)p_size / (double)fd->fixed_size); + gpos *= gl_scale; + csize *= gl_scale; + } + } + cpos += gpos; if (lcd_aa) { RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); } else { @@ -2504,8 +2647,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->language_support_overrides.has(p_language)) { @@ -2516,32 +2659,32 @@ bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, cons } void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides[p_language] = p_supported; } bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->language_support_overrides[p_language]; } void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->language_support_overrides.erase(p_language); } PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -2552,8 +2695,8 @@ PackedStringArray TextServerFallback::_font_get_language_support_overrides(const } bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); if (fd->script_support_overrides.has(p_script)) { @@ -2564,24 +2707,24 @@ bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const } void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); fd->script_support_overrides[p_script] = p_supported; } bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, false); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); MutexLock lock(fd->mutex); return fd->script_support_overrides[p_script]; } void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2590,8 +2733,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_ } PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, PackedStringArray()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedStringArray()); MutexLock lock(fd->mutex); PackedStringArray out; @@ -2602,8 +2745,8 @@ PackedStringArray TextServerFallback::_font_get_script_support_overrides(const R } void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND(!fd); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2612,8 +2755,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_ } Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); return fd->feature_overrides; @@ -2624,8 +2767,8 @@ Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_ri } Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const { - FontFallback *fd = font_owner.get_or_null(p_font_rid); - ERR_FAIL_COND_V(!fd, Dictionary()); + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, Dictionary()); MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); @@ -2714,7 +2857,7 @@ RID TextServerFallback::_create_shaped_text(TextServer::Direction p_direction, T void TextServerFallback::_shaped_text_clear(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); sd->parent = RID(); @@ -2744,7 +2887,7 @@ TextServer::Direction TextServerFallback::_shaped_text_get_inferred_direction(co void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) { _THREAD_SAFE_METHOD_ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->custom_punct != p_punct) { if (sd->parent != RID()) { @@ -2758,13 +2901,13 @@ void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shaped) const { _THREAD_SAFE_METHOD_ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, String()); + ERR_FAIL_NULL_V(sd, String()); return sd->custom_punct; } void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->orientation != p_orientation) { @@ -2782,7 +2925,7 @@ void TextServerFallback::_shaped_text_set_bidi_override(const RID &p_shaped, con TextServer::Orientation TextServerFallback::_shaped_text_get_orientation(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL); + ERR_FAIL_NULL_V(sd, TextServer::ORIENTATION_HORIZONTAL); MutexLock lock(sd->mutex); return sd->orientation; @@ -2792,7 +2935,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped, ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); MutexLock lock(sd->mutex); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); if (sd->preserve_invalid != p_enabled) { if (sd->parent != RID()) { full_copy(sd); @@ -2804,7 +2947,7 @@ void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_invalid; @@ -2812,7 +2955,7 @@ bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->preserve_control != p_enabled) { @@ -2826,7 +2969,7 @@ void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->preserve_control; @@ -2835,7 +2978,7 @@ bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) void TextServerFallback::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) { ERR_FAIL_INDEX((int)p_spacing, 4); ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); MutexLock lock(sd->mutex); if (sd->extra_spacing[p_spacing] != p_value) { @@ -2851,7 +2994,7 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin ERR_FAIL_INDEX_V((int)p_spacing, 4, 0); const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); return sd->extra_spacing[p_spacing]; @@ -2859,20 +3002,20 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); return sd->spans.size(); } Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Variant()); + ERR_FAIL_NULL_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(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND(!sd); + ERR_FAIL_NULL(sd); ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index]; @@ -2896,13 +3039,13 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64 bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t 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); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_size <= 0, false); for (int i = 0; i < p_fonts.size(); i++) { - ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false); + ERR_FAIL_NULL_V(_get_font_data(p_fonts[i]), false); } if (p_text.is_empty()) { @@ -2947,7 +3090,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(p_key == Variant(), false); @@ -2979,7 +3122,7 @@ bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Vari bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), false); @@ -3123,7 +3266,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start _THREAD_SAFE_METHOD_ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); if (sd->parent != RID()) { @@ -3215,7 +3358,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, RID()); + ERR_FAIL_NULL_V(sd, RID()); MutexLock lock(sd->mutex); return sd->parent; @@ -3223,7 +3366,7 @@ RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<JustificationFlag> p_jst_flags) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3332,7 +3475,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3388,7 +3531,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3444,7 +3587,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3460,7 +3603,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); - ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3617,7 +3760,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.trim_pos; @@ -3625,7 +3768,7 @@ int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, -1, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_pos; @@ -3633,7 +3776,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) c const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, nullptr, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.ptr(); @@ -3641,7 +3784,7 @@ const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_s int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid."); + ERR_FAIL_NULL_V_MSG(sd, 0, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); return sd->overrun_trim_data.ellipsis_glyph_buf.size(); @@ -3649,7 +3792,7 @@ int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_s bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); if (sd->valid) { @@ -3958,7 +4101,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, false); + ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); return sd->valid; @@ -3966,7 +4109,7 @@ bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3977,7 +4120,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0); + ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -3988,7 +4131,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, nullptr); + ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4000,7 +4143,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Vector2i()); + ERR_FAIL_NULL_V(sd, Vector2i()); MutexLock lock(sd->mutex); return Vector2(sd->start, sd->end); @@ -4009,7 +4152,7 @@ Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const { Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const { Array ret; const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, ret); + ERR_FAIL_NULL_V(sd, ret); MutexLock lock(sd->mutex); for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) { @@ -4021,7 +4164,7 @@ Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const { Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Rect2()); + ERR_FAIL_NULL_V(sd, Rect2()); MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); @@ -4033,7 +4176,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, Size2()); + ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4048,7 +4191,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4059,7 +4202,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4070,7 +4213,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4081,7 +4224,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4093,7 +4236,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, 0.0); + ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); if (!sd->valid) { @@ -4105,7 +4248,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); - ERR_FAIL_COND_V(!sd, PackedInt32Array()); + ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); if (!sd->valid) { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index c44b45fc27..5c30ea0c05 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -245,12 +245,18 @@ class TextServerFallback : public TextServerExtension { } }; + struct FontFallbackLinkedVariation { + RID base_font; + int extra_spacing[4] = { 0, 0, 0, 0 }; + }; + struct FontFallback { Mutex mutex; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; bool mipmaps = false; bool msdf = false; + FixedSizeScaleMode fixed_size_scale_mode = FIXED_SIZE_SCALE_DISABLE; int msdf_range = 14; int msdf_source_size = 48; int fixed_size = 0; @@ -451,9 +457,19 @@ class TextServerFallback : public TextServerExtension { // Common data. double oversampling = 1.0; + mutable RID_PtrOwner<FontFallbackLinkedVariation> font_var_owner; mutable RID_PtrOwner<FontFallback> font_owner; mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + _FORCE_INLINE_ FontFallback *_get_font_data(const RID &p_font_rid) const { + RID rid = p_font_rid; + FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid); + if (unlikely(fdv)) { + rid = fdv->base_font; + } + return font_owner.get_or_null(rid); + } + struct SystemFontKey { String font_name; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; @@ -569,6 +585,7 @@ public: /* Font interface */ MODBIND0R(RID, create_font); + MODBIND1R(RID, create_font_linked_variation, const RID &); MODBIND2(font_set_data, const RID &, const PackedByteArray &); MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t); @@ -611,6 +628,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_fixed_size_scale_mode, const RID &, FixedSizeScaleMode); + MODBIND1RC(FixedSizeScaleMode, font_get_fixed_size_scale_mode, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index df7672754b..aef4f394b2 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -242,14 +242,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const { } void UPNP::add_device(Ref<UPNPDevice> device) { - ERR_FAIL_COND(device == nullptr); + ERR_FAIL_NULL(device); devices.push_back(device); } void UPNP::set_device(int index, Ref<UPNPDevice> device) { ERR_FAIL_INDEX(index, devices.size()); - ERR_FAIL_COND(device == nullptr); + ERR_FAIL_NULL(device); devices.set(index, device); } diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index b54335b724..8a265ffaf3 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -264,11 +264,10 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) { return; } - vorbis_synthesis_restart(&dsp_state); - if (p_time >= vorbis_stream->get_length()) { p_time = 0; } + frames_mixed = uint32_t(vorbis_data->get_sampling_rate() * p_time); const int64_t desired_sample = p_time * get_stream_sampling_rate(); @@ -278,107 +277,81 @@ void AudioStreamPlaybackOggVorbis::seek(double p_time) { return; } - ogg_packet *packet; - if (!vorbis_data_playback->next_ogg_packet(&packet)) { - WARN_PRINT_ONCE("seeking beyond limits"); - return; + // We want to start decoding before the page that we expect the sample to be in (the sample may + // be part of a partial packet across page boundaries). Otherwise, the decoder may not have + // synchronized before reaching the sample. + int64_t start_page_number = vorbis_data_playback->get_page_number() - 1; + if (start_page_number < 0) { + start_page_number = 0; } - // The granule position of the page we're seeking through. - int64_t granule_pos = 0; - - int headers_remaining = 0; - int samples_in_page = 0; - int err; while (true) { - if (vorbis_synthesis_idheader(packet)) { - headers_remaining = 3; - } - if (!headers_remaining) { - err = vorbis_synthesis(&block, packet); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err)); - - err = vorbis_synthesis_blockin(&dsp_state, &block); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err)); - - int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); - err = vorbis_synthesis_read(&dsp_state, samples_out); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err)); - - samples_in_page += samples_out; - - } else { - headers_remaining--; - } - if (packet->granulepos != -1 && headers_remaining == 0) { - // This indicates the end of the page. - granule_pos = packet->granulepos; - break; - } - if (packet->e_o_s) { - break; - } - if (!vorbis_data_playback->next_ogg_packet(&packet)) { - // We should get an e_o_s flag before this happens. - WARN_PRINT("Vorbis file ended without warning."); - break; - } - } + ogg_packet *packet; + int err; - int64_t samples_to_burn = samples_in_page - (granule_pos - desired_sample); + // We start at an unknown granule position. + int64_t granule_pos = -1; - if (samples_to_burn > samples_in_page) { - WARN_PRINT("Burning more samples than we have in this page. Check seek algorithm."); - } else if (samples_to_burn < 0) { - WARN_PRINT("Burning negative samples doesn't make sense. Check seek algorithm."); - } + // Decode data until we get to the desired sample or notice that we have read past it. + vorbis_data_playback->set_page_number(start_page_number); + vorbis_synthesis_restart(&dsp_state); - // Seek again, this time we'll burn a specific number of samples instead of all of them. - if (!vorbis_data_playback->seek_page(desired_sample)) { - WARN_PRINT("seek failed"); - return; - } - - if (!vorbis_data_playback->next_ogg_packet(&packet)) { - WARN_PRINT_ONCE("seeking beyond limits"); - return; - } - vorbis_synthesis_restart(&dsp_state); + while (true) { + if (!vorbis_data_playback->next_ogg_packet(&packet)) { + WARN_PRINT_ONCE("Seeking beyond limits"); + return; + } - while (true) { - if (vorbis_synthesis_idheader(packet)) { - headers_remaining = 3; - } - if (!headers_remaining) { err = vorbis_synthesis(&block, packet); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err)); - - err = vorbis_synthesis_blockin(&dsp_state, &block); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err)); - - int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); - int read_samples = samples_to_burn > samples_out ? samples_out : samples_to_burn; - err = vorbis_synthesis_read(&dsp_state, samples_out); - ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err)); - samples_to_burn -= read_samples; - - if (samples_to_burn <= 0) { - break; + if (err != OV_ENOTAUDIO) { + ERR_FAIL_COND_MSG(err != 0, "Error during vorbis synthesis " + itos(err) + "."); + + err = vorbis_synthesis_blockin(&dsp_state, &block); + ERR_FAIL_COND_MSG(err != 0, "Error during vorbis block processing " + itos(err) + "."); + + int samples_out = vorbis_synthesis_pcmout(&dsp_state, nullptr); + + if (granule_pos < 0) { + // We don't know where we are yet, so just keep on decoding. + err = vorbis_synthesis_read(&dsp_state, samples_out); + ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + "."); + } else if (granule_pos + samples_out >= desired_sample) { + // Our sample is in this block. Skip the beginning of the block up to the sample, then + // return. + int skip_samples = (int)(desired_sample - granule_pos); + err = vorbis_synthesis_read(&dsp_state, skip_samples); + ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + "."); + have_samples_left = skip_samples < samples_out; + have_packets_left = !packet->e_o_s; + return; + } else { + // Our sample is not in this block. Skip it. + err = vorbis_synthesis_read(&dsp_state, samples_out); + ERR_FAIL_COND_MSG(err != 0, "Error during vorbis read updating " + itos(err) + "."); + granule_pos += samples_out; + } + } + if (packet->granulepos != -1) { + // We found an update to our granule position. + granule_pos = packet->granulepos; + if (granule_pos > desired_sample) { + // We've read past our sample. We need to start on an earlier page. + if (start_page_number == 0) { + // We didn't find the sample even reading from the beginning. + have_samples_left = false; + have_packets_left = !packet->e_o_s; + return; + } + start_page_number--; + break; + } + } + if (packet->e_o_s) { + // We've reached the end of the stream and didn't find our sample. + have_samples_left = false; + have_packets_left = false; + return; } - } else { - headers_remaining--; - } - if (packet->granulepos != -1 && headers_remaining == 0) { - // This indicates the end of the page. - break; - } - if (packet->e_o_s) { - break; - } - if (!vorbis_data_playback->next_ogg_packet(&packet)) { - // We should get an e_o_s flag before this happens. - WARN_PRINT("Vorbis file ended without warning."); - break; } } } diff --git a/modules/vorbis/audio_stream_ogg_vorbis.h b/modules/vorbis/audio_stream_ogg_vorbis.h index 41ce942eec..6abaeaaebd 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.h +++ b/modules/vorbis/audio_stream_ogg_vorbis.h @@ -107,9 +107,9 @@ class AudioStreamOggVorbis : public AudioStream { friend class AudioStreamPlaybackOggVorbis; int channels = 1; - float length = 0.0; + double length = 0.0; bool loop = false; - float loop_offset = 0.0; + double loop_offset = 0.0; // Performs a seek to the beginning of the stream, should not be called during playback! // Also causes allocation and deallocation. diff --git a/modules/vorbis/resource_importer_ogg_vorbis.cpp b/modules/vorbis/resource_importer_ogg_vorbis.cpp index b42cd20589..a8c92f06f6 100644 --- a/modules/vorbis/resource_importer_ogg_vorbis.cpp +++ b/modules/vorbis/resource_importer_ogg_vorbis.cpp @@ -97,7 +97,7 @@ void ResourceImporterOggVorbis::show_advanced_options(const String &p_path) { Error ResourceImporterOggVorbis::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { bool loop = p_options["loop"]; - float loop_offset = p_options["loop_offset"]; + double loop_offset = p_options["loop_offset"]; double bpm = p_options["bpm"]; int beat_count = p_options["beat_count"]; int bar_beats = p_options["bar_beats"]; @@ -184,7 +184,7 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect ERR_FAIL_COND_V_MSG(err != 0, Ref<AudioStreamOggVorbis>(), "Ogg stream error " + itos(err)); int desync_iters = 0; - Vector<Vector<uint8_t>> packet_data; + RBMap<uint64_t, Vector<Vector<uint8_t>>> sorted_packets; int64_t granule_pos = 0; while (true) { @@ -192,6 +192,7 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect if (err == -1) { // According to the docs this is usually recoverable, but don't sit here spinning forever. desync_iters++; + WARN_PRINT_ONCE("Desync during ogg import."); ERR_FAIL_COND_V_MSG(desync_iters > 100, Ref<AudioStreamOggVorbis>(), "Packet sync issue during Ogg import"); continue; } else if (err == 0) { @@ -207,16 +208,24 @@ Ref<AudioStreamOggVorbis> ResourceImporterOggVorbis::load_from_buffer(const Vect } break; } - granule_pos = packet.granulepos; + if (packet.granulepos > granule_pos) { + granule_pos = packet.granulepos; + } PackedByteArray data; data.resize(packet.bytes); memcpy(data.ptrw(), packet.packet, packet.bytes); - packet_data.push_back(data); + sorted_packets[granule_pos].push_back(data); packet_count++; } + Vector<Vector<uint8_t>> packet_data; + for (const KeyValue<uint64_t, Vector<Vector<uint8_t>>> &pair : sorted_packets) { + for (const Vector<uint8_t> &packets : pair.value) { + packet_data.push_back(packets); + } + } if (initialized_stream) { - ogg_packet_sequence->push_page(granule_pos, packet_data); + ogg_packet_sequence->push_page(ogg_page_granulepos(&page), packet_data); } } if (initialized_stream) { diff --git a/modules/webrtc/library_godot_webrtc.js b/modules/webrtc/library_godot_webrtc.js index 7ece4aa872..eae4120741 100644 --- a/modules/webrtc/library_godot_webrtc.js +++ b/modules/webrtc/library_godot_webrtc.js @@ -90,6 +90,7 @@ const GodotRTCDataChannel = { }, }, + godot_js_rtc_datachannel_ready_state_get__proxy: 'sync', godot_js_rtc_datachannel_ready_state_get__sig: 'ii', godot_js_rtc_datachannel_ready_state_get: function (p_id) { const ref = IDHandler.get(p_id); @@ -110,6 +111,7 @@ const GodotRTCDataChannel = { } }, + godot_js_rtc_datachannel_send__proxy: 'sync', godot_js_rtc_datachannel_send__sig: 'iiiii', godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) { const ref = IDHandler.get(p_id); @@ -131,16 +133,19 @@ const GodotRTCDataChannel = { return 0; }, + godot_js_rtc_datachannel_is_ordered__proxy: 'sync', godot_js_rtc_datachannel_is_ordered__sig: 'ii', godot_js_rtc_datachannel_is_ordered: function (p_id) { return GodotRTCDataChannel.get_prop(p_id, 'ordered', true); }, + godot_js_rtc_datachannel_id_get__proxy: 'sync', godot_js_rtc_datachannel_id_get__sig: 'ii', godot_js_rtc_datachannel_id_get: function (p_id) { return GodotRTCDataChannel.get_prop(p_id, 'id', 65535); }, + godot_js_rtc_datachannel_max_packet_lifetime_get__proxy: 'sync', godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii', godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) { const ref = IDHandler.get(p_id); @@ -156,21 +161,25 @@ const GodotRTCDataChannel = { return 65535; }, + godot_js_rtc_datachannel_max_retransmits_get__proxy: 'sync', godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii', godot_js_rtc_datachannel_max_retransmits_get: function (p_id) { return GodotRTCDataChannel.get_prop(p_id, 'maxRetransmits', 65535); }, + godot_js_rtc_datachannel_is_negotiated__proxy: 'sync', godot_js_rtc_datachannel_is_negotiated__sig: 'ii', godot_js_rtc_datachannel_is_negotiated: function (p_id) { return GodotRTCDataChannel.get_prop(p_id, 'negotiated', 65535); }, + godot_js_rtc_datachannel_get_buffered_amount__proxy: 'sync', godot_js_rtc_datachannel_get_buffered_amount__sig: 'ii', godot_js_rtc_datachannel_get_buffered_amount: function (p_id) { return GodotRTCDataChannel.get_prop(p_id, 'bufferedAmount', 0); }, + godot_js_rtc_datachannel_label_get__proxy: 'sync', godot_js_rtc_datachannel_label_get__sig: 'ii', godot_js_rtc_datachannel_label_get: function (p_id) { const ref = IDHandler.get(p_id); @@ -189,12 +198,14 @@ const GodotRTCDataChannel = { return GodotRuntime.allocString(ref.protocol); }, + godot_js_rtc_datachannel_destroy__proxy: 'sync', godot_js_rtc_datachannel_destroy__sig: 'vi', godot_js_rtc_datachannel_destroy: function (p_id) { GodotRTCDataChannel.close(p_id); IDHandler.remove(p_id); }, + godot_js_rtc_datachannel_connect__proxy: 'sync', godot_js_rtc_datachannel_connect__sig: 'viiiiii', godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) { const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref); @@ -204,6 +215,7 @@ const GodotRTCDataChannel = { GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose); }, + godot_js_rtc_datachannel_close__proxy: 'sync', godot_js_rtc_datachannel_close__sig: 'vi', godot_js_rtc_datachannel_close: function (p_id) { const ref = IDHandler.get(p_id); @@ -356,6 +368,7 @@ const GodotRTCPeerConnection = { }, }, + godot_js_rtc_pc_create__proxy: 'sync', godot_js_rtc_pc_create__sig: 'iiiiiiii', godot_js_rtc_pc_create: function (p_config, p_ref, p_on_connection_state_change, p_on_ice_gathering_state_change, p_on_signaling_state_change, p_on_ice_candidate, p_on_datachannel) { const wrap = function (p_func) { @@ -371,6 +384,7 @@ const GodotRTCPeerConnection = { ); }, + godot_js_rtc_pc_close__proxy: 'sync', godot_js_rtc_pc_close__sig: 'vi', godot_js_rtc_pc_close: function (p_id) { const ref = IDHandler.get(p_id); @@ -380,11 +394,13 @@ const GodotRTCPeerConnection = { ref.close(); }, + godot_js_rtc_pc_destroy__proxy: 'sync', godot_js_rtc_pc_destroy__sig: 'vi', godot_js_rtc_pc_destroy: function (p_id) { GodotRTCPeerConnection.destroy(p_id); }, + godot_js_rtc_pc_offer_create__proxy: 'sync', godot_js_rtc_pc_offer_create__sig: 'viiii', godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) { const ref = IDHandler.get(p_id); @@ -400,6 +416,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_local_description_set__proxy: 'sync', godot_js_rtc_pc_local_description_set__sig: 'viiiii', godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) { const ref = IDHandler.get(p_id); @@ -417,6 +434,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_remote_description_set__proxy: 'sync', godot_js_rtc_pc_remote_description_set__sig: 'viiiiii', godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) { const ref = IDHandler.get(p_id); @@ -442,6 +460,7 @@ const GodotRTCPeerConnection = { }); }, + godot_js_rtc_pc_ice_candidate_add__proxy: 'sync', godot_js_rtc_pc_ice_candidate_add__sig: 'viiii', godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) { const ref = IDHandler.get(p_id); @@ -458,6 +477,7 @@ const GodotRTCPeerConnection = { }, godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'], + godot_js_rtc_pc_datachannel_create__proxy: 'sync', godot_js_rtc_pc_datachannel_create__sig: 'iiii', godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) { try { diff --git a/modules/webrtc/register_types.cpp b/modules/webrtc/register_types.cpp index 687c7b711e..28ce36f1e8 100644 --- a/modules/webrtc/register_types.cpp +++ b/modules/webrtc/register_types.cpp @@ -42,11 +42,7 @@ void initialize_webrtc_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } - -#define SET_HINT(NAME, _VAL_, _MAX_) \ - GLOBAL_DEF(PropertyInfo(Variant::INT, NAME, PROPERTY_HINT_RANGE, "2," #_MAX_ ",1,or_greater"), _VAL_); - - SET_HINT(WRTC_IN_BUF, 64, 4096); + GLOBAL_DEF(PropertyInfo(Variant::INT, "network/limits/webrtc/max_channel_in_buffer_kb", PROPERTY_HINT_RANGE, "2,4096,1,or_greater"), 64); ClassDB::register_custom_instance_class<WebRTCPeerConnection>(); GDREGISTER_CLASS(WebRTCPeerConnectionExtension); @@ -55,8 +51,6 @@ void initialize_webrtc_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(WebRTCDataChannelExtension); GDREGISTER_CLASS(WebRTCMultiplayerPeer); - -#undef SET_HINT } void uninitialize_webrtc_module(ModuleInitializationLevel p_level) { diff --git a/modules/webrtc/webrtc_data_channel.cpp b/modules/webrtc/webrtc_data_channel.cpp index bebf5c2741..6c0d0bea37 100644 --- a/modules/webrtc/webrtc_data_channel.cpp +++ b/modules/webrtc/webrtc_data_channel.cpp @@ -61,7 +61,7 @@ void WebRTCDataChannel::_bind_methods() { } WebRTCDataChannel::WebRTCDataChannel() { - _in_buffer_shift = nearest_shift((int)GLOBAL_GET(WRTC_IN_BUF) - 1) + 10; + _in_buffer_shift = nearest_shift((int)GLOBAL_GET("network/limits/webrtc/max_channel_in_buffer_kb") - 1) + 10; } WebRTCDataChannel::~WebRTCDataChannel() { diff --git a/modules/webrtc/webrtc_data_channel.h b/modules/webrtc/webrtc_data_channel.h index e884c8425d..f35461a5a0 100644 --- a/modules/webrtc/webrtc_data_channel.h +++ b/modules/webrtc/webrtc_data_channel.h @@ -33,8 +33,6 @@ #include "core/io/packet_peer.h" -#define WRTC_IN_BUF PNAME("network/limits/webrtc/max_channel_in_buffer_kb") - class WebRTCDataChannel : public PacketPeer { GDCLASS(WebRTCDataChannel, PacketPeer); diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 8bad6fd784..0a50b677c4 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -40,14 +40,14 @@ StringName WebRTCPeerConnection::default_extension; void WebRTCPeerConnection::set_default_extension(const StringName &p_extension) { ERR_FAIL_COND_MSG(!ClassDB::is_parent_class(p_extension, WebRTCPeerConnectionExtension::get_class_static()), vformat("Can't make %s the default WebRTC extension since it does not extend WebRTCPeerConnectionExtension.", p_extension)); - default_extension = p_extension; + default_extension = StringName(p_extension, true); } WebRTCPeerConnection *WebRTCPeerConnection::create() { #ifdef WEB_ENABLED return memnew(WebRTCPeerConnectionJS); #else - if (default_extension == String()) { + if (default_extension == StringName()) { WARN_PRINT_ONCE("No default WebRTC extension configured."); return memnew(WebRTCPeerConnectionExtension); } diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js index ed01c69725..a6ebdeba6a 100644 --- a/modules/websocket/library_godot_websocket.js +++ b/modules/websocket/library_godot_websocket.js @@ -124,7 +124,7 @@ const GodotWebSocket = { const ref = IDHandler.get(p_id); if (ref && ref.readyState < ref.CLOSING) { const code = p_code; - const reason = GodotRuntime.parseString(p_reason); + const reason = p_reason; ref.close(code, reason); } }, @@ -144,6 +144,7 @@ const GodotWebSocket = { }, }, + godot_js_websocket_create__proxy: 'sync', godot_js_websocket_create__sig: 'iiiiiiii', godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) { const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref); @@ -166,6 +167,7 @@ const GodotWebSocket = { return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close); }, + godot_js_websocket_send__proxy: 'sync', godot_js_websocket_send__sig: 'iiiii', godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) { const bytes_array = new Uint8Array(p_buf_len); @@ -180,11 +182,13 @@ const GodotWebSocket = { return GodotWebSocket.send(p_id, out); }, + godot_js_websocket_buffered_amount__proxy: 'sync', godot_js_websocket_buffered_amount__sig: 'ii', godot_js_websocket_buffered_amount: function (p_id) { return GodotWebSocket.bufferedAmount(p_id); }, + godot_js_websocket_close__proxy: 'sync', godot_js_websocket_close__sig: 'viii', godot_js_websocket_close: function (p_id, p_code, p_reason) { const code = p_code; @@ -192,6 +196,7 @@ const GodotWebSocket = { GodotWebSocket.close(p_id, code, reason); }, + godot_js_websocket_destroy__proxy: 'sync', godot_js_websocket_destroy__sig: 'vi', godot_js_websocket_destroy: function (p_id) { GodotWebSocket.destroy(p_id); diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index a127a6b75a..38cb614847 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -600,7 +600,7 @@ ssize_t WSLPeer::_wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t * } int WSLPeer::_wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { - ERR_FAIL_COND_V(!_static_rng, WSLAY_ERR_CALLBACK_FAILURE); + ERR_FAIL_NULL_V(_static_rng, WSLAY_ERR_CALLBACK_FAILURE); Error err = _static_rng->get_random_bytes(buf, len); ERR_FAIL_COND_V(err != OK, WSLAY_ERR_CALLBACK_FAILURE); return 0; @@ -676,7 +676,7 @@ void WSLPeer::poll() { } if (ready_state == STATE_OPEN || ready_state == STATE_CLOSING) { - ERR_FAIL_COND(!wsl_ctx); + ERR_FAIL_NULL(wsl_ctx); int err = 0; if ((err = wslay_event_recv(wsl_ctx)) != 0 || (err = wslay_event_send(wsl_ctx)) != 0) { // Error close. diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp index c8b4fb4e77..5f623476fc 100644 --- a/modules/zip/zip_packer.cpp +++ b/modules/zip/zip_packer.cpp @@ -33,7 +33,7 @@ #include "core/io/zip_io.h" #include "core/os/os.h" -Error ZIPPacker::open(String p_path, ZipAppend p_append) { +Error ZIPPacker::open(const String &p_path, ZipAppend p_append) { if (fa.is_valid()) { close(); } @@ -55,7 +55,7 @@ Error ZIPPacker::close() { return err; } -Error ZIPPacker::start_file(String p_path) { +Error ZIPPacker::start_file(const String &p_path) { ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use."); zip_fileinfo zipfi; @@ -76,7 +76,7 @@ Error ZIPPacker::start_file(String p_path) { return err == ZIP_OK ? OK : FAILED; } -Error ZIPPacker::write_file(Vector<uint8_t> p_data) { +Error ZIPPacker::write_file(const Vector<uint8_t> &p_data) { ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use."); return zipWriteInFileInZip(zf, p_data.ptr(), p_data.size()) == ZIP_OK ? OK : FAILED; diff --git a/modules/zip/zip_packer.h b/modules/zip/zip_packer.h index 142d0fddbf..e194f5ebbe 100644 --- a/modules/zip/zip_packer.h +++ b/modules/zip/zip_packer.h @@ -52,11 +52,11 @@ public: APPEND_ADDINZIP = 2, }; - Error open(String p_path, ZipAppend p_append); + Error open(const String &p_path, ZipAppend p_append); Error close(); - Error start_file(String p_path); - Error write_file(Vector<uint8_t> p_data); + Error start_file(const String &p_path); + Error write_file(const Vector<uint8_t> &p_data); Error close_file(); ZIPPacker(); diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp index 5752b829ef..f4a92dce5b 100644 --- a/modules/zip/zip_reader.cpp +++ b/modules/zip/zip_reader.cpp @@ -33,7 +33,7 @@ #include "core/error/error_macros.h" #include "core/io/zip_io.h" -Error ZIPReader::open(String p_path) { +Error ZIPReader::open(const String &p_path) { if (fa.is_valid()) { close(); } @@ -81,7 +81,7 @@ PackedStringArray ZIPReader::get_files() { return arr; } -PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) { +PackedByteArray ZIPReader::read_file(const String &p_path, bool p_case_sensitive) { ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "ZIPReader must be opened before use."); int err = UNZ_OK; @@ -118,7 +118,7 @@ PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) { return data; } -bool ZIPReader::file_exists(String p_path, bool p_case_sensitive) { +bool ZIPReader::file_exists(const String &p_path, bool p_case_sensitive) { ERR_FAIL_COND_V_MSG(fa.is_null(), false, "ZIPReader must be opened before use."); int cs = p_case_sensitive ? 1 : 2; diff --git a/modules/zip/zip_reader.h b/modules/zip/zip_reader.h index 0f78352e3f..874bd81ed3 100644 --- a/modules/zip/zip_reader.h +++ b/modules/zip/zip_reader.h @@ -46,12 +46,12 @@ protected: static void _bind_methods(); public: - Error open(String p_path); + Error open(const String &p_path); Error close(); PackedStringArray get_files(); - PackedByteArray read_file(String p_path, bool p_case_sensitive); - bool file_exists(String p_path, bool p_case_sensitive); + PackedByteArray read_file(const String &p_path, bool p_case_sensitive); + bool file_exists(const String &p_path, bool p_case_sensitive); ZIPReader(); ~ZIPReader(); |