On older machines loading high portal dense areas this check is using 70-90% of CPU in Object.keys(cache).length, leading to resource starvation, and refresh times up to 10 times longer. The LRU cache high-water mark is checked every time we receive portal state from the intel servers to see if we need to do GC. In my case this peaks out 500-1000 portal adds per second on an aging laptop with the existing code with chrome-unstable, and minimal IITC plugins. GC itself (ignoring v8 GC) was only taking 15ms, amortized to about 15usec a portal. Unforutnately the Object.keys(cache).length check was taking 2-3ms each portal add, and 70-90% of the main thread was spent there, grinding older machines to a halt. I've just added a counter to track adds to the cache, and am updating it with the real cache size on GC (the two checks are almost always identical in higher density areas, and won't lead to performance issues if cache adds are over- estimated). Effects: - pushPortalGuidPositionCache goes from using the majority of CPU when loading dense areas to almost nothing. - Older machines can actually view western europe without grinding the browser to a halt. - the cache itself is now hitting GC_LIMIT in less than 100ms on my old laptop. (I think increasing GC_LIMIT to 80,000 and GC_KEEP to 40,000 is going to put less load on the Javascript engine GC, and have the cache be useful still, but I left that out of the PR as it's slightly more subjective) - v8's GC is being slammed slightly less often than normal (it's still thrashing, but this isn't the main culprit) The patch has been tested by about half a dozen people using different blends of IITC plugins on both Chrome, Firefox and across different OSs and with very different hardware. Reports ranged from minor to (more often) huge improvements in load speed and responsiveness in high density areas.
189 lines
5.5 KiB
JavaScript
189 lines
5.5 KiB
JavaScript
/// PORTAL DATA TOOLS ///////////////////////////////////////////////////
|
|
// misc functions to get portal info
|
|
|
|
// search through the links data for all that link from or to a portal. returns an object with separate lists of in
|
|
// and out links. may or may not be as accurate as the portal details, depending on how much data the API returns
|
|
window.getPortalLinks = function(guid) {
|
|
|
|
var links = { in: [], out: [] };
|
|
|
|
$.each(window.links, function(g,l) {
|
|
var d = l.options.data;
|
|
|
|
if (d.oGuid == guid) {
|
|
links.out.push(g);
|
|
}
|
|
if (d.dGuid == guid) {
|
|
links.in.push(g);
|
|
}
|
|
});
|
|
|
|
return links;
|
|
}
|
|
|
|
window.getPortalLinksCount = function(guid) {
|
|
var links = getPortalLinks(guid);
|
|
return links.in.length+links.out.length;
|
|
}
|
|
|
|
|
|
// search through the fields for all that reference a portal
|
|
window.getPortalFields = function(guid) {
|
|
var fields = [];
|
|
|
|
$.each(window.fields, function(g,f) {
|
|
var d = f.options.data;
|
|
|
|
if ( d.points[0].guid == guid
|
|
|| d.points[1].guid == guid
|
|
|| d.points[2].guid == guid ) {
|
|
|
|
fields.push(g);
|
|
}
|
|
});
|
|
|
|
return fields;
|
|
}
|
|
|
|
window.getPortalFieldsCount = function(guid) {
|
|
var fields = getPortalFields(guid);
|
|
return fields.length;
|
|
}
|
|
|
|
|
|
// find the lat/lon for a portal, using any and all available data
|
|
// (we have the list of portals, the cached portal details, plus links and fields as sources of portal locations)
|
|
window.findPortalLatLng = function(guid) {
|
|
if (window.portals[guid]) {
|
|
return window.portals[guid].getLatLng();
|
|
}
|
|
|
|
// not found in portals - try the cached (and possibly stale) details - good enough for location
|
|
var details = portalDetail.get(guid);
|
|
if (details) {
|
|
return L.latLng (details.latE6/1E6, details.lngE6/1E6);
|
|
}
|
|
|
|
// now try searching through fields
|
|
for (var fguid in window.fields) {
|
|
var f = window.fields[fguid].options.data;
|
|
|
|
for (var i in f.points) {
|
|
if (f.points[i].guid == guid) {
|
|
return L.latLng (f.points[i].latE6/1E6, f.points[i].lngE6/1E6);
|
|
}
|
|
}
|
|
}
|
|
|
|
// and finally search through links
|
|
for (var lguid in window.links) {
|
|
var l = window.links[lguid].options.data;
|
|
if (l.oGuid == guid) {
|
|
return L.latLng (l.oLatE6/1E6, l.oLngE6/1E6);
|
|
}
|
|
if (l.dGuid == guid) {
|
|
return L.latLng (l.dLatE6/1E6, l.dLngE6/1E6);
|
|
}
|
|
}
|
|
|
|
// no luck finding portal lat/lng
|
|
return undefined;
|
|
};
|
|
|
|
|
|
(function() {
|
|
var cache = {};
|
|
var cache_level = 0;
|
|
var GC_LIMIT = 5000; // run garbage collector when cache has more that 5000 items
|
|
var GC_KEEP = 4000; // keep the 4000 most recent items
|
|
|
|
window.findPortalGuidByPositionE6 = function(latE6, lngE6) {
|
|
var item = cache[latE6+","+lngE6];
|
|
if(item) return item[0];
|
|
|
|
// now try searching through currently rendered portals
|
|
for(var guid in window.portals) {
|
|
var data = window.portals[guid].options.data;
|
|
if(data.latE6 == latE6 && data.lngE6 == lngE6) return guid;
|
|
}
|
|
|
|
// now try searching through fields
|
|
for(var fguid in window.fields) {
|
|
var points = window.fields[fguid].options.data.points;
|
|
|
|
for(var i in points) {
|
|
var point = points[i];
|
|
if(point.latE6 == latE6 && point.lngE6 == lngE6) return point.guid;
|
|
}
|
|
}
|
|
|
|
// and finally search through links
|
|
for(var lguid in window.links) {
|
|
var l = window.links[lguid].options.data;
|
|
if(l.oLatE6 == latE6 && l.oLngE6 == lngE6) return l.oGuid;
|
|
if(l.dLatE6 == latE6 && l.dLngE6 == lngE6) return l.dGuid;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
window.pushPortalGuidPositionCache = function(guid, latE6, lngE6) {
|
|
cache[latE6+","+lngE6] = [guid, Date.now()];
|
|
cache_level += 1;
|
|
|
|
if(cache_level > GC_LIMIT) {
|
|
Object.keys(cache) // get all latlngs
|
|
.map(function(latlng) { return [latlng, cache[latlng][1]]; }) // map them to [latlng, timestamp]
|
|
.sort(function(a,b) { return b[1] - a[1]; }) // sort them
|
|
.slice(GC_KEEP) // drop the MRU
|
|
.forEach(function(item) { delete cache[item[0]] }); // delete the rest
|
|
cache_level = Object.keys(cache).length
|
|
}
|
|
}
|
|
})();
|
|
|
|
|
|
// get the AP gains from a portal, based only on the brief summary data from portals, links and fields
|
|
// not entirely accurate - but available for all portals on the screen
|
|
window.getPortalApGain = function(guid) {
|
|
|
|
var p = window.portals[guid];
|
|
if (p) {
|
|
var data = p.options.data;
|
|
|
|
var linkCount = getPortalLinksCount(guid);
|
|
var fieldCount = getPortalFieldsCount(guid);
|
|
|
|
var result = portalApGainMaths(data.resCount, linkCount, fieldCount);
|
|
return result;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// given counts of resonators, links and fields, calculate the available AP
|
|
// doesn't take account AP for resonator upgrades or AP for adding mods
|
|
window.portalApGainMaths = function(resCount, linkCount, fieldCount) {
|
|
|
|
var deployAp = (8-resCount)*DEPLOY_RESONATOR;
|
|
if (resCount == 0) deployAp += CAPTURE_PORTAL;
|
|
if (resCount != 8) deployAp += COMPLETION_BONUS;
|
|
// there could also be AP for upgrading existing resonators, and for deploying mods - but we don't have data for that
|
|
var friendlyAp = deployAp;
|
|
|
|
var destroyResoAp = resCount*DESTROY_RESONATOR;
|
|
var destroyLinkAp = linkCount*DESTROY_LINK;
|
|
var destroyFieldAp = fieldCount*DESTROY_FIELD;
|
|
var captureAp = CAPTURE_PORTAL + 8 * DEPLOY_RESONATOR + COMPLETION_BONUS;
|
|
var destroyAp = destroyResoAp+destroyLinkAp+destroyFieldAp;
|
|
var enemyAp = destroyAp+captureAp;
|
|
|
|
return {
|
|
friendlyAp: friendlyAp,
|
|
enemyAp: enemyAp,
|
|
destroyAp: destroyAp,
|
|
destroyResoAp: destroyResoAp,
|
|
captureAp: captureAp
|
|
}
|
|
}
|