🏃 massive speed gain after a night of node profiling and lessons learned

This commit is contained in:
Michael Straßburger 2016-11-08 06:44:53 +01:00
parent 461a4ab049
commit c206055596
5 changed files with 58 additions and 63 deletions

View File

@ -27,14 +27,17 @@ module.exports = class BrailleBuffer
termReset: "\x1B[39;49m" termReset: "\x1B[39;49m"
constructor: (@width, @height) -> constructor: (@width, @height) ->
@pixelBuffer = new Buffer @width*@height/8 size = @width*@height/8
@pixelBuffer = new Buffer size
@foregroundBuffer = new Buffer size
@backgroundBuffer = new Buffer size
@clear() @clear()
clear: -> clear: ->
@pixelBuffer.fill 0 @pixelBuffer.fill 0
@charBuffer = [] @charBuffer = []
@foregroundBuffer = [] @foregroundBuffer.fill 0
@backgroundBuffer = [] @backgroundBuffer.fill 0
setGlobalBackground: (@globalBackground) -> setGlobalBackground: (@globalBackground) ->

View File

@ -13,7 +13,6 @@
bresenham = require 'bresenham' bresenham = require 'bresenham'
earcut = require 'earcut' earcut = require 'earcut'
BrailleBuffer = require './BrailleBuffer' BrailleBuffer = require './BrailleBuffer'
utils = require './utils' utils = require './utils'
@ -56,14 +55,9 @@ module.exports = class Canvas
# if vertices.length # if vertices.length
# continue # continue
# holes.push vertices.length/2 # holes.push vertices.length/2
polylines.push polylines[0]
for point in polylines for point in polylines
vertices = vertices.concat point vertices = vertices.concat point
xs[point[0]] = ys[point[1]] = true
# Check if we actually got a valid polygon after projection and clamping
if Object.keys(xs).length is 1 or Object.keys(ys).length is 1
return false
try try
triangles = earcut vertices, holes triangles = earcut vertices, holes
@ -78,9 +72,6 @@ module.exports = class Canvas
pb = extract(triangles[i+1]) pb = extract(triangles[i+1])
pc = extract(triangles[i+2]) pc = extract(triangles[i+2])
if (0 <= pa[0] < @width and 0 <= pa[1] < @height) or
(0 <= pb[0] < @width and 0 <= pb[1] < @height) or
(0 <= pc[0] < @width and 0 <= pc[1] < @height)
@_filledTriangle pa, pb, pc, color @_filledTriangle pa, pb, pc, color
# Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm" # Inspired by Alois Zingl's "The Beauty of Bresenham's Algorithm"
@ -159,13 +150,17 @@ module.exports = class Canvas
[lastPoint, last] = [last, point] [lastPoint, last] = [last, point]
not lastPoint or lastPoint.x isnt point.x or lastPoint.y isnt point.y not lastPoint or lastPoint.x isnt point.x or lastPoint.y isnt point.y
for i, point of points for i in [0...points.length]
point = points[i]
next = points[i*1+1] next = points[i*1+1]
if point.y is next?.y if point.y is next?.y
left = Math.max 0, point.x left = Math.max 0, point.x
right = Math.min @width, next?.x right = Math.min @width, next.x
if left and right
@buffer.setPixel x, point.y, color for x in [left..right] @buffer.setPixel x, point.y, color for x in [left..right]
else else
@buffer.setPixel point.x, point.y, color @buffer.setPixel point.x, point.y, color
break unless next

View File

@ -210,18 +210,19 @@ module.exports = class Renderer
if feature.style.minzoom and tile.zoom < feature.style.minzoom if feature.style.minzoom and tile.zoom < feature.style.minzoom
return false return false
switch feature.style.type
when "line"
points = @_scaleAndReduce tile, feature, feature.points points = @_scaleAndReduce tile, feature, feature.points
switch feature.style.type
when "line"
width = feature.style.paint['line-width'] width = feature.style.paint['line-width']
width = width.stops[0][1] if width instanceof Object width = width.stops[0][1] if width instanceof Object
@canvas.polyline points, feature.color, width if points.length @canvas.polyline points, feature.color, width if points.length
when "fill" when "fill"
vertices = (@_scaleAndReduce tile, feature, points, false for points in feature.points) @canvas.polygon points, feature.color
@canvas.polygon vertices[0], feature.color # if points.length is 3
# @canvas._filledTriangle points[0], points[1], points[2], feature.color
true true
when "symbol" when "symbol"
@ -242,12 +243,15 @@ module.exports = class Renderer
else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3 else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3
@canvas.text "", point[0], point[1], feature.color @canvas.text "", point[0], point[1], feature.color
true
_seen: {} _seen: {}
_scaleAndReduce: (tile, feature, points, filter = true) -> _scaleAndReduce: (tile, feature, points, filter = true) ->
lastX = null lastX = null
lastY = null lastY = null
outside = false outside = false
scaled = [] scaled = []
#seen = {}
for point in points for point in points
x = Math.floor tile.position.x+(point.x/tile.scale) x = Math.floor tile.position.x+(point.x/tile.scale)
@ -259,8 +263,12 @@ module.exports = class Renderer
lastY = y lastY = y
lastX = x lastX = x
# TODO: benchmark
# continue if seen[idx = (y<<8)+x]
# seen[idx] = true
if filter if filter
if tile.xyz.z > 1 and ( if (
x < -@tilePadding or x < -@tilePadding or
y < -@tilePadding or y < -@tilePadding or
x > @width+@tilePadding or x > @width+@tilePadding or
@ -277,8 +285,8 @@ module.exports = class Renderer
if filter if filter
if scaled.length is 2 if scaled.length is 2
if @_seen[ka = scaled[0].concat(scaled[1]).join '-'] or if @_seen[ka = (scaled[0]<<8)+scaled[1]] or
@_seen[kb = scaled[1].concat(scaled[0]).join '-'] @_seen[kb = (scaled[1]<<8)+scaled[0]]
return [] return []
@_seen[ka] = @_seen[kb] = true @_seen[ka] = @_seen[kb] = true

View File

@ -23,8 +23,8 @@ module.exports = class Termap
styleFile: __dirname+"/../styles/bright.json" styleFile: __dirname+"/../styles/bright.json"
initialZoom: null initialZoom: null
maxZoom: 17 maxZoom: 18
zoomStep: 0.1 zoomStep: 0.2
headless: false headless: false
# size: # size:
@ -154,7 +154,9 @@ module.exports = class Termap
when "q" when "q"
process.exit 0 process.exit 0
when "w" then @zoomy = true when "w" then @zoomy = 1
when "s" then @zoomy = -1
when "a" then @zoomBy @config.zoomStep when "a" then @zoomBy @config.zoomStep
when "z" then @zoomBy -@config.zoomStep when "z" then @zoomBy -@config.zoomStep
@ -181,11 +183,12 @@ module.exports = class Termap
.catch => .catch =>
@notify "renderer is busy" @notify "renderer is busy"
.then => .then =>
if @zoomy and @zoom < @config.maxZoom if @zoomy
@zoom += @config.zoomStep if (@zoomy > 0 and @zoom < @config.maxZoom) or (@zoomy < 0 and @zoom > @minZoom)
@_draw() @zoom += @zoomy * @config.zoomStep
else else
@zoomy = false @zoomy *= -1
@_draw()
_getFooter: -> _getFooter: ->
# features = @renderer.featuresAt @mousePosition.x-1-(@view[0]>>1), @mousePosition.y-1-(@view[1]>>2) # features = @renderer.featuresAt @mousePosition.x-1-(@view[0]>>1), @mousePosition.y-1-(@view[1]>>2)
@ -195,16 +198,11 @@ module.exports = class Termap
# type: f.feature.properties.type # type: f.feature.properties.type
# rank: f.feature.properties.scalerank # rank: f.feature.properties.scalerank
# ).join(", ")+"] "+ # ).join(", ")+"] "+
"#{@mousePosition.x} #{@mousePosition.y} " + #{}"#{@mousePosition.x} #{@mousePosition.y} " +
#"center: [#{utils.digits @center.lat, 2}, #{utils.digits @center.lng, 2}]}"
# bbox = @_getBBox() # bbox = @_getBBox()
# tiles = @_tilesInBBox(bbox) # tiles = @_tilesInBBox(bbox)
"center: #{utils.digits @center.lat, 3}, #{utils.digits @center.lon, 3} "+
"zoom: #{utils.digits @zoom, 2} " "zoom: #{utils.digits @zoom, 2} "
#{}"bbox: [#{bbox.map((z) -> utils.digits(z, 2)).join(', ')}]"+
# "tiles: "+("#{k}: #{v}" for k,v of @_tilesInBBox(bbox) when typeof v is "number").join(",")
#features.map((f) -> JSON.stringify f.feature.properties).join(" - ")
notify: (text) -> notify: (text) ->
@_write "\r\x1B[K"+text unless @config.headless @_write "\r\x1B[K"+text unless @config.headless

View File

@ -11,6 +11,7 @@ Promise = require 'bluebird'
zlib = require 'zlib' zlib = require 'zlib'
rbush = require 'rbush' rbush = require 'rbush'
x256 = require 'x256' x256 = require 'x256'
earcut = require 'earcut'
utils = require "./utils" utils = require "./utils"
@ -46,6 +47,7 @@ class Tile
for name, layer of tile.layers for name, layer of tile.layers
nodes = [] nodes = []
#continue if name is "water"
for i in [0...layer.length] for i in [0...layer.length]
# TODO: caching of similar attributes to avoid looking up the style each time # TODO: caching of similar attributes to avoid looking up the style each time
#continue if @styler and not @styler.getStyleFor layer, feature #continue if @styler and not @styler.getStyleFor layer, feature
@ -62,7 +64,7 @@ class Tile
style.paint['fill-color'] or style.paint['fill-color'] or
style.paint['text-color'] style.paint['text-color']
# TODO: zoom calculation todo for perfect styling # TODO: style zoom stops handling
if color instanceof Object if color instanceof Object
color = color.stops[0][1] color = color.stops[0][1]
@ -72,18 +74,7 @@ class Tile
# use feature.loadGeometry() again as soon as we got a 512 extent tileset # use feature.loadGeometry() again as soon as we got a 512 extent tileset
geometries = feature.loadGeometry() #@_reduceGeometry feature, 8 geometries = feature.loadGeometry() #@_reduceGeometry feature, 8
# TODO: handling polygon holes, only handling outer area for now for points in (if style.type is "fill" then [geometries[0]] else geometries)
if style.type is "fill"
nodes.push @_addBoundaries
id: feature.id
layer: name
style: style
properties: feature.properties
points: geometries
color: colorCode
else
for points in geometries
nodes.push @_addBoundaries nodes.push @_addBoundaries
id: feature.id id: feature.id
layer: name layer: name
@ -92,26 +83,26 @@ class Tile
points: points points: points
color: colorCode color: colorCode
tree = rbush()
tree = rbush 18
tree.load nodes tree.load nodes
layers[name] = tree layers[name] = tree
@layers = layers @layers = layers
_addBoundaries: (data) -> _addBoundaries: (data) ->
[minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity] minX = Infinity
minMax = (points) -> maxX = -Infinity
for p in points minY = Infinity
maxY = -Infinity
for p in data.points
minX = p.x if p.x < minX minX = p.x if p.x < minX
maxX = p.x if p.x > maxX maxX = p.x if p.x > maxX
minY = p.y if p.y < minY minY = p.y if p.y < minY
maxY = p.y if p.y > maxY maxY = p.y if p.y > maxY
if data.points[0] instanceof Array
minMax points for points in data.points
else
minMax data.points
data.minX = minX data.minX = minX
data.maxX = maxX data.maxX = maxX
data.minY = minY data.minY = minY