Multithreaded Function Slower Than Main Thread, And Slowing Down Main Thread As Well

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By ArtS18

Hi, I understand I could be completely silly right now, but I’m making a 2D top-down game (survival sandbox building stuff type of game) so I am randomly generating a terrain. I decided to use chunks much like Minecraft except I noticed that every time a new chunk had to be generated I’d get a 1-second lag spike. So I decided to move it to a separate thread, I don’t know much about multithreading I do have a book I haven’t gotten around to tho lol. After a quick scan of Godot’s documentation, I started a thread, used a semaphore to signal the thread to process, and moved my entire chunk generation code into the new multithreaded function.

Here is just a sample of the GameMap script:

# Made up of MapChunks
var map = {}
var chunk_loaders = [Vector2(0,0)]
var render_distance = 7 # Must be Odd
var chunk_tick_dist = 19 # Must be Odd

var noise_list = {"map_noise" : null, "foliage_noise" : null}

var chunk_gen_thread
var chunk_gen_thread_running = true
var mutex
var semaphore

var indices_to_generate = []

onready var player = get_node("../Player")

func _ready():
	mutex = Mutex.new()
	semaphore = Semaphore.new()
	
	chunk_gen_thread = Thread.new()
	chunk_gen_thread.start(self, "chunk_generation_thread")
	
	generate_noise()
	regen_chunks(get_chunk_from_world(player.position.x, player.position.y), 0)

func chunk_generation_thread(userData):
	var should_quit = false
	while !should_quit:
		semaphore.wait() # Wait for chunk gen signal
		
		# Protect run loop with mutex
		mutex.lock()
		should_quit = !chunk_gen_thread_running
		mutex.unlock()
		print("wtf")
		# Regen Chunks
		mutex.lock()
		for i in indices_to_generate:
			var lc_pos = Vector2(chunk_loaders[i].x - floor(render_distance/2), chunk_loaders[i].y - floor(render_distance/2))
			var upper_lc = lc_pos - Vector2(1, 1)

			for x in render_distance:
				for y in render_distance:
					var chunk_pos = Vector2(lc_pos.x+x, lc_pos.y+y)
						
					var chunk = retrieve_chunk(chunk_pos.x, chunk_pos.y)
					chunk.rerender_chunk()

			for x in render_distance+2:
				for y in render_distance+2:
					if x != 0 and x != render_distance+1 and y != 0 and y != render_distance+1:
						continue
					
					var chunk = Vector2(upper_lc.x+x, upper_lc.y+y)
					var unrender_chunk = retrieve_chunk(chunk.x, chunk.y)
					
					unrender_chunk.unrender()
		mutex.unlock()

func regen_chunks(chunk_position, chunk_loader_index):
	mutex.lock()
	if chunk_loader_index > chunk_loaders.size():
		chunk_loaders.append(Vector2(0,0))
	chunk_loaders[chunk_loader_index] = chunk_position
	indices_to_generate = [chunk_loader_index]
	mutex.unlock()
	
	semaphore.post()

func tick_chunks():
	for chunk_pos in chunk_loaders:
		var lc_pos = Vector2(chunk_pos.x - floor(chunk_tick_dist), chunk_pos.y - floor(chunk_tick_dist))
		for x in chunk_tick_dist:
			for y in chunk_tick_dist:
				retrieve_chunk(lc_pos.x+x, lc_pos.y+y).on_tick()
		
func retrieve_chunk(x, y):
	if !map.has(Vector2(x, y)):
		create_chunk(x, y)
	
	return map[Vector2(x, y)]
	
func create_chunk(x, y):
	var new_chunk = MapChunk.new()
	add_child(new_chunk)
	new_chunk.generate_chunk(x, y)

	map[Vector2(x, y)] = new_chunk

Basically, the “chunk_generation_thread” code used to be inside the regen_chunks function on the main thread, but I moved stuff around. Now the character is frozen in place while you visibly watch the chunks load in. Forget about walking around or moving the mouse before the chunks load in.

If the problem is that I making function calls to the MapChunk objects for rerender and unrender I would expect the worst case to be back to the normal lag spike no? If it is the function calls making such a huge frame drop what do I have to do? Move anything that has to do with chunks into that one tiny function?

This is a gif but I assure you, the frame rate is very representative lmaoo

LaggyChunkLoading