Avoid using Object.keys(obj).length in hot code paths

Complexity of Object.keys(obj).length is O(n) where n is the number of
object keys. Unfortunately, JavaScript have no built-in means of
determining number of object keys in constant time. Therefore, we have
to count object keys using separate counter variable.

It may look ugly (and indeed it is), but it greatly improves smoothness
and overall feel of IITC.
This commit is contained in:
Inye 2013-08-02 20:00:03 +04:00
parent 8563498ded
commit 55c055425b
5 changed files with 28 additions and 12 deletions

View File

@ -5,9 +5,9 @@
window.debug = function() {}
window.debug.renderDetails = function() {
console.log('portals: ' + Object.keys(portals).length);
console.log('links: ' + Object.keys(links).length);
console.log('fields: ' + Object.keys(fields).length);
console.log('portals: ' + window.portalsCount);
console.log('links: ' + window.linksCount);
console.log('fields: ' + window.fieldsCount);
}
window.debug.printStackTrace = function() {

View File

@ -572,7 +572,7 @@ window.getMarker = function(ent, portalLevel, latlng, team) {
// renders a portal on the map from the given entity
window.renderPortal = function(ent) {
if(Object.keys(portals).length >= MAX_DRAWN_PORTALS && ent[0] !== selectedPortal)
if(window.portalsCount >= MAX_DRAWN_PORTALS && ent[0] !== selectedPortal)
return removeByGuid(ent[0]);
// hide low level portals on low zooms
@ -633,6 +633,7 @@ window.renderPortal = function(ent) {
removeByGuid(portalResonatorGuid(portalGuid, i));
}
delete window.portals[portalGuid];
window.portalsCount --;
if(window.selectedPortal === portalGuid) {
window.unselectOldPortal();
}
@ -642,6 +643,7 @@ window.renderPortal = function(ent) {
// enable for debugging
if(window.portals[this.options.guid]) throw('duplicate portal detected');
window.portals[this.options.guid] = this;
window.portalsCount ++;
window.renderResonators(this.options.guid, this.options.details, this);
// handles the case where a selected portal gets removed from the
@ -781,7 +783,7 @@ window.resonatorsSetStyle = function(portalGuid, resoStyle, lineStyle) {
// renders a link on the map from the given entity
window.renderLink = function(ent) {
if(Object.keys(links).length >= MAX_DRAWN_LINKS)
if(window.linksCount >= MAX_DRAWN_LINKS)
return removeByGuid(ent[0]);
// some links are constructed from portal linkedEdges data. These have no valid 'creator' data.
@ -823,11 +825,15 @@ window.renderLink = function(ent) {
if(!getPaddedBounds().intersects(poly.getBounds())) return;
poly.on('remove', function() { delete window.links[this.options.guid]; });
poly.on('remove', function() {
delete window.links[this.options.guid];
window.linksCount--;
});
poly.on('add', function() {
// enable for debugging
if(window.links[this.options.guid]) throw('duplicate link detected');
window.links[this.options.guid] = this;
window.linksCount++;
this.bringToBack();
});
poly.addTo(linksLayer);
@ -835,7 +841,7 @@ window.renderLink = function(ent) {
// renders a field on the map from a given entity
window.renderField = function(ent) {
if(Object.keys(fields).length >= MAX_DRAWN_FIELDS)
if(window.fieldsCount >= MAX_DRAWN_FIELDS)
return window.removeByGuid(ent[0]);
var old = findEntityInLeaflet(fieldsLayer, window.fields, ent[0]);
@ -921,11 +927,15 @@ window.renderField = function(ent) {
// events, thus this listener will be attached to the field. It
// doesnt matter to which element these are bound since Leaflet
// will add/remove all elements of the LayerGroup at once.
poly.on('remove', function() { delete window.fields[this.options.guid]; });
poly.on('remove', function() {
delete window.fields[this.options.guid];
window.fieldsCount--;
});
poly.on('add', function() {
// enable for debugging
if(window.fields[this.options.guid]) console.warn('duplicate field detected');
window.fields[this.options.guid] = f;
window.fieldsCount++;
this.bringToBack();
});
f.addTo(fieldsLayer);

View File

@ -41,6 +41,7 @@ window.portalRenderLimit.previousMinLevel = -1;
window.portalRenderLimit.previousZoomLevel = null;
window.portalRenderLimit.newPortalsPerLevel = new Array(MAX_PORTAL_LEVEL + 1);
window.portalRenderLimit.portalsLowerThanPrevMinLv = new Array(MAX_PORTAL_LEVEL + 1);
window.portalRenderLimit.portalsLowerThanPrevMinLvCnt = new Array(MAX_PORTAL_LEVEL + 1);
window.portalRenderLimit.init = function () {
var currentZoomLevel = map.getZoom();
@ -72,6 +73,7 @@ window.portalRenderLimit.resetCounting = function() {
window.portalRenderLimit.resetPortalsLowerThanPrevMinLv = function() {
for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
portalRenderLimit.portalsLowerThanPrevMinLv[i] = {};
portalRenderLimit.portalsLowerThanPrevMinLvCnt[i] = 0;
}
}
@ -125,6 +127,7 @@ window.portalRenderLimit.splitLowLevelPortals = function(portals) {
if(!portalOnMap && portalLevel < portalRenderLimit.previousMinLevel) {
portalRenderLimit.portalsLowerThanPrevMinLv[portalLevel][guid] = portal;
portalRenderLimit.portalsLowerThanPrevMinLvCnt[portalLevel]++;
} else {
resultPortals[guid] = portal;
}
@ -158,7 +161,7 @@ window.portalRenderLimit.setMinLevel = function() {
// Find the min portal level under render limit
while(newMinLevel > 0) {
var oldPortalCount = layerGroupLength(portalsLayers[newMinLevel - 1]);
var storedPortalCount = Object.keys(portalRenderLimit.portalsLowerThanPrevMinLv[newMinLevel - 1]).length;
var storedPortalCount = portalRenderLimit.portalsLowerThanPrevMinLvCnt[newMinLevel - 1];
var newPortalCount = Math.max(storedPortalCount, portalRenderLimit.newPortalsPerLevel[newMinLevel - 1]);
totalPortalsCount += oldPortalCount + newPortalCount;

View File

@ -245,9 +245,9 @@ window.getPaddedBounds = function() {
// cally detect if the render limit will be hit.
window.renderLimitReached = function(ratio) {
ratio = ratio || 1;
if(Object.keys(portals).length*ratio >= MAX_DRAWN_PORTALS) return true;
if(Object.keys(links).length*ratio >= MAX_DRAWN_LINKS) return true;
if(Object.keys(fields).length*ratio >= MAX_DRAWN_FIELDS) return true;
if(window.portalsCount*ratio >= MAX_DRAWN_PORTALS) return true;
if(window.linksCount*ratio >= MAX_DRAWN_LINKS) return true;
if(window.fieldsCount*ratio >= MAX_DRAWN_FIELDS) return true;
var param = { 'reached': false };
window.runHooks('checkRenderLimit', param);
return param.reached;

View File

@ -250,8 +250,11 @@ var portalsLayers, linksLayer, fieldsLayer;
// automatically kept in sync with the items on *sLayer, so never ever
// write to them.
window.portals = {};
window.portalsCount = 0;
window.links = {};
window.linksCount = 0;
window.fields = {};
window.fieldsCount = 0;
window.resonators = {};
// contain current status(on/off) of overlay layerGroups.