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.