Weird errors with multi-threading terrain generation

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

Hello!

I have been following some tutorials on YouTube to generate landscape from height maps. As my code does not seem very performant - generating anything over like a 300x300 block crashes the algorithm, I wanted to implement threading to generate the landscape in the background.

I am not familiar with threading at all really, and I have created a rudimentary solution that seems to work okay, my only issue is that it seems to randomly crash with unhelpful error messages, or at least, error messages that I don’t know how to solve.

This is the code for my Terrain scene:

extends Spatial
export(OpenSimplexNoise) var noise
export(float) var noiseScale
export(bool) var regenerate setget regenSet
export(int) var chunkSize
export(int) var mapWidth
export(int) var mapHeight


var mapLoading = true
var mutex
var threads = []
var lazyThreads = []
var workList = []

var numberThreads = 8






func generateMap(cs, mw, mh):
	mutex = Mutex.new()
	# Make threads for map generator
	for i in numberThreads:
		var tempThread = Thread.new()
		print("Made thread: ", tempThread.is_active())
		lazyThreads.push_back(tempThread)
	
	
	
	# Make data for map generator
	for i in mh:
		for j in mw:
			var xoffset = i * cs
			var yoffset = j * cs
			var threadData = [cs, xoffset, yoffset, noise, noiseScale, mutex]
			workList.push_front(threadData)
			

			


func assignLazyThreads():
	
	var lazy = lazyThreads.pop_back()
	if lazy != null:
		# We have a thread
		mutex.lock()
		var thrdData = workList.pop_back()
		mutex.unlock()
		if thrdData != null:
			print("Thread: ", lazy)
			thrdData.push_back(lazy)
			if lazy.is_active():
				lazy.wait_to_finish()
				
			lazy.start(self, "getChunk", thrdData)
			
		
		
func _process(_delta):
	# 2 thread lists, lazy and active
	assignLazyThreads()
	
	
	

			

			
			
func getChunk(data):
	print("Getting chunk for data: ", data)
	mutex.lock()
	var MapGen = load("res://MapGenerator.gd").new()
	mutex.unlock()
	MapGen.setChunk(data[0])
	var chunkData = MapGen.generateLandscape(data)	
	
	var chunkMesh = MeshInstance.new()
	chunkMesh.mesh = chunkData[0]
	call_deferred("add_child", chunkMesh)
	print("Thread has finished")
	mutex.lock()
	lazyThreads.push_back(data[6])
	mutex.unlock()
	
	


func clearMap():
	for i in range(0, get_child_count()):
		get_child(i).queue_free()	



func _ready():
	clearMap()
	generateMap(chunkSize, mapWidth, mapHeight)	
	
	
func regenSet(_regen):
	regenerate = true
	print("Regenerating map")
	clearMap()
	generateMap(chunkSize, mapWidth, mapHeight)

And the code for my MapGenerator:

	
extends Spatial

var chunkSize
var width
var height
var noise = null
var noiseScale
var tempMesh
var vertices
var UVs
var normals
var mutex

func createQuad(x, y):
	var vert1 # vertex positions (Vector2)
	var vert2
	var vert3
	
	var side1 # sides of each triangle (Vector3)
	var side2
	
	var normal # normal for each triangle (Vector3)
	
	# triangle 1
	vert1 = Vector3(x, getNoise(x, -y), -y)
	vert2 = Vector3(x,   getNoise(x, -y-1), -y-1)
	vert3 = Vector3(x+1, getNoise(x+1, -y-1), -y-1)
	vertices.push_back(vert1)
	vertices.push_back(vert2)
	vertices.push_back(vert3)
	
	UVs.push_back(Vector2(vert1.x/10, -vert1.z/10))
	UVs.push_back(Vector2(vert2.x/10, -vert2.z/10))
	UVs.push_back(Vector2(vert3.x/10, -vert3.z/10))
	
	side1 = vert2-vert1
	side2 = vert2-vert3
	normal = side1.cross(side2)
	
	for _i in range(0,3):
		normals.push_back(normal)
	
	# triangle 2
	vert1 = Vector3(x, getNoise(x, -y), -y)
	vert2 = Vector3(x+1, getNoise(x+1, -y-1), -y-1)
	vert3 = Vector3(x+1, getNoise(x+1, -y), -y)
	vertices.push_back(vert1)
	vertices.push_back(vert2)
	vertices.push_back(vert3)
	
	UVs.push_back(Vector2(vert1.x/10, -vert1.z/10))
	UVs.push_back(Vector2(vert2.x/10, -vert2.z/10))
	UVs.push_back(Vector2(vert3.x/10, -vert3.z/10))
	
	side1 = vert2-vert1
	side2 = vert2-vert3
	normal = side1.cross(side2)
	
	for _i in range(0,3):
		normals.push_back(normal)


func getNoise(x, y):
	if noise != null:
		return noise.get_noise_2d(x, y) * noiseScale
	else:
		return 0

func setChunk(size):
	chunkSize = size
	
func generateLandscape(chunkData):
	var xoffset = chunkData[1]
	var yoffset = chunkData[2]
	noise       = chunkData[3]
	noiseScale  = chunkData[4]
	mutex = chunkData[5]

	tempMesh = Mesh.new()
	vertices = PoolVector3Array()
	UVs      = PoolVector2Array()
	normals  = PoolVector3Array()
	
	for x in range(0, chunkSize):
		for y in range(0, chunkSize):
			createQuad(x + xoffset, y + yoffset)
			
	var st = SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)
	mutex.lock()
	st.set_material(load("res://mat.tres"))
	mutex.unlock()
			
	for v in vertices.size():
		st.add_uv(UVs[v])
		st.add_normal(normals[v])
		st.add_vertex(vertices[v])
	
	st.commit(tempMesh)
	var shape = ConcavePolygonShape.new()
	shape.set_faces(tempMesh.get_faces())
	return [tempMesh, shape]

My code is also hosted here: GitHub - thummper/Massive if you would like to download and run it - generally I get errors after changing the size of the chunks, of the size of the map and rerunning the code.

My errors look like this https://i.imgur.com/qczqVP9.png:
Errors

	

Any help in solving these errors would be appreciated, also any help with the structure of my code would be helpful as I have never done any multithreaded stuff before - initially I was just making a new thread each time I needed a new chunk, but that led to 100% CPU usage, so I thought limiting the threads would be a better solution.

Thanks.