Repository navigation
fix(tilemap): carry tileset textures across surface loss/restore (#847) - #907
Conversation
Tileset textures (the per-tileset sheet AND the per-tile images of a collection tileset) are uploaded by the tilemap runtime straight through the renderer, so the direct-upload re-arm (#820) never covered them and a tilemap drew blank after an Android TERM_WINDOW/INIT_WINDOW cycle. The runtime now owns its own surface lifecycle on gfx's minted-key re-arm seam (invalidateTexture + reuploadTextureFromMemory): - surfaceLost invalidates (never frees) every owned id and suppresses the map's draw pass. - surfaceRestored re-decodes each id's image from the embedded provider (program-lifetime bytes, nothing duplicated) and re-uploads it under its ORIGINAL id, then rebinds gfx's TileMapRenderer, which caches backend texture values rather than ids. The decoded map (runtime setTile edits) and the per-tile animation clock are kept. Game.surfaceLost/surfaceRestored drive it via the tilemap mixin. Without the seam both are no-ops (pre-#847 behaviour). Tests: new test/tilemap_surface_test.zig (sheet + per-tile, same-id re-upload with the right bytes, rebind to fresh backend textures, draw suppression, edits kept, teardown/free-while-lost, partial failure), a real-gfx TileMapRenderer rebind case in tilemap_mutation_test.zig, and the no-seam degrade in tilemap_collection_test.zig. The FakeGfx stand-in moves to test/tilemap_fake_gfx.zig so both suites share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (10)
Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour. 📝 WalkthroughWalkthroughThe change adds tilemap texture invalidation and restoration to the game’s GPU surface lifecycle. Runtime retains texture sources, re-uploads textures under their existing IDs when supported, and rebinds renderers. Tests cover surface cycles, partial failures, renderer compatibility, and teardown. ChangesTilemap surface lifecycle
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant Game
participant TilemapMixin
participant Runtime
participant ImageProvider
participant RenderImpl
Game->>TilemapMixin: invalidateTilemapTextures
TilemapMixin->>Runtime: surfaceLost
Runtime->>RenderImpl: invalidateTexture for owned IDs
Game->>TilemapMixin: reloadTilemapTextures
TilemapMixin->>Runtime: surfaceRestored with image provider
Runtime->>ImageProvider: fetch retained image sources
Runtime->>RenderImpl: re-upload textures under existing IDs
Runtime->>RenderImpl: rebind tilemap renderer
Merge Risk: ⚪ Minimal · up to Tilemap sheet and per-tile textures are now invalidated when the surface is lost and re-uploaded under their original IDs when it is restored. Runtime tile edits are preserved, and renderers without re-arm support keep their existing behavior. No merge-blocking issues were found, and tests cover the lifecycle, partial failures, and teardown. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Closes #847.
Problem
Tilemap tileset textures — the per-tileset sheet and the per-tile images of a collection tileset — are uploaded by
tilemap_runtime.initInPlacedirectly throughRenderImpl.loadTextureFromMemory. OnlyGame.loadTextureFromMemoryU32keeps the bytes inWorld.direct_textures, and the #820 surface re-arm handles only that store. So after an Android TERM_WINDOW/INIT_WINDOW cycle every tilemap drew blank. On currentmain,surfaceLost/surfaceRestorednever touchedworld.tilemaps.Fix
The runtime now handles its own surface lifecycle, using gfx's minted-key re-arm seam (
invalidateTexture+reuploadTextureFromMemory, the same both-or-neither gate astracks_direct_uploads):Runtime.surfaceLostinvalidates every owned id without freeing it, so nothing is freed through a dead or recycled handle (the surface_lost/restored events are delivered AFTER restore (buffered emit + parked loop) — game-side GPU cleanup is impossible; direct uploads have no restore lifecycle #820 bug). It also setssurface_lost, which skipsdraw/drawLayerAt.Runtime.surfaceRestored(provider)re-reads each id's image from the embedded provider (program-lifetime@embedFilebytes, so no copy is kept). It re-uploads each image under its original id, then rebinds gfx'sTileMapRenderer. The rebind is required: gfx resolves textures once, at init, and stores the backend texture value, not the id. Without it the renderer would keep drawing with the dead handles.setTile/setLayerTilesedits survive. The per-tile animation clock carries over to the rebound renderer (the animator depends only on the tilesets).owned_sources(running alongsideowned_ids) records which image each id came from.Game.surfaceLost/Game.surfaceRestoredcall the newinvalidateTilemapTextures/reloadTilemapTexturesmixin functions.On the issue's options: this is neither A nor B. Unlike A, the TMX is not decoded again, so runtime edits are not lost. Unlike B, no second copy of the bytes is kept. It uses B's same-id re-upload plus a renderer rebind, which B would also have needed.
Tests
test/tilemap_surface_test.zigdrives the realGame.surfaceLost/surfaceRestoredagainst a renderer that records every call to gfx's registry-style seam. It asserts:test/tilemap_mutation_test.zigadds the same cycle against the real gfxTileMapRendererWith(MockBackend)(the test renderer gets the seam). After restore,tm.texturesholds the new backend handle, and the next frame draws 5/5 tiles from it and none from the old one.test/tilemap_collection_test.zigcovers the renderer without the seam: nothing is invalidated, unloaded or re-uploaded.FakeGfx+parseTmxmove totest/tilemap_fake_gfx.zig, so both suites share them. This also brings the collection suite under 1000 lines.I checked that the tests catch a regression: with the two lifecycle calls commented out, 3 of the 6 surface tests fail.
Locally (Windows) the tilemap suites all pass.
zig build teststill fails there on the usual Windows-only errors (std.c.timespec, preview sockets, storage), which don't involve the files changed here; CI (Linux) is the gate.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.