diff options
Diffstat (limited to 'modules')
65 files changed, 2018 insertions, 2161 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 9b0dc9577b..dff6e41dca 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -494,8 +494,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = ref->get_parser()->head->get_datatype(); } else { result.kind = GDScriptParser::DataType::SCRIPT; - result.native_type = ScriptServer::get_global_class_native_base(first); result.script_type = ResourceLoader::load(path, "Script"); + result.native_type = result.script_type->get_instance_base_type(); result.script_path = path; result.is_constant = true; result.is_meta_type = false; @@ -1162,6 +1162,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (p_function->parameters[i]->default_value->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED @@ -1214,11 +1216,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = p_function->identifier->name.operator String() + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1235,7 +1233,15 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } @@ -2563,8 +2569,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !base_type.is_meta_type && !(callee_type != GDScriptParser::Node::SUBSCRIPT && parser->current_function != nullptr && parser->current_function->is_static)) { - parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, base_type.to_string()); + if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } #endif // DEBUG_ENABLED @@ -2727,21 +2739,13 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return ref->get_parser()->head->get_datatype(); } else { type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::SCRIPT; type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); type.script_type = ResourceLoader::load(path, "Script"); + type.native_type = type.script_type->get_instance_base_type(); type.script_path = path; type.is_constant = true; type.is_meta_type = true; @@ -3125,14 +3129,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { - Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_packed_scene_script(autoload.path, err); - if (err == OK && scr.is_valid()) { - Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path()); - if (singl_parser.is_valid()) { - err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - if (err == OK) { - result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<Script> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } } } } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index f35318e4c6..021504f242 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -260,7 +260,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Ref<GDScript> script; r_error = OK; if (singleton->full_gdscript_cache.has(p_path)) { - script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]); + script = singleton->full_gdscript_cache[p_path]; if (!p_update_from_disk) { return script; } @@ -360,36 +360,10 @@ Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_ singleton->packed_scene_cache[p_path] = scene; singleton->packed_scene_dependencies[p_path].insert(p_owner); - scene->recreate_state(); scene->reload_from_file(); return scene; } -Ref<GDScript> GDScriptCache::get_packed_scene_script(const String &p_path, Error &r_error) { - r_error = OK; - Ref<PackedScene> scene = get_packed_scene(p_path, r_error); - - if (r_error != OK) { - return Ref<GDScript>(); - } - - int node_count = scene->get_state()->get_node_count(); - if (node_count == 0) { - return Ref<GDScript>(); - } - - const int ROOT_NODE = 0; - for (int i = 0; i < scene->get_state()->get_node_property_count(ROOT_NODE); i++) { - if (scene->get_state()->get_node_property_name(ROOT_NODE, i) != SNAME("script")) { - continue; - } - - return scene->get_state()->get_node_property_value(ROOT_NODE, i); - } - - return Ref<GDScript>(); -} - void GDScriptCache::clear_unreferenced_packed_scenes() { if (singleton == nullptr) { return; diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 0f9d87aa67..2195932aa3 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -102,7 +102,6 @@ public: static Error finish_compiling(const String &p_owner); static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); - static Ref<GDScript> get_packed_scene_script(const String &p_path, Error &r_error); static void clear_unreferenced_packed_scenes(); static bool is_destructing() { diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index f0ceb42f89..24241b712b 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1875,6 +1875,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); + bool initialized = false; if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { @@ -1896,15 +1897,23 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + initialized = true; } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } // The `else` branch is for objects, in such case we leave it as `null`. } + + // Assigns a null for the unassigned variables in loops. + if (!initialized && p_block->is_loop) { + codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>()); + } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. @@ -2284,7 +2293,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return ERR_COMPILATION_FAILED; } - ERR_FAIL_COND_V(!base->is_valid(), ERR_BUG); + ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); } p_script->base = base; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 7628bffd22..c02ee99a86 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -3265,15 +3265,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - // Need special checks for assert and preload as they are technically - // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } - if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; @@ -3283,11 +3274,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co GDScriptParser parser; parser.parse(p_code, p_path, true); - GDScriptAnalyzer analyzer(&parser); - analyzer.analyze(); GDScriptParser::CompletionContext context = parser.get_completion_context(); + // Allows class functions with the names like built-ins to be handled properly. + if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { + // Need special checks for assert and preload as they are technically + // keywords, so are not registered in GDScriptUtilityFunctions. + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); + if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; ClassDB::get_integer_constant(context.current_class->extends[0], p_symbol, &success); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 7f2f49f336..f2aafe9f0c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1835,9 +1835,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } - suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + n_for->loop->is_loop = true; complete_extents(n_for); // Reset break/continue state. @@ -2169,6 +2169,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + n_while->loop->is_loop = true; complete_extents(n_while); // Reset break/continue state. @@ -3740,6 +3741,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + if (p_annotation->name == SNAME("@export_range")) { + if (export_type.builtin_type == Variant::INT) { + variable->export_info.type = Variant::INT; + } + } + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); @@ -3779,15 +3786,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } break; case GDScriptParser::DataType::CLASS: - // Can assume type is a global GDScript class. if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else { push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable); return false; @@ -3796,16 +3802,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node break; case GDScriptParser::DataType::SCRIPT: { StringName class_name; - if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + StringName native_base; + if (export_type.script_type.is_valid()) { class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + native_base = export_type.script_type->get_instance_base_type(); } if (class_name == StringName()) { Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); if (script.is_valid()) { class_name = script->get_language()->get_global_class_name(export_type.script_path); + native_base = script->get_instance_base_type(); } } - if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = class_name; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index f9a1c5a697..d092a2a5e9 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1055,12 +1055,12 @@ public: HashMap<StringName, int> locals_indices; FunctionNode *parent_function = nullptr; - ForNode *parent_for = nullptr; IfNode *parent_if = nullptr; bool has_return = false; bool has_continue = false; bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + bool is_loop = false; bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 39f4c976a4..551973140d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "editor/doc_tools.h" +#include "editor/editor_help.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 7f42643c8f..f59983ca90 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -71,27 +71,38 @@ void init_autoloads() { continue; } - Ref<Resource> res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - Ref<PackedScene> scn = res; - Ref<Script> script = res; - if (scn.is_valid()) { - n = scn->instantiate(); - } else if (script.is_valid()) { - StringName ibt = script->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit from Node: " + info.path); + if (ResourceLoader::get_resource_type(info.path) == "PackedScene") { + // Cache the scene reference before loading it (for cyclic references) + Ref<PackedScene> scn; + scn.instantiate(); + scn->set_path(info.path); + scn->reload_from_file(); + ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path)); + + if (scn.is_valid()) { + n = scn->instantiate(); + } + } else { + Ref<Resource> res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path)); - Object *obj = ClassDB::instantiate(ibt); + Ref<Script> scr = res; + if (scr.is_valid()) { + StringName ibt = scr->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path)); - ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); + Object *obj = ClassDB::instantiate(ibt); - n = Object::cast_to<Node>(obj); - n->set_script(script); + ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(scr); + } } - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path)); n->set_name(info.name); for (int i = 0; i < ScriptServer::get_language_count(); i++) { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". 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 665c229339..c70a1df10d 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 "int my_function(int = default)". +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/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out index 5b22739a93..61004ff627 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function()". +The function signature doesn't match the parent. Parent signature is "my_function() -> int". diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 588015de62..f313f4b28f 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -14,7 +14,6 @@ <param index="1" name="base_path" type="String" /> <param index="2" name="state" type="GLTFState" /> <param index="3" name="flags" type="int" default="0" /> - <param index="4" name="bake_fps" type="int" default="30" /> <description> Takes a [PackedByteArray] defining a gLTF and returns a [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty. @@ -25,8 +24,7 @@ <param index="0" name="path" type="String" /> <param index="1" name="state" type="GLTFState" /> <param index="2" name="flags" type="int" default="0" /> - <param index="3" name="bake_fps" type="int" default="30" /> - <param index="4" name="base_path" type="String" default="""" /> + <param index="3" name="base_path" type="String" default="""" /> <description> Takes a path to a gLTF file and returns a [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty. @@ -37,7 +35,6 @@ <param index="0" name="node" type="Node" /> <param index="1" name="state" type="GLTFState" /> <param index="2" name="flags" type="int" default="0" /> - <param index="3" name="bake_fps" type="int" default="30" /> <description> Takes a Godot Engine scene node and returns a [GLTFState] object through the [param state] parameter. </description> @@ -52,7 +49,8 @@ <method name="generate_scene"> <return type="Node" /> <param index="0" name="state" type="GLTFState" /> - <param index="1" name="bake_fps" type="int" default="30" /> + <param index="1" name="bake_fps" type="float" default="30" /> + <param index="2" name="trimming" type="bool" default="false" /> <description> Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node. </description> diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 95db1c0965..0c0b134bd1 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -85,7 +85,7 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { state.instantiate(); int32_t flags = 0; flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; - Error err = doc->append_from_scene(root, state, flags, 30.0f); + Error err = doc->append_from_scene(root, state, flags); if (err != OK) { ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err))); } diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 20c9508474..7007ea5d13 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -41,6 +41,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "main/main.h" +#include "scene/gui/line_edit.h" #include "scene/main/node.h" #include "scene/resources/animation.h" @@ -58,7 +59,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) } Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { // Get global paths for source and sink. @@ -227,14 +228,14 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { base_dir = sink.get_base_dir(); } - Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, p_bake_fps, base_dir); + Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { if (r_err) { *r_err = FAILED; } return nullptr; } - return gltf->generate_scene(state, p_bake_fps); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); } Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index a1485ff82e..fe687f19fc 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -66,7 +66,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index 017a44cccf..14f2117413 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -49,7 +49,7 @@ void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) co } Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { // Get global paths for source and sink. @@ -95,14 +95,14 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t Ref<GLTFState> state; state.instantiate(); print_verbose(vformat("glTF path: %s", sink)); - Error err = gltf->append_from_file(sink, state, p_flags, p_bake_fps); + Error err = gltf->append_from_file(sink, state, p_flags); if (err != OK) { if (r_err) { *r_err = FAILED; } return nullptr; } - return gltf->generate_scene(state, p_bake_fps); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); } Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation, diff --git a/modules/gltf/editor/editor_scene_importer_fbx.h b/modules/gltf/editor/editor_scene_importer_fbx.h index b0039b1c8f..6bf9f3e033 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.h +++ b/modules/gltf/editor/editor_scene_importer_fbx.h @@ -45,7 +45,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 161808aade..3cf49a3046 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -47,13 +47,13 @@ void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) c } Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { Ref<GLTFDocument> doc; doc.instantiate(); Ref<GLTFState> state; state.instantiate(); - Error err = doc->append_from_file(p_path, state, p_flags, p_bake_fps); + Error err = doc->append_from_file(p_path, state, p_flags); if (err != OK) { if (r_err) { *r_err = err; @@ -63,7 +63,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t if (p_options.has("animation/import")) { state->set_create_animations(bool(p_options["animation/import"])); } - return doc->generate_scene(state, p_bake_fps); + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); } #endif // TOOLS_ENABLED diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index edca038532..c0582b26c1 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -45,7 +45,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; }; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index eb8f7e5ebc..735e35ac1e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5860,7 +5860,7 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T ERR_FAIL_V(p_values[0]); } -void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { +void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const float bake_fps, const bool trimming) { Ref<GLTFAnimation> anim = state->animations[index]; String anim_name = anim->get_name(); @@ -5877,7 +5877,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, animation->set_loop_mode(Animation::LOOP_LINEAR); } - float length = 0.0; + double anim_start = trimming ? INFINITY : 0.0; + double anim_end = 0.0; for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) { const GLTFAnimation::Track &track = track_i.value; @@ -5907,19 +5908,40 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, transform_node_path = node_path; } - for (int i = 0; i < track.rotation_track.times.size(); i++) { - length = MAX(length, track.rotation_track.times[i]); - } - for (int i = 0; i < track.position_track.times.size(); i++) { - length = MAX(length, track.position_track.times[i]); - } - for (int i = 0; i < track.scale_track.times.size(); i++) { - length = MAX(length, track.scale_track.times[i]); - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - length = MAX(length, track.weight_tracks[i].times[j]); + if (trimming) { + for (int i = 0; i < track.rotation_track.times.size(); i++) { + anim_start = MIN(anim_start, track.rotation_track.times[i]); + anim_end = MAX(anim_end, track.rotation_track.times[i]); + } + for (int i = 0; i < track.position_track.times.size(); i++) { + anim_start = MIN(anim_start, track.position_track.times[i]); + anim_end = MAX(anim_end, track.position_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + anim_start = MIN(anim_start, track.scale_track.times[i]); + anim_end = MAX(anim_end, track.scale_track.times[i]); + } + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + anim_start = MIN(anim_start, track.weight_tracks[i].times[j]); + anim_end = MAX(anim_end, track.weight_tracks[i].times[j]); + } + } + } else { + // If you don't use trimming and the first key time is not at 0.0, fake keys will be inserted. + for (int i = 0; i < track.rotation_track.times.size(); i++) { + anim_end = MAX(anim_end, track.rotation_track.times[i]); + } + for (int i = 0; i < track.position_track.times.size(); i++) { + anim_end = MAX(anim_end, track.position_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + anim_end = MAX(anim_end, track.scale_track.times[i]); + } + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + anim_end = MAX(anim_end, track.weight_tracks[i].times[j]); + } } } @@ -5988,10 +6010,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } - //first determine animation length - const double increment = 1.0 / bake_fps; - double time = 0.0; + double time = anim_start; Vector3 base_pos; Quaternion base_rot; @@ -6017,26 +6037,26 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (position_idx >= 0) { pos = _interpolate_track<Vector3>(track.position_track.times, track.position_track.values, time, track.position_track.interpolation); - animation->position_track_insert_key(position_idx, time, pos); + animation->position_track_insert_key(position_idx, time - anim_start, pos); } if (rotation_idx >= 0) { rot = _interpolate_track<Quaternion>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); - animation->rotation_track_insert_key(rotation_idx, time, rot); + animation->rotation_track_insert_key(rotation_idx, time - anim_start, rot); } if (scale_idx >= 0) { scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); - animation->scale_track_insert_key(scale_idx, time, scale); + animation->scale_track_insert_key(scale_idx, time - anim_start, scale); } if (last) { break; } time += increment; - if (time >= length) { + if (time >= anim_end) { last = true; - time = length; + time = anim_end; } } } @@ -6071,21 +6091,21 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, bool last = false; while (true) { real_t blend = _interpolate_track<real_t>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); - animation->blend_shape_track_insert_key(track_idx, time, blend); + animation->blend_shape_track_insert_key(track_idx, time - anim_start, blend); if (last) { break; } time += increment; - if (time >= length) { + if (time >= anim_end) { last = true; - time = length; + time = anim_end; } } } } } - animation->set_length(length); + animation->set_length(anim_end - anim_start); Ref<AnimationLibrary> library; if (!ap->has_animation_library("")) { @@ -6585,7 +6605,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } -Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps) { +Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f) { Error err; if (f.is_null()) { return FAILED; @@ -6636,7 +6656,7 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> } } - err = _parse_gltf_state(state, p_path, p_bake_fps); + err = _parse_gltf_state(state, p_path); ERR_FAIL_COND_V(err != OK, err); return OK; @@ -6754,14 +6774,14 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { } void GLTFDocument::_bind_methods() { - ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "bake_fps", "base_path"), - &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(30), DEFVAL(String())); - ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags", "bake_fps"), - &GLTFDocument::append_from_buffer, DEFVAL(0), DEFVAL(30)); - ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags", "bake_fps"), - &GLTFDocument::append_from_scene, DEFVAL(0), DEFVAL(30)); - ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps"), - &GLTFDocument::generate_scene, DEFVAL(30)); + ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"), + &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags"), + &GLTFDocument::append_from_buffer, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags"), + &GLTFDocument::append_from_scene, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming"), + &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false)); ClassDB::bind_method(D_METHOD("generate_buffer", "state"), &GLTFDocument::generate_buffer); ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"), @@ -6870,7 +6890,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> state, const String &p_pa return OK; } -Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { +Node *GLTFDocument::generate_scene(Ref<GLTFState> state, float p_bake_fps, bool p_trimming) { ERR_FAIL_NULL_V(state, nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); Error err = OK; @@ -6884,7 +6904,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { root->add_child(ap, true); ap->set_owner(root); for (int i = 0; i < state->animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps); + _import_animation(state, ap, i, p_bake_fps, p_trimming); } } for (KeyValue<GLTFNodeIndex, Node *> E : state->scene_nodes) { @@ -6910,7 +6930,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { return root; } -Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) { +Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32_t p_flags) { ERR_FAIL_COND_V(state.is_null(), FAILED); state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; @@ -6930,7 +6950,7 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32 return OK; } -Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) { +Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> state, uint32_t p_flags) { ERR_FAIL_COND_V(state.is_null(), FAILED); // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire Error err = FAILED; @@ -6941,7 +6961,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa file_access.instantiate(); file_access->open_custom(p_bytes.ptr(), p_bytes.size()); state->base_path = p_base_path.get_base_dir(); - err = _parse(state, state->base_path, file_access, p_bake_fps); + err = _parse(state, state->base_path, file_access); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); @@ -6951,7 +6971,7 @@ Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_pa return OK; } -Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps) { +Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_search_path) { Error err; /* PARSE EXTENSIONS */ @@ -7047,7 +7067,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_sear return OK; } -Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, int32_t p_bake_fps, String p_base_path) { +Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) { // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire if (r_state == Ref<GLTFState>()) { r_state.instantiate(); @@ -7064,7 +7084,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint base_path = p_path.get_base_dir(); } r_state->base_path = base_path; - err = _parse(r_state, base_path, f, p_bake_fps); + err = _parse(r_state, base_path, f); ERR_FAIL_COND_V(err != OK, err); for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 5a0e4ff498..6eb38354a2 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -292,17 +292,17 @@ private: static float get_max_component(const Color &p_color); public: - Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30, String p_base_path = String()); - Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); - Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); + Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String()); + Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0); + Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0); public: - Node *generate_scene(Ref<GLTFState> state, int32_t p_bake_fps = 30.0f); + Node *generate_scene(Ref<GLTFState> state, float p_bake_fps = 30.0f, bool p_trimming = false); PackedByteArray generate_buffer(Ref<GLTFState> state); Error write_to_filesystem(Ref<GLTFState> state, const String &p_path); public: - Error _parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps); + Error _parse_gltf_state(Ref<GLTFState> state, const String &p_search_path); Error _parse_gltf_extensions(Ref<GLTFState> state); void _process_mesh_instances(Ref<GLTFState> state, Node *scene_root); void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent, @@ -310,7 +310,7 @@ public: const GLTFNodeIndex node_index); void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index); void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap, - const GLTFAnimationIndex index, const int bake_fps); + const GLTFAnimationIndex index, const float bake_fps, const bool trimming); void _convert_mesh_instances(Ref<GLTFState> state); GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera3D *p_camera); void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node); @@ -368,7 +368,7 @@ public: void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, String p_animation_track_name); Error _serialize(Ref<GLTFState> state, const String &p_path); - Error _parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps); + Error _parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f); }; #endif // GLTF_DOCUMENT_H diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index c8aedc8b92..89ef9ddd90 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -40,6 +40,8 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" #include "scene/main/window.h" void GridMapEditor::_node_removed(Node *p_node) { diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 345d2e4694..cfb9a0fbfb 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -46,6 +46,7 @@ #include "editor/editor_internal_calls.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/inspector_dock.h" #include "editor/node_dock.h" #include "editor/script_templates/templates.gen.h" #endif @@ -710,6 +711,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } + if (!Engine::get_singleton()->is_editor_hint()) { + // We disable collectible assemblies in the game player, because the limitations cause + // issues with mocking libraries. As such, we can only reload assemblies in the editor. + return; + } + // TODO: // Currently, this reloads all scripts, including those whose class is not part of the // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs index 3020cfbc50..eb83833b40 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs @@ -12,6 +12,95 @@ namespace Godot.SourceGenerators.Sample [SuppressMessage("ReSharper", "InconsistentNaming")] public partial class ExportedProperties : Godot.Object { + // Do not generate default value + private String _notGenerate_Property_String = new string("not generate"); + [Export] + public String NotGenerate_Complex_Lamda_Property + { + get => _notGenerate_Property_String + Convert.ToInt32("1"); + set => _notGenerate_Property_String = value; + } + + [Export] + public String NotGenerate_Lamda_NoField_Property + { + get => new string("not generate"); + set => _notGenerate_Property_String = value; + } + + [Export] + public String NotGenerate_Complex_Return_Property + { + get + { + return _notGenerate_Property_String + Convert.ToInt32("1"); + } + set + { + _notGenerate_Property_String = value; + } + } + + private int _notGenerate_Property_Int = 1; + [Export] + public string NotGenerate_Returns_Property + { + get + { + if (_notGenerate_Property_Int == 1) + { + return "a"; + } + else + { + return "b"; + } + } + set + { + _notGenerate_Property_Int = value == "a" ? 1 : 2; + } + } + + // Full Property + private String _fullProperty_String = "FullProperty_String"; + [Export] + public String FullProperty_String + { + get + { + return _fullProperty_String; + } + set + { + _fullProperty_String = value; + } + } + + private String _fullProperty_String_Complex = new string("FullProperty_String_Complex") + Convert.ToInt32("1"); + [Export] + public String FullProperty_String_Complex + { + get + { + return _fullProperty_String_Complex; + } + set + { + _fullProperty_String_Complex = value; + } + } + + // Lamda Property + private String _lamdaProperty_String = "LamdaProperty_String"; + [Export] + public String LamdaProperty_String + { + get => _lamdaProperty_String; + set => _lamdaProperty_String = value; + } + + // Auto Property [Export] private Boolean property_Boolean { get; set; } = true; [Export] private Char property_Char { get; set; } = 'f'; [Export] private SByte property_SByte { get; set; } = 10; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index e28788ec0b..4eed2d7b7b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -14,7 +14,7 @@ namespace Godot.SourceGenerators { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' which is a subclass of '{GodotClasses.Object}'"; string description = $"{message}. Subclasses of '{GodotClasses.Object}' " + "must be declared with the partial modifier."; @@ -41,7 +41,7 @@ namespace Godot.SourceGenerators .GetDeclaredSymbol(outerTypeDeclSyntax); string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? - namedTypeSymbol.FullQualifiedName() : + namedTypeSymbol.FullQualifiedNameOmitGlobal() : "type not found"; string message = diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 9e3add4262..7008fb638f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -149,13 +149,6 @@ namespace Godot.SourceGenerators }; } - private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = - SymbolDisplayFormat.FullyQualifiedFormat - .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - - public static string FullQualifiedName(this ITypeSymbol symbol) - => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); - public static string NameWithTypeParameters(this INamedTypeSymbol symbol) { return symbol.IsGenericType ? @@ -163,25 +156,39 @@ namespace Godot.SourceGenerators symbol.Name; } - public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol) + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included); + + public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol) => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); - public static string FullQualifiedName(this ISymbol symbol) - => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal); public static string FullQualifiedSyntax(this SyntaxNode node, SemanticModel sm) { StringBuilder sb = new(); - FullQualifiedSyntax_(node, sm, sb, true); + FullQualifiedSyntax(node, sm, sb, true); return sb.ToString(); } - private static void FullQualifiedSyntax_(SyntaxNode node, SemanticModel sm, StringBuilder sb, bool isFirstNode) + private static void FullQualifiedSyntax(SyntaxNode node, SemanticModel sm, StringBuilder sb, bool isFirstNode) { if (node is NameSyntax ns && isFirstNode) { SymbolInfo nameInfo = sm.GetSymbolInfo(ns); - sb.Append(nameInfo.Symbol?.FullQualifiedName() ?? ns.ToString()); + sb.Append(nameInfo.Symbol?.ToDisplayString(FullyQualifiedFormatIncludeGlobal) ?? ns.ToString()); return; } @@ -195,7 +202,7 @@ namespace Godot.SourceGenerators if (child.IsNode) { - FullQualifiedSyntax_(child.AsNode()!, sm, sb, isFirstNode: innerIsFirstNode); + FullQualifiedSyntax(child.AsNode()!, sm, sb, isFirstNode: innerIsFirstNode); innerIsFirstNode = false; } else diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index bd40675fd3..4fdd40f638 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -220,7 +220,7 @@ namespace Godot.SourceGenerators _ => null }; case "Collections" - when type.ContainingNamespace?.FullQualifiedName() == "Godot.Collections": + when type.ContainingNamespace?.FullQualifiedNameOmitGlobal() == "Godot.Collections": return type switch { { Name: "Dictionary" } => @@ -367,7 +367,7 @@ namespace Godot.SourceGenerators MarshalType.SignalInfo => source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"), MarshalType.Enum => - source.Append("(", typeSymbol.FullQualifiedName(), + source.Append("(", typeSymbol.FullQualifiedNameIncludeGlobal(), ")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"), MarshalType.ByteArray => source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"), @@ -389,7 +389,7 @@ namespace Godot.SourceGenerators source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"), MarshalType.GodotObjectOrDerivedArray => source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<", - ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"), + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")"), MarshalType.SystemArrayOfStringName => source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"), MarshalType.SystemArrayOfNodePath => @@ -399,7 +399,7 @@ namespace Godot.SourceGenerators MarshalType.Variant => source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"), MarshalType.GodotObjectOrDerived => - source.Append("(", typeSymbol.FullQualifiedName(), + source.Append("(", typeSymbol.FullQualifiedNameIncludeGlobal(), ")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"), MarshalType.StringName => source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"), @@ -413,11 +413,11 @@ namespace Godot.SourceGenerators source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"), MarshalType.GodotGenericDictionary => source.Append(VariantUtils, ".ConvertToDictionaryObject<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", - ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"), + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")"), MarshalType.GodotGenericArray => source.Append(VariantUtils, ".ConvertToArrayObject<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"), + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")"), _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, "Received unexpected marshal type") }; @@ -578,7 +578,7 @@ namespace Godot.SourceGenerators MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"), MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"), MarshalType.Enum => - source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"), + source.Append("(", typeSymbol.FullQualifiedNameIncludeGlobal(), ")", inputExpr, ".AsInt64()"), MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"), MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"), MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"), @@ -589,23 +589,23 @@ namespace Godot.SourceGenerators MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"), MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"), MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<", - ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"), + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedNameIncludeGlobal(), ">()"), MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"), MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"), MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"), MarshalType.Variant => source.Append(inputExpr), MarshalType.GodotObjectOrDerived => source.Append("(", - typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"), + typeSymbol.FullQualifiedNameIncludeGlobal(), ")", inputExpr, ".AsGodotObject()"), MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"), MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"), MarshalType.RID => source.Append(inputExpr, ".AsRID()"), MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"), MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"), MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", - ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"), + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedNameIncludeGlobal(), ">()"), MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"), + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ">()"), _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, "Received unexpected marshal type") }; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index d5d80df643..2f51018293 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -80,13 +80,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptMethods.generated"; var source = new StringBuilder(); @@ -135,7 +135,7 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class MethodName : {symbol.BaseType.FullQualifiedName()}.MethodName {{\n"); + source.Append($" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup @@ -146,7 +146,7 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(methodName); source.Append(" = \""); source.Append(methodName); @@ -159,7 +159,7 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") .Append(listType) @@ -248,7 +248,7 @@ namespace Godot.SourceGenerators AppendPropertyInfo(source, methodInfo.ReturnVal); - source.Append(", flags: (Godot.MethodFlags)") + source.Append(", flags: (global::Godot.MethodFlags)") .Append((int)methodInfo.Flags) .Append(", arguments: "); @@ -276,15 +276,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append("new(type: (Godot.Variant.Type)") + source.Append("new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: \"") .Append(propertyInfo.Name) - .Append("\", hint: (Godot.PropertyHint)") + .Append("\", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index ccfb405d26..fb32f6192f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -92,11 +92,11 @@ namespace Godot.SourceGenerators INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptPath.generated"; var source = new StringBuilder(); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 1198c633d9..252f162b0c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -66,13 +66,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptProperties.generated"; var source = new StringBuilder(); @@ -124,14 +124,14 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class PropertyName : {symbol.BaseType.FullQualifiedName()}.PropertyName {{\n"); + source.Append($" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var property in godotClassProperties) { string propertyName = property.PropertySymbol.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(propertyName); source.Append(" = \""); source.Append(propertyName); @@ -141,7 +141,7 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(fieldName); source.Append(" = \""); source.Append(fieldName); @@ -216,7 +216,7 @@ namespace Godot.SourceGenerators // Generate GetGodotPropertyList - string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; source.Append(" internal new static ") .Append(dictionaryType) @@ -292,7 +292,7 @@ namespace Godot.SourceGenerators source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") - .Append(" ") + .Append(" this.") .Append(propertyMemberName) .Append(" = ") .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType) @@ -317,7 +317,7 @@ namespace Godot.SourceGenerators .Append(propertyMemberName) .Append(") {\n") .Append(" value = ") - .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType) + .AppendManagedToNativeVariantExpr("this." + propertyMemberName, propertyMarshalType) .Append(";\n") .Append(" return true;\n") .Append(" }\n"); @@ -340,15 +340,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append(" properties.Add(new(type: (Godot.Variant.Type)") + source.Append(" properties.Add(new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: PropertyName.") .Append(propertyInfo.Name) - .Append(", hint: (Godot.PropertyHint)") + .Append(", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 9a18ba3ab2..63339b3487 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; @@ -66,21 +67,17 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptPropertyDefVal.generated"; var source = new StringBuilder(); - source.Append("using Godot;\n"); - source.Append("using Godot.NativeInterop;\n"); - source.Append("\n"); - if (hasNamespace) { source.Append("namespace "); @@ -163,19 +160,68 @@ namespace Godot.SourceGenerators continue; } - // TODO: Detect default value from simple property getters (currently we only detect from initializers) - - EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences - .Select(r => r.GetSyntax() as PropertyDeclarationSyntax) - .Select(s => s?.Initializer ?? null) - .FirstOrDefault(); + var propertyDeclarationSyntax = property.DeclaringSyntaxReferences + .Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault(); // Fully qualify the value to avoid issues with namespaces. string? value = null; - if (initializer != null) + if (propertyDeclarationSyntax != null) { - var sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); - value = initializer.Value.FullQualifiedSyntax(sm); + if (propertyDeclarationSyntax.Initializer != null) + { + var sm = context.Compilation.GetSemanticModel(propertyDeclarationSyntax.Initializer.SyntaxTree); + value = propertyDeclarationSyntax.Initializer.Value.FullQualifiedSyntax(sm); + } + else + { + var propertyGet = propertyDeclarationSyntax.AccessorList?.Accessors.Where(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)).FirstOrDefault(); + if (propertyGet != null) + { + if (propertyGet.ExpressionBody != null) + { + if (propertyGet.ExpressionBody.Expression is IdentifierNameSyntax identifierNameSyntax) + { + var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree); + var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol; + EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + if (initializer != null) + { + sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); + value = initializer.Value.FullQualifiedSyntax(sm); + } + } + } + else + { + var returns = propertyGet.DescendantNodes().OfType<ReturnStatementSyntax>(); + if (returns.Count() == 1) + {// Generate only single return + var returnStatementSyntax = returns.Single(); + if (returnStatementSyntax.Expression is IdentifierNameSyntax identifierNameSyntax) + { + var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree); + var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol; + EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + if (initializer != null) + { + sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); + value = initializer.Value.FullQualifiedSyntax(sm); + } + } + } + } + } + } } exportedMembers.Add(new ExportedPropertyMetadata( @@ -231,7 +277,7 @@ namespace Godot.SourceGenerators { source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>"; + string dictionaryType = "System.Collections.Generic.Dictionary<Godot.StringName, object>"; source.Append("#if TOOLS\n"); source.Append(" internal new static "); @@ -249,7 +295,7 @@ namespace Godot.SourceGenerators string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value"); source.Append(" "); - source.Append(exportedMember.TypeSymbol.FullQualifiedName()); + source.Append(exportedMember.TypeSymbol.FullQualifiedNameIncludeGlobal()); source.Append(" "); source.Append(defaultValueLocalName); source.Append(" = "); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 11e0a6fa21..ed877cbd17 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -66,13 +66,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptSerialization.generated"; var source = new StringBuilder(); @@ -241,7 +241,7 @@ namespace Godot.SourceGenerators foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; - string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName(); + string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal(); source.Append(" if (info.TryGetSignalEventDelegate<") .Append(signalDelegateQualifiedName) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 50196b84f0..119cc9d4f0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -75,13 +75,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptSignals.generated"; var source = new StringBuilder(); @@ -176,14 +176,14 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class SignalName : {symbol.BaseType.FullQualifiedName()}.SignalName {{\n"); + source.Append($" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(signalName); source.Append(" = \""); source.Append(signalName); @@ -196,7 +196,7 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") .Append(listType) @@ -231,15 +231,15 @@ namespace Godot.SourceGenerators // as it doesn't emit the signal, only the event delegates. This can confuse users. // Maybe we should directly connect the delegates, as we do with native signals? source.Append(" private ") - .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()) .Append(" backing_") .Append(signalName) .Append(";\n"); - source.Append($" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedName()}\"/>\n"); + source.Append($" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n"); source.Append(" public event ") - .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()) .Append(" ") .Append(signalName) .Append(" {\n") @@ -300,7 +300,7 @@ namespace Godot.SourceGenerators AppendPropertyInfo(source, methodInfo.ReturnVal); - source.Append(", flags: (Godot.MethodFlags)") + source.Append(", flags: (global::Godot.MethodFlags)") .Append((int)methodInfo.Flags) .Append(", arguments: "); @@ -328,15 +328,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append("new(type: (Godot.Variant.Type)") + source.Append("new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: \"") .Append(propertyInfo.Name) - .Append("\", hint: (Godot.PropertyHint)") + .Append("\", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index b90321b586..9185506776 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2274,7 +2274,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(");\n"); // Generate Callable trampoline for the delegate - p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline" + p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline" << "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n" << INDENT1 "{\n" << INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n" @@ -2289,9 +2289,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output << ","; } - // TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant]. - p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<" - << arg_type->cs_type << ">()(args[" << itos(idx) << "])"; + p_output << sformat(arg_type->cs_variant_to_managed, + "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name); idx++; } @@ -2543,15 +2542,13 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, << INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n"; r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n" - << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() :\n" + << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold] :\n" << INDENT3 "new godot_variant.movable[vararg_length];\n"; r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n" << INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n" << INDENT3 "new IntPtr[total_length];\n"; - r_output << INDENT2 "using var variantSpanDisposer = new VariantSpanDisposer(varargs_span);\n"; - r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n" << INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = " "&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n" diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs index 16e96c725a..d3726d69f0 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs @@ -12,7 +12,7 @@ internal static class Common { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedName()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " + "must be declared with the partial modifier."; @@ -39,7 +39,7 @@ internal static class Common .GetDeclaredSymbol(outerTypeDeclSyntax); string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? - namedTypeSymbol.FullQualifiedName() : + namedTypeSymbol.FullQualifiedNameOmitGlobal() : "type not found"; string message = diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs index fac362479a..37f7005d01 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -94,13 +94,6 @@ internal static class ExtensionMethods }; } - private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = - SymbolDisplayFormat.FullyQualifiedFormat - .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - - public static string FullQualifiedName(this ITypeSymbol symbol) - => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); - public static string NameWithTypeParameters(this INamedTypeSymbol symbol) { return symbol.IsGenericType ? @@ -108,8 +101,25 @@ internal static class ExtensionMethods symbol.Name; } - public static string FullQualifiedName(this INamespaceSymbol symbol) - => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included); + + public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal); public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) => qualifiedName diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs index da578309bc..3226ca79e5 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -96,7 +96,7 @@ internal class GenerateUnmanagedCallbacksAttribute : Attribute INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -144,7 +144,7 @@ using Godot.NativeInterop; source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); source.Append($"unsafe partial class {symbol.Name}\n"); source.Append("{\n"); - source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); + source.Append($" private static {data.FuncStructSymbol.FullQualifiedNameIncludeGlobal()} _unmanagedCallbacks;\n\n"); foreach (var callback in data.Methods) { @@ -159,7 +159,7 @@ using Godot.NativeInterop; source.Append("static "); source.Append("partial "); - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(callback.Name); source.Append('('); @@ -228,7 +228,7 @@ using Godot.NativeInterop; if (!callback.ReturnsVoid) { if (methodSourceAfterCall.Length != 0) - source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); + source.Append($"{callback.ReturnType.FullQualifiedNameIncludeGlobal()} ret = "); else source.Append("return "); } @@ -267,7 +267,7 @@ using Godot.NativeInterop; source.Append("\n\n#pragma warning restore CA1707\n"); - context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{data.NativeTypeSymbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } @@ -277,7 +277,7 @@ using Godot.NativeInterop; INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -338,18 +338,18 @@ using Godot.NativeInterop; // just pass it by-ref and let it be pinned. AppendRefKind(source, parameter.RefKind) .Append(' ') - .Append(parameter.Type.FullQualifiedName()); + .Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } } else { - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } source.Append(", "); } - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append($"> {callback.Name};\n"); } @@ -372,12 +372,12 @@ using Godot.NativeInterop; source.Append("\n#pragma warning restore CA1707\n"); - context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } private static bool IsGodotInteropStruct(ITypeSymbol type) => - GodotInteropStructs.Contains(type.FullQualifiedName()); + GodotInteropStructs.Contains(type.FullQualifiedNameOmitGlobal()); private static bool IsByRefParameter(IParameterSymbol parameter) => parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref; @@ -393,7 +393,7 @@ using Godot.NativeInterop; private static void AppendPointerType(StringBuilder source, ITypeSymbol type) { - source.Append(type.FullQualifiedName()); + source.Append(type.FullQualifiedNameIncludeGlobal()); source.Append('*'); } @@ -426,7 +426,7 @@ using Godot.NativeInterop; { varName = $"{parameter.Name}_copy"; - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(varName); if (parameter.RefKind is RefKind.In or RefKind.Ref) diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 8308bada24..4ce02d221e 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -28,17 +28,24 @@ namespace GodotPlugins get => _pluginLoadContext?.AssemblyLoadedPath; } + public bool IsCollectible + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _pluginLoadContext?.IsCollectible ?? false; + } + [MethodImpl(MethodImplOptions.NoInlining)] public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName( AssemblyName assemblyName, string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext + AssemblyLoadContext mainLoadContext, + bool isCollectible ) { var wrapper = new PluginLoadContextWrapper(); wrapper._pluginLoadContext = new PluginLoadContext( - pluginPath, sharedAssemblies, mainLoadContext); + pluginPath, sharedAssemblies, mainLoadContext, isCollectible); var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); return (assembly, wrapper); } @@ -61,6 +68,7 @@ namespace GodotPlugins private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; private static Assembly? _editorApiAssembly; private static PluginLoadContextWrapper? _projectLoadContext; + private static bool _editorHint = false; private static readonly AssemblyLoadContext MainLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? @@ -77,15 +85,17 @@ namespace GodotPlugins { try { + _editorHint = editorHint.ToBool(); + _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; SharedAssemblies.Add(CoreApiAssembly.GetName()); NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver); - AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool()); + AlcReloadCfg.Configure(alcReloadEnabled: _editorHint); NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); - if (editorHint.ToBool()) + if (_editorHint) { _editorApiAssembly = Assembly.Load("GodotSharpEditor"); SharedAssemblies.Add(_editorApiAssembly.GetName()); @@ -128,7 +138,7 @@ namespace GodotPlugins string assemblyPath = new(nAssemblyPath); - (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath); + (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath, isCollectible: _editorHint); string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); @@ -155,7 +165,7 @@ namespace GodotPlugins if (_editorApiAssembly == null) throw new InvalidOperationException("The Godot editor API assembly is not loaded."); - var (assembly, _) = LoadPlugin(assemblyPath); + var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: _editorHint); NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); @@ -180,7 +190,7 @@ namespace GodotPlugins } } - private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath) + private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath, bool isCollectible) { string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); @@ -194,7 +204,7 @@ namespace GodotPlugins } return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName( - new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext); + new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext, isCollectible); } [UnmanagedCallersOnly] @@ -218,6 +228,12 @@ namespace GodotPlugins if (pluginLoadContext == null) return true; + if (!pluginLoadContext.IsCollectible) + { + Console.Error.WriteLine("Cannot unload a non-collectible assembly load context."); + return false; + } + Console.WriteLine("Unloading assembly load context..."); var alcWeakReference = pluginLoadContext.CreateWeakReference(); diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs index dcd572c65e..344b76a202 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -15,8 +15,8 @@ namespace GodotPlugins public string? AssemblyLoadedPath { get; private set; } public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext) - : base(isCollectible: true) + AssemblyLoadContext mainLoadContext, bool isCollectible) + : base(isCollectible) { _resolver = new AssemblyDependencyResolver(pluginPath); _sharedAssemblies = sharedAssemblies; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index f1b46e293b..1b9f63c22d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -474,6 +474,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotArray + { + public Array UnderlyingArray { get; } + } + /// <summary> /// Typed wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when @@ -487,7 +492,8 @@ namespace Godot.Collections IList<T>, IReadOnlyList<T>, ICollection<T>, - IEnumerable<T> + IEnumerable<T>, + IGenericGodotArray { private static godot_variant ToVariantFunc(in Array<T> godotArray) => VariantUtils.CreateFromArray(godotArray); @@ -495,39 +501,16 @@ namespace Godot.Collections private static Array<T> FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToArrayObject<T>(variant); - // ReSharper disable StaticMemberInGenericType - // Warning is about unique static fields being created for each generic type combination: - // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html - // In our case this is exactly what we want. - - private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback; - - // ReSharper restore StaticMemberInGenericType - static unsafe Array() { - VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] = - ( - (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc, - (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc - ); - - ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); - ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); - } - - private static unsafe void ValidateVariantConversionCallbacks() - { - if (ConvertToVariantCallback == null || ConvertToManagedCallback == null) - { - throw new InvalidOperationException( - $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'."); - } + VariantUtils.GenericConversion<Array<T>>.ToVariantCb = &ToVariantFunc; + VariantUtils.GenericConversion<Array<T>>.FromVariantCb = &FromVariantFunc; } private readonly Array _underlyingArray; + Array IGenericGodotArray.UnderlyingArray => _underlyingArray; + internal ref godot_array.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -539,8 +522,6 @@ namespace Godot.Collections /// </summary> public Array() { - ValidateVariantConversionCallbacks(); - _underlyingArray = new Array(); } @@ -551,8 +532,6 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(IEnumerable<T> collection) { - ValidateVariantConversionCallbacks(); - if (collection == null) throw new ArgumentNullException(nameof(collection)); @@ -569,8 +548,6 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(T[] array) : this() { - ValidateVariantConversionCallbacks(); - if (array == null) throw new ArgumentNullException(nameof(array)); @@ -586,8 +563,6 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - ValidateVariantConversionCallbacks(); - _underlyingArray = array; } @@ -665,7 +640,7 @@ namespace Godot.Collections get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return ConvertToManagedCallback(borrowElem); + return VariantUtils.ConvertTo<T>(borrowElem); } set { @@ -675,7 +650,7 @@ namespace Godot.Collections godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* itemPtr = &ptrw[index]; (*itemPtr).Dispose(); - *itemPtr = ConvertToVariantCallback(value); + *itemPtr = VariantUtils.CreateFrom(value); } } @@ -685,9 +660,9 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> - public unsafe int IndexOf(T item) + public int IndexOf(T item) { - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } @@ -700,12 +675,12 @@ namespace Godot.Collections /// </summary> /// <param name="index">The index to insert at.</param> /// <param name="item">The item to insert.</param> - public unsafe void Insert(int index, T item) + public void Insert(int index, T item) { if (index < 0 || index > Count) throw new ArgumentOutOfRangeException(nameof(index)); - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } @@ -736,9 +711,9 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> - public unsafe void Add(T item) + public void Add(T item) { - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index d83cf43eb2..d6fad391b6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -827,7 +827,7 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_info[length]; + var aux = stackalloc godotsharp_property_info[stackMaxLength]; interopProperties = aux; } else @@ -947,7 +947,7 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_def_val_pair[length]; + var aux = stackalloc godotsharp_property_def_val_pair[stackMaxLength]; interopDefaultValues = aux; } else diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index f9309ca13e..23b0aa9204 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -77,7 +77,7 @@ namespace Godot _trampoline = trampoline; } - private const int VarArgsSpanThreshold = 5; + private const int VarArgsSpanThreshold = 10; /// <summary> /// Calls the method represented by this <see cref="Callable"/>. @@ -92,15 +92,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { @@ -128,15 +126,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs index 6c6a104019..ff385da1c9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs @@ -54,7 +54,7 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 1); ((Action<T0>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + VariantUtils.ConvertTo<T0>(args[0]) ); ret = default; @@ -73,8 +73,8 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 2); ((Action<T0, T1>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]) ); ret = default; @@ -93,9 +93,9 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 3); ((Action<T0, T1, T2>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]) ); ret = default; @@ -114,10 +114,10 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 4); ((Action<T0, T1, T2, T3>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]) ); ret = default; @@ -136,11 +136,11 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 5); ((Action<T0, T1, T2, T3, T4>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]) ); ret = default; @@ -159,12 +159,12 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 6); ((Action<T0, T1, T2, T3, T4, T5>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]) ); ret = default; @@ -183,13 +183,13 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 7); ((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]) ); ret = default; @@ -208,14 +208,14 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 8); ((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]) ); ret = default; @@ -234,15 +234,15 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 9); ((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), - VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]), + VariantUtils.ConvertTo<T8>(args[8]) ); ret = default; @@ -265,7 +265,7 @@ public readonly partial struct Callable TResult res = ((Func<TResult>)delegateObj)(); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -281,10 +281,10 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 1); TResult res = ((Func<T0, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + VariantUtils.ConvertTo<T0>(args[0]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -300,11 +300,11 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 2); TResult res = ((Func<T0, T1, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -320,12 +320,12 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 3); TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -341,13 +341,13 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 4); TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -363,14 +363,14 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 5); TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -386,15 +386,15 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 6); TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -410,16 +410,16 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 7); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -435,17 +435,17 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 8); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -461,18 +461,18 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 9); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), - VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]), + VariantUtils.ConvertTo<T8>(args[8]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index f8793332a0..cf25e1f0ae 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -344,6 +344,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotDictionary + { + public Dictionary UnderlyingDictionary { get; } + } + /// <summary> /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when @@ -354,7 +359,8 @@ namespace Godot.Collections /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> : IDictionary<TKey, TValue>, - IReadOnlyDictionary<TKey, TValue> + IReadOnlyDictionary<TKey, TValue>, + IGenericGodotDictionary { private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) => VariantUtils.CreateFromDictionary(godotDictionary); @@ -362,49 +368,16 @@ namespace Godot.Collections private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToDictionaryObject<TKey, TValue>(variant); - // ReSharper disable StaticMemberInGenericType - // Warning is about unique static fields being created for each generic type combination: - // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html - // In our case this is exactly what we want. - - private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback; - private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback; - - // ReSharper restore StaticMemberInGenericType - static unsafe Dictionary() { - VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] = - ( - (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc, - (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc - ); - - ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); - ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); - ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); - ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); - } - - private static unsafe void ValidateVariantConversionCallbacks() - { - if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null) - { - throw new InvalidOperationException( - $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'."); - } - - if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null) - { - throw new InvalidOperationException( - $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'."); - } + VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = &ToVariantFunc; + VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.FromVariantCb = &FromVariantFunc; } private readonly Dictionary _underlyingDict; + Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict; + internal ref godot_dictionary.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -416,8 +389,6 @@ namespace Godot.Collections /// </summary> public Dictionary() { - ValidateVariantConversionCallbacks(); - _underlyingDict = new Dictionary(); } @@ -428,8 +399,6 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - ValidateVariantConversionCallbacks(); - if (dictionary == null) throw new ArgumentNullException(nameof(dictionary)); @@ -446,8 +415,6 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - ValidateVariantConversionCallbacks(); - _underlyingDict = dictionary; } @@ -481,18 +448,18 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="key"/>. /// </summary> /// <value>The value at the given <paramref name="key"/>.</value> - public unsafe TValue this[TKey key] + public TValue this[TKey key] { get { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant value).ToBool()) { using (value) - return ConvertValueToManagedCallback(value); + return VariantUtils.ConvertTo<TValue>(value); } else { @@ -501,8 +468,8 @@ namespace Godot.Collections } set { - using var variantKey = ConvertKeyToVariantCallback(key); - using var variantValue = ConvertValueToVariantCallback(value); + using var variantKey = VariantUtils.CreateFrom(key); + using var variantValue = VariantUtils.CreateFrom(value); var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_set_value(ref self, variantKey, variantValue); @@ -541,7 +508,7 @@ namespace Godot.Collections IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; - private unsafe KeyValuePair<TKey, TValue> GetKeyValuePair(int index) + private KeyValuePair<TKey, TValue> GetKeyValuePair(int index) { var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, @@ -551,8 +518,8 @@ namespace Godot.Collections using (value) { return new KeyValuePair<TKey, TValue>( - ConvertKeyToManagedCallback(key), - ConvertValueToManagedCallback(value)); + VariantUtils.ConvertTo<TKey>(key), + VariantUtils.ConvertTo<TValue>(value)); } } @@ -562,15 +529,15 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key at which to add the object.</param> /// <param name="value">The object to add.</param> - public unsafe void Add(TKey key, TValue value) + public void Add(TKey key, TValue value) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) throw new ArgumentException("An element with the same key already exists.", nameof(key)); - using var variantValue = ConvertValueToVariantCallback(value); + using var variantValue = VariantUtils.CreateFrom(value); NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } @@ -579,9 +546,9 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public unsafe bool ContainsKey(TKey key) + public bool ContainsKey(TKey key) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } @@ -590,9 +557,9 @@ namespace Godot.Collections /// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public unsafe bool Remove(TKey key) + public bool Remove(TKey key) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } @@ -603,15 +570,15 @@ namespace Godot.Collections /// <param name="key">The key of the element to get.</param> /// <param name="value">The value at the given <paramref name="key"/>.</param> /// <returns>If an object was found for the given <paramref name="key"/>.</returns> - public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); using (retValue) - value = found ? ConvertValueToManagedCallback(retValue) : default; + value = found ? VariantUtils.ConvertTo<TValue>(retValue) : default; return found; } @@ -635,9 +602,9 @@ namespace Godot.Collections /// </summary> public void Clear() => _underlyingDict.Clear(); - unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - using var variantKey = ConvertKeyToVariantCallback(item.Key); + using var variantKey = VariantUtils.CreateFrom(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -647,7 +614,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = ConvertValueToVariantCallback(item.Value); + using var variantValue = VariantUtils.CreateFrom(item.Value); return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); } } @@ -680,9 +647,9 @@ namespace Godot.Collections } } - unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { - using var variantKey = ConvertKeyToVariantCallback(item.Key); + using var variantKey = VariantUtils.CreateFrom(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -692,7 +659,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = ConvertValueToVariantCallback(item.Value); + using var variantValue = VariantUtils.CreateFrom(item.Value); if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) { return NativeFuncs.godotsharp_dictionary_remove_key( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 649661ee06..d2a80d0e92 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -293,6 +293,10 @@ namespace Godot.NativeInterop return VariantUtils.CreateFromDictionary(godotDictionary); case Collections.Array godotArray: return VariantUtils.CreateFromArray(godotArray); + case Collections.IGenericGodotDictionary godotDictionary: + return VariantUtils.CreateFromDictionary(godotDictionary.UnderlyingDictionary); + case Collections.IGenericGodotArray godotArray: + return VariantUtils.CreateFromArray(godotArray.UnderlyingArray); case Variant variant: return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index b30b6a0752..c7deb6423b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -414,21 +414,6 @@ namespace Godot.NativeInterop // StringExtensions - public static partial void godotsharp_string_md5_buffer(in godot_string p_self, - out godot_packed_byte_array r_md5_buffer); - - public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); - - public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, - out godot_packed_byte_array r_sha256_buffer); - - public static partial void godotsharp_string_sha256_text(in godot_string p_self, - out godot_string r_sha256_text); - public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs deleted file mode 100644 index 4b3db0c01a..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs +++ /dev/null @@ -1,1057 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Godot.NativeInterop; - -// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time. -internal static unsafe class VariantConversionCallbacks -{ - internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)> - GenericConversionCallbacks = new(); - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in T, godot_variant> GetToVariantCallback<T>() - { - static godot_variant FromBool(in bool @bool) => - VariantUtils.CreateFromBool(@bool); - - static godot_variant FromChar(in char @char) => - VariantUtils.CreateFromInt(@char); - - static godot_variant FromInt8(in sbyte @int8) => - VariantUtils.CreateFromInt(@int8); - - static godot_variant FromInt16(in short @int16) => - VariantUtils.CreateFromInt(@int16); - - static godot_variant FromInt32(in int @int32) => - VariantUtils.CreateFromInt(@int32); - - static godot_variant FromInt64(in long @int64) => - VariantUtils.CreateFromInt(@int64); - - static godot_variant FromUInt8(in byte @uint8) => - VariantUtils.CreateFromInt(@uint8); - - static godot_variant FromUInt16(in ushort @uint16) => - VariantUtils.CreateFromInt(@uint16); - - static godot_variant FromUInt32(in uint @uint32) => - VariantUtils.CreateFromInt(@uint32); - - static godot_variant FromUInt64(in ulong @uint64) => - VariantUtils.CreateFromInt(@uint64); - - static godot_variant FromFloat(in float @float) => - VariantUtils.CreateFromFloat(@float); - - static godot_variant FromDouble(in double @double) => - VariantUtils.CreateFromFloat(@double); - - static godot_variant FromVector2(in Vector2 @vector2) => - VariantUtils.CreateFromVector2(@vector2); - - static godot_variant FromVector2I(in Vector2i vector2I) => - VariantUtils.CreateFromVector2i(vector2I); - - static godot_variant FromRect2(in Rect2 @rect2) => - VariantUtils.CreateFromRect2(@rect2); - - static godot_variant FromRect2I(in Rect2i rect2I) => - VariantUtils.CreateFromRect2i(rect2I); - - static godot_variant FromTransform2D(in Transform2D @transform2D) => - VariantUtils.CreateFromTransform2D(@transform2D); - - static godot_variant FromVector3(in Vector3 @vector3) => - VariantUtils.CreateFromVector3(@vector3); - - static godot_variant FromVector3I(in Vector3i vector3I) => - VariantUtils.CreateFromVector3i(vector3I); - - static godot_variant FromBasis(in Basis @basis) => - VariantUtils.CreateFromBasis(@basis); - - static godot_variant FromQuaternion(in Quaternion @quaternion) => - VariantUtils.CreateFromQuaternion(@quaternion); - - static godot_variant FromTransform3D(in Transform3D @transform3d) => - VariantUtils.CreateFromTransform3D(@transform3d); - - static godot_variant FromVector4(in Vector4 @vector4) => - VariantUtils.CreateFromVector4(@vector4); - - static godot_variant FromVector4I(in Vector4i vector4I) => - VariantUtils.CreateFromVector4i(vector4I); - - static godot_variant FromAabb(in AABB @aabb) => - VariantUtils.CreateFromAABB(@aabb); - - static godot_variant FromColor(in Color @color) => - VariantUtils.CreateFromColor(@color); - - static godot_variant FromPlane(in Plane @plane) => - VariantUtils.CreateFromPlane(@plane); - - static godot_variant FromCallable(in Callable @callable) => - VariantUtils.CreateFromCallable(@callable); - - static godot_variant FromSignalInfo(in SignalInfo @signalInfo) => - VariantUtils.CreateFromSignalInfo(@signalInfo); - - static godot_variant FromString(in string @string) => - VariantUtils.CreateFromString(@string); - - static godot_variant FromByteArray(in byte[] byteArray) => - VariantUtils.CreateFromPackedByteArray(byteArray); - - static godot_variant FromInt32Array(in int[] int32Array) => - VariantUtils.CreateFromPackedInt32Array(int32Array); - - static godot_variant FromInt64Array(in long[] int64Array) => - VariantUtils.CreateFromPackedInt64Array(int64Array); - - static godot_variant FromFloatArray(in float[] floatArray) => - VariantUtils.CreateFromPackedFloat32Array(floatArray); - - static godot_variant FromDoubleArray(in double[] doubleArray) => - VariantUtils.CreateFromPackedFloat64Array(doubleArray); - - static godot_variant FromStringArray(in string[] stringArray) => - VariantUtils.CreateFromPackedStringArray(stringArray); - - static godot_variant FromVector2Array(in Vector2[] vector2Array) => - VariantUtils.CreateFromPackedVector2Array(vector2Array); - - static godot_variant FromVector3Array(in Vector3[] vector3Array) => - VariantUtils.CreateFromPackedVector3Array(vector3Array); - - static godot_variant FromColorArray(in Color[] colorArray) => - VariantUtils.CreateFromPackedColorArray(colorArray); - - static godot_variant FromStringNameArray(in StringName[] stringNameArray) => - VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); - - static godot_variant FromNodePathArray(in NodePath[] nodePathArray) => - VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); - - static godot_variant FromRidArray(in RID[] ridArray) => - VariantUtils.CreateFromSystemArrayOfRID(ridArray); - - static godot_variant FromGodotObject(in Godot.Object godotObject) => - VariantUtils.CreateFromGodotObject(godotObject); - - static godot_variant FromStringName(in StringName stringName) => - VariantUtils.CreateFromStringName(stringName); - - static godot_variant FromNodePath(in NodePath nodePath) => - VariantUtils.CreateFromNodePath(nodePath); - - static godot_variant FromRid(in RID rid) => - VariantUtils.CreateFromRID(rid); - - static godot_variant FromGodotDictionary(in Collections.Dictionary godotDictionary) => - VariantUtils.CreateFromDictionary(godotDictionary); - - static godot_variant FromGodotArray(in Collections.Array godotArray) => - VariantUtils.CreateFromArray(godotArray); - - static godot_variant FromVariant(in Variant variant) => - NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); - - var typeOfT = typeof(T); - - if (typeOfT == typeof(bool)) - { - return (delegate*<in T, godot_variant>)(delegate*<in bool, godot_variant>) - &FromBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in T, godot_variant>)(delegate*<in char, godot_variant>) - &FromChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in T, godot_variant>)(delegate*<in float, godot_variant>) - &FromFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in T, godot_variant>)(delegate*<in double, godot_variant>) - &FromDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2, godot_variant>) - &FromVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2i, godot_variant>) - &FromVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2, godot_variant>) - &FromRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2i, godot_variant>) - &FromRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform2D, godot_variant>) - &FromTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3, godot_variant>) - &FromVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3i, godot_variant>) - &FromVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Basis, godot_variant>) - &FromBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Quaternion, godot_variant>) - &FromQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform3D, godot_variant>) - &FromTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4, godot_variant>) - &FromVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4i, godot_variant>) - &FromVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in T, godot_variant>)(delegate*<in AABB, godot_variant>) - &FromAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color, godot_variant>) - &FromColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Plane, godot_variant>) - &FromPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Callable, godot_variant>) - &FromCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in T, godot_variant>)(delegate*<in SignalInfo, godot_variant>) - &FromSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - case TypeCode.Int16: - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - case TypeCode.Int32: - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - case TypeCode.Int64: - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - case TypeCode.Byte: - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in T, godot_variant>)(delegate*<in string, godot_variant>) - &FromString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte[], godot_variant>) - &FromByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in int[], godot_variant>) - &FromInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in long[], godot_variant>) - &FromInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in float[], godot_variant>) - &FromFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in double[], godot_variant>) - &FromDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in string[], godot_variant>) - &FromStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2[], godot_variant>) - &FromVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3[], godot_variant>) - &FromVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color[], godot_variant>) - &FromColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName[], godot_variant>) - &FromStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath[], godot_variant>) - &FromNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID[], godot_variant>) - &FromRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Object, godot_variant>) - &FromGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName, godot_variant>) - &FromStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath, godot_variant>) - &FromNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID, godot_variant>) - &FromRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Dictionary, godot_variant>) - &FromGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Array, godot_variant>) - &FromGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Variant, godot_variant>) - &FromVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in T, godot_variant>)genericConversion.ToVariant; - } - } - } - - return null; - } - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in godot_variant, T> GetToManagedCallback<T>() - { - static bool ToBool(in godot_variant variant) => - VariantUtils.ConvertToBool(variant); - - static char ToChar(in godot_variant variant) => - VariantUtils.ConvertToChar(variant); - - static sbyte ToInt8(in godot_variant variant) => - VariantUtils.ConvertToInt8(variant); - - static short ToInt16(in godot_variant variant) => - VariantUtils.ConvertToInt16(variant); - - static int ToInt32(in godot_variant variant) => - VariantUtils.ConvertToInt32(variant); - - static long ToInt64(in godot_variant variant) => - VariantUtils.ConvertToInt64(variant); - - static byte ToUInt8(in godot_variant variant) => - VariantUtils.ConvertToUInt8(variant); - - static ushort ToUInt16(in godot_variant variant) => - VariantUtils.ConvertToUInt16(variant); - - static uint ToUInt32(in godot_variant variant) => - VariantUtils.ConvertToUInt32(variant); - - static ulong ToUInt64(in godot_variant variant) => - VariantUtils.ConvertToUInt64(variant); - - static float ToFloat(in godot_variant variant) => - VariantUtils.ConvertToFloat32(variant); - - static double ToDouble(in godot_variant variant) => - VariantUtils.ConvertToFloat64(variant); - - static Vector2 ToVector2(in godot_variant variant) => - VariantUtils.ConvertToVector2(variant); - - static Vector2i ToVector2I(in godot_variant variant) => - VariantUtils.ConvertToVector2i(variant); - - static Rect2 ToRect2(in godot_variant variant) => - VariantUtils.ConvertToRect2(variant); - - static Rect2i ToRect2I(in godot_variant variant) => - VariantUtils.ConvertToRect2i(variant); - - static Transform2D ToTransform2D(in godot_variant variant) => - VariantUtils.ConvertToTransform2D(variant); - - static Vector3 ToVector3(in godot_variant variant) => - VariantUtils.ConvertToVector3(variant); - - static Vector3i ToVector3I(in godot_variant variant) => - VariantUtils.ConvertToVector3i(variant); - - static Basis ToBasis(in godot_variant variant) => - VariantUtils.ConvertToBasis(variant); - - static Quaternion ToQuaternion(in godot_variant variant) => - VariantUtils.ConvertToQuaternion(variant); - - static Transform3D ToTransform3D(in godot_variant variant) => - VariantUtils.ConvertToTransform3D(variant); - - static Vector4 ToVector4(in godot_variant variant) => - VariantUtils.ConvertToVector4(variant); - - static Vector4i ToVector4I(in godot_variant variant) => - VariantUtils.ConvertToVector4i(variant); - - static AABB ToAabb(in godot_variant variant) => - VariantUtils.ConvertToAABB(variant); - - static Color ToColor(in godot_variant variant) => - VariantUtils.ConvertToColor(variant); - - static Plane ToPlane(in godot_variant variant) => - VariantUtils.ConvertToPlane(variant); - - static Callable ToCallable(in godot_variant variant) => - VariantUtils.ConvertToCallableManaged(variant); - - static SignalInfo ToSignalInfo(in godot_variant variant) => - VariantUtils.ConvertToSignalInfo(variant); - - static string ToString(in godot_variant variant) => - VariantUtils.ConvertToStringObject(variant); - - static byte[] ToByteArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedByteArrayToSystemArray(variant); - - static int[] ToInt32Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(variant); - - static long[] ToInt64Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(variant); - - static float[] ToFloatArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(variant); - - static double[] ToDoubleArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(variant); - - static string[] ToStringArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedStringArrayToSystemArray(variant); - - static Vector2[] ToVector2Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(variant); - - static Vector3[] ToVector3Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(variant); - - static Color[] ToColorArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedColorArrayToSystemArray(variant); - - static StringName[] ToStringNameArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfStringName(variant); - - static NodePath[] ToNodePathArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfNodePath(variant); - - static RID[] ToRidArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfRID(variant); - - static Godot.Object ToGodotObject(in godot_variant variant) => - VariantUtils.ConvertToGodotObject(variant); - - static StringName ToStringName(in godot_variant variant) => - VariantUtils.ConvertToStringNameObject(variant); - - static NodePath ToNodePath(in godot_variant variant) => - VariantUtils.ConvertToNodePathObject(variant); - - static RID ToRid(in godot_variant variant) => - VariantUtils.ConvertToRID(variant); - - static Collections.Dictionary ToGodotDictionary(in godot_variant variant) => - VariantUtils.ConvertToDictionaryObject(variant); - - static Collections.Array ToGodotArray(in godot_variant variant) => - VariantUtils.ConvertToArrayObject(variant); - - static Variant ToVariant(in godot_variant variant) => - Variant.CreateCopyingBorrowed(variant); - - var typeOfT = typeof(T); - - // ReSharper disable RedundantCast - // Rider is being stupid here. These casts are definitely needed. We get build errors without them. - - if (typeOfT == typeof(bool)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, bool>) - &ToBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, char>) - &ToChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float>) - &ToFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double>) - &ToDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2>) - &ToVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2i>) - &ToVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2>) - &ToRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2i>) - &ToRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform2D>) - &ToTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3>) - &ToVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3i>) - &ToVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Basis>) - &ToBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Quaternion>) - &ToQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform3D>) - &ToTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4>) - &ToVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4i>) - &ToVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, AABB>) - &ToAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color>) - &ToColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Plane>) - &ToPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Callable>) - &ToCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, SignalInfo>) - &ToSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - case TypeCode.Int16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - case TypeCode.Int32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - case TypeCode.Int64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - case TypeCode.Byte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string>) - &ToString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte[]>) - &ToByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int[]>) - &ToInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long[]>) - &ToInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float[]>) - &ToFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double[]>) - &ToDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string[]>) - &ToStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2[]>) - &ToVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3[]>) - &ToVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color[]>) - &ToColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName[]>) - &ToStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath[]>) - &ToNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID[]>) - &ToRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Object>) - &ToGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName>) - &ToStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath>) - &ToNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID>) - &ToRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Dictionary>) - &ToGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Array>) - &ToGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Variant>) - &ToVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in godot_variant, T>)genericConversion.FromVariant; - } - } - } - - // ReSharper restore RedundantCast - - return null; - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs deleted file mode 100644 index 46f31bbf4e..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Godot.NativeInterop -{ - internal readonly ref struct VariantSpanDisposer - { - private readonly Span<godot_variant.movable> _variantSpan; - - // IMPORTANT: The span element must be default initialized. - // Make sure call Clear() on the span if it was created with stackalloc. - public VariantSpanDisposer(Span<godot_variant.movable> variantSpan) - { - _variantSpan = variantSpan; - } - - public void Dispose() - { - for (int i = 0; i < _variantSpan.Length; i++) - _variantSpan[i].DangerousSelfRef.Dispose(); - } - } - - internal static class VariantSpanExtensions - { - // Used to make sure we always initialize the span values to the default, - // as we need that in order to safely dispose all elements after. - public static Span<godot_variant.movable> Cleared(this Span<godot_variant.movable> span) - { - span.Clear(); - return span; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index 57f9ec7d95..ba8e7a6c65 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -8,7 +8,7 @@ using Godot.Collections; namespace Godot.NativeInterop { - public static class VariantUtils + public static partial class VariantUtils { public static godot_variant CreateFromRID(RID from) => new() { Type = Variant.Type.Rid, RID = from }; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs new file mode 100644 index 0000000000..694da6db77 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs @@ -0,0 +1,406 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop; + +public partial class VariantUtils +{ + private static Exception UnsupportedType<T>() => new InvalidOperationException( + $"The type is not supported for conversion to/from Variant: '{typeof(T).FullName}'"); + + internal static class GenericConversion<T> + { + public static unsafe godot_variant ToVariant(in T from) => + ToVariantCb != null ? ToVariantCb(from) : throw UnsupportedType<T>(); + + public static unsafe T FromVariant(in godot_variant variant) => + FromVariantCb != null ? FromVariantCb(variant) : throw UnsupportedType<T>(); + + // ReSharper disable once StaticMemberInGenericType + internal static unsafe delegate*<in T, godot_variant> ToVariantCb; + + // ReSharper disable once StaticMemberInGenericType + internal static unsafe delegate*<in godot_variant, T> FromVariantCb; + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + static GenericConversion() + { + RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static godot_variant CreateFrom<[MustBeVariant] T>(in T from) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static TTo UnsafeAs<TTo>(in T f) => Unsafe.As<T, TTo>(ref Unsafe.AsRef(f)); + + // `typeof(T) == typeof(X)` is optimized away. We cannot cache `typeof(T)` in a local variable, as it's not optimized when done like that. + + if (typeof(T) == typeof(bool)) + return CreateFromBool(UnsafeAs<bool>(from)); + + if (typeof(T) == typeof(char)) + return CreateFromInt(UnsafeAs<char>(from)); + + if (typeof(T) == typeof(sbyte)) + return CreateFromInt(UnsafeAs<sbyte>(from)); + + if (typeof(T) == typeof(short)) + return CreateFromInt(UnsafeAs<short>(from)); + + if (typeof(T) == typeof(int)) + return CreateFromInt(UnsafeAs<int>(from)); + + if (typeof(T) == typeof(long)) + return CreateFromInt(UnsafeAs<long>(from)); + + if (typeof(T) == typeof(byte)) + return CreateFromInt(UnsafeAs<byte>(from)); + + if (typeof(T) == typeof(ushort)) + return CreateFromInt(UnsafeAs<ushort>(from)); + + if (typeof(T) == typeof(uint)) + return CreateFromInt(UnsafeAs<uint>(from)); + + if (typeof(T) == typeof(ulong)) + return CreateFromInt(UnsafeAs<ulong>(from)); + + if (typeof(T) == typeof(float)) + return CreateFromFloat(UnsafeAs<float>(from)); + + if (typeof(T) == typeof(double)) + return CreateFromFloat(UnsafeAs<double>(from)); + + if (typeof(T) == typeof(Vector2)) + return CreateFromVector2(UnsafeAs<Vector2>(from)); + + if (typeof(T) == typeof(Vector2i)) + return CreateFromVector2i(UnsafeAs<Vector2i>(from)); + + if (typeof(T) == typeof(Rect2)) + return CreateFromRect2(UnsafeAs<Rect2>(from)); + + if (typeof(T) == typeof(Rect2i)) + return CreateFromRect2i(UnsafeAs<Rect2i>(from)); + + if (typeof(T) == typeof(Transform2D)) + return CreateFromTransform2D(UnsafeAs<Transform2D>(from)); + + if (typeof(T) == typeof(Vector3)) + return CreateFromVector3(UnsafeAs<Vector3>(from)); + + if (typeof(T) == typeof(Vector3i)) + return CreateFromVector3i(UnsafeAs<Vector3i>(from)); + + if (typeof(T) == typeof(Basis)) + return CreateFromBasis(UnsafeAs<Basis>(from)); + + if (typeof(T) == typeof(Quaternion)) + return CreateFromQuaternion(UnsafeAs<Quaternion>(from)); + + if (typeof(T) == typeof(Transform3D)) + return CreateFromTransform3D(UnsafeAs<Transform3D>(from)); + + if (typeof(T) == typeof(Vector4)) + return CreateFromVector4(UnsafeAs<Vector4>(from)); + + if (typeof(T) == typeof(Vector4i)) + return CreateFromVector4i(UnsafeAs<Vector4i>(from)); + + if (typeof(T) == typeof(AABB)) + return CreateFromAABB(UnsafeAs<AABB>(from)); + + if (typeof(T) == typeof(Color)) + return CreateFromColor(UnsafeAs<Color>(from)); + + if (typeof(T) == typeof(Plane)) + return CreateFromPlane(UnsafeAs<Plane>(from)); + + if (typeof(T) == typeof(Callable)) + return CreateFromCallable(UnsafeAs<Callable>(from)); + + if (typeof(T) == typeof(SignalInfo)) + return CreateFromSignalInfo(UnsafeAs<SignalInfo>(from)); + + if (typeof(T) == typeof(string)) + return CreateFromString(UnsafeAs<string>(from)); + + if (typeof(T) == typeof(byte[])) + return CreateFromPackedByteArray(UnsafeAs<byte[]>(from)); + + if (typeof(T) == typeof(int[])) + return CreateFromPackedInt32Array(UnsafeAs<int[]>(from)); + + if (typeof(T) == typeof(long[])) + return CreateFromPackedInt64Array(UnsafeAs<long[]>(from)); + + if (typeof(T) == typeof(float[])) + return CreateFromPackedFloat32Array(UnsafeAs<float[]>(from)); + + if (typeof(T) == typeof(double[])) + return CreateFromPackedFloat64Array(UnsafeAs<double[]>(from)); + + if (typeof(T) == typeof(string[])) + return CreateFromPackedStringArray(UnsafeAs<string[]>(from)); + + if (typeof(T) == typeof(Vector2[])) + return CreateFromPackedVector2Array(UnsafeAs<Vector2[]>(from)); + + if (typeof(T) == typeof(Vector3[])) + return CreateFromPackedVector3Array(UnsafeAs<Vector3[]>(from)); + + if (typeof(T) == typeof(Color[])) + return CreateFromPackedColorArray(UnsafeAs<Color[]>(from)); + + if (typeof(T) == typeof(StringName[])) + return CreateFromSystemArrayOfStringName(UnsafeAs<StringName[]>(from)); + + if (typeof(T) == typeof(NodePath[])) + return CreateFromSystemArrayOfNodePath(UnsafeAs<NodePath[]>(from)); + + if (typeof(T) == typeof(RID[])) + return CreateFromSystemArrayOfRID(UnsafeAs<RID[]>(from)); + + if (typeof(T) == typeof(StringName)) + return CreateFromStringName(UnsafeAs<StringName>(from)); + + if (typeof(T) == typeof(NodePath)) + return CreateFromNodePath(UnsafeAs<NodePath>(from)); + + if (typeof(T) == typeof(RID)) + return CreateFromRID(UnsafeAs<RID>(from)); + + if (typeof(T) == typeof(Godot.Collections.Dictionary)) + return CreateFromDictionary(UnsafeAs<Godot.Collections.Dictionary>(from)); + + if (typeof(T) == typeof(Godot.Collections.Array)) + return CreateFromArray(UnsafeAs<Godot.Collections.Array>(from)); + + if (typeof(T) == typeof(Variant)) + return NativeFuncs.godotsharp_variant_new_copy((godot_variant)UnsafeAs<Variant>(from).NativeVar); + + // More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away. + + // `typeof(X).IsAssignableFrom(typeof(T))` is optimized away + + if (typeof(Godot.Object).IsAssignableFrom(typeof(T))) + return CreateFromGodotObject(UnsafeAs<Godot.Object>(from)); + + // `typeof(T).IsValueType` is optimized away + // `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113 + // Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job! + + if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T))) + { + // `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away. + // Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away. + // We don't need to know whether it's signed or unsigned. + + if (Unsafe.SizeOf<T>() == 1) + return CreateFromInt(UnsafeAs<sbyte>(from)); + + if (Unsafe.SizeOf<T>() == 2) + return CreateFromInt(UnsafeAs<short>(from)); + + if (Unsafe.SizeOf<T>() == 4) + return CreateFromInt(UnsafeAs<int>(from)); + + if (Unsafe.SizeOf<T>() == 8) + return CreateFromInt(UnsafeAs<long>(from)); + + throw UnsupportedType<T>(); + } + + return GenericConversion<T>.ToVariant(from); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static T ConvertTo<[MustBeVariant] T>(in godot_variant variant) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static T UnsafeAsT<TFrom>(TFrom f) => Unsafe.As<TFrom, T>(ref Unsafe.AsRef(f)); + + if (typeof(T) == typeof(bool)) + return UnsafeAsT(ConvertToBool(variant)); + + if (typeof(T) == typeof(char)) + return UnsafeAsT(ConvertToChar(variant)); + + if (typeof(T) == typeof(sbyte)) + return UnsafeAsT(ConvertToInt8(variant)); + + if (typeof(T) == typeof(short)) + return UnsafeAsT(ConvertToInt16(variant)); + + if (typeof(T) == typeof(int)) + return UnsafeAsT(ConvertToInt32(variant)); + + if (typeof(T) == typeof(long)) + return UnsafeAsT(ConvertToInt64(variant)); + + if (typeof(T) == typeof(byte)) + return UnsafeAsT(ConvertToUInt8(variant)); + + if (typeof(T) == typeof(ushort)) + return UnsafeAsT(ConvertToUInt16(variant)); + + if (typeof(T) == typeof(uint)) + return UnsafeAsT(ConvertToUInt32(variant)); + + if (typeof(T) == typeof(ulong)) + return UnsafeAsT(ConvertToUInt64(variant)); + + if (typeof(T) == typeof(float)) + return UnsafeAsT(ConvertToFloat32(variant)); + + if (typeof(T) == typeof(double)) + return UnsafeAsT(ConvertToFloat64(variant)); + + if (typeof(T) == typeof(Vector2)) + return UnsafeAsT(ConvertToVector2(variant)); + + if (typeof(T) == typeof(Vector2i)) + return UnsafeAsT(ConvertToVector2i(variant)); + + if (typeof(T) == typeof(Rect2)) + return UnsafeAsT(ConvertToRect2(variant)); + + if (typeof(T) == typeof(Rect2i)) + return UnsafeAsT(ConvertToRect2i(variant)); + + if (typeof(T) == typeof(Transform2D)) + return UnsafeAsT(ConvertToTransform2D(variant)); + + if (typeof(T) == typeof(Vector3)) + return UnsafeAsT(ConvertToVector3(variant)); + + if (typeof(T) == typeof(Vector3i)) + return UnsafeAsT(ConvertToVector3i(variant)); + + if (typeof(T) == typeof(Basis)) + return UnsafeAsT(ConvertToBasis(variant)); + + if (typeof(T) == typeof(Quaternion)) + return UnsafeAsT(ConvertToQuaternion(variant)); + + if (typeof(T) == typeof(Transform3D)) + return UnsafeAsT(ConvertToTransform3D(variant)); + + if (typeof(T) == typeof(Vector4)) + return UnsafeAsT(ConvertToVector4(variant)); + + if (typeof(T) == typeof(Vector4i)) + return UnsafeAsT(ConvertToVector4i(variant)); + + if (typeof(T) == typeof(AABB)) + return UnsafeAsT(ConvertToAABB(variant)); + + if (typeof(T) == typeof(Color)) + return UnsafeAsT(ConvertToColor(variant)); + + if (typeof(T) == typeof(Plane)) + return UnsafeAsT(ConvertToPlane(variant)); + + if (typeof(T) == typeof(Callable)) + return UnsafeAsT(ConvertToCallableManaged(variant)); + + if (typeof(T) == typeof(SignalInfo)) + return UnsafeAsT(ConvertToSignalInfo(variant)); + + if (typeof(T) == typeof(string)) + return UnsafeAsT(ConvertToStringObject(variant)); + + if (typeof(T) == typeof(byte[])) + return UnsafeAsT(ConvertAsPackedByteArrayToSystemArray(variant)); + + if (typeof(T) == typeof(int[])) + return UnsafeAsT(ConvertAsPackedInt32ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(long[])) + return UnsafeAsT(ConvertAsPackedInt64ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(float[])) + return UnsafeAsT(ConvertAsPackedFloat32ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(double[])) + return UnsafeAsT(ConvertAsPackedFloat64ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(string[])) + return UnsafeAsT(ConvertAsPackedStringArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Vector2[])) + return UnsafeAsT(ConvertAsPackedVector2ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Vector3[])) + return UnsafeAsT(ConvertAsPackedVector3ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Color[])) + return UnsafeAsT(ConvertAsPackedColorArrayToSystemArray(variant)); + + if (typeof(T) == typeof(StringName[])) + return UnsafeAsT(ConvertToSystemArrayOfStringName(variant)); + + if (typeof(T) == typeof(NodePath[])) + return UnsafeAsT(ConvertToSystemArrayOfNodePath(variant)); + + if (typeof(T) == typeof(RID[])) + return UnsafeAsT(ConvertToSystemArrayOfRID(variant)); + + if (typeof(T) == typeof(StringName)) + return UnsafeAsT(ConvertToStringNameObject(variant)); + + if (typeof(T) == typeof(NodePath)) + return UnsafeAsT(ConvertToNodePathObject(variant)); + + if (typeof(T) == typeof(RID)) + return UnsafeAsT(ConvertToRID(variant)); + + if (typeof(T) == typeof(Godot.Collections.Dictionary)) + return UnsafeAsT(ConvertToDictionaryObject(variant)); + + if (typeof(T) == typeof(Godot.Collections.Array)) + return UnsafeAsT(ConvertToArrayObject(variant)); + + if (typeof(T) == typeof(Variant)) + return UnsafeAsT(Variant.CreateCopyingBorrowed(variant)); + + // More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away. + + // `typeof(X).IsAssignableFrom(typeof(T))` is optimized away + + if (typeof(Godot.Object).IsAssignableFrom(typeof(T))) + return (T)(object)ConvertToGodotObject(variant); + + // `typeof(T).IsValueType` is optimized away + // `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113 + // Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job! + + if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T))) + { + // `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away. + // Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away. + // We don't need to know whether it's signed or unsigned. + + if (Unsafe.SizeOf<T>() == 1) + return UnsafeAsT(ConvertToInt8(variant)); + + if (Unsafe.SizeOf<T>() == 2) + return UnsafeAsT(ConvertToInt16(variant)); + + if (Unsafe.SizeOf<T>() == 4) + return UnsafeAsT(ConvertToInt32(variant)); + + if (Unsafe.SizeOf<T>() == 8) + return UnsafeAsT(ConvertToInt64(variant)); + + throw UnsupportedType<T>(); + } + + return GenericConversion<T>.FromVariant(variant); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index 371729ebec..8b1b73fcc3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -3,6 +3,14 @@ using System.Runtime.InteropServices; namespace Godot { + /// <summary> + /// A 4x4 matrix used for 3D projective transformations. It can represent transformations such as + /// translation, rotation, scaling, shearing, and perspective division. It consists of four + /// <see cref="Vector4"/> columns. + /// For purely linear transformations (translation, rotation, and scale), it is recommended to use + /// <see cref="Transform3D"/>, as it is more performant and has a lower memory footprint. + /// Used internally as <see cref="Camera3D"/>'s projection matrix. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Projection : IEquatable<Projection> @@ -59,48 +67,107 @@ namespace Godot public Vector4 w; /// <summary> - /// Constructs a projection from 4 vectors (matrix columns). + /// Access whole columns in the form of <see cref="Vector4"/>. /// </summary> - /// <param name="x">The X column, or column index 0.</param> - /// <param name="y">The Y column, or column index 1.</param> - /// <param name="z">The Z column, or column index 2.</param> - /// <param name="w">The W column, or column index 3.</param> - public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + /// <param name="column">Which column vector.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> is not 0, 1, 2 or 3. + /// </exception> + public Vector4 this[int column] { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + readonly get + { + switch (column) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + case 3: + w = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// Access single values. /// </summary> - /// <param name="transform">The <see cref="Transform3D"/>.</param> - public Projection(Transform3D transform) + /// <param name="column">Which column vector.</param> + /// <param name="row">Which row of the column.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. + /// </exception> + public real_t this[int column, int row] { - x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); - y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); - z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); - w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + readonly get + { + switch (column) + { + case 0: + return x[row]; + case 1: + return y[row]; + case 2: + return z[row]; + case 3: + return w[row]; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x[row] = value; + return; + case 1: + y[row] = value; + return; + case 2: + z[row] = value; + return; + case 3: + w[row] = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. + /// Creates a new <see cref="Projection"/> that projects positions from a depth range of + /// <c>-1</c> to <c>1</c> to one that ranges from <c>0</c> to <c>1</c>, and flips the projected + /// positions vertically, according to <paramref name="flipY"/>. /// </summary> - /// <param name="proj">The <see cref="Projection"/>.</param> - public static explicit operator Transform3D(Projection proj) - { - return new Transform3D( - new Basis( - new Vector3(proj.x.x, proj.x.y, proj.x.z), - new Vector3(proj.y.x, proj.y.y, proj.y.z), - new Vector3(proj.z.x, proj.z.y, proj.z.z) - ), - new Vector3(proj.w.x, proj.w.y, proj.w.z) - ); - } - + /// <param name="flipY">If the projection should be flipped vertically.</param> + /// <returns>The created projection.</returns> public static Projection CreateDepthCorrection(bool flipY) { return new Projection( @@ -111,6 +178,12 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that scales a given projection to fit around + /// a given <see cref="AABB"/> in projection space. + /// </summary> + /// <param name="aabb">The AABB to fit the projection around.</param> + /// <returns>The created projection.</returns> public static Projection CreateFitAabb(AABB aabb) { Vector3 min = aabb.Position; @@ -124,6 +197,25 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> for projecting positions onto a head-mounted display with + /// the given X:Y aspect ratio, distance between eyes, display width, distance to lens, oversampling factor, + /// and depth clipping planes. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// </summary> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="displayWidth">The display width.</param> + /// <param name="displayToLens">The distance to the lens.</param> + /// <param name="oversample">The oversampling factor.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateForHmd(int eye, real_t aspect, real_t intraocularDist, real_t displayWidth, real_t displayToLens, real_t oversample, real_t zNear, real_t zFar) { real_t f1 = (intraocularDist * (real_t)0.5) / displayToLens; @@ -148,6 +240,17 @@ namespace Godot } } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t near, real_t far) { if (right <= left) @@ -179,6 +282,18 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given size, X:Y aspect ratio, offset, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="offset">The offset to apply.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t near, real_t far, bool flipFov) { if (!flipFov) @@ -188,6 +303,11 @@ namespace Godot return CreateFrustum(-size / 2 + offset.x, +size / 2 + offset.x, -size / aspect / 2 + offset.y, +size / aspect / 2 + offset.y, near, far); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions into the given <see cref="Rect2"/>. + /// </summary> + /// <param name="rect">The Rect2 to project positions into.</param> + /// <returns>The created projection.</returns> public static Projection CreateLightAtlasRect(Rect2 rect) { return new Projection( @@ -198,6 +318,17 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonal(real_t left, real_t right, real_t bottom, real_t top, real_t zNear, real_t zFar) { Projection proj = Projection.Identity; @@ -211,6 +342,17 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given size, X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonalAspect(real_t size, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (!flipFov) @@ -220,6 +362,17 @@ namespace Godot return CreateOrthogonal(-size / 2, +size / 2, -size / aspect / 2, +size / aspect / 2, zNear, zFar); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspective(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (flipFov) @@ -249,6 +402,27 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping distances. + /// The projection is adjusted for a head-mounted display with the given distance between eyes and distance + /// to a point that can be focused on. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="convergenceDist">The distance to a point of convergence that can be focused on.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspectiveHmd(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov, int eye, real_t intraocularDist, real_t convergenceDist) { if (flipFov) @@ -286,6 +460,13 @@ namespace Godot return proj * cm; } + /// <summary> + /// Returns a scalar value that is the signed factor by which areas are scaled by this matrix. + /// If the sign is negative, the matrix flips the orientation of the area. + /// The determinant can be used to calculate the invertibility of a matrix or solve linear systems + /// of equations involving the matrix, among other applications. + /// </summary> + /// <returns>The determinant calculated from this projection.</returns> public readonly real_t Determinant() { return x.w * y.z * z.y * w.x - x.z * y.w * z.y * w.x - @@ -302,12 +483,20 @@ namespace Godot x.y * y.x * z.z * w.w + x.x * y.y * z.z * w.w; } + /// <summary> + /// Returns the X:Y aspect ratio of this <see cref="Projection"/>'s viewport. + /// </summary> + /// <returns>The aspect ratio from this projection's viewport.</returns> public readonly real_t GetAspect() { Vector2 vpHe = GetViewportHalfExtents(); return vpHe.x / vpHe.y; } + /// <summary> + /// Returns the horizontal field of view of the projection (in degrees). + /// </summary> + /// <returns>The horizontal field of view of this projection.</returns> public readonly real_t GetFov() { Plane rightPlane = new Plane(x.w - x.x, y.w - y.x, z.w - z.x, -w.w + w.x).Normalized(); @@ -322,11 +511,22 @@ namespace Godot } } + /// <summary> + /// Returns the vertical field of view of the projection (in degrees) associated with + /// the given horizontal field of view (in degrees) and aspect ratio. + /// </summary> + /// <param name="fovx">The horizontal field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <returns>The vertical field of view of this projection.</returns> public static real_t GetFovy(real_t fovx, real_t aspect) { return Mathf.RadToDeg(Mathf.Atan(aspect * Mathf.Tan(Mathf.DegToRad(fovx) * (real_t)0.5)) * (real_t)2.0); } + /// <summary> + /// Returns the factor by which the visible level of detail is scaled by this <see cref="Projection"/>. + /// </summary> + /// <returns>The level of detail factor for this projection.</returns> public readonly real_t GetLodMultiplier() { if (IsOrthogonal()) @@ -341,6 +541,12 @@ namespace Godot } } + /// <summary> + /// Returns the number of pixels with the given pixel width displayed per meter, after + /// this <see cref="Projection"/> is applied. + /// </summary> + /// <param name="forPixelWidth">The width for each pixel (in meters).</param> + /// <returns>The number of pixels per meter.</returns> public readonly int GetPixelsPerMeter(int forPixelWidth) { Vector3 result = this * new Vector3(1, 0, -1); @@ -348,6 +554,15 @@ namespace Godot return (int)((result.x * (real_t)0.5 + (real_t)0.5) * forPixelWidth); } + /// <summary> + /// Returns the clipping plane of this <see cref="Projection"/> whose index is given + /// by <paramref name="plane"/>. + /// <paramref name="plane"/> should be equal to one of <see cref="Planes.Near"/>, + /// <see cref="Planes.Far"/>, <see cref="Planes.Left"/>, <see cref="Planes.Top"/>, + /// <see cref="Planes.Right"/>, or <see cref="Planes.Bottom"/>. + /// </summary> + /// <param name="plane">The kind of clipping plane to get from the projection.</param> + /// <returns>The clipping plane of this projection.</returns> public readonly Plane GetProjectionPlane(Planes plane) { Plane newPlane = plane switch @@ -364,28 +579,49 @@ namespace Godot return newPlane.Normalized(); } + /// <summary> + /// Returns the dimensions of the far clipping plane of the projection, divided by two. + /// </summary> + /// <returns>The half extents for this projection's far plane.</returns> public readonly Vector2 GetFarPlaneHalfExtents() { var res = GetProjectionPlane(Planes.Far).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } + /// <summary> + /// Returns the dimensions of the viewport plane that this <see cref="Projection"/> + /// projects positions onto, divided by two. + /// </summary> + /// <returns>The half extents for this projection's viewport plane.</returns> public readonly Vector2 GetViewportHalfExtents() { var res = GetProjectionPlane(Planes.Near).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } + /// <summary> + /// Returns the distance for this <see cref="Projection"/> beyond which positions are clipped. + /// </summary> + /// <returns>The distance beyond which positions are clipped.</returns> public readonly real_t GetZFar() { return GetProjectionPlane(Planes.Far).D; } + /// <summary> + /// Returns the distance for this <see cref="Projection"/> before which positions are clipped. + /// </summary> + /// <returns>The distance before which positions are clipped.</returns> public readonly real_t GetZNear() { return -GetProjectionPlane(Planes.Near).D; } + /// <summary> + /// Returns a copy of this <see cref="Projection"/> with the signs of the values of the Y column flipped. + /// </summary> + /// <returns>The flipped projection.</returns> public readonly Projection FlippedY() { Projection proj = this; @@ -393,6 +629,13 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> with the near clipping distance adjusted to be + /// <paramref name="newZNear"/>. + /// Note: The original <see cref="Projection"/> must be a perspective projection. + /// </summary> + /// <param name="newZNear">The near clipping distance to adjust the projection to.</param> + /// <returns>The adjusted projection.</returns> public readonly Projection PerspectiveZNearAdjusted(real_t newZNear) { Projection proj = this; @@ -404,6 +647,12 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> with the X and Y values from the given <see cref="Vector2"/> + /// added to the first and second values of the final column respectively. + /// </summary> + /// <param name="offset">The offset to apply to the projection.</param> + /// <returns>The offseted projection.</returns> public readonly Projection JitterOffseted(Vector2 offset) { Projection proj = this; @@ -412,6 +661,11 @@ namespace Godot return proj; } + /// <summary> + /// Returns a <see cref="Projection"/> that performs the inverse of this <see cref="Projection"/>'s + /// projective transformation. + /// </summary> + /// <returns>The inverted projection.</returns> public readonly Projection Inverse() { Projection proj = this; @@ -535,11 +789,70 @@ namespace Godot return proj; } + /// <summary> + /// Returns <see langword="true"/> if this <see cref="Projection"/> performs an orthogonal projection. + /// </summary> + /// <returns>If the projection performs an orthogonal projection.</returns> public readonly bool IsOrthogonal() { return w.w == (real_t)1.0; } + // Constants + private static readonly Projection _zero = new Projection( + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0) + ); + private static readonly Projection _identity = new Projection( + new Vector4(1, 0, 0, 0), + new Vector4(0, 1, 0, 0), + new Vector4(0, 0, 1, 0), + new Vector4(0, 0, 0, 1) + ); + + /// <summary> + /// Zero projection, a projection with all components set to <c>0</c>. + /// </summary> + /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> + public static Projection Zero { get { return _zero; } } + + /// <summary> + /// The identity projection, with no distortion applied. + /// This is used as a replacement for <c>Projection()</c> in GDScript. + /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> + public static Projection Identity { get { return _identity; } } + + /// <summary> + /// Constructs a projection from 4 vectors (matrix columns). + /// </summary> + /// <param name="x">The X column, or column index 0.</param> + /// <param name="y">The Y column, or column index 1.</param> + /// <param name="z">The Z column, or column index 2.</param> + /// <param name="w">The W column, or column index 3.</param> + public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /// <summary> + /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// </summary> + /// <param name="transform">The <see cref="Transform3D"/>.</param> + public Projection(Transform3D transform) + { + x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); + y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); + z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); + w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + } + /// <summary> /// Composes these two projections by multiplying them /// together. This has the effect of applying the right @@ -646,127 +959,41 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of <see cref="Vector4"/>. + /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. /// </summary> - /// <param name="column">Which column vector.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> is not 0, 1, 2 or 3. - /// </exception> - public Vector4 this[int column] + /// <param name="proj">The <see cref="Projection"/>.</param> + public static explicit operator Transform3D(Projection proj) { - readonly get - { - switch (column) - { - case 0: - return x; - case 1: - return y; - case 2: - return z; - case 3: - return w; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x = value; - return; - case 1: - y = value; - return; - case 2: - z = value; - return; - case 3: - w = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return new Transform3D( + new Basis( + new Vector3(proj.x.x, proj.x.y, proj.x.z), + new Vector3(proj.y.x, proj.y.y, proj.y.z), + new Vector3(proj.z.x, proj.z.y, proj.z.z) + ), + new Vector3(proj.w.x, proj.w.y, proj.w.z) + ); } /// <summary> - /// Access single values. + /// Returns <see langword="true"/> if the projection is exactly equal + /// to the given object (<see paramref="obj"/>). /// </summary> - /// <param name="column">Which column vector.</param> - /// <param name="row">Which row of the column.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. - /// </exception> - public real_t this[int column, int row] + /// <param name="obj">The object to compare with.</param> + /// <returns>Whether or not the vector and the object are equal.</returns> + public override readonly bool Equals(object obj) { - readonly get - { - switch (column) - { - case 0: - return x[row]; - case 1: - return y[row]; - case 2: - return z[row]; - case 3: - return w[row]; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x[row] = value; - return; - case 1: - y[row] = value; - return; - case 2: - z[row] = value; - return; - case 3: - w[row] = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return obj is Projection other && Equals(other); } - // Constants - private static readonly Projection _zero = new Projection( - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0) - ); - private static readonly Projection _identity = new Projection( - new Vector4(1, 0, 0, 0), - new Vector4(0, 1, 0, 0), - new Vector4(0, 0, 1, 0), - new Vector4(0, 0, 0, 1) - ); - - /// <summary> - /// Zero projection, a projection with all components set to <c>0</c>. - /// </summary> - /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> - public static Projection Zero { get { return _zero; } } - /// <summary> - /// The identity projection, with no distortion applied. - /// This is used as a replacement for <c>Projection()</c> in GDScript. - /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// Returns <see langword="true"/> if the projections are exactly equal. /// </summary> - /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> - public static Projection Identity { get { return _identity; } } + /// <param name="other">The other projection.</param> + /// <returns>Whether or not the projections are exactly equal.</returns> + public readonly bool Equals(Projection other) + { + return x == other.x && y == other.y && z == other.z && w == other.w; + } /// <summary> /// Serves as the hash function for <see cref="Projection"/>. @@ -797,26 +1024,5 @@ namespace Godot $"{z.x.ToString(format)}, {z.y.ToString(format)}, {z.z.ToString(format)}, {z.w.ToString(format)}\n" + $"{w.x.ToString(format)}, {w.y.ToString(format)}, {w.z.ToString(format)}, {w.w.ToString(format)}\n"; } - - /// <summary> - /// Returns <see langword="true"/> if the projection is exactly equal - /// to the given object (<see paramref="obj"/>). - /// </summary> - /// <param name="obj">The object to compare with.</param> - /// <returns>Whether or not the vector and the object are equal.</returns> - public override readonly bool Equals(object obj) - { - return obj is Projection other && Equals(other); - } - - /// <summary> - /// Returns <see langword="true"/> if the projections are exactly equal. - /// </summary> - /// <param name="other">The other projection.</param> - /// <returns>Whether or not the projections are exactly equal.</returns> - public readonly bool Equals(Projection other) - { - return x == other.x && y == other.y && z == other.z && w == other.w; - } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index f511233fcc..d4329d78c1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Godot.NativeInterop; @@ -67,30 +69,13 @@ namespace Godot } /// <summary> - /// If the string is a path to a file, return the path to the file without the extension. - /// </summary> - /// <seealso cref="GetExtension(string)"/> - /// <seealso cref="GetBaseDir(string)"/> - /// <seealso cref="GetFile(string)"/> - /// <param name="instance">The path to a file.</param> - /// <returns>The path to the file without the extension.</returns> - public static string GetBaseName(this string instance) - { - int index = instance.LastIndexOf('.'); - - if (index > 0) - return instance.Substring(0, index); - - return instance; - } - - /// <summary> /// Returns <see langword="true"/> if the strings begins /// with the given string <paramref name="text"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <param name="text">The beginning string.</param> /// <returns>If the string begins with the given string.</returns> + [Obsolete("Use string.StartsWith instead.")] public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); @@ -144,15 +129,15 @@ namespace Godot } /// <summary> - /// Returns the amount of substrings <paramref name="what"/> in the string. + /// Returns the number of occurrences of substring <paramref name="what"/> in the string. /// </summary> /// <param name="instance">The string where the substring will be searched.</param> /// <param name="what">The substring that will be counted.</param> - /// <param name="caseSensitive">If the search is case sensitive.</param> /// <param name="from">Index to start searching from.</param> /// <param name="to">Index to stop searching at.</param> - /// <returns>Amount of substrings in the string.</returns> - public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + /// <param name="caseSensitive">If the search is case sensitive.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int Count(this string instance, string what, int from = 0, int to = 0, bool caseSensitive = true) { if (what.Length == 0) { @@ -211,6 +196,82 @@ namespace Godot } /// <summary> + /// Returns the number of occurrences of substring <paramref name="what"/> (ignoring case) + /// between <paramref name="from"/> and <paramref name="to"/> positions. If <paramref name="from"/> + /// and <paramref name="to"/> equals 0 the whole string will be used. If only <paramref name="to"/> + /// equals 0 the remained substring will be used. + /// </summary> + /// <param name="instance">The string where the substring will be searched.</param> + /// <param name="what">The substring that will be counted.</param> + /// <param name="from">Index to start searching from.</param> + /// <param name="to">Index to stop searching at.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int CountN(this string instance, string what, int from = 0, int to = 0) + { + return instance.Count(what, from, to, caseSensitive: false); + } + + /// <summary> + /// Returns a copy of the string with indentation (leading tabs and spaces) removed. + /// See also <see cref="Indent"/> to add indentation. + /// </summary> + /// <param name="instance">The string to remove the indentation from.</param> + /// <returns>The string with the indentation removed.</returns> + public static string Dedent(this string instance) + { + var sb = new StringBuilder(); + string indent = ""; + bool hasIndent = false; + bool hasText = false; + int lineStart = 0; + int indentStop = -1; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (hasText) + { + sb.Append(instance.Substring(indentStop, i - indentStop)); + } + sb.Append('\n'); + hasText = false; + lineStart = i + 1; + indentStop = -1; + } + else if (!hasText) + { + if (c > 32) + { + hasText = true; + if (!hasIndent) + { + hasIndent = true; + indent = instance.Substring(lineStart, i - lineStart); + indentStop = i; + } + } + if (hasIndent && indentStop < 0) + { + int j = i - lineStart; + if (j >= indent.Length || c != indent[j]) + { + indentStop = i; + } + } + } + } + + if (hasText) + { + sb.Append(instance.Substring(indentStop, instance.Length - indentStop)); + } + + return sb.ToString(); + } + + /// <summary> /// Returns a copy of the string with special characters escaped using the C language standard. /// </summary> /// <param name="instance">The string to escape.</param> @@ -443,29 +504,6 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the strings ends - /// with the given string <paramref name="text"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="text">The ending string.</param> - /// <returns>If the string ends with the given string.</returns> - public static bool EndsWith(this string instance, string text) - { - return instance.EndsWith(text); - } - - /// <summary> - /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>. - /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Starting position from which to erase.</param> - /// <param name="chars">Amount of characters to erase.</param> - public static void Erase(this StringBuilder instance, int pos, int chars) - { - instance.Remove(pos, chars); - } - - /// <summary> /// Returns the extension without the leading period character (<c>.</c>) /// if the string is a valid file name or path. If the string does not contain /// an extension, returns an empty string instead. @@ -489,7 +527,7 @@ namespace Godot /// <returns>The extension of the file or an empty string.</returns> public static string GetExtension(this string instance) { - int pos = instance.FindLast("."); + int pos = instance.RFind("."); if (pos < 0) return instance; @@ -498,12 +536,16 @@ namespace Godot } /// <summary> - /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. + /// Returns the index of the first occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. + /// Note: If you just want to know whether a string contains a substring, use the + /// <see cref="string.Contains(string)"/> method. /// </summary> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -519,9 +561,9 @@ namespace Godot /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -529,50 +571,21 @@ namespace Godot /// <returns>The first instance of the char, or -1 if not found.</returns> public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { - // TODO: Could be more efficient if we get a char version of `IndexOf`. - // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); - } + if (caseSensitive) + return instance.IndexOf(what, from); - /// <summary>Find the last occurrence of a substring.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, bool caseSensitive = true) - { - return instance.FindLast(what, instance.Length - 1, caseSensitive); - } - - /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="from">The search starting position.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) - { - return instance.LastIndexOf(what, from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(instance, what, from, CompareOptions.OrdinalIgnoreCase); } /// <summary> - /// Find the first occurrence of a substring but search as case-insensitive. - /// Optionally, the search starting position can be passed. + /// Returns the index of the first case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -616,7 +629,7 @@ namespace Godot } } - int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); + int sep = Mathf.Max(rs.RFind("/"), rs.RFind("\\")); if (sep == -1) return directory; @@ -625,6 +638,24 @@ namespace Godot } /// <summary> + /// If the string is a path to a file, return the path to the file without the extension. + /// </summary> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The path to the file without the extension.</returns> + public static string GetBaseName(this string instance) + { + int index = instance.RFind("."); + + if (index > 0) + return instance.Substring(0, index); + + return instance; + } + + /// <summary> /// If the string is a path to a file, return the file and ignore the base directory. /// </summary> /// <seealso cref="GetBaseName(string)"/> @@ -634,7 +665,7 @@ namespace Godot /// <returns>The file name.</returns> public static string GetFile(this string instance) { - int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); + int sep = Mathf.Max(instance.RFind("/"), instance.RFind("\\")); if (sep == -1) return instance; @@ -643,8 +674,8 @@ namespace Godot } /// <summary> - /// Converts the given byte array of ASCII encoded text to a string. - /// Faster alternative to <see cref="GetStringFromUTF8"/> if the + /// Converts ASCII encoded array to string. + /// Fast alternative to <see cref="GetStringFromUTF8"/> if the /// content is ASCII-only. Unlike the UTF-8 function this function /// maps every byte to a character in the array. Multibyte sequences /// will not be interpreted correctly. For parsing user input always @@ -658,13 +689,35 @@ namespace Godot } /// <summary> - /// Converts the given byte array of UTF-8 encoded text to a string. + /// Converts UTF-16 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-16 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF16(this byte[] bytes) + { + return Encoding.Unicode.GetString(bytes); + } + + /// <summary> + /// Converts UTF-32 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-32 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF32(this byte[] bytes) + { + return Encoding.UTF32.GetString(bytes); + } + + /// <summary> + /// Converts UTF-8 encoded array to string. /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8 /// encoded data. Use this function if you are unsure about the /// source of the data. For user input this function /// should always be preferred. /// </summary> - /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param> + /// <param name="bytes"> + /// A byte array of UTF-8 characters (a character may take up multiple bytes). + /// </param> /// <returns>A string created from the bytes.</returns> public static string GetStringFromUTF8(this byte[] bytes) { @@ -766,18 +819,44 @@ namespace Godot } /// <summary> - /// Inserts a substring at a given position. + /// Returns a copy of the string with lines indented with <paramref name="prefix"/>. + /// For example, the string can be indented with two tabs using <c>"\t\t"</c>, + /// or four spaces using <c>" "</c>. The prefix can be any string so it can + /// also be used to comment out strings with e.g. <c>"// </c>. + /// See also <see cref="Dedent"/> to remove indentation. + /// Note: Empty lines are kept empty. /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Position at which to insert the substring.</param> - /// <param name="what">Substring to insert.</param> - /// <returns> - /// The string with <paramref name="what"/> inserted at the given - /// position <paramref name="pos"/>. - /// </returns> - public static string Insert(this string instance, int pos, string what) + /// <param name="instance">The string to add indentation to.</param> + /// <param name="prefix">The string to use as indentation.</param> + /// <returns>The string with indentation added.</returns> + public static string Indent(this string instance, string prefix) { - return instance.Insert(pos, what); + var sb = new StringBuilder(); + int lineStart = 0; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (i == lineStart) + { + sb.Append(c); // Leave empty lines empty. + } + else + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart, i - lineStart + 1)); + } + lineStart = i + 1; + } + } + if (lineStart != instance.Length) + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart)); + } + return sb.ToString(); } /// <summary> @@ -873,19 +952,94 @@ namespace Godot return instance.IsSubsequenceOf(text, caseSensitive: false); } + private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' }; + + /// <summary> + /// Returns <see langword="true"/> if this string is free from characters that + /// aren't allowed in file names. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid file name.</returns> + public static bool IsValidFileName(this string instance) + { + var stripped = instance.Trim(); + if (instance != stripped) + return false; + + if (string.IsNullOrEmpty(stripped)) + return false; + + return instance.IndexOfAny(_invalidFileNameCharacters) == -1; + } + /// <summary> - /// Check whether the string contains a valid <see langword="float"/>. + /// Returns <see langword="true"/> if this string contains a valid <see langword="float"/>. + /// This is inclusive of integers, and also supports exponents. /// </summary> + /// <example> + /// <code> + /// GD.Print("1.7".IsValidFloat()) // Prints "True" + /// GD.Print("24".IsValidFloat()) // Prints "True" + /// GD.Print("7e3".IsValidFloat()) // Prints "True" + /// GD.Print("Hello".IsValidFloat()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid floating point number.</returns> public static bool IsValidFloat(this string instance) { - float f; - return float.TryParse(instance, out f); + return float.TryParse(instance, out _); + } + + /// <summary> + /// Returns <see langword="true"/> if this string contains a valid hexadecimal number. + /// If <paramref name="withPrefix"/> is <see langword="true"/>, then a validity of the + /// hexadecimal number is determined by <c>0x</c> prefix, for instance: <c>0xDEADC0DE</c>. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="withPrefix">If the string must contain the <c>0x</c> prefix to be valid.</param> + /// <returns>If the string contains a valid hexadecimal number.</returns> + public static bool IsValidHexNumber(this string instance, bool withPrefix = false) + { + if (string.IsNullOrEmpty(instance)) + return false; + + int from = 0; + if (instance.Length != 1 && instance[0] == '+' || instance[0] == '-') + { + from++; + } + + if (withPrefix) + { + if (instance.Length < 3) + return false; + if (instance[from] != '0' || instance[from + 1] != 'x') + return false; + from += 2; + } + + for (int i = from; i < instance.Length; i++) + { + char c = instance[i]; + if (IsHexDigit(c)) + continue; + + return false; + } + + return true; + + static bool IsHexDigit(char c) + { + return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } } /// <summary> - /// Check whether the string contains a valid color in HTML notation. + /// Returns <see langword="true"/> if this string contains a valid color in hexadecimal + /// HTML notation. Other HTML notations such as named colors or <c>hsl()</c> aren't + /// considered valid by this method and will return <see langword="false"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid HTML color.</returns> @@ -895,10 +1049,17 @@ namespace Godot } /// <summary> - /// Check whether the string is a valid identifier. As is common in - /// programming languages, a valid identifier may contain only letters, - /// digits and underscores (_) and the first character may not be a digit. + /// Returns <see langword="true"/> if this string is a valid identifier. + /// A valid identifier may contain only letters, digits and underscores (<c>_</c>) + /// and the first character may not be a digit. /// </summary> + /// <example> + /// <code> + /// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True" + /// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False" + /// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid identifier.</returns> public static bool IsValidIdentifier(this string instance) @@ -926,38 +1087,73 @@ namespace Godot } /// <summary> - /// Check whether the string contains a valid integer. + /// Returns <see langword="true"/> if this string contains a valid <see langword="int"/>. /// </summary> + /// <example> + /// <code> + /// GD.Print("7".IsValidInt()) // Prints "True" + /// GD.Print("14.6".IsValidInt()) // Prints "False" + /// GD.Print("L".IsValidInt()) // Prints "False" + /// GD.Print("+3".IsValidInt()) // Prints "True" + /// GD.Print("-12".IsValidInt()) // Prints "True" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid integer.</returns> - public static bool IsValidInteger(this string instance) + public static bool IsValidInt(this string instance) { - int f; - return int.TryParse(instance, out f); + return int.TryParse(instance, out _); } /// <summary> - /// Check whether the string contains a valid IP address. + /// Returns <see langword="true"/> if this string contains only a well-formatted + /// IPv4 or IPv6 address. This method considers reserved IP addresses such as + /// <c>0.0.0.0</c> as valid. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid IP address.</returns> public static bool IsValidIPAddress(this string instance) { - // TODO: Support IPv6 addresses - string[] ip = instance.Split("."); + if (instance.Contains(':')) + { + string[] ip = instance.Split(':'); - if (ip.Length != 4) - return false; + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (n.Length == 0) + continue; + + if (n.IsValidHexNumber(withPrefix: false)) + { + long nint = n.HexToInt(); + if (nint < 0 || nint > 0xffff) + return false; - for (int i = 0; i < ip.Length; i++) + continue; + } + + if (!n.IsValidIPAddress()) + return false; + } + } + else { - string n = ip[i]; - if (!n.IsValidInteger()) - return false; + string[] ip = instance.Split('.'); - int val = n.ToInt(); - if (val < 0 || val > 255) + if (ip.Length != 4) return false; + + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (!n.IsValidInt()) + return false; + + int val = n.ToInt(); + if (val < 0 || val > 255) + return false; + } } return true; @@ -1003,41 +1199,20 @@ namespace Godot } /// <summary> - /// Returns the length of the string in characters. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <returns>The length of the string.</returns> - public static int Length(this string instance) - { - return instance.Length; - } - - /// <summary> /// Returns a copy of the string with characters removed from the left. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a prefix. See <see cref="TrimPrefix"/> + /// method that will remove a single prefix string rather than a set of characters. /// </summary> /// <seealso cref="RStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the left.</returns> + [Obsolete("Use string.TrimStart instead.")] public static string LStrip(this string instance, string chars) { - int len = instance.Length; - int beg; - - for (beg = 0; beg < len; beg++) - { - if (chars.Find(instance[beg]) == -1) - { - break; - } - } - - if (beg == 0) - { - return instance; - } - - return instance.Substr(beg, len - beg); + return instance.TrimStart(chars.ToCharArray()); } /// <summary> @@ -1117,10 +1292,9 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); - using (md5Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + return MD5.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5351 } /// <summary> @@ -1131,10 +1305,7 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); - using (md5Text) - return Marshaling.ConvertStringToManaged(md5Text); + return instance.MD5Buffer().HexEncode(); } /// <summary> @@ -1151,17 +1322,6 @@ namespace Godot } /// <summary> - /// Returns the character code at position <paramref name="at"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="at">The position int the string for the character to check.</param> - /// <returns>The character code.</returns> - public static int OrdAt(this string instance, int at) - { - return instance[at]; - } - - /// <summary> /// Format a number to have an exact number of <paramref name="digits"/> /// after the decimal point. /// </summary> @@ -1282,34 +1442,47 @@ namespace Godot } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. + /// Returns the index of the last occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> - public static int RFind(this string instance, string what, int from = -1) + public static int RFind(this string instance, string what, int from = -1, bool caseSensitive = true) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. - /// Also search case-insensitive. + /// Returns the index of the last case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> - /// <seealso cref="RFind(string, string, int)"/> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -1332,30 +1505,43 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the right. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a suffix. See <see cref="TrimSuffix"/> + /// method that will remove a single suffix string rather than a set of characters. /// </summary> /// <seealso cref="LStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the right.</returns> + [Obsolete("Use string.TrimEnd instead.")] public static string RStrip(this string instance, string chars) { - int len = instance.Length; - int end; - - for (end = len - 1; end >= 0; end--) - { - if (chars.Find(instance[end]) == -1) - { - break; - } - } + return instance.TrimEnd(chars.ToCharArray()); + } - if (end == len - 1) - { - return instance; - } + /// <summary> + /// Returns the SHA-1 hash of the string as an array of bytes. + /// </summary> + /// <seealso cref="SHA1Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static byte[] SHA1Buffer(this string instance) + { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + return SHA1.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5350 + } - return instance.Substr(0, end + 1); + /// <summary> + /// Returns the SHA-1 hash of the string as a string. + /// </summary> + /// <seealso cref="SHA1Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static string SHA1Text(this string instance) + { + return instance.SHA1Buffer().HexEncode(); } /// <summary> @@ -1366,10 +1552,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); - using (sha256Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); + return SHA256.HashData(Encoding.UTF8.GetBytes(instance)); } /// <summary> @@ -1380,10 +1563,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); - using (sha256Text) - return Marshaling.ConvertStringToManaged(sha256Text); + return instance.SHA256Buffer().HexEncode(); } /// <summary> @@ -1455,7 +1635,7 @@ namespace Godot /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, + return instance.Split(divisor, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } @@ -1503,8 +1683,10 @@ namespace Godot }; /// <summary> - /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. - /// The optional arguments are used to toggle stripping on the left and right edges respectively. + /// Returns a copy of the string stripped of any non-printable character + /// (including tabulations, spaces and line breaks) at the beginning and the end. + /// The optional arguments are used to toggle stripping on the left and right + /// edges respectively. /// </summary> /// <param name="instance">The string to strip.</param> /// <param name="left">If the left side should be stripped.</param> @@ -1522,6 +1704,30 @@ namespace Godot return instance.TrimEnd(_nonPrintable); } + + /// <summary> + /// Returns a copy of the string stripped of any escape character. + /// These include all non-printable control characters of the first page + /// of the ASCII table (< 32), such as tabulation (<c>\t</c>) and + /// newline (<c>\n</c> and <c>\r</c>) characters, but not spaces. + /// </summary> + /// <param name="instance">The string to strip.</param> + /// <returns>The string stripped of any escape characters.</returns> + public static string StripEscapes(this string instance) + { + var sb = new StringBuilder(); + for (int i = 0; i < instance.Length; i++) + { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (instance[i] < 32) + continue; + + sb.Append(instance[i]); + } + + return sb.ToString(); + } + /// <summary> /// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. /// </summary> @@ -1539,13 +1745,15 @@ namespace Godot /// <summary> /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). - /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption - /// that all the characters the String contains are only ASCII characters. + /// The conversion is faster compared to <see cref="ToUTF8Buffer(string)"/>, + /// as this method assumes that all the characters in the String are ASCII characters. /// </summary> - /// <seealso cref="ToUTF8(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as ASCII encoded bytes.</returns> - public static byte[] ToAscii(this string instance) + public static byte[] ToASCIIBuffer(this string instance) { return Encoding.ASCII.GetBytes(instance); } @@ -1573,41 +1781,76 @@ namespace Godot } /// <summary> - /// Returns the string converted to lowercase. + /// Converts the string (which is an array of characters) to an UTF-16 encoded array of bytes. /// </summary> - /// <seealso cref="ToUpper(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to lowercase.</returns> - public static string ToLower(this string instance) + /// <returns>The string as UTF-16 encoded bytes.</returns> + public static byte[] ToUTF16Buffer(this string instance) { - return instance.ToLower(); + return Encoding.Unicode.GetBytes(instance); } /// <summary> - /// Returns the string converted to uppercase. + /// Converts the string (which is an array of characters) to an UTF-32 encoded array of bytes. /// </summary> - /// <seealso cref="ToLower(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to uppercase.</returns> - public static string ToUpper(this string instance) + /// <returns>The string as UTF-32 encoded bytes.</returns> + public static byte[] ToUTF32Buffer(this string instance) { - return instance.ToUpper(); + return Encoding.UTF32.GetBytes(instance); } /// <summary> - /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). - /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters. - /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>. + /// Converts the string (which is an array of characters) to an UTF-8 encoded array of bytes. + /// The conversion is a bit slower than <see cref="ToASCIIBuffer(string)"/>, + /// but supports all UTF-8 characters. Therefore, you should prefer this function + /// over <see cref="ToASCIIBuffer(string)"/>. /// </summary> - /// <seealso cref="ToAscii(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as UTF-8 encoded bytes.</returns> - public static byte[] ToUTF8(this string instance) + public static byte[] ToUTF8Buffer(this string instance) { return Encoding.UTF8.GetBytes(instance); } /// <summary> + /// Removes a given string from the start if it starts with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the prefix from.</param> + /// <param name="prefix">The string to remove from the start.</param> + /// <returns>A copy of the string with the prefix string removed from the start.</returns> + public static string TrimPrefix(this string instance, string prefix) + { + if (instance.StartsWith(prefix)) + return instance.Substring(prefix.Length); + + return instance; + } + + /// <summary> + /// Removes a given string from the end if it ends with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the suffix from.</param> + /// <param name="suffix">The string to remove from the end.</param> + /// <returns>A copy of the string with the suffix string removed from the end.</returns> + public static string TrimSuffix(this string instance, string suffix) + { + if (instance.EndsWith(suffix)) + return instance.Substring(0, instance.Length - suffix.Length); + + return instance; + } + + /// <summary> /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. /// This mostly wraps around <see cref="Uri.UnescapeDataString"/>, @@ -1634,6 +1877,25 @@ namespace Godot return Uri.EscapeDataString(instance); } + private const string _uniqueNodePrefix = "%"; + private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix }; + + /// <summary> + /// Removes any characters from the string that are prohibited in + /// <see cref="Node"/> names (<c>.</c> <c>:</c> <c>@</c> <c>/</c> <c>"</c>). + /// </summary> + /// <param name="instance">The string to sanitize.</param> + /// <returns>The string sanitized as a valid node name.</returns> + public static string ValidateNodeName(this string instance) + { + string name = instance.Replace(_invalidNodeNameCharacters[0], ""); + for (int i = 1; i < _invalidNodeNameCharacters.Length; i++) + { + name = name.Replace(_invalidNodeNameCharacters[i], ""); + } + return name; + } + /// <summary> /// Returns a copy of the string with special characters escaped using the XML standard. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs index d354509dbf..237a4da364 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs @@ -121,6 +121,14 @@ public partial struct Variant : IDisposable } [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant From<[MustBeVariant] T>(in T from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFrom(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T As<[MustBeVariant] T>() => + VariantUtils.ConvertTo<T>(NativeVar.DangerousSelfRef); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool AsBool() => VariantUtils.ConvertToBool((godot_variant)NativeVar); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index e3fb254f49..503e5abe37 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -101,9 +101,8 @@ <Compile Include="Core\NativeInterop\InteropUtils.cs" /> <Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" /> <Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" /> - <Compile Include="Core\NativeInterop\VariantConversionCallbacks.cs" /> - <Compile Include="Core\NativeInterop\VariantSpanHelpers.cs" /> <Compile Include="Core\NativeInterop\VariantUtils.cs" /> + <Compile Include="Core\NativeInterop\VariantUtils.generic.cs" /> <Compile Include="Core\NodePath.cs" /> <Compile Include="Core\Object.base.cs" /> <Compile Include="Core\Object.exceptions.cs" /> @@ -123,6 +122,7 @@ <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Transform3D.cs" /> + <Compile Include="Core\Variant.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> @@ -131,7 +131,6 @@ <Compile Include="Core\Vector4i.cs" /> <Compile Include="GlobalUsings.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Variant.cs" /> </ItemGroup> <!-- We import a props file with auto-generated includes. This works well with Rider. diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index e20a88076a..338e5a0147 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1067,30 +1067,6 @@ void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } -void godotsharp_string_md5_buffer(const String *p_self, PackedByteArray *r_md5_buffer) { - memnew_placement(r_md5_buffer, PackedByteArray(p_self->md5_buffer())); -} - -void godotsharp_string_md5_text(const String *p_self, String *r_md5_text) { - memnew_placement(r_md5_text, String(p_self->md5_text())); -} - -int32_t godotsharp_string_rfind(const String *p_self, const String *p_what, int32_t p_from) { - return p_self->rfind(*p_what, p_from); -} - -int32_t godotsharp_string_rfindn(const String *p_self, const String *p_what, int32_t p_from) { - return p_self->rfindn(*p_what, p_from); -} - -void godotsharp_string_sha256_buffer(const String *p_self, PackedByteArray *r_sha256_buffer) { - memnew_placement(r_sha256_buffer, PackedByteArray(p_self->sha256_buffer())); -} - -void godotsharp_string_sha256_text(const String *p_self, String *r_sha256_text) { - memnew_placement(r_sha256_text, String(p_self->sha256_text())); -} - void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_path) { memnew_placement(r_simplified_path, String(p_self->simplify_path())); } @@ -1473,12 +1449,6 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_dictionary_duplicate, (void *)godotsharp_dictionary_remove_key, (void *)godotsharp_dictionary_to_string, - (void *)godotsharp_string_md5_buffer, - (void *)godotsharp_string_md5_text, - (void *)godotsharp_string_rfind, - (void *)godotsharp_string_rfindn, - (void *)godotsharp_string_sha256_buffer, - (void *)godotsharp_string_sha256_text, (void *)godotsharp_string_simplify_path, (void *)godotsharp_string_to_camel_case, (void *)godotsharp_string_to_pascal_case, diff --git a/modules/noise/editor/noise_editor_plugin.cpp b/modules/noise/editor/noise_editor_plugin.cpp index 47f5f8f819..a8e376e142 100644 --- a/modules/noise/editor/noise_editor_plugin.cpp +++ b/modules/noise/editor/noise_editor_plugin.cpp @@ -34,6 +34,7 @@ #include "editor/editor_inspector.h" #include "editor/editor_scale.h" +#include "scene/gui/button.h" #include "scene/gui/texture_rect.h" #include "modules/noise/noise.h" diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp index ea539f2053..753fc5fa89 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -47,11 +47,15 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api) { singleton = this; - request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = nullptr; // must be available + request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = &loader_init_extension_available; request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available; } void OpenXRAndroidExtension::on_before_instance_created() { + if (!loader_init_extension_available) { + print_line("OpenXR: XR_KHR_loader_init_android is not reported as available - trying to initialize anyway..."); + } + EXT_INIT_XR_FUNC(xrInitializeLoaderKHR); JNIEnv *env = get_jni_env(); diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h index ca6011559a..087b634756 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -48,6 +48,7 @@ public: private: static OpenXRAndroidExtension *singleton; + bool loader_init_extension_available = false; bool create_instance_extension_available = false; // Initialize the loader diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index ee69144123..234c5f8391 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -138,7 +138,7 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex graphics_binding_gl.display = eglGetCurrentDisplay(); graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122 - graphics_binding_gl.context = eglGetCurrentContext(); + graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT); #else graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR; graphics_binding_gl.next = p_next_pointer; @@ -160,16 +160,8 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex } void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { -#ifdef WIN32 - p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); - p_usable_swap_chains.push_back(GL_RGBA8); -#elif ANDROID_ENABLED p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); p_usable_swap_chains.push_back(GL_RGBA8); -#else - p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8_EXT); - p_usable_swap_chains.push_back(GL_RGBA8_EXT); -#endif } void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_depth_formats) { @@ -294,59 +286,7 @@ void OpenXROpenGLExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g String OpenXROpenGLExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { // These are somewhat different per platform, will need to weed some stuff out... switch (p_swapchain_format) { -#ifdef WIN32 - // using definitions from GLAD - ENUM_TO_STRING_CASE(GL_R8_SNORM) - ENUM_TO_STRING_CASE(GL_RG8_SNORM) - ENUM_TO_STRING_CASE(GL_RGB8_SNORM) - ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) - ENUM_TO_STRING_CASE(GL_R16_SNORM) - ENUM_TO_STRING_CASE(GL_RG16_SNORM) - ENUM_TO_STRING_CASE(GL_RGB16_SNORM) - ENUM_TO_STRING_CASE(GL_RGBA16_SNORM) - ENUM_TO_STRING_CASE(GL_RGB4) - ENUM_TO_STRING_CASE(GL_RGB5) - ENUM_TO_STRING_CASE(GL_RGB8) - ENUM_TO_STRING_CASE(GL_RGB10) - ENUM_TO_STRING_CASE(GL_RGB12) - ENUM_TO_STRING_CASE(GL_RGB16) - ENUM_TO_STRING_CASE(GL_RGBA2) - ENUM_TO_STRING_CASE(GL_RGBA4) - ENUM_TO_STRING_CASE(GL_RGB5_A1) - ENUM_TO_STRING_CASE(GL_RGBA8) - ENUM_TO_STRING_CASE(GL_RGB10_A2) - ENUM_TO_STRING_CASE(GL_RGBA12) - ENUM_TO_STRING_CASE(GL_RGBA16) - ENUM_TO_STRING_CASE(GL_RGBA32F) - ENUM_TO_STRING_CASE(GL_RGB32F) - ENUM_TO_STRING_CASE(GL_RGBA16F) - ENUM_TO_STRING_CASE(GL_RGB16F) - ENUM_TO_STRING_CASE(GL_RGBA32UI) - ENUM_TO_STRING_CASE(GL_RGB32UI) - ENUM_TO_STRING_CASE(GL_RGBA16UI) - ENUM_TO_STRING_CASE(GL_RGB16UI) - ENUM_TO_STRING_CASE(GL_RGBA8UI) - ENUM_TO_STRING_CASE(GL_RGB8UI) - ENUM_TO_STRING_CASE(GL_RGBA32I) - ENUM_TO_STRING_CASE(GL_RGB32I) - ENUM_TO_STRING_CASE(GL_RGBA16I) - ENUM_TO_STRING_CASE(GL_RGB16I) - ENUM_TO_STRING_CASE(GL_RGBA8I) - ENUM_TO_STRING_CASE(GL_RGB8I) - ENUM_TO_STRING_CASE(GL_RGB10_A2UI) - ENUM_TO_STRING_CASE(GL_SRGB) - ENUM_TO_STRING_CASE(GL_SRGB8) - ENUM_TO_STRING_CASE(GL_SRGB_ALPHA) - ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32) - ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) - ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32F) - ENUM_TO_STRING_CASE(GL_DEPTH32F_STENCIL8) - -#elif ANDROID_ENABLED +#ifdef ANDROID_ENABLED // using definitions from GLES3/gl3.h ENUM_TO_STRING_CASE(GL_RGBA4) @@ -418,44 +358,56 @@ String OpenXROpenGLExtension::get_swapchain_format_name(int64_t p_swapchain_form ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) #else - // using definitions from GL/gl.h - ENUM_TO_STRING_CASE(GL_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA8_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA12_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA16_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE8_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE16_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE4_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE6_ALPHA2_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE8_ALPHA8_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA12_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE16_ALPHA16_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY4_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY8_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY12_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY16_EXT) - ENUM_TO_STRING_CASE(GL_RGB2_EXT) - ENUM_TO_STRING_CASE(GL_RGB4_EXT) - ENUM_TO_STRING_CASE(GL_RGB5_EXT) - ENUM_TO_STRING_CASE(GL_RGB8_EXT) - ENUM_TO_STRING_CASE(GL_RGB10_EXT) - ENUM_TO_STRING_CASE(GL_RGB12_EXT) - ENUM_TO_STRING_CASE(GL_RGB16_EXT) - ENUM_TO_STRING_CASE(GL_RGBA2_EXT) - ENUM_TO_STRING_CASE(GL_RGBA4_EXT) - ENUM_TO_STRING_CASE(GL_RGB5_A1_EXT) - ENUM_TO_STRING_CASE(GL_RGBA8_EXT) - ENUM_TO_STRING_CASE(GL_RGB10_A2_EXT) - ENUM_TO_STRING_CASE(GL_RGBA12_EXT) - ENUM_TO_STRING_CASE(GL_RGBA16_EXT) - ENUM_TO_STRING_CASE(GL_SRGB_EXT) - ENUM_TO_STRING_CASE(GL_SRGB8_EXT) - ENUM_TO_STRING_CASE(GL_SRGB_ALPHA_EXT) - ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8_EXT) + // using definitions from GLAD + ENUM_TO_STRING_CASE(GL_R8_SNORM) + ENUM_TO_STRING_CASE(GL_RG8_SNORM) + ENUM_TO_STRING_CASE(GL_RGB8_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) + ENUM_TO_STRING_CASE(GL_R16_SNORM) + ENUM_TO_STRING_CASE(GL_RG16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB16_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB4) + ENUM_TO_STRING_CASE(GL_RGB5) + ENUM_TO_STRING_CASE(GL_RGB8) + ENUM_TO_STRING_CASE(GL_RGB10) + ENUM_TO_STRING_CASE(GL_RGB12) + ENUM_TO_STRING_CASE(GL_RGB16) + ENUM_TO_STRING_CASE(GL_RGBA2) + ENUM_TO_STRING_CASE(GL_RGBA4) + ENUM_TO_STRING_CASE(GL_RGB5_A1) + ENUM_TO_STRING_CASE(GL_RGBA8) + ENUM_TO_STRING_CASE(GL_RGB10_A2) + ENUM_TO_STRING_CASE(GL_RGBA12) + ENUM_TO_STRING_CASE(GL_RGBA16) + ENUM_TO_STRING_CASE(GL_RGBA32F) + ENUM_TO_STRING_CASE(GL_RGB32F) + ENUM_TO_STRING_CASE(GL_RGBA16F) + ENUM_TO_STRING_CASE(GL_RGB16F) + ENUM_TO_STRING_CASE(GL_RGBA32UI) + ENUM_TO_STRING_CASE(GL_RGB32UI) + ENUM_TO_STRING_CASE(GL_RGBA16UI) + ENUM_TO_STRING_CASE(GL_RGB16UI) + ENUM_TO_STRING_CASE(GL_RGBA8UI) + ENUM_TO_STRING_CASE(GL_RGB8UI) + ENUM_TO_STRING_CASE(GL_RGBA32I) + ENUM_TO_STRING_CASE(GL_RGB32I) + ENUM_TO_STRING_CASE(GL_RGBA16I) + ENUM_TO_STRING_CASE(GL_RGB16I) + ENUM_TO_STRING_CASE(GL_RGBA8I) + ENUM_TO_STRING_CASE(GL_RGB8I) + ENUM_TO_STRING_CASE(GL_RGB10_A2UI) + ENUM_TO_STRING_CASE(GL_SRGB) + ENUM_TO_STRING_CASE(GL_SRGB8) + ENUM_TO_STRING_CASE(GL_SRGB_ALPHA) + ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32) + ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) + ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32F) + ENUM_TO_STRING_CASE(GL_DEPTH32F_STENCIL8) #endif default: { return String("Swapchain format 0x") + String::num_int64(p_swapchain_format, 16); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index b666653c8e..473c5157c0 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -59,9 +59,8 @@ #include OPENGL_INCLUDE_H #define GL_GLEXT_PROTOTYPES 1 #define GL3_PROTOTYPES 1 -#include <GL/gl.h> -#include <GL/glext.h> -#include <GL/glx.h> +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" #include <X11/Xlib.h> #endif diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index b7c95415d0..59d3865acd 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -51,7 +51,7 @@ #define XR_USE_GRAPHICS_API_VULKAN #endif #ifdef GLES3_ENABLED -#ifdef ANDROID +#ifdef ANDROID_ENABLED #define XR_USE_GRAPHICS_API_OPENGL_ES #include <EGL/egl.h> #include <EGL/eglext.h> @@ -59,14 +59,13 @@ #include <GLES3/gl3ext.h> #else #define XR_USE_GRAPHICS_API_OPENGL -#endif // ANDROID +#endif // ANDROID_ENABLED #ifdef X11_ENABLED #include OPENGL_INCLUDE_H #define GL_GLEXT_PROTOTYPES 1 #define GL3_PROTOTYPES 1 -#include <GL/gl.h> -#include <GL/glext.h> -#include <GL/glx.h> +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" #include <X11/Xlib.h> #endif // X11_ENABLED #endif // GLES_ENABLED diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index e31d88b741..527e02f855 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -1,5 +1,7 @@ import glob, os, shutil, subprocess, re +git_tag = "v3.13.5" + include_dirs = [ "common/tasking", "kernels/bvh", @@ -12,6 +14,7 @@ include_dirs = [ "common/lexers", "common/simd", "common/simd/arm", + "common/simd/wasm", "include/embree3", "kernels/subdiv", "kernels/geometry", @@ -76,6 +79,7 @@ if os.path.exists(dir_name): subprocess.run(["git", "clone", "https://github.com/embree/embree.git", "embree-tmp"]) os.chdir("embree-tmp") +subprocess.run(["git", "checkout", git_tag]) commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() @@ -94,8 +98,7 @@ for f in all_files: with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: hash_file.write( - f""" -// Copyright 2009-2020 Intel Corporation + f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #define RTC_HASH "{commit_hash}" @@ -104,8 +107,7 @@ with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: config_file.write( - """ -// Copyright 2009-2020 Intel Corporation + """// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 /* #undef EMBREE_RAY_MASK */ @@ -126,6 +128,7 @@ with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: /* #undef EMBREE_COMPACT_POLYS */ #define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0 +#define EMBREE_DISC_POINT_SELF_INTERSECTION_AVOIDANCE #if defined(EMBREE_GEOMETRY_TRIANGLE) #define IF_ENABLED_TRIS(x) x @@ -192,8 +195,7 @@ with open("CMakeLists.txt", "r") as cmake_file: with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as config_file: config_file.write( - f""" -// Copyright 2009-2021 Intel Corporation + f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #pragma once @@ -209,14 +211,16 @@ with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as con #define EMBREE_MIN_WIDTH 0 #define RTC_MIN_WIDTH EMBREE_MIN_WIDTH -#define EMBREE_STATIC_LIB -/* #undef EMBREE_API_NAMESPACE */ +#if !defined(EMBREE_STATIC_LIB) +# define EMBREE_STATIC_LIB +#endif +/* #undef EMBREE_API_NAMESPACE*/ #if defined(EMBREE_API_NAMESPACE) # define RTC_NAMESPACE -# define RTC_NAMESPACE_BEGIN namespace {{ +# define RTC_NAMESPACE_BEGIN namespace {{ # define RTC_NAMESPACE_END }} -# define RTC_NAMESPACE_USE using namespace ; +# define RTC_NAMESPACE_USE using namespace; # define RTC_API_EXTERN_C # undef EMBREE_API_NAMESPACE #else |