basant307/AI_Governance_Project
048
1import * as util from '../../../util/index.mjs';2import * as math from '../../../math.mjs';3import Heap from '../../../heap.mjs';4import * as is from '../../../is.mjs';5import defs from './texture-cache-defs.mjs';6 7var defNumLayers = 1; // default number of layers to use8var minLvl = -4; // when scaling smaller than that we don't need to re-render9var maxLvl = 2; // when larger than this scale just render directly (caching is not helpful)10var maxZoom = 3.99; // beyond this zoom level, layered textures are not used11var deqRedrawThreshold = 50; // time to batch redraws together from dequeueing to allow more dequeueing calcs to happen in the meanwhile12var refineEleDebounceTime = 50; // time to debounce sharper ele texture updates13var disableEleImgSmoothing = true; // when drawing eles on layers from an ele cache ; crisper and more performant when true14var deqCost = 0.15; // % of add'l rendering cost allowed for dequeuing ele caches each frame15var deqAvgCost = 0.1; // % of add'l rendering cost compared to average overall redraw time16var deqNoDrawCost = 0.9; // % of avg frame time that can be used for dequeueing when not drawing17var deqFastCost = 0.9; // % of frame time to be used when >60fps18var maxDeqSize = 1; // number of eles to dequeue and render at higher texture in each batch19var invalidThreshold = 250; // time threshold for disabling b/c of invalidations20var maxLayerArea = 4000 * 4000; // layers can't be bigger than this21var maxLayerDim = 32767; // maximum size for the width/height of layer canvases22var alwaysQueue = true; // never draw all the layers in a level on a frame; draw directly until all dequeued23var useHighQualityEleTxrReqs = true; // whether to use high quality ele txr requests (generally faster and cheaper in the longterm)24 25var useEleTxrCaching = true; // whether to use individual ele texture caching underneath this cache26 27// var log = function(){ console.log.apply( console, arguments ); };28 29var LayeredTextureCache = function( renderer ){30 var self = this;31 var r = self.renderer = renderer;32 var cy = r.cy;33 34 self.layersByLevel = {}; // e.g. 2 => [ layer1, layer2, ..., layerN ]35 36 self.firstGet = true;37 38 self.lastInvalidationTime = util.performanceNow() - 2*invalidThreshold;39 40 self.skipping = false;41 42 self.eleTxrDeqs = cy.collection();43 44 self.scheduleElementRefinement = util.debounce( function(){45 self.refineElementTextures( self.eleTxrDeqs );46 47 self.eleTxrDeqs.unmerge( self.eleTxrDeqs );48 }, refineEleDebounceTime );49 50 r.beforeRender(function( willDraw, now ){51 if( now - self.lastInvalidationTime <= invalidThreshold ){52 self.skipping = true;53 } else {54 self.skipping = false;55 }56 }, r.beforeRenderPriorities.lyrTxrSkip);57 58 var qSort = function(a, b){59 return b.reqs - a.reqs;60 };61 62 self.layersQueue = new Heap( qSort );63 64 self.setupDequeueing();65};66 67var LTCp = LayeredTextureCache.prototype;68 69var layerIdPool = 0;70var MAX_INT = Math.pow(2, 53) - 1;71 72LTCp.makeLayer = function( bb, lvl ){73 var scale = Math.pow( 2, lvl );74 75 var w = Math.ceil( bb.w * scale );76 var h = Math.ceil( bb.h * scale );77 78 var canvas = this.renderer.makeOffscreenCanvas(w, h);79 80 var layer = {81 id: (layerIdPool = ++layerIdPool % MAX_INT ),82 bb: bb,83 level: lvl,84 width: w,85 height: h,86 canvas: canvas,87 context: canvas.getContext('2d'),88 eles: [],89 elesQueue: [],90 reqs: 091 };92 93 // log('make layer %s with w %s and h %s and lvl %s', layer.id, layer.width, layer.height, layer.level);94 95 var cxt = layer.context;96 var dx = -layer.bb.x1;97 var dy = -layer.bb.y1;98 99 // do the transform on creation to save cycles (it's the same for all eles)100 cxt.scale( scale, scale );101 cxt.translate( dx, dy );102 103 return layer;104};105 106LTCp.getLayers = function( eles, pxRatio, lvl ){107 var self = this;108 var r = self.renderer;109 var cy = r.cy;110 var zoom = cy.zoom();111 var firstGet = self.firstGet;112 113 self.firstGet = false;114 115 // log('--\nget layers with %s eles', eles.length);116 //log eles.map(function(ele){ return ele.id() }) );117 118 if( lvl == null ){119 lvl = Math.ceil( math.log2( zoom * pxRatio ) );120 121 if( lvl < minLvl ){122 lvl = minLvl;123 } else if( zoom >= maxZoom || lvl > maxLvl ){124 return null;125 }126 }127 128 self.validateLayersElesOrdering( lvl, eles );129 130 var layersByLvl = self.layersByLevel;131 var scale = Math.pow( 2, lvl );132 var layers = layersByLvl[ lvl ] = layersByLvl[ lvl ] || [];133 var bb;134 135 var lvlComplete = self.levelIsComplete( lvl, eles );136 var tmpLayers;137 138 var checkTempLevels = function(){139 var canUseAsTmpLvl = function( l ){140 self.validateLayersElesOrdering( l, eles );141 142 if( self.levelIsComplete( l, eles ) ){143 tmpLayers = layersByLvl[l];144 return true;145 }146 };147 148 var checkLvls = function( dir ){149 if( tmpLayers ){ return; }150 151 for( var l = lvl + dir; minLvl <= l && l <= maxLvl; l += dir ){152 if( canUseAsTmpLvl(l) ){ break; }153 }154 };155 156 checkLvls( +1 );157 checkLvls( -1 );158 159 // remove the invalid layers; they will be replaced as needed later in this function160 for( var i = layers.length - 1; i >= 0; i-- ){161 var layer = layers[i];162 163 if( layer.invalid ){164 util.removeFromArray( layers, layer );165 }166 }167 };168 169 if( !lvlComplete ){170 // if the current level is incomplete, then use the closest, best quality layerset temporarily171 // and later queue the current layerset so we can get the proper quality level soon172 173 checkTempLevels();174 175 } else {176 // log('level complete, using existing layers\n--');177 return layers;178 }179 180 var getBb = function(){181 if( !bb ){182 bb = math.makeBoundingBox();183 184 for( var i = 0; i < eles.length; i++ ){185 math.updateBoundingBox( bb, eles[i].boundingBox() );186 }187 }188 189 return bb;190 };191 192 var makeLayer = function( opts ){193 opts = opts || {};194 195 var after = opts.after;196 197 getBb();198 199 var w = Math.ceil( bb.w * scale );200 var h = Math.ceil( bb.h * scale );201 202 if( w > maxLayerDim || h > maxLayerDim ){203 return null;204 }205 206 var area = w * h;207 208 if( area > maxLayerArea ){209 return null;210 }211 212 var layer = self.makeLayer( bb, lvl );213 214 if( after != null ){215 var index = layers.indexOf( after ) + 1;216 217 layers.splice( index, 0, layer );218 } else if( opts.insert === undefined || opts.insert ){219 // no after specified => first layer made so put at start220 layers.unshift( layer );221 }222 223 // if( tmpLayers ){224 //self.queueLayer( layer );225 // }226 227 return layer;228 };229 230 if( self.skipping && !firstGet ){231 // log('skip layers');232 return null;233 }234 235 // log('do layers');236 237 var layer = null;238 var maxElesPerLayer = eles.length / defNumLayers;239 var allowLazyQueueing = alwaysQueue && !firstGet;240 241 for( var i = 0; i < eles.length; i++ ){242 var ele = eles[i];243 var rs = ele._private.rscratch;244 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};245 246 // log('look at ele', ele.id());247 248 var existingLayer = caches[ lvl ];249 250 if( existingLayer ){251 // reuse layer for later eles252 // log('reuse layer for', ele.id());253 layer = existingLayer;254 continue;255 }256 257 if(258 !layer259 || layer.eles.length >= maxElesPerLayer260 || !math.boundingBoxInBoundingBox( layer.bb, ele.boundingBox() )261 ){262 // log('make new layer for ele %s', ele.id());263 264 layer = makeLayer({ insert: true, after: layer });265 266 // if now layer can be built then we can't use layers at this level267 if( !layer ){ return null; }268 269 // log('new layer with id %s', layer.id);270 }271 272 if( tmpLayers || allowLazyQueueing ){273 // log('queue ele %s in layer %s', ele.id(), layer.id);274 self.queueLayer( layer, ele );275 } else {276 // log('draw ele %s in layer %s', ele.id(), layer.id);277 self.drawEleInLayer( layer, ele, lvl, pxRatio );278 }279 280 layer.eles.push( ele );281 282 caches[ lvl ] = layer;283 }284 285 // log('--');286 287 if( tmpLayers ){ // then we only queued the current layerset and can't draw it yet288 return tmpLayers;289 }290 291 if( allowLazyQueueing ){292 // log('lazy queue level', lvl);293 return null;294 }295 296 return layers;297};298 299// a layer may want to use an ele cache of a higher level to avoid blurriness300// so the layer level might not equal the ele level301LTCp.getEleLevelForLayerLevel = function( lvl, pxRatio ){302 return lvl;303};304 305LTCp.drawEleInLayer = function( layer, ele, lvl, pxRatio ){306 var self = this;307 var r = this.renderer;308 var context = layer.context;309 var bb = ele.boundingBox();310 311 if( bb.w === 0 || bb.h === 0 || !ele.visible() ){ return; }312 313 lvl = self.getEleLevelForLayerLevel( lvl, pxRatio );314 315 if( disableEleImgSmoothing ){ r.setImgSmoothing( context, false ); }316 317 if( useEleTxrCaching ){318 r.drawCachedElement( context, ele, null, null, lvl, useHighQualityEleTxrReqs );319 } else { // if the element is not cacheable, then draw directly320 r.drawElement( context, ele );321 }322 323 if( disableEleImgSmoothing ){ r.setImgSmoothing( context, true ); }324};325 326LTCp.levelIsComplete = function( lvl, eles ){327 var self = this;328 var layers = self.layersByLevel[ lvl ];329 330 if( !layers || layers.length === 0 ){ return false; }331 332 var numElesInLayers = 0;333 334 for( var i = 0; i < layers.length; i++ ){335 var layer = layers[i];336 337 // if there are any eles needed to be drawn yet, the level is not complete338 if( layer.reqs > 0 ){ return false; }339 340 // if the layer is invalid, the level is not complete341 if( layer.invalid ){ return false; }342 343 numElesInLayers += layer.eles.length;344 }345 346 // we should have exactly the number of eles passed in to be complete347 if( numElesInLayers !== eles.length ){ return false; }348 349 return true;350};351 352LTCp.validateLayersElesOrdering = function( lvl, eles ){353 var layers = this.layersByLevel[ lvl ];354 355 if( !layers ){ return; }356 357 // if in a layer the eles are not in the same order, then the layer is invalid358 // (i.e. there is an ele in between the eles in the layer)359 360 for( var i = 0; i < layers.length; i++ ){361 var layer = layers[i];362 var offset = -1;363 364 // find the offset365 for( var j = 0; j < eles.length; j++ ){366 if( layer.eles[0] === eles[j] ){367 offset = j;368 break;369 }370 }371 372 if( offset < 0 ){373 // then the layer has nonexistent elements and is invalid374 this.invalidateLayer( layer );375 continue;376 }377 378 // the eles in the layer must be in the same continuous order, else the layer is invalid379 380 var o = offset;381 382 for( var j = 0; j < layer.eles.length; j++ ){383 if( layer.eles[j] !== eles[o+j] ){384 // log('invalidate based on ordering', layer.id);385 386 this.invalidateLayer( layer );387 break;388 }389 }390 }391};392 393LTCp.updateElementsInLayers = function( eles, update ){394 var self = this;395 var isEles = is.element( eles[0] );396 397 // collect udpated elements (cascaded from the layers) and update each398 // layer itself along the way399 for( var i = 0; i < eles.length; i++ ){400 var req = isEles ? null : eles[i];401 var ele = isEles ? eles[i] : eles[i].ele;402 var rs = ele._private.rscratch;403 var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};404 405 for( var l = minLvl; l <= maxLvl; l++ ){406 var layer = caches[l];407 408 if( !layer ){ continue; }409 410 // if update is a request from the ele cache, then it affects only411 // the matching level412 if( req && self.getEleLevelForLayerLevel( layer.level ) !== req.level ){413 continue;414 }415 416 update( layer, ele, req );417 }418 }419};420 421LTCp.haveLayers = function(){422 var self = this;423 var haveLayers = false;424 425 for( var l = minLvl; l <= maxLvl; l++ ){426 var layers = self.layersByLevel[l];427 428 if( layers && layers.length > 0 ){429 haveLayers = true;430 break;431 }432 }433 434 return haveLayers;435};436 437LTCp.invalidateElements = function( eles ){438 var self = this;439 440 if( eles.length === 0 ){ return; }441 442 self.lastInvalidationTime = util.performanceNow();443 444 // log('update invalidate layer time from eles');445 446 if( eles.length === 0 || !self.haveLayers() ){ return; }447 448 self.updateElementsInLayers( eles, function invalAssocLayers( layer, ele, req ){449 self.invalidateLayer( layer );450 } );451};452 453LTCp.invalidateLayer = function( layer ){454 // log('update invalidate layer time');455 456 this.lastInvalidationTime = util.performanceNow();457 458 if( layer.invalid ){ return; } // save cycles459 460 var lvl = layer.level;461 var eles = layer.eles;462 var layers = this.layersByLevel[ lvl ];463 464 // log('invalidate layer', layer.id );465 466 util.removeFromArray( layers, layer );467 // layer.eles = [];468 469 layer.elesQueue = [];470 471 layer.invalid = true;472 473 if( layer.replacement ){474 layer.replacement.invalid = true;475 }476 477 for( var i = 0; i < eles.length; i++ ){478 var caches = eles[i]._private.rscratch.imgLayerCaches;479 480 if( caches ){481 caches[ lvl ] = null;482 }483 }484};485 486LTCp.refineElementTextures = function( eles ){487 var self = this;488 489 // log('refine', eles.length);490 491 self.updateElementsInLayers( eles, function refineEachEle( layer, ele, req ){492 var rLyr = layer.replacement;493 494 if( !rLyr ){495 rLyr = layer.replacement = self.makeLayer( layer.bb, layer.level );496 rLyr.replaces = layer;497 rLyr.eles = layer.eles;498 499 // log('make replacement layer %s for %s with level %s', rLyr.id, layer.id, rLyr.level);500 }501 502 if( !rLyr.reqs ){503 for( var i = 0; i < rLyr.eles.length; i++ ){504 self.queueLayer( rLyr, rLyr.eles[i] );505 }506 507 // log('queue replacement layer refinement', rLyr.id);508 }509 } );510};511 512LTCp.enqueueElementRefinement = function( ele ){513 if( !useEleTxrCaching ){ return; }514 515 this.eleTxrDeqs.merge( ele );516 this.scheduleElementRefinement();517};518 519LTCp.queueLayer = function( layer, ele ){520 var self = this;521 var q = self.layersQueue;522 var elesQ = layer.elesQueue;523 var hasId = elesQ.hasId = elesQ.hasId || {};524 525 // if a layer is going to be replaced, queuing is a waste of time526 if( layer.replacement ){ return; }527 528 if( ele ){529 if( hasId[ ele.id() ] ){530 return;531 }532 533 elesQ.push( ele );534 hasId[ ele.id() ] = true;535 }536 537 if( layer.reqs ){538 layer.reqs++;539 540 q.updateItem( layer );541 } else {542 layer.reqs = 1;543 544 q.push( layer );545 }546};547 548LTCp.dequeue = function( pxRatio ){549 var self = this;550 var q = self.layersQueue;551 var deqd = [];552 var eleDeqs = 0;553 554 while( eleDeqs < maxDeqSize ){555 if( q.size() === 0 ){ break; }556 557 var layer = q.peek();558 559 // if a layer has been or will be replaced, then don't waste time with it560 if( layer.replacement ){561 // log('layer %s in queue skipped b/c it already has a replacement', layer.id);562 q.pop();563 continue;564 }565 566 // if this is a replacement layer that has been superceded, then forget it567 if( layer.replaces && layer !== layer.replaces.replacement ){568 // log('layer is no longer the most uptodate replacement; dequeued', layer.id)569 q.pop();570 continue;571 }572 573 if( layer.invalid ){574 // log('replacement layer %s is invalid; dequeued', layer.id);575 q.pop();576 continue;577 }578 579 var ele = layer.elesQueue.shift();580 581 if( ele ){582 // log('dequeue layer %s', layer.id);583 584 self.drawEleInLayer( layer, ele, layer.level, pxRatio );585 586 eleDeqs++;587 }588 589 if( deqd.length === 0 ){590 // we need only one entry in deqd to queue redrawing etc591 deqd.push( true );592 }593 594 // if the layer has all its eles done, then remove from the queue595 if( layer.elesQueue.length === 0 ){596 q.pop();597 598 layer.reqs = 0;599 600 // log('dequeue of layer %s complete', layer.id);601 602 // when a replacement layer is dequeued, it replaces the old layer in the level603 if( layer.replaces ){604 self.applyLayerReplacement( layer );605 }606 607 self.requestRedraw();608 }609 }610 611 return deqd;612};613 614LTCp.applyLayerReplacement = function( layer ){615 var self = this;616 var layersInLevel = self.layersByLevel[ layer.level ];617 var replaced = layer.replaces;618 var index = layersInLevel.indexOf( replaced );619 620 // if the replaced layer is not in the active list for the level, then replacing621 // refs would be a mistake (i.e. overwriting the true active layer)622 if( index < 0 || replaced.invalid ){623 // log('replacement layer would have no effect', layer.id);624 return;625 }626 627 layersInLevel[ index ] = layer; // replace level ref628 629 // replace refs in eles630 for( var i = 0; i < layer.eles.length; i++ ){631 var _p = layer.eles[i]._private;632 var cache = _p.imgLayerCaches = _p.imgLayerCaches || {};633 634 if( cache ){635 cache[ layer.level ] = layer;636 }637 }638 639 // log('apply replacement layer %s over %s', layer.id, replaced.id);640 641 self.requestRedraw();642};643 644LTCp.requestRedraw = util.debounce( function(){645 var r = this.renderer;646 647 r.redrawHint( 'eles', true );648 r.redrawHint( 'drag', true );649 r.redraw();650}, 100 );651 652LTCp.setupDequeueing = defs.setupDequeueing({653 deqRedrawThreshold: deqRedrawThreshold,654 deqCost: deqCost,655 deqAvgCost: deqAvgCost,656 deqNoDrawCost: deqNoDrawCost,657 deqFastCost: deqFastCost,658 deq: function( self, pxRatio ){659 return self.dequeue( pxRatio );660 },661 onDeqd: util.noop,662 shouldRedraw: util.trueify,663 priority: function( self ){664 return self.renderer.beforeRenderPriorities.lyrTxrDeq;665 }666});667 668export default LayeredTextureCache;669 