How to yield nothing or a static value in an if/else branch (avoid using `yield(get_tree(), "idle_frame"))?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By juninhopernambucano

Im making a party game where the player move through a board, just like the mario party game. Sometimes there is more than one path for the player to follow, and he has to choose what he wants.

I have a class called Tile that represents a position on this board. This class has a function called get_next_tile() responsible to return the next tile for the player. This function is written as:

func get_next_tile():
  var next_size := self.next_tiles.size()
  if next_size > 0:
	if next_size == 1:
		yield(get_tree(), "idle_frame")
		return next_tiles[0]
	else:
		var choosed_tile = yield(choose_path(), "completed")
		return choosed_tile

As seen, if there is more than one path to follow the function choose_path is called. This function waits a path_choosed signal to complete and returns the choosed path. But if there is only one possible path, I would like to already return it (the player doesnt need to choose in this case), and for this to work I am obliged to use the yield(..., idle_frame) otherwise I get an error about GDState not returned or about Error connecting to signal: completed during yield on the function that calls get_next_tile.

In JS I think I could do something like

function getNextTile(): Promise<Tile> {
  let nextSize = this.next_tiles.length;
  if (nextSize > 0) {
    if (nextSize === 1) {
      return Promise.resolve(this.next_tiles[0]);
    } else {
      return choosePath();
    }
  }
}

async function getNextTileAsync(): Promise<Tile> {
  let nextSize = this.next_tiles.length;
  if (nextSize > 0) {
    if (nextSize === 1) {
      return this.next_tiles[0];
    } else {
      return (await choosePath());
    }
  }
}

without awaiting anything on the if branch. Is this possible on GDScript?

In my experience, things become tricky when you start using yield. I recommend you rethink your logic to avoid using yield to have cleaner code (if I understand correctly, you are using yield as a hack/workaround to a bug, which is probably not the best design choice), better design, and to avoid future bugs.

godot_dev_ | 2023-02-24 18:28

Well Im not using yield only as a hack/workaround. Im using yield(choose_path(), "completed") to wait the player choose what path he wishs to follow, which I believe its the intended way to use yield (Im only a beginner with gdscript, so this may be misleading).

But the yield(get_tree(), "idle_frame") is indeed a workaround and my question is how to avoid it

juninhopernambucano | 2023-02-24 20:36

:bust_in_silhouette: Reply From: Lopy

You can let your function either yield or return directly, and the calling function handle it automatically:

func calling_function():
    var state_or_value = get_next_tile()
    while state_or_value is GDScriptFunctionState:
        state_or_value = yield(state_or_value, "completed")
    print(state_or_value)

func get_next_tile():
    var next_size := self.next_tiles.size()
    if next_size > 0:
        if next_size == 1:
            #yield(get_tree(), "idle_frame")
            return next_tiles[0]
        else:
            var choosed_tile = yield(choose_path(), "completed")
            return choosed_tile

If case you want to always yield instead, you can:

  • Do as you did before, with both cases being resumed externally.
  • Or use an empty yield(), and resume your GDScriptFunctionState manually in the calling function (my_state.resume()). However I do not see a good way to distinguish between the cases in the calling function.