Skip to content

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

Description

@greenfox1505

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.

use godot::prelude::*;

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

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

#[godot_api]
impl DropEmitter {
    #[func]
    fn make_with_label(label: GString) -> Gd<Self> {
        Gd::from_init_fn(|base| Self { label, base })
    }
    #[signal]
    fn trigger_watchers();
    #[func]
    fn emit_watcher(&mut self, watcher: GString) -> Gd<DropWatcher> {
        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<RefCounted>,
}

#[godot_api]
impl IRefCounted for DropWatcher {}

#[godot_api]
impl DropWatcher {
    #[func]
    fn make_with_label(label: GString) -> Gd<Self> {
        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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions