Generating the world using Perlin noise

Godot 4.4

In my game, I generate tiles using Perlin noise. To improve performance during the tile installation process, I moved it to a separate thread. However, some voids are still appearing. These voids do not occur when running the game without multithreading. I am not sure what the problem might be.

func _process(delta):
	PlayerCords = get_owner().find_child("Player").global_position
	chunk()
	if ThreadTileTerrain.is_started():
		ThreadTileTerrain.wait_to_finish()
	if detChunk.x != PastCords.x or detChunk.y != PastCords.y:
		PastCords = detChunk
		#SpawnArea(MultiplerChunk)
		ThreadTileTerrain.start(SpawnArea.bind(MultiplerChunk),Thread.PRIORITY_HIGH)



func chunk():
	detChunk = Vector2i(int(PlayerCords.x)/LengthChunk, int(PlayerCords.y)/LengthChunk)
	MultiplerChunk = detChunk
	#MultiplerChunk = Vector2i(int(PlayerCords.x)/LengthChunk, int(PlayerCords.y)/LengthChunk)
	if PlayerCords.x < 0 and PlayerCords.x != 0:
		MultiplerChunk.x -= 1
	if PlayerCords.y < 0 and PlayerCords.y != 0:
		MultiplerChunk.y -= 1
	detChunk = MultiplerChunk * LengthChunk


func SpawnArea(Multi):
	if ChunkCount > 10:
		genTileEnv.clear()
		genTileMap.clear()
		ChunkArray.clear()
		ChunkCount = 0
	for x in range(-1,2):
		for y in range(-1,2):
			if !ChunkArray.has((Multi + Vector2i(x, y))):
				ChunkCount += 1
				ChunkArray.append((Multi + Vector2i(x, y)))
				GenerateMap((Multi + Vector2i(x, y))*LengthChunk/16)
				GenerateEnvironment((Multi + Vector2i(x, y))*LengthChunk/16)
			


func GenerateMap(Cords):
	for x in range(Cords.x, Cords.x + Width):
		for y in range(Cords.y, Cords.y + Heigth):
			temp1 = abs((x + y) % 10) % 4
			NoiseValues = shum.get_noise_2d(x,y)
			if NoiseValues > -0.2 and NoiseValues < -0.1:
				genTileMap.set_cell(Vector2i(x,y), AtlasID, Vector2i(0, 1), temp1)
			else:
				genTileMap.set_cell(Vector2i(x,y), AtlasID, Vector2i(0, 0), temp1)	
1 Like

My initial guess is your thread is dying before it finishes its task. I haven’t messed with multithreading in Godot, so I don’t have much practical advice.

I used the counter in func GenerateMap(Cords). The number of passes was equal to the number of tiles in total, but some of tiles were not displayed

Cool. That’s good info. So in debugging this, the next thing I would try would be a smaller sample set of data. Try creating say an 8 x 8 map and see what happens. You want to isolate a pass that failed, and having less data to sort through should help you. If they all pass, you increase it a little until you get a failure. Then you count across your grid until you find which pass failed, and then go look at that execution.

My reading of that is: What you’re doing probably isn’t thread safe. It’s already in the scene tree if _process() is being called, so I think you’ll need mutexing around all the accesses to .set_cell() at the least.

Be somewhat careful of the performance of .set_cell() as well; it gets very slow for large maps, since (it seems?) it has to regenerate submeshes on the GPU whatever frame you call it. I found that batching generating by submesh-sized regions was significantly faster than sequentially generating each tile, but I still have to throw up a load screen to hide the frame rate stuttering when I first generate the map.

1 Like

Yep. Mutex has made the generation stable. But I don’t understand why some tiles weren’t placed.

Tilemaps are “infinite” (ish), in that they get as big as you need them to, and grow on the fly as you add tiles. If two different threads are simultaneously growing the same data structure with no locking, all sorts of hilarity could ensue.

1 Like