import * as util from '../../../util';
import * as math from '../../../math';
import Heap from '../../../heap';
import * as is from '../../../is';
import defs from './texture-cache-defs';
var defNumLayers = 1; var minLvl = -4; var maxLvl = 2; var maxZoom = 3.99; var deqRedrawThreshold = 50; var refineEleDebounceTime = 50; var disableEleImgSmoothing = true; var deqCost = 0.15; var deqAvgCost = 0.1; var deqNoDrawCost = 0.9; var deqFastCost = 0.9; var maxDeqSize = 1; var invalidThreshold = 250; var maxLayerArea = 4000 * 4000; var alwaysQueue = true; var useHighQualityEleTxrReqs = true;
var useEleTxrCaching = true;
var LayeredTextureCache = function( renderer ){
var self = this;
var r = self.renderer = renderer;
var cy = r.cy;
self.layersByLevel = {};
self.firstGet = true;
self.lastInvalidationTime = util.performanceNow() - 2*invalidThreshold;
self.skipping = false;
self.eleTxrDeqs = cy.collection();
self.scheduleElementRefinement = util.debounce( function(){
self.refineElementTextures( self.eleTxrDeqs );
self.eleTxrDeqs.unmerge( self.eleTxrDeqs );
}, refineEleDebounceTime );
r.beforeRender(function( willDraw, now ){
if( now - self.lastInvalidationTime <= invalidThreshold ){
self.skipping = true;
} else {
self.skipping = false;
}
}, r.beforeRenderPriorities.lyrTxrSkip);
var qSort = function(a, b){
return b.reqs - a.reqs;
};
self.layersQueue = new Heap( qSort );
self.setupDequeueing();
};
var LTCp = LayeredTextureCache.prototype;
var layerIdPool = 0;
var MAX_INT = Math.pow(2, 53) - 1;
LTCp.makeLayer = function( bb, lvl ){
var scale = Math.pow( 2, lvl );
var w = Math.ceil( bb.w * scale );
var h = Math.ceil( bb.h * scale );
var canvas = this.renderer.makeOffscreenCanvas(w, h);
var layer = {
id: (layerIdPool = ++layerIdPool % MAX_INT ),
bb: bb,
level: lvl,
width: w,
height: h,
canvas: canvas,
context: canvas.getContext('2d'),
eles: [],
elesQueue: [],
reqs: 0
};
var cxt = layer.context;
var dx = -layer.bb.x1;
var dy = -layer.bb.y1;
cxt.scale( scale, scale );
cxt.translate( dx, dy );
return layer;
};
LTCp.getLayers = function( eles, pxRatio, lvl ){
var self = this;
var r = self.renderer;
var cy = r.cy;
var zoom = cy.zoom();
var firstGet = self.firstGet;
self.firstGet = false;
if( lvl == null ){
lvl = Math.ceil( math.log2( zoom * pxRatio ) );
if( lvl < minLvl ){
lvl = minLvl;
} else if( zoom >= maxZoom || lvl > maxLvl ){
return null;
}
}
self.validateLayersElesOrdering( lvl, eles );
var layersByLvl = self.layersByLevel;
var scale = Math.pow( 2, lvl );
var layers = layersByLvl[ lvl ] = layersByLvl[ lvl ] || [];
var bb;
var lvlComplete = self.levelIsComplete( lvl, eles );
var tmpLayers;
var checkTempLevels = function(){
var canUseAsTmpLvl = function( l ){
self.validateLayersElesOrdering( l, eles );
if( self.levelIsComplete( l, eles ) ){
tmpLayers = layersByLvl[l];
return true;
}
};
var checkLvls = function( dir ){
if( tmpLayers ){ return; }
for( var l = lvl + dir; minLvl <= l && l <= maxLvl; l += dir ){
if( canUseAsTmpLvl(l) ){ break; }
}
};
checkLvls( +1 );
checkLvls( -1 );
for( var i = layers.length - 1; i >= 0; i-- ){
var layer = layers[i];
if( layer.invalid ){
util.removeFromArray( layers, layer );
}
}
};
if( !lvlComplete ){
checkTempLevels();
} else {
return layers;
}
var getBb = function(){
if( !bb ){
bb = math.makeBoundingBox();
for( var i = 0; i < eles.length; i++ ){
math.updateBoundingBox( bb, eles[i].boundingBox() );
}
}
return bb;
};
var makeLayer = function( opts ){
opts = opts || {};
var after = opts.after;
getBb();
var area = ( bb.w * scale ) * ( bb.h * scale );
if( area > maxLayerArea ){
return null;
}
var layer = self.makeLayer( bb, lvl );
if( after != null ){
var index = layers.indexOf( after ) + 1;
layers.splice( index, 0, layer );
} else if( opts.insert === undefined || opts.insert ){
layers.unshift( layer );
}
return layer;
};
if( self.skipping && !firstGet ){
return null;
}
var layer = null;
var maxElesPerLayer = eles.length / defNumLayers;
var allowLazyQueueing = alwaysQueue && !firstGet;
for( var i = 0; i < eles.length; i++ ){
var ele = eles[i];
var rs = ele._private.rscratch;
var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};
var existingLayer = caches[ lvl ];
if( existingLayer ){
layer = existingLayer;
continue;
}
if(
!layer
|| layer.eles.length >= maxElesPerLayer
|| !math.boundingBoxInBoundingBox( layer.bb, ele.boundingBox() )
){
layer = makeLayer({ insert: true, after: layer });
if( !layer ){ return null; }
}
if( tmpLayers || allowLazyQueueing ){
self.queueLayer( layer, ele );
} else {
self.drawEleInLayer( layer, ele, lvl, pxRatio );
}
layer.eles.push( ele );
caches[ lvl ] = layer;
}
if( tmpLayers ){ return tmpLayers;
}
if( allowLazyQueueing ){
return null;
}
return layers;
};
LTCp.getEleLevelForLayerLevel = function( lvl, pxRatio ){
return lvl;
};
LTCp.drawEleInLayer = function( layer, ele, lvl, pxRatio ){
var self = this;
var r = this.renderer;
var context = layer.context;
var bb = ele.boundingBox();
if( bb.w === 0 || bb.h === 0 || !ele.visible() ){ return; }
lvl = self.getEleLevelForLayerLevel( lvl, pxRatio );
if( disableEleImgSmoothing ){ r.setImgSmoothing( context, false ); }
if( useEleTxrCaching ){
r.drawCachedElement( context, ele, null, null, lvl, useHighQualityEleTxrReqs );
} else { r.drawElement( context, ele );
}
if( disableEleImgSmoothing ){ r.setImgSmoothing( context, true ); }
};
LTCp.levelIsComplete = function( lvl, eles ){
var self = this;
var layers = self.layersByLevel[ lvl ];
if( !layers || layers.length === 0 ){ return false; }
var numElesInLayers = 0;
for( var i = 0; i < layers.length; i++ ){
var layer = layers[i];
if( layer.reqs > 0 ){ return false; }
if( layer.invalid ){ return false; }
numElesInLayers += layer.eles.length;
}
if( numElesInLayers !== eles.length ){ return false; }
return true;
};
LTCp.validateLayersElesOrdering = function( lvl, eles ){
var layers = this.layersByLevel[ lvl ];
if( !layers ){ return; }
for( var i = 0; i < layers.length; i++ ){
var layer = layers[i];
var offset = -1;
for( var j = 0; j < eles.length; j++ ){
if( layer.eles[0] === eles[j] ){
offset = j;
break;
}
}
if( offset < 0 ){
this.invalidateLayer( layer );
continue;
}
var o = offset;
for( var j = 0; j < layer.eles.length; j++ ){
if( layer.eles[j] !== eles[o+j] ){
this.invalidateLayer( layer );
break;
}
}
}
};
LTCp.updateElementsInLayers = function( eles, update ){
var self = this;
var isEles = is.element( eles[0] );
for( var i = 0; i < eles.length; i++ ){
var req = isEles ? null : eles[i];
var ele = isEles ? eles[i] : eles[i].ele;
var rs = ele._private.rscratch;
var caches = rs.imgLayerCaches = rs.imgLayerCaches || {};
for( var l = minLvl; l <= maxLvl; l++ ){
var layer = caches[l];
if( !layer ){ continue; }
if( req && self.getEleLevelForLayerLevel( layer.level ) !== req.level ){
continue;
}
update( layer, ele, req );
}
}
};
LTCp.haveLayers = function(){
var self = this;
var haveLayers = false;
for( var l = minLvl; l <= maxLvl; l++ ){
var layers = self.layersByLevel[l];
if( layers && layers.length > 0 ){
haveLayers = true;
break;
}
}
return haveLayers;
};
LTCp.invalidateElements = function( eles ){
var self = this;
if( eles.length === 0 ){ return; }
self.lastInvalidationTime = util.performanceNow();
if( eles.length === 0 || !self.haveLayers() ){ return; }
self.updateElementsInLayers( eles, function invalAssocLayers( layer, ele, req ){
self.invalidateLayer( layer );
} );
};
LTCp.invalidateLayer = function( layer ){
this.lastInvalidationTime = util.performanceNow();
if( layer.invalid ){ return; }
var lvl = layer.level;
var eles = layer.eles;
var layers = this.layersByLevel[ lvl ];
util.removeFromArray( layers, layer );
layer.elesQueue = [];
layer.invalid = true;
if( layer.replacement ){
layer.replacement.invalid = true;
}
for( var i = 0; i < eles.length; i++ ){
var caches = eles[i]._private.rscratch.imgLayerCaches;
if( caches ){
caches[ lvl ] = null;
}
}
};
LTCp.refineElementTextures = function( eles ){
var self = this;
self.updateElementsInLayers( eles, function refineEachEle( layer, ele, req ){
var rLyr = layer.replacement;
if( !rLyr ){
rLyr = layer.replacement = self.makeLayer( layer.bb, layer.level );
rLyr.replaces = layer;
rLyr.eles = layer.eles;
}
if( !rLyr.reqs ){
for( var i = 0; i < rLyr.eles.length; i++ ){
self.queueLayer( rLyr, rLyr.eles[i] );
}
}
} );
};
LTCp.enqueueElementRefinement = function( ele ){
if( !useEleTxrCaching ){ return; }
this.eleTxrDeqs.merge( ele );
this.scheduleElementRefinement();
};
LTCp.queueLayer = function( layer, ele ){
var self = this;
var q = self.layersQueue;
var elesQ = layer.elesQueue;
var hasId = elesQ.hasId = elesQ.hasId || {};
if( layer.replacement ){ return; }
if( ele ){
if( hasId[ ele.id() ] ){
return;
}
elesQ.push( ele );
hasId[ ele.id() ] = true;
}
if( layer.reqs ){
layer.reqs++;
q.updateItem( layer );
} else {
layer.reqs = 1;
q.push( layer );
}
};
LTCp.dequeue = function( pxRatio ){
var self = this;
var q = self.layersQueue;
var deqd = [];
var eleDeqs = 0;
while( eleDeqs < maxDeqSize ){
if( q.size() === 0 ){ break; }
var layer = q.peek();
if( layer.replacement ){
q.pop();
continue;
}
if( layer.replaces && layer !== layer.replaces.replacement ){
q.pop();
continue;
}
if( layer.invalid ){
q.pop();
continue;
}
var ele = layer.elesQueue.shift();
if( ele ){
self.drawEleInLayer( layer, ele, layer.level, pxRatio );
eleDeqs++;
}
if( deqd.length === 0 ){
deqd.push( true );
}
if( layer.elesQueue.length === 0 ){
q.pop();
layer.reqs = 0;
if( layer.replaces ){
self.applyLayerReplacement( layer );
}
self.requestRedraw();
}
}
return deqd;
};
LTCp.applyLayerReplacement = function( layer ){
var self = this;
var layersInLevel = self.layersByLevel[ layer.level ];
var replaced = layer.replaces;
var index = layersInLevel.indexOf( replaced );
if( index < 0 || replaced.invalid ){
return;
}
layersInLevel[ index ] = layer;
for( var i = 0; i < layer.eles.length; i++ ){
var _p = layer.eles[i]._private;
var cache = _p.imgLayerCaches = _p.imgLayerCaches || {};
if( cache ){
cache[ layer.level ] = layer;
}
}
self.requestRedraw();
};
LTCp.requestRedraw = util.debounce( function(){
var r = this.renderer;
r.redrawHint( 'eles', true );
r.redrawHint( 'drag', true );
r.redraw();
}, 100 );
LTCp.setupDequeueing = defs.setupDequeueing({
deqRedrawThreshold: deqRedrawThreshold,
deqCost: deqCost,
deqAvgCost: deqAvgCost,
deqNoDrawCost: deqNoDrawCost,
deqFastCost: deqFastCost,
deq: function( self, pxRatio ){
return self.dequeue( pxRatio );
},
onDeqd: util.noop,
shouldRedraw: util.trueify,
priority: function( self ){
return self.renderer.beforeRenderPriorities.lyrTxrDeq;
}
});
export default LayeredTextureCache;