godot_dialogue_manager icon indicating copy to clipboard operation
godot_dialogue_manager copied to clipboard

DialogueLabel: possible race condition between `finished_typing` event and `type_out` method

Open SylvainBigonneau opened this issue 11 months ago • 0 comments

Describe the bug I am having an issue at the end of this dialogue script:

~ this_is_a_node_title

Nathan: [[Hi|Hello|Howdy]] ! What's your name??
- Kenny
- Ken
- Bob
Nathan: And I...[wait=1.0] am...[wait=1.0] Nathan! Which option ?
- First one
	Nathan: You picked the first option.
- Start again => this_is_a_node_title
- End the conversation => END
Nathan: For more information see the online documentation.

=> END

Here is my code:

extends Node2D

var dialog_option = preload("res://DialogOption.tscn")
@onready var prompt = $Prompt # Prompt is a DialogueLabel
var resource = load("res://dialogue/main.dialogue")

var next_dialog_id := "this_is_a_node_title"
var dialogue_line : DialogueLine

# Called when the node enters the scene tree for the first time.
func _ready():
	Events.connect("phrase_spoken", self._on_Events_phrase_spoken)
	prompt.connect("finished_typing", self._on_prompt_finished)
	_generate_dialogs()

func _generate_dialogs():
	dialogue_line = await resource.get_next_dialogue_line(next_dialog_id)
	if dialogue_line:
		dialogue_line.text = dialogue_line.text
		prompt.dialogue_line = dialogue_line
		prompt.type_out()
		next_dialog_id = dialogue_line.next_id
		for i in len(dialogue_line.responses):
			var speech = dialog_option.instantiate()
			add_child(speech)
			speech.set_text(dialogue_line.responses[i].text)
			speech.next_id = dialogue_line.responses[i].next_id
		

func _on_Events_phrase_spoken(next_id: String) -> void:
	next_dialog_id = next_id
	_generate_dialogs()
	
func _on_prompt_finished() -> void:
	if not dialogue_line.responses:
		_generate_dialogs()

(phrase_spoken is triggered when a response has been picked by the user. When there are no responses to be picked, I want to trigger the next dialogue line directly.)

The issue

The issue I am having is that when the "You picked the first option" line finishes typing out, this bit of code in dialogue_label.gd triggers in order to set is_typing to false:

var is_typing: bool = false:
        set(value):
                if is_typing != value and value == false:
                        finished_typing.emit()
                is_typing = value

What occurs then, is that my code catches the finished_typing event that was just emitted and manages to call type_out() again before is_typing = value is ran in the setter function. What happens then is that is_typing is finally set to false in the original setter execution (the one that emitted the event) and the last dialogue line is never typed.

Workaround

I managed to fix this issue in my project by moving the emit to the end of the setter function:

var is_typing: bool = false:
        set(value):
                var has_finished_typing = is_typing != value and value == false
                is_typing = value
                if has_finished_typing:
                        finished_typing.emit()

I am definitely not sure this is the best solution, but I just figured it would give an idea of my issue

Affected version

  • Dialogue Manager version: 2.34.0 and 2.35.0 at least
  • Godot version: 4.2.1

To Reproduce See my code up there

Expected behavior There should not be a race condition maybe?

SylvainBigonneau avatar Feb 28 '24 19:02 SylvainBigonneau