@@ -3,6 +3,7 @@ import { Logger } from '@map-colonies/js-logger';
33import { IDetilerClient } from '@map-colonies/detiler-client' ;
44import { inject , injectable } from 'tsyringe' ;
55import { AxiosInstance } from 'axios' ;
6+ import { TILEGRID_WORLD_CRS84 , tileToBoundingBox } from '@map-colonies/tile-calc' ;
67import { IConfig } from '../common/interfaces' ;
78import { IProjectConfig } from '../common/interfaces' ;
89import { fetchTimestampValue , timestampToUnix } from '../common/util' ;
@@ -18,13 +19,22 @@ import {
1819import { MapProvider , MapSplitterProvider , TilesStorageProvider } from './interfaces' ;
1920import { TileWithMetadata } from './types' ;
2021
22+ type SkipReason = 'tile_up_to_date' | 'cooldown' ;
23+ type ProcessReason = 'project_updated' | 'force' | 'no_detiler' | 'error_occurred' ;
24+
25+ interface PreProcessReult {
26+ shouldSkipProcessing : boolean ;
27+ reason ?: ProcessReason | SkipReason ;
28+ }
29+
2130@injectable ( )
2231export class TileProcessor {
2332 private readonly project : IProjectConfig ;
2433 private readonly forceProcess : boolean ;
2534 private readonly detilerProceedOnFailure : boolean ;
2635
2736 private readonly tilesCounter ?: client . Counter < 'status' | 'z' > ;
37+ private readonly preProcessResultsCounter ?: client . Counter < 'result' | 'z' > ;
2838 private readonly tilesDurationHistogram ?: client . Histogram < 'z' | 'kind' > ;
2939
3040 public constructor (
@@ -57,6 +67,13 @@ export class TileProcessor {
5767 labelNames : [ 'status' , 'z' ] as const ,
5868 registers : [ registry ] ,
5969 } ) ;
70+
71+ this . preProcessResultsCounter = new client . Counter ( {
72+ name : 'retiler_pre_process_results_count' ,
73+ help : 'The results of the pre process' ,
74+ labelNames : [ 'result' , 'z' ] as const ,
75+ registers : [ registry ] ,
76+ } ) ;
6077 }
6178 }
6279
@@ -66,8 +83,9 @@ export class TileProcessor {
6683 const preRenderTimestamp = Math . floor ( Date . now ( ) / MILLISECONDS_IN_SECOND ) ;
6784
6885 // check if possibly the tile processing can be skipped according to detiler
69- const shouldSkip = await this . preProcess ( tile , preRenderTimestamp ) ;
70- if ( shouldSkip ) {
86+ const { shouldSkipProcessing } = await this . preProcess ( tile , preRenderTimestamp ) ;
87+
88+ if ( shouldSkipProcessing ) {
7189 this . tilesCounter ?. inc ( { status : 'skipped' , z : tile . z } ) ;
7290 return ;
7391 }
@@ -104,16 +122,21 @@ export class TileProcessor {
104122 }
105123 }
106124
107- private async preProcess ( tile : TileWithMetadata , timestamp : number ) : Promise < boolean > {
108- const isForced = this . forceProcess || tile . force === true ;
125+ private async preProcess ( tile : TileWithMetadata , timestamp : number ) : Promise < PreProcessReult > {
126+ let preProcessTimerEnd ;
127+ let result : PreProcessReult = { shouldSkipProcessing : false } ;
109128
110- if ( this . detiler === undefined || isForced ) {
111- return false ;
112- }
129+ try {
130+ // check for forced rendering or if detiler option is off
131+ const isForced = this . forceProcess || tile . force === true ;
113132
114- const detilerGetTimerEnd = this . tilesDurationHistogram ?. startTimer ( { kind : 'detilerGet' } ) ;
133+ if ( isForced || this . detiler === undefined ) {
134+ result = { shouldSkipProcessing : false , reason : isForced ? 'force' : 'no_detiler' } ;
135+ return result ;
136+ }
137+
138+ preProcessTimerEnd = this . tilesDurationHistogram ?. startTimer ( { kind : 'pre_process' } ) ;
115139
116- try {
117140 // attempt to get latest tile details
118141 const tileDetails = await this . detiler . getTileDetails ( { kit : this . project . name , z : tile . z , x : tile . x , y : tile . y } ) ;
119142
@@ -129,20 +152,77 @@ export class TileProcessor {
129152 if ( tileDetails . renderedAt >= projectTimestamp ) {
130153 await this . detiler . setTileDetails (
131154 { kit : this . project . name , z : tile . z , x : tile . x , y : tile . y } ,
132- { hasSkipped : true , state : tile . state , timestamp }
155+ { status : 'skipped' , state : tile . state , timestamp }
133156 ) ;
134- this . logger . info ( { msg : 'skipping tile processing' , tile, tileDetails, sourceUpdatedAt : projectTimestamp } ) ;
135- return true ;
157+
158+ this . logger . info ( {
159+ msg : 'tile processing can be skipping due to tile being up do date' ,
160+ tile,
161+ tileDetails,
162+ sourceUpdatedAt : projectTimestamp ,
163+ } ) ;
164+
165+ result = { shouldSkipProcessing : true , reason : 'tile_up_to_date' } ;
166+
167+ return result ;
168+ }
169+
170+ // tile geometry in bbox
171+ const { west, south, east, north } = tileToBoundingBox ( tile , TILEGRID_WORLD_CRS84 , true ) ;
172+
173+ // time elapsed since last rendered
174+ const cooled = timestamp - tileDetails . renderedAt ;
175+
176+ // only render if the time elapsed is longer than the relavant cooldowns duration otherwise the tile is still cooling
177+ const cooldownsGenerator = this . detiler . queryCooldownsAsyncGenerator ( {
178+ enabled : true ,
179+ minZoom : tile . z ,
180+ maxZoom : tile . z ,
181+ kits : [ this . project . name ] ,
182+ area : [ west , south , east , north ] ,
183+ } ) ;
184+
185+ for await ( const cooldowns of cooldownsGenerator ) {
186+ const isCooling = cooldowns . filter ( ( cooldown ) => cooldown . duration > cooled ) . length > 0 ;
187+
188+ this . logger . info ( {
189+ msg : 'tile processing should be skipped due to active cooldown' ,
190+ tile,
191+ tileDetails,
192+ tileCooled : cooled ,
193+ cooldowns,
194+ sourceUpdatedAt : projectTimestamp ,
195+ } ) ;
196+
197+ if ( isCooling ) {
198+ await this . detiler . setTileDetails (
199+ { kit : this . project . name , z : tile . z , x : tile . x , y : tile . y } ,
200+ { status : 'cooled' , state : tile . state , timestamp }
201+ ) ;
202+
203+ result = { shouldSkipProcessing : true , reason : 'cooldown' } ;
204+
205+ return result ;
206+ }
136207 }
137208 }
138209
139- return false ;
210+ result = { shouldSkipProcessing : false , reason : 'project_updated' } ;
211+
212+ return result ;
140213 } catch ( error ) {
141214 this . logger . error ( { msg : 'an error occurred while pre processing, tile will be processed' , error } ) ;
142- return false ;
215+
216+ result = { shouldSkipProcessing : false , reason : 'error_occurred' } ;
217+
218+ return result ;
143219 } finally {
144- if ( detilerGetTimerEnd ) {
145- detilerGetTimerEnd ( ) ;
220+ this . logger . info ( { msg : 'pre processing done' , tile, result } ) ;
221+
222+ this . preProcessResultsCounter ?. inc ( { result : result . reason , z : tile . z } ) ;
223+
224+ if ( preProcessTimerEnd ) {
225+ preProcessTimerEnd ( ) ;
146226 }
147227 }
148228 }
@@ -152,18 +232,21 @@ export class TileProcessor {
152232 return ;
153233 }
154234
155- const detilerSetTimerEnd = this . tilesDurationHistogram ?. startTimer ( { kind : 'detilerSet ' } ) ;
235+ const postProcessTimerEnd = this . tilesDurationHistogram ?. startTimer ( { kind : 'post_process ' } ) ;
156236
157237 try {
158- await this . detiler . setTileDetails ( { kit : this . project . name , z : tile . z , x : tile . x , y : tile . y } , { state : tile . state , timestamp } ) ;
238+ await this . detiler . setTileDetails (
239+ { kit : this . project . name , z : tile . z , x : tile . x , y : tile . y } ,
240+ { status : 'rendered' , state : tile . state , timestamp }
241+ ) ;
159242 } catch ( error ) {
160243 this . logger . error ( { msg : 'an error occurred while post processing, skipping details set' , error } ) ;
161244 if ( ! this . detilerProceedOnFailure ) {
162245 throw error ;
163246 }
164247 } finally {
165- if ( detilerSetTimerEnd ) {
166- detilerSetTimerEnd ( ) ;
248+ if ( postProcessTimerEnd ) {
249+ postProcessTimerEnd ( ) ;
167250 }
168251 }
169252 }
0 commit comments