Skip to content
Draft
22 changes: 22 additions & 0 deletions core/object/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
#include "core/string/translation_server.h"
#include "core/variant/typed_array.h"

// Static member initialization for signal emission callback
std::atomic<Object::SignalEmissionCallback> Object::signal_emission_callback = nullptr;
std::atomic<bool> Object::signal_emission_callback_enabled = false;

#ifdef DEBUG_ENABLED

struct _ObjectDebugLock {
Expand Down Expand Up @@ -1225,6 +1229,12 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
uint32_t *slot_flags = slot_flags_stack;
uint32_t slot_count = 0;

// STEP 1: Signal emission hook - Declare variables for callback capture
// This allows editor tools like the Signal Viewer to track signal emissions globally
// We capture the callback while holding the lock, but call it after releasing to prevent deadlocks
Object::SignalEmissionCallback emission_callback = nullptr;
bool should_call_callback = false;

{
OBJ_SIGNAL_LOCK

Expand All @@ -1239,6 +1249,12 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
return ERR_UNAVAILABLE;
}

// Capture callback state under lock (thread-safe atomic load)
should_call_callback = signal_emission_callback_enabled.load(std::memory_order_acquire) && signal_emission_callback.load(std::memory_order_acquire);
if (should_call_callback) {
emission_callback = signal_emission_callback.load(std::memory_order_acquire);
}

if (s->slot_map.size() > MAX_SLOTS_ON_STACK) {
slot_callables = (Callable *)memalloc(sizeof(Callable) * s->slot_map.size());
slot_flags = (uint32_t *)memalloc(sizeof(uint32_t) * s->slot_map.size());
Expand Down Expand Up @@ -1269,6 +1285,12 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
}
}

// STEP 1: Call the emission callback outside the lock to prevent deadlocks
// if the callback re-enters signal code paths
if (should_call_callback && emission_callback) {
emission_callback(this, p_name, p_args, p_argcount);
}

OBJ_DEBUG_LOCK

// If this is a ref-counted object, prevent it from being destroyed during signal
Expand Down
25 changes: 24 additions & 1 deletion core/object/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,30 @@ class Object {
public:
static constexpr bool _class_is_enabled = true;

void notify_property_list_changed(); ///< Scripts may add variables, so refresh is desired
// Signal tracking callback for editor tools (e.g., Signal Viewer)
// This callback is invoked whenever a signal is emitted, allowing tools to track signal activity
// The callback is only active when explicitly registered to minimize performance impact
typedef void (*SignalEmissionCallback)(Object *p_emitter, const StringName &p_signal, const Variant **p_args, int p_argcount);

private:
static std::atomic<SignalEmissionCallback> signal_emission_callback;
static std::atomic<bool> signal_emission_callback_enabled;

public:
static void set_signal_emission_callback(SignalEmissionCallback p_callback) {
signal_emission_callback.store(p_callback, std::memory_order_release);
signal_emission_callback_enabled.store(p_callback != nullptr, std::memory_order_release);
}

static SignalEmissionCallback get_signal_emission_callback() {
return signal_emission_callback.load(std::memory_order_acquire);
}

static bool is_signal_emission_callback_enabled() {
return signal_emission_callback_enabled.load(std::memory_order_acquire);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void notify_property_list_changed();

static void *get_class_ptr_static() {
static int ptr;
Expand Down
32 changes: 32 additions & 0 deletions doc/classes/SignalViewerRuntime.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="SignalViewerRuntime" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
<method name="is_tracking_enabled" qualifiers="const">
<return type="bool" />
<description>
</description>
</method>
<method name="send_node_signal_data">
<return type="void" />
<param index="0" name="node_id" type="int" />
<description>
</description>
</method>
<method name="start_tracking">
<return type="void" />
<description>
</description>
</method>
<method name="stop_tracking">
<return type="void" />
<description>
</description>
</method>
</methods>
</class>
2 changes: 1 addition & 1 deletion editor/debugger/debug_adapter/debug_adapter_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) con
String msg = args["message"];
Array data = args["data"];

EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
EditorDebuggerNode::get_singleton()->get_default_debugger()->put_msg(msg, data);

return prepare_success_response(p_params);
}
Expand Down
Loading
Loading