Quick answer: Batch all set_cell calls in one tight loop, then let Godot rebuild once. For 4.3+, use TileMapLayer nodes to isolate edits per system.
A procedural dungeon generator rewrites the floor each level. Quadrants visibly flicker for several frames during generation. Each set_cell triggers an internal rebuild.
Batch the Writes
func regenerate_dungeon():
var changes = _compute_changes() # pure logic, no rendering
for change in changes:
tilemap.set_cell(0, change.pos, change.source, change.atlas)
# Godot rebuilds affected quadrants once after this frame
Compute first, write in one pass. Avoids interleaving with other systems that read the tilemap mid-update.
TileMapLayer (Godot 4.3+)
Per-system tilemap layer nodes. Each layer can be updated independently without affecting siblings:
var floor_layer: TileMapLayer = $Floor
var wall_layer: TileMapLayer = $Walls
floor_layer.set_cell(pos, source, atlas) # walls unaffected
Smaller individual rebuild scope; less visible flicker.
Yield Across Frames for Huge Edits
func regen_async():
for i in range(changes.size()):
tilemap.set_cell(...)
if i % 100 == 0: await get_tree().process_frame
Yields every 100 writes, lets the engine breathe. Visible “build-up” effect rather than flicker.
Force Update for Save/Load
If you need the visual to match immediately (e.g. just before a screenshot), call update_internals() after a batch:
tilemap.update_internals()
await RenderingServer.frame_post_draw
Verifying
Regenerate dungeon. No flicker. Render starts with empty visible; ends with full level. Frame time during regen within budget.
“Mass tilemap edits need batching. Compute first, write second, render third.”
For really large maps (1000×1000+ tiles), pre-build chunks off-screen and swap visible region — better than full regeneration in front of the player.