From 07c28538fd941a763e3880bedae8dcd432c3d063 Mon Sep 17 00:00:00 2001 From: Jon Atkins Date: Thu, 22 Aug 2013 22:32:31 +0100 Subject: [PATCH] work in progress - first attempt to use new rendering code lots of things currently broken though... --- code/boot.js | 17 +- code/map_data.js | 687 ++-------------------------------------- code/map_data_render.js | 70 +++- 3 files changed, 80 insertions(+), 694 deletions(-) diff --git a/code/boot.js b/code/boot.js index 69bd6f23..a2332cf0 100644 --- a/code/boot.js +++ b/code/boot.js @@ -183,22 +183,7 @@ window.setupMap = function() { map.attributionControl.setPrefix(''); // listen for changes and store them in cookies map.on('moveend', window.storeMapPosition); - map.on('zoomend', function() { - window.storeMapPosition(); - - // remove all resonators if zoom out to < RESONATOR_DISPLAY_ZOOM_LEVEL - if(isResonatorsShow()) return; - for(var i = 1; i < portalsLayers.length; i++) { - portalsLayers[i].eachLayer(function(item) { - var itemGuid = item.options.guid; - // check if 'item' is a resonator - if(getTypeByGuid(itemGuid) != TYPE_RESONATOR) return true; - portalsLayers[i].removeLayer(item); - }); - } - - console.log('Remove all resonators'); - }); + map.on('zoomend', window.storeMapPosition); // map update status handling & update map hooks diff --git a/code/map_data.js b/code/map_data.js index 22bb298c..bb72422e 100644 --- a/code/map_data.js +++ b/code/map_data.js @@ -38,7 +38,7 @@ window.debugSetTileColour = function(qk,bordercol,fillcol) { // cache for data tiles. indexed by the query key (qk) window.cache = undefined; - +window.render = undefined; // due to the cache (and race conditions in the server) - and now also oddities in the returned data @@ -49,11 +49,11 @@ window._deletedEntityGuid = {} // works, refer to the description in “MAP DATA REQUEST CALCULATORS” window.requestData = function() { if (window.cache === undefined) window.cache = new DataCache(); + if (window.render === undefined) window.render = new Render(); console.log('refreshing data'); requests.abort(); - cleanUp(); window.statusTotalMapTiles = 0; window.statusCachedMapTiles = 0; window.statusSuccessMapTiles = 0; @@ -67,6 +67,10 @@ window.requestData = function() { cache.expire(); + + render.startRenderPass(); + + //a limit on the number of map tiles to be pulled in a single request var MAX_TILES_PER_BUCKET = 18; // the number of separate buckets. more can be created if the size exceeds MAX_TILES_PER_BUCKET @@ -87,7 +91,6 @@ window.requestData = function() { tiles = {}; fullBucketCount = 0; - var cachedData = { result: { map: {} } }; var requestTileCount = 0; // y goes from left to right @@ -105,9 +108,10 @@ window.requestData = function() { // TODO?: if the selected portal is in this tile, always fetch the data if (cache.isFresh(tile_id)) { - // TODO: don't add tiles from the cache when 1. they were fully visible before, and 2. the zoom level is unchanged - // TODO?: if a closer zoom level has all four tiles in the cache, use them instead? - cachedData.result.map[tile_id] = cache.get(tile_id); + var tiledata = cache.get(tile_id); + + render.processTileData (tiledata); + debugSetTileColour(tile_id,'#0f0','#ff0'); window.statusCachedMapTiles++; } else { @@ -146,6 +150,7 @@ window.requestData = function() { // Reset previous result of Portal Render Limit handler portalRenderLimit.init(); + // send ajax requests console.log('requesting '+requestTileCount+' tiles in '+Object.keys(tiles).length+' requests'); $.each(tiles, function(ind, tls) { @@ -167,12 +172,11 @@ window.requestData = function() { window.requests.add(window.postAjax('getThinnedEntitiesV4', data, function(data, textStatus, jqXHR) { window.handleDataResponse(data,false,tile_ids); }, function() { window.handleFailedRequest(tile_ids); })); }); - // process the requests from the cache - console.log('got '+Object.keys(cachedData.result.map).length+' data tiles from cache'); - if(Object.keys(cachedData.result.map).length > 0) { - handleDataResponse(cachedData, true); + if(tiles.length == 0) { + // if everything was cached, we immediately end the render pass + // otherwise, the render pass will be ended in the callbacks + render.endRenderPass(); } - } // Handle failed map data request @@ -199,8 +203,9 @@ window.handleFailedRequest = function(tile_ids) { } if(requests.isLastRequest('getThinnedEntitiesV4')) { - var leftOverPortals = portalRenderLimit.mergeLowLevelPortals(null); - handlePortalsRender(leftOverPortals); + render.endRenderPass(); +// var leftOverPortals = portalRenderLimit.mergeLowLevelPortals(null); +// handlePortalsRender(leftOverPortals); } runHooks('requestFinished', {success: false}); } @@ -255,661 +260,19 @@ window.handleDataResponse = function(data, fromCache, tile_ids) { } - $.each(val.deletedGameEntityGuids || [], function(ind, guid) { - // avoid processing a delete we've already done - if (guid in window._deletedEntityGuid) - return; + render.processTileData(val); - // store that we've processed it - window._deletedEntityGuid[guid] = true; - - // if the deleted entity is a field, remove this field from the linkedFields entries for any portals - if(getTypeByGuid(guid) === TYPE_FIELD && window.fields[guid] !== undefined) { - $.each(window.fields[guid].options.vertices, function(ind, vertex) { - if(window.portals[vertex.guid] === undefined) return true; - fieldArray = window.portals[vertex.guid].options.details.portalV2.linkedFields; - fieldArray.splice($.inArray(guid, fieldArray), 1); - }); - } - - // TODO? if the deleted entity is a link, remove it from any portals linkedEdges - - window.removeByGuid(guid); - }); - - $.each(val.gameEntities || [], function(ind, ent) { - // ent = [GUID, id(?), details] - // format for links: { controllingTeam, creator, edge } - // format for portals: { controllingTeam, turret } - - // skip entities in the deleted list - if(ent[0] in window._deletedEntityGuid) return true; - - - if(ent[2].turret !== undefined) { - // TODO? remove any linkedEdges or linkedFields that have entries in window._deletedEntityGuid - - var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6]; - if(!window.getPaddedBounds().contains(latlng) - && selectedPortal !== ent[0] - && urlPortal !== ent[0] - && !(urlPortalLL && urlPortalLL[0] === latlng[0] && urlPortalLL[1] === latlng[1]) - ) return; - - if('imageByUrl' in ent[2] && 'imageUrl' in ent[2].imageByUrl) { - if(window.location.protocol === 'https:') { - ent[2].imageByUrl.imageUrl = ent[2].imageByUrl.imageUrl.indexOf('www.panoramio.com') !== -1 - ? ent[2].imageByUrl.imageUrl.replace(/^http:\/\/www/, 'https://ssl').replace('small', 'medium') - : ent[2].imageByUrl.imageUrl.replace(/^http:\/\//, '//'); - } - } else { - ent[2].imageByUrl = {'imageUrl': DEFAULT_PORTAL_IMG}; - } - - ppp[ent[0]] = ent; // delay portal render - } else if(ent[2].edge !== undefined) { - renderLink(ent); - } else if(ent[2].capturedRegion !== undefined) { - $.each(ent[2].capturedRegion, function(ind, vertex) { - if(p2f[vertex.guid] === undefined) - p2f[vertex.guid] = new Array(); - p2f[vertex.guid].push(ent[0]); - }); - renderField(ent); - } else { - throw('Unknown entity: ' + JSON.stringify(ent)); - } - }); }); - $.each(ppp, function(ind, portal) { - // when both source and destination portal are in the same response, no explicit 'edge' is returned - // instead, we need to reconstruct them from the data within the portal details - - if ('portalV2' in portal[2] && 'linkedEdges' in portal[2].portalV2) { - $.each(portal[2].portalV2.linkedEdges, function (ind, edge) { - // don't reconstruct deleted links - if (edge.edgeGuid in window._deletedEntityGuid) - return; - - // no data for other portal - can't reconstruct - if (!ppp[edge.otherPortalGuid]) - return; - - var otherportal = ppp[edge.otherPortalGuid]; - - // check other portal has matching data for the reverse direction - var hasOtherEdge = false; - - if ('portalV2' in otherportal[2] && 'linkedEdges' in otherportal[2].portalV2) { - $.each(otherportal[2].portalV2.linkedEdges, function(otherind, otheredge) { - if (otheredge.edgeGuid == edge.edgeGuid) { - hasOtherEdge = true; - } - }); - } - - if (!hasOtherEdge) - return; - - renderLink([ - edge.edgeGuid, - 0, // link data modified time - set to 0 as it's unknown - { - "controllingTeam": portal[2].controllingTeam, - "edge": { - "destinationPortalGuid": edge.isOrigin ? ppp[edge.otherPortalGuid][0] : portal[0], - "destinationPortalLocation": edge.isOrigin ? ppp[edge.otherPortalGuid][2].locationE6 : portal[2].locationE6, - "originPortalGuid": !edge.isOrigin ? ppp[edge.otherPortalGuid][0] : portal[0], - "originPortalLocation": !edge.isOrigin ? ppp[edge.otherPortalGuid][2].locationE6 : portal[2].locationE6 - }, - } - ]); - - }); - } - - if(portal[2].portalV2['linkedFields'] === undefined) { - portal[2].portalV2['linkedFields'] = []; - } - if(p2f[portal[0]] !== undefined) { - $.merge(p2f[portal[0]], portal[2].portalV2['linkedFields']); - portal[2].portalV2['linkedFields'] = uniqueArray(p2f[portal[0]]); - } - }); - - // Process the portals with portal render limit handler first - // Low level portal will hold until last request - var newPpp = portalRenderLimit.splitOrMergeLowLevelPortals(ppp); - // Clean up level of portal which over render limit after portalRenderLimit handler counted the new portals - portalRenderLimit.cleanUpOverLimitPortalLevel(); - handlePortalsRender(newPpp); resolvePlayerNames(); renderUpdateStatus(); + + if(requests.isLastRequest('getThinnedEntitiesV4')) { + render.endRenderPass(); +// var leftOverPortals = portalRenderLimit.mergeLowLevelPortals(null); +// handlePortalsRender(leftOverPortals); + } runHooks('requestFinished', {success: true}); } -window.handlePortalsRender = function(portals) { - var portalInUrlAvailable = false; - - // Preserve selectedPortal because it will get lost on re-rendering - // the portal - var oldSelectedPortal = selectedPortal; - runHooks('portalDataLoaded', {portals : portals}); - $.each(portals, function(guid, portal) { - //~ if(selectedPortal === portal[0]) portalUpdateAvailable = true; - if(urlPortalLL && urlPortalLL[0] === portal[2].locationE6.latE6/1E6 && urlPortalLL[1] === portal[2].locationE6.lngE6/1E6) { - urlPortal = guid; - portalInUrlAvailable = true; - urlPortalLL = null; - } - if(window.portals[guid]) { - highlightPortal(window.portals[guid]); - } - renderPortal(portal); - }); - - // restore selected portal if still available - var selectedPortalGroup = portals[oldSelectedPortal]; - if(selectedPortalGroup) { - selectedPortal = oldSelectedPortal; - renderPortalDetails(selectedPortal); - try { - selectedPortalGroup.bringToFront(); - } catch(e) { /* portal is now visible, catch Leaflet error */ } - } - - if(portalInUrlAvailable) { - renderPortalDetails(urlPortal); - urlPortal = null; // select it only once - } -} - -// removes entities that are still handled by Leaflet, although they -// do not intersect the current viewport. -window.cleanUp = function() { - var cnt = [0,0,0]; - var b = getPaddedBounds(); - var minlvl = getMinPortalLevel(); - for(var i = 0; i < portalsLayers.length; i++) { - // i is also the portal level - portalsLayers[i].eachLayer(function(item) { - var itemGuid = item.options.guid; - // check if 'item' is a portal - if(getTypeByGuid(itemGuid) != TYPE_PORTAL) return true; - // portal must be in bounds and have a high enough level. Also don’t - // remove if it is selected. - if(itemGuid == window.selectedPortal || - (b.contains(item.getLatLng()) && i >= minlvl)) return true; - cnt[0]++; - portalsLayers[i].removeLayer(item); - }); - } - linksLayer.eachLayer(function(link) { - if(b.intersects(link.getBounds())) return; - cnt[1]++; - linksLayer.removeLayer(link); - }); - fieldsLayer.eachLayer(function(fieldgroup) { - fieldgroup.eachLayer(function(item) { - if(!item.options.guid) return true; // Skip MU div container as this doesn't have the bounds we need - if(b.intersects(item.getBounds())) return; - cnt[2]++; - fieldsLayer.removeLayer(fieldgroup); - }); - }); - console.log('removed out-of-bounds: '+cnt[0]+' portals, '+cnt[1]+' links, '+cnt[2]+' fields'); -} - - -// removes given entity from map -window.removeByGuid = function(guid) { - switch(getTypeByGuid(guid)) { - case TYPE_PORTAL: - if(!window.portals[guid]) return; - var p = window.portals[guid]; - for(var i = 0; i < portalsLayers.length; i++) - portalsLayers[i].removeLayer(p); - break; - case TYPE_LINK: - if(!window.links[guid]) return; - linksLayer.removeLayer(window.links[guid]); - break; - case TYPE_FIELD: - if(!window.fields[guid]) return; - fieldsLayer.removeLayer(window.fields[guid]); - break; - case TYPE_RESONATOR: - if(!window.resonators[guid]) return; - var r = window.resonators[guid]; - for(var i = 1; i < portalsLayers.length; i++) - portalsLayers[i].removeLayer(r); - break; - default: - console.warn('unknown GUID type: ' + guid); - //window.debug.printStackTrace(); - } -} - - -// Separation of marker style setting from the renderPortal method -// Having this as a separate function allows subsituting alternate marker rendering (for plugins) -window.getMarker = function(ent, portalLevel, latlng, team) { - var lvWeight = Math.max(2, Math.floor(portalLevel) / 1.5); - var lvRadius = Math.floor(portalLevel) + 4; - if(team === window.TEAM_NONE) { - lvRadius = 7; - } - - var p = L.circleMarker(latlng, { - radius: lvRadius + (L.Browser.mobile ? PORTAL_RADIUS_ENLARGE_MOBILE : 0), - color: ent[0] === selectedPortal ? COLOR_SELECTED_PORTAL : COLORS[team], - opacity: 1, - weight: lvWeight, - fillColor: COLORS[team], - fillOpacity: 0.5, - clickable: true, - level: portalLevel, - team: team, - ent: ent, - details: ent[2], - guid: ent[0]}); - - return p; -} - - -// renders a portal on the map from the given entity -window.renderPortal = function(ent) { - if(window.portalsCount >= MAX_DRAWN_PORTALS && ent[0] !== selectedPortal) - return removeByGuid(ent[0]); - - // hide low level portals on low zooms - var portalLevel = getPortalLevel(ent[2]); - if(portalLevel < getMinPortalLevel() && ent[0] !== selectedPortal) - return removeByGuid(ent[0]); - - var team = getTeam(ent[2]); - - // do nothing if portal did not change - var layerGroup = portalsLayers[parseInt(portalLevel)]; - var old = findEntityInLeaflet(layerGroup, window.portals, ent[0]); - if(!changing_highlighters && old) { - var oo = old.options; - - // if the data we have is older than/the same as the data already rendered, do nothing - if (oo.ent[1] >= ent[1]) { - // let resos handle themselves if they need to be redrawn - renderResonators(ent[0], ent[2], old); - return; - } - - // Default checks to see if a portal needs to be re-rendered - var u = oo.team !== team; - u = u || oo.level !== portalLevel; - - // Allow plugins to add additional conditions as to when a portal gets re-rendered - var hookData = {portal: ent[2], oldPortal: oo.details, portalGuid: ent[0], mtime: ent[1], oldMtime: oo.ent[1], reRender: false}; - runHooks('beforePortalReRender', hookData); - u = u || hookData.reRender; - - // nothing changed that requires re-rendering the portal. - if(!u) { - // let resos handle themselves if they need to be redrawn - renderResonators(ent[0], ent[2], old); - // update stored details for portal details in sidebar. - old.options.details = ent[2]; - return; - } - } - - // there were changes, remove old portal. Don’t put this in old, in - // case the portal changed level and findEntityInLeaflet doesn’t find - // it. - removeByGuid(ent[0]); - - var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6]; - - // pre-loads player names for high zoom levels - loadPlayerNamesForPortal(ent[2]); - - var p = getMarker(ent, portalLevel, latlng, team); - - p.on('remove', function() { - var portalGuid = this.options.guid - - // remove attached resonators, skip if - // all resonators have already removed by zooming - if(isResonatorsShow()) { - for(var i = 0; i <= 7; i++) - removeByGuid(portalResonatorGuid(portalGuid, i)); - } - delete window.portals[portalGuid]; - window.portalsCount --; - if(window.selectedPortal === portalGuid) { - window.unselectOldPortal(); - } - }); - - p.on('add', function() { - // 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 - // map by hiding all portals with said level - if(window.selectedPortal !== this.options.guid) - window.portalResetColor(this); - }); - - p.on('click', function() { window.renderPortalDetails(ent[0]); }); - p.on('dblclick', function() { - window.renderPortalDetails(ent[0]); - window.map.setView(latlng, 17); - }); - - highlightPortal(p); - window.runHooks('portalAdded', {portal: p}); - p.addTo(layerGroup); -} - -window.renderResonators = function(portalGuid, portalDetails, portalLayer) { - if(!isResonatorsShow()) return; - - // only draw when the portal is not hidden - if(portalLayer && !window.map.hasLayer(portalLayer)) return; - - var portalLevel = getPortalLevel(portalDetails); - var portalLatLng = [portalDetails.locationE6.latE6/1E6, portalDetails.locationE6.lngE6/1E6]; - - var reRendered = false; - $.each(portalDetails.resonatorArray.resonators, function(i, rdata) { - // skip if resonator didn't change - var oldRes = window.resonators[portalResonatorGuid(portalGuid, i)]; - if(oldRes) { - if(isSameResonator(oldRes.options.details, rdata)) return true; - // remove old resonator if exist - removeByGuid(oldRes.options.guid); - } - - // skip and remove old resonator if no new resonator - if(rdata === null) return true; - - var resoLatLng = getResonatorLatLng(rdata.distanceToPortal, rdata.slot, portalLatLng); - var resoGuid = portalResonatorGuid(portalGuid, i); - - // the resonator - var resoStyle = - portalGuid === selectedPortal ? OPTIONS_RESONATOR_SELECTED : OPTIONS_RESONATOR_NON_SELECTED; - var resoProperty = $.extend({ - fillColor: COLORS_LVL[rdata.level], - fillOpacity: rdata.energyTotal/RESO_NRG[rdata.level], - guid: resoGuid - }, resoStyle); - - var reso = L.circleMarker(resoLatLng, resoProperty); - - // line connecting reso to portal - var connProperty = - portalGuid === selectedPortal ? OPTIONS_RESONATOR_LINE_SELECTED : OPTIONS_RESONATOR_LINE_NON_SELECTED; - - var conn = L.polyline([portalLatLng, resoLatLng], connProperty); - - // put both in one group, so they can be handled by the same logic. - var r = L.layerGroup([reso, conn]); - r.options = { - level: rdata.level, - details: rdata, - pDetails: portalDetails, - guid: resoGuid - }; - - // However, LayerGroups (and FeatureGroups) don’t fire add/remove - // events, thus this listener will be attached to the resonator. It - // doesn’t matter to which element these are bound since Leaflet - // will add/remove all elements of the LayerGroup at once. - reso.on('remove', function() { delete window.resonators[this.options.guid]; }); - reso.on('add', function() { - if(window.resonators[this.options.guid]) throw('duplicate resonator detected'); - window.resonators[this.options.guid] = r; - }); - - r.addTo(portalsLayers[parseInt(portalLevel)]); - reRendered = true; - }); - // if there is any resonator re-rendered, bring portal to front - if(reRendered && portalLayer) portalLayer.bringToFront(); -} - -// append portal guid with -resonator-[slot] to get guid for resonators -window.portalResonatorGuid = function(portalGuid, slot) { - return portalGuid + '-resonator-' + slot; -} - -window.isResonatorsShow = function() { - return map.getZoom() >= RESONATOR_DISPLAY_ZOOM_LEVEL; -} - -window.isSameResonator = function(oldRes, newRes) { - if(!oldRes && !newRes) return true; - if(!oldRes || !newRes) return false; - if(typeof oldRes !== typeof newRes) return false; - if(oldRes.level !== newRes.level) return false; - if(oldRes.energyTotal !== newRes.energyTotal) return false; - if(oldRes.distanceToPortal !== newRes.distanceToPortal) return false; - return true; -} - -window.portalResetColor = function(portal) { - portal.setStyle({color: COLORS[getTeam(portal.options.details)]}); - resonatorsResetStyle(portal.options.guid); -} - -window.resonatorsResetStyle = function(portalGuid) { - window.resonatorsSetStyle(portalGuid, OPTIONS_RESONATOR_NON_SELECTED, OPTIONS_RESONATOR_LINE_NON_SELECTED); -} - -window.resonatorsSetSelectStyle = function(portalGuid) { - window.resonatorsSetStyle(portalGuid, OPTIONS_RESONATOR_SELECTED, OPTIONS_RESONATOR_LINE_SELECTED); -} - -window.resonatorsSetStyle = function(portalGuid, resoStyle, lineStyle) { - for(var i = 0; i < 8; i++) { - resonatorLayerGroup = resonators[portalResonatorGuid(portalGuid, i)]; - if(!resonatorLayerGroup) continue; - // bring resonators and their connection lines to front separately. - // this way the resonators are drawn on top of the lines. - resonatorLayerGroup.eachLayer(function(layer) { - if (!layer.options.guid) // Resonator line - layer.bringToFront().setStyle(lineStyle); - }); - resonatorLayerGroup.eachLayer(function(layer) { - if (layer.options.guid) // Resonator - layer.bringToFront().setStyle(resoStyle); - }); - } - portals[portalGuid].bringToFront(); -} - -// renders a link on the map from the given entity -window.renderLink = function(ent) { - if(window.linksCount >= MAX_DRAWN_LINKS) - return removeByGuid(ent[0]); - - // some links are constructed from portal linkedEdges data. These have no valid 'creator' data. - // replace with the more detailed data - // (we assume the other values - coordinates, etc - remain unchanged) - var found=findEntityInLeaflet(linksLayer, links, ent[0]); - if (found) { - if (!found.options.data.creator && ent[2].creator) { - //our existing data has no creator, but the new data does - update - found.options.data = ent[2]; - } - return; - } - - var team = getTeam(ent[2]); - var edge = ent[2].edge; - var latlngs = [ - [edge.originPortalLocation.latE6/1E6, edge.originPortalLocation.lngE6/1E6], - [edge.destinationPortalLocation.latE6/1E6, edge.destinationPortalLocation.lngE6/1E6] - ]; - var poly = L.geodesicPolyline(latlngs, { - color: COLORS[team], - opacity: 1, - weight:2, - clickable: false, - guid: ent[0], - data: ent[2], - smoothFactor: 0 // doesn’t work for two points anyway, so disable - }); - // determine which links are very short and don’t render them at all. - // in most cases this will go unnoticed, but improve rendering speed. - poly._map = window.map; - poly.projectLatlngs(); - var op = poly._originalPoints; - var dist = Math.abs(op[0].x - op[1].x) + Math.abs(op[0].y - op[1].y); - if(dist <= 10) { - return; - } - - if(!getPaddedBounds().intersects(poly.getBounds())) return; - - 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); -} - -// renders a field on the map from a given entity -window.renderField = function(ent) { - if(window.fieldsCount >= MAX_DRAWN_FIELDS) - return window.removeByGuid(ent[0]); - - var old = findEntityInLeaflet(fieldsLayer, window.fields, ent[0]); - // If this already exists and the zoom level has not changed, we don't need to do anything - if(old && map.getZoom() === old.options.creationZoom) return; - - var team = getTeam(ent[2]); - var reg = ent[2].capturedRegion; - var latlngs = [ - L.latLng(reg.vertexA.location.latE6/1E6, reg.vertexA.location.lngE6/1E6), - L.latLng(reg.vertexB.location.latE6/1E6, reg.vertexB.location.lngE6/1E6), - L.latLng(reg.vertexC.location.latE6/1E6, reg.vertexC.location.lngE6/1E6) - ]; - - var poly = L.geodesicPolygon(latlngs, { - fillColor: COLORS[team], - fillOpacity: 0.25, - stroke: false, - clickable: false, - smoothFactor: 0, // hiding small fields will be handled below - guid: ent[0]}); - - // determine which fields are too small to be rendered and don’t - // render them, so they don’t count towards the maximum fields limit. - // This saves some DOM operations as well, but given the relatively - // low amount of fields there isn’t much to gain. - // The algorithm is the same as used by Leaflet. - poly._map = window.map; - poly.projectLatlngs(); - var count = L.LineUtil.simplify(poly._originalPoints, 6).length; - if(count <= 2) return; - - if(!getPaddedBounds().intersects(poly.getBounds())) return; - - // Curve fit equation to normalize zoom window area - var areaZoomRatio = calcTriArea(latlngs)/Math.exp(14.2714860198866-1.384987247*map.getZoom()); - var countForMUDisplay = L.LineUtil.simplify(poly._originalPoints, FIELD_MU_DISPLAY_POINT_TOLERANCE).length - - // Do nothing if zoom did not change. We need to recheck the field if the - // zoom level is different then when the field was rendered as it could - // now be appropriate or not to show an MU count - if(old) { - var layerCount = 0; - old.eachLayer(function(item) { - layerCount++; - }); - // Don't do anything since we already have an MU display and we still want to - if(areaZoomRatio > FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay > 2 && layerCount === 2) return; - // Don't do anything since we don't have an MU display and don't want to - if(areaZoomRatio <= FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay <= 2 && layerCount === 1) return; - removeByGuid(ent[0]); - } - - // put both in one group, so they can be handled by the same logic. - if (areaZoomRatio > FIELD_MU_DISPLAY_AREA_ZOOM_RATIO && countForMUDisplay > 2) { - // centroid of field for placing MU count at - var centroid = [ - (latlngs[0].lat + latlngs[1].lat + latlngs[2].lat)/3, - (latlngs[0].lng + latlngs[1].lng + latlngs[2].lng)/3 - ]; - - var fieldMu = L.marker(centroid, { - icon: L.divIcon({ - className: 'fieldmu', - iconSize: [70,12], - html: digits(ent[2].entityScore.entityScore) - }), - clickable: false - }); - var f = L.layerGroup([poly, fieldMu]); - } else { - var f = L.layerGroup([poly]); - } - f.options = { - vertices: reg, - lastUpdate: ent[1], - creationZoom: map.getZoom(), - guid: ent[0], - data: ent[2] - }; - - // However, LayerGroups (and FeatureGroups) don’t fire add/remove - // events, thus this listener will be attached to the field. It - // doesn’t 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]; - 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); -} - - -// looks for the GUID in either the layerGroup or entityHash, depending -// on which is faster. Will either return the Leaflet entity or null, if -// it does not exist. -// For example, to find a field use the function like this: -// field = findEntityInLeaflet(fieldsLayer, fields, 'asdasdasd'); -window.findEntityInLeaflet = function(layerGroup, entityHash, guid) { - // fast way - if(map.hasLayer(layerGroup)) return entityHash[guid] || null; - - // slow way in case the layer is currently hidden - var ent = null; - layerGroup.eachLayer(function(entity) { - if(entity.options.guid !== guid) return true; - ent = entity; - return false; - }); - return ent; -} diff --git a/code/map_data_render.js b/code/map_data_render.js index d2f23d84..20feff5c 100644 --- a/code/map_data_render.js +++ b/code/map_data_render.js @@ -13,16 +13,20 @@ window.Render.prototype.startRenderPass = function() { this.isRendering = true; this.deletedGuid = {}; // object - represents the set of all deleted game entity GUIDs seen in a render pass + + this.seenPortalsGuid = {}; + this.seenLinksGuid = {}; + this.seenFieldsGuid = {}; } // process deleted entity list and entity data -window.Render.prototype.processTileData = function(deleted, entities) { - this.processDeletedGameEntityGuids(deleted); - this.processGameEntities(entities); +window.Render.prototype.processTileData = function(tiledata) { + this.processDeletedGameEntityGuids(tiledata.deletedGameEntityGuids||[]); + this.processGameEntities(tiledata.gameEntities||[]); } -window.Render.prototype.processDeletedGamEntityGuids = function(deleted) { +window.Render.prototype.processDeletedGameEntityGuids = function(deleted) { for(var i in deleted) { var guid = deleted[i]; @@ -49,8 +53,7 @@ window.Render.prototype.processGameEntities = function(entities) { } } - // TODO: reconstruct links 'optimised' out of the data from the portal link data - + // now reconstruct links 'optimised' out of the data from the portal link data } @@ -58,29 +61,58 @@ window.Render.prototype.processGameEntities = function(entities) { // is considered complete window.Render.prototype.endRenderPass = function() { + // check to see if there's eny entities we haven't seen. if so, delete them + for (var guid in window.portals) { + if (!(guid in this.seenPortalsGuid)) { + this.deletePortalEntity(guid); + } + } + for (var guid in window.links) { + if (!(guid in this.seenLinksGuid)) { + this.deleteLinkEntity(guid); + } + } + for (var guid in window.fields) { + if (!(guid in this.seenFieldsGuid)) { + this.deleteFieldEntity(guid); + } + } + this.isRendering = false; } window.Render.prototype.deleteEntity = function(guid) { + this.deletePortalEntity(guid); + this.deleteLinkEntity(guid); + this.deleteFieldEntity(guid); +} +window.Render.prototype.deletePortalEntity = function(guid) { if (guid in window.portals) { var p = window.portals[guid]; for(var i in portalsLayers) { portalsLayers[i].removeLayer(p); } delete window.portals[guid]; - } else if (guid in window.links) { + } +} + +window.Render.prototype.deleteLinkEntity = function(guid) { + if (guid in window.links) { var l = window.links[guid]; linksLayer.removeLayer(l); delete window.links[guid]; - } else if (guid in window.fields) { + } +} + +window.Render.prototype.deleteFieldEntity = function(guid) { + if (guid in window.fields) { var f = window.fields[guid]; - fieldsLayer.removeLayer[guid]; + fieldsLayer.removeLayer(f); delete window.fields[f]; } - } @@ -106,6 +138,8 @@ window.Render.prototype.createEntity = function(ent) { window.Render.prototype.createPortalEntity = function(ent) { + this.seenPortalsGuid[ent[0]] = true; // flag we've seen it + // check if entity already exists if (ent[0] in window.portals) { // yes. now check to see if the entity data we have is newer than that in place @@ -117,13 +151,13 @@ window.Render.prototype.createPortalEntity = function(ent) { // (e.g. level changed, so size is different, or stats changed so highlighter is different) // so to keep things simple we'll always re-create the entity in this case - deleteEntity(ent[0]); + this.deletePortalEntity(ent[0]); } var portalLevel = getPortalLevel(ent[2]); var team = getTeam(ent[2]); - var latlng = L.latlng(ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6); + var latlng = L.latLng(ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6); var marker = this.createMarker(ent, portalLevel, latlng, team); @@ -172,6 +206,8 @@ window.Render.prototype.portalPolyOptions = function(ent, portalLevel, team) { window.Render.prototype.createFieldEntity = function(ent) { + this.seenFieldsGuid[ent[0]] = true; // flag we've seen it + // check if entity already exists if(ent[0] in window.fields) { // yes. in theory, we should never get updated data for an existing field. they're created, and they're destroyed - never changed @@ -183,7 +219,7 @@ window.Render.prototype.createFieldEntity = function(ent) { // the data we have is newer - two options // 1. just update the data, assume the field render appearance is unmodified // 2. delete the entity, then re-create with the new data - deleteEntity(ent[0]); // option 2, for now + this.deleteFieldEntity(ent[0]); // option 2, for now } var team = getTeam(ent[2]); @@ -212,6 +248,8 @@ window.Render.prototype.createFieldEntity = function(ent) { } window.Render.prototype.createLinkEntity = function(ent) { + this.seenLinksGuid[ent[0]] = true; // flag we've seen it + // check if entity already exists if (ent[0] in window.links) { // yes. now, as sometimes links are 'faked', they have incomplete data. if the data we have is better, replace the data @@ -223,14 +261,14 @@ window.Render.prototype.createLinkEntity = function(ent) { // the data is newer/better - two options // 1. just update the data. assume the link render appearance is unmodified // 2. delete the entity, then re-create it with the new data - deleteEntity(ent[0]); // option 2 - for now + this.deleteLinkEntity(ent[0]); // option 2 - for now } var team = getTeam(ent[2]); var edge = ent[2].edge; var latlngs = [ - L.latlng(edge.originPortalLocation.latE6/1E6, edge.originPortalLocation.lngE6/1E6), - L.latlng(edge.destinationPortalLocation.latE6/1E6, edge.destinationPortalLocation.lngE6/1E6) + L.latLng(edge.originPortalLocation.latE6/1E6, edge.originPortalLocation.lngE6/1E6), + L.latLng(edge.destinationPortalLocation.latE6/1E6, edge.destinationPortalLocation.lngE6/1E6) ]; var poly = L.geodesicPolyline(latlngs, { color: COLORS[team],