Failing to connect to singleton properly

Godot Version

4.2

Question

Ok, here’s the scenario. I recently wrote a temperature system for my game. I got it working to a certain extent, but I wanted the system to be easier to use, for things in my game to be able to be both temperature sources and receivers, and for temperature objects to not need to be created at the time the scene tree was opened and so on. I decided to create a temperature singleton that would receive data about temperature reactants and then, upon request, pass them along to temperature reactants.

Currently my temperature reactants are only partially connecting to the singleton. I’m not sure why, but some of my signals are failing to connect.

my exact two errors are:

temperature.gd:50 @ _connect_to_global(): In Object of type 'Node3D': Attempt to connect nonexistent signal 'provide_data' to callable 'Node3D(temperature.gd)::global_recieved

and

temperature.gd:51 @ _connect_to_global(): Error calling from signal 'register+heat' to callable: 'Node3d(Globals.gd)::register_heat': Cannot convert argument 1 from Object to Callable

The second one makes even sense to me, because at no point have a passed the type Callable into the signal constructor.

By using print() as proof of execution of some of my functions, I have confirmed that the call to the group “Unregistered Heat” works fine.

the code for Globals.gd (the singleton for temperature)

extends Node3D

var request_timer = null
var heat_objects = []
var heat_recievers = []
var heat_sources = []
signal provide_data(target, heat_recievers, heat_sources)

func _ready() -> void:
	request_timer = Timer.new()
	add_child(request_timer)
	request_timer.set_wait_time(.5 + (randf() * .1))
	request_timer.timeout.connect(search_for_heat.bind())
	request_timer.start()

func information_requested(target : Callable, heat_recievers_requested : bool, heat_sources_requested: bool):
	if  heat_recievers_requested and heat_sources_requested:
		provide_data.emit(target, heat_recievers, heat_sources)
	elif heat_recievers_requested:
		provide_data.emit(target, heat_recievers, null)
	elif heat_sources_requested:
		provide_data.emit(target, null, heat_sources)

func register_heat(heat : Callable, is_heat_reciever : bool, is_heat_source : bool):
	heat_objects.append(heat)
	if is_heat_reciever:
		heat_recievers.append(heat)
	if is_heat_source:
		heat_sources.append(heat)

func deregister_heat(heat : Callable):
	heat_objects.erase(heat)
	heat_recievers.erase(heat)
	heat_sources.erase(heat)

func search_for_heat():
	print("Searching started")
	request_timer.set_wait_time(.5 + (randf() * .1))
	for i in get_tree().get_nodes_in_group("Unconnected_Heat"):
		i.connect("register_heat", register_heat)
	get_tree().call_group("Unconnected_Heat", "_connect_to_global")
	print("Searching finished")

and the code for the temperature reactants is here

extends Node3D
@export_group("Tempurature Reactivity")
@export_subgroup("Temperature Source Settings")
@export var Source := true
@export var heat_radius = 1.0
@export var decay_per_meter = 1.1
@export var heat_strength = 1.0

@export_subgroup("Temperature Reciever Settings")
@export var Reciever := true
@export var freezable := false
@export var burnable := false
@export var heat_tolerance := -100.0
@export var cold_tolerance := 100.0

#Connection to the singleton
signal register_heat(broadcaster, is_heat_reciever, is_heat_source)
signal deregister_heat(broadcater)

var origin = Vector3(0,0,0)
signal modify_heat(heat_modifier, target)
@onready var heat_recievers := []
var heat_sources := []
var heat_modifier = 0.0
var timer = null
var connection_timer = null
var size := Vector3(0,0,0)
var coldness = 0
var connected_to_global = false
enum reported {COLD, HEAT, DEFAULT}
signal temperature_tolerance(exceeded_value) 
signal temperature_report(coldness)


# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	add_to_group("Unconnected_Heat")
	origin = get_parent().get_position()
	size = get_parent().get_scale()
	if Source:
		initialize_source()
	if Reciever:
		initialize_reciever()

#global connections
func _connect_to_global():
	remove_from_group("Unconnected_Heat")
	print(get_parent(), " connected to global")
	$".".connect("provide_data", global_recieved)
	register_heat.emit(get_parent(), Reciever, Source)
func global_recieved(target, recievers, sources):
	if target == self.get_parent():
		heat_recievers = recievers
		heat_sources = sources


#Initialization functions
func initialize_source():
	get_parent().add_to_group("Heat Sources")
	$Area3D/outer_layer.shape.set_radius(heat_radius)
	$center.position = origin
	$center.scale = size
	timer = Timer.new()
	add_child(timer)
	timer.set_wait_time(1.0 + (randf() * .01))
	timer.timeout.connect(distribute_heat.bind())
	timer.start()
func initialize_reciever():
	get_parent().add_to_group("Heat Reciever")
	for i in get_tree().get_nodes_in_group("Heat Sources"):
		i.find_child("Temperature Reactivity").connect("modify_heat", modify_own_heat)
	set_scale(size)
	set_position(origin)


#Modifier functions
func _on_area_3d_area_entered(area: Area3D) -> void:
	if area.get_parent().is_in_group("Heat Reciever"):
		heat_recievers.append(area.get_parent_node_3d())
func _on_area_3d_area_exited(area: Area3D) -> void:
	if area.get_parent().is_in_group("Heat Reciever"):
		heat_recievers.remove_at(heat_recievers.find(area.get_parent_node_3d()))
func distribute_heat():
		if heat_recievers.size()>0:
			for i in heat_recievers:
				heat_modifier = heat_strength / abs(i.get_position().distance_to($center.get_position()) * decay_per_meter) 
				modify_heat.emit(heat_modifier, i)

#Reciever functions
func modify_own_heat(heat_modifier: Variant, target: Variant) -> void:
	if target == get_parent():
		coldness -= heat_modifier
		temperature_report.emit(coldness)
		evaluate_heat()
func evaluate_heat():
	if (coldness <= cold_tolerance and freezable):
		temperature_tolerance.emit(0)
	elif (coldness>= heat_tolerance and burnable):
		temperature_tolerance.emit(1)
	else:
		temperature_tolerance.emit(2)

#getters and setters
func set_reciever(new_status: bool):
	Reciever = new_status
func set_source(new_status: bool):
	Source = new_status
func set_heat_tolerance(new_heat: float):
	heat_tolerance = new_heat
func set_cold_tolerance(new_cold: float):
	cold_tolerance = new_cold
func get_heat_recievers():
	return heat_recievers
func get_cold_tolerance() -> float:
	return cold_tolerance
func get_heat_tolerance() -> float:
	return heat_tolerance


func _on_tree_exiting() -> void:
	deregister_heat.emit(get_parent())
	connected_to_global = false

Any help would be much appreciated :sob:. I’ve tried to make my code pretty readable; however, if you need me to elaborate, just let me know. btw, I should mention that I have already double-checked that the singleton is autoloaded.

1 Like

For the first error, “provide_data” is not a signal on the temperature reactants, but the global

$"." is a slow version of self, referring to the current script. This is usually implicit so these two lines are the same and the cause of your error.

$".".connect("provide_data", global_recieved)
connect("provide_data", global_recieved)

You want to connect to your autoload/singleton which I will assume is named Globals

Globals.provide_data.connect(global_recieved)

Your second error is just a type mis-match

func information_requested(target : Callable, heat_recievers_requested : bool, heat_sources_requested: bool):

The target isn’t a Callable but a Object or even Node3D. Callables are functions, not nodes.