diff options
Diffstat (limited to 'modules')
30 files changed, 388 insertions, 154 deletions
diff --git a/modules/assimp/import_utils.h b/modules/assimp/import_utils.h index f4505249db..c522b01727 100644 --- a/modules/assimp/import_utils.h +++ b/modules/assimp/import_utils.h @@ -355,11 +355,13 @@ public: print_verbose("Open Asset Import: Loading embedded texture " + filename); if (tex->mHeight == 0) { if (tex->CheckFormat("png")) { + ERR_FAIL_COND_V(Image::_png_mem_loader_func == NULL, Ref<Image>()); Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); state.path_to_image_cache.insert(p_path, img); return img; } else if (tex->CheckFormat("jpg")) { + ERR_FAIL_COND_V(Image::_jpg_mem_loader_func == NULL, Ref<Image>()); Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth); ERR_FAIL_COND_V(img.is_null(), Ref<Image>()); state.path_to_image_cache.insert(p_path, img); diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 61fc7688c5..21dd758391 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -479,23 +479,23 @@ void NetworkedMultiplayerENet::disconnect_peer(int p_peer, bool now) { ERR_FAIL_COND(!peer_map.has(p_peer)); if (now) { + int *id = (int *)peer_map[p_peer]->data; enet_peer_disconnect_now(peer_map[p_peer], 0); // enet_peer_disconnect_now doesn't generate ENET_EVENT_TYPE_DISCONNECT, // notify everyone else, send disconnect signal & remove from peer_map like in poll() + if (server_relay) { + for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { - int *id = NULL; - for (Map<int, ENetPeer *>::Element *E = peer_map.front(); E; E = E->next()) { + if (E->key() == p_peer) { + continue; + } - if (E->key() == p_peer) { - id = (int *)(E->get()->data); - continue; + ENetPacket *packet = enet_packet_create(NULL, 8, ENET_PACKET_FLAG_RELIABLE); + encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]); + encode_uint32(p_peer, &packet->data[4]); + enet_peer_send(E->get(), SYSCH_CONFIG, packet); } - - ENetPacket *packet = enet_packet_create(NULL, 8, ENET_PACKET_FLAG_RELIABLE); - encode_uint32(SYSMSG_REMOVE_PEER, &packet->data[0]); - encode_uint32(p_peer, &packet->data[4]); - enet_peer_send(E->get(), SYSCH_CONFIG, packet); } if (id) diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8abf2ee7ca..a255b92257 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -320,7 +320,7 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { if (ScriptDebugger::get_singleton()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); } ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + "."); } @@ -915,14 +915,43 @@ GDScript::GDScript() : #endif } +void GDScript::_save_orphaned_subclasses() { + struct ClassRefWithName { + ObjectID id; + String fully_qualified_name; + }; + Vector<ClassRefWithName> weak_subclasses; + // collect subclasses ObjectID and name + for (Map<StringName, Ref<GDScript> >::Element *E = subclasses.front(); E; E = E->next()) { + E->get()->_owner = NULL; //bye, you are no longer owned cause I died + ClassRefWithName subclass; + subclass.id = E->get()->get_instance_id(); + subclass.fully_qualified_name = E->get()->fully_qualified_name; + weak_subclasses.push_back(subclass); + } + + // clear subclasses to allow unused subclasses to be deleted + subclasses.clear(); + // subclasses are also held by constants, clear those as well + constants.clear(); + + // keep orphan subclass only for subclasses that are still in use + for (int i = 0; i < weak_subclasses.size(); i++) { + ClassRefWithName subclass = weak_subclasses[i]; + Object *obj = ObjectDB::get_instance(subclass.id); + if (!obj) + continue; + // subclass is not released + GDScriptLanguage::get_singleton()->add_orphan_subclass(subclass.fully_qualified_name, subclass.id); + } +} + GDScript::~GDScript() { for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { memdelete(E->get()); } - for (Map<StringName, Ref<GDScript> >::Element *E = subclasses.front(); E; E = E->next()) { - E->get()->_owner = NULL; //bye, you are no longer owned cause I died - } + _save_orphaned_subclasses(); #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->lock) { @@ -2176,6 +2205,22 @@ GDScriptLanguage::~GDScriptLanguage() { singleton = NULL; } +void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) { + orphan_subclasses[p_qualified_name] = p_subclass; +} + +Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_name) { + Map<String, ObjectID>::Element *orphan_subclass_element = orphan_subclasses.find(p_qualified_name); + if (!orphan_subclass_element) + return Ref<GDScript>(); + ObjectID orphan_subclass = orphan_subclass_element->get(); + Object *obj = ObjectDB::get_instance(orphan_subclass); + orphan_subclasses.erase(orphan_subclass_element); + if (!obj) + return Ref<GDScript>(); + return Ref<GDScript>(Object::cast_to<GDScript>(obj)); +} + /*************** RESOURCE ***************/ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 72389cdddc..4ae52238ce 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -111,6 +111,7 @@ class GDScript : public Script { String source; String path; String name; + String fully_qualified_name; SelfList<GDScript> script_list; GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error); @@ -131,6 +132,8 @@ class GDScript : public Script { bool _update_exports(); + void _save_orphaned_subclasses(); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -354,6 +357,8 @@ class GDScriptLanguage : public ScriptLanguage { bool profiling; uint64_t script_frame_time; + Map<String, ObjectID> orphan_subclasses; + public: int calls; @@ -505,6 +510,9 @@ public: virtual bool handles_global_class_type(const String &p_type) const; virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL, String *r_icon_path = NULL) const; + void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass); + Ref<GDScript> get_orphan_subclass(const String &p_qualified_name); + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7b3175d9cd..fba1b992ec 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2123,14 +2123,21 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C StringName name = p_class->subclasses[i]->name; Ref<GDScript> subclass; + String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { - subclass.instance(); + Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); + if (orphan_subclass.is_valid()) { + subclass = orphan_subclass; + } else { + subclass.instance(); + } } subclass->_owner = p_script; + subclass->fully_qualified_name = fully_qualified_name; p_script->subclasses.insert(name, subclass); _make_scripts(subclass.ptr(), p_class->subclasses[i], false); @@ -2149,6 +2156,9 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); + // The best fully qualified name for a base level script is its file path + p_script->fully_qualified_name = p_script->path; + // Create scripts for subclasses beforehand so they can be referenced _make_scripts(p_script, static_cast<const GDScriptParser::ClassNode *>(root), p_keep_state); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index e2f2b7fc3b..65c61cb57c 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -335,7 +335,9 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> * ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) { - ERR_FAIL_COND_V(_debug_parse_err_line >= 0, NULL); + if (_debug_parse_err_line >= 0) + return NULL; + ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, NULL); int l = _debug_call_stack_pos - p_level - 1; @@ -555,7 +557,7 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_isarg = tr } if (p_info.type == Variant::NIL) { if (p_isarg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { - return "var"; + return "Variant"; } else { return "void"; } @@ -1736,14 +1738,12 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { if (i > 0) { arghint += ", "; - } else { - arghint += " "; } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } - arghint += _get_visual_datatype(E->get(), true) + " " + E->get().name; + arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true); if (i - def_args >= 0) { arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); @@ -1759,8 +1759,6 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { if (p_info.flags & METHOD_FLAG_VARARG) { if (p_info.arguments.size() > 0) { arghint += ", "; - } else { - arghint += " "; } if (p_arg_idx >= p_info.arguments.size()) { arghint += String::chr(0xFFFF); @@ -1770,9 +1768,6 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx) { arghint += String::chr(0xFFFF); } } - if (p_info.arguments.size() > 0 || (p_info.flags & METHOD_FLAG_VARARG)) { - arghint += " "; - } arghint += ")"; @@ -1787,14 +1782,12 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio for (int i = 0; i < p_function->arguments.size(); i++) { if (i > 0) { arghint += ", "; - } else { - arghint += " "; } if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } - arghint += p_function->argument_types[i].to_string() + " " + p_function->arguments[i].operator String(); + arghint += p_function->arguments[i].operator String() + ": " + p_function->argument_types[i].to_string(); if (i - def_args >= 0) { String def_val = "<unknown>"; @@ -1818,9 +1811,6 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio } } - if (p_function->arguments.size() > 0) { - arghint += " "; - } arghint += ")"; return arghint; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index eef39da8b5..524be480d7 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -67,23 +67,23 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta case ADDR_TYPE_CLASS_CONSTANT: { //todo change to index! - GDScript *o = p_script; + GDScript *s = p_script; #ifdef DEBUG_ENABLED ERR_FAIL_INDEX_V(address, _global_names_count, NULL); #endif const StringName *sn = &_global_names_ptr[address]; - while (o) { - GDScript *s = o; - while (s) { + while (s) { + GDScript *o = s; + while (o) { - Map<StringName, Variant>::Element *E = s->constants.find(*sn); + Map<StringName, Variant>::Element *E = o->constants.find(*sn); if (E) { return &E->get(); } - s = s->_base; + o = o->_owner; } - o = o->_owner; + s = s->_base; } ERR_FAIL_V_MSG(NULL, "GDScriptCompiler bug."); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 236ca720a2..ad95ebc543 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -76,14 +76,17 @@ struct GDScriptDataType { if (p_variant.get_type() != Variant::OBJECT) { return false; } + Object *obj = p_variant.operator Object *(); - if (obj) { - if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { - // Try with underscore prefix - StringName underscore_native_type = "_" + native_type; - if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) { - return false; - } + if (!obj || !ObjectDB::instance_validate(obj)) { + return false; + } + + if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { + // Try with underscore prefix + StringName underscore_native_type = "_" + native_type; + if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) { + return false; } } return true; @@ -96,7 +99,12 @@ struct GDScriptDataType { if (p_variant.get_type() != Variant::OBJECT) { return false; } + Object *obj = p_variant.operator Object *(); + if (!obj || !ObjectDB::instance_validate(obj)) { + return false; + } + Ref<Script> base = obj && obj->get_script_instance() ? obj->get_script_instance()->get_script() : NULL; bool valid = false; while (base.is_valid()) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index dcf5d35e36..5c2e7137bf 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -4741,10 +4741,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.line = tokenizer->get_token_line(); member.usages = 0; member.rpc_mode = rpc_mode; -#ifdef TOOLS_ENABLED - Variant::CallError ce; - member.default_value = Variant::construct(member._export.type, NULL, 0, ce); -#endif if (current_class->constant_expressions.has(member.identifier)) { _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " + @@ -4797,6 +4793,32 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } + if (autoexport && member.data_type.has_type) { + if (member.data_type.kind == DataType::BUILTIN) { + member._export.type = member.data_type.builtin_type; + } else if (member.data_type.kind == DataType::NATIVE) { + if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { + member._export.type = Variant::OBJECT; + member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; + member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + member._export.hint_string = member.data_type.native_type; + member._export.class_name = member.data_type.native_type; + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + + } else { + _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); + return; + } + } + +#ifdef TOOLS_ENABLED + Variant::CallError ce; + member.default_value = Variant::construct(member._export.type, NULL, 0, ce); +#endif + if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) { #ifdef DEBUG_ENABLED @@ -4930,27 +4952,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member.initial_assignment = op; } - if (autoexport && member.data_type.has_type) { - if (member.data_type.kind == DataType::BUILTIN) { - member._export.type = member.data_type.builtin_type; - } else if (member.data_type.kind == DataType::NATIVE) { - if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) { - member._export.type = Variant::OBJECT; - member._export.hint = PROPERTY_HINT_RESOURCE_TYPE; - member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; - member._export.hint_string = member.data_type.native_type; - member._export.class_name = member.data_type.native_type; - } else { - _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); - return; - } - - } else { - _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line); - return; - } - } - if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) { tokenizer->advance(); @@ -7046,12 +7047,10 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat return_type = _type_from_property(mi.return_val, false); -#ifdef DEBUG_ENABLED // Check all arguments beforehand to solve warnings for (int i = 1; i < p_call->arguments.size(); i++) { _reduce_node_type(p_call->arguments[i]); } -#endif // DEBUG_ENABLED // Check arguments @@ -7079,12 +7078,10 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat ERR_FAIL_V(DataType()); } -#ifdef DEBUG_ENABLED // Check all arguments beforehand to solve warnings for (int i = arg_id + 1; i < p_call->arguments.size(); i++) { _reduce_node_type(p_call->arguments[i]); } -#endif // DEBUG_ENABLED IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]); callee_name = func_id->name; @@ -8210,12 +8207,10 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { _add_warning(GDScriptWarning::FUNCTION_MAY_YIELD, op->line, _find_function_name(static_cast<OperatorNode *>(op->arguments[1]))); } - bool type_match = check_types; #endif // DEBUG_ENABLED + bool type_match = lh_type.has_type && rh_type.has_type; if (check_types && !_is_type_compatible(lh_type, rh_type)) { -#ifdef DEBUG_ENABLED type_match = false; -#endif // DEBUG_ENABLED // Try supertype test if (_is_type_compatible(rh_type, lh_type)) { @@ -8247,9 +8242,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { op->arguments.write[1] = convert_call; -#ifdef DEBUG_ENABLED type_match = true; // Since we are converting, the type is matching -#endif // DEBUG_ENABLED } #ifdef DEBUG_ENABLED if (lh_type.builtin_type == Variant::INT && rh_type.builtin_type == Variant::REAL) { @@ -8262,10 +8255,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) { if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) { _mark_line_as_unsafe(op->line); } - op->datatype.has_type = type_match; -#else - op->datatype.has_type = false; #endif // DEBUG_ENABLED + op->datatype.has_type = type_match; } break; case OperatorNode::OP_CALL: case OperatorNode::OP_PARENT_CALL: { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index c8ff471401..701809e755 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -112,9 +112,10 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES); tokenizer.set_code(p_code); while (true) { - if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) { + GDScriptTokenizerText::Token token = tokenizer.get_token(); + if (token == GDScriptTokenizer::TK_EOF || token == GDScriptTokenizer::TK_ERROR) { break; - } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) { + } else if (token == GDScriptTokenizer::TK_CONSTANT) { const Variant &const_val = tokenizer.get_token_constant(); if (const_val.get_type() == Variant::STRING) { String path = const_val; @@ -538,16 +539,16 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi } while (c >= 0) { - const CharType &charactor = line[c]; - if (charactor == ')') { + const CharType &character = line[c]; + if (character == ')') { ++bracket_stack; - } else if (charactor == '(') { + } else if (character == '(') { --bracket_stack; if (bracket_stack < 0) { found = true; } } - if (bracket_stack <= 0 && charactor == ',') { + if (bracket_stack <= 0 && character == ',') { ++index; } --c; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index cff7653d3a..7133c6b4be 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -156,7 +156,7 @@ void GDScriptLanguageProtocol::poll() { server->poll(); } -Error GDScriptLanguageProtocol::start(int p_port) { +Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) { if (server == NULL) { server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer")); ERR_FAIL_COND_V(!server, FAILED); @@ -165,6 +165,7 @@ Error GDScriptLanguageProtocol::start(int p_port) { server->connect("client_connected", this, "on_client_connected"); server->connect("client_disconnected", this, "on_client_disconnected"); } + server->set_bind_ip(p_bind_ip); return server->listen(p_port); } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index e45db274e9..52c680ab19 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -77,7 +77,7 @@ public: _FORCE_INLINE_ bool is_initialized() const { return _initialized; } void poll(); - Error start(int p_port); + Error start(int p_port, const IP_Address &p_bind_ip); void stop(); void notify_all_clients(const String &p_method, const Variant &p_params = Variant()); diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 19bb3ed1ee..7170c63058 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -84,7 +84,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { port = (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port) == OK) { + if (protocol.start(port, IP_Address("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { ERR_FAIL_COND(thread != NULL); diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 8126c9338f..3d40220869 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -122,7 +122,7 @@ bool GridMap::_get(const StringName &p_name, Variant &r_ret) const { Array ret; ret.resize(baked_meshes.size()); for (int i = 0; i < baked_meshes.size(); i++) { - ret.push_back(baked_meshes[i].mesh); + ret[i] = baked_meshes[i].mesh; } r_ret = ret; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index d82e78d080..0f6b8357b8 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -159,6 +159,19 @@ void CSharpLanguage::finish() { // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements. script_bindings.clear(); +#ifdef DEBUG_ENABLED + for (List<ObjectID>::Element *E = unsafely_referenced_objects.front(); E; E = E->next()) { + const ObjectID &id = E->get(); + Object *obj = ObjectDB::get_instance(id); + + if (obj) { + ERR_PRINTS("Leaked unsafe reference to object: " + obj->get_class() + ":" + itos(id)); + } else { + ERR_PRINTS("Leaked unsafe reference to deleted object: " + itos(id)); + } + } +#endif + finalizing = false; } @@ -546,6 +559,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + GD_MONO_SCOPE_THREAD_ATTACH; if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) return Vector<StackInfo>(); @@ -570,6 +584,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + GD_MONO_SCOPE_THREAD_ATTACH; MonoException *exc = NULL; @@ -615,6 +630,23 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec } #endif +void CSharpLanguage::post_unsafe_reference(Object *p_obj) { +#ifdef DEBUG_ENABLED + ObjectID id = p_obj->get_instance_id(); + ERR_FAIL_COND_MSG(unsafely_referenced_objects.find(id), "Multiple unsafe references for object: " + p_obj->get_class() + ":" + itos(id)); + unsafely_referenced_objects.push_back(id); +#endif +} + +void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { +#ifdef DEBUG_ENABLED + ObjectID id = p_obj->get_instance_id(); + List<ObjectID>::Element *elem = unsafely_referenced_objects.find(id); + ERR_FAIL_NULL(elem); + unsafely_referenced_objects.erase(elem); +#endif +} + void CSharpLanguage::frame() { if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { @@ -659,6 +691,7 @@ void CSharpLanguage::reload_all_scripts() { #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { + GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(false); } #endif @@ -676,6 +709,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { + GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(p_soft_reload); } #endif @@ -1286,6 +1320,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) ref->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); } return true; @@ -1325,6 +1360,8 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (finalizing) return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there + GD_MONO_ASSERT_THREAD_ATTACHED; + { SCOPED_MUTEX_LOCK(language_bind_mutex); @@ -1351,6 +1388,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { #ifdef DEBUG_ENABLED CRASH_COND(!ref_owner); + CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif void *data = p_object->get_script_instance_binding(get_language_index()); @@ -1363,6 +1401,8 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { return; if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1384,6 +1424,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { #ifdef DEBUG_ENABLED CRASH_COND(!ref_owner); + CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif void *data = p_object->get_script_instance_binding(get_language_index()); @@ -1398,6 +1439,8 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { return refcount == 0; if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1449,6 +1492,8 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!script.is_valid(), false); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL_V(mono_object, false); @@ -1501,6 +1546,8 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { ERR_FAIL_COND_V(!script.is_valid(), false); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL_V(mono_object, false); @@ -1594,6 +1641,8 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { ERR_FAIL_COND(!script.is_valid()); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); @@ -1638,6 +1687,8 @@ bool CSharpInstance::has_method(const StringName &p_method) const { if (!script.is_valid()) return false; + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1653,6 +1704,11 @@ bool CSharpInstance::has_method(const StringName &p_method) const { Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + if (!script.is_valid()) + ERR_FAIL_V(Variant()); + + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); if (!mono_object) { @@ -1660,9 +1716,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, ERR_FAIL_V(Variant()); } - if (!script.is_valid()) - ERR_FAIL_V(Variant()); - GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1690,6 +1743,8 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { + GD_MONO_SCOPE_THREAD_ATTACH; + if (script.is_valid()) { MonoObject *mono_object = get_mono_object(); @@ -1701,6 +1756,8 @@ void CSharpInstance::call_multilevel(const StringName &p_method, const Variant * void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) { + GD_MONO_ASSERT_THREAD_ATTACHED; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1736,9 +1793,12 @@ bool CSharpInstance::_reference_owner_unsafe() { // See: _unreference_owner_unsafe() // May not me referenced yet, so we must use init_ref() instead of reference() - bool success = Object::cast_to<Reference>(owner)->init_ref(); - unsafe_referenced = success; - return success; + if (static_cast<Reference *>(owner)->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(owner); + unsafe_referenced = true; + } + + return unsafe_referenced; } bool CSharpInstance::_unreference_owner_unsafe() { @@ -1759,6 +1819,7 @@ bool CSharpInstance::_unreference_owner_unsafe() { // See: _reference_owner_unsafe() // Destroying the owner here means self destructing, so we defer the owner destruction to the caller. + CSharpLanguage::get_singleton()->pre_unsafe_unreference(owner); return static_cast<Reference *>(owner)->unreference(); } @@ -1859,6 +1920,8 @@ void CSharpInstance::refcount_incremented() { Reference *ref_owner = Object::cast_to<Reference>(owner); if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1882,6 +1945,8 @@ bool CSharpInstance::refcount_decremented() { int refcount = ref_owner->reference_get_count(); if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1922,6 +1987,8 @@ MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(IMonoClassMember *p MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1938,6 +2005,8 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1959,6 +2028,8 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab void CSharpInstance::notification(int p_notification) { + GD_MONO_SCOPE_THREAD_ATTACH; + if (p_notification == Object::NOTIFICATION_PREDELETE) { // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed @@ -1996,6 +2067,8 @@ void CSharpInstance::notification(int p_notification) { void CSharpInstance::_call_notification(int p_notification) { + GD_MONO_ASSERT_THREAD_ATTACHED; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); @@ -2020,6 +2093,8 @@ void CSharpInstance::_call_notification(int p_notification) { } String CSharpInstance::to_string(bool *r_valid) { + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); if (mono_object == NULL) { @@ -2068,6 +2143,8 @@ CSharpInstance::CSharpInstance() : CSharpInstance::~CSharpInstance() { + GD_MONO_SCOPE_THREAD_ATTACH; + destructing_script_instance = true; if (gchandle.is_valid()) { @@ -2099,6 +2176,17 @@ CSharpInstance::~CSharpInstance() { // Transfer ownership to an "instance binding" + Reference *ref_owner = static_cast<Reference *>(owner); + + // We will unreference the owner before referencing it again, so we need to keep it alive + Ref<Reference> scope_keep_owner_alive(ref_owner); + (void)scope_keep_owner_alive; + + // Unreference the owner here, before the new "instance binding" references it. + // Otherwise, the unsafe reference debug checks will incorrectly detect a bug. + bool die = _unreference_owner_unsafe(); + CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die + void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); CRASH_COND(data == NULL); @@ -2114,8 +2202,10 @@ CSharpInstance::~CSharpInstance() { } } - bool die = _unreference_owner_unsafe(); - CRASH_COND(die == true); // The "instance binding" should be holding a reference +#ifdef DEBUG_ENABLED + // The "instance binding" holds a reference so the refcount should be at least 2 before `scope_keep_owner_alive` goes out of scope + CRASH_COND(ref_owner->reference_get_count() <= 1); +#endif } if (script.is_valid() && owner) { @@ -2158,6 +2248,8 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List void CSharpScript::_update_member_info_no_exports() { if (exports_invalidated) { + GD_MONO_ASSERT_THREAD_ATTACHED; + exports_invalidated = false; member_info.clear(); @@ -2216,6 +2308,8 @@ bool CSharpScript::_update_exports() { bool changed = false; if (exports_invalidated) { + GD_MONO_SCOPE_THREAD_ATTACH; + exports_invalidated = false; changed = true; @@ -2243,7 +2337,11 @@ bool CSharpScript::_update_exports() { MonoException *ctor_exc = NULL; ctor->invoke(tmp_object, NULL, &ctor_exc); + Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + if (ctor_exc) { + // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + MonoGCHandle::free_handle(tmp_pinned_gchandle); tmp_object = NULL; @@ -2322,6 +2420,15 @@ bool CSharpScript::_update_exports() { MonoGCHandle::free_handle(tmp_pinned_gchandle); tmp_object = NULL; + + if (tmp_native && !Object::cast_to<Reference>(tmp_native)) { + Node *node = Object::cast_to<Node>(tmp_native); + if (node && node->is_inside_tree()) { + ERR_PRINTS("Temporary instance was added to the scene tree."); + } else { + memdelete(tmp_native); + } + } } placeholder_fallback_enabled = false; @@ -2352,6 +2459,8 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati // make sure this classes signals are empty when loading for the first time _signals.clear(); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = p_class; while (top && top != p_native_class) { const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); @@ -2372,6 +2481,8 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati } bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { + GD_MONO_ASSERT_THREAD_ATTACHED; + if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { MonoType *raw_type = p_delegate->get_mono_type(); @@ -2413,6 +2524,8 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve */ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { + GD_MONO_ASSERT_THREAD_ATTACHED; + // Goddammit, C++. All I wanted was some nested functions. #define MEMBER_FULL_QUALIFIED_NAME(m_member) \ (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) @@ -2491,6 +2604,8 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { + GD_MONO_ASSERT_THREAD_ATTACHED; + if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) { r_hint = PROPERTY_HINT_ENUM; @@ -2600,6 +2715,8 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i return Variant(); } + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script_class; while (top && top != native) { @@ -2792,6 +2909,8 @@ StringName CSharpScript::get_instance_base_type() const { CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) { + GD_MONO_ASSERT_THREAD_ATTACHED; + /* STEP 1, CREATE */ // Search the constructor first, to fail with an error if it's not found before allocating anything else. @@ -2886,12 +3005,14 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call } r_error.error = Variant::CallError::CALL_OK; - REF ref; ERR_FAIL_NULL_V(native, Variant()); + GD_MONO_SCOPE_THREAD_ATTACH; + Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + REF ref; Reference *r = Object::cast_to<Reference>(owner); if (r) { ref = REF(r); @@ -2929,6 +3050,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { } } + GD_MONO_SCOPE_THREAD_ATTACH; + Variant::CallError unchecked_error; return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); } @@ -2976,6 +3099,8 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { if (!script_class) return; + GD_MONO_SCOPE_THREAD_ATTACH; + // TODO: Filter out things unsuitable for explicit calls, like constructors. const Vector<GDMonoMethod *> &methods = script_class->get_all_methods(); for (int i = 0; i < methods.size(); ++i) { @@ -2988,6 +3113,8 @@ bool CSharpScript::has_method(const StringName &p_method) const { if (!script_class) return false; + GD_MONO_SCOPE_THREAD_ATTACH; + return script_class->has_fetched_method_unknown_params(p_method); } @@ -2996,6 +3123,8 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { if (!script_class) return MethodInfo(); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script_class; while (top && top != native) { @@ -3020,6 +3149,8 @@ Error CSharpScript::reload(bool p_keep_state) { ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); if (project_assembly) { @@ -3247,39 +3378,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p script->set_path(p_original_path); -#ifndef TOOLS_ENABLED - -#ifdef DEBUG_ENABLED - // User is responsible for thread attach/detach - CRASH_COND_MSG(mono_domain_get() == NULL, "Thread is not attached."); -#endif - -#endif - -#ifdef TOOLS_ENABLED - MonoDomain *domain = mono_domain_get(); - if (Engine::get_singleton()->is_editor_hint() && domain == NULL) { - - CRASH_COND(Thread::get_caller_id() == Thread::get_main_id()); - - // Thread is not attached, but we will make an exception in this case - // because this may be called by one of the editor's worker threads. - // Attach this thread temporarily to reload the script. - - if (domain) { - MonoThread *mono_thread = mono_thread_attach(domain); - CRASH_COND(mono_thread == NULL); - script->reload(); - mono_thread_detach(mono_thread); - } - - } else { // just reload it normally -#endif - script->reload(); - -#ifdef TOOLS_ENABLED - } -#endif + script->reload(); if (r_error) *r_error = OK; diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 027f429125..30f56e00bd 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -307,6 +307,11 @@ class CSharpLanguage : public ScriptLanguage { Map<Object *, CSharpScriptBinding> script_bindings; +#ifdef DEBUG_ENABLED + // List of unsafely referenced objects + List<ObjectID> unsafely_referenced_objects; +#endif + struct StringNameCache { StringName _signal_callback; @@ -458,6 +463,9 @@ public: Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); #endif + void post_unsafe_reference(Object *p_obj); + void pre_unsafe_unreference(Object *p_obj); + CSharpLanguage(); ~CSharpLanguage(); }; diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 04a489f1f9..02246b2f2f 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -108,6 +108,7 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea // Unsafe refcount decrement. The managed instance also counts as a reference. // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + CSharpLanguage::get_singleton()->pre_unsafe_unreference(ref); if (ref->unreference()) { memdelete(ref); } else { diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 9d7ac5c5ea..6cf5377e2c 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -129,7 +129,7 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d (void)user_data; // UNUSED - String name = mono_assembly_name_get_name(aname); + String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); if (no_search) @@ -176,7 +176,7 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, vo no_search = true; in_preload = true; - String name = mono_assembly_name_get_name(aname); + String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll"); GDMonoAssembly *res = NULL; @@ -276,7 +276,7 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const } void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) { - String name = mono_assembly_name_get_name(mono_assembly_get_name(assembly)); + String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); MonoImage *image = mono_assembly_get_image(assembly); diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 8669182c4e..75aa77c7b0 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -83,7 +83,9 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) // May not me referenced yet, so we must use init_ref() instead of reference() - ref->init_ref(); + if (ref->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); + } } // The object was just created, no script instance binding should have been attached diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 3c8aa0c727..4e7f590a69 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -115,6 +115,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) ref->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); } return mono_object; @@ -124,10 +125,12 @@ void set_main_thread(MonoThread *p_thread) { mono_thread_set_main(p_thread); } -void attach_current_thread() { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_attach(mono_get_root_domain()); - ERR_FAIL_NULL(mono_thread); +MonoThread *attach_current_thread() { + ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), NULL); + MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain(); + MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain()); + ERR_FAIL_NULL_V(mono_thread, NULL); + return mono_thread; } void detach_current_thread() { @@ -137,10 +140,20 @@ void detach_current_thread() { mono_thread_detach(mono_thread); } +void detach_current_thread(MonoThread *p_mono_thread) { + ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); + ERR_FAIL_NULL(p_mono_thread); + mono_thread_detach(p_mono_thread); +} + MonoThread *get_current_thread() { return mono_thread_current(); } +bool is_thread_attached() { + return mono_domain_get() != NULL; +} + void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) { GDMonoMethod *ctor = p_class->get_method(".ctor", 0); ERR_FAIL_NULL(ctor); @@ -616,4 +629,19 @@ GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, Mon } // namespace Marshal +ScopeThreadAttach::ScopeThreadAttach() : + mono_thread(NULL) { + if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { + mono_thread = GDMonoUtils::attach_current_thread(); + } +} + +ScopeThreadAttach::~ScopeThreadAttach() { + if (unlikely(mono_thread)) { + GDMonoUtils::detach_current_thread(mono_thread); + } +} + +// namespace Marshal + } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index d9eb912cd7..db9f99bfdc 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -83,9 +83,11 @@ _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) MonoObject *unmanaged_get_managed(Object *unmanaged); void set_main_thread(MonoThread *p_thread); -void attach_current_thread(); +MonoThread *attach_current_thread(); void detach_current_thread(); +void detach_current_thread(MonoThread *p_mono_thread); MonoThread *get_current_thread(); +bool is_thread_attached(); _FORCE_INLINE_ bool is_main_thread() { return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); @@ -142,6 +144,14 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool & void dispose(MonoObject *p_mono_object, MonoException **r_exc); +struct ScopeThreadAttach { + ScopeThreadAttach(); + ~ScopeThreadAttach(); + +private: + MonoThread *mono_thread; +}; + } // namespace GDMonoUtils #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) @@ -153,4 +163,15 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc); #define GD_MONO_END_RUNTIME_INVOKE \ _runtime_invoke_count_ref -= 1; +#define GD_MONO_SCOPE_THREAD_ATTACH \ + GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \ + (void)__gdmono__scope__thread__attach__; + +#ifdef DEBUG_ENABLED +#define GD_MONO_ASSERT_THREAD_ATTACHED \ + { CRASH_COND(!GDMonoUtils::is_thread_attached()); } +#else +#define GD_MONO_ASSERT_THREAD_ATTACHED +#endif + #endif // GD_MONOUTILS_H diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_compress_squish.cpp index ef4ea93e57..58b8115dfc 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_compress_squish.cpp @@ -73,7 +73,6 @@ void image_decompress_squish(Image *p_image) { p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } -#ifdef TOOLS_ENABLED void image_compress_squish(Image *p_image, float p_lossy_quality, Image::CompressSource p_source) { if (p_image->get_format() >= Image::FORMAT_DXT1) @@ -203,4 +202,3 @@ void image_compress_squish(Image *p_image, float p_lossy_quality, Image::Compres p_image->create(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); } } -#endif diff --git a/modules/squish/image_compress_squish.h b/modules/squish/image_compress_squish.h index 2666815e47..b5a209ceb9 100644 --- a/modules/squish/image_compress_squish.h +++ b/modules/squish/image_compress_squish.h @@ -33,9 +33,7 @@ #include "core/image.h" -#ifdef TOOLS_ENABLED void image_compress_squish(Image *p_image, float p_lossy_quality, Image::CompressSource p_source); -#endif void image_decompress_squish(Image *p_image); #endif // IMAGE_COMPRESS_SQUISH_H diff --git a/modules/squish/register_types.cpp b/modules/squish/register_types.cpp index 1cd551defb..2a0cf82b56 100644 --- a/modules/squish/register_types.cpp +++ b/modules/squish/register_types.cpp @@ -33,9 +33,7 @@ void register_squish_types() { -#ifdef TOOLS_ENABLED Image::set_compress_bc_func(image_compress_squish); -#endif Image::_image_decompress_bc = image_decompress_squish; } diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 4bfd4f48ce..ec20698ae8 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -575,7 +575,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { Button *btn = memnew(Button); btn->set_text(TTR("Add Input Port")); hbnc->add_child(btn); - btn->connect("pressed", this, "_add_input_port", varray(E->get())); + btn->connect("pressed", this, "_add_input_port", varray(E->get()), CONNECT_DEFERRED); } if (nd_list->is_output_port_editable()) { if (nd_list->is_input_port_editable()) @@ -584,7 +584,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { Button *btn = memnew(Button); btn->set_text(TTR("Add Output Port")); hbnc->add_child(btn); - btn->connect("pressed", this, "_add_output_port", varray(E->get())); + btn->connect("pressed", this, "_add_output_port", varray(E->get()), CONNECT_DEFERRED); } gnode->add_child(hbnc); } else if (Object::cast_to<VisualScriptExpression>(node.ptr())) { diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 705e3485f5..52a98d8c32 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -27,7 +27,8 @@ Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). - You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request (not supported in HTML5 platform). + You can optionally pass a list of [code]custom_headers[/code] to be added to the handshake HTTP request. + [b]Note:[/b] Specifying [code]custom_headers[/code] is not supported in HTML5 exports due to browsers restrictions. </description> </method> <method name="disconnect_from_host"> @@ -59,6 +60,7 @@ <members> <member name="trusted_ssl_certificate" type="X509Certificate" setter="set_trusted_ssl_certificate" getter="get_trusted_ssl_certificate"> If specified, this [X509Certificate] will be the only one accepted when connecting to an SSL host. Any other certificate provided by the server will be regarded as invalid. + [b]Note:[/b] Specifying a custom [code]trusted_ssl_certificate[/code] is not supported in HTML5 exports due to browsers restrictions. </member> <member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled"> If [code]true[/code], SSL certificate verification is enabled. diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 86f2dae64f..2074a10fa9 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -6,7 +6,7 @@ <description> This class implements a WebSocket server that can also support the high-level multiplayer API. After starting the server ([method listen]), you will need to [method NetworkedMultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). When clients connect, disconnect, or send data, you will receive the appropriate signal. - [b]Note:[/b] This class will not work in HTML5 exports due to browser restrictions. + [b]Note:[/b] Not available in HTML5 exports. </description> <tutorials> </tutorials> @@ -83,6 +83,9 @@ </method> </methods> <members> + <member name="bind_ip" type="String" setter="set_bind_ip" getter="get_bind_ip" default=""*""> + When not set to [code]*[/code] will restrict incoming connections to the specified IP address. Setting [code]bind_ip[/code] to [code]127.0.0.1[/code] will cause the server to listen only to the local host. + </member> <member name="ca_chain" type="X509Certificate" setter="set_ca_chain" getter="get_ca_chain"> When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake. </member> diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index ded1850846..76e88d72b9 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -34,6 +34,7 @@ GDCINULL(WebSocketServer); WebSocketServer::WebSocketServer() { _peer_id = 1; + bind_ip = IP_Address("*"); } WebSocketServer::~WebSocketServer() { @@ -49,6 +50,10 @@ void WebSocketServer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port); ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketServer::disconnect_peer, DEFVAL(1000), DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_bind_ip"), &WebSocketServer::get_bind_ip); + ClassDB::bind_method(D_METHOD("set_bind_ip"), &WebSocketServer::set_bind_ip); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "bind_ip"), "set_bind_ip", "get_bind_ip"); + ClassDB::bind_method(D_METHOD("get_private_key"), &WebSocketServer::get_private_key); ClassDB::bind_method(D_METHOD("set_private_key"), &WebSocketServer::set_private_key); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "private_key", PROPERTY_HINT_RESOURCE_TYPE, "CryptoKey", 0), "set_private_key", "get_private_key"); @@ -67,6 +72,16 @@ void WebSocketServer::_bind_methods() { ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); } +IP_Address WebSocketServer::get_bind_ip() const { + return bind_ip; +} + +void WebSocketServer::set_bind_ip(const IP_Address &p_bind_ip) { + ERR_FAIL_COND(is_listening()); + ERR_FAIL_COND(!p_bind_ip.is_valid() && !p_bind_ip.is_wildcard()); + bind_ip = p_bind_ip; +} + Ref<CryptoKey> WebSocketServer::get_private_key() const { return private_key; } diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h index bfdac11489..3ce4dbe711 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -41,6 +41,8 @@ class WebSocketServer : public WebSocketMultiplayerPeer { GDCLASS(WebSocketServer, WebSocketMultiplayerPeer); GDCICLASS(WebSocketServer); + IP_Address bind_ip; + protected: static void _bind_methods(); @@ -67,6 +69,9 @@ public: void _on_disconnect(int32_t p_peer_id, bool p_was_clean); void _on_close_request(int32_t p_peer_id, int p_code, String p_reason); + IP_Address get_bind_ip() const; + void set_bind_ip(const IP_Address &p_bind_ip); + Ref<CryptoKey> get_private_key() const; void set_private_key(Ref<CryptoKey> p_key); diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index c98c62cce9..c3dd79a89c 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -165,7 +165,7 @@ Error WSLServer::listen(int p_port, const Vector<String> p_protocols, bool gd_mp for (int i = 0; i < p_protocols.size(); i++) { pw[i] = p_protocols[i].strip_edges(); } - _server->listen(p_port); + _server->listen(p_port, bind_ip); return OK; } |