1/**************************************************************************/
2/* multiplayer_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "multiplayer_editor_plugin.h"
32
33#include "../multiplayer_synchronizer.h"
34#include "editor_network_profiler.h"
35#include "replication_editor.h"
36
37#include "editor/editor_interface.h"
38#include "editor/editor_node.h"
39
40void MultiplayerEditorDebugger::_bind_methods() {
41 ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path")));
42}
43
44bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const {
45 return p_capture == "multiplayer";
46}
47
48void MultiplayerEditorDebugger::_open_request(const String &p_path) {
49 emit_signal("open_request", p_path);
50}
51
52bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
53 ERR_FAIL_COND_V(!profilers.has(p_session), false);
54 EditorNetworkProfiler *profiler = profilers[p_session];
55 if (p_message == "multiplayer:rpc") {
56 MultiplayerDebugger::RPCFrame frame;
57 frame.deserialize(p_data);
58 for (int i = 0; i < frame.infos.size(); i++) {
59 profiler->add_rpc_frame_data(frame.infos[i]);
60 }
61 return true;
62 } else if (p_message == "multiplayer:syncs") {
63 MultiplayerDebugger::ReplicationFrame frame;
64 frame.deserialize(p_data);
65 for (const KeyValue<ObjectID, MultiplayerDebugger::SyncInfo> &E : frame.infos) {
66 profiler->add_sync_frame_data(E.value);
67 }
68 Array missing = profiler->pop_missing_node_data();
69 if (missing.size()) {
70 // Asks for the object information.
71 get_session(p_session)->send_message("multiplayer:cache", missing);
72 }
73 return true;
74 } else if (p_message == "multiplayer:cache") {
75 ERR_FAIL_COND_V(p_data.size() % 3, false);
76 for (int i = 0; i < p_data.size(); i += 3) {
77 EditorNetworkProfiler::NodeInfo info;
78 info.id = p_data[i].operator ObjectID();
79 info.type = p_data[i + 1].operator String();
80 info.path = p_data[i + 2].operator String();
81 profiler->add_node_data(info);
82 }
83 return true;
84 } else if (p_message == "multiplayer:bandwidth") {
85 ERR_FAIL_COND_V(p_data.size() < 2, false);
86 profiler->set_bandwidth(p_data[0], p_data[1]);
87 return true;
88 }
89 return false;
90}
91
92void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) {
93 Ref<EditorDebuggerSession> session = get_session(p_session_id);
94 ERR_FAIL_COND(session.is_null());
95 session->toggle_profiler("multiplayer:bandwidth", p_enable);
96 session->toggle_profiler("multiplayer:rpc", p_enable);
97 session->toggle_profiler("multiplayer:replication", p_enable);
98}
99
100void MultiplayerEditorDebugger::setup_session(int p_session_id) {
101 Ref<EditorDebuggerSession> session = get_session(p_session_id);
102 ERR_FAIL_COND(session.is_null());
103 EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler);
104 profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
105 profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request));
106 profiler->set_name(TTR("Network Profiler"));
107 session->add_session_tab(profiler);
108 profilers[p_session_id] = profiler;
109}
110
111/// MultiplayerEditorPlugin
112
113MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
114 repl_editor = memnew(ReplicationEditor);
115 button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
116 button->hide();
117 repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned));
118 debugger.instantiate();
119 debugger->connect("open_request", callable_mp(this, &MultiplayerEditorPlugin::_open_request));
120}
121
122void MultiplayerEditorPlugin::_open_request(const String &p_path) {
123 EditorInterface::get_singleton()->open_scene_from_path(p_path);
124}
125
126void MultiplayerEditorPlugin::_notification(int p_what) {
127 switch (p_what) {
128 case NOTIFICATION_ENTER_TREE: {
129 get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed));
130 add_debugger_plugin(debugger);
131 } break;
132 case NOTIFICATION_EXIT_TREE: {
133 remove_debugger_plugin(debugger);
134 }
135 }
136}
137
138void MultiplayerEditorPlugin::_node_removed(Node *p_node) {
139 if (p_node && p_node == repl_editor->get_current()) {
140 repl_editor->edit(nullptr);
141 if (repl_editor->is_visible_in_tree()) {
142 EditorNode::get_singleton()->hide_bottom_panel();
143 }
144 button->hide();
145 repl_editor->get_pin()->set_pressed(false);
146 }
147}
148
149void MultiplayerEditorPlugin::_pinned() {
150 if (!repl_editor->get_pin()->is_pressed()) {
151 if (repl_editor->is_visible_in_tree()) {
152 EditorNode::get_singleton()->hide_bottom_panel();
153 }
154 button->hide();
155 }
156}
157
158void MultiplayerEditorPlugin::edit(Object *p_object) {
159 repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
160}
161
162bool MultiplayerEditorPlugin::handles(Object *p_object) const {
163 return p_object->is_class("MultiplayerSynchronizer");
164}
165
166void MultiplayerEditorPlugin::make_visible(bool p_visible) {
167 if (p_visible) {
168 button->show();
169 EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor);
170 } else if (!repl_editor->get_pin()->is_pressed()) {
171 if (repl_editor->is_visible_in_tree()) {
172 EditorNode::get_singleton()->hide_bottom_panel();
173 }
174 button->hide();
175 }
176}
177