diff options
25 files changed, 1080 insertions, 76 deletions
diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 234a4ddb79..95a4187062 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -239,13 +239,18 @@ void Basis::scale_orthogonal(const Vector3 &p_scale) { Basis Basis::scaled_orthogonal(const Vector3 &p_scale) const { Basis m = *this; Vector3 s = Vector3(-1, -1, -1) + p_scale; + bool sign = signbit(s.x + s.y + s.z); + Basis b = m.orthonormalized(); + s = b.xform_inv(s); Vector3 dots; - Basis b; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { dots[j] += s[i] * abs(m.get_column(i).normalized().dot(b.get_column(j))); } } + if (sign != signbit(dots.x + dots.y + dots.z)) { + dots = -dots; + } m.scale_local(Vector3(1, 1, 1) + dots); return m; } diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 2d7dff0b27..2e1adb9167 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -629,14 +629,14 @@ void Array::shuffle() { } } -int Array::bsearch(const Variant &p_value, bool p_before) { +int Array::bsearch(const Variant &p_value, bool p_before) const { Variant value = p_value; ERR_FAIL_COND_V(!_p->typed.validate(value, "binary search"), -1); SearchArray<Variant, _ArrayVariantSort> avs; return avs.bisect(_p->array.ptrw(), _p->array.size(), value, p_before); } -int Array::bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before) { +int Array::bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before) const { Variant value = p_value; ERR_FAIL_COND_V(!_p->typed.validate(value, "custom binary search"), -1); diff --git a/core/variant/array.h b/core/variant/array.h index 4ef8ba8ce7..8b1f8c0678 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -83,8 +83,8 @@ public: void sort(); void sort_custom(const Callable &p_callable); void shuffle(); - int bsearch(const Variant &p_value, bool p_before = true); - int bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before = true); + int bsearch(const Variant &p_value, bool p_before = true) const; + int bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before = true) const; void reverse(); int find(const Variant &p_value, int p_from = 0) const; diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 213a2254af..c4fec5a729 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -214,7 +214,7 @@ [b]Note:[/b] Calling this function is not the same as writing [code]array[-1][/code]. If the array is empty, accessing by index will pause project execution when running from the editor. </description> </method> - <method name="bsearch"> + <method name="bsearch" qualifiers="const"> <return type="int" /> <param index="0" name="value" type="Variant" /> <param index="1" name="before" type="bool" default="true" /> @@ -223,7 +223,7 @@ [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior. </description> </method> - <method name="bsearch_custom"> + <method name="bsearch_custom" qualifiers="const"> <return type="int" /> <param index="0" name="value" type="Variant" /> <param index="1" name="func" type="Callable" /> @@ -276,7 +276,7 @@ array.fill(0) # Initialize the 10 elements to 0. [/gdscript] [csharp] - var array = new Godot.Collections.Array{}; + var array = new Godot.Collections.Array(); array.Resize(10); array.Fill(0); // Initialize the 10 elements to 0. [/csharp] @@ -347,7 +347,7 @@ print(["inside", 7].has("7")) # False [/gdscript] [csharp] - var arr = new Godot.Collections.Array{"inside", 7}; + var arr = new Godot.Collections.Array { "inside", 7 }; // has is renamed to Contains GD.Print(arr.Contains("inside")); // True GD.Print(arr.Contains("outside")); // False @@ -364,7 +364,7 @@ [/gdscript] [csharp] // As there is no "in" keyword in C#, you have to use Contains - var array = new Godot.Collections.Array{2, 4, 6, 8}; + var array = new Godot.Collections.Array { 2, 4, 6, 8 }; if (array.Contains(2)) { GD.Print("Contains!"); @@ -454,10 +454,16 @@ <return type="Variant" /> <description> Returns a random value from the target array. - [codeblock] + [codeblocks] + [gdscript] var array: Array[int] = [1, 2, 3, 4] print(array.pick_random()) # Prints either of the four numbers. - [/codeblock] + [/gdscript] + [csharp] + var array = new Godot.Collections.Array { 1, 2, 3, 4 }; + GD.Print(array.PickRandom()); // Prints either of the four numbers. + [/csharp] + [/codeblocks] </description> </method> <method name="pop_at"> @@ -566,7 +572,7 @@ Returns the slice of the [Array], from [param begin] (inclusive) to [param end] (exclusive), as a new [Array]. The absolute value of [param begin] and [param end] will be clamped to the array size, so the default value for [param end] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]). If either [param begin] or [param end] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]). - If specified, [param step] is the relative index between source elements. It can be negative, then [param begin] must be higher than [param end]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code]). + If specified, [param step] is the relative index between source elements. It can be negative, then [param begin] must be higher than [param end]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code]. If [param deep] is true, each element will be copied by value rather than by reference. </description> </method> @@ -583,7 +589,9 @@ print(strings) # Prints [string1, string10, string11, string2] [/gdscript] [csharp] - // There is no sort support for Godot.Collections.Array + var strings = new Godot.Collections.Array { "string1", "string2", "string10", "string11" }; + strings.Sort(); + GD.Print(strings); // Prints [string1, string10, string11, string2] [/csharp] [/codeblocks] To perform natural order sorting, you can use [method sort_custom] with [method String.naturalnocasecmp_to] as follows: diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 8fc44d7536..50be9b86bf 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -173,14 +173,14 @@ <method name="rpc" qualifiers="vararg const"> <return type="void" /> <description> - Perform an RPC (Remote Procedure Call). This is used for multiplayer and is normally not available, unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. + Perform an RPC (Remote Procedure Call). This is used for multiplayer and is normally not available, unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. See [method Node.rpc]. </description> </method> <method name="rpc_id" qualifiers="vararg const"> <return type="void" /> <param index="0" name="peer_id" type="int" /> <description> - Perform an RPC (Remote Procedure Call) on a specific peer ID (see multiplayer documentation for reference). This is used for multiplayer and is normally not available unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. + Perform an RPC (Remote Procedure Call) on a specific peer ID (see multiplayer documentation for reference). This is used for multiplayer and is normally not available unless the function being called has been marked as [i]RPC[/i]. Calling this method on unsupported functions will result in an error. See [method Node.rpc_id]. </description> </method> <method name="unbind" qualifiers="const"> diff --git a/doc/classes/Decal.xml b/doc/classes/Decal.xml index fb8bc18c1a..b63f6e7252 100644 --- a/doc/classes/Decal.xml +++ b/doc/classes/Decal.xml @@ -8,6 +8,7 @@ They are made of an [AABB] and a group of [Texture2D]s specifying [Color], normal, ORM (ambient occlusion, roughness, metallic), and emission. Decals are projected within their [AABB] so altering the orientation of the Decal affects the direction in which they are projected. By default, Decals are projected down (i.e. from positive Y to negative Y). The [Texture2D]s associated with the Decal are automatically stored in a texture atlas which is used for drawing the decals so all decals can be drawn at once. Godot uses clustered decals, meaning they are stored in cluster data and drawn when the mesh is drawn, they are not drawn as a post-processing effect after. [b]Note:[/b] Decals cannot affect an underlying material's transparency, regardless of its transparency mode (alpha blend, alpha scissor, alpha hash, opaque pre-pass). This means translucent or transparent areas of a material will remain translucent or transparent even if an opaque decal is applied on them. + [b]Note:[/b] When using the Mobile rendering method, decals will only correctly affect meshes whose visibility AABB intersects with the decal's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the decal may not be visible on the mesh. </description> <tutorials> </tutorials> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 7c40c189c0..22665c8ffb 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -648,7 +648,7 @@ <return type="int" enum="Error" /> <param index="0" name="method" type="StringName" /> <description> - Sends a remote procedure call request for the given [param method] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behavior depends on the RPC configuration for the given method, see [method rpc_config]. Methods are not exposed to RPCs by default. Returns [code]null[/code]. + Sends a remote procedure call request for the given [param method] to peers on the network (and locally), optionally sending all additional arguments as arguments to the method called by the RPC. The call request will only be received by nodes with the same [NodePath], including the exact same node name. Behavior depends on the RPC configuration for the given method, see [method rpc_config] and [annotation @GDScript.@rpc]. Methods are not exposed to RPCs by default. Returns [code]null[/code]. [b]Note:[/b] You can only safely use RPCs on clients after you received the [code]connected_to_server[/code] signal from the [MultiplayerAPI]. You also need to keep track of the connection state, either by the [MultiplayerAPI] signals like [code]server_disconnected[/code] or by checking [code]get_multiplayer().peer.get_connection_status() == CONNECTION_CONNECTED[/code]. </description> </method> @@ -666,7 +666,7 @@ channel = 0, } [/codeblock] - See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc("any")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs). + See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding [annotation @GDScript.@rpc] annotation ([code]@rpc(any_peer)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs). </description> </method> <method name="rpc_id" qualifiers="vararg"> diff --git a/doc/classes/OmniLight3D.xml b/doc/classes/OmniLight3D.xml index f71c81e713..c0e10574c8 100644 --- a/doc/classes/OmniLight3D.xml +++ b/doc/classes/OmniLight3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> An Omnidirectional light is a type of [Light3D] that emits light in all directions. The light is attenuated by distance and this attenuation can be configured by changing its energy, radius, and attenuation parameters. + [b]Note:[/b] When using the Mobile or Compatibility rendering methods, omni lights will only correctly affect meshes whose visibility AABB intersects with the light's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the light may not be visible on the mesh. </description> <tutorials> <link title="3D lights and shadows">$DOCS_URL/tutorials/3d/lights_and_shadows.html</link> diff --git a/doc/classes/ReflectionProbe.xml b/doc/classes/ReflectionProbe.xml index fa0b1ab00b..e912925cd2 100644 --- a/doc/classes/ReflectionProbe.xml +++ b/doc/classes/ReflectionProbe.xml @@ -7,6 +7,7 @@ Captures its surroundings as a cubemap, and stores versions of it with increasing levels of blur to simulate different material roughnesses. The [ReflectionProbe] is used to create high-quality reflections at a low performance cost (when [member update_mode] is [constant UPDATE_ONCE]). [ReflectionProbe]s can be blended together and with the rest of the scene smoothly. [ReflectionProbe]s can also be combined with [VoxelGI], SDFGI ([member Environment.sdfgi_enabled]) and screen-space reflections ([member Environment.ssr_enabled]) to get more accurate reflections in specific areas. [ReflectionProbe]s render all objects within their [member cull_mask], so updating them can be quite expensive. It is best to update them once with the important static objects and then leave them as-is. [b]Note:[/b] Unlike [VoxelGI] and SDFGI, [ReflectionProbe]s only source their environment from a [WorldEnvironment] node. If you specify an [Environment] resource within a [Camera3D] node, it will be ignored by the [ReflectionProbe]. This can lead to incorrect lighting within the [ReflectionProbe]. + [b]Note:[/b] When using the Mobile rendering method, reflection probes will only correctly affect meshes whose visibility AABB intersects with the reflection probe's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the reflection probe may not be visible on the mesh. </description> <tutorials> <link title="Reflection probes">$DOCS_URL/tutorials/3d/reflection_probes.html</link> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index 59d36aefab..9dff742748 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -5,6 +5,7 @@ </brief_description> <description> A Spotlight is a type of [Light3D] node that emits lights in a specific direction, in the shape of a cone. The light is attenuated through the distance. This attenuation can be configured by changing the energy, radius and attenuation parameters of [Light3D]. + [b]Note:[/b] When using the Mobile or Compatibility rendering methods, spot lights will only correctly affect meshes whose visibility AABB intersects with the light's AABB. If using a shader to deform the mesh in a way that makes it go outside its AABB, [member GeometryInstance3D.extra_cull_margin] must be increased on the mesh. Otherwise, the light may not be visible on the mesh. </description> <tutorials> <link title="3D lights and shadows">$DOCS_URL/tutorials/3d/lights_and_shadows.html</link> diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 05d7ac4719..7f67651e62 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1128,6 +1128,12 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend state.canvas_instance_batches[state.current_batch_index].tex = mm->texture; state.canvas_instance_batches[state.current_batch_index].shader_variant = CanvasShaderGLES3::MODE_INSTANCED; + if (GLES3::MeshStorage::get_singleton()->multimesh_uses_colors(mm->multimesh)) { + state.instance_data_array[r_index].flags |= FLAGS_INSTANCING_HAS_COLORS; + } + if (GLES3::MeshStorage::get_singleton()->multimesh_uses_custom_data(mm->multimesh)) { + state.instance_data_array[r_index].flags |= FLAGS_INSTANCING_HAS_CUSTOM_DATA; + } } else if (c->type == Item::Command::TYPE_PARTICLES) { GLES3::ParticlesStorage *particles_storage = GLES3::ParticlesStorage::get_singleton(); GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 96278c3b92..2e3e6263ed 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -179,7 +179,8 @@ typedef void (*DEBUGPROCARB)(GLenum source, typedef void (*DebugMessageCallbackARB)(DEBUGPROCARB callback, const void *userParam); void RasterizerGLES3::initialize() { - print_line(vformat("OpenGL API %s - Compatibility - Using Device: %s - %s", RS::get_singleton()->get_video_adapter_api_version(), RS::get_singleton()->get_video_adapter_vendor(), RS::get_singleton()->get_video_adapter_name())); + // NVIDIA suffixes all GPU model names with "/PCIe/SSE2" in OpenGL (but not Vulkan). This isn't necessary to display nowadays, so it can be trimmed. + print_line(vformat("OpenGL API %s - Compatibility - Using Device: %s - %s", RS::get_singleton()->get_video_adapter_api_version(), RS::get_singleton()->get_video_adapter_vendor(), RS::get_singleton()->get_video_adapter_name().trim_suffix("/PCIe/SSE2"))); } void RasterizerGLES3::finalize() { diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 12a8302231..ea0a0b660d 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -162,9 +162,13 @@ void main() { vec2 uv = uv_attrib; #ifdef USE_INSTANCING - vec4 instance_color = vec4(unpackHalf2x16(instance_color_custom_data.x), unpackHalf2x16(instance_color_custom_data.y)); - color *= instance_color; - instance_custom = vec4(unpackHalf2x16(instance_color_custom_data.z), unpackHalf2x16(instance_color_custom_data.w)); + if (bool(read_draw_data_flags & FLAGS_INSTANCING_HAS_COLORS)) { + vec4 instance_color = vec4(unpackHalf2x16(instance_color_custom_data.x), unpackHalf2x16(instance_color_custom_data.y)); + color *= instance_color; + } + if (bool(read_draw_data_flags & FLAGS_INSTANCING_HAS_CUSTOM_DATA)) { + instance_custom = vec4(unpackHalf2x16(instance_color_custom_data.z), unpackHalf2x16(instance_color_custom_data.w)); + } #endif #else diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 3f8fddacff..36b34dd8a2 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1835,8 +1835,12 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible } if (multimesh->data_cache.size()) { - //there is a data cache.. + // There is a data cache, but we may need to update some sections. _multimesh_mark_all_dirty(multimesh, false, true); + int start = multimesh->visible_instances >= 0 ? multimesh->visible_instances : multimesh->instances; + for (int i = start; i < p_visible; i++) { + _multimesh_mark_dirty(multimesh, i, true); + } } multimesh->visible_instances = p_visible; @@ -1868,7 +1872,7 @@ void MeshStorage::_update_dirty_multimeshes() { if (multimesh->data_cache_used_dirty_regions > 32 || multimesh->data_cache_used_dirty_regions > visible_region_count / 2) { // If there too many dirty regions, or represent the majority of regions, just copy all, else transfer cost piles up too much glBindBuffer(GL_ARRAY_BUFFER, multimesh->buffer); - glBufferData(GL_ARRAY_BUFFER, MIN(visible_region_count * region_size, multimesh->instances * multimesh->stride_cache * sizeof(float)), data, GL_STATIC_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, MIN(visible_region_count * region_size, multimesh->instances * multimesh->stride_cache * sizeof(float)), data); glBindBuffer(GL_ARRAY_BUFFER, 0); } else { // Not that many regions? update them all diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index e48a5bb95d..bcb94b0f32 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -4876,6 +4876,10 @@ NavigationRegion3DGizmoPlugin::NavigationRegion3DGizmoPlugin() { create_material("face_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_face_disabled_color(), false, false, true); create_material("edge_material", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_color()); create_material("edge_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_geometry_edge_disabled_color()); + + Color baking_aabb_material_color = Color(0.8, 0.5, 0.7); + baking_aabb_material_color.a = 0.1; + create_material("baking_aabb_material", baking_aabb_material_color); } bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -4899,6 +4903,16 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { return; } + AABB baking_aabb = navigationmesh->get_filter_baking_aabb(); + if (baking_aabb.has_volume()) { + Vector3 baking_aabb_offset = navigationmesh->get_filter_baking_aabb_offset(); + + if (p_gizmo->is_selected()) { + Ref<Material> material = get_material("baking_aabb_material", p_gizmo); + p_gizmo->add_solid_box(material, baking_aabb.get_size(), baking_aabb.get_center() + baking_aabb_offset); + } + } + Vector<Vector3> vertices = navigationmesh->get_vertices(); const Vector3 *vr = vertices.ptr(); List<Face3> faces; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 36d1e54246..b001b4f766 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1415,9 +1415,6 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const // Recalculate orthogonalized scale without moving origin. if (p_orthogonal) { s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1)); - // The scaled_orthogonal() does not require orthogonal Basis, - // but it may make a bit skew by precision problems. - s.basis.orthogonalize(); } } @@ -4634,7 +4631,7 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { if (se->gizmo.is_valid()) { for (KeyValue<int, Transform3D> &GE : se->subgizmos) { Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. + Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 782e365138..7f43de3240 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1301,7 +1301,7 @@ void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gi Transform3D original_to_local; int parent_idx = skeleton->get_bone_parent(p_id); if (parent_idx >= 0) { - original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + original_to_local = skeleton->get_bone_global_pose(parent_idx); } Basis to_local = original_to_local.get_basis().inverse(); diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 32acad76aa..026b603683 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -565,14 +565,22 @@ </annotation> <annotation name="@rpc" qualifiers="vararg"> <return type="void" /> - <param index="0" name="mode" type="String" default="""" /> - <param index="1" name="sync" type="String" default="""" /> - <param index="2" name="transfer_mode" type="String" default="""" /> + <param index="0" name="mode" type="String" default=""authority"" /> + <param index="1" name="sync" type="String" default=""call_remote"" /> + <param index="2" name="transfer_mode" type="String" default=""unreliable"" /> <param index="3" name="transfer_channel" type="int" default="0" /> <description> Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. + The order of [code]mode[/code], [code]sync[/code] and [code]transfer_mode[/code] does not matter and all arguments can be omitted, but [code]transfer_channel[/code] always has to be the last argument. The accepted values for [code]mode[/code] are [code]"any_peer"[/code] or [code]"authority"[/code], for [code]sync[/code] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [code]transfer_mode[/code] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code]. [codeblock] - @rpc() + @rpc + func fn(): pass + + @rpc(any_peer, unreliable_ordered) + func fn_update_pos(): pass + + @rpc(authority, call_remote, unreliable, 0) # Equivalent to @rpc + func fn_default(): pass [/codeblock] </description> </annotation> diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index ed2dce471f..5c4ea60fd5 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -109,7 +109,7 @@ GDScriptParser::GDScriptParser() { // Warning annotations. register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. - register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0), true); #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index a61c5403b9..8598c32760 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using Godot.NativeInterop; @@ -174,7 +175,15 @@ namespace Godot.Collections } /// <summary> - /// Duplicates this <see cref="Array"/>. + /// Returns a copy of the <see cref="Array"/>. + /// If <paramref name="deep"/> is <see langword="true"/>, a deep copy if performed: + /// all nested arrays and dictionaries are duplicated and will not be shared with + /// the original array. If <see langword="false"/>, a shallow copy is made and + /// references to the original nested arrays and dictionaries are kept, so that + /// modifying a sub-array or dictionary in the copy will also impact those + /// referenced in the source array. Note that any <see cref="GodotObject"/> derived + /// 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 Array.</returns> @@ -187,7 +196,102 @@ namespace Godot.Collections } /// <summary> - /// Resizes this <see cref="Array"/> to the given size. + /// Assigns the given value to all elements in the array. This can typically be + /// used together with <see cref="Resize(int)"/> to create an array with a given + /// size and initialized elements. + /// Note: If <paramref name="value"/> is of a reference type (<see cref="GodotObject"/> + /// derived, <see cref="Array"/> or <see cref="Dictionary"/>, etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array(); + /// array.Resize(10); + /// array.Fill(0); // Initialize the 10 elements to 0. + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <param name="value">The value to fill the array with.</param> + public void Fill(Variant value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = (godot_variant)value.NativeVar; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_fill(ref self, variantValue); + } + + /// <summary> + /// Returns the maximum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="null"/> + /// is returned. + /// </summary> + /// <returns>The maximum value contained in the array.</returns> + public Variant Max() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_max(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="null"/> + /// is returned. + /// </summary> + /// <returns>The minimum value contained in the array.</returns> + public Variant Min() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_min(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Returns a random value from the target array. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array { 1, 2, 3, 4 }; + /// GD.Print(array.PickRandom()); // Prints either of the four numbers. + /// </code> + /// </example> + /// <returns>A random element from the array.</returns> + public Variant PickRandom() + { + godot_variant resVariant; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_pick_random(ref self, out resVariant); + return Variant.CreateTakingOwnershipOfDisposableValue(resVariant); + } + + /// <summary> + /// Compares this <see cref="Array"/> against the <paramref name="other"/> + /// <see cref="Array"/> recursively. Returns <see langword="true"/> if the + /// sizes and contents of the arrays are equal, <see langword="false"/> + /// otherwise. + /// </summary> + /// <param name="other">The other array to compare against.</param> + /// <returns> + /// <see langword="true"/> if the sizes and contents of the arrays are equal, + /// <see langword="false"/> otherwise. + /// </returns> + public bool RecursiveEqual(Array other) + { + var self = (godot_array)NativeValue; + var otherVariant = (godot_array)other.NativeValue; + return NativeFuncs.godotsharp_array_recursive_equal(ref self, otherVariant).ToBool(); + } + + /// <summary> + /// Resizes the array to contain a different number of elements. If the array + /// size is smaller, elements are cleared, if bigger, new elements are + /// <see langword="null"/>. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -203,7 +307,25 @@ namespace Godot.Collections } /// <summary> - /// Shuffles the contents of this <see cref="Array"/> into a random order. + /// Reverses the order of the elements in the array. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Reverse() + { + ThrowIfReadOnly(); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_reverse(ref self); + } + + /// <summary> + /// Shuffles the array such that the items will have a random order. + /// This method uses the global random number generator common to methods + /// such as <see cref="GD.Randi"/>. Call <see cref="GD.Randomize"/> to + /// ensure that a new seed will be used each time if you want + /// non-reproducible shuffling. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -217,7 +339,104 @@ namespace Godot.Collections } /// <summary> - /// Concatenates these two <see cref="Array"/>s. + /// Creates a shallow copy of a range of elements in the source <see cref="Array"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array Slice(int start) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// <summary> + /// Creates a shallow copy of a range of elements in the source <see cref="Array"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// -or- + /// <paramref name="length"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="length">The length of the range.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + // The Slice method must have this signature to get implicit Range support. + public Array Slice(int start, int length) + { + if (start < 0 || start > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (length < 0 || length > Count) + throw new ArgumentOutOfRangeException(nameof(start)); + + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// <summary> + /// Returns the slice of the <see cref="Array"/>, from <paramref name="start"/> + /// (inclusive) to <paramref name="end"/> (exclusive), as a new <see cref="Array"/>. + /// The absolute value of <paramref name="start"/> and <paramref name="end"/> + /// will be clamped to the array size. + /// If either <paramref name="start"/> or <paramref name="end"/> are negative, they + /// will be relative to the end of the array (i.e. <c>arr.GetSliceRange(0, -2)</c> + /// is a shorthand for <c>arr.GetSliceRange(0, arr.Count - 2)</c>). + /// If specified, <paramref name="step"/> is the relative index between source + /// elements. It can be negative, then <paramref name="start"/> must be higher than + /// <paramref name="end"/>. For example, <c>[0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2)</c> + /// returns <c>[5, 3]</c>. + /// If <paramref name="deep"/> is true, each element will be copied by value + /// rather than by reference. + /// </summary> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="end">The zero-based index at which the range ends.</param> + /// <param name="step">The relative index between source elements to take.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + godot_array newArray; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_slice(ref self, start, end, step, deep.ToGodotBool(), out newArray); + return CreateTakingOwnershipOfDisposableValue(newArray); + } + + /// <summary> + /// Sorts the array. + /// Note: The sorting algorithm used is not stable. This means that values + /// considered equal may have their order changed when using <see cref="Sort"/>. + /// Note: Strings are sorted in alphabetical order (as opposed to natural order). + /// This may lead to unexpected behavior when sorting an array of strings ending + /// with a sequence of numbers. + /// To sort with a custom predicate use + /// <see cref="Enumerable.OrderBy{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>. + /// </summary> + /// <example> + /// <code> + /// var strings = new Godot.Collections.Array { "string1", "string2", "string10", "string11" }; + /// strings.Sort(); + /// GD.Print(strings); // Prints [string1, string10, string11, string2] + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Sort() + { + ThrowIfReadOnly(); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_sort(ref self); + } + + /// <summary> + /// Concatenates two <see cref="Array"/>s together, with the <paramref name="right"/> + /// being added to the end of the <see cref="Array"/> specified in <paramref name="left"/>. + /// For example, <c>[1, 2] + [3, 4]</c> results in <c>[1, 2, 3, 4]</c>. /// </summary> /// <param name="left">The first array.</param> /// <param name="right">The second array.</param> @@ -253,6 +472,9 @@ namespace Godot.Collections /// <exception cref="InvalidOperationException"> /// The property is assigned and the array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> public unsafe Variant this[int index] { @@ -294,14 +516,146 @@ namespace Godot.Collections } /// <summary> - /// Checks if this <see cref="Array"/> contains the given item. + /// Adds the elements of the specified collection to the end of this <see cref="Array"/>. /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> + /// <param name="collection">Collection of <see cref="Variant"/> items to add.</param> + public void AddRange<[MustBeVariant] T>(IEnumerable<T> collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Array, we can add the items + // with a single interop call. + if (collection is Array array) + { + var self = (godot_array)NativeValue; + var collectionNative = (godot_array)array.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + if (collection is Array<T> typedArray) + { + var self = (godot_array)NativeValue; + var collectionNative = (godot_array)typedArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List<T>), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[count + i] = Variant.From(enumerator.Current); + } + + return; + } + + foreach (var item in collection) + { + Add(Variant.From(item)); + } + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(int, int, Variant)"/> on an + /// unsorted array results in unexpected behavior. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0. + /// -or- + /// <paramref name="count"/> is less than 0. + /// </exception> + /// <exception cref="ArgumentException"> + /// <paramref name="index"/> and <paramref name="count"/> do not denote + /// a valid range in the <see cref="Array"/>. + /// </exception> + /// <param name="index">The starting index of the range to search.</param> + /// <param name="count">The length of the range to search.</param> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(int index, int count, Variant item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_binary_search(ref self, index, count, variantValue); + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(Variant)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(Variant item) + { + return BinarySearch(0, Count, item); + } + + /// <summary> + /// Returns <see langword="true"/> if the array contains the given value. + /// </summary> + /// <example> + /// <code> + /// var arr = new Godot.Collections.Array { "inside", 7 }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // True + /// GD.Print(arr.Contains("7")); // False + /// </code> + /// </example> /// <param name="item">The <see cref="Variant"/> item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(Variant item) => IndexOf(item) != -1; /// <summary> - /// Erases all items from this <see cref="Array"/>. + /// Clears the array. This is the equivalent to using <see cref="Resize(int)"/> + /// with a size of <c>0</c> /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -309,27 +663,104 @@ namespace Godot.Collections public void Clear() => Resize(0); /// <summary> - /// Searches this <see cref="Array"/> for an item - /// and returns its index or -1 if not found. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. /// </summary> /// <param name="item">The <see cref="Variant"/> item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(Variant item) { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + godot_variant variantValue = (godot_variant)item.NativeVar; var self = (godot_array)NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> - /// Inserts a new item at a given position in the array. - /// The position must be a valid position of an existing item, - /// or the position at the end of the array. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(Variant item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, Count - 1); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (<c>pos == Count - 1</c>). /// Existing items will be moved to the right. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index to insert at.</param> /// <param name="item">The <see cref="Variant"/> item to insert.</param> public void Insert(int index, Variant item) @@ -367,11 +798,16 @@ namespace Godot.Collections } /// <summary> - /// Removes an element from this <see cref="Array"/> by index. + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// <see cref="Remove(Variant)"/> instead. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { @@ -424,6 +860,9 @@ 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="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </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) @@ -518,6 +957,9 @@ namespace Godot.Collections /// <summary> /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> internal void GetVariantBorrowElementAt(int index, out godot_variant elem) { if (index < 0 || index >= Count) @@ -658,6 +1100,97 @@ namespace Godot.Collections } /// <summary> + /// Assigns the given value to all elements in the array. This can typically be + /// used together with <see cref="Resize(int)"/> to create an array with a given + /// size and initialized elements. + /// Note: If <paramref name="value"/> is of a reference type (<see cref="GodotObject"/> + /// derived, <see cref="Array"/> or <see cref="Dictionary"/>, etc.) then the array + /// is filled with the references to the same object, i.e. no duplicates are + /// created. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array<int>(); + /// array.Resize(10); + /// array.Fill(0); // Initialize the 10 elements to 0. + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <param name="value">The value to fill the array with.</param> + public void Fill(T value) + { + ThrowIfReadOnly(); + + godot_variant variantValue = VariantUtils.CreateFrom(value); + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_fill(ref self, variantValue); + } + + /// <summary> + /// Returns the maximum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="default"/> + /// is returned. + /// </summary> + /// <returns>The maximum value contained in the array.</returns> + public T Max() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_max(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Returns the minimum value contained in the array if all elements are of + /// comparable types. If the elements can't be compared, <see langword="default"/> + /// is returned. + /// </summary> + /// <returns>The minimum value contained in the array.</returns> + public T Min() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_min(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Returns a random value from the target array. + /// </summary> + /// <example> + /// <code> + /// var array = new Godot.Collections.Array<int> { 1, 2, 3, 4 }; + /// GD.Print(array.PickRandom()); // Prints either of the four numbers. + /// </code> + /// </example> + /// <returns>A random element from the array.</returns> + public T PickRandom() + { + godot_variant resVariant; + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_pick_random(ref self, out resVariant); + return VariantUtils.ConvertTo<T>(resVariant); + } + + /// <summary> + /// Compares this <see cref="Array{T}"/> against the <paramref name="other"/> + /// <see cref="Array{T}"/> recursively. Returns <see langword="true"/> if the + /// sizes and contents of the arrays are equal, <see langword="false"/> + /// otherwise. + /// </summary> + /// <param name="other">The other array to compare against.</param> + /// <returns> + /// <see langword="true"/> if the sizes and contents of the arrays are equal, + /// <see langword="false"/> otherwise. + /// </returns> + public bool RecursiveEqual(Array<T> other) + { + return _underlyingArray.RecursiveEqual(other._underlyingArray); + } + + /// <summary> /// Resizes this <see cref="Array{T}"/> to the given size. /// </summary> /// <exception cref="InvalidOperationException"> @@ -671,7 +1204,22 @@ namespace Godot.Collections } /// <summary> - /// Shuffles the contents of this <see cref="Array{T}"/> into a random order. + /// Reverses the order of the elements in the array. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Reverse() + { + _underlyingArray.Reverse(); + } + + /// <summary> + /// Shuffles the array such that the items will have a random order. + /// This method uses the global random number generator common to methods + /// such as <see cref="GD.Randi"/>. Call <see cref="GD.Randomize"/> to + /// ensure that a new seed will be used each time if you want + /// non-reproducible shuffling. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -682,7 +1230,89 @@ namespace Godot.Collections } /// <summary> - /// Concatenates these two <see cref="Array{T}"/>s. + /// Creates a shallow copy of a range of elements in the source <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array<T> Slice(int start) + { + return GetSliceRange(start, Count, step: 1, deep: false); + } + + /// <summary> + /// Creates a shallow copy of a range of elements in the source <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="start"/> is less than 0 or greater than the array's size. + /// -or- + /// <paramref name="length"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="length">The length of the range.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + // The Slice method must have this signature to get implicit Range support. + public Array<T> Slice(int start, int length) + { + return GetSliceRange(start, start + length, step: 1, deep: false); + } + + /// <summary> + /// Returns the slice of the <see cref="Array{T}"/>, from <paramref name="start"/> + /// (inclusive) to <paramref name="end"/> (exclusive), as a new <see cref="Array{T}"/>. + /// The absolute value of <paramref name="start"/> and <paramref name="end"/> + /// will be clamped to the array size. + /// If either <paramref name="start"/> or <paramref name="end"/> are negative, they + /// will be relative to the end of the array (i.e. <c>arr.GetSliceRange(0, -2)</c> + /// is a shorthand for <c>arr.GetSliceRange(0, arr.Count - 2)</c>). + /// If specified, <paramref name="step"/> is the relative index between source + /// elements. It can be negative, then <paramref name="start"/> must be higher than + /// <paramref name="end"/>. For example, <c>[0, 1, 2, 3, 4, 5].GetSliceRange(5, 1, -2)</c> + /// returns <c>[5, 3]</c>. + /// If <paramref name="deep"/> is true, each element will be copied by value + /// rather than by reference. + /// </summary> + /// <param name="start">The zero-based index at which the range starts.</param> + /// <param name="end">The zero-based index at which the range ends.</param> + /// <param name="step">The relative index between source elements to take.</param> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new array that contains the elements inside the slice range.</returns> + public Array<T> GetSliceRange(int start, int end, int step = 1, bool deep = false) + { + return new Array<T>(_underlyingArray.GetSliceRange(start, end, step, deep)); + } + + /// <summary> + /// Sorts the array. + /// Note: The sorting algorithm used is not stable. This means that values + /// considered equal may have their order changed when using <see cref="Sort"/>. + /// Note: Strings are sorted in alphabetical order (as opposed to natural order). + /// This may lead to unexpected behavior when sorting an array of strings ending + /// with a sequence of numbers. + /// To sort with a custom predicate use + /// <see cref="Enumerable.OrderBy{TSource, TKey}(IEnumerable{TSource}, Func{TSource, TKey})"/>. + /// </summary> + /// <example> + /// <code> + /// var strings = new Godot.Collections.Array<string> { "string1", "string2", "string10", "string11" }; + /// strings.Sort(); + /// GD.Print(strings); // Prints [string1, string10, string11, string2] + /// </code> + /// </example> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + public void Sort() + { + _underlyingArray.Sort(); + } + + /// <summary> + /// Concatenates two <see cref="Array{T}"/>s together, with the <paramref name="right"/> + /// being added to the end of the <see cref="Array{T}"/> specified in <paramref name="left"/>. + /// For example, <c>[1, 2] + [3, 4]</c> results in <c>[1, 2, 3, 4]</c>. /// </summary> /// <param name="left">The first array.</param> /// <param name="right">The second array.</param> @@ -706,12 +1336,15 @@ namespace Godot.Collections // IList<T> /// <summary> - /// Returns the value at the given <paramref name="index"/>. + /// Returns the item at the given <paramref name="index"/>. /// </summary> /// <exception cref="InvalidOperationException"> /// The property is assigned and the array is read-only. /// </exception> - /// <value>The value at the given <paramref name="index"/>.</value> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> public unsafe T this[int index] { get @@ -735,29 +1368,106 @@ namespace Godot.Collections } /// <summary> - /// Searches this <see cref="Array{T}"/> for an item - /// and returns its index or -1 if not found. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. /// </summary> - /// <param name="item">The item to search for.</param> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> public int IndexOf(T item) { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> - /// Inserts a new item at a given position in the <see cref="Array{T}"/>. - /// The position must be a valid position of an existing item, - /// or the position at the end of the array. + /// Searches the array for a value and returns its index or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(T item, int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item) + { + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, Count - 1); + } + + /// <summary> + /// Searches the array for a value in reverse order and returns its index + /// or <c>-1</c> if not found. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <param name="index">The initial search index to start from.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int LastIndexOf(Variant item, int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + godot_variant variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_last_index_of(ref self, variantValue, index); + } + + /// <summary> + /// Inserts a new element at a given position in the array. The position + /// must be valid, or at the end of the array (<c>pos == Count - 1</c>). /// Existing items will be moved to the right. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index to insert at.</param> - /// <param name="item">The item to insert.</param> + /// <param name="item">The <see cref="Variant"/> item to insert.</param> public void Insert(int index, T item) { ThrowIfReadOnly(); @@ -771,11 +1481,16 @@ namespace Godot.Collections } /// <summary> - /// Removes an element from this <see cref="Array{T}"/> by index. + /// Removes an element from the array by index. + /// To remove an element by searching for its value, use + /// <see cref="Remove(T)"/> instead. /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0 or greater than the array's size. + /// </exception> /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { @@ -814,8 +1529,7 @@ namespace Godot.Collections /// <exception cref="InvalidOperationException"> /// The array is read-only. /// </exception> - /// <param name="item">The item to add.</param> - /// <returns>The new size after adding the item.</returns> + /// <param name="item">The <see cref="Variant"/> item to add.</param> public void Add(T item) { ThrowIfReadOnly(); @@ -826,7 +1540,130 @@ namespace Godot.Collections } /// <summary> - /// Erases all items from this <see cref="Array{T}"/>. + /// Adds the elements of the specified collection to the end of this <see cref="Array{T}"/>. + /// </summary> + /// <exception cref="InvalidOperationException"> + /// The array is read-only. + /// </exception> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="collection"/> is <see langword="null"/>. + /// </exception> + /// <param name="collection">Collection of <see cref="Variant"/> items to add.</param> + public void AddRange(IEnumerable<T> collection) + { + ThrowIfReadOnly(); + + if (collection == null) + throw new ArgumentNullException(nameof(collection), "Value cannot be null."); + + // If the collection is another Godot Array, we can add the items + // with a single interop call. + if (collection is Array array) + { + var self = (godot_array)_underlyingArray.NativeValue; + var collectionNative = (godot_array)array.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + if (collection is Array<T> typedArray) + { + var self = (godot_array)_underlyingArray.NativeValue; + var collectionNative = (godot_array)typedArray._underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add_range(ref self, collectionNative); + return; + } + + // If we can retrieve the count of the collection without enumerating it + // (e.g.: the collections is a List<T>), use it to resize the array once + // instead of growing it as we add items. + if (collection.TryGetNonEnumeratedCount(out int count)) + { + Resize(Count + count); + + using var enumerator = collection.GetEnumerator(); + + for (int i = 0; i < count; i++) + { + enumerator.MoveNext(); + this[count + i] = enumerator.Current; + } + + return; + } + + foreach (var item in collection) + { + Add(item); + } + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(int, int, T)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="index"/> is less than 0. + /// -or- + /// <paramref name="count"/> is less than 0. + /// </exception> + /// <exception cref="ArgumentException"> + /// <paramref name="index"/> and <paramref name="count"/> do not denote + /// a valid range in the <see cref="Array{T}"/>. + /// </exception> + /// <param name="index">The starting index of the range to search.</param> + /// <param name="count">The length of the range to search.</param> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(int index, int count, T item) + { + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), "index cannot be negative."); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), "count cannot be negative."); + if (Count - index < count) + throw new ArgumentException("length is out of bounds or count is greater than the number of elements."); + + if (Count == 0) + { + // Special case for empty array to avoid an interop call. + return -1; + } + + using var variantValue = VariantUtils.CreateFrom(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_binary_search(ref self, index, count, variantValue); + } + + /// <summary> + /// Finds the index of an existing value using binary search. + /// If the value is not present in the array, it returns the bitwise + /// complement of the insertion index that maintains sorting order. + /// Note: Calling <see cref="BinarySearch(T)"/> on an unsorted + /// array results in unexpected behavior. + /// </summary> + /// <param name="item">The object to locate.</param> + /// <returns> + /// The index of the item in the array, if <paramref name="item"/> is found; + /// otherwise, a negative number that is the bitwise complement of the index + /// of the next element that is larger than <paramref name="item"/> or, if + /// there is no larger element, the bitwise complement of <see cref="Count"/>. + /// </returns> + public int BinarySearch(T item) + { + return BinarySearch(0, Count, item); + } + + /// <summary> + /// Clears the array. This is the equivalent to using <see cref="Resize(int)"/> + /// with a size of <c>0</c> /// </summary> /// <exception cref="InvalidOperationException"> /// The array is read-only. @@ -837,8 +1674,17 @@ namespace Godot.Collections } /// <summary> - /// Checks if this <see cref="Array{T}"/> contains the given item. + /// Returns <see langword="true"/> if the array contains the given value. /// </summary> + /// <example> + /// <code> + /// var arr = new Godot.Collections.Array<string> { "inside", "7" }; + /// GD.Print(arr.Contains("inside")); // True + /// GD.Print(arr.Contains("outside")); // False + /// GD.Print(arr.Contains(7)); // False + /// GD.Print(arr.Contains("7")); // True + /// </code> + /// </example> /// <param name="item">The item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> public bool Contains(T item) => IndexOf(item) != -1; @@ -847,6 +1693,9 @@ 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="ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size. + /// </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) @@ -876,7 +1725,7 @@ namespace Godot.Collections } /// <summary> - /// Removes the first occurrence of the specified value + /// Removes the first occurrence of the specified <paramref name="item"/> /// from this <see cref="Array{T}"/>. /// </summary> /// <exception cref="InvalidOperationException"> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 8ac18c4314..3d72ee0036 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -365,21 +365,44 @@ namespace Godot.NativeInterop public static partial int godotsharp_array_add(ref godot_array p_self, in godot_variant p_item); + public static partial int godotsharp_array_add_range(ref godot_array p_self, in godot_array p_collection); + + public static partial int godotsharp_array_binary_search(ref godot_array p_self, int p_index, int p_count, in godot_variant p_value); + public static partial void godotsharp_array_duplicate(ref godot_array p_self, godot_bool p_deep, out godot_array r_dest); - public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item); + public static partial void godotsharp_array_fill(ref godot_array p_self, in godot_variant p_value); + + public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item, int p_index = 0); public static partial void godotsharp_array_insert(ref godot_array p_self, int p_index, in godot_variant p_item); + public static partial int godotsharp_array_last_index_of(ref godot_array p_self, in godot_variant p_item, int p_index); + + public static partial void godotsharp_array_make_read_only(ref godot_array p_self); + + public static partial void godotsharp_array_max(ref godot_array p_self, out godot_variant r_value); + + public static partial void godotsharp_array_min(ref godot_array p_self, out godot_variant r_value); + + public static partial void godotsharp_array_pick_random(ref godot_array p_self, out godot_variant r_value); + + public static partial godot_bool godotsharp_array_recursive_equal(ref godot_array p_self, in godot_array p_other); + public static partial void godotsharp_array_remove_at(ref godot_array p_self, int p_index); public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size); - public static partial void godotsharp_array_make_read_only(ref godot_array p_self); + public static partial void godotsharp_array_reverse(ref godot_array p_self); public static partial void godotsharp_array_shuffle(ref godot_array p_self); + public static partial void godotsharp_array_slice(ref godot_array p_self, int p_start, int p_end, + int p_step, godot_bool p_deep, out godot_array r_dest); + + public static partial void godotsharp_array_sort(ref godot_array p_self); + public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); // Dictionary diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index a90b07620c..306ac333eb 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -992,18 +992,78 @@ int32_t godotsharp_array_add(Array *p_self, const Variant *p_item) { return p_self->size(); } +int32_t godotsharp_array_add_range(Array *p_self, const Array *p_collection) { + p_self->append_array(*p_collection); + return p_self->size(); +} + +int32_t godotsharp_array_binary_search(const Array *p_self, int32_t p_index, int32_t p_length, const Variant *p_value) { + ERR_FAIL_COND_V(p_index < 0, -1); + ERR_FAIL_COND_V(p_length < 0, -1); + ERR_FAIL_COND_V(p_self->size() - p_index < p_length, -1); + + const Variant &value = *p_value; + const Array &array = *p_self; + + int lo = p_index; + int hi = p_index + p_length - 1; + while (lo <= hi) { + int mid = lo + ((hi - lo) >> 1); + const Variant &mid_item = array[mid]; + + if (mid_item == value) { + return mid; + } + if (mid_item < value) { + lo = mid + 1; + } else { + hi = mid - 1; + } + } + + return ~lo; +} + void godotsharp_array_duplicate(const Array *p_self, bool p_deep, Array *r_dest) { memnew_placement(r_dest, Array(p_self->duplicate(p_deep))); } -int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item) { - return p_self->find(*p_item); +void godotsharp_array_fill(Array *p_self, const Variant *p_value) { + p_self->fill(*p_value); +} + +int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item, int32_t p_index = 0) { + return p_self->find(*p_item, p_index); } void godotsharp_array_insert(Array *p_self, int32_t p_index, const Variant *p_item) { p_self->insert(p_index, *p_item); } +int32_t godotsharp_array_last_index_of(const Array *p_self, const Variant *p_item, int32_t p_index) { + return p_self->rfind(*p_item, p_index); +} + +void godotsharp_array_make_read_only(Array *p_self) { + p_self->make_read_only(); +} + +void godotsharp_array_max(const Array *p_self, Variant *r_value) { + *r_value = p_self->max(); +} + +void godotsharp_array_min(const Array *p_self, Variant *r_value) { + *r_value = p_self->min(); +} + +void godotsharp_array_pick_random(const Array *p_self, Variant *r_value) { + *r_value = p_self->pick_random(); +} + +bool godotsharp_array_recursive_equal(const Array *p_self, const Array *p_other) { + return p_self->recursive_equal(*p_other, 0); +} + void godotsharp_array_remove_at(Array *p_self, int32_t p_index) { p_self->remove_at(p_index); } @@ -1012,14 +1072,22 @@ int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) { return (int32_t)p_self->resize(p_new_size); } -void godotsharp_array_make_read_only(Array *p_self) { - p_self->make_read_only(); +void godotsharp_array_reverse(Array *p_self) { + p_self->reverse(); } void godotsharp_array_shuffle(Array *p_self) { p_self->shuffle(); } +void godotsharp_array_slice(Array *p_self, int32_t p_start, int32_t p_end, int32_t p_step, bool p_deep, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->slice(p_start, p_end, p_step, p_deep))); +} + +void godotsharp_array_sort(Array *p_self) { + p_self->sort(); +} + void godotsharp_array_to_string(const Array *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } @@ -1450,13 +1518,24 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_array_destroy, (void *)godotsharp_dictionary_destroy, (void *)godotsharp_array_add, + (void *)godotsharp_array_add_range, + (void *)godotsharp_array_binary_search, (void *)godotsharp_array_duplicate, + (void *)godotsharp_array_fill, (void *)godotsharp_array_index_of, (void *)godotsharp_array_insert, + (void *)godotsharp_array_last_index_of, + (void *)godotsharp_array_make_read_only, + (void *)godotsharp_array_max, + (void *)godotsharp_array_min, + (void *)godotsharp_array_pick_random, + (void *)godotsharp_array_recursive_equal, (void *)godotsharp_array_remove_at, (void *)godotsharp_array_resize, - (void *)godotsharp_array_make_read_only, + (void *)godotsharp_array_reverse, (void *)godotsharp_array_shuffle, + (void *)godotsharp_array_slice, + (void *)godotsharp_array_sort, (void *)godotsharp_array_to_string, (void *)godotsharp_dictionary_try_get_value, (void *)godotsharp_dictionary_set_value, diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 7e1b42c80b..1d13f07b12 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -278,7 +278,7 @@ bool NavigationMesh::get_filter_walkable_low_height_spans() const { void NavigationMesh::set_filter_baking_aabb(const AABB &p_aabb) { filter_baking_aabb = p_aabb; - notify_property_list_changed(); + emit_changed(); } AABB NavigationMesh::get_filter_baking_aabb() const { @@ -287,7 +287,7 @@ AABB NavigationMesh::get_filter_baking_aabb() const { void NavigationMesh::set_filter_baking_aabb_offset(const Vector3 &p_aabb_offset) { filter_baking_aabb_offset = p_aabb_offset; - notify_property_list_changed(); + emit_changed(); } Vector3 NavigationMesh::get_filter_baking_aabb_offset() const { diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 6894498af5..96618c3352 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1805,8 +1805,12 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible } if (multimesh->data_cache.size()) { - //there is a data cache.. + // There is a data cache, but we may need to update some sections. _multimesh_mark_all_dirty(multimesh, false, true); + int start = multimesh->visible_instances >= 0 ? multimesh->visible_instances : multimesh->instances; + for (int i = start; i < p_visible; i++) { + _multimesh_mark_dirty(multimesh, i, true); + } } multimesh->visible_instances = p_visible; diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h index 2ec75b03ef..c3eec83d44 100644 --- a/servers/rendering/renderer_scene_cull.h +++ b/servers/rendering/renderer_scene_cull.h @@ -485,6 +485,7 @@ public: singleton->_instance_queue_update(instance, true, false); } break; + case Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES: case Dependency::DEPENDENCY_CHANGED_MATERIAL: { singleton->_instance_queue_update(instance, false, true); } break; @@ -496,9 +497,6 @@ public: case Dependency::DEPENDENCY_CHANGED_REFLECTION_PROBE: { singleton->_instance_queue_update(instance, true, true); } break; - case Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES: { - //ignored - } break; case Dependency::DEPENDENCY_CHANGED_LIGHT_SOFT_SHADOW_AND_PROJECTOR: { //requires repairing if (instance->indexer_id.is_valid()) { |