Search code examples
c#async-awaitgodot

Using C# async delays in Godot


I am currently experimenting with Godot C# making a basic shooter and for the gun's fire rate I have been experimenting with different delay systems. Node Timers work although I'm trying to make the script generic, and the Timer calls seem to only call functions in the parent script.

I'm now looking at C#'s Task.Delay method and it also seems to work, with it being an async action it does not look to be affected by the frame rate or slow down the game.

My question is, is there any known issue for using Task.Delay in game applications: like is it unreliable or can it crash if too many instances of the method are called?

Here's the code below although I don't think it’s important:

 private void shoot() {
  //if "canShoot" spawn bullet
  ShootCooledDown();
}

private async void ShootCooledDown() {
  TimeSpan span = TimeSpan.FromSeconds((double)(new decimal(shotDelay)));
  canShoot = false;
  await Task.Delay(span);
  canShoot = true;
}  

Solution

  • My question is, is there any known issue for using Task.Delay in game applications: like is it unreliable or can it crash if too many instances of the method are called?

    Not per se. There is nothing in particular wrong with Task.Delay in games, nor too many instances of it.

    However, what you are doing after Task.Delay can be a problem. If you execute await Task.Delay(span);, the code that comes after might run in a different thread, and thus it could cause a race condition. This is because of await, not because of Task.Delay.

    For example, if after await Task.Delay(span); you will be adding a Node to the scene tree (e.g. a bullet), that will interfere with any other thread using the scene tree. And Godot will be using the scene tree every frame. A quick look at Thread-safe APIs will tell you that the scene tree is not thread-safe. By the way, the same happen with virtually any widget API out there.

    The solution is use call_deferred (CallDeferred in C#) to interact with the scene tree. And, yes, that could offset the moment it happens to the next frame.


    I'll give you a non threading alternative to do that.

    There are method get_ticks_msec and get_ticks_usec (GetTicksMsec and GetTicksUsec in C#) on the Time class (previously OS class), that give you monotone time which you can use for time comparison.

    So, if you make a queue with the times it should shoot (computed by taking the current time plus whatever interval you need). Then in your process or physics process callback, you can check the queue. Dequeue all the times that are overdue, and create those bullets.

    If you don't want to solve this with Godot APIs, then start a Stopwatch at the start of the game, and use its elapsed time.


    But perhaps that is not the mechanic you want anyway. If you want a good old cool-down, you can start the Stopwatch when you need the cool-down, and then compare the elapsed time with the cool-down duration you want to know if it is over.