feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
@ -12,3 +13,10 @@ if env.editor_build:
env_mp.add_source_files(module_obj, "editor/*.cpp")
env.modules_sources += module_obj
if env["tests"]:
env_mp.Append(CPPDEFINES=["TESTS_ENABLED"])
env_mp.add_source_files(env.modules_sources, "./tests/*.cpp")
if env["disable_exceptions"]:
env_mp.Append(CPPDEFINES=["DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS"])

View file

@ -12,6 +12,7 @@ def get_doc_classes():
"SceneMultiplayer",
"MultiplayerSpawner",
"MultiplayerSynchronizer",
"OfflineMultiplayerPeer",
]

View file

@ -48,11 +48,11 @@
</methods>
<members>
<member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function">
Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
Method called on all peers when a custom [method spawn] is requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree.
[b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
</member>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
Maximum number of nodes allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
When set to [code]0[/code] (the default), there is no limit.
</member>
<member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath(&quot;&quot;)">
@ -63,13 +63,13 @@
<signal name="despawned">
<param index="0" name="node" type="Node" />
<description>
Emitted when a spawnable scene or custom spawn was despawned by the multiplayer authority. Only called on puppets.
Emitted when a spawnable scene or custom spawn was despawned by the multiplayer authority. Only called on remote peers.
</description>
</signal>
<signal name="spawned">
<param index="0" name="node" type="Node" />
<description>
Emitted when a spawnable scene or custom spawn was spawned by the multiplayer authority. Only called on puppets.
Emitted when a spawnable scene or custom spawn was spawned by the multiplayer authority. Only called on remote peers.
</description>
</signal>
</signals>

View file

@ -53,7 +53,7 @@
</methods>
<members>
<member name="delta_interval" type="float" setter="set_delta_interval" getter="get_delta_interval" default="0.0">
Time interval between delta synchronizations. When set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame.
Time interval between delta synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ON_CHANGE]. If set to [code]0.0[/code] (the default), delta synchronizations happen every network process frame.
</member>
<member name="public_visibility" type="bool" setter="set_visibility_public" getter="is_visibility_public" default="true">
Whether synchronization should be visible to all peers by default. See [method set_visibility_for] and [method add_visibility_filter] for ways of configuring fine-grained visibility options.
@ -62,7 +62,7 @@
Resource containing which properties to synchronize.
</member>
<member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0">
Time interval between synchronizations. When set to [code]0.0[/code] (the default), synchronizations happen every network process frame.
Time interval between synchronizations. Used when the replication is set to [constant SceneReplicationConfig.REPLICATION_MODE_ALWAYS]. If set to [code]0.0[/code] (the default), synchronizations happen every network process frame.
</member>
<member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;..&quot;)">
Node path that replicated properties are relative to.

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="OfflineMultiplayerPeer" inherits="MultiplayerPeer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A [MultiplayerPeer] which is always connected and acts as a server.
</brief_description>
<description>
This is the default [member MultiplayerAPI.multiplayer_peer] for the [member Node.multiplayer]. It mimics the behavior of a server with no peers connected.
This means that the [SceneTree] will act as the multiplayer authority by default. Calls to [method MultiplayerAPI.is_server] will return [code]true[/code], and calls to [method MultiplayerAPI.get_unique_id] will return [constant MultiplayerPeer.TARGET_PEER_SERVER].
</description>
<tutorials>
</tutorials>
</class>

View file

@ -65,10 +65,10 @@
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution.
</member>
<member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback" default="Callable()">
The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
The callback to execute when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
</member>
<member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0">
If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
If set to a value greater than [code]0.0[/code], the maximum duration in seconds peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
</member>
<member name="max_delta_packet_size" type="int" setter="set_max_delta_packet_size" getter="get_max_delta_packet_size" default="65535">
Maximum size of each delta packet. Higher values increase the chance of receiving full updates in a single frame, but also the chance of causing networking congestion (higher latency, disconnections). See [MultiplayerSynchronizer].

View file

@ -30,10 +30,11 @@
#include "editor_network_profiler.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/check_box.h"
void EditorNetworkProfiler::_bind_methods() {
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
@ -44,11 +45,11 @@ void EditorNetworkProfiler::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
if (activate->is_pressed()) {
activate->set_icon(theme_cache.stop_icon);
activate->set_button_icon(theme_cache.stop_icon);
} else {
activate->set_icon(theme_cache.play_icon);
activate->set_button_icon(theme_cache.play_icon);
}
clear_button->set_icon(theme_cache.clear_icon);
clear_button->set_button_icon(theme_cache.clear_icon);
incoming_bandwidth_text->set_right_icon(theme_cache.incoming_bandwidth_icon);
outgoing_bandwidth_text->set_right_icon(theme_cache.outgoing_bandwidth_icon);
@ -170,15 +171,46 @@ void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) {
}
void EditorNetworkProfiler::_activate_pressed() {
_update_button_text();
if (activate->is_pressed()) {
refresh_timer->start();
activate->set_icon(theme_cache.stop_icon);
activate->set_text(TTR("Stop"));
} else {
refresh_timer->stop();
activate->set_icon(theme_cache.play_icon);
}
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
}
void EditorNetworkProfiler::_update_button_text() {
if (activate->is_pressed()) {
activate->set_button_icon(theme_cache.stop_icon);
activate->set_text(TTR("Stop"));
} else {
activate->set_button_icon(theme_cache.play_icon);
activate->set_text(TTR("Start"));
}
}
void EditorNetworkProfiler::started() {
_clear_pressed();
activate->set_disabled(false);
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false)) {
set_profiling(true);
refresh_timer->start();
}
}
void EditorNetworkProfiler::stopped() {
activate->set_disabled(true);
set_profiling(false);
refresh_timer->stop();
}
void EditorNetworkProfiler::set_profiling(bool p_pressed) {
activate->set_pressed(p_pressed);
_update_button_text();
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
}
@ -190,6 +222,12 @@ void EditorNetworkProfiler::_clear_pressed() {
set_bandwidth(0, 0);
refresh_rpc_data();
refresh_replication_data();
clear_button->set_disabled(true);
}
void EditorNetworkProfiler::_autostart_toggled(bool p_toggled_on) {
EditorSettings::get_singleton()->set_project_metadata("debug_options", "autostart_network_profiler", p_toggled_on);
EditorRunBar::get_singleton()->update_profiler_autostart_indicator();
}
void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) {
@ -203,6 +241,9 @@ void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_
}
void EditorNetworkProfiler::add_rpc_frame_data(const RPCNodeInfo &p_frame) {
if (clear_button->is_disabled()) {
clear_button->set_disabled(false);
}
dirty = true;
if (!rpc_data.has(p_frame.node)) {
rpc_data.insert(p_frame.node, p_frame);
@ -219,6 +260,9 @@ void EditorNetworkProfiler::add_rpc_frame_data(const RPCNodeInfo &p_frame) {
}
void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
if (clear_button->is_disabled()) {
clear_button->set_disabled(false);
}
dirty = true;
if (!sync_data.has(p_frame.synchronizer)) {
sync_data[p_frame.synchronizer] = p_frame;
@ -227,10 +271,10 @@ void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs;
}
SyncInfo &info = sync_data[p_frame.synchronizer];
if (info.incoming_syncs) {
if (p_frame.incoming_syncs) {
info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs;
}
if (info.outgoing_syncs) {
if (p_frame.outgoing_syncs) {
info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs;
}
}
@ -260,14 +304,22 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
activate = memnew(Button);
activate->set_toggle_mode(true);
activate->set_text(TTR("Start"));
activate->set_disabled(true);
activate->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_activate_pressed));
hb->add_child(activate);
clear_button = memnew(Button);
clear_button->set_text(TTR("Clear"));
clear_button->set_disabled(true);
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_clear_pressed));
hb->add_child(clear_button);
CheckBox *autostart_checkbox = memnew(CheckBox);
autostart_checkbox->set_text(TTR("Autostart"));
autostart_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false));
autostart_checkbox->connect(SceneStringName(toggled), callable_mp(this, &EditorNetworkProfiler::_autostart_toggled));
hb->add_child(autostart_checkbox);
hb->add_spacer();
Label *lb = memnew(Label);

View file

@ -92,7 +92,9 @@ private:
void _activate_pressed();
void _clear_pressed();
void _autostart_toggled(bool p_toggled_on);
void _refresh();
void _update_button_text();
void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button);
protected:
@ -112,6 +114,10 @@ public:
void set_bandwidth(int p_incoming, int p_outgoing);
bool is_profiling();
void set_profiling(bool p_pressed);
void started();
void stopped();
EditorNetworkProfiler();
};

View file

@ -106,6 +106,8 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) {
profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request));
profiler->set_name(TTR("Network Profiler"));
session->connect("started", callable_mp(profiler, &EditorNetworkProfiler::started));
session->connect("stopped", callable_mp(profiler, &EditorNetworkProfiler::stopped));
session->add_session_tab(profiler);
profilers[p_session_id] = profiler;
}
@ -114,7 +116,7 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) {
MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
repl_editor = memnew(ReplicationEditor);
button = EditorNode::get_bottom_panel()->add_item(TTR("Replication"), repl_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_replication_bottom_panel", TTR("Toggle Replication Bottom Panel")));
button = EditorNode::get_bottom_panel()->add_item(TTR("Replication"), repl_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_replication_bottom_panel", TTRC("Toggle Replication Bottom Panel")));
button->hide();
repl_editor->get_pin()->connect(SceneStringName(pressed), callable_mp(this, &MultiplayerEditorPlugin::_pinned));
debugger.instantiate();

View file

@ -37,7 +37,6 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
#include "editor/property_selector.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
@ -93,24 +92,6 @@ void ReplicationEditor::_pick_node_select_recursive(TreeItem *p_item, const Stri
}
}
void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) {
Ref<InputEventKey> k = p_ie;
if (k.is_valid()) {
switch (k->get_keycode()) {
case Key::UP:
case Key::DOWN:
case Key::PAGEUP:
case Key::PAGEDOWN: {
pick_node->get_scene_tree()->get_scene_tree()->gui_input(k);
pick_node->get_filter_line_edit()->accept_event();
} break;
default:
break;
}
}
}
void ReplicationEditor::_pick_node_selected(NodePath p_path) {
Node *root = current->get_node(current->get_root_path());
ERR_FAIL_NULL(root);
@ -184,11 +165,9 @@ ReplicationEditor::ReplicationEditor() {
pick_node = memnew(SceneTreeDialog);
add_child(pick_node);
pick_node->register_text_enter(pick_node->get_filter_line_edit());
pick_node->set_title(TTR("Pick a node to synchronize:"));
pick_node->connect("selected", callable_mp(this, &ReplicationEditor::_pick_node_selected));
pick_node->get_filter_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &ReplicationEditor::_pick_node_filter_text_changed));
pick_node->get_filter_line_edit()->connect("gui_input", callable_mp(this, &ReplicationEditor::_pick_node_filter_input));
prop_selector = memnew(PropertySelector);
add_child(prop_selector);
@ -256,7 +235,7 @@ ReplicationEditor::ReplicationEditor() {
np_line_edit = memnew(LineEdit);
np_line_edit->set_placeholder(":property");
np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
np_line_edit->connect("text_submitted", callable_mp(this, &ReplicationEditor::_np_text_submitted));
np_line_edit->connect(SceneStringName(text_submitted), callable_mp(this, &ReplicationEditor::_np_text_submitted));
hb->add_child(np_line_edit);
add_from_path_button = memnew(Button);
@ -269,7 +248,7 @@ ReplicationEditor::ReplicationEditor() {
hb->add_child(vs);
pin = memnew(Button);
pin->set_theme_type_variation("FlatButton");
pin->set_theme_type_variation(SceneStringName(FlatButton));
pin->set_toggle_mode(true);
pin->set_tooltip_text(TTR("Pin replication editor"));
hb->add_child(pin);
@ -373,8 +352,8 @@ void ReplicationEditor::_notification(int p_what) {
}
case NOTIFICATION_ENTER_TREE: {
add_theme_style_override(SceneStringName(panel), EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SceneStringName(panel), SNAME("Panel")));
add_pick_button->set_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
pin->set_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons)));
add_pick_button->set_button_icon(get_theme_icon(SNAME("Add"), EditorStringName(EditorIcons)));
pin->set_button_icon(get_theme_icon(SNAME("Pin"), EditorStringName(EditorIcons)));
} break;
}
}
@ -395,7 +374,7 @@ void ReplicationEditor::_add_pressed() {
return;
}
int idx = np_text.find(":");
int idx = np_text.find_char(':');
if (idx == -1) {
np_text = ".:" + np_text;
} else if (idx == 0) {
@ -511,7 +490,7 @@ void ReplicationEditor::_update_config() {
tree->clear();
tree->create_item();
drop_label->set_visible(true);
if (!config.is_valid()) {
if (config.is_null()) {
return;
}
TypedArray<NodePath> props = config->get_properties();
@ -574,7 +553,7 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
Ref<Texture2D> icon = _get_class_icon(root_node);
if (root_node) {
String path = prop.substr(0, prop.find(":"));
String path = prop.substr(0, prop.find_char(':'));
String subpath = prop.substr(path.size());
Node *node = root_node->get_node_or_null(path);
if (!node) {

View file

@ -81,7 +81,6 @@ private:
void _pick_node_filter_text_changed(const String &p_newtext);
void _pick_node_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates);
void _pick_node_filter_input(const Ref<InputEvent> &p_ie);
void _pick_node_selected(NodePath p_path);
void _pick_new_property();

View file

@ -32,7 +32,6 @@
#define MULTIPLAYER_DEBUGGER_H
#include "core/debugger/engine_profiler.h"
#include "core/os/os.h"
class MultiplayerSynchronizer;

View file

@ -30,9 +30,7 @@
#include "multiplayer_spawner.h"
#include "core/io/marshalls.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/window.h"
#ifdef TOOLS_ENABLED
/* This is editor only */
@ -46,7 +44,7 @@ bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value)
if (ns.begins_with("scenes/")) {
uint32_t index = ns.get_slicec('/', 1).to_int();
ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false);
spawnable_scenes[index].path = p_value;
spawnable_scenes[index].path = ResourceUID::ensure_path(p_value);
return true;
}
}
@ -62,7 +60,7 @@ bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const {
if (ns.begins_with("scenes/")) {
uint32_t index = ns.get_slicec('/', 1).to_int();
ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false);
r_ret = spawnable_scenes[index].path;
r_ret = ResourceUID::path_to_uid(spawnable_scenes[index].path);
return true;
}
}
@ -97,9 +95,9 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const {
void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
SpawnableScene sc;
sc.path = p_path;
sc.path = ResourceUID::ensure_path(p_path);
if (Engine::get_singleton()->is_editor_hint()) {
ERR_FAIL_COND(!ResourceLoader::exists(p_path));
ERR_FAIL_COND(!ResourceLoader::exists(sc.path));
}
spawnable_scenes.push_back(sc);
#ifdef TOOLS_ENABLED
@ -139,7 +137,7 @@ Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
Vector<String> ss;
ss.resize(spawnable_scenes.size());
for (int i = 0; i < ss.size(); i++) {
ss.write[i] = spawnable_scenes[i].path;
ss.write[i] = ResourceUID::path_to_uid(spawnable_scenes[i].path);
}
return ss;
}

View file

@ -31,10 +31,7 @@
#ifndef MULTIPLAYER_SPAWNER_H
#define MULTIPLAYER_SPAWNER_H
#include "scene_replication_config.h"
#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
#include "scene/main/node.h"
#include "scene/resources/packed_scene.h"

View file

@ -31,7 +31,7 @@
#ifndef SCENE_CACHE_INTERFACE_H
#define SCENE_CACHE_INTERFACE_H
#include "scene/main/multiplayer_api.h"
#include "core/object/ref_counted.h"
class Node;
class SceneMultiplayer;

View file

@ -440,7 +440,7 @@ void SceneMultiplayer::disconnect_peer(int p_id) {
Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
ERR_FAIL_COND_V_MSG(p_data.is_empty(), ERR_INVALID_DATA, "Trying to send an empty raw packet.");
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer.is_null(), ERR_UNCONFIGURED, "Trying to send a raw packet while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a multiplayer peer which is not connected.");
if (packet_cache.size() < p_data.size() + 1) {
@ -530,22 +530,22 @@ void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_p
}
int SceneMultiplayer::get_unique_id() {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), 0, "No multiplayer peer is assigned. Unable to get unique ID.");
ERR_FAIL_COND_V_MSG(multiplayer_peer.is_null(), 0, "No multiplayer peer is assigned. Unable to get unique ID.");
return multiplayer_peer->get_unique_id();
}
void SceneMultiplayer::set_refuse_new_connections(bool p_refuse) {
ERR_FAIL_COND_MSG(!multiplayer_peer.is_valid(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'.");
ERR_FAIL_COND_MSG(multiplayer_peer.is_null(), "No multiplayer peer is assigned. Unable to set 'refuse_new_connections'.");
multiplayer_peer->set_refuse_new_connections(p_refuse);
}
bool SceneMultiplayer::is_refusing_new_connections() const {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'.");
ERR_FAIL_COND_V_MSG(multiplayer_peer.is_null(), false, "No multiplayer peer is assigned. Unable to get 'refuse_new_connections'.");
return multiplayer_peer->is_refusing_new_connections();
}
Vector<int> SceneMultiplayer::get_peer_ids() {
ERR_FAIL_COND_V_MSG(!multiplayer_peer.is_valid(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected.");
ERR_FAIL_COND_V_MSG(multiplayer_peer.is_null(), Vector<int>(), "No multiplayer peer is assigned. Assume no peers are connected.");
Vector<int> ret;
for (const int &E : connected_peers) {
@ -684,9 +684,9 @@ void SceneMultiplayer::_bind_methods() {
SceneMultiplayer::SceneMultiplayer() {
relay_buffer.instantiate();
cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this, cache.ptr())));
rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this, cache.ptr(), replicator.ptr())));
cache.instantiate(this);
replicator.instantiate(this, cache.ptr());
rpc.instantiate(this, cache.ptr(), replicator.ptr());
set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer)));
}

View file

@ -52,14 +52,14 @@ public:
virtual void set_target_peer(int p_peer_id) override {}
virtual int get_packet_peer() const override { return 0; }
virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; };
virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }
virtual int get_packet_channel() const override { return 0; }
virtual void disconnect_peer(int p_peer, bool p_force = false) override {}
virtual bool is_server() const override { return true; }
virtual void poll() override {}
virtual void close() override {}
virtual int get_unique_id() const override { return TARGET_PEER_SERVER; }
virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; };
virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; }
};
class SceneMultiplayer : public MultiplayerAPI {

View file

@ -30,9 +30,6 @@
#include "scene_replication_config.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/node.h"
bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name;

View file

@ -80,9 +80,9 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no
ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY);
const Dictionary config = p_config;
Array names = config.keys();
names.sort(); // Ensure ID order
names.sort_custom(callable_mp_static(&StringLikeVariantOrder::compare)); // Ensure ID order
for (int i = 0; i < names.size(); i++) {
ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME);
ERR_CONTINUE(!names[i].is_string());
String name = names[i].operator String();
ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY);
ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode"));
@ -108,7 +108,7 @@ const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(con
return rpc_cache[oid];
}
RPCConfigCache cache;
_parse_rpc_config(p_node->get_node_rpc_config(), true, cache);
_parse_rpc_config(p_node->get_rpc_config(), true, cache);
if (p_node->get_script_instance()) {
_parse_rpc_config(p_node->get_script_instance()->get_rpc_config(), false, cache);
}
@ -461,7 +461,7 @@ void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, con
Error SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
ERR_FAIL_COND_V_MSG(!peer.is_valid(), ERR_UNCONFIGURED, "Trying to call an RPC while no multiplayer peer is active.");
ERR_FAIL_COND_V_MSG(peer.is_null(), ERR_UNCONFIGURED, "Trying to call an RPC while no multiplayer peer is active.");
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_COND_V_MSG(!node || !node->is_inside_tree(), ERR_INVALID_PARAMETER, "The object must be a valid Node inside the SceneTree");
ERR_FAIL_COND_V_MSG(peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_CONNECTION_ERROR, "Trying to call an RPC via a multiplayer peer which is not connected.");

View file

@ -0,0 +1,284 @@
/**************************************************************************/
/* test_scene_multiplayer.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TEST_SCENE_MULTIPLAYER_H
#define TEST_SCENE_MULTIPLAYER_H
#include "tests/test_macros.h"
#include "tests/test_utils.h"
#include "../scene_multiplayer.h"
namespace TestSceneMultiplayer {
static inline Array build_array() {
return Array();
}
template <typename... Targs>
static inline Array build_array(Variant item, Targs... Fargs) {
Array a = build_array(Fargs...);
a.push_front(item);
return a;
}
TEST_CASE("[Multiplayer][SceneMultiplayer] Defaults") {
Ref<SceneMultiplayer> scene_multiplayer;
scene_multiplayer.instantiate();
REQUIRE(scene_multiplayer->has_multiplayer_peer());
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance.");
CHECK_EQ(scene_multiplayer->poll(), Error::OK);
CHECK_EQ(scene_multiplayer->get_unique_id(), MultiplayerPeer::TARGET_PEER_SERVER);
CHECK_EQ(scene_multiplayer->get_peer_ids(), Vector<int>());
CHECK_EQ(scene_multiplayer->get_remote_sender_id(), 0);
CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
CHECK(scene_multiplayer->get_connected_peers().is_empty());
CHECK_FALSE(scene_multiplayer->is_refusing_new_connections());
CHECK_FALSE(scene_multiplayer->is_object_decoding_allowed());
CHECK(scene_multiplayer->is_server_relay_enabled());
CHECK_EQ(scene_multiplayer->get_max_sync_packet_size(), 1350);
CHECK_EQ(scene_multiplayer->get_max_delta_packet_size(), 65535);
CHECK(scene_multiplayer->is_server());
}
TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] SceneTree has a OfflineMultiplayerPeer by default") {
Ref<SceneMultiplayer> scene_multiplayer = SceneTree::get_singleton()->get_multiplayer();
REQUIRE(scene_multiplayer->has_multiplayer_peer());
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
REQUIRE_MESSAGE(Object::cast_to<OfflineMultiplayerPeer>(multiplayer_peer.ptr()) != nullptr, "By default it must be an OfflineMultiplayerPeer instance.");
}
TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Object configuration add/remove") {
Ref<SceneMultiplayer> scene_multiplayer;
scene_multiplayer.instantiate();
SUBCASE("Returns invalid parameter") {
CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER);
CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, "ImInvalid"), Error::ERR_INVALID_PARAMETER);
NodePath foo_path("/Foo");
NodePath bar_path("/Bar");
CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, bar_path), Error::ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
SUBCASE("Sets root path") {
NodePath foo_path("/Foo");
CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
CHECK_EQ(scene_multiplayer->get_root_path(), foo_path);
}
SUBCASE("Unsets root path") {
NodePath foo_path("/Foo");
CHECK_EQ(scene_multiplayer->object_configuration_add(nullptr, foo_path), Error::OK);
CHECK_EQ(scene_multiplayer->object_configuration_remove(nullptr, foo_path), Error::OK);
CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
}
SUBCASE("Add/Remove a MultiplayerSpawner") {
Node2D *node = memnew(Node2D);
MultiplayerSpawner *spawner = memnew(MultiplayerSpawner);
CHECK_EQ(scene_multiplayer->object_configuration_add(node, spawner), Error::OK);
CHECK_EQ(scene_multiplayer->object_configuration_remove(node, spawner), Error::OK);
memdelete(spawner);
memdelete(node);
}
SUBCASE("Add/Remove a MultiplayerSynchronizer") {
Node2D *node = memnew(Node2D);
MultiplayerSynchronizer *synchronizer = memnew(MultiplayerSynchronizer);
CHECK_EQ(scene_multiplayer->object_configuration_add(node, synchronizer), Error::OK);
CHECK_EQ(scene_multiplayer->object_configuration_remove(node, synchronizer), Error::OK);
memdelete(synchronizer);
memdelete(node);
}
}
TEST_CASE("[Multiplayer][SceneMultiplayer] Root Path") {
Ref<SceneMultiplayer> scene_multiplayer;
scene_multiplayer.instantiate();
SUBCASE("Is set") {
NodePath foo_path("/Foo");
scene_multiplayer->set_root_path(foo_path);
CHECK_EQ(scene_multiplayer->get_root_path(), foo_path);
}
SUBCASE("Fails when path is empty") {
ERR_PRINT_OFF;
scene_multiplayer->set_root_path(NodePath());
ERR_PRINT_ON;
}
SUBCASE("Fails when path is relative") {
NodePath foo_path("Foo");
ERR_PRINT_OFF;
scene_multiplayer->set_root_path(foo_path);
ERR_PRINT_ON;
CHECK_EQ(scene_multiplayer->get_root_path(), NodePath());
}
}
// This one could be a dummy callback because the current set of test is not actually testing the full auth flow.
static Variant auth_callback(Variant sv, Variant pvav) {
return Variant();
}
TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Send Authentication") {
Ref<SceneMultiplayer> scene_multiplayer;
scene_multiplayer.instantiate();
SceneTree::get_singleton()->set_multiplayer(scene_multiplayer);
scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback));
SUBCASE("Is properly sent") {
SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authenticating");
// Adding a peer to MultiplayerPeer.
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
int peer_id = 42;
multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
SIGNAL_CHECK("peer_authenticating", build_array(build_array(peer_id)));
CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
Vector<int> expected_peer_ids = { peer_id };
CHECK_EQ(scene_multiplayer->get_authenticating_peer_ids(), expected_peer_ids);
SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authenticating");
}
SUBCASE("peer_authentication_failed is emitted when a peer is deleted before authentication is completed") {
SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
// Adding a peer to MultiplayerPeer.
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
int peer_id = 42;
multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
multiplayer_peer->emit_signal(SNAME("peer_disconnected"), peer_id);
SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(peer_id)));
SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
}
SUBCASE("peer_authentication_failed is emitted when authentication timeout") {
SIGNAL_WATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
scene_multiplayer->set_auth_timeout(0.01);
CHECK_EQ(scene_multiplayer->get_auth_timeout(), 0.01);
// Adding two peesr to MultiplayerPeer.
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
int first_peer_id = 42;
int second_peer_id = 84;
multiplayer_peer->emit_signal(SNAME("peer_connected"), first_peer_id);
multiplayer_peer->emit_signal(SNAME("peer_connected"), second_peer_id);
// Let timeout happens.
OS::get_singleton()->delay_usec(500000);
CHECK_EQ(scene_multiplayer->poll(), Error::OK);
SIGNAL_CHECK("peer_authentication_failed", build_array(build_array(first_peer_id), build_array(second_peer_id)));
SIGNAL_UNWATCH(scene_multiplayer.ptr(), "peer_authentication_failed");
}
SUBCASE("Fails when there is no MultiplayerPeer configured") {
scene_multiplayer->set_multiplayer_peer(nullptr);
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->send_auth(42, Vector<uint8_t>()), Error::ERR_UNCONFIGURED);
ERR_PRINT_ON;
}
SUBCASE("Fails when the peer to send the auth is not pending") {
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->send_auth(42, String("It's me").to_ascii_buffer()), Error::ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
}
TEST_CASE("[Multiplayer][SceneMultiplayer][SceneTree] Complete Authentication") {
Ref<SceneMultiplayer> scene_multiplayer;
scene_multiplayer.instantiate();
SceneTree::get_singleton()->set_multiplayer(scene_multiplayer);
scene_multiplayer->set_auth_callback(callable_mp_static(auth_callback));
SUBCASE("Is properly completed") {
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
int peer_id = 42;
multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK);
}
SUBCASE("Fails when there is no MultiplayerPeer configured") {
scene_multiplayer->set_multiplayer_peer(nullptr);
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_UNCONFIGURED);
ERR_PRINT_ON;
}
SUBCASE("Fails when the peer to complete the auth is not pending") {
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->complete_auth(42), Error::ERR_INVALID_PARAMETER);
ERR_PRINT_ON;
}
SUBCASE("Fails to send auth or completed for a second time") {
Ref<MultiplayerPeer> multiplayer_peer = scene_multiplayer->get_multiplayer_peer();
int peer_id = 42;
multiplayer_peer->emit_signal(SNAME("peer_connected"), peer_id);
CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::OK);
CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::OK);
ERR_PRINT_OFF;
CHECK_EQ(scene_multiplayer->send_auth(peer_id, String("It's me").to_ascii_buffer()), Error::ERR_FILE_CANT_WRITE);
CHECK_EQ(scene_multiplayer->complete_auth(peer_id), Error::ERR_FILE_CANT_WRITE);
ERR_PRINT_ON;
}
}
} // namespace TestSceneMultiplayer
#endif // TEST_SCENE_MULTIPLAYER_H