Fix unbounded tree node growth in XYZ globe image tiles#1555
Closed
WilliamLiu-1997 wants to merge 1 commit intoNASA-AMMOS:masterfrom
Closed
Fix unbounded tree node growth in XYZ globe image tiles#1555WilliamLiu-1997 wants to merge 1 commit intoNASA-AMMOS:masterfrom
WilliamLiu-1997 wants to merge 1 commit intoNASA-AMMOS:masterfrom
Conversation
Move ensureChildrenArePreprocessed from resetFrameState/markUsedTiles to canTraverse so children are only created when the traversal decides to refine deeper. Add per-frame subtree pruning in ImageFormatPlugin to reclaim node objects that are no longer retained by the LRU cache or traversal state. Make expandChildren idempotent so pruned subtrees can be safely re-created on demand.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tree Node Memory Leak in XYZ Globe Image Tiles
Relate to #1554
Problem
When using
ImageFormatPlugin(XYZ / TMS / WMS image tile sources) on a globe, the tile node tree grows unboundedly as the camera pans. Unlike standard 3D Tiles where the tree structure is finite and defined bytileset.json, image tile trees are infinite and dynamically generated —ImageFormatPlugin.expandChildrencreates child nodes on the fly as the quadtree subdivides to meet the screen-space error target.The root cause is that
ensureChildrenArePreprocessed(tile)was called insideresetFrameState(optimized path) andmarkUsedTiles/recursivelyMarkUsed(non-optimized path). This means every tile visited during traversal unconditionally triggered child creation, even when the traversal ultimately decided not to descend. As the camera panned across the globe, children were created for every visited tile but never reclaimed — the tree grew monotonically.The leak is in node objects themselves, not in content/textures/geometry (those are managed by the LRU cache). Over time, the accumulated tile node objects cause significant memory growth that does not recover even after returning to a previous view.
Fix
The fix has two parts:
1. Lazy expansion — Move
ensureChildrenArePreprocessedfrom the "visit" phase (resetFrameState/markUsedTiles) to the "descend" phase (canTraverse). Children are only created when the traversal actually decides to refine deeper (error target not yet met). This is a change in the core traversal files and applies to all tile types.2. Subtree pruning — After each traversal,
ImageFormatPluginwalks the tree and removes subtrees where no descendant is retained (not in the LRU cache, not used, not visible, not active).expandChildrenis made idempotent (deduplicates against existing children) so that pruned subtrees can be safely re-created on demand when the camera returns. A_imageChildrenCompleteflag provides a fast path to skip the dedup check on steady-state frames.Why this only affects ImageFormatPlugin
expandChildreninpreprocessNode(on demand)expandChildrenis idempotent)Other plugins that dynamically create children (
ImplicitTilingPlugin,QuantizedMeshPlugin,ImageOverlayPlugin) do so inparseTile(after content loads), not inpreprocessNode, so they are not affected by the eager expansion inresetFrameState. Their child lifecycle is already managed through LRU eviction callbacks.