Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions core/object/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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 @@ -1223,6 +1227,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 @@ -1237,6 +1247,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 @@ -1267,6 +1283,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
23 changes: 23 additions & 0 deletions core/object/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,29 @@ class Object {
public:
static constexpr bool _class_is_enabled = true;

// 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() {
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 @@ -514,7 +514,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