Question regarding awaiting ProcessFrame (or PhysicsFrame) in c#

Godot Version

4.5

Hi, I’ve been enjoying Godot for quite some time and ran into an odd issue that can be summed up like this:

using Godot;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public partial class Node2d : Node2D
{
    private List<int> ints = new List<int>();
    private bool isGeneratingChunk = false;
    public override async void _Process(double delta)
    {
        if (!isGeneratingChunk)
        {
            isGeneratingChunk = true;
            await CallSignal();
            isGeneratingChunk = false;            
        }
        GD.Print($"number of ints: {ints.Count} is generating chunk: {isGeneratingChunk}");
    }

    private async Task CallSignal()
    {
        var i = 0;
        while(i < 100)
        {
            await ToSignal(GetTree(), SceneTree.SignalName.PhysicsFrame);
            ints.Add(i++);
        }
    }
}

I’m not certain why, but the GD.Print statement occurs before the iteration completes. The number of ints is wrong and IsGeneratingChunk reports as being true. After the print statement, the code continues with the iteration in the CallSignal function.

In my game, I do this periodically in large loops to give Godot a breather so things still work while I’m loading data. Is this wrong? If so, please let me know the proper way to handle this.

Thanks!
AJ

Whatever you do - never await in perpetual callbacks like _process(). Unless you really really know what you’re doing. You can potentially create hundreds of new actively awaiting coroutines each second.

1 Like

Thanks for the response.

I’m not performing hundreds, this is just an example. But I need to await the PhysicsFrame signal periodically and this causes issues.

Why doesn’t godot make the Process function return a task by default in c#? This causes so many issues when creating procedurally generated maps. At this point, it seems godot is simply not performant enough for my needs.

Unless there’s another way?

You shouldn’t be doing any generation in _Process().

This function is called 60 times a second incessantly, awaiting inside it makes no sense because no matter how long you await there, the new “instance” of the function will be invoked again in 1/60 of a second. And again and again. If all of those instances are awaiting, you’ll end up with a stack full of them. As soon as the signal fires all those awaiting instances will wake up “at once” and execute “at the same time”. Resulting in chaos.

Generate in _Ready() or if you need to do a lot of work - launch a worker thread.

That sounds horribly inefficient and a really, really bad idea.

1 Like

Ok, maybe I should’ve reworded this. I didn’t mean to offend anybody. Let me get into more detail.

I have a player that moves around a ‘map’ which is made up of a 5x5 set of chunks. Each chunk is 64x64 tiles (each tile is 16x16 pixels). I track the player in the process function. When he gets to the to the next chunk, then I load into memory the next set of chunks. So, the player is in the center all of the time, and when he moves forward 1 chunk, the next set of chunks loads (so 5 chunks).

Each chunk takes approximately 20ms to load, and it uses a Noise Map to create the tiles. All of the time is spent drawing to a TileMapLayer (which I read can’t be set on a separate thread) .

To further complicate things, after the layer is loaded, I need to set an AStarGrid with the collisions of the map.

Currently, the process is:
-player moves to next chunk
-new chunks get loaded. (I think I could move loading to Ready function and add a signal to the main map loader)
-set astar with chunk

would moving the loading of the map into its own ready function make a difference? Doing it synchronously, I need each loaded map to run on the next Process.

I’ll do some testing

I am new to game dev so I’m still actively working on this. There just aren’t many resources on this subject in Godot.

I think you don’t fully understand how _Process() works. The engine calls it every frame. This function has no business doing anything heavy/blocking, nor it should/can operate asynchronously in any way. It’s meant to run every frame in synch with rendered frame rate. The least it does the better.

If you need to stream data in real time use background threaded loading provided by ResourceLoader. If you additionally need to build something upon that data that takes long enough to cause frame drop spikes, either manually distribute the work through frames or launch a worker thread. Godot has built in basic thread functionality or you can use C# threading facilities.

1 Like

There’s nothing stopping you building a tile map layer in a thread. The only thing that is not thread safe is the scene tree. If you keep the node orphaned, threads can do whatever they wish with it.

that is helpful!

I’ll try it