Signal not emited (anymore)

Godot Version

4.3

Question

` Goal of my code:
I created a Dialogue system that relies on quest progression. Basically, when you talk to an NPC, a menu appears with dialogue options: each option represent a dialogue that you can have with said NPC regarding one of the current active objectives.

The components involved:
I have a QuestManager that keeps track of the active and finished quests.
I have a Signal Bus singleton that serve as a general place I create signals destined to be emitted and connected to by multiple components (like new_state, interacted etc.)
And I have the DialogueManager proper, that handles…well dialogue stuff

How it worked so far:
In the ready function for the QuestManager, I load the save, then I use the SignalBusSingleton to emit an “update_all_quests” signal.

class_name QuestManager
extends Node

var active_quests : Array[QuestData]
var finished_quests : Array[QuestData]
var current_objectives : Array[QuestObjectiveComponent]

func _ready():
	load_quests_from_save_file(true, "active_quests")
	load_quests_from_save_file(true, "finished_quests")
	SignalBusSingleton.update_all_quests.emit(self, active_quests, finished_quests)
#other stuff
#SignalBusSingleton.gd
extends Node

#QuestManager
##Emitters: QuestManager 
##Connected: DialogueManager
signal update_all_quests(emitter : Node, active_quests : Array[QuestData], finished_quests : Array[QuestData])

#bunch of other signals

In the ready function for the DialogueManager, I connect to the “update_all_quests” signal, and in the “_on_update_all_quests” callback function, I do the things necessary to keep track of the current_objectives, so that I can select the appropriate dialogue when talking to an NPC.

class_name DialogueManager
extends Node

@export var dialogue_menu : DialogueMenu
@export var dialogue_data : DialogueData
@export var dialogue_component : DialogueComponent
var current_game_state : String
var current_quests_objectives : Array[String]
var current_dialogues : Array[DialogueData.DialogueObjectiveData]


func _ready():
	SignalBusSingleton.newstate.connect(_on_newstate)
	SignalBusSingleton.interacted.connect(_on_player_character_interacted)
	SignalBusSingleton.update_all_quests.connect(_on_update_all_quests)

#other stuff

func _on_update_all_quests(emitter : Node, active_quests : Array[QuestData], finished_quests : Array[QuestData]) -> void:
	#do stuff with active_quests

The issue:
The issue here is the DialogueManager never loads the quests’ objectives and so no dialogue menu. Annnnd…IT USED TO WORK. For real xD

What I tried:

  • If I change the signal to one defined directly in QuestManager (not using SignalBusSingleton) it works. But I’d prefer to use SignalBusSingleton because I need to connect other components to that update_all_quests signal, and it’s easier not to have to aim at QuestManager
  • I tried to connect to the SignalBusSingleton.update_all_quests signal in another script that runs fine, to see if the issue was DialogueManager not being properly connected…and it doesn’t work. This tells me that the signal (as writen in the above script) doesn’t emit in the firs place.
  • All the other signals, that QuestManager connects to, work. So I don’t thing the issue comes from SingleBusSingleton

Any idea for debug or for a solution would be highly appreciated :slight_smile:
Have a good day all :smiley:

[EDIT] I found the issue, and it stemmed from a misunderstanding of the function _ready()

Basically, before I add a bunch of stuff in the _ready() for DialogueManager, the game just barely had the time to connect DialogueManager to the signal before the QuestManager emitted the signal.

It seems that a couple of features later, that wasn’t the case anymore and QuestManager was emitting the signal before DialogueManager had the time to connect to it.

For now, I just fixed it with band-aid, by waiting for a frame before the emit. My QuestManager script looks like this, now:

class_name QuestManager
extends Node

var active_quests : Array[QuestData]
var finished_quests : Array[QuestData]
var current_objectives : Array[QuestObjectiveComponent]

func _ready():
	load_quests_from_save_file(true, "active_quests")
	load_quests_from_save_file(true, "finished_quests")
	#other unrelated stuff happens
	#Waits for other tree to connect to update_all_quests signal
	await get_tree().process_frame
	SignalBusSingleton.update_all_quests.emit(self, active_quests, finished_quests)

I need to read more about the order of tree loading because I’m pretty sure this might bite me in the butt later.

Doc for await: GDScript reference — Godot Engine (stable) documentation in English
Article about tree loading: Understanding tree order :: Godot 4 Recipes

`

1 Like

One thing you could do is text search through all your gd, tscn, tres files to see where update_all_quests appears. Maybe some misnaming conflict.

Also check if you need an @onready someplace. It could be the source is not fully available.

This looks weird to me. What is QuestData? It does not appear to be defined anywhere. Should this not be:

func _on_update_all_quests(emitter: Node, active_quests: Array, finished_quests: Array) -> void:

Perhaps I am wrong and this is something more advanced than I am used to. If so, apologies in advance.

It would explain why the signal is not emitting as it is the same in your signal bus. However I am surprised you do not get an error for it.

Thank you for your answer :smiley:

QuestData is a class I defined in a another script, so it’s normal you don’t see the definition here. It’s the complex object that represent my quests. Here, I’m just saying that you can expect an Array of QuestData, which let’s me use the attributes and functions from that class in the _on_update_all_quests function.

I always type my variables as precisely as possible (maybe because I’m mostly working with c# on a daily basis). So if I’m using an Array, I always write what it is an array of. :slight_smile:

And I found my issue, I’m going to edit my post to explain what it was!

1 Like

Thank you for answering!

I didn’t mention it in the post, but I had made a spell check to verify, and all was good in that area. And also, good remark for the @onready decorator, but I haven’t used it at all so far on this project. :smiley:

But I found the issue, I’ll edit the post to explain what it was before closing it :grin:

if you have a complicated signal dependencies I would write a unit test to make sure everything is connected. here is an example of a integration test I made to make sure all signals are connected to functions defined in a script. ( I used GDUnit plugin from the asset store.)

unit test script
class_name ShipSceneTest
extends GdUnitTestSuite

#integration tests

# better test
func test_check_for_unconnected_signal_functions():
	var node_under_test = load("res:/sut.tscn").instantiate()
	# activate any dynamic signal connections with dependencies
	assert_signal_functions(node_under_test)

# if node is connected dynamically add them here, otherwise trigger the action to get them connected
var exceptions:Array = [""]

func assert_signal_functions(node:Node):
	for child in node.get_children():
		if child.get_child_count():
			assert_signal_functions(child)
		if child.get_script():
			var signal_functions = child.get_script().get_script_method_list()\
			.filter(func(method):
				return method.name.begins_with("_on")
			)
			signal_functions = signal_functions.map( func(method):
				return method.name
			)
			var incoming = child.get_incoming_connections()
			incoming = incoming.map(func(connection): return connection["callable"].get_method())
			for function in signal_functions:
				if exceptions.has(function):
					continue
				assert_bool(incoming.has(function)).override_failure_message(child.name + ": function isn't connected: " + function).is_true()

1 Like

Thanks for the answer :smiley:

That is a very good idea. And that’s actually how I generally operate, but this prototype got a bit out of hand tbh :sweat_smile:

You can mark an answer as “solved” to actually mark this post as solved

1 Like

Thank you!

Can’t you directly mark the post as solved if you found the answer yourself?

I think you have to answer to the post with the solution so other people with the same problem also recieve the solution

1 Like