godot-rust / godot-rust/gdext

Signals hold a RefCount, which doesn't match the GDScript behaviour

Open
#1,665 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
5.2k
Forks
312
Avg merge
11h 10m
Merged PRs (30d)
10

Description

I have an `Emitter` that emits `Watchers`. The `Watcher` is `RefCounted`. When it is dropped, they are expected to untangle and remove themselves from the emitter, through a Drop trait. It's not working because the `Emitter` seems to be holding on to a reference to it. As far as I can tell, this only happens when the `Emitter` has a signal connection to the `Watcher`. Without the signal, this doesn't happen.

This does not match a GDScript equivalent. When a the last RefCounted reference is lost (or a Node is freed, etc), a signal doesn't hold it open. I have some sample code of what this looks like.

```rust
use godot::prelude::*;

#[derive(GodotClass)]
#[class(base=RefCounted,no_init)]

struct DropEmitter {
#[export]
label: GString,
base: Base,
}
#[godot_api]
impl IRefCounted for DropEmitter {}

#[godot_api]
impl DropEmitter {
#[func]
fn make_with_label(label: GString) -> Gd {
Gd::from_init_fn(|base| Self { label, base })
}
#[signal]
fn trigger_watchers();
#[func]
fn emit_watcher(&mut self, watcher: GString) -> Gd {
let watch_label = format!("{}:{}", self.label, watcher);
let t = DropWatcher::make_with_label(GString::from(&watch_label));
self.signals()
.trigger_watchers()
.connect_other(&t, DropWatcher::emit_test_signal);
t
}
}

#[derive(GodotClass)]
#[class(base=RefCounted,no_init)]

struct DropWatcher {
label: GString,
base: Base,
}

#[godot_api]
impl IRefCounted for DropWatcher {}

#[godot_api]
impl DropWatcher {
#[func]
fn make_with_label(label: GString) -> Gd {
Gd::from_init_fn(|base| Self { label, base })
}
#[func]
fn emit_test_signal(&mut self) {
let l = self.label.clone();
self.signals().test_signal().emit(&l);
}

#[signal]
fn test_signal(label: GString);
}

impl Drop for DropWatcher {
fn drop(&mut self) {
godot_print!("{} dropped!", self.label);
}
}

```

GDScript Testing

```gdscript
extends Node

class Emitter:
signal trigger
func make_watcher(label:String)->DropWatcher:
var output = DropWatcher.make_with_label(label)
trigger.connect(output.emit_test_signal)
return output

var drop_emitter = DropEmitter.make_with_label("test emitter")

func _ready() -> void:
print("==== test: drop watcher_inside ====")
var watcher_inside := drop_emitter.emit_watcher("watcher inside emitter")
watcher_inside = null

print("==== test: drop pure_watcher ====")
var pure_watcher := DropWatcher.make_with_label("pure watcher")
pure_watcher = null

print("==== test: drop side_watcher ====")
var side_watcher := DropWatcher.make_with_label("side attached watcher")
drop_emitter.trigger_watchers.connect(side_watcher.emit_test_signal)
side_watcher = null

print("==== test: drop emitter ====")
drop_emitter = null

print("==== test: try gd script version ====`")
var emitter = Emitter.new()
var gd_script_watcher = emitter.make_watcher("gd script watcher")

print("==== test: drop gd watcher ====`")
gd_script_watcher = null

print("==== test: drop gd emitter ====`")
emitter = null
pass

```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the Rust and GDScript examples in the issue, focusing on signal connections to RefCounted watchers and when their Drop behavior runs. Compare the watcher lifetime after its last external reference is cleared; done when Rust signal connections no longer keep the watcher alive, matching the GDScript behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.