Godot TileMap chunking - is this impossible?

Godot Version

4.2.1

Question

Hi all. I’ve been hitting a brick wall on this problem - I haven’t found any solution within Godot since the end of last year when I started working on this project, and I’m at my wits’ end.

To summarize, I’m making a chunking system for a procedurally generated overworld. The tiles are set on top of perlin noise values. The issue I’m facing is the performance of setting the tiles in chunks. I’m using set_cell_terrain_connect(), as my world is made up of various terrains. Here is the current state of loading chunks via this function:

As you can see, there is a bad stutter when using the terrain. The grass/sand is done via set_cell at the minute. I have tried splitting it up across frames as you can see, which is only 50 calls in a frame, and the frames will drop. Doing the same with the set_cell_terrain_connect function array is even worse:

Clearly, the main thread is being blocked. So what have I tried to do to mitigate this beyond the above? Firstly, I tried implementing threads. I realize that altering the scene tree on threads is not safe, so what I thought to do instead was to give every chunk IT’S OWN tilemap, and then do all the calculations, including the set_cell operations, on the tilemap which isn’t on the tree just yet. Then, I proceeded to call_defer the adding of the chunk to the scene_tree (as I also know that doing this on a thread is not safe). Here’s the result:

As you can, the offloading of the chunk to the thread works perfectly with no stuttering… until it crashes. The docs state that ‘Interacting with the active scene tree is NOT thread-safe.’, which I thought was fine, as I’m not interacting the with the active scene tree at all, I’m setting cells on something which wasn’t been added yet. However, the docs also mention that

Instancing nodes that render anything in 2D or 3D (such as Sprite) is not thread-safe by default. To make rendering thread-safe, set the Rendering > Driver > Thread Model project setting to Multi-Threaded.

This implies that this method shouldn’t be used either - even if it’s not affecting the scene tree from the thread. Setting the rendering Thread Model to Multi-Threaded made no difference either.

So what options are left? Is this a complete impasse as far as TileMaps are concerned? I’m reluctant to progress with the rest of my project at all, given the state of this basic feature that many published games include.

Any help would be greatly appreciated, as I do not want to drop this as it is a passion project and something I’ve wanted to do for a long time. Thank you in advance.

1 Like

The docs state that ‘Interacting with the active scene tree is NOT thread-safe.’

Use ResourceLoader.load_threaded_request, which is thread safe. Don’t use the Thread class directly. You’re making it harder than is actually is. I even posted the complete core of a background loader you can use for this very purpose.

Look at my YouTube channel (https://www.youtube.com/@soapspangledgames2444) to see videos of it in action. The progress bar and notification text work smoothly because the chunk loading is done in the BackgroundLoader class I posted in other responses.

You’re going to have to restructure your code, but I’m leaving that as a programming exercise for you.

1 Like

If you want to create endless/infinite world generation with tilemap + biomes + objects then you can watch my this video

1 Like

Good video on the basics - but it doesn’t cover the complex issue of the stuttering caused by set_cell_terrain_connect and how it’s not possible to thread this at all

1 Like

If I can’t use set_cell_terrain_connect on a thread of any kind then I simply can’t achieve what I’m trying to do

I’m running into the same problem. I do all my calculations on another thread, which creates a list of Vector2I that I add to the tilemap on the main thread. To minimize the stutter I use setcellterrainconnect for my borders only and use setcell for my inner “solid” area - which is on a different layer. Then through trial and error find the ideal chunk size, which is a trade-off between frequent, minor stutters or less frequent, more obvious stutters. It is very annoying how hamstrung the threading is and how expensive some of these operations are.

Thank you, this is really helpful!

1 Like

The tutorial is not so good, as it has no explanation. So you can just download the project from description.

I did so, it is quite possible to figure it out :smiley: (if you didn’t download Godot yesterday)

1 Like

If you are going to use Godot’s built-in NavigationServer2D, its produces lag spikes every time when new navigation map joins map. You’ll have to deal with if you planning to use it for procedural generation and it becomes absurdly laggy if you’ll use Camera2D zoom property to zoom out (too many tile primitives on screen) and lags become harder if you use isometric type tile map.

would you consider reduce the chunk size(or chunk’s dimension)? If the world is only generated when one triggers the condition, maybe this case it’s position? and at that frame all the tiles required in those chunks flushes in CPU the same time, then when you trigger the generation of chunks, it’s best that the generation task is as few as possible at the same time.

because of how TileMapLayer.set_tile() works. One tile one navigation region. Tile generation happens relatively fast, problem happen when you try to update tiles, using set_tile().
After some struggling I managed to avoid TileMapLayer completely using just VisibleOnScreenNotifier2D for one chunk and draw_colored_polygon() with runtime rendered texture and got a result of smooth generation and navigation updates which you can control, because it is limited if used in tile map

I’ve posted several similar responses to this, and I am only partially correct. While this does work to load anything in the background, there is a secondary problem for which there is no current workaround: add_child() is not thread-safe, must be done on the main thread, and will cause hiccups regardless of whatever else you do.

There are plenty of situations where add_child() is fast enough that the hiccup in not noticeable, but if it’s going to hiccup, there is nothing you can do about it.

add_child() really needs to be made thread-safe.

1 Like