summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml16
-rw-r--r--modules/gdscript/gdscript.cpp14
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp216
-rw-r--r--modules/gdscript/gdscript_analyzer.h4
-rw-r--r--modules/gdscript/gdscript_editor.cpp33
-rw-r--r--modules/gdscript/gdscript_function.cpp9
-rw-r--r--modules/gdscript/gdscript_function.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp69
-rw-r--r--modules/gdscript/gdscript_parser.h13
-rw-r--r--modules/gdscript/gdscript_vm.cpp29
-rw-r--r--modules/gdscript/gdscript_warning.cpp31
-rw-r--r--modules/gdscript/gdscript_warning.h49
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd18
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs907
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs31
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs34
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs2
-rw-r--r--modules/mono/glue/runtime_interop.cpp99
-rw-r--r--modules/navigation/godot_navigation_server.cpp34
-rw-r--r--modules/navigation/godot_navigation_server.h4
-rw-r--r--modules/navigation/nav_agent.cpp (renamed from modules/navigation/rvo_agent.cpp)14
-rw-r--r--modules/navigation/nav_agent.h (renamed from modules/navigation/rvo_agent.h)10
-rw-r--r--modules/navigation/nav_map.cpp18
-rw-r--r--modules/navigation/nav_map.h20
-rw-r--r--modules/openxr/SCsub1
-rw-r--r--modules/openxr/extensions/openxr_ml2_controller_extension.cpp71
-rw-r--r--modules/openxr/extensions/openxr_ml2_controller_extension.h48
-rw-r--r--modules/openxr/openxr_api.cpp62
-rw-r--r--modules/openxr/openxr_api.h11
-rw-r--r--modules/openxr/openxr_interface.cpp54
-rw-r--r--modules/openxr/openxr_interface.h4
-rw-r--r--modules/openxr/openxr_util.cpp271
-rw-r--r--modules/openxr/openxr_util.h1
-rw-r--r--modules/openxr/register_types.cpp2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp50
-rw-r--r--modules/text_server_adv/text_server_adv.h3
64 files changed, 1984 insertions, 456 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 32acad76aa..e05b17168d 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="&quot;&quot;" />
- <param index="1" name="sync" type="String" default="&quot;&quot;" />
- <param index="2" name="transfer_mode" type="String" default="&quot;&quot;" />
+ <param index="0" name="mode" type="String" default="&quot;authority&quot;" />
+ <param index="1" name="sync" type="String" default="&quot;call_remote&quot;" />
+ <param index="2" name="transfer_mode" type="String" default="&quot;unreliable&quot;" />
<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.cpp b/modules/gdscript/gdscript.cpp
index 8324cb0fe0..a876229276 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1496,7 +1496,12 @@ GDScript::~GDScript() {
// Order matters since clearing the stack may already cause
// the GDScriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
- E->self()->_clear_stack();
+ GDScriptFunctionState *state = E->self();
+ ObjectID state_id = state->get_instance_id();
+ state->_clear_connections();
+ if (ObjectDB::get_instance(state_id)) {
+ state->_clear_stack();
+ }
}
}
@@ -1920,7 +1925,12 @@ GDScriptInstance::~GDScriptInstance() {
// Order matters since clearing the stack may already cause
// the GDSCriptFunctionState to be destroyed and thus removed from the list.
pending_func_states.remove(E);
- E->self()->_clear_stack();
+ GDScriptFunctionState *state = E->self();
+ ObjectID state_id = state->get_instance_id();
+ state->_clear_connections();
+ if (ObjectDB::get_instance(state_id)) {
+ state->_clear_stack();
+ }
}
if (script.is_valid() && owner) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 77fc1a209d..cafc7328e0 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -138,13 +138,25 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
}
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) {
- GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta);
+ // Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
+ StringName native_base = p_native_class;
+ while (true && native_base != StringName()) {
+ if (ClassDB::has_enum(native_base, p_enum_name, true)) {
+ break;
+ }
+ native_base = ClassDB::get_parent_class_nocheck(native_base);
+ }
+
+ GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta);
+ if (p_meta) {
+ type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries
+ }
List<StringName> enum_values;
- ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values);
+ ClassDB::get_enum_constants(native_base, p_enum_name, &enum_values, true);
for (const StringName &E : enum_values) {
- type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E);
+ type.enum_values[E] = ClassDB::get_integer_constant(native_base, E);
}
return type;
@@ -782,6 +794,22 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
resolving_datatype.kind = GDScriptParser::DataType::RESOLVING;
{
+#ifdef DEBUG_ENABLED
+ HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
+ GDScriptParser::Node *member_node = member.get_source_node();
+ if (member_node && member_node->type != GDScriptParser::Node::ANNOTATION) {
+ // Apply @warning_ignore annotations before resolving member.
+ for (GDScriptParser::AnnotationNode *&E : member_node->annotations) {
+ if (E->name == SNAME("@warning_ignore")) {
+ resolve_annotation(E);
+ E->apply(parser, member.variable);
+ }
+ }
+ for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) {
+ parser->ignored_warnings.insert(ignored_warning);
+ }
+ }
+#endif
switch (member.type) {
case GDScriptParser::ClassNode::Member::VARIABLE: {
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
@@ -790,9 +818,48 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
- resolve_annotation(E);
- E->apply(parser, member.variable);
+ if (E->name != SNAME("@warning_ignore")) {
+ resolve_annotation(E);
+ E->apply(parser, member.variable);
+ }
+ }
+#ifdef DEBUG_ENABLED
+ if (member.variable->exported && member.variable->onready) {
+ parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT);
+ }
+ if (member.variable->initializer) {
+ // Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed.
+ // This could be improved by traversing the expression fully and checking the presence of get_node at any level.
+ if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) {
+ GDScriptParser::Node *expr = member.variable->initializer;
+ if (expr->type == GDScriptParser::Node::CAST) {
+ expr = static_cast<GDScriptParser::CastNode *>(expr)->operand;
+ }
+ bool is_get_node = expr->type == GDScriptParser::Node::GET_NODE;
+ bool is_using_shorthand = is_get_node;
+ if (!is_get_node && expr->type == GDScriptParser::Node::CALL) {
+ is_using_shorthand = false;
+ GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(expr);
+ if (call->function_name == SNAME("get_node")) {
+ switch (call->get_callee_type()) {
+ case GDScriptParser::Node::IDENTIFIER: {
+ is_get_node = true;
+ } break;
+ case GDScriptParser::Node::SUBSCRIPT: {
+ GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(call->callee);
+ is_get_node = subscript->is_attribute && subscript->base->type == GDScriptParser::Node::SELF;
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ if (is_get_node) {
+ parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, is_using_shorthand ? "$" : "get_node()");
+ }
+ }
}
+#endif
} break;
case GDScriptParser::ClassNode::Member::CONSTANT: {
check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant);
@@ -878,6 +945,10 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
}
} break;
case GDScriptParser::ClassNode::Member::FUNCTION:
+ for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, member.function);
+ }
resolve_function_signature(member.function, p_source);
break;
case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
@@ -931,6 +1002,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
ERR_PRINT("Trying to resolve undefined member.");
break;
}
+#ifdef DEBUG_ENABLED
+ parser->ignored_warnings = previously_ignored_warnings;
+#endif
}
parser->current_class = previous_class;
@@ -1059,19 +1133,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
resolve_annotation(E);
E->apply(parser, member.function);
}
-
-#ifdef DEBUG_ENABLED
- HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes;
- for (uint32_t ignored_warning : member.function->ignored_warnings) {
- parser->ignored_warning_codes.insert(ignored_warning);
- }
-#endif // DEBUG_ENABLED
-
resolve_function_body(member.function);
-
-#ifdef DEBUG_ENABLED
- parser->ignored_warning_codes = previously_ignored;
-#endif // DEBUG_ENABLED
} 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) {
@@ -1102,9 +1164,9 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
#ifdef DEBUG_ENABLED
- HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes;
- for (uint32_t ignored_warning : member.function->ignored_warnings) {
- parser->ignored_warning_codes.insert(ignored_warning);
+ HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
+ for (GDScriptWarning::Code ignored_warning : member.variable->ignored_warnings) {
+ parser->ignored_warnings.insert(ignored_warning);
}
if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
@@ -1179,7 +1241,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
}
}
#ifdef DEBUG_ENABLED
- parser->ignored_warning_codes = previously_ignored;
+ parser->ignored_warnings = previously_ignored_warnings;
#endif // DEBUG_ENABLED
}
}
@@ -1289,6 +1351,11 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
+ if (p_annotation->is_resolved) {
+ return;
+ }
+ p_annotation->is_resolved = true;
+
const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info;
const List<PropertyInfo>::Element *E = annotation_info.arguments.front();
@@ -1355,6 +1422,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
}
p_function->resolved_signature = true;
+#ifdef DEBUG_ENABLED
+ HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
+ for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) {
+ parser->ignored_warnings.insert(ignored_warning);
+ }
+#endif
+
GDScriptParser::FunctionNode *previous_function = parser->current_function;
parser->current_function = p_function;
@@ -1421,7 +1495,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
int default_par_count = 0;
bool is_static = false;
bool is_vararg = false;
- if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) {
+ 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, is_static, is_vararg, &native_base)) {
bool valid = p_function->is_static == is_static;
valid = valid && parent_return_type == p_function->get_datatype();
@@ -1447,8 +1522,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
parameter = "Variant";
}
parent_signature += parameter;
- if (j == parameters_types.size() - default_par_count) {
- parent_signature += " = default";
+ if (j >= parameters_types.size() - default_par_count) {
+ parent_signature += " = <default>";
}
j++;
@@ -1464,6 +1539,11 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function);
}
+#ifdef DEBUG_ENABLED
+ if (native_base != StringName()) {
+ parser->push_warning(p_function, GDScriptWarning::NATIVE_METHOD_OVERRIDE, function_name, native_base);
+ }
+#endif
}
#endif // TOOLS_ENABLED
}
@@ -1472,6 +1552,9 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
p_function->set_datatype(prev_datatype);
}
+#ifdef DEBUG_ENABLED
+ parser->ignored_warnings = previously_ignored_warnings;
+#endif
parser->current_function = previous_function;
}
@@ -1481,6 +1564,13 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
}
p_function->resolved_body = true;
+#ifdef DEBUG_ENABLED
+ HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
+ for (GDScriptWarning::Code ignored_warning : p_function->ignored_warnings) {
+ parser->ignored_warnings.insert(ignored_warning);
+ }
+#endif
+
GDScriptParser::FunctionNode *previous_function = parser->current_function;
parser->current_function = p_function;
@@ -1498,6 +1588,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
}
}
+#ifdef DEBUG_ENABLED
+ parser->ignored_warnings = previously_ignored_warnings;
+#endif
parser->current_function = previous_function;
}
@@ -1538,16 +1631,16 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
}
#ifdef DEBUG_ENABLED
- HashSet<uint32_t> previously_ignored = parser->ignored_warning_codes;
- for (uint32_t ignored_warning : stmt->ignored_warnings) {
- parser->ignored_warning_codes.insert(ignored_warning);
+ HashSet<GDScriptWarning::Code> previously_ignored_warnings = parser->ignored_warnings;
+ for (GDScriptWarning::Code ignored_warning : stmt->ignored_warnings) {
+ parser->ignored_warnings.insert(ignored_warning);
}
#endif // DEBUG_ENABLED
resolve_node(stmt);
#ifdef DEBUG_ENABLED
- parser->ignored_warning_codes = previously_ignored;
+ parser->ignored_warnings = previously_ignored_warnings;
#endif // DEBUG_ENABLED
decide_suite_type(p_suite, stmt);
@@ -1599,6 +1692,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) {
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
}
+#ifdef DEBUG_ENABLED
+ if (initializer_type.is_hard_type() && initializer_type.is_variant()) {
+ parser->push_warning(p_assignable, GDScriptWarning::INFERENCE_ON_VARIANT, p_kind);
+ }
+#endif
} else {
if (!initializer_type.is_set()) {
push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer);
@@ -2240,6 +2338,28 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) {
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
+ return;
+ } else if (assignee_type.is_read_only) {
+ push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
+ return;
+ } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) {
+ GDScriptParser::SubscriptNode *sub = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee);
+ while (sub) {
+ const GDScriptParser::DataType &base_type = sub->base->datatype;
+ if (base_type.is_hard_type() && base_type.is_read_only) {
+ if (base_type.kind == GDScriptParser::DataType::BUILTIN && !Variant::is_type_shared(base_type.builtin_type)) {
+ push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee);
+ return;
+ }
+ } else {
+ break;
+ }
+ if (sub->base->type == GDScriptParser::Node::SUBSCRIPT) {
+ sub = static_cast<GDScriptParser::SubscriptNode *>(sub->base);
+ } else {
+ sub = nullptr;
+ }
+ }
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
@@ -2931,7 +3051,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
// Enums do not have functions other than the built-in dictionary ones.
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
- push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee);
+ if (base_type.builtin_type == Variant::DICTIONARY) {
+ push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee);
+ } else {
+ push_error(vformat(R"*(The native enum "%s" does not behave like Dictionary and does not have methods of its own.)*", base_type.enum_type), p_call->callee);
+ }
} else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else.
GDScriptParser::IdentifierNode *callee_id;
if (callee_type == GDScriptParser::Node::IDENTIFIER) {
@@ -3329,7 +3453,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
StringName getter_name = ClassDB::get_property_getter(native, name);
MethodBind *getter = ClassDB::get_method(native, getter_name);
if (getter != nullptr) {
- p_identifier->set_datatype(type_from_property(getter->get_return_info()));
+ bool has_setter = ClassDB::get_property_setter(native, name) != StringName();
+ p_identifier->set_datatype(type_from_property(getter->get_return_info(), false, !has_setter));
p_identifier->source = GDScriptParser::IdentifierNode::INHERITED_VARIABLE;
}
return;
@@ -4037,6 +4162,10 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
Variant GDScriptAnalyzer::make_expression_reduced_value(GDScriptParser::ExpressionNode *p_expression, bool &is_reduced) {
Variant value;
+ if (p_expression == nullptr) {
+ return value;
+ }
+
if (p_expression->is_constant) {
is_reduced = true;
value = p_expression->reduced_value;
@@ -4101,6 +4230,10 @@ Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::Dictiona
}
Variant GDScriptAnalyzer::make_subscript_reduced_value(GDScriptParser::SubscriptNode *p_subscript, bool &is_reduced) {
+ if (p_subscript->base == nullptr || p_subscript->index == nullptr) {
+ return Variant();
+ }
+
bool is_base_value_reduced = false;
Variant base_value = make_expression_reduced_value(p_subscript->base, is_base_value_reduced);
if (!is_base_value_reduced) {
@@ -4260,8 +4393,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars
return result;
}
-GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg) const {
+GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg, bool p_is_readonly) const {
GDScriptParser::DataType result;
+ result.is_read_only = p_is_readonly;
result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
if (p_property.type == Variant::NIL && (p_is_arg || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
// Variant
@@ -4302,15 +4436,27 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(elem_type);
+ } else if (p_property.type == Variant::INT) {
+ // Check if it's enum.
+ if (p_property.class_name != StringName()) {
+ Vector<String> names = String(p_property.class_name).split(".");
+ if (names.size() == 2) {
+ result = make_native_enum_type(names[1], names[0], false);
+ result.is_constant = false;
+ }
+ }
}
}
return result;
}
-bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) {
+bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class) {
r_static = false;
r_vararg = false;
r_default_arg_count = 0;
+ if (r_native_class) {
+ *r_native_class = StringName();
+ }
StringName function_name = p_function;
bool was_enum = false;
@@ -4445,6 +4591,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
if (valid && Engine::get_singleton()->has_singleton(base_native)) {
r_static = true;
}
+#ifdef DEBUG_ENABLED
+ MethodBind *native_method = ClassDB::get_method(base_native, function_name);
+ if (native_method && r_native_class) {
+ *r_native_class = native_method->get_instance_class();
+ }
+#endif
return valid;
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index b51564fb0a..a4c84db6b9 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -115,9 +115,9 @@ class GDScriptAnalyzer {
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
- GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const;
+ GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
- bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
+ bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class = nullptr);
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index f88ac581ca..4e7d278aab 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -800,6 +800,15 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
warning.insert_text = warning.display.quote(p_quote_style);
r_result.insert(warning.display, warning);
}
+ } else if (p_annotation->name == SNAME("@rpc")) {
+ if (p_argument == 0 || p_argument == 1 || p_argument == 2) {
+ static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" };
+ for (int i = 0; i < 7; i++) {
+ ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ option.insert_text = option.display.quote(p_quote_style);
+ r_result.insert(option.display, option);
+ }
+ }
}
}
@@ -1953,17 +1962,19 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
case GDScriptParser::DataType::CLASS:
if (base_type.class_type->has_function(p_context.current_function->identifier->name)) {
GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function;
- const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]];
- if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
- id_type = parameter->get_datatype();
- }
- if (parameter->initializer) {
- GDScriptParser::CompletionContext c = p_context;
- c.current_function = parent_function;
- c.current_class = base_type.class_type;
- c.base = nullptr;
- if (_guess_expression_type(c, parameter->initializer, r_type)) {
- return true;
+ if (parent_function->parameters_indices.has(p_identifier)) {
+ const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier]];
+ if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
+ id_type = parameter->get_datatype();
+ }
+ if (parameter->initializer) {
+ GDScriptParser::CompletionContext c = p_context;
+ c.current_function = parent_function;
+ c.current_class = base_type.class_type;
+ c.base = nullptr;
+ if (_guess_expression_type(c, parameter->initializer, r_type)) {
+ return true;
+ }
}
}
}
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index 71831a3a97..a6b4dc7981 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -296,6 +296,15 @@ void GDScriptFunctionState::_clear_stack() {
}
}
+void GDScriptFunctionState::_clear_connections() {
+ List<Object::Connection> conns;
+ get_signals_connected_to_this(&conns);
+
+ for (Object::Connection &c : conns) {
+ c.signal.disconnect(c.callable);
+ }
+}
+
void GDScriptFunctionState::_bind_methods() {
ClassDB::bind_method(D_METHOD("resume", "arg"), &GDScriptFunctionState::resume, DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("is_valid", "extended_check"), &GDScriptFunctionState::is_valid, DEFVAL(false));
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 37416a734d..f45c1f9577 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -628,6 +628,7 @@ public:
Variant resume(const Variant &p_arg = Variant());
void _clear_stack();
+ void _clear_connections();
GDScriptFunctionState();
~GDScriptFunctionState();
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 713ad3ed17..acc3c5d079 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");
@@ -158,14 +158,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
return;
}
- if (ignored_warning_codes.has(p_code)) {
+ if (ignored_warnings.has(p_code)) {
return;
}
- String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
- if (ignored_warnings.has(warn_name)) {
- return;
- }
int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code));
if (!warn_level) {
return;
@@ -180,7 +176,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
warning.rightmost_column = p_source->rightmost_column;
if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) {
- push_error(warning.get_message(), p_source);
+ push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source);
return;
}
@@ -3548,7 +3544,11 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con
return empty;
}
-bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const {
+bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) {
+ if (is_applied) {
+ return true;
+ }
+ is_applied = true;
return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target);
}
@@ -3602,6 +3602,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
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.)");
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
ClassNode *p_class = static_cast<ClassNode *>(p_node);
p_class->icon_path = p_annotation->resolved_arguments[0];
return true;
@@ -3610,6 +3611,10 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
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.)");
+ if (head && !ClassDB::is_parent_class(head->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);
if (variable->onready) {
push_error(R"("@onready" annotation can only be used once per variable.)");
@@ -3830,6 +3835,10 @@ template <PropertyUsageFlags t_usage>
bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) {
AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
+ if (annotation->resolved_arguments.is_empty()) {
+ return false;
+ }
+
annotation->export_info.name = annotation->resolved_arguments[0];
switch (t_usage) {
@@ -3887,7 +3896,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- if (p_annotation->resolved_arguments.size()) {
+ if (!p_annotation->resolved_arguments.is_empty()) {
int last = p_annotation->resolved_arguments.size() - 1;
if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int();
@@ -3897,26 +3906,46 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
return false;
}
+
+ unsigned char locality_args = 0;
+ unsigned char permission_args = 0;
+ unsigned char transfer_mode_args = 0;
+
for (int i = last; i >= 0; i--) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "any_peer") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
- } else if (mode == "authority") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- } else if (mode == "call_local") {
+ String arg = p_annotation->resolved_arguments[i].operator String();
+ if (arg == "call_local") {
+ locality_args++;
rpc_config["call_local"] = true;
- } else if (mode == "call_remote") {
+ } else if (arg == "call_remote") {
+ locality_args++;
rpc_config["call_local"] = false;
- } else if (mode == "reliable") {
+ } else if (arg == "any_peer") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
+ } else if (arg == "authority") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
+ } else if (arg == "reliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
- } else if (mode == "unreliable") {
+ } else if (arg == "unreliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
- } else if (mode == "unreliable_ordered") {
+ } else if (arg == "unreliable_ordered") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation);
+ push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)", p_annotation);
}
}
+
+ if (locality_args > 1) {
+ push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation);
+ } else if (permission_args > 1) {
+ push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation);
+ } else if (transfer_mode_args > 1) {
+ push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation);
+ }
}
function->rpc_config = rpc_config;
return true;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 07dac25ec5..0ba0d5b6da 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -122,6 +122,7 @@ public:
TypeSource type_source = UNDETECTED;
bool is_constant = false;
+ bool is_read_only = false;
bool is_meta_type = false;
bool is_coroutine = false; // For function calls.
@@ -206,6 +207,7 @@ public:
void operator=(const DataType &p_other) {
kind = p_other.kind;
type_source = p_other.type_source;
+ is_read_only = p_other.is_read_only;
is_constant = p_other.is_constant;
is_meta_type = p_other.is_meta_type;
is_coroutine = p_other.is_coroutine;
@@ -297,7 +299,9 @@ public:
int leftmost_column = 0, rightmost_column = 0;
Node *next = nullptr;
List<AnnotationNode *> annotations;
- Vector<uint32_t> ignored_warnings;
+#ifdef DEBUG_ENABLED
+ Vector<GDScriptWarning::Code> ignored_warnings;
+#endif
DataType datatype;
@@ -329,8 +333,10 @@ public:
AnnotationInfo *info = nullptr;
PropertyInfo export_info;
+ bool is_resolved = false;
+ bool is_applied = false;
- bool apply(GDScriptParser *p_this, Node *p_target) const;
+ bool apply(GDScriptParser *p_this, Node *p_target);
bool applies_to(uint32_t p_target_kinds) const;
AnnotationNode() {
@@ -1263,8 +1269,7 @@ private:
#ifdef DEBUG_ENABLED
bool is_ignoring_warnings = false;
List<GDScriptWarning> warnings;
- HashSet<String> ignored_warnings;
- HashSet<uint32_t> ignored_warning_codes;
+ HashSet<GDScriptWarning::Code> ignored_warnings;
HashSet<int> unsafe_lines;
#endif
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index e18a4a6190..b99f5d2685 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -811,13 +811,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (!valid) {
+ Object *obj = dst->get_validated_object();
String v = index->operator String();
- if (!v.is_empty()) {
- v = "'" + v + "'";
+ bool read_only_property = false;
+ if (obj) {
+ read_only_property = ClassDB::has_property(obj->get_class_name(), v) && (ClassDB::get_property_setter(obj->get_class_name(), v) == StringName());
+ }
+ if (read_only_property) {
+ err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", v, _get_var_type(dst));
} else {
- v = "of type '" + _get_var_type(index) + "'";
+ if (!v.is_empty()) {
+ v = "'" + v + "'";
+ } else {
+ v = "of type '" + _get_var_type(index) + "'";
+ }
+ err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
}
- err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'";
OPCODE_BREAK;
}
#endif
@@ -1003,8 +1012,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (!valid) {
- String err_type;
- err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+ Object *obj = dst->get_validated_object();
+ bool read_only_property = false;
+ if (obj) {
+ read_only_property = ClassDB::has_property(obj->get_class_name(), *index) && (ClassDB::get_property_setter(obj->get_class_name(), *index) == StringName());
+ }
+ if (read_only_property) {
+ err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst));
+ } else {
+ err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'.";
+ }
OPCODE_BREAK;
}
#endif
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 9436146bed..ef59a07f1a 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -170,6 +170,21 @@ String GDScriptWarning::get_message() const {
case RENAMED_IN_GD4_HINT: {
break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here.
}
+ case INFERENCE_ON_VARIANT: {
+ CHECK_SYMBOLS(1);
+ return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]);
+ }
+ case NATIVE_METHOD_OVERRIDE: {
+ CHECK_SYMBOLS(2);
+ return vformat(R"(The method "%s" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)", symbols[0], symbols[1]);
+ }
+ case GET_NODE_DEFAULT_WITHOUT_ONREADY: {
+ CHECK_SYMBOLS(1);
+ return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
+ }
+ case ONREADY_WITH_EXPORT: {
+ return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)";
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -179,14 +194,8 @@ String GDScriptWarning::get_message() const {
}
int GDScriptWarning::get_default_value(Code p_code) {
- if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) {
- return WarnLevel::IGNORE;
- }
- // Too spammy by default on common cases (connect, Tween, etc.).
- if (p_code == RETURN_VALUE_DISCARDED) {
- return WarnLevel::IGNORE;
- }
- return WarnLevel::WARN;
+ ERR_FAIL_INDEX_V_MSG(p_code, WARNING_MAX, WarnLevel::IGNORE, "Getting default value of invalid warning code.");
+ return default_warning_levels[p_code];
}
PropertyInfo GDScriptWarning::get_property_info(Code p_code) {
@@ -240,7 +249,11 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"INT_AS_ENUM_WITHOUT_MATCH",
"STATIC_CALLED_ON_INSTANCE",
"CONFUSABLE_IDENTIFIER",
- "RENAMED_IN_GODOT_4_HINT"
+ "RENAMED_IN_GODOT_4_HINT",
+ "INFERENCE_ON_VARIANT",
+ "NATIVE_METHOD_OVERRIDE",
+ "GET_NODE_DEFAULT_WITHOUT_ONREADY",
+ "ONREADY_WITH_EXPORT",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index fa2907cdae..f0123c518c 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -82,9 +82,58 @@ public:
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4
+ INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant.
+ NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
+ GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
+ ONREADY_WITH_EXPORT, // The `@onready` annotation will set the value after `@export` which is likely not intended.
WARNING_MAX,
};
+ constexpr static WarnLevel default_warning_levels[] = {
+ WARN, // UNASSIGNED_VARIABLE
+ WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN
+ WARN, // UNUSED_VARIABLE
+ WARN, // UNUSED_LOCAL_CONSTANT
+ WARN, // SHADOWED_VARIABLE
+ WARN, // SHADOWED_VARIABLE_BASE_CLASS
+ WARN, // UNUSED_PRIVATE_CLASS_VARIABLE
+ WARN, // UNUSED_PARAMETER
+ WARN, // UNREACHABLE_CODE
+ WARN, // UNREACHABLE_PATTERN
+ WARN, // STANDALONE_EXPRESSION
+ WARN, // NARROWING_CONVERSION
+ WARN, // INCOMPATIBLE_TERNARY
+ WARN, // UNUSED_SIGNAL
+ IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
+ WARN, // PROPERTY_USED_AS_FUNCTION
+ WARN, // CONSTANT_USED_AS_FUNCTION
+ WARN, // FUNCTION_USED_AS_PROPERTY
+ WARN, // INTEGER_DIVISION
+ 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.
+ IGNORE, // UNSAFE_CALL_ARGUMENT // Too common in untyped scenarios.
+ WARN, // UNSAFE_VOID_RETURN
+ WARN, // DEPRECATED_KEYWORD
+ WARN, // STANDALONE_TERNARY
+ WARN, // ASSERT_ALWAYS_TRUE
+ WARN, // ASSERT_ALWAYS_FALSE
+ WARN, // REDUNDANT_AWAIT
+ WARN, // EMPTY_FILE
+ WARN, // SHADOWED_GLOBAL_IDENTIFIER
+ WARN, // INT_AS_ENUM_WITHOUT_CAST
+ WARN, // INT_AS_ENUM_WITHOUT_MATCH
+ WARN, // STATIC_CALLED_ON_INSTANCE
+ WARN, // CONFUSABLE_IDENTIFIER
+ WARN, // RENAMED_IN_GD4_HINT
+ ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type.
+ ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
+ ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
+ ERROR, // ONREADY_WITH_EXPORT // May not work as expected.
+ };
+
+ static_assert((sizeof(default_warning_levels) / sizeof(default_warning_levels[0])) == WARNING_MAX, "Amount of default levels does not match the amount of warnings.");
+
Code code = WARNING_MAX;
int start_line = -1, end_line = -1;
int leftmost_column = -1, rightmost_column = -1;
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 5b8af0ff34..57405aa1ce 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -146,11 +146,11 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l
init_language(p_source_dir);
}
#ifdef DEBUG_ENABLED
- // Enable all warnings for GDScript, so we can test them.
+ // 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++) {
- String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
- ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
+ String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i);
+ ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN);
}
#endif
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd
new file mode 100644
index 0000000000..2b1c4c9594
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.gd
@@ -0,0 +1,4 @@
+func test():
+ var tree := SceneTree.new()
+ tree.root = Window.new()
+ tree.free()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out
new file mode 100644
index 0000000000..b236d70ec8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a read-only property.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd
new file mode 100644
index 0000000000..c97ee0ea69
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.gd
@@ -0,0 +1,4 @@
+func test():
+ var state := PhysicsDirectBodyState3DExtension.new()
+ state.center_of_mass.x += 1.0
+ state.free()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out
new file mode 100644
index 0000000000..b236d70ec8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_to_read_only_property_indirectly.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a new value to a read-only property.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
index c70a1df10d..508e46742f 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int".
+The function signature doesn't match the parent. Parent signature is "my_function(int = <default>) -> int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd
new file mode 100644
index 0000000000..91f5071fa9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd
@@ -0,0 +1,5 @@
+extends RefCounted
+
+func test():
+ var nope := $Node
+ print("Cannot use $ without a Node base")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out
new file mode 100644
index 0000000000..33365908bf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd
new file mode 100644
index 0000000000..e781315266
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd
@@ -0,0 +1,6 @@
+extends RefCounted
+
+@onready var nope := 0
+
+func test():
+ print("Cannot use @onready without a Node base")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out
new file mode 100644
index 0000000000..8088d28329
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+"@onready" can only be used in classes that inherit "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd
new file mode 100644
index 0000000000..a9004a346b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.gd
@@ -0,0 +1,18 @@
+extends Node
+
+@onready var shorthand = $Node
+@onready var call = get_node(^"Node")
+@onready var shorthand_with_cast = $Node as Node
+@onready var call_with_cast = get_node(^"Node") as Node
+
+func _init():
+ var node := Node.new()
+ node.name = "Node"
+ add_child(node)
+
+func test():
+ # Those are expected to be `null` since `_ready()` is never called on tests.
+ prints("shorthand", shorthand)
+ prints("call", call)
+ prints("shorthand_with_cast", shorthand_with_cast)
+ prints("call_with_cast", call_with_cast)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out
new file mode 100644
index 0000000000..eddc2deec0
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/allow_get_node_with_onready.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+shorthand <null>
+call <null>
+shorthand_with_cast <null>
+call_with_cast <null>
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd
new file mode 100644
index 0000000000..02120db868
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.gd
@@ -0,0 +1,13 @@
+# https://github.com/godotengine/godot/issues/72501
+extends Node
+
+func test():
+ prints("before", process_mode)
+ process_mode = PROCESS_MODE_PAUSABLE
+ prints("after", process_mode)
+
+ var node := Node.new()
+ add_child(node)
+ prints("before", node.process_mode)
+ node.process_mode = PROCESS_MODE_PAUSABLE
+ prints("after", node.process_mode)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out
new file mode 100644
index 0000000000..1eb045a4e4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/assign_to_native_enum_property.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+before 0
+after 1
+before 0
+after 1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
index 48a804ff54..b447180ea8 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
@@ -2,16 +2,18 @@ func variant() -> Variant: return null
var member_weak = variant()
var member_typed: Variant = variant()
+@warning_ignore("inference_on_variant")
var member_inferred := variant()
func param_weak(param = variant()) -> void: print(param)
func param_typed(param: Variant = variant()) -> void: print(param)
+@warning_ignore("inference_on_variant")
func param_inferred(param := variant()) -> void: print(param)
func return_untyped(): return variant()
func return_typed() -> Variant: return variant()
-@warning_ignore("unused_variable")
+@warning_ignore("unused_variable", "inference_on_variant")
func test() -> void:
var weak = variant()
var typed: Variant = variant()
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
new file mode 100644
index 0000000000..849df0921e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd
@@ -0,0 +1,17 @@
+extends Node
+
+var add_node = do_add_node() # Hack to have one node on init and not fail at runtime.
+
+var shorthand = $Node
+var with_self = self.get_node(^"Node")
+var without_self = get_node(^"Node")
+var with_cast = get_node(^"Node") as Node
+var shorthand_with_cast = $Node as Node
+
+func test():
+ print("warn")
+
+func do_add_node():
+ var node = Node.new()
+ node.name = "Node"
+ add_child(node)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out
new file mode 100644
index 0000000000..62b3ae291f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.out
@@ -0,0 +1,22 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> GET_NODE_DEFAULT_WITHOUT_ONREADY
+>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
+>> WARNING
+>> Line: 6
+>> GET_NODE_DEFAULT_WITHOUT_ONREADY
+>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
+>> WARNING
+>> Line: 7
+>> GET_NODE_DEFAULT_WITHOUT_ONREADY
+>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
+>> WARNING
+>> Line: 8
+>> GET_NODE_DEFAULT_WITHOUT_ONREADY
+>> The default value is using "get_node()" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
+>> WARNING
+>> Line: 9
+>> GET_NODE_DEFAULT_WITHOUT_ONREADY
+>> The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
+warn
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd
new file mode 100644
index 0000000000..024e91b7c6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.gd
@@ -0,0 +1,6 @@
+func test():
+ var inferred_with_variant := return_variant()
+ print(inferred_with_variant)
+
+func return_variant() -> Variant:
+ return "warn"
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out
new file mode 100644
index 0000000000..1d4078d2f7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/inference_with_variant.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 2
+>> INFERENCE_ON_VARIANT
+>> The variable type is being inferred from a Variant value, so it will be typed as Variant.
+warn
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd
new file mode 100644
index 0000000000..0b358ca5f2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.gd
@@ -0,0 +1,6 @@
+extends Node
+
+@onready @export var conflict = ""
+
+func test():
+ print("warn")
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out
new file mode 100644
index 0000000000..ff184f9f04
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> ONREADY_WITH_EXPORT
+>> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.
+warn
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd
new file mode 100644
index 0000000000..19d40f8ec8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.gd
@@ -0,0 +1,5 @@
+func test():
+ print("warn")
+
+func get(_property: StringName) -> Variant:
+ return null
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out
new file mode 100644
index 0000000000..793faa05d4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> NATIVE_METHOD_OVERRIDE
+>> The method "get" overrides a method from native class "Object". This won't be called by the engine and may not work as expected.
+warn
diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd
new file mode 100644
index 0000000000..19c4186622
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.gd
@@ -0,0 +1,7 @@
+func test():
+ var state = PhysicsDirectBodyState3DExtension.new()
+ assign(state)
+ state.free()
+
+func assign(state):
+ state.center_of_mass.x -= 1.0
diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out
new file mode 100644
index 0000000000..c181c5dd02
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: assign()
+>> runtime/assign_to_read_only_property.gd
+>> 7
+>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only.
diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd
new file mode 100644
index 0000000000..f15f580272
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.gd
@@ -0,0 +1,8 @@
+func test():
+ var state = PhysicsDirectBodyState3DExtension.new()
+ var prop = &"center_of_mass"
+ assign(state, prop)
+ state.free()
+
+func assign(state, prop):
+ state[prop].x = 1.0
diff --git a/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out
new file mode 100644
index 0000000000..2cdc81aacc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/assign_to_read_only_property_with_variable_index.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: assign()
+>> runtime/assign_to_read_only_property_with_variable_index.gd
+>> 8
+>> Cannot set value into property "center_of_mass" (on base "PhysicsDirectBodyState3DExtension") because it is read-only.
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 019504ad66..70b48b0e3a 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -185,7 +185,9 @@ namespace GodotTools.Export
foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
{
- AddSharedObject(file, tags: null, projectDataDirName);
+ AddSharedObject(file, tags: null,
+ Path.Join(projectDataDirName,
+ Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 344b76a202..93baf4e51c 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -21,6 +21,26 @@ namespace GodotPlugins
_resolver = new AssemblyDependencyResolver(pluginPath);
_sharedAssemblies = sharedAssemblies;
_mainLoadContext = mainLoadContext;
+
+ if (string.IsNullOrEmpty(AppContext.BaseDirectory))
+ {
+ // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.AnyOS.cs#L17-L35
+ // but Assembly.Location is unavailable, because we load assemblies from memory.
+ string? baseDirectory = Path.GetDirectoryName(pluginPath);
+ if (baseDirectory != null)
+ {
+ if (!Path.EndsInDirectorySeparator(baseDirectory))
+ baseDirectory += Path.DirectorySeparatorChar;
+ // This SetData call effectively sets AppContext.BaseDirectory
+ // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25
+ AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory);
+ }
+ else
+ {
+ // TODO: How to log from GodotPlugins? (delegate pointer?)
+ Console.Error.WriteLine("Failed to set AppContext.BaseDirectory. Dynamic loading of libraries may fail.");
+ }
+ }
}
protected override Assembly? Load(AssemblyName assemblyName)
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&lt;int&gt;();
+ /// 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&lt;int&gt; { 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&lt;string&gt; { "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&lt;string&gt; { "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 1e23689c95..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
@@ -459,6 +482,10 @@ namespace Godot.NativeInterop
public static partial godot_bool godotsharp_node_path_is_absolute(in godot_node_path p_self);
+ public static partial godot_bool godotsharp_node_path_equals(in godot_node_path p_self, in godot_node_path p_other);
+
+ public static partial int godotsharp_node_path_hash(in godot_node_path p_self);
+
// GD, etc
internal static partial void godotsharp_bytes_to_var(in godot_packed_byte_array p_bytes,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index b02bd167a1..f216fb7ea3 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -39,7 +39,7 @@ namespace Godot
/// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene.
/// </code>
/// </example>
- public sealed class NodePath : IDisposable
+ public sealed class NodePath : IDisposable, IEquatable<NodePath>
{
internal godot_node_path.movable NativeValue;
@@ -288,5 +288,37 @@ namespace Godot
/// </summary>
/// <returns>If the <see cref="NodePath"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
+
+ public static bool operator ==(NodePath left, NodePath right)
+ {
+ if (left is null)
+ return right is null;
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(NodePath left, NodePath right)
+ {
+ return !(left == right);
+ }
+
+ public bool Equals(NodePath other)
+ {
+ if (other is null)
+ return false;
+ var self = (godot_node_path)NativeValue;
+ var otherNative = (godot_node_path)other.NativeValue;
+ return NativeFuncs.godotsharp_node_path_equals(self, otherNative).ToBool();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ReferenceEquals(this, obj) || (obj is NodePath other && Equals(other));
+ }
+
+ public override int GetHashCode()
+ {
+ var self = (godot_node_path)NativeValue;
+ return NativeFuncs.godotsharp_node_path_hash(self);
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
index b9ee0bc278..97d28f9ee9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
@@ -10,7 +10,7 @@ namespace Godot
/// Comparing them is much faster than with regular strings, because only the pointers are compared,
/// not the whole strings.
/// </summary>
- public sealed class StringName : IDisposable
+ public sealed class StringName : IDisposable, IEquatable<StringName>
{
internal godot_string_name.movable NativeValue;
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index d17fe3e75f..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();
}
@@ -1141,6 +1209,14 @@ bool godotsharp_node_path_is_absolute(const NodePath *p_self) {
return p_self->is_absolute();
}
+bool godotsharp_node_path_equals(const NodePath *p_self, const NodePath *p_other) {
+ return *p_self == *p_other;
+}
+
+int godotsharp_node_path_hash(const NodePath *p_self) {
+ return p_self->hash();
+}
+
void godotsharp_randomize() {
Math::randomize();
}
@@ -1442,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,
@@ -1477,6 +1564,8 @@ static const void *unmanaged_callbacks[]{
(void *)godotsharp_node_path_get_subname,
(void *)godotsharp_node_path_get_subname_count,
(void *)godotsharp_node_path_is_absolute,
+ (void *)godotsharp_node_path_equals,
+ (void *)godotsharp_node_path_hash,
(void *)godotsharp_bytes_to_var,
(void *)godotsharp_convert,
(void *)godotsharp_hash,
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index d546c5d3ba..c3cb1c5f13 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -262,7 +262,7 @@ TypedArray<RID> GodotNavigationServer::map_get_agents(RID p_map) const {
const NavMap *map = map_owner.get_or_null(p_map);
ERR_FAIL_COND_V(map == nullptr, agents_rids);
- const LocalVector<RvoAgent *> agents = map->get_agents();
+ const LocalVector<NavAgent *> agents = map->get_agents();
agents_rids.resize(agents.size());
for (uint32_t i = 0; i < agents.size(); i++) {
@@ -282,7 +282,7 @@ RID GodotNavigationServer::region_get_map(RID p_region) const {
}
RID GodotNavigationServer::agent_get_map(RID p_agent) const {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND_V(agent == nullptr, RID());
if (agent->get_map()) {
@@ -579,13 +579,13 @@ RID GodotNavigationServer::agent_create() {
MutexLock lock(operations_mutex);
RID rid = agent_owner.make_rid();
- RvoAgent *agent = agent_owner.get_or_null(rid);
+ NavAgent *agent = agent_owner.get_or_null(rid);
agent->set_self(rid);
return rid;
}
COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
if (agent->get_map()) {
@@ -612,77 +612,77 @@ COMMAND_2(agent_set_map, RID, p_agent, RID, p_map) {
}
COMMAND_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_distance) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->neighborDist_ = p_distance;
}
COMMAND_2(agent_set_max_neighbors, RID, p_agent, int, p_count) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxNeighbors_ = p_count;
}
COMMAND_2(agent_set_time_horizon, RID, p_agent, real_t, p_time) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->timeHorizon_ = p_time;
}
COMMAND_2(agent_set_radius, RID, p_agent, real_t, p_radius) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->radius_ = p_radius;
}
COMMAND_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->maxSpeed_ = p_max_speed;
}
COMMAND_2(agent_set_velocity, RID, p_agent, Vector3, p_velocity) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->velocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_target_velocity, RID, p_agent, Vector3, p_velocity) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->prefVelocity_ = RVO::Vector3(p_velocity.x, p_velocity.y, p_velocity.z);
}
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->position_ = RVO::Vector3(p_position.x, p_position.y, p_position.z);
}
COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->get_agent()->ignore_y_ = p_ignore;
}
bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND_V(agent == nullptr, false);
return agent->is_map_changed();
}
COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) {
- RvoAgent *agent = agent_owner.get_or_null(p_agent);
+ NavAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
agent->set_callback(p_callback);
@@ -713,7 +713,7 @@ COMMAND_1(free, RID, p_object) {
}
// Remove any assigned agent
- for (RvoAgent *agent : map->get_agents()) {
+ for (NavAgent *agent : map->get_agents()) {
map->remove_agent(agent);
agent->set_map(nullptr);
}
@@ -746,7 +746,7 @@ COMMAND_1(free, RID, p_object) {
link_owner.free(p_object);
} else if (agent_owner.owns(p_object)) {
- RvoAgent *agent = agent_owner.get_or_null(p_object);
+ NavAgent *agent = agent_owner.get_or_null(p_object);
// Removes this agent from the map if assigned
if (agent->get_map() != nullptr) {
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index eea5713c40..0b113b77d4 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -36,10 +36,10 @@
#include "core/templates/rid_owner.h"
#include "servers/navigation_server_3d.h"
+#include "nav_agent.h"
#include "nav_link.h"
#include "nav_map.h"
#include "nav_region.h"
-#include "rvo_agent.h"
/// The commands are functions executed during the `sync` phase.
@@ -71,7 +71,7 @@ class GodotNavigationServer : public NavigationServer3D {
mutable RID_Owner<NavLink> link_owner;
mutable RID_Owner<NavMap> map_owner;
mutable RID_Owner<NavRegion> region_owner;
- mutable RID_Owner<RvoAgent> agent_owner;
+ mutable RID_Owner<NavAgent> agent_owner;
bool active = true;
LocalVector<NavMap *> active_maps;
diff --git a/modules/navigation/rvo_agent.cpp b/modules/navigation/nav_agent.cpp
index 40f1e925be..293544c0a5 100644
--- a/modules/navigation/rvo_agent.cpp
+++ b/modules/navigation/nav_agent.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* rvo_agent.cpp */
+/* nav_agent.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,15 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "rvo_agent.h"
+#include "nav_agent.h"
#include "nav_map.h"
-void RvoAgent::set_map(NavMap *p_map) {
+void NavAgent::set_map(NavMap *p_map) {
map = p_map;
}
-bool RvoAgent::is_map_changed() {
+bool NavAgent::is_map_changed() {
if (map) {
bool is_changed = map->get_map_update_id() != map_update_id;
map_update_id = map->get_map_update_id();
@@ -46,15 +46,15 @@ bool RvoAgent::is_map_changed() {
}
}
-void RvoAgent::set_callback(Callable p_callback) {
+void NavAgent::set_callback(Callable p_callback) {
callback = p_callback;
}
-bool RvoAgent::has_callback() const {
+bool NavAgent::has_callback() const {
return callback.is_valid();
}
-void RvoAgent::dispatch_callback() {
+void NavAgent::dispatch_callback() {
if (!callback.is_valid()) {
return;
}
diff --git a/modules/navigation/rvo_agent.h b/modules/navigation/nav_agent.h
index 5f377b6079..f154ce14d9 100644
--- a/modules/navigation/rvo_agent.h
+++ b/modules/navigation/nav_agent.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* rvo_agent.h */
+/* nav_agent.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef RVO_AGENT_H
-#define RVO_AGENT_H
+#ifndef NAV_AGENT_H
+#define NAV_AGENT_H
#include "core/object/class_db.h"
#include "nav_rid.h"
@@ -38,7 +38,7 @@
class NavMap;
-class RvoAgent : public NavRid {
+class NavAgent : public NavRid {
NavMap *map = nullptr;
RVO::Agent agent;
Callable callback = Callable();
@@ -62,4 +62,4 @@ public:
void dispatch_callback();
};
-#endif // RVO_AGENT_H
+#endif // NAV_AGENT_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index d763b1d3bc..b1674c8fc5 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -31,9 +31,9 @@
#include "nav_map.h"
#include "core/object/worker_thread_pool.h"
+#include "nav_agent.h"
#include "nav_link.h"
#include "nav_region.h"
-#include "rvo_agent.h"
#include <algorithm>
#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
@@ -568,18 +568,18 @@ void NavMap::remove_link(NavLink *p_link) {
}
}
-bool NavMap::has_agent(RvoAgent *agent) const {
+bool NavMap::has_agent(NavAgent *agent) const {
return (agents.find(agent) != -1);
}
-void NavMap::add_agent(RvoAgent *agent) {
+void NavMap::add_agent(NavAgent *agent) {
if (!has_agent(agent)) {
agents.push_back(agent);
agents_dirty = true;
}
}
-void NavMap::remove_agent(RvoAgent *agent) {
+void NavMap::remove_agent(NavAgent *agent) {
remove_agent_as_controlled(agent);
int64_t agent_index = agents.find(agent);
if (agent_index != -1) {
@@ -588,7 +588,7 @@ void NavMap::remove_agent(RvoAgent *agent) {
}
}
-void NavMap::set_agent_as_controlled(RvoAgent *agent) {
+void NavMap::set_agent_as_controlled(NavAgent *agent) {
const bool exist = (controlled_agents.find(agent) != -1);
if (!exist) {
ERR_FAIL_COND(!has_agent(agent));
@@ -596,7 +596,7 @@ void NavMap::set_agent_as_controlled(RvoAgent *agent) {
}
}
-void NavMap::remove_agent_as_controlled(RvoAgent *agent) {
+void NavMap::remove_agent_as_controlled(NavAgent *agent) {
int64_t active_avoidance_agent_index = controlled_agents.find(agent);
if (active_avoidance_agent_index != -1) {
controlled_agents.remove_at_unordered(active_avoidance_agent_index);
@@ -895,7 +895,7 @@ void NavMap::sync() {
// cannot use LocalVector here as RVO library expects std::vector to build KdTree
std::vector<RVO::Agent *> raw_agents;
raw_agents.reserve(agents.size());
- for (RvoAgent *agent : agents) {
+ for (NavAgent *agent : agents) {
raw_agents.push_back(agent->get_agent());
}
rvo.buildAgentTree(raw_agents);
@@ -916,7 +916,7 @@ void NavMap::sync() {
pm_edge_free_count = _new_pm_edge_free_count;
}
-void NavMap::compute_single_step(uint32_t index, RvoAgent **agent) {
+void NavMap::compute_single_step(uint32_t index, NavAgent **agent) {
(*(agent + index))->get_agent()->computeNeighbors(&rvo);
(*(agent + index))->get_agent()->computeNewVelocity(deltatime);
}
@@ -930,7 +930,7 @@ void NavMap::step(real_t p_deltatime) {
}
void NavMap::dispatch_callbacks() {
- for (RvoAgent *agent : controlled_agents) {
+ for (NavAgent *agent : controlled_agents) {
agent->dispatch_callback();
}
}
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index fce7aff3ba..ab6a48dd70 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -42,7 +42,7 @@
class NavLink;
class NavRegion;
-class RvoAgent;
+class NavAgent;
class NavMap : public NavRid {
/// Map Up
@@ -78,10 +78,10 @@ class NavMap : public NavRid {
bool agents_dirty = false;
/// All the Agents (even the controlled one)
- LocalVector<RvoAgent *> agents;
+ LocalVector<NavAgent *> agents;
/// Controlled agents
- LocalVector<RvoAgent *> controlled_agents;
+ LocalVector<NavAgent *> controlled_agents;
/// Physics delta time
real_t deltatime = 0.0;
@@ -144,15 +144,15 @@ public:
return links;
}
- bool has_agent(RvoAgent *agent) const;
- void add_agent(RvoAgent *agent);
- void remove_agent(RvoAgent *agent);
- const LocalVector<RvoAgent *> &get_agents() const {
+ bool has_agent(NavAgent *agent) const;
+ void add_agent(NavAgent *agent);
+ void remove_agent(NavAgent *agent);
+ const LocalVector<NavAgent *> &get_agents() const {
return agents;
}
- void set_agent_as_controlled(RvoAgent *agent);
- void remove_agent_as_controlled(RvoAgent *agent);
+ void set_agent_as_controlled(NavAgent *agent);
+ void remove_agent_as_controlled(NavAgent *agent);
uint32_t get_map_update_id() const {
return map_update_id;
@@ -173,7 +173,7 @@ public:
int get_pm_edge_free_count() const { return pm_edge_free_count; }
private:
- void compute_single_step(uint32_t index, RvoAgent **agent);
+ void compute_single_step(uint32_t index, NavAgent **agent);
void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const;
};
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index 3b39967ba4..0dd41675b6 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -103,6 +103,7 @@ env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extens
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")
env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_ml2_controller_extension.cpp")
env.modules_sources += module_obj
diff --git a/modules/openxr/extensions/openxr_ml2_controller_extension.cpp b/modules/openxr/extensions/openxr_ml2_controller_extension.cpp
new file mode 100644
index 0000000000..ae372f69b3
--- /dev/null
+++ b/modules/openxr/extensions/openxr_ml2_controller_extension.cpp
@@ -0,0 +1,71 @@
+/**************************************************************************/
+/* openxr_ml2_controller_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_ml2_controller_extension.h"
+#include "../action_map/openxr_interaction_profile_meta_data.h"
+
+HashMap<String, bool *> OpenXRML2ControllerExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+bool OpenXRML2ControllerExtension::is_available() {
+ return available;
+}
+
+void OpenXRML2ControllerExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetaData *metadata = OpenXRInteractionProfileMetaData::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Magic Leap 2 Controller
+ const String profile_path = "/interaction_profiles/ml/ml2_controller";
+ metadata->register_interaction_profile("Magic Leap 2 controller", "/interaction_profiles/ml/ml2_controller", XR_ML_ML2_CONTROLLER_INTERACTION_EXTENSION_NAME);
+ for (const String user_path : { "/user/hand/left", "/user/hand/right" }) {
+ metadata->register_io_path(profile_path, "Grip pose", user_path, user_path + "/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path(profile_path, "Aim pose", user_path, user_path + "/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path(profile_path, "Menu click", user_path, user_path + "/input/menu/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path(profile_path, "Trigger", user_path, user_path + "/input/trigger/value", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path(profile_path, "Trigger click", user_path, user_path + "/input/trigger/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path(profile_path, "Shoulder click", user_path, user_path + "/input/shoulder/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+
+ metadata->register_io_path(profile_path, "Trackpad click", user_path, user_path + "/input/trackpad/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path(profile_path, "Trackpad force", user_path, user_path + "/input/trackpad/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path(profile_path, "Trackpad X", user_path, user_path + "/input/trackpad/x", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path(profile_path, "Trackpad Y", user_path, user_path + "/input/trackpad/y", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path(profile_path, "Trackpad touch", user_path, user_path + "/input/trackpad/touch", "", OpenXRAction::OPENXR_ACTION_VECTOR2);
+
+ metadata->register_io_path(profile_path, "Haptic output", user_path, user_path + "/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ }
+}
diff --git a/modules/openxr/extensions/openxr_ml2_controller_extension.h b/modules/openxr/extensions/openxr_ml2_controller_extension.h
new file mode 100644
index 0000000000..216cd55a2f
--- /dev/null
+++ b/modules/openxr/extensions/openxr_ml2_controller_extension.h
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* openxr_ml2_controller_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_ML2_CONTROLLER_EXTENSION_H
+#define OPENXR_ML2_CONTROLLER_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRML2ControllerExtension : public OpenXRExtensionWrapper {
+public:
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+ virtual void on_register_metadata() override;
+
+private:
+ bool available = false;
+};
+
+#endif // OPENXR_ML2_CONTROLLER_EXTENSION_H
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 0a25cd68b7..ddb3114b59 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -483,6 +483,37 @@ bool OpenXRAPI::load_supported_view_configuration_types() {
return true;
}
+bool OpenXRAPI::load_supported_environmental_blend_modes() {
+ // This queries the supported environmental blend modes.
+
+ ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
+
+ if (supported_environment_blend_modes != nullptr) {
+ // free previous results
+ memfree(supported_environment_blend_modes);
+ supported_environment_blend_modes = nullptr;
+ num_supported_environment_blend_modes = 0;
+ }
+
+ XrResult result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, 0, &num_supported_environment_blend_modes, nullptr);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to get supported environmental blend mode count [", get_error_string(result), "]");
+ return false;
+ }
+
+ supported_environment_blend_modes = (XrEnvironmentBlendMode *)memalloc(sizeof(XrEnvironmentBlendMode) * num_supported_environment_blend_modes);
+ ERR_FAIL_NULL_V(supported_environment_blend_modes, false);
+
+ 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");
+
+ 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]));
+ }
+
+ return true;
+}
+
bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const {
ERR_FAIL_NULL_V(supported_view_configuration_types, false);
@@ -551,6 +582,12 @@ void OpenXRAPI::destroy_instance() {
supported_view_configuration_types = nullptr;
}
+ if (supported_environment_blend_modes != nullptr) {
+ memfree(supported_environment_blend_modes);
+ supported_environment_blend_modes = nullptr;
+ num_supported_environment_blend_modes = 0;
+ }
+
if (instance != XR_NULL_HANDLE) {
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_instance_destroyed();
@@ -1205,6 +1242,7 @@ bool OpenXRAPI::resolve_instance_openxr_symbols() {
OPENXR_API_INIT_XR_FUNC_V(xrDestroySwapchain);
OPENXR_API_INIT_XR_FUNC_V(xrEndFrame);
OPENXR_API_INIT_XR_FUNC_V(xrEndSession);
+ OPENXR_API_INIT_XR_FUNC_V(xrEnumerateEnvironmentBlendModes);
OPENXR_API_INIT_XR_FUNC_V(xrEnumerateReferenceSpaces);
OPENXR_API_INIT_XR_FUNC_V(xrEnumerateSwapchainFormats);
OPENXR_API_INIT_XR_FUNC_V(xrEnumerateViewConfigurations);
@@ -1312,6 +1350,11 @@ bool OpenXRAPI::initialize(const String &p_rendering_driver) {
return false;
}
+ if (!load_supported_environmental_blend_modes()) {
+ destroy_instance();
+ return false;
+ }
+
return true;
}
@@ -1822,7 +1865,7 @@ void OpenXRAPI::end_frame() {
XR_TYPE_FRAME_END_INFO, // type
nullptr, // next
frame_state.predictedDisplayTime, // displayTime
- XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode
+ environment_blend_mode, // environmentBlendMode
0, // layerCount
nullptr // layers
};
@@ -1874,7 +1917,7 @@ void OpenXRAPI::end_frame() {
XR_TYPE_FRAME_END_INFO, // type
nullptr, // next
frame_state.predictedDisplayTime, // displayTime
- XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode
+ environment_blend_mode, // environmentBlendMode
static_cast<uint32_t>(layers_list.size()), // layerCount
layers_list.ptr() // layers
};
@@ -2777,3 +2820,18 @@ void OpenXRAPI::register_composition_layer_provider(OpenXRCompositionLayerProvid
void OpenXRAPI::unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider) {
composition_layer_providers.erase(provider);
}
+
+const XrEnvironmentBlendMode *OpenXRAPI::get_supported_environment_blend_modes(uint32_t &count) {
+ count = num_supported_environment_blend_modes;
+ return supported_environment_blend_modes;
+}
+
+bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode mode) {
+ for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) {
+ if (supported_environment_blend_modes[i] == mode) {
+ environment_blend_mode = mode;
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index e1787c6da0..8c642c4ff4 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -99,9 +99,13 @@ private:
XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
- // XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled.
+ // blend mode
+ XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ uint32_t num_supported_environment_blend_modes = 0;
+ XrEnvironmentBlendMode *supported_environment_blend_modes = nullptr;
+
// state
XrInstance instance = XR_NULL_HANDLE;
XrSystemId system_id = 0;
@@ -182,6 +186,7 @@ private:
EXT_PROTO_XRRESULT_FUNC2(xrEndFrame, (XrSession), session, (const XrFrameEndInfo *), frameEndInfo)
EXT_PROTO_XRRESULT_FUNC1(xrEndSession, (XrSession), session)
EXT_PROTO_XRRESULT_FUNC3(xrEnumerateApiLayerProperties, (uint32_t), propertyCapacityInput, (uint32_t *), propertyCountOutput, (XrApiLayerProperties *), properties)
+ EXT_PROTO_XRRESULT_FUNC6(xrEnumerateEnvironmentBlendModes, (XrInstance), instance, (XrSystemId), systemId, (XrViewConfigurationType), viewConfigurationType, (uint32_t), environmentBlendModeCapacityInput, (uint32_t *), environmentBlendModeCountOutput, (XrEnvironmentBlendMode *), environmentBlendModes)
EXT_PROTO_XRRESULT_FUNC4(xrEnumerateInstanceExtensionProperties, (const char *), layerName, (uint32_t), propertyCapacityInput, (uint32_t *), propertyCountOutput, (XrExtensionProperties *), properties)
EXT_PROTO_XRRESULT_FUNC4(xrEnumerateReferenceSpaces, (XrSession), session, (uint32_t), spaceCapacityInput, (uint32_t *), spaceCountOutput, (XrReferenceSpaceType *), spaces)
EXT_PROTO_XRRESULT_FUNC4(xrEnumerateSwapchainFormats, (XrSession), session, (uint32_t), formatCapacityInput, (uint32_t *), formatCountOutput, (int64_t *), formats)
@@ -210,6 +215,7 @@ private:
bool create_instance();
bool get_system_info();
bool load_supported_view_configuration_types();
+ bool load_supported_environmental_blend_modes();
bool is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const;
bool load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type);
void destroy_instance();
@@ -390,6 +396,9 @@ public:
void register_composition_layer_provider(OpenXRCompositionLayerProvider *provider);
void unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider);
+ const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count);
+ bool set_environment_blend_mode(XrEnvironmentBlendMode mode);
+
OpenXRAPI();
~OpenXRAPI();
};
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 702e56b410..51de9b913a 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -870,6 +870,60 @@ void OpenXRInterface::stop_passthrough() {
}
}
+Array OpenXRInterface::get_supported_environment_blend_modes() {
+ Array modes;
+
+ if (!openxr_api) {
+ return modes;
+ }
+
+ uint32_t count = 0;
+ const XrEnvironmentBlendMode *env_blend_modes = openxr_api->get_supported_environment_blend_modes(count);
+
+ if (!env_blend_modes) {
+ return modes;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ switch (env_blend_modes[i]) {
+ case XR_ENVIRONMENT_BLEND_MODE_OPAQUE:
+ modes.push_back(XR_ENV_BLEND_MODE_OPAQUE);
+ break;
+ case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE:
+ modes.push_back(XR_ENV_BLEND_MODE_ADDITIVE);
+ break;
+ case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND:
+ modes.push_back(XR_ENV_BLEND_MODE_ALPHA_BLEND);
+ break;
+ default:
+ WARN_PRINT("Unsupported blend mode found: " + String::num_int64(int64_t(env_blend_modes[i])));
+ }
+ }
+ return modes;
+}
+
+bool OpenXRInterface::set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) {
+ if (openxr_api) {
+ XrEnvironmentBlendMode oxr_blend_mode;
+ switch (mode) {
+ case XR_ENV_BLEND_MODE_OPAQUE:
+ oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ break;
+ case XR_ENV_BLEND_MODE_ADDITIVE:
+ oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
+ break;
+ case XR_ENV_BLEND_MODE_ALPHA_BLEND:
+ oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
+ break;
+ default:
+ WARN_PRINT("Unknown blend mode requested: " + String::num_int64(int64_t(mode)));
+ oxr_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ }
+ return openxr_api->set_environment_blend_mode(oxr_blend_mode);
+ }
+ return false;
+}
+
void OpenXRInterface::on_state_ready() {
emit_signal(SNAME("session_begun"));
}
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index cce329d8e6..40ee95f02f 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -147,6 +147,10 @@ public:
virtual bool start_passthrough() override;
virtual void stop_passthrough() override;
+ /** environment blend mode. */
+ virtual Array get_supported_environment_blend_modes() override;
+ virtual bool set_environment_blend_mode(XRInterface::EnvironmentBlendMode mode) override;
+
void on_state_ready();
void on_state_visible();
void on_state_focused();
diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp
index 50ebf468b9..926e918390 100644
--- a/modules/openxr/openxr_util.cpp
+++ b/modules/openxr/openxr_util.cpp
@@ -29,267 +29,38 @@
/**************************************************************************/
#include "openxr_util.h"
+#include <openxr/openxr_reflection.h>
-#define ENUM_TO_STRING_CASE(e) \
- case e: { \
- return String(#e); \
- } break;
+#define XR_ENUM_CASE_STR(name, val) \
+ case name: \
+ return #name;
+#define XR_ENUM_SWITCH(enumType, var) \
+ switch (var) { \
+ XR_LIST_ENUM_##enumType(XR_ENUM_CASE_STR) default : return "Unknown " #enumType ": " + String::num_int64(int64_t(var)); \
+ }
-// TODO see if we can generate this code further using the xml file with meta data supplied by OpenXR
+String OpenXRUtil::get_view_configuration_name(XrViewConfigurationType p_view_configuration){
+ XR_ENUM_SWITCH(XrViewConfigurationType, p_view_configuration)
+}
-String OpenXRUtil::get_view_configuration_name(XrViewConfigurationType p_view_configuration) {
- switch (p_view_configuration) {
- ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO)
- ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)
- ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO)
- ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT)
- ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_MAX_ENUM)
- default: {
- return String("View Configuration ") + String::num_int64(int64_t(p_view_configuration));
- } break;
- }
+String OpenXRUtil::get_reference_space_name(XrReferenceSpaceType p_reference_space){
+ XR_ENUM_SWITCH(XrReferenceSpaceType, p_reference_space)
}
-String OpenXRUtil::get_reference_space_name(XrReferenceSpaceType p_reference_space) {
- switch (p_reference_space) {
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_VIEW)
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_LOCAL)
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_STAGE)
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT)
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO)
- ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_MAX_ENUM)
- default: {
- return String("Reference space ") + String::num_int64(int64_t(p_reference_space));
- } break;
- }
+String OpenXRUtil::get_structure_type_name(XrStructureType p_structure_type){
+ XR_ENUM_SWITCH(XrStructureType, p_structure_type)
}
-String OpenXRUtil::get_structure_type_name(XrStructureType p_structure_type) {
- switch (p_structure_type) {
- ENUM_TO_STRING_CASE(XR_TYPE_UNKNOWN)
- ENUM_TO_STRING_CASE(XR_TYPE_API_LAYER_PROPERTIES)
- ENUM_TO_STRING_CASE(XR_TYPE_EXTENSION_PROPERTIES)
- ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_GET_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PROPERTIES)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW)
- ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SESSION_BEGIN_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_STATE)
- ENUM_TO_STRING_CASE(XR_TYPE_FRAME_END_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_VIBRATION)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_BUFFER)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_BOOLEAN)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_FLOAT)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_VECTOR2F)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_POSE)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SET_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_PROPERTIES)
- ENUM_TO_STRING_CASE(XR_TYPE_FRAME_WAIT_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_QUAD)
- ENUM_TO_STRING_CASE(XR_TYPE_REFERENCE_SPACE_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SPACE_CREATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW)
- ENUM_TO_STRING_CASE(XR_TYPE_SPACE_LOCATION)
- ENUM_TO_STRING_CASE(XR_TYPE_SPACE_VELOCITY)
- ENUM_TO_STRING_CASE(XR_TYPE_FRAME_STATE)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_PROPERTIES)
- ENUM_TO_STRING_CASE(XR_TYPE_FRAME_BEGIN_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_EVENTS_LOST)
- ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED)
- ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_STATE)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_GET_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_ACTION_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_ACTIONS_SYNC_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CUBE_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_SWAPCHAIN_FORMAT_LIST_CREATE_INFO_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_LABEL_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XCB_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WAYLAND_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D11_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D12_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_EYE_GAZE_SAMPLE_TIME_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_VISIBILITY_MASK_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_EGL_MNDX)
- ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_LOCATIONS_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_VELOCITIES_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_MESH_PROPERTIES_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_SPACE_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_UPDATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_POSE_TYPE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SWAPCHAIN_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_STATE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC)
- ENUM_TO_STRING_CASE(XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT)
- ENUM_TO_STRING_CASE(XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECTS_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANES_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESHES_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_VIVE_TRACKER_PATHS_HTCX)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_MESH_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_SCALE_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_AIM_STATE_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_STYLE_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_BINDING_MODIFICATIONS_KHR)
- ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO)
- ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB)
- ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB)
- ENUM_TO_STRING_CASE(XR_STRUCTURE_TYPE_MAX_ENUM)
- default: {
- return String("Structure type ") + String::num_int64(int64_t(p_structure_type));
- } break;
- }
+String OpenXRUtil::get_session_state_name(XrSessionState p_session_state){
+ XR_ENUM_SWITCH(XrSessionState, p_session_state)
}
-String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) {
- switch (p_session_state) {
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_UNKNOWN)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_IDLE)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_READY)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_SYNCHRONIZED)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_VISIBLE)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_FOCUSED)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_STOPPING)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_LOSS_PENDING)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_EXITING)
- ENUM_TO_STRING_CASE(XR_SESSION_STATE_MAX_ENUM)
- default: {
- return String("Session state ") + String::num_int64(int64_t(p_session_state));
- } break;
- }
+String OpenXRUtil::get_action_type_name(XrActionType p_action_type){
+ XR_ENUM_SWITCH(XrActionType, p_action_type)
}
-String OpenXRUtil::get_action_type_name(XrActionType p_action_type) {
- switch (p_action_type) {
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_BOOLEAN_INPUT)
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_FLOAT_INPUT)
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VECTOR2F_INPUT)
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_POSE_INPUT)
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_VIBRATION_OUTPUT)
- ENUM_TO_STRING_CASE(XR_ACTION_TYPE_MAX_ENUM)
- default: {
- return String("Action type ") + String::num_int64(int64_t(p_action_type));
- } break;
- }
+String OpenXRUtil::get_environment_blend_mode_name(XrEnvironmentBlendMode p_blend_mode) {
+ XR_ENUM_SWITCH(XrEnvironmentBlendMode, p_blend_mode);
}
String OpenXRUtil::make_xr_version_string(XrVersion p_version) {
diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h
index dfda537474..7e7a6a1880 100644
--- a/modules/openxr/openxr_util.h
+++ b/modules/openxr/openxr_util.h
@@ -41,6 +41,7 @@ public:
static String get_structure_type_name(XrStructureType p_structure_type);
static String get_session_state_name(XrSessionState p_session_state);
static String get_action_type_name(XrActionType p_action_type);
+ static String get_environment_blend_mode_name(XrEnvironmentBlendMode p_blend_mode);
static String make_xr_version_string(XrVersion p_version);
};
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 306a2f1bbd..c39e49387a 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -52,6 +52,7 @@
#include "extensions/openxr_htc_controller_extension.h"
#include "extensions/openxr_htc_vive_tracker_extension.h"
#include "extensions/openxr_huawei_controller_extension.h"
+#include "extensions/openxr_ml2_controller_extension.h"
#include "extensions/openxr_palm_pose_extension.h"
#include "extensions/openxr_pico_controller_extension.h"
#include "extensions/openxr_wmr_controller_extension.h"
@@ -102,6 +103,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRFbPassthroughExtensionWrapper));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRDisplayRefreshRateExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRWMRControllerExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRML2ControllerExtension));
}
if (OpenXRAPI::openxr_is_enabled()) {
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 9b474bf2ce..b55188ce0c 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -6118,20 +6118,22 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
Vector<UChar *> skeletons;
skeletons.resize(p_dict.size());
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setChecks(sc, USPOOF_CONFUSABLE, &status);
+ if (sc_conf == nullptr) {
+ sc_conf = uspoof_open(&status);
+ uspoof_setChecks(sc_conf, USPOOF_CONFUSABLE, &status);
+ }
for (int i = 0; i < p_dict.size(); i++) {
Char16String word = p_dict[i].utf16();
- int32_t len = uspoof_getSkeleton(sc, 0, word.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, NULL, 0, &status);
skeletons.write[i] = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, word.get_data(), -1, skeletons.write[i], len, &status);
+ uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, skeletons.write[i], len, &status);
}
- int32_t len = uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, NULL, 0, &status);
UChar *skel = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, skel, len, &status);
+ uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, skel, len, &status);
for (int i = 0; i < skeletons.size(); i++) {
if (u_strcmp(skel, skeletons[i]) == 0) {
match_index = i;
@@ -6143,7 +6145,6 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
for (int i = 0; i < skeletons.size(); i++) {
memfree(skeletons.write[i]);
}
- uspoof_close(sc);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), -1, u_errorName(status));
@@ -6159,19 +6160,18 @@ bool TextServerAdvanced::_spoof_check(const String &p_string) const {
UErrorCode status = U_ZERO_ERROR;
Char16String utf16 = p_string.utf16();
- USet *allowed = uset_openEmpty();
- uset_addAll(allowed, uspoof_getRecommendedSet(&status));
- uset_addAll(allowed, uspoof_getInclusionSet(&status));
-
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setAllowedChars(sc, allowed, &status);
- uspoof_setRestrictionLevel(sc, USPOOF_MODERATELY_RESTRICTIVE);
-
- int32_t bitmask = uspoof_check(sc, utf16.get_data(), -1, NULL, &status);
-
- uspoof_close(sc);
- uset_close(allowed);
+ if (allowed == nullptr) {
+ allowed = uset_openEmpty();
+ uset_addAll(allowed, uspoof_getRecommendedSet(&status));
+ uset_addAll(allowed, uspoof_getInclusionSet(&status));
+ }
+ if (sc_spoof == nullptr) {
+ sc_spoof = uspoof_open(&status);
+ uspoof_setAllowedChars(sc_spoof, allowed, &status);
+ uspoof_setRestrictionLevel(sc_spoof, USPOOF_MODERATELY_RESTRICTIVE);
+ }
+ int32_t bitmask = uspoof_check(sc_spoof, utf16.get_data(), -1, NULL, &status);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), false, u_errorName(status));
return (bitmask != 0);
@@ -6587,5 +6587,17 @@ TextServerAdvanced::~TextServerAdvanced() {
FT_Done_FreeType(ft_library);
}
#endif
+ if (sc_spoof != nullptr) {
+ uspoof_close(sc_spoof);
+ sc_spoof = nullptr;
+ }
+ if (sc_conf != nullptr) {
+ uspoof_close(sc_conf);
+ sc_conf = nullptr;
+ }
+ if (allowed != nullptr) {
+ uset_close(allowed);
+ allowed = nullptr;
+ }
u_cleanup();
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index c7fe46d554..1acf5b21f0 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -159,6 +159,9 @@ class TextServerAdvanced : public TextServerExtension {
// ICU support data.
bool icu_data_loaded = false;
+ mutable USet *allowed = nullptr;
+ mutable USpoofChecker *sc_spoof = nullptr;
+ mutable USpoofChecker *sc_conf = nullptr;
// Font cache data.