1/**************************************************************************/
2/* multiplayer_debugger.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_debugger.h"
32
33#include "multiplayer_synchronizer.h"
34#include "scene_replication_config.h"
35
36#include "core/debugger/engine_debugger.h"
37#include "scene/main/node.h"
38
39List<Ref<EngineProfiler>> multiplayer_profilers;
40
41void MultiplayerDebugger::initialize() {
42 Ref<BandwidthProfiler> bandwidth;
43 bandwidth.instantiate();
44 bandwidth->bind("multiplayer:bandwidth");
45 multiplayer_profilers.push_back(bandwidth);
46
47 Ref<RPCProfiler> rpc_profiler;
48 rpc_profiler.instantiate();
49 rpc_profiler->bind("multiplayer:rpc");
50 multiplayer_profilers.push_back(rpc_profiler);
51
52 Ref<ReplicationProfiler> replication_profiler;
53 replication_profiler.instantiate();
54 replication_profiler->bind("multiplayer:replication");
55 multiplayer_profilers.push_back(replication_profiler);
56
57 EngineDebugger::register_message_capture("multiplayer", EngineDebugger::Capture(nullptr, &_capture));
58}
59
60void MultiplayerDebugger::deinitialize() {
61 multiplayer_profilers.clear();
62}
63
64Error MultiplayerDebugger::_capture(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
65 if (p_msg == "cache") {
66 Array out;
67 for (int i = 0; i < p_args.size(); i++) {
68 ObjectID id = p_args[i].operator ObjectID();
69 Object *obj = ObjectDB::get_instance(id);
70 ERR_CONTINUE(!obj);
71 if (Object::cast_to<SceneReplicationConfig>(obj)) {
72 out.push_back(id);
73 out.push_back(obj->get_class());
74 out.push_back(((SceneReplicationConfig *)obj)->get_path());
75 } else if (Object::cast_to<Node>(obj)) {
76 out.push_back(id);
77 out.push_back(obj->get_class());
78 out.push_back(String(((Node *)obj)->get_path()));
79 } else {
80 ERR_FAIL_V(FAILED);
81 }
82 }
83 EngineDebugger::get_singleton()->send_message("multiplayer:cache", out);
84 return OK;
85 }
86 ERR_FAIL_V(FAILED);
87}
88
89// BandwidthProfiler
90
91int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
92 ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
93 int total_bandwidth = 0;
94
95 uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
96 uint64_t final_timestamp = timestamp - 1000;
97
98 int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
99
100 while (i != p_pointer && p_buffer[i].packet_size > 0) {
101 if (p_buffer[i].timestamp < final_timestamp) {
102 return total_bandwidth;
103 }
104 total_bandwidth += p_buffer[i].packet_size;
105 i = (i + p_buffer.size() - 1) % p_buffer.size();
106 }
107
108 ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
109 return total_bandwidth;
110}
111
112void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) {
113 if (!p_enable) {
114 bandwidth_in.clear();
115 bandwidth_out.clear();
116 } else {
117 bandwidth_in_ptr = 0;
118 bandwidth_in.resize(16384); // ~128kB
119 for (int i = 0; i < bandwidth_in.size(); ++i) {
120 bandwidth_in.write[i].packet_size = -1;
121 }
122 bandwidth_out_ptr = 0;
123 bandwidth_out.resize(16384); // ~128kB
124 for (int i = 0; i < bandwidth_out.size(); ++i) {
125 bandwidth_out.write[i].packet_size = -1;
126 }
127 }
128}
129
130void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) {
131 ERR_FAIL_COND(p_data.size() < 3);
132 const String inout = p_data[0];
133 int time = p_data[1];
134 int size = p_data[2];
135 if (inout == "in") {
136 bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
137 bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
138 bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
139 } else if (inout == "out") {
140 bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
141 bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
142 bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
143 }
144}
145
146void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
147 uint64_t pt = OS::get_singleton()->get_ticks_msec();
148 if (pt - last_bandwidth_time > 200) {
149 last_bandwidth_time = pt;
150 int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
151 int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);
152
153 Array arr;
154 arr.push_back(incoming_bandwidth);
155 arr.push_back(outgoing_bandwidth);
156 EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
157 }
158}
159
160// RPCProfiler
161
162Array MultiplayerDebugger::RPCFrame::serialize() {
163 Array arr;
164 arr.push_back(infos.size() * 6);
165 for (int i = 0; i < infos.size(); ++i) {
166 arr.push_back(uint64_t(infos[i].node));
167 arr.push_back(infos[i].node_path);
168 arr.push_back(infos[i].incoming_rpc);
169 arr.push_back(infos[i].incoming_size);
170 arr.push_back(infos[i].outgoing_rpc);
171 arr.push_back(infos[i].outgoing_size);
172 }
173 return arr;
174}
175
176bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
177 ERR_FAIL_COND_V(p_arr.size() < 1, false);
178 uint32_t size = p_arr[0];
179 ERR_FAIL_COND_V(size % 6, false);
180 ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
181 infos.resize(size / 6);
182 int idx = 1;
183 for (uint32_t i = 0; i < size / 6; i++) {
184 infos.write[i].node = uint64_t(p_arr[idx]);
185 infos.write[i].node_path = p_arr[idx + 1];
186 infos.write[i].incoming_rpc = p_arr[idx + 2];
187 infos.write[i].incoming_size = p_arr[idx + 3];
188 infos.write[i].outgoing_rpc = p_arr[idx + 4];
189 infos.write[i].outgoing_size = p_arr[idx + 5];
190 idx += 6;
191 }
192 return true;
193}
194
195void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) {
196 if (rpc_node_data.has(p_node)) {
197 return;
198 }
199 rpc_node_data.insert(p_node, RPCNodeInfo());
200 rpc_node_data[p_node].node = p_node;
201 rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
202}
203
204void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) {
205 rpc_node_data.clear();
206}
207
208void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) {
209 ERR_FAIL_COND(p_data.size() != 3);
210 const String what = p_data[0];
211 const ObjectID id = p_data[1];
212 const int size = p_data[2];
213 init_node(id);
214 RPCNodeInfo &info = rpc_node_data[id];
215 if (what == "rpc_in") {
216 info.incoming_rpc++;
217 info.incoming_size += size;
218 } else if (what == "rpc_out") {
219 info.outgoing_rpc++;
220 info.outgoing_size += size;
221 }
222}
223
224void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
225 uint64_t pt = OS::get_singleton()->get_ticks_msec();
226 if (pt - last_profile_time > 100) {
227 last_profile_time = pt;
228 RPCFrame frame;
229 for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) {
230 frame.infos.push_back(E.value);
231 }
232 rpc_node_data.clear();
233 EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
234 }
235}
236
237// ReplicationProfiler
238
239MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) {
240 ERR_FAIL_COND(!p_sync);
241 synchronizer = p_sync->get_instance_id();
242 if (p_sync->get_replication_config().is_valid()) {
243 config = p_sync->get_replication_config()->get_instance_id();
244 }
245 if (p_sync->get_root_node()) {
246 root_node = p_sync->get_root_node()->get_instance_id();
247 }
248}
249
250void MultiplayerDebugger::SyncInfo::write_to_array(Array &r_arr) const {
251 r_arr.push_back(synchronizer);
252 r_arr.push_back(config);
253 r_arr.push_back(root_node);
254 r_arr.push_back(incoming_syncs);
255 r_arr.push_back(incoming_size);
256 r_arr.push_back(outgoing_syncs);
257 r_arr.push_back(outgoing_size);
258}
259
260bool MultiplayerDebugger::SyncInfo::read_from_array(const Array &p_arr, int p_offset) {
261 ERR_FAIL_COND_V(p_arr.size() - p_offset < 7, false);
262 synchronizer = int64_t(p_arr[p_offset]);
263 config = int64_t(p_arr[p_offset + 1]);
264 root_node = int64_t(p_arr[p_offset + 2]);
265 incoming_syncs = p_arr[p_offset + 3];
266 incoming_size = p_arr[p_offset + 4];
267 outgoing_syncs = p_arr[p_offset + 5];
268 outgoing_size = p_arr[p_offset + 6];
269 return true;
270}
271
272Array MultiplayerDebugger::ReplicationFrame::serialize() {
273 Array arr;
274 arr.push_back(infos.size() * 7);
275 for (const KeyValue<ObjectID, SyncInfo> &E : infos) {
276 E.value.write_to_array(arr);
277 }
278 return arr;
279}
280
281bool MultiplayerDebugger::ReplicationFrame::deserialize(const Array &p_arr) {
282 ERR_FAIL_COND_V(p_arr.size() < 1, false);
283 uint32_t size = p_arr[0];
284 ERR_FAIL_COND_V(size % 7, false);
285 ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
286 int idx = 1;
287 for (uint32_t i = 0; i < size / 7; i++) {
288 SyncInfo info;
289 if (!info.read_from_array(p_arr, idx)) {
290 return false;
291 }
292 infos[info.synchronizer] = info;
293 idx += 7;
294 }
295 return true;
296}
297
298void MultiplayerDebugger::ReplicationProfiler::toggle(bool p_enable, const Array &p_opts) {
299 sync_data.clear();
300}
301
302void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) {
303 ERR_FAIL_COND(p_data.size() != 3);
304 const String what = p_data[0];
305 const ObjectID id = p_data[1];
306 const uint64_t size = p_data[2];
307 MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id));
308 ERR_FAIL_COND(!sync);
309 if (!sync_data.has(id)) {
310 sync_data[id] = SyncInfo(sync);
311 }
312 SyncInfo &info = sync_data[id];
313 if (what == "sync_in") {
314 info.incoming_syncs++;
315 info.incoming_size += size;
316 } else if (what == "sync_out") {
317 info.outgoing_syncs++;
318 info.outgoing_size += size;
319 }
320}
321
322void MultiplayerDebugger::ReplicationProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
323 uint64_t pt = OS::get_singleton()->get_ticks_msec();
324 if (pt - last_profile_time > 100) {
325 last_profile_time = pt;
326 ReplicationFrame frame;
327 for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) {
328 frame.infos[E.key] = E.value;
329 }
330 sync_data.clear();
331 EngineDebugger::get_singleton()->send_message("multiplayer:syncs", frame.serialize());
332 }
333}
334