Skip to content

Commit

Permalink
Merge pull request #66938 from Faless/mp/4.x_debugger_split
Browse files Browse the repository at this point in the history
[Editor] Better expose editor debugger plugins, use it in the multiplayer module.
  • Loading branch information
akien-mga committed Nov 14, 2022
2 parents 471c42e + 67265d1 commit 2f573f2
Show file tree
Hide file tree
Showing 25 changed files with 894 additions and 499 deletions.
87 changes: 0 additions & 87 deletions core/debugger/remote_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,89 +39,6 @@
#include "core/object/script_language.h"
#include "core/os/os.h"

class RemoteDebugger::MultiplayerProfiler : public EngineProfiler {
struct BandwidthFrame {
uint32_t timestamp;
int packet_size;
};

int bandwidth_in_ptr = 0;
Vector<BandwidthFrame> bandwidth_in;
int bandwidth_out_ptr = 0;
Vector<BandwidthFrame> bandwidth_out;
uint64_t last_bandwidth_time = 0;

int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
int total_bandwidth = 0;

uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
uint64_t final_timestamp = timestamp - 1000;

int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();

while (i != p_pointer && p_buffer[i].packet_size > 0) {
if (p_buffer[i].timestamp < final_timestamp) {
return total_bandwidth;
}
total_bandwidth += p_buffer[i].packet_size;
i = (i + p_buffer.size() - 1) % p_buffer.size();
}

ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
return total_bandwidth;
}

public:
void toggle(bool p_enable, const Array &p_opts) {
if (!p_enable) {
bandwidth_in.clear();
bandwidth_out.clear();
} else {
bandwidth_in_ptr = 0;
bandwidth_in.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_in.size(); ++i) {
bandwidth_in.write[i].packet_size = -1;
}
bandwidth_out_ptr = 0;
bandwidth_out.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_out.size(); ++i) {
bandwidth_out.write[i].packet_size = -1;
}
}
}

void add(const Array &p_data) {
ERR_FAIL_COND(p_data.size() < 3);
const String inout = p_data[0];
int time = p_data[1];
int size = p_data[2];
if (inout == "in") {
bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
} else if (inout == "out") {
bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
}
}

void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
uint64_t pt = OS::get_singleton()->get_ticks_msec();
if (pt - last_bandwidth_time > 200) {
last_bandwidth_time = pt;
int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);

Array arr;
arr.push_back(incoming_bandwidth);
arr.push_back(outgoing_bandwidth);
EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
}
}
};

class RemoteDebugger::PerformanceProfiler : public EngineProfiler {
Object *performance = nullptr;
int last_perf_time = 0;
Expand Down Expand Up @@ -659,10 +576,6 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");
max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second");

// Multiplayer Profiler
multiplayer_profiler.instantiate();
multiplayer_profiler->bind("multiplayer");

// Performance Profiler
Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
if (perf) {
Expand Down
2 changes: 0 additions & 2 deletions core/debugger/remote_debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ class RemoteDebugger : public EngineDebugger {
private:
typedef DebuggerMarshalls::OutputError ErrorMessage;

class MultiplayerProfiler;
class PerformanceProfiler;

Ref<MultiplayerProfiler> multiplayer_profiler;
Ref<PerformanceProfiler> performance_profiler;

Ref<RemoteDebuggerPeer> peer;
Expand Down
116 changes: 58 additions & 58 deletions doc/classes/EditorDebuggerPlugin.xml
Original file line number Diff line number Diff line change
@@ -1,88 +1,88 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorDebuggerPlugin" inherits="Control" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="EditorDebuggerPlugin" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A base class to implement debugger plugins.
</brief_description>
<description>
[EditorDebuggerPlugin] provides functions related to the editor side of the debugger.
You don't need to instantiate this class; that is automatically handled by the debugger. [Control] nodes can be added as child nodes to provide a GUI for the plugin.
Do not free or reparent this node, otherwise it becomes unusable.
To use [EditorDebuggerPlugin], register it using the [method EditorPlugin.add_debugger_plugin] method first.
To interact with the debugger, an instance of this class must be added to the editor via [method EditorPlugin.add_debugger_plugin].
Once added, the [method _setup_session] callback will be called for every [EditorDebuggerSession] available to the plugin, and when new ones are created (the sessions may be inactive during this stage).
You can retrieve the available [EditorDebuggerSession]s via [method get_sessions] or get a specific one via [method get_session].
[codeblocks]
[gdscript]
@tool
extends EditorPlugin

class ExampleEditorDebugger extends EditorDebuggerPlugin:

func _has_capture(prefix):
# Return true if you wish to handle message with this prefix.
return prefix == "my_plugin"

func _capture(message, data, session_id):
if message == "my_plugin:ping":
get_session(session_id).send_message("my_plugin:echo", data)

func _setup_session(session_id):
# Add a new tab in the debugger session UI containing a label.
var label = Label.new()
label.name = "Example plugin"
label.text = "Example plugin"
var session = get_session(session_id)
# Listens to the session started and stopped signals.
session.started.connect(func (): print("Session started"))
session.stopped.connect(func (): print("Session stopped"))
session.add_session_tab(label)

var debugger = ExampleEditorDebugger.new()

func _enter_tree():
add_debugger_plugin(debugger)

func _exit_tree():
remove_debugger_plugin(debugger)
[/gdscript]
[/codeblocks]
</description>
<tutorials>
</tutorials>
<methods>
<method name="has_capture">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if a message capture with given name is present otherwise [code]false[/code].
</description>
</method>
<method name="is_breaked">
<return type="bool" />
<description>
Returns [code]true[/code] if the game is in break state otherwise [code]false[/code].
</description>
</method>
<method name="is_debuggable">
<method name="_capture" qualifiers="virtual">
<return type="bool" />
<param index="0" name="message" type="String" />
<param index="1" name="data" type="Array" />
<param index="2" name="session_id" type="int" />
<description>
Returns [code]true[/code] if the game can be debugged otherwise [code]false[/code].
Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the message (which you can retrieve via [method get_session]).
</description>
</method>
<method name="is_session_active">
<method name="_has_capture" qualifiers="virtual const">
<return type="bool" />
<param index="0" name="capture" type="String" />
<description>
Returns [code]true[/code] if there is an instance of the game running with the attached debugger otherwise [code]false[/code].
Override this method to enable receiving messages from the debugger. If [param capture] is "my_message" then messages starting with "my_message:" will be passes to the [method _capture] method.
</description>
</method>
<method name="register_message_capture">
<method name="_setup_session" qualifiers="virtual">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="callable" type="Callable" />
<param index="0" name="session_id" type="int" />
<description>
Registers a message capture with given [param name]. If [param name] is "my_message" then messages starting with "my_message:" will be called with the given callable.
Callable must accept a message string and a data array as argument. If the message and data are valid then callable must return [code]true[/code] otherwise [code]false[/code].
Override this method to be notified whenever a new [EditorDebuggerSession] is created (the session may be inactive during this stage).
</description>
</method>
<method name="send_message">
<return type="void" />
<param index="0" name="message" type="String" />
<param index="1" name="data" type="Array" />
<method name="get_session">
<return type="EditorDebuggerSession" />
<param index="0" name="id" type="int" />
<description>
Sends a message with given [param message] and [param data] array.
Returns the [EditorDebuggerSession] with the given [param id].
</description>
</method>
<method name="unregister_message_capture">
<return type="void" />
<param index="0" name="name" type="StringName" />
<method name="get_sessions">
<return type="Array" />
<description>
Unregisters the message capture with given name.
Returns an array of [EditorDebuggerSession] currently available to this debugger plugin.
Note: Not sessions in the array may be inactive, check their state via [method EditorDebuggerSession.is_active]
</description>
</method>
</methods>
<signals>
<signal name="breaked">
<param index="0" name="can_debug" type="bool" />
<description>
Emitted when the game enters a break state.
</description>
</signal>
<signal name="continued">
<description>
Emitted when the game exists a break state.
</description>
</signal>
<signal name="started">
<description>
Emitted when the debugging starts.
</description>
</signal>
<signal name="stopped">
<description>
Emitted when the debugging stops.
</description>
</signal>
</signals>
</class>
86 changes: 86 additions & 0 deletions doc/classes/EditorDebuggerSession.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorDebuggerSession" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A class to interact with the editor debugger.
</brief_description>
<description>
This class cannot be directly instantiated and must be retrieved via a [EditorDebuggerPlugin].
You can add tabs to the session UI via [method add_session_tab], send messages via [method send_message], and toggle [EngineProfiler]s via [method toggle_profiler].
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_session_tab">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Adds the given [param control] to the debug session UI in the debugger bottom panel.
</description>
</method>
<method name="is_active">
<return type="bool" />
<description>
Returns [code]true[/code] if the debug session is currently attached to a remote instance.
</description>
</method>
<method name="is_breaked">
<return type="bool" />
<description>
Returns [code]true[/code] if the attached remote instance is currently in the debug loop.
</description>
</method>
<method name="is_debuggable">
<return type="bool" />
<description>
Returns [code]true[/code] if the attached remote instance can be debugged.
</description>
</method>
<method name="remove_session_tab">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Removes the given [param control] from the debug session UI in the debugger bottom panel.
</description>
</method>
<method name="send_message">
<return type="void" />
<param index="0" name="message" type="String" />
<param index="1" name="data" type="Array" default="[]" />
<description>
Sends the given [param message] to the attached remote instance, optionally passing additionally [param data]. See [EngineDebugger] for how to retrieve those messages.
</description>
</method>
<method name="toggle_profiler">
<return type="void" />
<param index="0" name="profiler" type="String" />
<param index="1" name="enable" type="bool" />
<param index="2" name="data" type="Array" default="[]" />
<description>
Toggle the given [param profiler] on the attached remote instance, optionally passing additionally [param data]. See [EngineProfiler] for more details.
</description>
</method>
</methods>
<signals>
<signal name="breaked">
<param index="0" name="can_debug" type="bool" />
<description>
Emitted when the attached remote instance enters a break state. If [param can_debug] is [code]true[/code], the remote instance will enter the debug loop.
</description>
</signal>
<signal name="continued">
<description>
Emitted when the attached remote instance exits a break state.
</description>
</signal>
<signal name="started">
<description>
Emitted when a remote instance is attached to this session (i.e. the session becomes active).
</description>
</signal>
<signal name="stopped">
<description>
Emitted when a remote instance is detached from this session (i.e. the session becomes inactive).
</description>
</signal>
</signals>
</class>
4 changes: 2 additions & 2 deletions doc/classes/EditorPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@
</method>
<method name="add_debugger_plugin">
<return type="void" />
<param index="0" name="script" type="Script" />
<param index="0" name="script" type="EditorDebuggerPlugin" />
<description>
Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin].
</description>
Expand Down Expand Up @@ -599,7 +599,7 @@
</method>
<method name="remove_debugger_plugin">
<return type="void" />
<param index="0" name="script" type="Script" />
<param index="0" name="script" type="EditorDebuggerPlugin" />
<description>
Removes the debugger plugin with given script from the Debugger.
</description>
Expand Down
Loading

0 comments on commit 2f573f2

Please sign in to comment.