Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/core/renderer/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,19 @@ potentially causing brief gaps during rapid movement.
Only applies when `optimizedLoadStrategy` is enabled.


### .loadParents

```js
loadParents: boolean
```

**Experimental.** When `true`, parent tiles are queued for download and displayed as a
fallback while children are loading — similar to the behavior of the standard load
strategy. Increases memory usage but provides smoother transitions on first load.

Only applies when `optimizedLoadStrategy` is enabled.


### .maxTilesProcessed

```js
Expand Down
1 change: 1 addition & 0 deletions src/core/renderer/tiles/TilesRendererBase.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class TilesRendererBase<TEventMap extends TilesRendererBaseEventMap = Til
displayActiveTiles : boolean;
maxDepth : number;
loadSiblings : boolean;
loadParents : boolean;
optimizedLoadStrategy : boolean;
maxTilesProcessed : number;

Expand Down
24 changes: 22 additions & 2 deletions src/core/renderer/tiles/TilesRendererBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,21 @@ const optimizedPriorityCallback = ( a, b ) => {
// load internal tile sets first
return a.internal.hasUnrenderableContent ? 1 : - 1;

} else if ( a.traversal.error !== b.traversal.error ) {

// load the tile with the higher error
return a.traversal.error > b.traversal.error ? 1 : - 1;

} else if ( a.traversal.distanceFromCamera !== b.traversal.distanceFromCamera ) {

// load closer tiles first
return a.traversal.distanceFromCamera > b.traversal.distanceFromCamera ? - 1 : 1;

} else if ( a.internal.depthFromRenderedParent !== b.internal.depthFromRenderedParent ) {

// when distance is equal (e.g. camera inside bounds), load shallower tiles first
return a.internal.depthFromRenderedParent > b.internal.depthFromRenderedParent ? - 1 : 1;

}

return 0;
Expand Down Expand Up @@ -569,6 +579,16 @@ export class TilesRendererBase {
*/
this.loadSiblings = true;

/**
* **Experimental.** When `true`, parent tiles are queued for download and displayed as a
* fallback while children are loading — similar to the behavior of the standard load
* strategy. Increases memory usage but provides smoother transitions on first load.
*
* Only applies when `optimizedLoadStrategy` is enabled.
* @type {boolean}
*/
this.loadParents = false;

/**
* The number of tiles to process immediately when traversing the tile set to determine
* what to render. Lower numbers prevent frame hiccups caused by processing too many tiles
Expand Down Expand Up @@ -766,7 +786,7 @@ export class TilesRendererBase {
update() {

// load root
const { lruCache, usedSet, stats, root, downloadQueue, parseQueue, processNodeQueue, optimizedLoadStrategy } = this;
const { lruCache, usedSet, stats, root, downloadQueue, parseQueue, processNodeQueue, optimizedLoadStrategy, loadParents } = this;
if ( this.rootLoadingState === UNLOADED ) {

this.rootLoadingState = LOADING;
Expand Down Expand Up @@ -864,7 +884,7 @@ export class TilesRendererBase {
usedSet.clear();

// assign the correct callbacks
const priorityCallback = optimizedLoadStrategy ? optimizedPriorityCallback : defaultPriorityCallback;
const priorityCallback = ( optimizedLoadStrategy && ! loadParents ) ? optimizedPriorityCallback : defaultPriorityCallback;
downloadQueue.priorityCallback = priorityCallback;
parseQueue.priorityCallback = priorityCallback;

Expand Down
45 changes: 45 additions & 0 deletions src/core/renderer/tiles/optimizedTraverseFunctions.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function resetFrameState( tile, renderer ) {
tile.traversal.error = Infinity;
tile.traversal.distanceFromCamera = Infinity;
tile.traversal.allChildrenReady = false;
tile.traversal.allChildrenLoaded = false;
tile.traversal.kicked = false;
tile.traversal.allUsedChildrenProcessed = false;

Expand Down Expand Up @@ -308,6 +309,31 @@ function markUsedSetLeaves( tile, renderer ) {

}

// compute content-readiness so markVisibleTiles can stop traversal at this tile when
// loadParents is enabled and children haven't finished loading their content yet.
// only computed when anyChildrenUsed — leaf tiles retain allChildrenLoaded = false
// (from resetFrameState) so parents correctly see them as not yet loaded.
let allChildrenLoaded = true;
for ( let i = 0, l = children.length; i < l; i ++ ) {

const c = children[ i ];
if ( isUsedThisFrame( c, frameCount ) ) {

const childCanDisplay = ! canUnconditionallyRefine( c );
const childContentReady = ! c.internal.hasContent || isDownloadFinished( c.internal.loadingState );
const childIsReady = ( childCanDisplay && childContentReady ) || c.traversal.allChildrenLoaded;
if ( ! childIsReady ) {

allChildrenLoaded = false;

}

}

}

tile.traversal.allChildrenLoaded = allChildrenLoaded;

}

// save whether any children are processed
Expand Down Expand Up @@ -340,6 +366,16 @@ function markVisibleTiles( tile, renderer ) {
const hasContent = tile.internal.hasContent;
const loadedContent = isDownloadFinished( tile.internal.loadingState ) && hasContent;
const children = tile.children;

// When loading parent tiles as fallbacks: if children aren't content-ready yet, mark this tile
// as a leaf so it is displayed as a placeholder while children load (mirrors legacy behavior).
// allChildrenLoaded was computed bottom-up in markUsedSetLeaves so it can be checked before recursing.
if ( renderer.loadParents && ! tile.traversal.allChildrenLoaded && ! canUnconditionallyRefine( tile ) ) {

tile.traversal.isLeaf = true;

}

if ( tile.traversal.isLeaf ) {

// if we're allowed to stop at this tile then mark it as active and allow any previously active tiles to
Expand Down Expand Up @@ -440,6 +476,15 @@ function toggleTiles( tile, renderer ) {

}

// when loading parent tiles as fallbacks, keep all used tiles downloaded
// regardless of active state so they are available to display while children load
if ( renderer.loadParents && tile.internal.hasContent ) {

renderer.markTileUsed( tile );
renderer.queueTileForDownload( tile );

}

// keep tiles with virtual children retained in the LRU cache so the content is
// available to regenerate virtual children if the overlay configuration changes.
if ( tile.internal.virtualChildCount > 0 && tile.internal.hasContent ) {
Expand Down
Loading