I’ve been doing Steam integration with GodotSteam and made a tricky mistake. It’s not an issue with GodotSteam in particular, but a pattern that is fairly common in GDScript. So, I thought I’d share.

Mistake

GodotSteam API looks sort of like this (pseudocode):

class_name Steam

signal leaderboard_find_result(handle: int, found: int)

func findLeaderboard(lb_name: String) -> void:
    ...

You call findLeaderboard, which is a long I/O bound operation, so you need to connect to leaderboard_find_result to get the result.

Because I didn’t really care to block anything, I wrote code like this:

Steam.findLeaderboard("highscore")
var result = await Steam.leaderboard_find_result

Do you see a problem here?

What about here:

Steam.findLeaderboard("highscore")
await get_tree().create_timer(5.0).timeout
var result = await Steam.leaderboard_find_result

If leaderboard is found within the 5 second timeout, you may never get the handle you expect to get.

It may sound obvious, but you need to connect to a signal before making a function call. So awaiting with this API may not be the best option.

Solution

So your options are either avoiding awaits entirely

func fetch() -> void:
    Steam.leaderboard_find_result.connect(_on_leaderboard_find_result, CONNECT_ONE_SHOT)
    Steam.findLeaderboard("highscore")

func _on_leaderboard_find_result(handle: int, found: int) -> void:
     # process result
     pass

or using call_deferred.


func fetch() -> void:
    Steam.findLeaderboard.call_deferred("highscore")
    var result = await Steam.leaderboard_find_result

In latter case Steam.findLeaderboard will be called at the end of the frame, i.e. after we have already started awaiting for Steam.leaderboard_find_result.