diff options
Diffstat (limited to 'modules')
25 files changed, 624 insertions, 127 deletions
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp index 28b6bb035a..38ca38385c 100644 --- a/modules/enet/enet_multiplayer_peer.cpp +++ b/modules/enet/enet_multiplayer_peer.cpp @@ -33,6 +33,14 @@ #include "core/io/marshalls.h" #include "core/os/os.h" +void ENetMultiplayerPeer::set_transfer_channel(int p_channel) { + transfer_channel = p_channel; +} + +int ENetMultiplayerPeer::get_transfer_channel() const { + return transfer_channel; +} + void ENetMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { transfer_mode = p_mode; } @@ -234,8 +242,8 @@ bool ENetMultiplayerPeer::_poll_client() { } switch (ret) { case ENetConnection::EVENT_CONNECT: { - emit_signal(SNAME("peer_connected"), 1); connection_status = CONNECTION_CONNECTED; + emit_signal(SNAME("peer_connected"), 1); emit_signal(SNAME("connection_succeeded")); return false; } @@ -441,20 +449,23 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size int packet_flags = 0; int channel = SYSCH_RELIABLE; - - switch (transfer_mode) { - case TRANSFER_MODE_UNRELIABLE: { - packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; - channel = SYSCH_UNRELIABLE; - } break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: { - packet_flags = 0; - channel = SYSCH_UNRELIABLE; - } break; - case TRANSFER_MODE_RELIABLE: { - packet_flags = ENET_PACKET_FLAG_RELIABLE; - channel = SYSCH_RELIABLE; - } break; + if (transfer_channel > 0) { + channel = SYSCH_MAX + transfer_channel - 1; + } else { + switch (transfer_mode) { + case TRANSFER_MODE_UNRELIABLE: { + packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; + channel = SYSCH_UNRELIABLE; + } break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: { + packet_flags = 0; + channel = SYSCH_UNRELIABLE; + } break; + case TRANSFER_MODE_RELIABLE: { + packet_flags = ENET_PACKET_FLAG_RELIABLE; + channel = SYSCH_RELIABLE; + } break; + } } ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size + 8, packet_flags); diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h index 703396d0cb..78e280db7c 100644 --- a/modules/enet/enet_multiplayer_peer.h +++ b/modules/enet/enet_multiplayer_peer.h @@ -47,10 +47,10 @@ private: }; enum { - SYSCH_CONFIG, - SYSCH_RELIABLE, - SYSCH_UNRELIABLE, - SYSCH_MAX + SYSCH_CONFIG = 0, + SYSCH_RELIABLE = 1, + SYSCH_UNRELIABLE = 2, + SYSCH_MAX = 3 }; enum Mode { @@ -65,6 +65,7 @@ private: uint32_t unique_id = 0; int target_peer = 0; + int transfer_channel = 0; TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; bool refuse_connections = false; @@ -100,6 +101,9 @@ protected: static void _bind_methods(); public: + virtual void set_transfer_channel(int p_channel) override; + virtual int get_transfer_channel() const override; + virtual void set_transfer_mode(TransferMode p_mode) override; virtual TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer) override; diff --git a/modules/gdnative/include/net/godot_net.h b/modules/gdnative/include/net/godot_net.h index 94e7739ef9..3fb7b9e1cc 100644 --- a/modules/gdnative/include/net/godot_net.h +++ b/modules/gdnative/include/net/godot_net.h @@ -91,6 +91,8 @@ typedef struct { godot_int (*get_max_packet_size)(const void *); /* This is MultiplayerPeer */ + void (*set_transfer_channel)(void *, godot_int); + godot_int (*get_transfer_channel)(void *); void (*set_transfer_mode)(void *, godot_int); godot_int (*get_transfer_mode)(const void *); // 0 = broadcast, 1 = server, <0 = all but abs(value) diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.cpp b/modules/gdnative/net/multiplayer_peer_gdnative.cpp index 8ceba0f339..9908ed4533 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.cpp +++ b/modules/gdnative/net/multiplayer_peer_gdnative.cpp @@ -62,6 +62,16 @@ int MultiplayerPeerGDNative::get_available_packet_count() const { } /* MultiplayerPeer */ +void MultiplayerPeerGDNative::set_transfer_channel(int p_channel) { + ERR_FAIL_COND(interface == nullptr); + return interface->set_transfer_channel(interface->data, p_channel); +} + +int MultiplayerPeerGDNative::get_transfer_channel() const { + ERR_FAIL_COND_V(interface == nullptr, 0); + return interface->get_transfer_channel(interface->data); +} + void MultiplayerPeerGDNative::set_transfer_mode(TransferMode p_mode) { ERR_FAIL_COND(interface == nullptr); interface->set_transfer_mode(interface->data, (godot_int)p_mode); @@ -113,6 +123,7 @@ MultiplayerPeer::ConnectionStatus MultiplayerPeerGDNative::get_connection_status } void MultiplayerPeerGDNative::_bind_methods() { + ADD_PROPERTY_DEFAULT("transfer_channel", 0); ADD_PROPERTY_DEFAULT("transfer_mode", TRANSFER_MODE_UNRELIABLE); ADD_PROPERTY_DEFAULT("refuse_new_connections", true); } diff --git a/modules/gdnative/net/multiplayer_peer_gdnative.h b/modules/gdnative/net/multiplayer_peer_gdnative.h index 7c10ab77f7..ab084faae6 100644 --- a/modules/gdnative/net/multiplayer_peer_gdnative.h +++ b/modules/gdnative/net/multiplayer_peer_gdnative.h @@ -56,6 +56,8 @@ public: virtual int get_available_packet_count() const override; /* Specific to MultiplayerPeer */ + virtual void set_transfer_channel(int p_channel) override; + virtual int get_transfer_channel() const override; virtual void set_transfer_mode(TransferMode p_mode) override; virtual TransferMode get_transfer_mode() const override; virtual void set_target_peer(int p_peer_id) override; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 2a93bb620b..2e570d5a5b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2795,6 +2795,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co if (base_type.class_type->has_member(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); + r_result.class_path = base_type.script_path; return OK; } base_type = base_type.class_type->base_type; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 466ddb4b10..a500dfd51a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -168,7 +168,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>, 4, true); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>, 4, true); // TODO: Warning annotations. } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 0d1f98778e..b6c48468f5 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -128,13 +128,13 @@ Error GDScriptLanguageProtocol::on_client_connected() { peer->connection = tcp_peer; clients.set(next_client_id, peer); next_client_id++; - EditorNode::get_log()->add_message("Connection Taken", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Connection Taken", EditorLog::MSG_TYPE_EDITOR); return OK; } void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) { clients.erase(p_client_id); - EditorNode::get_log()->add_message("Disconnected", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR); } String GDScriptLanguageProtocol::process_message(const String &p_text) { diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 69ddbe5d1e..c0013ac23a 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -42,10 +42,12 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave); ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); + ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename); ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange); ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -79,6 +81,20 @@ void GDScriptTextDocument::didChange(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didSave(const Variant &p_param) { + lsp::TextDocumentItem doc = load_document_item(p_param); + Dictionary dict = p_param; + String text = dict["text"]; + + sync_script_content(doc.uri, text); + + /*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri); + + Ref<GDScript> script = ResourceLoader::load(path); + script->load_source_code(path); + script->reload(true);*/ +} + lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) { lsp::TextDocumentItem doc; Dictionary params = p_param; @@ -215,6 +231,14 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { return arr; } +Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { + lsp::TextDocumentPositionParams params; + params.load(p_params); + String new_name = p_params["newName"]; + + return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); +} + Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { lsp::CompletionItem item; item.load(p_params); @@ -405,7 +429,11 @@ GDScriptTextDocument::~GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + EditorFileSystem::get_singleton()->update_file(path); + Ref<GDScript> script = ResourceLoader::load(path); + script->load_source_code(path); + script->reload(true); } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index e2987f779c..9021c84a3f 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -45,6 +45,7 @@ protected: void didOpen(const Variant &p_param); void didClose(const Variant &p_param); void didChange(const Variant &p_param); + void didSave(const Variant &p_param); void sync_script_content(const String &p_path, const String &p_content); void show_native_symbol_in_editor(const String &p_symbol_id); @@ -61,6 +62,7 @@ public: Array documentSymbol(const Dictionary &p_params); Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); + Dictionary rename(const Dictionary &p_params); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); Array documentLink(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index e6c819b22f..4fc229a0f8 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -116,6 +116,36 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_ return nullptr; } +const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) { + for (int i = 0; i < p_parent->children.size(); ++i) { + const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i]; + if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) { + return parameter_symbol; + } + } + + return nullptr; +} + +const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) { + const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); + + for (int i = 0; i < class_symbol->children.size(); ++i) { + if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { + const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + + for (int l = 0; l < function_symbol->children.size(); ++l) { + const lsp::DocumentSymbol *local = &function_symbol->children[l]; + if (!local->detail.is_empty() && local->name == p_symbol_identifier) { + return local; + } + } + } + } + + return nullptr; +} + void GDScriptWorkspace::reload_all_workspace_scripts() { List<String> paths; list_script_files("res://", paths); @@ -231,18 +261,13 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } - Vector<DocData::PropertyDoc> properties; - properties.append_array(class_data.properties); - const int theme_prop_start_idx = properties.size(); - properties.append_array(class_data.theme_properties); - for (int i = 0; i < class_data.properties.size(); i++) { const DocData::PropertyDoc &data = class_data.properties[i]; lsp::DocumentSymbol symbol; symbol.name = data.name; symbol.native_class = class_name; symbol.kind = lsp::SymbolKind::Property; - symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name; + symbol.detail = "var " + class_name + "." + data.name; if (data.enumeration.length()) { symbol.detail += ": " + data.enumeration; } else { @@ -252,6 +277,17 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } + for (int i = 0; i < class_data.theme_properties.size(); i++) { + const DocData::ThemeItemDoc &data = class_data.theme_properties[i]; + lsp::DocumentSymbol symbol; + symbol.name = data.name; + symbol.native_class = class_name; + symbol.kind = lsp::SymbolKind::Property; + symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type; + symbol.documentation = data.description; + class_symbol.children.push_back(symbol); + } + Vector<DocData::MethodDoc> methods_signals; methods_signals.append_array(class_data.methods); const int signal_start_idx = methods_signals.size(); @@ -350,6 +386,50 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont return err; } +Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { + Error err; + String path = get_file_path(p_doc_pos.textDocument.uri); + + lsp::WorkspaceEdit edit; + + List<String> paths; + list_script_files("res://", paths); + + const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); + if (reference_symbol) { + String identifier = reference_symbol->name; + + for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { + PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n"); + for (int i = 0; i < content.size(); ++i) { + String line = content[i]; + + int character = line.find(identifier); + while (character > -1) { + lsp::TextDocumentPositionParams params; + + lsp::TextDocumentIdentifier text_doc; + text_doc.uri = get_file_uri(PE->get()); + + params.textDocument = text_doc; + params.position.line = i; + params.position.character = character; + + const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + + if (other_symbol == reference_symbol) { + edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); + } + + character = line.find(identifier, character + 1); + } + } + } + } + + return edit.to_json(); +} + Error GDScriptWorkspace::parse_local_script(const String &p_path) { Error err; String content = FileAccess::get_file_as_string(p_path, &err); @@ -440,8 +520,29 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S if (const ExtendGDScriptParser *parser = get_parse_result(path)) { Node *owner_scene_node = _get_owner_scene_node(path); + + Array stack; + Node *current = nullptr; + stack.push_back(owner_scene_node); + + while (!stack.is_empty()) { + current = stack.pop_back(); + Ref<GDScript> script = current->get_script(); + if (script.is_valid() && script->get_path() == path) { + break; + } + for (int i = 0; i < current->get_child_count(); ++i) { + stack.push_back(current->get_child(i)); + } + } + + Ref<GDScript> script = current->get_script(); + if (!script.is_valid() || script->get_path() != path) { + current = owner_scene_node; + } + String code = parser->get_text_for_completion(p_params.position); - GDScriptLanguage::get_singleton()->complete_code(code, path, owner_scene_node, r_options, forced, call_hint); + GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint); if (owner_scene_node) { memdelete(owner_scene_node); } @@ -478,10 +579,16 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu String target_script_path = path; if (!ret.script.is_null()) { target_script_path = ret.script->get_path(); + } else if (!ret.class_path.is_empty()) { + target_script_path = ret.class_path; } if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + + if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { + symbol = get_parameter_symbol(symbol, symbol_identifier); + } } } else { @@ -493,6 +600,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } } else { symbol = parser->get_member_symbol(symbol_identifier); + + if (!symbol) { + symbol = get_local_symbol(parser, symbol_identifier); + } } } } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 8b166a873c..9496677449 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -52,6 +52,8 @@ protected: const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const; const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; + const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier); + const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier); void reload_all_workspace_scripts(); @@ -90,6 +92,7 @@ public: Dictionary generate_script_api(const String &p_path); Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); void did_delete_files(const Dictionary &p_params); + Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 0138f132ad..9ac6c6bd4e 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -255,6 +255,62 @@ struct TextEdit { }; /** + * The edits to be applied. + */ +struct WorkspaceEdit { + /** + * Holds changes to existing resources. + */ + Map<String, Vector<TextEdit>> changes; + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + + Dictionary out_changes; + for (Map<String, Vector<TextEdit>>::Element *E = changes.front(); E; E = E->next()) { + Array edits; + for (int i = 0; i < E->get().size(); ++i) { + Dictionary text_edit; + text_edit["range"] = E->get()[i].range.to_json(); + text_edit["newText"] = E->get()[i].newText; + edits.push_back(text_edit); + } + out_changes[E->key()] = edits; + } + dict["changes"] = out_changes; + + return dict; + } + + _FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { + if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { + Vector<TextEdit> edit_list = E->value(); + for (int i = 0; i < edit_list.size(); ++i) { + TextEdit edit = edit_list[i]; + if (edit.range.start.character == start_character) { + return; + } + } + } + + TextEdit new_edit; + new_edit.newText = new_text; + new_edit.range.start.line = line; + new_edit.range.start.character = start_character; + new_edit.range.end.line = line; + new_edit.range.end.character = end_character; + + if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { + E->value().push_back(new_edit); + } else { + Vector<TextEdit> edit_list; + edit_list.push_back(new_edit); + changes.insert(uri, edit_list); + } + } +}; + +/** * Represents a reference to a command. * Provides a title which will be used to represent a command in the UI. * Commands are identified by a string identifier. @@ -486,7 +542,7 @@ struct TextDocumentSyncOptions { * If present save notifications are sent to the server. If omitted the notification should not be * sent. */ - bool save = false; + SaveOptions save; Dictionary to_json() { Dictionary dict; @@ -494,7 +550,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save; + dict["save"] = save.to_json(); return dict; } }; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 15a5807370..520262c0eb 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -875,6 +875,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // As scripts are going to be reloaded, must proceed without locking here for (Ref<CSharpScript> &script : scripts) { + // If someone removes a script from a node, deletes the script, builds, adds a script to the + // same node, then builds again, the script might have no path and also no script_class. In + // that case, we can't (and don't need to) reload it. + if (script->get_path().is_empty() && !script->script_class) { + continue; + } + to_reload.push_back(script); if (script->get_path().is_empty()) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index ed69c2b833..897f1b2822 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -11,6 +11,7 @@ namespace GodotTools.Build { public BuildOutputView BuildOutputView { get; private set; } + private MenuButton buildMenuBtn; private Button errorsBtn; private Button warningsBtn; private Button viewLogBtn; @@ -131,7 +132,7 @@ namespace GodotTools.Build var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill}; AddChild(toolBarHBox); - var buildMenuBtn = new MenuButton {Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons")}; + buildMenuBtn = new MenuButton {Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons")}; toolBarHBox.AddChild(buildMenuBtn); var buildMenu = buildMenuBtn.GetPopup(); @@ -177,5 +178,16 @@ namespace GodotTools.Build BuildOutputView = new BuildOutputView(); AddChild(BuildOutputView); } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == NotificationThemeChanged) { + buildMenuBtn.Icon = GetThemeIcon("Play", "EditorIcons"); + errorsBtn.Icon = GetThemeIcon("StatusError", "EditorIcons"); + warningsBtn.Icon = GetThemeIcon("NodeWarning", "EditorIcons"); + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 8271b43b48..968f853c2d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -207,7 +207,7 @@ namespace Godot } } - public Quaternion RotationQuaternion() + public Quaternion GetRotationQuaternion() { Basis orthonormalizedBasis = Orthonormalized(); real_t det = orthonormalizedBasis.Determinant(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 213fc181c1..61a34bfc87 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Diagnostics.CodeAnalysis; namespace Godot.Collections { @@ -25,6 +26,11 @@ namespace Godot.Collections } } + /// <summary> + /// Wrapper around Godot's Dictionary class, a dictionary of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine. + /// </summary> public class Dictionary : IDictionary, IDisposable @@ -32,11 +38,19 @@ namespace Godot.Collections DictionarySafeHandle safeHandle; bool disposed = false; + /// <summary> + /// Constructs a new empty <see cref="Dictionary"/>. + /// </summary> public Dictionary() { safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); } + /// <summary> + /// Constructs a new <see cref="Dictionary"/> from the given dictionary's elements. + /// </summary> + /// <param name="dictionary">The dictionary to construct from.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary dictionary) : this() { if (dictionary == null) @@ -64,6 +78,9 @@ namespace Godot.Collections return safeHandle.DangerousGetHandle(); } + /// <summary> + /// Disposes of this <see cref="Dictionary"/>. + /// </summary> public void Dispose() { if (disposed) @@ -78,6 +95,11 @@ namespace Godot.Collections disposed = true; } + /// <summary> + /// Duplicates this <see cref="Dictionary"/>. + /// </summary> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary Duplicate(bool deep = false) { return new Dictionary(godot_icall_Dictionary_Duplicate(GetPtr(), deep)); @@ -85,6 +107,9 @@ namespace Godot.Collections // IDictionary + /// <summary> + /// Gets the collection of keys in this <see cref="Dictionary"/>. + /// </summary> public ICollection Keys { get @@ -94,6 +119,9 @@ namespace Godot.Collections } } + /// <summary> + /// Gets the collection of elements in this <see cref="Dictionary"/>. + /// </summary> public ICollection Values { get @@ -103,47 +131,88 @@ namespace Godot.Collections } } - public bool IsFixedSize => false; + private (Array keys, Array values, int count) GetKeyValuePairs() + { + int count = godot_icall_Dictionary_KeyValuePairs(GetPtr(), out IntPtr keysHandle, out IntPtr valuesHandle); + Array keys = new Array(new ArraySafeHandle(keysHandle)); + Array values = new Array(new ArraySafeHandle(valuesHandle)); + return (keys, values, count); + } + + bool IDictionary.IsFixedSize => false; - public bool IsReadOnly => false; + bool IDictionary.IsReadOnly => false; + /// <summary> + /// Returns the object at the given <paramref name="key"/>. + /// </summary> + /// <value>The object at the given <paramref name="key"/>.</value> public object this[object key] { get => godot_icall_Dictionary_GetValue(GetPtr(), key); set => godot_icall_Dictionary_SetValue(GetPtr(), key, value); } + /// <summary> + /// Adds an object <paramref name="value"/> at key <paramref name="key"/> + /// to this <see cref="Dictionary"/>. + /// </summary> + /// <param name="key">The key at which to add the object.</param> + /// <param name="value">The object to add.</param> public void Add(object key, object value) => godot_icall_Dictionary_Add(GetPtr(), key, value); + /// <summary> + /// Erases all items from this <see cref="Dictionary"/>. + /// </summary> public void Clear() => godot_icall_Dictionary_Clear(GetPtr()); + /// <summary> + /// Checks if this <see cref="Dictionary"/> contains the given key. + /// </summary> + /// <param name="key">The key to look for.</param> + /// <returns>Whether or not this dictionary contains the given key.</returns> public bool Contains(object key) => godot_icall_Dictionary_ContainsKey(GetPtr(), key); + /// <summary> + /// Gets an enumerator for this <see cref="Dictionary"/>. + /// </summary> + /// <returns>An enumerator.</returns> public IDictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this); + /// <summary> + /// Removes an element from this <see cref="Dictionary"/> by key. + /// </summary> + /// <param name="key">The key of the element to remove.</param> public void Remove(object key) => godot_icall_Dictionary_RemoveKey(GetPtr(), key); // ICollection - public object SyncRoot => this; + object ICollection.SyncRoot => this; - public bool IsSynchronized => false; + bool ICollection.IsSynchronized => false; + /// <summary> + /// Returns the number of elements in this <see cref="Dictionary"/>. + /// This is also known as the size or length of the dictionary. + /// </summary> + /// <returns>The number of elements.</returns> public int Count => godot_icall_Dictionary_Count(GetPtr()); + /// <summary> + /// Copies the elements of this <see cref="Dictionary"/> to the given + /// untyped C# array, starting at the given index. + /// </summary> + /// <param name="array">The array to copy to.</param> + /// <param name="index">The index to start at.</param> public void CopyTo(System.Array array, int index) { - // TODO Can be done with single internal call - if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); - Array keys = (Array)Keys; - Array values = (Array)Values; - int count = Count; + var (keys, values, count) = GetKeyValuePairs(); if (array.Length < (index + count)) throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); @@ -161,24 +230,39 @@ namespace Godot.Collections private class DictionaryEnumerator : IDictionaryEnumerator { - Array keys; - Array values; - int count; - int index = -1; + private readonly Dictionary dictionary; + private readonly int count; + private int index = -1; + private bool dirty = true; + + private DictionaryEntry entry; public DictionaryEnumerator(Dictionary dictionary) { - // TODO 3 internal calls, can reduce to 1 - keys = (Array)dictionary.Keys; - values = (Array)dictionary.Values; + this.dictionary = dictionary; count = dictionary.Count; } public object Current => Entry; - public DictionaryEntry Entry => - // TODO 2 internal calls, can reduce to 1 - new DictionaryEntry(keys[index], values[index]); + public DictionaryEntry Entry + { + get + { + if (dirty) + { + UpdateEntry(); + } + return entry; + } + } + + private void UpdateEntry() + { + dirty = false; + godot_icall_Dictionary_KeyValuePairAt(dictionary.GetPtr(), index, out object key, out object value); + entry = new DictionaryEntry(key, value); + } public object Key => Entry.Key; @@ -187,15 +271,21 @@ namespace Godot.Collections public bool MoveNext() { index++; + dirty = true; return index < count; } public void Reset() { index = -1; + dirty = true; } } + /// <summary> + /// Converts this <see cref="Dictionary"/> to a string. + /// </summary> + /// <returns>A string representation of this dictionary.</returns> public override string ToString() { return godot_icall_Dictionary_ToString(GetPtr()); @@ -226,6 +316,12 @@ namespace Godot.Collections internal extern static int godot_icall_Dictionary_Count(IntPtr ptr); [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static int godot_icall_Dictionary_KeyValuePairs(IntPtr ptr, out IntPtr keys, out IntPtr values); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Dictionary_KeyValuePairAt(IntPtr ptr, int index, out object key, out object value); + + [MethodImpl(MethodImplOptions.InternalCall)] internal extern static void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); [MethodImpl(MethodImplOptions.InternalCall)] @@ -259,10 +355,18 @@ namespace Godot.Collections internal extern static string godot_icall_Dictionary_ToString(IntPtr ptr); } + /// <summary> + /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant + /// typed elements allocated in the engine in C++. Useful when + /// interfacing with the engine. Otherwise prefer .NET collections + /// such as <see cref="System.Collections.Generic.Dictionary{TKey, TValue}"/>. + /// </summary> + /// <typeparam name="TKey">The type of the dictionary's keys.</typeparam> + /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue> { - Dictionary objectDict; + private readonly Dictionary objectDict; internal static int valTypeEncoding; internal static IntPtr valTypeClass; @@ -272,11 +376,19 @@ namespace Godot.Collections Dictionary.godot_icall_Dictionary_Generic_GetValueTypeInfo(typeof(TValue), out valTypeEncoding, out valTypeClass); } + /// <summary> + /// Constructs a new empty <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> public Dictionary() { objectDict = new Dictionary(); } + /// <summary> + /// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements. + /// </summary> + /// <param name="dictionary">The dictionary to construct from.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { objectDict = new Dictionary(); @@ -294,6 +406,11 @@ namespace Godot.Collections } } + /// <summary> + /// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements. + /// </summary> + /// <param name="dictionary">The dictionary to construct from.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { objectDict = dictionary; @@ -309,6 +426,10 @@ namespace Godot.Collections objectDict = new Dictionary(handle); } + /// <summary> + /// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>. + /// </summary> + /// <param name="from">The typed dictionary to convert.</param> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { return from.objectDict; @@ -319,6 +440,11 @@ namespace Godot.Collections return objectDict.GetPtr(); } + /// <summary> + /// Duplicates this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> + /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param> + /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { return new Dictionary<TKey, TValue>(objectDict.Duplicate(deep)); @@ -326,12 +452,19 @@ namespace Godot.Collections // IDictionary<TKey, TValue> + /// <summary> + /// Returns the value at the given <paramref name="key"/>. + /// </summary> + /// <value>The value at the given <paramref name="key"/>.</value> public TValue this[TKey key] { get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } set { objectDict[key] = value; } } + /// <summary> + /// Gets the collection of keys in this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> public ICollection<TKey> Keys { get @@ -341,6 +474,9 @@ namespace Godot.Collections } } + /// <summary> + /// Gets the collection of elements in this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> public ICollection<TValue> Values { get @@ -350,56 +486,93 @@ namespace Godot.Collections } } + private KeyValuePair<TKey, TValue> GetKeyValuePair(int index) + { + Dictionary.godot_icall_Dictionary_KeyValuePairAt(GetPtr(), index, out object key, out object value); + return new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value); + } + + /// <summary> + /// Adds an object <paramref name="value"/> at key <paramref name="key"/> + /// to this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> + /// <param name="key">The key at which to add the object.</param> + /// <param name="value">The object to add.</param> public void Add(TKey key, TValue value) { objectDict.Add(key, value); } + /// <summary> + /// Checks if this <see cref="Dictionary{TKey, TValue}"/> contains the given key. + /// </summary> + /// <param name="key">The key to look for.</param> + /// <returns>Whether or not this dictionary contains the given key.</returns> public bool ContainsKey(TKey key) { return objectDict.Contains(key); } + /// <summary> + /// 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 bool Remove(TKey key) { return Dictionary.godot_icall_Dictionary_RemoveKey(GetPtr(), key); } - public bool TryGetValue(TKey key, out TValue value) + /// <summary> + /// Gets the object at the given <paramref name="key"/>. + /// </summary> + /// <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 bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - object retValue; - bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out retValue, valTypeEncoding, valTypeClass); - value = found ? (TValue)retValue : default(TValue); + bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out object retValue, valTypeEncoding, valTypeClass); + value = found ? (TValue)retValue : default; return found; } // ICollection<KeyValuePair<TKey, TValue>> + /// <summary> + /// Returns the number of elements in this <see cref="Dictionary{TKey, TValue}"/>. + /// This is also known as the size or length of the dictionary. + /// </summary> + /// <returns>The number of elements.</returns> public int Count { get { return objectDict.Count; } } - public bool IsReadOnly - { - get { return objectDict.IsReadOnly; } - } + bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; - public void Add(KeyValuePair<TKey, TValue> item) + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) { objectDict.Add(item.Key, item.Value); } + /// <summary> + /// Erases all the items from this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> public void Clear() { objectDict.Clear(); } - public bool Contains(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { return objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); } + /// <summary> + /// Copies the elements of this <see cref="Dictionary{TKey, TValue}"/> to the given + /// untyped C# array, starting at the given index. + /// </summary> + /// <param name="array">The array to copy to.</param> + /// <param name="arrayIndex">The index to start at.</param> public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { if (array == null) @@ -408,9 +581,6 @@ namespace Godot.Collections if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); - // TODO 3 internal calls, can reduce to 1 - Array<TKey> keys = (Array<TKey>)Keys; - Array<TValue> values = (Array<TValue>)Values; int count = Count; if (array.Length < (arrayIndex + count)) @@ -418,13 +588,12 @@ namespace Godot.Collections for (int i = 0; i < count; i++) { - // TODO 2 internal calls, can reduce to 1 - array[arrayIndex] = new KeyValuePair<TKey, TValue>(keys[i], values[i]); + array[arrayIndex] = GetKeyValuePair(i); arrayIndex++; } } - public bool Remove(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); ; @@ -432,17 +601,15 @@ namespace Godot.Collections // IEnumerable<KeyValuePair<TKey, TValue>> + /// <summary> + /// Gets an enumerator for this <see cref="Dictionary{TKey, TValue}"/>. + /// </summary> + /// <returns>An enumerator.</returns> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { - // TODO 3 internal calls, can reduce to 1 - Array<TKey> keys = (Array<TKey>)Keys; - Array<TValue> values = (Array<TValue>)Values; - int count = Count; - - for (int i = 0; i < count; i++) + for (int i = 0; i < Count; i++) { - // TODO 2 internal calls, can reduce to 1 - yield return new KeyValuePair<TKey, TValue>(keys[i], values[i]); + yield return GetKeyValuePair(i); } } @@ -451,6 +618,10 @@ namespace Godot.Collections return GetEnumerator(); } + /// <summary> + /// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string. + /// </summary> + /// <returns>A string representation of this dictionary.</returns> public override string ToString() => objectDict.ToString(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index f8f5e27397..71d0593916 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -28,6 +28,16 @@ namespace Godot return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); } + private static object[] GetPrintParams(object[] parameters) + { + if (parameters == null) + { + return new[] { "null" }; + } + + return Array.ConvertAll(parameters, x => x?.ToString() ?? "null"); + } + public static int Hash(object var) { return godot_icall_GD_hash(var); @@ -65,7 +75,7 @@ namespace Godot public static void Print(params object[] what) { - godot_icall_GD_print(Array.ConvertAll(what ?? new object[] { "null" }, x => x != null ? x.ToString() : "null")); + godot_icall_GD_print(GetPrintParams(what)); } public static void PrintStack() @@ -75,22 +85,22 @@ namespace Godot public static void PrintErr(params object[] what) { - godot_icall_GD_printerr(Array.ConvertAll(what ?? new object[] { "null" }, x => x != null ? x.ToString() : "null")); + godot_icall_GD_printerr(GetPrintParams(what)); } public static void PrintRaw(params object[] what) { - godot_icall_GD_printraw(Array.ConvertAll(what ?? new object[] { "null" }, x => x != null ? x.ToString() : "null")); + godot_icall_GD_printraw(GetPrintParams(what)); } public static void PrintS(params object[] what) { - godot_icall_GD_prints(Array.ConvertAll(what ?? new object[] { "null" }, x => x != null ? x.ToString() : "null")); + godot_icall_GD_prints(GetPrintParams(what)); } public static void PrintT(params object[] what) { - godot_icall_GD_printt(Array.ConvertAll(what ?? new object[] { "null" }, x => x != null ? x.ToString() : "null")); + godot_icall_GD_printt(GetPrintParams(what)); } public static float Randf() @@ -118,9 +128,9 @@ namespace Godot return godot_icall_GD_randi_range(from, to); } - public static uint RandSeed(ulong seed, out ulong newSeed) + public static uint RandFromSeed(ref ulong seed) { - return godot_icall_GD_rand_seed(seed, out newSeed); + return godot_icall_GD_rand_seed(seed, out seed); } public static IEnumerable<int> Range(int end) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 1b717fb4ae..afc6a65a45 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -124,11 +124,11 @@ namespace Godot /* not sure if very "efficient" but good enough? */ Vector3 sourceScale = basis.Scale; - Quaternion sourceRotation = basis.RotationQuaternion(); + Quaternion sourceRotation = basis.GetRotationQuaternion(); Vector3 sourceLocation = origin; Vector3 destinationScale = transform.basis.Scale; - Quaternion destinationRotation = transform.basis.RotationQuaternion(); + Quaternion destinationRotation = transform.basis.GetRotationQuaternion(); Vector3 destinationLocation = transform.origin; var interpolated = new Transform3D(); diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index 191f863350..86976de244 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -230,6 +230,19 @@ int32_t godot_icall_Dictionary_Count(Dictionary *ptr) { return ptr->size(); } +int32_t godot_icall_Dictionary_KeyValuePairs(Dictionary *ptr, Array **keys, Array **values) { + *keys = godot_icall_Dictionary_Keys(ptr); + *values = godot_icall_Dictionary_Values(ptr); + return godot_icall_Dictionary_Count(ptr); +} + +void godot_icall_Dictionary_KeyValuePairAt(Dictionary *ptr, int index, MonoObject **key, MonoObject **value) { + Array *keys = godot_icall_Dictionary_Keys(ptr); + Array *values = godot_icall_Dictionary_Values(ptr); + *key = GDMonoMarshal::variant_to_mono_object(keys->get(index)); + *value = GDMonoMarshal::variant_to_mono_object(values->get(index)); +} + void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) { Variant varKey = GDMonoMarshal::mono_object_to_variant(key); Variant *ret = ptr->getptr(varKey); @@ -338,6 +351,8 @@ void godot_register_collections_icalls() { GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", godot_icall_Dictionary_Keys); GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", godot_icall_Dictionary_Values); GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", godot_icall_Dictionary_Count); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairs", godot_icall_Dictionary_KeyValuePairs); + GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairAt", godot_icall_Dictionary_KeyValuePairAt); GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", godot_icall_Dictionary_Add); GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", godot_icall_Dictionary_Clear); GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", godot_icall_Dictionary_Contains); diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml index 0c4a0d4ea0..c53af22ae1 100644 --- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml +++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml @@ -51,10 +51,12 @@ <return type="int" enum="Error" /> <argument index="0" name="peer_id" type="int" /> <argument index="1" name="server_compatibility" type="bool" default="false" /> + <argument index="2" name="channels_config" type="Array" default="[]" /> <description> Initialize the multiplayer peer with the given [code]peer_id[/code] (must be between 1 and 2147483647). If [code]server_compatibilty[/code] is [code]false[/code] (default), the multiplayer peer will be immediately in state [constant MultiplayerPeer.CONNECTION_CONNECTED] and [signal MultiplayerPeer.connection_succeeded] will not be emitted. If [code]server_compatibilty[/code] is [code]true[/code] the peer will suppress all [signal MultiplayerPeer.peer_connected] signals until a peer with id [constant MultiplayerPeer.TARGET_PEER_SERVER] connects and then emit [signal MultiplayerPeer.connection_succeeded]. After that the signal [signal MultiplayerPeer.peer_connected] will be emitted for every already connected peer, and any new peer that might connect. If the server peer disconnects after that, signal [signal MultiplayerPeer.server_disconnected] will be emitted and state will become [constant MultiplayerPeer.CONNECTION_CONNECTED]. + You can optionally specify a [code]channels_config[/code] array of [enum MultiplayerPeer.TransferMode] which will be used to create extra channels (WebRTC only supports one transfer mode per channel). </description> </method> <method name="remove_peer"> diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp index 9b08a26aed..95c8c13449 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.cpp +++ b/modules/webrtc/webrtc_multiplayer_peer.cpp @@ -34,7 +34,7 @@ #include "core/os/os.h" void WebRTCMultiplayerPeer::_bind_methods() { - ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("initialize", "peer_id", "server_compatibility", "channels_config"), &WebRTCMultiplayerPeer::initialize, DEFVAL(false), DEFVAL(Array())); ClassDB::bind_method(D_METHOD("add_peer", "peer", "peer_id", "unreliable_lifetime"), &WebRTCMultiplayerPeer::add_peer, DEFVAL(1)); ClassDB::bind_method(D_METHOD("remove_peer", "peer_id"), &WebRTCMultiplayerPeer::remove_peer); ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayerPeer::has_peer); @@ -43,6 +43,14 @@ void WebRTCMultiplayerPeer::_bind_methods() { ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayerPeer::close); } +void WebRTCMultiplayerPeer::set_transfer_channel(int p_channel) { + transfer_channel = p_channel; +} + +int WebRTCMultiplayerPeer::get_transfer_channel() const { + return transfer_channel; +} + void WebRTCMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { transfer_mode = p_mode; } @@ -192,8 +200,34 @@ MultiplayerPeer::ConnectionStatus WebRTCMultiplayerPeer::get_connection_status() return connection_status; } -Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat) { - ERR_FAIL_COND_V(p_self_id < 0 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); +Error WebRTCMultiplayerPeer::initialize(int p_self_id, bool p_server_compat, Array p_channels_config) { + ERR_FAIL_COND_V(p_self_id < 1 || p_self_id > ~(1 << 31), ERR_INVALID_PARAMETER); + channels_config.clear(); + for (int i = 0; i < p_channels_config.size(); i++) { + ERR_FAIL_COND_V_MSG(p_channels_config[i].get_type() != Variant::INT, ERR_INVALID_PARAMETER, "The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'"); + int mode = p_channels_config[i].operator int(); + // Initialize data channel configurations. + Dictionary cfg; + cfg["id"] = CH_RESERVED_MAX + i + 1; + cfg["negotiated"] = true; + cfg["ordered"] = true; + + switch (mode) { + case TRANSFER_MODE_UNRELIABLE_ORDERED: + cfg["maxPacketLifetime"] = 1; + break; + case TRANSFER_MODE_UNRELIABLE: + cfg["maxPacketLifetime"] = 1; + cfg["ordered"] = false; + break; + case TRANSFER_MODE_RELIABLE: + break; + default: + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("The 'channels_config' array must contain only enum values from 'MultiplayerPeer.TransferMode'. Got: %d", mode)); + } + channels_config.push_back(cfg); + } + unique_id = p_self_id; server_compat = p_server_compat; @@ -260,17 +294,23 @@ Error WebRTCMultiplayerPeer::add_peer(Ref<WebRTCPeerConnection> p_peer, int p_pe cfg["id"] = 1; peer->channels[CH_RELIABLE] = p_peer->create_data_channel("reliable", cfg); - ERR_FAIL_COND_V(!peer->channels[CH_RELIABLE].is_valid(), FAILED); + ERR_FAIL_COND_V(peer->channels[CH_RELIABLE].is_null(), FAILED); cfg["id"] = 2; cfg["maxPacketLifetime"] = p_unreliable_lifetime; peer->channels[CH_ORDERED] = p_peer->create_data_channel("ordered", cfg); - ERR_FAIL_COND_V(!peer->channels[CH_ORDERED].is_valid(), FAILED); + ERR_FAIL_COND_V(peer->channels[CH_ORDERED].is_null(), FAILED); cfg["id"] = 3; cfg["ordered"] = false; peer->channels[CH_UNRELIABLE] = p_peer->create_data_channel("unreliable", cfg); - ERR_FAIL_COND_V(!peer->channels[CH_UNRELIABLE].is_valid(), FAILED); + ERR_FAIL_COND_V(peer->channels[CH_UNRELIABLE].is_null(), FAILED); + + for (const Dictionary &dict : channels_config) { + Ref<WebRTCDataChannel> ch = p_peer->create_data_channel(String::num_int64(dict["id"]), dict); + ERR_FAIL_COND_V(ch.is_null(), FAILED); + peer->channels.push_back(ch); + } peer_map[p_peer_id] = peer; // add the new peer connection to the peer_map @@ -312,17 +352,21 @@ Error WebRTCMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { ERR_FAIL_COND_V(connection_status == CONNECTION_DISCONNECTED, ERR_UNCONFIGURED); - int ch = CH_RELIABLE; - switch (transfer_mode) { - case TRANSFER_MODE_RELIABLE: - ch = CH_RELIABLE; - break; - case TRANSFER_MODE_UNRELIABLE_ORDERED: - ch = CH_ORDERED; - break; - case TRANSFER_MODE_UNRELIABLE: - ch = CH_UNRELIABLE; - break; + int ch = transfer_channel; + if (ch == 0) { + switch (transfer_mode) { + case TRANSFER_MODE_RELIABLE: + ch = CH_RELIABLE; + break; + case TRANSFER_MODE_UNRELIABLE_ORDERED: + ch = CH_ORDERED; + break; + case TRANSFER_MODE_UNRELIABLE: + ch = CH_UNRELIABLE; + break; + } + } else { + ch += CH_RESERVED_MAX - 1; } Map<int, Ref<ConnectedPeer>>::Element *E = nullptr; @@ -331,8 +375,8 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si E = peer_map.find(target_peer); ERR_FAIL_COND_V_MSG(!E, ERR_INVALID_PARAMETER, "Invalid target peer: " + itos(target_peer) + "."); - ERR_FAIL_COND_V(E->value()->channels.size() <= ch, ERR_BUG); - ERR_FAIL_COND_V(!E->value()->channels[ch].is_valid(), ERR_BUG); + ERR_FAIL_COND_V_MSG(E->value()->channels.size() <= ch, ERR_INVALID_PARAMETER, vformat("Unable to send packet on channel %d, max channels: %d", ch, E->value()->channels.size())); + ERR_FAIL_COND_V(E->value()->channels[ch].is_null(), ERR_BUG); return E->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); } else { @@ -344,7 +388,8 @@ Error WebRTCMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_si continue; } - ERR_CONTINUE(F->value()->channels.size() <= ch || !F->value()->channels[ch].is_valid()); + ERR_CONTINUE_MSG(F->value()->channels.size() <= ch, vformat("Unable to send packet on channel %d, max channels: %d", ch, E->value()->channels.size())); + ERR_CONTINUE(F->value()->channels[ch].is_null()); F->value()->channels[ch]->put_packet(p_buffer, p_buffer_size); } } @@ -370,23 +415,13 @@ int WebRTCMultiplayerPeer::get_max_packet_size() const { void WebRTCMultiplayerPeer::close() { peer_map.clear(); + channels_config.clear(); unique_id = 0; next_packet_peer = 0; target_peer = 0; connection_status = CONNECTION_DISCONNECTED; } -WebRTCMultiplayerPeer::WebRTCMultiplayerPeer() { - unique_id = 0; - next_packet_peer = 0; - target_peer = 0; - client_count = 0; - transfer_mode = TRANSFER_MODE_RELIABLE; - refuse_connections = false; - connection_status = CONNECTION_DISCONNECTED; - server_compat = false; -} - WebRTCMultiplayerPeer::~WebRTCMultiplayerPeer() { close(); } diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h index 1d9387b6dc..ef4fe1678c 100644 --- a/modules/webrtc/webrtc_multiplayer_peer.h +++ b/modules/webrtc/webrtc_multiplayer_peer.h @@ -62,25 +62,27 @@ private: } }; - uint32_t unique_id; - int target_peer; - int client_count; - bool refuse_connections; - ConnectionStatus connection_status; - TransferMode transfer_mode; - int next_packet_peer; - bool server_compat; + uint32_t unique_id = 0; + int target_peer = 0; + int client_count = 0; + bool refuse_connections = false; + ConnectionStatus connection_status = CONNECTION_DISCONNECTED; + int transfer_channel = 0; + TransferMode transfer_mode = TRANSFER_MODE_RELIABLE; + int next_packet_peer = 0; + bool server_compat = false; Map<int, Ref<ConnectedPeer>> peer_map; + List<Dictionary> channels_config; void _peer_to_dict(Ref<ConnectedPeer> p_connected_peer, Dictionary &r_dict); void _find_next_peer(); public: - WebRTCMultiplayerPeer(); + WebRTCMultiplayerPeer() {} ~WebRTCMultiplayerPeer(); - Error initialize(int p_self_id, bool p_server_compat = false); + Error initialize(int p_self_id, bool p_server_compat = false, Array p_channels_config = Array()); Error add_peer(Ref<WebRTCPeerConnection> p_peer, int p_peer_id, int p_unreliable_lifetime = 1); void remove_peer(int p_peer_id); bool has_peer(int p_peer_id); @@ -95,6 +97,8 @@ public: int get_max_packet_size() const override; // MultiplayerPeer + void set_transfer_channel(int p_channel) override; + int get_transfer_channel() const override; void set_transfer_mode(TransferMode p_mode) override; TransferMode get_transfer_mode() const override; void set_target_peer(int p_peer_id) override; diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp index 52d9a602a1..163cc7706b 100644 --- a/modules/websocket/websocket_multiplayer_peer.cpp +++ b/modules/websocket/websocket_multiplayer_peer.cpp @@ -105,6 +105,14 @@ Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer // // MultiplayerPeer // +void WebSocketMultiplayerPeer::set_transfer_channel(int p_channel) { + // Websocket does not have channels. +} + +int WebSocketMultiplayerPeer::get_transfer_channel() const { + return 0; +} + void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { // Websocket uses TCP, reliable } diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h index 4e80f876d6..0fee196f41 100644 --- a/modules/websocket/websocket_multiplayer_peer.h +++ b/modules/websocket/websocket_multiplayer_peer.h @@ -78,6 +78,8 @@ protected: public: /* MultiplayerPeer */ + void set_transfer_channel(int p_channel) override; + int get_transfer_channel() const override; void set_transfer_mode(TransferMode p_mode) override; TransferMode get_transfer_mode() const override; void set_target_peer(int p_target_peer) override; |