summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp2
-rw-r--r--modules/gdscript/gdscript.cpp29
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp15
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_editor.cpp89
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp2
-rw-r--r--modules/gdscript/tests/README.md41
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg2
-rw-r--r--modules/gdscript/tests/test_completion.h18
-rw-r--r--modules/gltf/structures/gltf_buffer_view.cpp1
-rw-r--r--modules/mono/csharp_script.cpp29
-rw-r--r--modules/mono/csharp_script.h1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln6
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs164
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs56
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs20
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs20
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs22
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs71
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs27
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs4
-rw-r--r--modules/mono/glue/runtime_interop.cpp2
-rw-r--r--modules/mono/utils/path_utils.cpp2
-rw-r--r--modules/openxr/doc_classes/OpenXRHand.xml13
-rw-r--r--modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp2
-rw-r--r--modules/openxr/extensions/openxr_opengl_extension.h6
-rw-r--r--modules/openxr/extensions/openxr_vulkan_extension.h2
-rw-r--r--modules/openxr/scene/openxr_hand.cpp26
-rw-r--r--modules/openxr/scene/openxr_hand.h11
-rw-r--r--modules/svg/register_types.cpp9
38 files changed, 664 insertions, 77 deletions
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index f528b92cf2..e9ce92dd80 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -40,7 +40,7 @@
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
switch (p_channels) {
case Image::USED_CHANNELS_L:
- return EtcpakType::ETCPAK_TYPE_ETC1;
+ return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_LA:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
case Image::USED_CHANNELS_R:
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index c4509aa5c3..7b486f2a35 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2351,14 +2351,13 @@ struct GDScriptDepSort {
void GDScriptLanguage::reload_all_scripts() {
#ifdef DEBUG_ENABLED
print_verbose("GDScript: Reloading all scripts");
- List<Ref<GDScript>> scripts;
+ Array scripts;
{
MutexLock lock(this->mutex);
SelfList<GDScript> *elem = script_list.first();
while (elem) {
- // Scripts will reload all subclasses, so only reload root scripts.
- if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
+ if (elem->self()->get_path().is_resource_file()) {
print_verbose("GDScript: Found: " + elem->self()->get_path());
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
@@ -2379,19 +2378,11 @@ void GDScriptLanguage::reload_all_scripts() {
#endif
}
- //as scripts are going to be reloaded, must proceed without locking here
-
- scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
-
- for (Ref<GDScript> &scr : scripts) {
- print_verbose("GDScript: Reloading: " + scr->get_path());
- scr->load_source_code(scr->get_path());
- scr->reload(true);
- }
+ reload_scripts(scripts, true);
#endif
}
-void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
#ifdef DEBUG_ENABLED
List<Ref<GDScript>> scripts;
@@ -2417,7 +2408,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
for (Ref<GDScript> &scr : scripts) {
- bool reload = scr == p_script || to_reload.has(scr->get_base());
+ bool reload = p_scripts.has(scr) || to_reload.has(scr->get_base());
if (!reload) {
continue;
@@ -2440,7 +2431,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
}
}
-//same thing for placeholders
+ //same thing for placeholders
#ifdef TOOLS_ENABLED
while (scr->placeholders.size()) {
@@ -2468,6 +2459,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
Ref<GDScript> scr = E.key;
+ print_verbose("GDScript: Reloading: " + scr->get_path());
+ scr->load_source_code(scr->get_path());
scr->reload(p_soft_reload);
//restore state if saved
@@ -2515,6 +2508,12 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
#endif
}
+void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+ Array scripts;
+ scripts.push_back(p_script);
+ reload_scripts(scripts, p_soft_reload);
+}
+
void GDScriptLanguage::frame() {
calls = 0;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index e245738070..fdfd79f0fc 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -579,6 +579,7 @@ public:
virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override;
virtual void reload_all_scripts() override;
+ virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) override;
virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
virtual void frame() override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 3fd5b3f519..7026d131e3 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -4908,8 +4908,19 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
result.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
- result.kind = GDScriptParser::DataType::NATIVE;
- result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name;
+ if (ScriptServer::is_global_class(p_property.class_name)) {
+ result.kind = GDScriptParser::DataType::SCRIPT;
+ result.script_path = ScriptServer::get_global_class_path(p_property.class_name);
+ result.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
+
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name));
+ if (scr.is_valid()) {
+ result.script_type = scr;
+ }
+ } else {
+ result.kind = GDScriptParser::DataType::NATIVE;
+ result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ }
} else {
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = p_property.type;
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 9bface6136..f902cb10cc 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -121,7 +121,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
RBMap<MethodBind *, int> method_bind_map;
RBMap<GDScriptFunction *, int> lambdas_map;
-#if DEBUG_ENABLED
+#ifdef DEBUG_ENABLED
// Keep method and property names for pointer and validated operations.
// Used when disassembling the bytecode.
Vector<String> operator_names;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 9ad2ba1914..210e2c3898 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1370,7 +1370,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}
}
-static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
+static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, GDScriptParser::CompletionContext &p_context) {
GDScriptCompletionIdentifier ci;
ci.value = p_value;
ci.type.is_constant = true;
@@ -1392,8 +1392,22 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) {
scr = obj->get_script();
}
if (scr.is_valid()) {
- ci.type.script_type = scr;
+ ci.type.script_path = scr->get_path();
+
+ if (scr->get_path().ends_with(".gd")) {
+ Error err;
+ Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err);
+ if (err == OK) {
+ ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ ci.type.class_type = parser->get_parser()->get_tree();
+ ci.type.kind = GDScriptParser::DataType::CLASS;
+ p_context.dependent_parsers.push_back(parser);
+ return ci;
+ }
+ }
+
ci.type.kind = GDScriptParser::DataType::SCRIPT;
+ ci.type.script_type = scr;
ci.type.native_type = scr->get_instance_base_type();
} else {
ci.type.kind = GDScriptParser::DataType::NATIVE;
@@ -1418,8 +1432,19 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr
ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
ci.type.builtin_type = p_property.type;
if (p_property.type == Variant::OBJECT) {
- ci.type.kind = GDScriptParser::DataType::NATIVE;
- ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ if (ScriptServer::is_global_class(p_property.class_name)) {
+ ci.type.kind = GDScriptParser::DataType::SCRIPT;
+ ci.type.script_path = ScriptServer::get_global_class_path(p_property.class_name);
+ ci.type.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
+
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name));
+ if (scr.is_valid()) {
+ ci.type.script_type = scr;
+ }
+ } else {
+ ci.type.kind = GDScriptParser::DataType::NATIVE;
+ ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ }
} else {
ci.type.kind = GDScriptParser::DataType::BUILTIN;
}
@@ -1481,7 +1506,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (p_expression->is_constant) {
// Already has a value, so just use that.
- r_type = _type_from_variant(p_expression->reduced_value);
+ r_type = _type_from_variant(p_expression->reduced_value, p_context);
switch (p_expression->get_datatype().kind) {
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::CLASS:
@@ -1495,7 +1520,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
switch (p_expression->type) {
case GDScriptParser::Node::LITERAL: {
const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression);
- r_type = _type_from_variant(literal->value);
+ r_type = _type_from_variant(literal->value, p_context);
found = true;
} break;
case GDScriptParser::Node::SELF: {
@@ -1676,7 +1701,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (!which.is_empty()) {
// Try singletons first
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]);
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context);
found = true;
} else {
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
@@ -1727,7 +1752,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) {
- r_type = _type_from_variant(ret);
+ r_type = _type_from_variant(ret, p_context);
found = true;
}
}
@@ -1755,7 +1780,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) {
Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)];
- r_type = _type_from_variant(value);
+ r_type = _type_from_variant(value, p_context);
found = true;
break;
}
@@ -1807,7 +1832,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (base.value.in(index.value)) {
Variant value = base.value.get(index.value);
- r_type = _type_from_variant(value);
+ r_type = _type_from_variant(value, p_context);
found = true;
break;
}
@@ -1862,7 +1887,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
bool valid = false;
Variant res = base_val.get(index.value, &valid);
if (valid) {
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
r_type.value = Variant();
r_type.type.is_constant = false;
found = true;
@@ -1920,7 +1945,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
found = false;
break;
}
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
if (!v1_use_value || !v2_use_value) {
r_type.value = Variant();
r_type.type.is_constant = false;
@@ -2155,7 +2180,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
} else {
Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name));
if (scr.is_valid()) {
- r_type = _type_from_variant(scr);
+ r_type = _type_from_variant(scr, p_context);
r_type.type.is_meta_type = true;
return true;
}
@@ -2165,7 +2190,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
// Check global variables (including autoloads).
if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) {
- r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]);
+ r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name], p_context);
return true;
}
@@ -2218,7 +2243,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
const GDScriptParser::ExpressionNode *init = member.variable->initializer;
if (init->is_constant) {
r_type.value = init->reduced_value;
- r_type = _type_from_variant(init->reduced_value);
+ r_type = _type_from_variant(init->reduced_value, p_context);
return true;
} else if (init->start_line == p_context.current_line) {
return false;
@@ -2245,7 +2270,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
r_type.enumeration = member.m_enum->identifier->name;
return true;
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
- r_type = _type_from_variant(member.enum_value.value);
+ r_type = _type_from_variant(member.enum_value.value, p_context);
return true;
case GDScriptParser::ClassNode::Member::SIGNAL:
r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
@@ -2281,7 +2306,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
HashMap<StringName, Variant> constants;
scr->get_constants(&constants);
if (constants.has(p_identifier)) {
- r_type = _type_from_variant(constants[p_identifier]);
+ r_type = _type_from_variant(constants[p_identifier], p_context);
return true;
}
@@ -2345,7 +2370,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext &
bool valid = false;
Variant res = tmp.get(p_identifier, &valid);
if (valid) {
- r_type = _type_from_variant(res);
+ r_type = _type_from_variant(res, p_context);
r_type.value = Variant();
r_type.type.is_constant = false;
return true;
@@ -3197,6 +3222,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<String> opts;
p_owner->get_argument_options("get_node", 0, &opts);
+ bool for_unique_name = false;
+ if (completion_context.node != nullptr && completion_context.node->type == GDScriptParser::Node::GET_NODE && !static_cast<GDScriptParser::GetNodeNode *>(completion_context.node)->use_dollar) {
+ for_unique_name = true;
+ }
+
for (const String &E : opts) {
r_forced = true;
String opt = E.strip_edges();
@@ -3205,6 +3235,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
// or handle NodePaths which are valid identifiers and don't need quotes.
opt = opt.unquote();
}
+
+ if (for_unique_name) {
+ if (!opt.begins_with("%")) {
+ continue;
+ }
+ opt = opt.substr(1);
+ }
+
// The path needs quotes if it's not a valid identifier (with an exception
// for "/" as path separator, which also doesn't require quotes).
if (!opt.replace("/", "_").is_valid_identifier()) {
@@ -3216,11 +3254,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
options.insert(option.display, option);
}
- // Get autoloads.
- for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
- String path = "/root/" + E.key;
- ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
- options.insert(option.display, option);
+ if (!for_unique_name) {
+ // Get autoloads.
+ for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
+ String path = "/root/" + E.key;
+ ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
+ options.insert(option.display, option);
+ }
}
}
} break;
@@ -3564,7 +3604,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
case GDScriptParser::COMPLETION_ASSIGN:
case GDScriptParser::COMPLETION_CALL_ARGUMENTS:
case GDScriptParser::COMPLETION_IDENTIFIER:
- case GDScriptParser::COMPLETION_PROPERTY_METHOD: {
+ case GDScriptParser::COMPLETION_PROPERTY_METHOD:
+ case GDScriptParser::COMPLETION_SUBSCRIPT: {
GDScriptParser::DataType base_type;
if (context.current_class) {
if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index 95b3be2811..e00b92b752 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -114,7 +114,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
scr->update_exports();
ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
- ScriptEditor::get_singleton()->trigger_live_script_reload();
+ ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path());
}
}
diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md
index 361d586d32..cea251bab5 100644
--- a/modules/gdscript/tests/README.md
+++ b/modules/gdscript/tests/README.md
@@ -6,3 +6,44 @@ and output files.
See the
[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript)
for information about creating and running GDScript integration tests.
+
+# GDScript Autocompletion tests
+
+The `script/completion` folder contains test for the GDScript autocompletion.
+
+Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked.
+
+The config file contains two section:
+
+`[input]` contains keys that configure the test environment. The following keys are possible:
+
+- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build.
+- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test.
+- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened.
+
+`[output]` specifies the expected results for the test. The following key are supported:
+
+- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice.
+- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`.
+- `call_hint: String`: The expected call hint returned by autocompletion.
+- `forced: boolean`: Whether autocompletion is expected to force opening a completion window.
+
+Tests will only test against entries in `[output]` that were specified.
+
+## Writing autocompletion tests
+
+To avoid failing edge cases a certain behaviour needs to be tested multiple times. Some things that tests should account for:
+
+- All possible types: Test with all possible types that apply to the tested behaviour. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.)
+
+ - `BUILTIN`
+ - `NATIVE`
+ - GDScripts (with `class_name` as well as `preload`ed)
+ - C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed)
+ - Autoloads
+
+- Possible contexts: the completion might be placed in different places of the program. e.g:
+ - initializers of class members
+ - directly inside a suite
+ - assignments inside a suite
+ - as parameter to a call
diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
index 4edee46039..27e695d245 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg
@@ -1,4 +1,4 @@
[output]
-expected=[
+include=[
{"display": "autoplay"},
]
diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h
index abc34bd4bf..fd6b5321e6 100644
--- a/modules/gdscript/tests/test_completion.h
+++ b/modules/gdscript/tests/test_completion.h
@@ -128,19 +128,23 @@ static void test_directory(const String &p_dir) {
EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false));
List<Dictionary> include;
- to_dict_list(conf.get_value("result", "include", Array()), include);
+ to_dict_list(conf.get_value("output", "include", Array()), include);
List<Dictionary> exclude;
- to_dict_list(conf.get_value("result", "exclude", Array()), exclude);
+ to_dict_list(conf.get_value("output", "exclude", Array()), exclude);
List<ScriptLanguage::CodeCompletionOption> options;
String call_hint;
bool forced;
Node *owner = nullptr;
- if (dir->file_exists(next.get_basename() + ".tscn")) {
- String project_path = "res://completion";
- Ref<PackedScene> scene = ResourceLoader::load(project_path.path_join(next.get_basename() + ".tscn"), "PackedScene");
+ if (conf.has_section_key("input", "scene")) {
+ Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene");
+ if (scene.is_valid()) {
+ owner = scene->instantiate();
+ }
+ } else if (dir->file_exists(next.get_basename() + ".tscn")) {
+ Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene");
if (scene.is_valid()) {
owner = scene->instantiate();
}
@@ -169,8 +173,8 @@ static void test_directory(const String &p_dir) {
CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'.");
CHECK(include.is_empty());
- String expected_call_hint = conf.get_value("result", "call_hint", call_hint);
- bool expected_forced = conf.get_value("result", "forced", forced);
+ String expected_call_hint = conf.get_value("output", "call_hint", call_hint);
+ bool expected_forced = conf.get_value("output", "forced", forced);
CHECK(expected_call_hint == call_hint);
CHECK(expected_forced == forced);
diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp
index d40ed69915..8588de0752 100644
--- a/modules/gltf/structures/gltf_buffer_view.cpp
+++ b/modules/gltf/structures/gltf_buffer_view.cpp
@@ -94,6 +94,7 @@ void GLTFBufferView::set_indices(bool p_indices) {
}
Vector<uint8_t> GLTFBufferView::load_buffer_view_data(const Ref<GLTFState> p_state) const {
+ ERR_FAIL_COND_V(p_state.is_null(), Vector<uint8_t>());
ERR_FAIL_COND_V_MSG(byte_stride > 0, Vector<uint8_t>(), "Buffer views with byte stride are not yet supported by this method.");
const TypedArray<Vector<uint8_t>> &buffers = p_state->get_buffers();
ERR_FAIL_INDEX_V(buffer, buffers.size(), Vector<uint8_t>());
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index ac6977504a..ef24dc35ca 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -720,11 +720,22 @@ void CSharpLanguage::reload_all_scripts() {
#endif
}
-void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
- (void)p_script; // UNUSED
-
+void CSharpLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
+ bool has_csharp_script = false;
+ for (int i = 0; i < p_scripts.size(); ++i) {
+ Ref<CSharpScript> cs_script = p_scripts[i];
+ if (cs_script.is_valid()) {
+ has_csharp_script = true;
+ break;
+ }
+ }
+
+ if (!has_csharp_script) {
+ return;
+ }
+
#ifdef TOOLS_ENABLED
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
@@ -736,6 +747,12 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
#endif
}
+void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
+ Array scripts;
+ scripts.push_back(p_script);
+ reload_scripts(scripts, p_soft_reload);
+}
+
#ifdef GD_MONO_HOT_RELOAD
bool CSharpLanguage::is_assembly_reloading_needed() {
ERR_FAIL_NULL_V(gdmono, false);
@@ -1074,7 +1091,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
// The script instance could not be instantiated or wasn't in the list of placeholders to replace.
obj->set_script(scr);
-#if DEBUG_ENABLED
+#ifdef DEBUG_ENABLED
// If we reached here, the instantiated script must be a placeholder.
CRASH_COND(!obj->get_script_instance()->is_placeholder());
#endif
@@ -2310,7 +2327,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
p_script->_update_exports();
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
@@ -2666,7 +2683,7 @@ Error CSharpScript::reload(bool p_keep_state) {
_update_exports();
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 41e8d63be1..310cb81929 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -478,6 +478,7 @@ public:
/* TODO? */ void get_public_annotations(List<MethodInfo> *p_annotations) const override {}
void reload_all_scripts() override;
+ void reload_scripts(const Array &p_scripts, bool p_soft_reload) override;
void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
/* LOADER FUNCTIONS */
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
index 03a7dc453c..9674626183 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "G
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Tests", "Godot.SourceGenerators.Tests\Godot.SourceGenerators.Tests.csproj", "{07E6D201-35C9-4463-9B29-D16621EA733D}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
EndProject
Global
@@ -26,6 +28,10 @@ Global
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {07E6D201-35C9-4463-9B29-D16621EA733D}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs
new file mode 100644
index 0000000000..b9f11908e1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/GlobalClass.cs
@@ -0,0 +1,14 @@
+namespace Godot.SourceGenerators.Sample;
+
+[GlobalClass]
+public partial class CustomGlobalClass : GodotObject
+{
+}
+
+// This doesn't works because global classes can't have any generic type parameter.
+/*
+[GlobalClass]
+public partial class CustomGlobalClass<T> : Node
+{
+}
+*/
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
index 3f569ebac3..d0907c1cd4 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
+ <LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs
new file mode 100644
index 0000000000..1e06091e80
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MustBeVariantSamples.cs
@@ -0,0 +1,164 @@
+using System;
+using Godot.Collections;
+using Array = Godot.Collections.Array;
+
+namespace Godot.SourceGenerators.Sample;
+
+public class MustBeVariantMethods
+{
+ public void MustBeVariantMethodCalls()
+ {
+ Method<bool>();
+ Method<char>();
+ Method<sbyte>();
+ Method<byte>();
+ Method<short>();
+ Method<ushort>();
+ Method<int>();
+ Method<uint>();
+ Method<long>();
+ Method<ulong>();
+ Method<float>();
+ Method<double>();
+ Method<string>();
+ Method<Vector2>();
+ Method<Vector2I>();
+ Method<Rect2>();
+ Method<Rect2I>();
+ Method<Transform2D>();
+ Method<Vector3>();
+ Method<Vector3I>();
+ Method<Vector4>();
+ Method<Vector4I>();
+ Method<Basis>();
+ Method<Quaternion>();
+ Method<Transform3D>();
+ Method<Projection>();
+ Method<Aabb>();
+ Method<Color>();
+ Method<Plane>();
+ Method<Callable>();
+ Method<Signal>();
+ Method<GodotObject>();
+ Method<StringName>();
+ Method<NodePath>();
+ Method<Rid>();
+ Method<Dictionary>();
+ Method<Array>();
+ Method<byte[]>();
+ Method<int[]>();
+ Method<long[]>();
+ Method<float[]>();
+ Method<double[]>();
+ Method<string[]>();
+ Method<Vector2[]>();
+ Method<Vector3[]>();
+ Method<Color[]>();
+ Method<GodotObject[]>();
+ Method<StringName[]>();
+ Method<NodePath[]>();
+ Method<Rid[]>();
+
+ // This call fails because generic type is not Variant-compatible.
+ //Method<object>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+
+ public void MustBeVariantClasses()
+ {
+ new ClassWithGenericVariant<bool>();
+ new ClassWithGenericVariant<char>();
+ new ClassWithGenericVariant<sbyte>();
+ new ClassWithGenericVariant<byte>();
+ new ClassWithGenericVariant<short>();
+ new ClassWithGenericVariant<ushort>();
+ new ClassWithGenericVariant<int>();
+ new ClassWithGenericVariant<uint>();
+ new ClassWithGenericVariant<long>();
+ new ClassWithGenericVariant<ulong>();
+ new ClassWithGenericVariant<float>();
+ new ClassWithGenericVariant<double>();
+ new ClassWithGenericVariant<string>();
+ new ClassWithGenericVariant<Vector2>();
+ new ClassWithGenericVariant<Vector2I>();
+ new ClassWithGenericVariant<Rect2>();
+ new ClassWithGenericVariant<Rect2I>();
+ new ClassWithGenericVariant<Transform2D>();
+ new ClassWithGenericVariant<Vector3>();
+ new ClassWithGenericVariant<Vector3I>();
+ new ClassWithGenericVariant<Vector4>();
+ new ClassWithGenericVariant<Vector4I>();
+ new ClassWithGenericVariant<Basis>();
+ new ClassWithGenericVariant<Quaternion>();
+ new ClassWithGenericVariant<Transform3D>();
+ new ClassWithGenericVariant<Projection>();
+ new ClassWithGenericVariant<Aabb>();
+ new ClassWithGenericVariant<Color>();
+ new ClassWithGenericVariant<Plane>();
+ new ClassWithGenericVariant<Callable>();
+ new ClassWithGenericVariant<Signal>();
+ new ClassWithGenericVariant<GodotObject>();
+ new ClassWithGenericVariant<StringName>();
+ new ClassWithGenericVariant<NodePath>();
+ new ClassWithGenericVariant<Rid>();
+ new ClassWithGenericVariant<Dictionary>();
+ new ClassWithGenericVariant<Array>();
+ new ClassWithGenericVariant<byte[]>();
+ new ClassWithGenericVariant<int[]>();
+ new ClassWithGenericVariant<long[]>();
+ new ClassWithGenericVariant<float[]>();
+ new ClassWithGenericVariant<double[]>();
+ new ClassWithGenericVariant<string[]>();
+ new ClassWithGenericVariant<Vector2[]>();
+ new ClassWithGenericVariant<Vector3[]>();
+ new ClassWithGenericVariant<Color[]>();
+ new ClassWithGenericVariant<GodotObject[]>();
+ new ClassWithGenericVariant<StringName[]>();
+ new ClassWithGenericVariant<NodePath[]>();
+ new ClassWithGenericVariant<Rid[]>();
+
+ // This class fails because generic type is not Variant-compatible.
+ //new ClassWithGenericVariant<object>();
+ }
+}
+
+public class ClassWithGenericVariant<[MustBeVariant] T>
+{
+}
+
+public class MustBeVariantAnnotatedMethods
+{
+ [GenericTypeAttribute<string>()]
+ public void MethodWithAttributeOk()
+ {
+ }
+
+ // This method definition fails because generic type is not Variant-compatible.
+ /*
+ [GenericTypeAttribute<object>()]
+ public void MethodWithWrongAttribute()
+ {
+ }
+ */
+}
+
+[GenericTypeAttribute<string>()]
+public class ClassVariantAnnotated
+{
+}
+
+// This class definition fails because generic type is not Variant-compatible.
+/*
+[GenericTypeAttribute<object>()]
+public class ClassNonVariantAnnotated
+{
+}
+*/
+
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class GenericTypeAttribute<[MustBeVariant] T> : Attribute
+{
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs
new file mode 100644
index 0000000000..e3e7373b2e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/CSharpAnalyzerVerifier.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators.Tests;
+
+public static class CSharpAnalyzerVerifier<TAnalyzer>
+where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ public class Test : CSharpAnalyzerTest<TAnalyzer, XUnitVerifier>
+ {
+ public Test()
+ {
+ ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+
+ SolutionTransforms.Add((Solution solution, ProjectId projectId) =>
+ {
+ Project project =
+ solution.GetProject(projectId)!.AddMetadataReference(Constants.GodotSharpAssembly
+ .CreateMetadataReference());
+
+ return project.Solution;
+ });
+ }
+ }
+
+ public static Task Verify(string sources, params DiagnosticResult[] expected)
+ {
+ return MakeVerifier(new string[] { sources }, expected).RunAsync();
+ }
+
+ public static Test MakeVerifier(ICollection<string> sources, params DiagnosticResult[] expected)
+ {
+ var verifier = new Test();
+
+ verifier.TestState.AnalyzerConfigFiles.Add(("/.globalconfig", $"""
+ is_global = true
+ build_property.GodotProjectDir = {Constants.ExecutingAssemblyPath}
+ """));
+
+ verifier.TestState.Sources.AddRange(sources.Select(source =>
+ {
+ return (source, SourceText.From(File.ReadAllText(Path.Combine(Constants.SourceFolderPath, source))));
+ }));
+
+ verifier.ExpectedDiagnostics.AddRange(expected);
+ return verifier;
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs
new file mode 100644
index 0000000000..74d6afceb3
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/GlobalClassAnalyzerTests.cs
@@ -0,0 +1,20 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class GlobalClassAnalyzerTests
+{
+ [Fact]
+ public async void GlobalClassMustDeriveFromGodotObjectTest()
+ {
+ const string GlobalClassGD0401 = "GlobalClass.GD0401.cs";
+ await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0401);
+ }
+
+ [Fact]
+ public async void GlobalClassMustNotBeGenericTest()
+ {
+ const string GlobalClassGD0402 = "GlobalClass.GD0402.cs";
+ await CSharpAnalyzerVerifier<GlobalClassAnalyzer>.Verify(GlobalClassGD0402);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
index e39c14f049..13e54a543f 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/Godot.SourceGenerators.Tests.csproj
@@ -17,6 +17,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="1.1.1" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs
new file mode 100644
index 0000000000..62c602efbb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/MustBeVariantAnalyzerTests.cs
@@ -0,0 +1,20 @@
+using Xunit;
+
+namespace Godot.SourceGenerators.Tests;
+
+public class MustBeVariantAnalyzerTests
+{
+ [Fact]
+ public async void GenericTypeArgumentMustBeVariantTest()
+ {
+ const string MustBeVariantGD0301 = "MustBeVariant.GD0301.cs";
+ await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0301);
+ }
+
+ [Fact]
+ public async void GenericTypeParameterMustBeVariantAnnotatedTest()
+ {
+ const string MustBeVariantGD0302 = "MustBeVariant.GD0302.cs";
+ await CSharpAnalyzerVerifier<MustBeVariantAnalyzer>.Verify(MustBeVariantGD0302);
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs
new file mode 100644
index 0000000000..6e6d3a6f39
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0401.cs
@@ -0,0 +1,22 @@
+using Godot;
+
+// This works because it inherits from GodotObject.
+[GlobalClass]
+public partial class CustomGlobalClass1 : GodotObject
+{
+
+}
+
+// This works because it inherits from an object that inherits from GodotObject
+[GlobalClass]
+public partial class CustomGlobalClass2 : Node
+{
+
+}
+
+// This raises a GD0401 diagnostic error: global classes must inherit from GodotObject
+{|GD0401:[GlobalClass]
+public partial class CustomGlobalClass3
+{
+
+}|}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs
new file mode 100644
index 0000000000..1c0a169841
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/GlobalClass.GD0402.cs
@@ -0,0 +1,15 @@
+using Godot;
+
+// This works because it inherits from GodotObject and it doesn't have any generic type parameter.
+[GlobalClass]
+public partial class CustomGlobalClass : GodotObject
+{
+
+}
+
+// This raises a GD0402 diagnostic error: global classes can't have any generic type parameter
+{|GD0402:[GlobalClass]
+public partial class CustomGlobalClass<T> : GodotObject
+{
+
+}|}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
new file mode 100644
index 0000000000..031039cba1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0301.cs
@@ -0,0 +1,71 @@
+using System;
+using Godot;
+using Godot.Collections;
+using Array = Godot.Collections.Array;
+
+public class MustBeVariantGD0301
+{
+ public void MethodCallsError()
+ {
+ // This raises a GD0301 diagnostic error: object is not Variant (and Method<T> requires a variant generic type).
+ Method<{|GD0301:object|}>();
+ }
+ public void MethodCallsOk()
+ {
+ // All these calls are valid because they are Variant types.
+ Method<bool>();
+ Method<char>();
+ Method<sbyte>();
+ Method<byte>();
+ Method<short>();
+ Method<ushort>();
+ Method<int>();
+ Method<uint>();
+ Method<long>();
+ Method<ulong>();
+ Method<float>();
+ Method<double>();
+ Method<string>();
+ Method<Vector2>();
+ Method<Vector2I>();
+ Method<Rect2>();
+ Method<Rect2I>();
+ Method<Transform2D>();
+ Method<Vector3>();
+ Method<Vector3I>();
+ Method<Vector4>();
+ Method<Vector4I>();
+ Method<Basis>();
+ Method<Quaternion>();
+ Method<Transform3D>();
+ Method<Projection>();
+ Method<Aabb>();
+ Method<Color>();
+ Method<Plane>();
+ Method<Callable>();
+ Method<Signal>();
+ Method<GodotObject>();
+ Method<StringName>();
+ Method<NodePath>();
+ Method<Rid>();
+ Method<Dictionary>();
+ Method<Array>();
+ Method<byte[]>();
+ Method<int[]>();
+ Method<long[]>();
+ Method<float[]>();
+ Method<double[]>();
+ Method<string[]>();
+ Method<Vector2[]>();
+ Method<Vector3[]>();
+ Method<Color[]>();
+ Method<GodotObject[]>();
+ Method<StringName[]>();
+ Method<NodePath[]>();
+ Method<Rid[]>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs
new file mode 100644
index 0000000000..ce182e8c62
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/MustBeVariant.GD0302.cs
@@ -0,0 +1,27 @@
+using Godot;
+
+public class MustBeVariantGD0302
+{
+ public void MethodOk<[MustBeVariant] T>()
+ {
+ // T is guaranteed to be a Variant-compatible type because it's annotated with the [MustBeVariant] attribute, so it can be used here.
+ new ExampleClass<T>();
+ Method<T>();
+ }
+
+ public void MethodFail<T>()
+ {
+ // These two calls raise a GD0302 diagnostic error: T is not valid here because it may not a Variant type and method call and class require it.
+ new ExampleClass<{|GD0302:T|}>();
+ Method<{|GD0302:T|}>();
+ }
+
+ public void Method<[MustBeVariant] T>()
+ {
+ }
+}
+
+public class ExampleClass<[MustBeVariant] T>
+{
+
+}
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 322078423f..05dacd28fb 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -148,7 +148,7 @@ void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) {
}
void godot_icall_Internal_EditorDebuggerNodeReloadScripts() {
- EditorDebuggerNode::get_singleton()->reload_scripts();
+ EditorDebuggerNode::get_singleton()->reload_all_scripts();
}
bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) {
@@ -175,7 +175,7 @@ void godot_icall_Internal_EditorPlugin_AddControlToEditorRunBar(Control *p_contr
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
if (ed) {
- ed->reload_scripts();
+ ed->reload_all_scripts();
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index 57b5b09ebb..63af6ee6e8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -98,11 +98,11 @@ namespace Godot
Vector3 dstMax = with._position + with._size;
return srcMin.X <= dstMin.X &&
- srcMax.X > dstMax.X &&
+ srcMax.X >= dstMax.X &&
srcMin.Y <= dstMin.Y &&
- srcMax.Y > dstMax.Y &&
+ srcMax.Y >= dstMax.Y &&
srcMin.Z <= dstMin.Z &&
- srcMax.Z > dstMax.Z;
+ srcMax.Z >= dstMax.Z;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index 71a35ab809..cf4ac45a9f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -123,8 +123,8 @@ namespace Godot
public readonly bool Encloses(Rect2 b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
- b._position.X + b._size.X < _position.X + _size.X &&
- b._position.Y + b._size.Y < _position.Y + _size.Y;
+ b._position.X + b._size.X <= _position.X + _size.X &&
+ b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index ef7e9eacd8..58560df0c5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -113,8 +113,8 @@ namespace Godot
public readonly bool Encloses(Rect2I b)
{
return b._position.X >= _position.X && b._position.Y >= _position.Y &&
- b._position.X + b._size.X < _position.X + _size.X &&
- b._position.Y + b._size.Y < _position.Y + _size.Y;
+ b._position.X + b._size.X <= _position.X + _size.X &&
+ b._position.Y + b._size.Y <= _position.Y + _size.Y;
}
/// <summary>
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 3518507f8c..0089e9c2a2 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -316,7 +316,7 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) {
}
void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) {
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index aa97534675..ee17a668d7 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -152,7 +152,7 @@ String realpath(const String &p_path) {
}
return result.simplify_path();
-#elif UNIX_ENABLED
+#elif defined(UNIX_ENABLED)
char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr);
if (!resolved_path) {
diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml
index eb7decd30d..1c4da83138 100644
--- a/modules/openxr/doc_classes/OpenXRHand.xml
+++ b/modules/openxr/doc_classes/OpenXRHand.xml
@@ -7,10 +7,14 @@
This node enables OpenXR's hand tracking functionality. The node should be a child node of an [XROrigin3D] node, tracking will update its position to the player's tracked hand Palm joint location (the center of the middle finger's metacarpal bone). This node also updates the skeleton of a properly skinned hand or avatar model.
If the skeleton is a hand (one of the hand bones is the root node of the skeleton), then the skeleton will be placed relative to the hand palm location and the hand mesh and skeleton should be children of the OpenXRHand node.
If the hand bones are part of a full skeleton, then the root of the hand will keep its location with the assumption that IK is used to position the hand and arm.
+ By default the skeleton hand bones are repositioned to match the size of the tracked hand. To preserve the modeled bone sizes change [member bone_update] to apply rotation only.
</description>
<tutorials>
</tutorials>
<members>
+ <member name="bone_update" type="int" setter="set_bone_update" getter="get_bone_update" enum="OpenXRHand.BoneUpdate" default="0">
+ Specify the type of updates to perform on the bone.
+ </member>
<member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0">
Specifies whether this node tracks the left or right hand of the player.
</member>
@@ -52,5 +56,14 @@
<constant name="SKELETON_RIG_MAX" value="2" enum="SkeletonRig">
Maximum supported hands.
</constant>
+ <constant name="BONE_UPDATE_FULL" value="0" enum="BoneUpdate">
+ The skeletons bones are fully updated (both position and rotation) to match the tracked bones.
+ </constant>
+ <constant name="BONE_UPDATE_ROTATION_ONLY" value="1" enum="BoneUpdate">
+ The skeletons bones are only rotated to align with the tracked bones, preserving bone length.
+ </constant>
+ <constant name="BONE_UPDATE_MAX" value="2" enum="BoneUpdate">
+ Maximum supported bone update mode.
+ </constant>
</constants>
</class>
diff --git a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
index 1289183ea4..c3f692185b 100644
--- a/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
+++ b/modules/openxr/extensions/openxr_fb_update_swapchain_extension.cpp
@@ -30,7 +30,7 @@
#include "openxr_fb_update_swapchain_extension.h"
-// always include this as late as possible
+// Always include this as late as possible.
#include "../openxr_platform_inc.h"
OpenXRFBUpdateSwapchainExtension *OpenXRFBUpdateSwapchainExtension::singleton = nullptr;
diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h
index 3b0aa0bce9..5f529829a7 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/openxr_opengl_extension.h
@@ -39,7 +39,7 @@
#include "core/templates/vector.h"
-// always include this as late as possible
+// Always include this as late as possible.
#include "../openxr_platform_inc.h"
class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper {
@@ -65,9 +65,9 @@ private:
#ifdef WIN32
static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl;
-#elif ANDROID_ENABLED
+#elif defined(ANDROID_ENABLED)
static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl;
-#else
+#else // Linux/X11
static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl;
#endif
diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h
index f31621fda0..86c0f327dd 100644
--- a/modules/openxr/extensions/openxr_vulkan_extension.h
+++ b/modules/openxr/extensions/openxr_vulkan_extension.h
@@ -37,7 +37,7 @@
#include "core/templates/vector.h"
-// always include this as late as possible
+// Always include this as late as possible.
#include "../openxr_platform_inc.h"
class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks {
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index 8ce33b55c3..2a4104f6ee 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -49,10 +49,14 @@ void OpenXRHand::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_skeleton_rig", "skeleton_rig"), &OpenXRHand::set_skeleton_rig);
ClassDB::bind_method(D_METHOD("get_skeleton_rig"), &OpenXRHand::get_skeleton_rig);
+ ClassDB::bind_method(D_METHOD("set_bone_update", "bone_update"), &OpenXRHand::set_bone_update);
+ ClassDB::bind_method(D_METHOD("get_bone_update"), &OpenXRHand::get_bone_update);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand");
ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton");
ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton_rig", PROPERTY_HINT_ENUM, "OpenXR,Humanoid"), "set_skeleton_rig", "get_skeleton_rig");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update");
BIND_ENUM_CONSTANT(HAND_LEFT);
BIND_ENUM_CONSTANT(HAND_RIGHT);
@@ -65,6 +69,10 @@ void OpenXRHand::_bind_methods() {
BIND_ENUM_CONSTANT(SKELETON_RIG_OPENXR);
BIND_ENUM_CONSTANT(SKELETON_RIG_HUMANOID);
BIND_ENUM_CONSTANT(SKELETON_RIG_MAX);
+
+ BIND_ENUM_CONSTANT(BONE_UPDATE_FULL);
+ BIND_ENUM_CONSTANT(BONE_UPDATE_ROTATION_ONLY);
+ BIND_ENUM_CONSTANT(BONE_UPDATE_MAX);
}
OpenXRHand::OpenXRHand() {
@@ -134,6 +142,16 @@ OpenXRHand::SkeletonRig OpenXRHand::get_skeleton_rig() const {
return skeleton_rig;
}
+void OpenXRHand::set_bone_update(BoneUpdate p_bone_update) {
+ ERR_FAIL_INDEX(p_bone_update, BONE_UPDATE_MAX);
+
+ bone_update = p_bone_update;
+}
+
+OpenXRHand::BoneUpdate OpenXRHand::get_bone_update() const {
+ return bone_update;
+}
+
Skeleton3D *OpenXRHand::get_skeleton() {
if (!has_node(hand_skeleton)) {
return nullptr;
@@ -346,8 +364,12 @@ void OpenXRHand::_update_skeleton() {
const Quaternion q = inv_quaternions[parent_joint] * quaternions[joint];
const Vector3 p = inv_quaternions[parent_joint].xform(positions[joint] - positions[parent_joint]);
- // and set our pose
- skeleton->set_bone_pose_position(joints[joint].bone, p);
+ // Update the bone position if enabled by update mode.
+ if (bone_update == BONE_UPDATE_FULL) {
+ skeleton->set_bone_pose_position(joints[joint].bone, p);
+ }
+
+ // Always update the bone rotation.
skeleton->set_bone_pose_rotation(joints[joint].bone, q);
}
diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h
index 14eb893bcc..4c77e7277c 100644
--- a/modules/openxr/scene/openxr_hand.h
+++ b/modules/openxr/scene/openxr_hand.h
@@ -61,6 +61,12 @@ public:
SKELETON_RIG_MAX
};
+ enum BoneUpdate {
+ BONE_UPDATE_FULL,
+ BONE_UPDATE_ROTATION_ONLY,
+ BONE_UPDATE_MAX
+ };
+
private:
struct JointData {
int bone = -1;
@@ -74,6 +80,7 @@ private:
MotionRange motion_range = MOTION_RANGE_UNOBSTRUCTED;
NodePath hand_skeleton;
SkeletonRig skeleton_rig = SKELETON_RIG_OPENXR;
+ BoneUpdate bone_update = BONE_UPDATE_FULL;
JointData joints[XR_HAND_JOINT_COUNT_EXT];
@@ -101,11 +108,15 @@ public:
void set_skeleton_rig(SkeletonRig p_skeleton_rig);
SkeletonRig get_skeleton_rig() const;
+ void set_bone_update(BoneUpdate p_bone_update);
+ BoneUpdate get_bone_update() const;
+
void _notification(int p_what);
};
VARIANT_ENUM_CAST(OpenXRHand::Hands)
VARIANT_ENUM_CAST(OpenXRHand::MotionRange)
VARIANT_ENUM_CAST(OpenXRHand::SkeletonRig)
+VARIANT_ENUM_CAST(OpenXRHand::BoneUpdate)
#endif // OPENXR_HAND_H
diff --git a/modules/svg/register_types.cpp b/modules/svg/register_types.cpp
index 7b61749f61..82d816d833 100644
--- a/modules/svg/register_types.cpp
+++ b/modules/svg/register_types.cpp
@@ -34,6 +34,12 @@
#include <thorvg.h>
+#ifdef THREADS_ENABLED
+#define TVG_THREADS 1
+#else
+#define TVG_THREADS 0
+#endif
+
static Ref<ImageLoaderSVG> image_loader_svg;
void initialize_svg_module(ModuleInitializationLevel p_level) {
@@ -42,7 +48,8 @@ void initialize_svg_module(ModuleInitializationLevel p_level) {
}
tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw;
- if (tvg::Initializer::init(tvgEngine, 1) != tvg::Result::Success) {
+
+ if (tvg::Initializer::init(tvgEngine, TVG_THREADS) != tvg::Result::Success) {
return;
}