Correct way to solve "busy waiting" in GDScript

Godot Version

4.2.1

Question

Let’s assume we have an autosave feature that works basically as follows:

  • The game saving logic is contained within an autoloaded singleton
  • In the _ready of that singleton, a Thread object is spun up which contains a while loop, using some thread-safe condition that is removed on _exit_tree
  • It constantly checks for a “save trigger”. If a save trigger is received, it saves various resources to user:// based on a save data dictionary (more on this later and my fundamental question about how to avoid busy waiting)
  • Various objects in the game will set save values in the save dictionary, based on events firing - either signals or just setting the dictionary’s values via the singleton accessor, which is thread-safe

I am aware this is the wrong way to do this, and fundamentally creates busy waiting.

Some representative code (assume d is a Dictionary, which I understand is inherently thread-safe according to this, assuming the dictionary size does not change).

var d := {}
var save_data := {}
func _ready() -> void:
	thread = Thread.new()
	thread.start(queue_thread)

func queue_thread() -> void:
	while d.thread_is_alive:
		if d.trigger_save:
			d.trigger_save = false
			
			ResourceSaver.save(...)
			# convert save_data to JSON, whatever
			# the point is we are doing the auto-saving here

		OS.delay_msec(100)


func _exit_tree() -> void:
	d.thread_is_alive = false
	thread.wait_to_finish()

Clearly, the OS.delay_msec(100) is a bandaid that reduces the issue of busy waiting on the thread.

What is the correct way to implement this? Resource saving clearly needs to be on a separate thread.

In my case, I have about 2-3 MB of save data that will need to be saved to the user:// system. One chunk of that data is actually saving an ArrayMesh as a .tres, so I need to very carefully make a duplicate of that mesh data prior to handing it over to the resource saver, to ensure the saver is not accessing the node on a separate thread (which understandably, crashes the engine).

I’m familiar with workflows in other langauges, such as BlockingQueue in Java / BlockingCollections in C#, or WaitOne.

Another example - in python we would call recv on an open TCP socket connection in a while loop to read data on a separate thread - it sits there and waits until data appears rather than busy waiting. You can gracefully close the socket by forcing a new connection, which causes recv to return -1 or something like that, allowing the thread to cease.

Any help would be appreciated.

There are some options here. Regarding your code, there is no reason to have the while loop to spin like that as you have stated. But since you want to save on a separate thread you could just have this event driven with a signal and ended with a signal.

You save function can just be something like this.

Signal thread_done
Func save():
  Recoursesaver()
  thread_done.emit()

Fun _on_save_request():
  If thread.is_alive():
        Await thread_done
  Thread.start(save)

There could be some issues here if saving is spammed that could require a refactor but you could hopefully get away with the loop with this.

This would spin up a new thread on each thread request, which I was hoping to avoid. Ideally there would be a ThreadPool or something similar to use in this situation.

I found this but it seems to want to operate on an array of input data, in a parallelism-like way.

Maybe I am being too pedantic and spinning up a new thread every 5 seconds or so is fine.

The coroutine would return to the caller, delaying further execution until the thread done signal is emitted. But await would not prevent a queue of coroutines to stack up.

I wouldmt worry about it in this case, until it becomes an obvious problem. 5 seconds is an eternity on a CPU.

you are clearly using GDScript but you stated you were familiar with c#. I just use the System.Threading ThreadPool.

My game has a bit of an unfortunate history that’s tying me to GDscript. The game started as a jam and the demo was a web build. So no c# unless I want to refactor the whole game :slight_smile:

Fair enough. I get a little jealous of how vague GDScript lets your code be and still function correctly but I don’t think I could go back now.

My first thought is a semaphore. You’d wait on it in the worker thread and post to it from the main thread when you want the worker to wake up. Looks like there’s a tutorial in the docs for this use case too.

1 Like

This is what I was looking for. Thank you!