Background loading technique recommendation

Godot Version

4.2.2

Question

I’m working on a game that creates content procedurally. When you are in-game, at the push of a button, content will be created by running a make_new_content() function which is found in an autoloaded singleton called PROCEDURAL_MAKER, such as like:

(imagine you are running around in a 3D world…and see a 3D button next to a 3D television screen…so you have your character press it…)

_on_3Dbutton_pressed():
var new_content = PROCEDURAL_MAKER.make_new_content()
show_content_as_picture_on_3D_television_screen()

the problem is, the make_new_content() function always causes lag or freezing or stuttering (definitely it causes FPS drop), and I assume it’s because it’s blocking the main thread while it runs (it loads up scripts it needs to use to procedurally generate stuff and then runs them, has a lot of iteration and for loops in it blah blah, it makes sense that it would cause slowdown).

I’m thinking the best way to solve this would be to put a “temporarily loading” screen on the 3D screen while the content is being procedurally made in the background, and then showing the content on the 3D screen when its done being made, BUT…i don’t know where to start learning how to do this, and what technique to learn to make it work in this situation.

I’ve looked at using await (which used to be called yield?), and I’ve looked at using threads (which need to use call_deferred and a wrapper?) but the strategies for implementing this I don’t know if they will work for the use-case I figured would be a solution, and I don’t know enough about threads and async to be able to tell if I am barking up the wrong tree.

Could anyone point me in the right direction for what to learn how to do to get this working?

ps - i didn’t include code because the code i have is scattered everywhere ( i need to clean it up ) and i don’t have any problem with generating images procedurally and then displaying them as viewport-textures on quadmeshes, its just the FPS drop that’s a problem. but i’d be happy to provide anything if called for, and i hope the pseudo-code above illustrates the problem well enough

Thx in advance to all

You could try using the WorkThreadPool to offload the generation to a thread.

Example:

extends Node


@onready var sprite_2d: Sprite2D = $Sprite2D
@onready var button: Button = $Button
@onready var loading_sprite: Sprite2D = $LoadingSprite


var task_id = -1
var texture:ImageTexture


func _ready() -> void:
	button.pressed.connect(start)
	button.show()
	loading_sprite.hide()
	set_process(false)


func start() -> void:
	# Start the task and show the loading sprite
	task_id = WorkerThreadPool.add_task(_create_texture)
	loading_sprite.show()
	button.hide()
	# Enable process to check if the task is finished
	set_process(true)


func _process(delta: float) -> void:
	if not task_id == -1 and WorkerThreadPool.is_task_completed(task_id):
		# If the task is finished then change the texture and hide the loading sprite
		sprite_2d.texture = texture
		loading_sprite.hide()
		button.show()
		task_id = -1
		# Also disable process as it not needed anymore
		set_process(false)


func _create_texture() -> void:
	# Generate a new image
	var img = Image.create(4096, 4096, true, Image.FORMAT_RGBA8)
	for x in img.get_width():
		for y in img.get_height():
			img.fill_rect(Rect2i(x, y, 1, 1), Color(randf(), randf(), randf(), 1.0)) # I'm using fill_rect() to showcase slow loading as it's slower than set_pixel()

	if is_instance_valid(texture):
		# If the texture is valid then update it with the new image
		texture.set_image(img)
	else:
		# If it's not valid, then create a new texture
		texture = ImageTexture.create_from_image(img)

Result:

Read this documentation page Thread-safe APIs — Godot Engine (stable) documentation in English to know which systems are thread safe and which aren’t.