Are threads correct to use here?

Godot Version

4.2.1

Question

In my project I have a chunk scene, which has a Tilemap. When I instantiate this scene and run a function on it, it will perform somewhat intensive operations using set_cell, etc. Doing this on the main thread causes slowdowns. However, just doing it on a thread is not safe, as it would be modifying the scene tree once its added.

My chunks load 3x3 around the player. If I understand correctly, what I need to do is begin a thread for every chunk scene, and signal out when it’s done, and add_child, all within the thread. If I do this, then no set_cell operations are being done whilst on the main thread.

Is this correct logic and best practice? If it is, could anyone give me hints/tips on what the implementation would be using Godot 4.2.1 thread operations (I don’t understand mutexes/semaphores etc. too well or how I would apply them if needed). If this isn’t correct - what is the correct way to do everything I’m asking without a single hitch? I need these operations to be butter smooth.

Thanks for any help, it’s greatly appreciated.

just return the result of your operation to the main thread as an array or whatever fits your case and then just iterate over it and do the set cell operations on the main thread but the logic that determines which cells are to be used on the auxiliary thread

Set cell is what is a performance hit, so just doing to calculations on a thread wouldn’t help much

Generally - everything the user can see has to be done on the main thread.

With that said, there are some tricks you can use. If you really have to update so many tiles that it affects your framerate, I’d advise to split the work into several frames. Update the tilemap row by row, for example. You might get a little animation out of it and call it a feature :smiley:

Here I talk about something similar:

I tried this before - it does indeed help splitting it into rows, however it’s not quite enough for set_cell_terrain_connect. I don’t see any reason though why threads can’t be used as you’re not affecting something in the current scene tree, you’re just setting cell on something which hasn’t been added yet - seems fine?

Background threads should be fine to operate on a tilemap that hasn’t been added to the tree yet. Can you post your code and the error you are getting?

func _process(delta):
	currentChunk = Utilities.get_chunk_from_pos(tilemap.local_to_map(%player.position))
	if currentChunk != lastChunk and not chunk_loaded: # if a new chunk
		if generation_thread and generation_thread.is_alive():
			return # wait for the current thread to finish
		generation_thread = Thread.new()
		generation_thread.start(_generate_chunk_thread)
	else:
		chunk_loaded = false
	lastChunk = currentChunk
func _generate_chunk_thread():
	thread_mutex.lock()
	var loading_coords = []
	var new_chunks = []
	
	for x in range(currentChunk.x - (CHUNK_SIZE * RENDER_DISTANCE), currentChunk.x + (CHUNK_SIZE * RENDER_DISTANCE) + CHUNK_SIZE, CHUNK_SIZE):
		for y in range(currentChunk.y - (CHUNK_SIZE * RENDER_DISTANCE), currentChunk.y + (CHUNK_SIZE * RENDER_DISTANCE) + CHUNK_SIZE, CHUNK_SIZE):
			var chunk_coords = Vector2(x, y)
			loading_coords.append(chunk_coords)
			if not GameManager.activeChunks.has(chunk_coords):
				var chunk = chunknode.instantiate()
				chunk.initialize(altitude, temperature, moisture, island_data, CHUNK_SIZE, magic)
				GameManager.activeChunks[chunk_coords] = chunk
				chunk.start(chunk_coords)
				call_deferred("_instantiate_chunk", chunk)
	
	var deleting_chunks = []
	for x in GameManager.activeChunks:
		if not loading_coords.has(x):
			deleting_chunks.append(x)
	
	for x in deleting_chunks:
		GameManager.activeChunks[x].save()
		GameManager.activeChunks[x].unload()
		GameManager.activeChunks.erase(x)
		GameManager.empty_chunk(x)
		SaveManager.save_chunks_data()
	
	chunk_loaded = true
	thread_mutex.unlock()

func _instantiate_chunk(chunk):
	add_child(chunk)

Excuse the mess in parts of the code - needs to be refactored, but the general premise is that I’m using a thread to perform the intensive operations in chunk.start() and call_deferring the add_child operation because I know that should be done on the main thread. However, I’m also getting errors from the debug output that .instance() isn’t thread safe - but I don’t know where else to do that here? It will work somewhat - but randomly while generating these chunks the game will lock up and completely shut down.

Apologies for my lack of knowledge on the subject - I’m still quite inexperienced on threads. I thought there may be some race conditions/deadlocking going on or something? Thanks for taking the time to help.

Yes, load and instantiate your chunks in the thread, then add them to the scene from the main thread.

1 Like

Do you have any idea what’s wrong with the code snippet I posted above? It’s doing what you’re saying here, but the game completely crashes after some time of loading chunks. The only mention of anything wrong debug wise is that it says that instantiate() is not thread safe

Sorry, I don’t know what’s wrong with that code.