@@ -1005,6 +1005,7 @@ export class ImageOverlayPlugin {
10051005 range : null ,
10061006 target : null ,
10071007 meshInfo : new Map ( ) ,
1008+ failed : false ,
10081009 } ;
10091010
10101011 overlayInfo
@@ -1046,7 +1047,7 @@ export class ImageOverlayPlugin {
10461047
10471048 }
10481049
1049- const { tiles, overlayInfo, tileControllers, processQueue } = this ;
1050+ const { tiles, overlayInfo, tileControllers } = this ;
10501051 const { ellipsoid } = tiles ;
10511052 const { controller, tileInfo } = overlayInfo . get ( overlay ) ;
10521053 const tileController = tileControllers . get ( tile ) ;
@@ -1133,51 +1134,117 @@ export class ImageOverlayPlugin {
11331134
11341135 // if the image projection is outside the 0, 1 uvw range or there are no textures to draw in
11351136 // the tiled image set the don't allocate a texture for it.
1136- let target = null ;
11371137 if ( heightInRange && overlay . hasContent ( range ) ) {
11381138
1139- target = await processQueue
1140- . add ( { tile, overlay } , async ( ) => {
1139+ await this . _fetchTileOverlayTexture ( tile , overlay , info ) ;
11411140
1142- // check if the overlay has been disposed since starting this function
1143- if ( controller . signal . aborted || tileController . signal . aborted ) {
1141+ }
11441142
1145- return null ;
1143+ meshes . forEach ( ( mesh , i ) => {
11461144
1147- }
1145+ const array = new Float32Array ( uvs [ i ] ) ;
1146+ const attribute = new BufferAttribute ( array , 3 ) ;
1147+ info . meshInfo . set ( mesh , { attribute } ) ;
11481148
1149- // Get the texture from the overlay
1150- const regionTarget = await overlay . getTexture ( range ) ;
1149+ } ) ;
11511150
1152- // check if the overlay has been disposed since starting this function
1153- if ( controller . signal . aborted || tileController . signal . aborted ) {
1151+ }
11541152
1155- return null ;
1153+ // Queues an overlay texture fetch for the given tile, writing the result into info.target.
1154+ // Never throws — failures mark info.failed and dispatch a load-error event instead.
1155+ async _fetchTileOverlayTexture ( tile , overlay , info ) {
11561156
1157- }
1157+ const { tiles, overlayInfo, tileControllers, processQueue } = this ;
1158+ const { controller } = overlayInfo . get ( overlay ) ;
1159+ const tileController = tileControllers . get ( tile ) ;
1160+ const { range } = info ;
11581161
1159- return regionTarget ;
1162+ info . target = await processQueue
1163+ . add ( { tile, overlay } , async ( ) => {
11601164
1161- } )
1162- . catch ( err => {
1165+ // check if the overlay has been disposed since starting this function
1166+ if ( controller . signal . aborted || tileController . signal . aborted ) {
11631167
1164- if ( ! ( err instanceof PriorityQueueItemRemovedError ) ) {
1168+ return null ;
11651169
1166- throw err ;
1170+ }
11671171
1168- }
1172+ // Get the texture from the overlay
1173+ const regionTarget = await overlay . getTexture ( range ) ;
11691174
1170- } ) ;
1175+ // check if the overlay has been disposed since starting this function
1176+ if ( controller . signal . aborted || tileController . signal . aborted ) {
11711177
1172- }
1178+ return null ;
11731179
1174- info . target = target ;
1180+ }
11751181
1176- meshes . forEach ( ( mesh , i ) => {
1182+ return regionTarget ;
11771183
1178- const array = new Float32Array ( uvs [ i ] ) ;
1179- const attribute = new BufferAttribute ( array , 3 ) ;
1180- info . meshInfo . set ( mesh , { attribute } ) ;
1184+ } )
1185+ . catch ( err => {
1186+
1187+ if ( err instanceof PriorityQueueItemRemovedError ) {
1188+
1189+ return null ;
1190+
1191+ }
1192+
1193+ info . failed = true ;
1194+ tiles . dispatchEvent ( { type : 'load-error' , tile, overlay, error : err } ) ;
1195+ return null ;
1196+
1197+ } ) ;
1198+
1199+ }
1200+
1201+ /**
1202+ * Retries any overlay texture fetches that previously failed. Successfully loaded textures
1203+ * are applied to their tiles without requiring a geometry reload. Pairs with the `load-error`
1204+ * event, which fires on the `TilesRenderer` when an overlay texture fetch fails.
1205+ */
1206+ resetFailedOverlays ( ) {
1207+
1208+ const { processedTiles, overlayInfo, overlays } = this ;
1209+ const failed = [ ] ;
1210+
1211+ // Release all failed entries synchronously so their DataCache disposal
1212+ // microtasks are queued before we re-lock below.
1213+ processedTiles . forEach ( tile => {
1214+
1215+ overlays . forEach ( overlay => {
1216+
1217+ const { tileInfo } = overlayInfo . get ( overlay ) ;
1218+ const info = tileInfo . get ( tile ) ;
1219+ if ( ! info . failed ) {
1220+
1221+ return ;
1222+
1223+ }
1224+
1225+ info . failed = false ;
1226+ overlay . releaseTexture ( info . range ) ;
1227+ failed . push ( { tile, overlay, info } ) ;
1228+
1229+ } ) ;
1230+
1231+ } ) ;
1232+
1233+ // Defer to the next frame so all disposal microtasks — including nested sub-cache
1234+ // cleanup — have fully drained before re-locking.
1235+ requestAnimationFrame ( ( ) => {
1236+
1237+ failed . forEach ( ( { tile, overlay, info } ) => {
1238+
1239+ overlay . lockTexture ( info . range ) ;
1240+ this . _fetchTileOverlayTexture ( tile , overlay , info )
1241+ . then ( ( ) => {
1242+
1243+ this . _updateLayers ( tile ) ;
1244+
1245+ } ) ;
1246+
1247+ } ) ;
11811248
11821249 } ) ;
11831250
0 commit comments