4
.gitignore
vendored
4
.gitignore
vendored
@@ -12,6 +12,7 @@
|
||||
/mason_packages
|
||||
/config/*.gypi
|
||||
/build
|
||||
/lib
|
||||
/macosx/build
|
||||
/linux/build
|
||||
/ios/build
|
||||
@@ -33,3 +34,6 @@
|
||||
/ios/benchmark/assets/tiles/mapbox.mapbox-streets-v6/
|
||||
/ios/benchmark/assets/tiles/mapbox.mapbox-terrain-v2/
|
||||
/ios/benchmark/assets/glyphs/DIN Offc Pro*/
|
||||
|
||||
/platform/node/test/actual
|
||||
/platform/node/test/diff
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -58,6 +58,36 @@ matrix:
|
||||
addons:
|
||||
apt:
|
||||
packages: [ 'lib32stdc++6' ]
|
||||
- os: linux
|
||||
env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=iojs-v2
|
||||
addons:
|
||||
apt:
|
||||
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ]
|
||||
packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ]
|
||||
- os: linux
|
||||
env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.12
|
||||
addons:
|
||||
apt:
|
||||
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ]
|
||||
packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ]
|
||||
- os: linux
|
||||
env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.10
|
||||
addons:
|
||||
apt:
|
||||
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ]
|
||||
packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ]
|
||||
- os: osx
|
||||
osx_image: xcode6.4
|
||||
compiler: clang
|
||||
env: FLAVOR=node NODE_VERSION=iojs-v2
|
||||
- os: osx
|
||||
osx_image: xcode6.4
|
||||
compiler: clang
|
||||
env: FLAVOR=node NODE_VERSION=0.12
|
||||
- os: osx
|
||||
osx_image: xcode6.4
|
||||
compiler: clang
|
||||
env: FLAVOR=node NODE_VERSION=0.10
|
||||
|
||||
env:
|
||||
global:
|
||||
|
||||
4
Makefile
4
Makefile
@@ -80,6 +80,10 @@ android-all: android-lib-x86 android-lib-x86-64
|
||||
android-all: android-lib-mips android-lib-mips-64
|
||||
cd android/java && ./gradlew --parallel-threads=$(JOBS) assemble$(BUILDTYPE)
|
||||
|
||||
# Builds the Node.js library
|
||||
.PHONY: node
|
||||
node: ; $(RUN) HTTP=none ASSET=none CACHE=none Makefile/node
|
||||
|
||||
.PHONY: test
|
||||
test: ; $(RUN) Makefile/test
|
||||
test-%: ; $(RUN) test-$*
|
||||
|
||||
@@ -159,7 +159,11 @@ void NativeMapView::invalidate() {
|
||||
detach_jni_thread(vm, &env, detach);
|
||||
}
|
||||
|
||||
void NativeMapView::swap() {
|
||||
void NativeMapView::beforeRender() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
void NativeMapView::afterRender() {
|
||||
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::swap");
|
||||
|
||||
if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) {
|
||||
|
||||
40
binding.gyp
Normal file
40
binding.gyp
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
'includes': [
|
||||
'gyp/common.gypi',
|
||||
],
|
||||
'targets': [
|
||||
{ 'target_name': '<(module_name)',
|
||||
'dependencies': [
|
||||
'mbgl.gyp:core',
|
||||
'mbgl.gyp:platform-<(platform_lib)',
|
||||
'mbgl.gyp:headless-<(headless_lib)',
|
||||
],
|
||||
|
||||
'include_dirs': [ "<!(node -e \"require('nan')\")" ],
|
||||
|
||||
'sources': [
|
||||
'platform/node/src/node_mapbox_gl_native.cpp',
|
||||
'platform/node/src/node_log.hpp',
|
||||
'platform/node/src/node_log.cpp',
|
||||
'platform/node/src/node_file_source.hpp',
|
||||
'platform/node/src/node_file_source.cpp',
|
||||
'platform/node/src/node_map.hpp',
|
||||
'platform/node/src/node_map.cpp',
|
||||
'platform/node/src/node_request.hpp',
|
||||
'platform/node/src/node_request.cpp',
|
||||
'platform/node/src/util/async_queue.hpp',
|
||||
],
|
||||
},
|
||||
|
||||
{ 'target_name': 'action_after_build',
|
||||
'type': 'none',
|
||||
'dependencies': [ '<(module_name)' ],
|
||||
'copies': [
|
||||
{
|
||||
'files': [ '<(PRODUCT_DIR)/<(module_name).node' ],
|
||||
'destination': '<(module_path)'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -84,6 +84,42 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"PolicyName": "list-node",
|
||||
"PolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::mapbox-node-binary"
|
||||
],
|
||||
"Effect": "Allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"PolicyName": "build-node",
|
||||
"PolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:PutObject",
|
||||
"s3:PutObjectAcl"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*"
|
||||
],
|
||||
"Effect": "Allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"PolicyName": "android",
|
||||
"PolicyDocument": {
|
||||
@@ -27,7 +27,8 @@ public:
|
||||
void deactivate() override;
|
||||
void notify() override;
|
||||
void invalidate() override;
|
||||
void swap() override;
|
||||
void beforeRender() override;
|
||||
void afterRender() override;
|
||||
|
||||
void notifyMapChange(mbgl::MapChange) override;
|
||||
|
||||
|
||||
@@ -62,8 +62,11 @@ public:
|
||||
// (map->renderSync() from the main thread must be called as a result of this)
|
||||
virtual void invalidate() = 0;
|
||||
|
||||
// Called from the render thread before the render begins.
|
||||
virtual void beforeRender() = 0;
|
||||
|
||||
// Called from the render thread after the render is complete.
|
||||
virtual void swap() = 0;
|
||||
virtual void afterRender() = 0;
|
||||
|
||||
// Reads the pixel data from the current framebuffer. If your View implementation
|
||||
// doesn't support reading from the framebuffer, return a null pointer.
|
||||
|
||||
@@ -24,7 +24,8 @@ public:
|
||||
void deactivate() override;
|
||||
void notify() override;
|
||||
void invalidate() override;
|
||||
void swap() override;
|
||||
void beforeRender() override;
|
||||
void afterRender() override;
|
||||
|
||||
static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods);
|
||||
static void onScroll(GLFWwindow *window, double xoffset, double yoffset);
|
||||
|
||||
@@ -37,9 +37,11 @@ public:
|
||||
void deactivate() override;
|
||||
void notify() override;
|
||||
void invalidate() override;
|
||||
void swap() override;
|
||||
void beforeRender() override;
|
||||
void afterRender() override;
|
||||
std::unique_ptr<StillImage> readStillImage() override;
|
||||
|
||||
void resizeFramebuffer();
|
||||
void resize(uint16_t width, uint16_t height);
|
||||
|
||||
private:
|
||||
@@ -52,6 +54,7 @@ private:
|
||||
std::shared_ptr<HeadlessDisplay> display;
|
||||
const float pixelRatio;
|
||||
std::array<uint16_t, 2> dimensions;
|
||||
bool needsResize;
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLContextObj glContext = nullptr;
|
||||
|
||||
48
package.json
Normal file
48
package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "mapbox-gl-native",
|
||||
"version": "2.0.0-pre.3",
|
||||
"description": "Renders map tiles with Mapbox GL",
|
||||
"keywords": [
|
||||
"mapbox",
|
||||
"gl"
|
||||
],
|
||||
"main": "lib/mapbox-gl-native.node",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/mapbox/mapbox-gl-native.git"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
"type": "BSD",
|
||||
"url": "https://github.com/mapbox/mapbox-gl-native/blob/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nan": "^1.9.0",
|
||||
"node-pre-gyp": "^0.6.9"
|
||||
},
|
||||
"bundledDependencies": [
|
||||
"node-pre-gyp"
|
||||
],
|
||||
"devDependencies": {
|
||||
"aws-sdk": "^2.1.47",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pngjs": "^0.4.0",
|
||||
"request": "^2.61.0",
|
||||
"st": "^0.5.5",
|
||||
"tape": "^4.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "node-pre-gyp install --fallback-to-build=false || make node",
|
||||
"test": "tape platform/node/test/js/**/*.test.js",
|
||||
"test-suite": "tape platform/node/test/render.test.js"
|
||||
},
|
||||
"gypfile": true,
|
||||
"binary": {
|
||||
"module_name": "mapbox-gl-native",
|
||||
"module_path": "./lib/",
|
||||
"host": "https://mapbox-node-binary.s3.amazonaws.com",
|
||||
"remote_path": "./{name}/v{version}",
|
||||
"package_name": "{node_abi}-{platform}-{arch}.tar.gz"
|
||||
}
|
||||
}
|
||||
@@ -386,7 +386,11 @@ void GLFWView::invalidate() {
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
|
||||
void GLFWView::swap() {
|
||||
void GLFWView::beforeRender() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
void GLFWView::afterRender() {
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,10 +114,10 @@ bool HeadlessView::isActive() {
|
||||
return std::this_thread::get_id() == thread;
|
||||
}
|
||||
|
||||
void HeadlessView::resize(const uint16_t width, const uint16_t height) {
|
||||
activate();
|
||||
void HeadlessView::resizeFramebuffer() {
|
||||
assert(isActive());
|
||||
|
||||
dimensions = {{ width, height }};
|
||||
if (!needsResize) return;
|
||||
|
||||
clearBuffers();
|
||||
|
||||
@@ -158,7 +158,12 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height) {
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
deactivate();
|
||||
needsResize = false;
|
||||
}
|
||||
|
||||
void HeadlessView::resize(const uint16_t width, const uint16_t height) {
|
||||
dimensions = {{ width, height }};
|
||||
needsResize = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<StillImage> HeadlessView::readStillImage() {
|
||||
@@ -293,7 +298,11 @@ void HeadlessView::invalidate() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
void HeadlessView::swap() {
|
||||
void HeadlessView::beforeRender() {
|
||||
resizeFramebuffer();
|
||||
}
|
||||
|
||||
void HeadlessView::afterRender() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
|
||||
@@ -3039,7 +3039,12 @@ class MBGLView : public mbgl::View
|
||||
waitUntilDone:NO];
|
||||
}
|
||||
|
||||
void swap() override
|
||||
void beforeRender() override
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
void afterRender() override
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
52
platform/node/CHANGELOG.md
Normal file
52
platform/node/CHANGELOG.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 1.1.3
|
||||
|
||||
- Removes deprecated mbgl::Environment from NodeLogObserver.
|
||||
|
||||
# 1.1.2
|
||||
|
||||
- Check libuv version semver-ishly, fixes segfaults in Node.js 0.12.x
|
||||
and iojs.
|
||||
- Fixes segfault, throws JavaScript error instead when attempting to
|
||||
render without first loading a style.
|
||||
- Bumps mbgl submodule to v0.4.0
|
||||
|
||||
# 1.1.1
|
||||
|
||||
- Bumps mbgl submodule to v0.3.5
|
||||
|
||||
# 1.1.0
|
||||
|
||||
- Adds Node.js v0.12.x and io.js support.
|
||||
- Adds `map.release()` method for manual cleanup of map resources.
|
||||
- Fixes garbage collection of NodeMap objects.
|
||||
- Returns an error callback for failed NodeFileSource requests.
|
||||
- Fixes handling of corrupt NodeFileSource request data.
|
||||
- Implements request coalescing to fix NodeRequest cancellation.
|
||||
- Removes `setAccessToken` method from NodeMap, `mapbox://` URLs
|
||||
should be handled through `NodeFileSource` now.
|
||||
- Updates build scripts and Travis CI configuration.
|
||||
- Logs Environment ID and thread name when in an Environment scope.
|
||||
- Refactors NodeLog to implement mbgl::Log::Observer.
|
||||
- Fixes uncaught exception from missing sprites.
|
||||
- Fixes Unicode glyph range end.
|
||||
|
||||
# 1.0.3
|
||||
|
||||
- Fixes crash during garbage collection by assigning FileSource handle
|
||||
to a v8::Persistent in NodeMap constructor.
|
||||
|
||||
# 1.0.2
|
||||
|
||||
- Initialize shared display connection at module load time to avoid
|
||||
race condition when display connection is initialized on-demand.
|
||||
|
||||
# 1.0.1
|
||||
|
||||
- Adapts NodeFileSource around mbgl::Environment additions.
|
||||
- Adapts to minor changes in mapbox-gl-test-suite.
|
||||
- Adds tests for gzipped vector tile handling.
|
||||
- Cleans up documentation.
|
||||
|
||||
# 1.0.0
|
||||
|
||||
- Initial release.
|
||||
207
platform/node/README.md
Normal file
207
platform/node/README.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# node-mapbox-gl-native
|
||||
|
||||
[](https://npmjs.org/package/mapbox-gl-native)
|
||||
|
||||
## Installing
|
||||
|
||||
Requires a modern C++ runtime that supports C++14.
|
||||
|
||||
By default, installs binaries. On these platforms no additional dependencies are needed.
|
||||
|
||||
- 64 bit OS X or 64 bit Linux
|
||||
- Node.js v0.10.x, Node.js v0.12.x, io.js v2.x
|
||||
|
||||
Just run:
|
||||
|
||||
```
|
||||
npm install mapbox-gl-native
|
||||
```
|
||||
|
||||
Other platforms will fall back to a source compile with `make node`. To compile this module, make sure all submodules are initialized with `git submodule update --init` and install the [external dependencies required to build from source](https://github.com/mapbox/mapbox-gl-native/blob/master/INSTALL.md#depends).
|
||||
|
||||
## Rendering a map tile
|
||||
|
||||
```js
|
||||
var map = new mbgl.Map({ request: function() {} });
|
||||
map.load(require('./test/fixtures/style.json'));
|
||||
map.render({}, function(err, image) {
|
||||
if (err) throw err;
|
||||
fs.writeFileSync('image.png', image);
|
||||
});
|
||||
```
|
||||
|
||||
The first argument passed to `map.render` is an options object, all keys are optional:
|
||||
|
||||
```js
|
||||
{
|
||||
zoom: {zoom}, // number, defaults to 0
|
||||
width: {width}, // number (px), defaults to 512
|
||||
height: {height}, // number (px), defaults to 512
|
||||
center: [{latitude}, {longitude}], // array of numbers (coordinates), defaults to [0,0]
|
||||
bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0
|
||||
classes: {classes} // array of strings
|
||||
}
|
||||
```
|
||||
|
||||
When you are finished using a map object, you can call `map.release()` to dispose the internal map resources manually. This is not necessary, but can be helpful to optimize resource usage (memory, file sockets) on a more granualar level than v8's garbage collector.
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Implementing a file source
|
||||
|
||||
When creating a `Map`, you must pass an options object (with a required `ratio`, required `request` and optional `cancel` method) as the first parameter.
|
||||
|
||||
```js
|
||||
var map = new mbgl.Map({
|
||||
request: function(req) {
|
||||
// TODO
|
||||
},
|
||||
cancel: function(req) {
|
||||
// TODO
|
||||
},
|
||||
ratio: 1.0
|
||||
});
|
||||
```
|
||||
|
||||
The `request()` method starts a new request to a file, while `cancel()` tells the FileSource to cancel the request (if possible). The `ratio` sets the scale at which the map will render tiles, such as `2.0` for rendering images for high pixel density displays. The `req` parameter has two properties:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "http://example.com",
|
||||
"kind": 1
|
||||
}
|
||||
```
|
||||
|
||||
The `kind` is an enum and defined in [`mbgl.Resource`](https://github.com/mapbox/mapbox-gl-native/blob/node/include/mbgl/storage/resource.hpp):
|
||||
|
||||
```json
|
||||
{
|
||||
"Unknown": 0,
|
||||
"Style": 1,
|
||||
"Source": 2,
|
||||
"Tile": 3,
|
||||
"Glyphs": 4,
|
||||
"SpriteImage": 5,
|
||||
"SpriteJSON": 6
|
||||
}
|
||||
```
|
||||
|
||||
It has no significance for anything but serves as a hint to your implemention as to what sort of resource to expect. E.g., your implementation could choose caching strategies based on the expected file type.
|
||||
|
||||
A sample implementation that reads files from disk would look like the following:
|
||||
|
||||
```js
|
||||
var map = new mbgl.Map({
|
||||
request: function(req) {
|
||||
fs.readFile(path.join('base/path', req.url), function(err, data) {
|
||||
req.respond(err, { data: data });
|
||||
});
|
||||
},
|
||||
ratio: 1.0
|
||||
});
|
||||
```
|
||||
|
||||
This is a very barebones implementation and you'll probably want a better implementation. E.g. it passes the url verbatim to the file system, but you'd want add some logic that normalizes `http` URLs. You'll notice that once your implementation has obtained the requested file, you have to deliver it to the requestee by calling `req.respond()`, which takes either an error object or `null` and an object with several settings:
|
||||
|
||||
```js
|
||||
{
|
||||
modified: new Date(),
|
||||
expires: new Date(),
|
||||
etag: "string",
|
||||
data: new Buffer()
|
||||
};
|
||||
```
|
||||
|
||||
A sample implementation that uses [`request`](https://github.com/request/request) to query data from HTTP:
|
||||
|
||||
```js
|
||||
var mbgl = require('mapbox-gl-native');
|
||||
var request = require('request');
|
||||
|
||||
var map = new mbgl.Map({
|
||||
request: function(req) {
|
||||
request({
|
||||
url: req.url,
|
||||
encoding: null,
|
||||
gzip: true
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
req.respond(err);
|
||||
} else if (res.statusCode == 200) {
|
||||
var response = {};
|
||||
|
||||
if (res.headers.modified) { response.modified = new Date(res.headers.modified); }
|
||||
if (res.headers.expires) { response.expires = new Date(res.headers.expires); }
|
||||
if (res.headers.etag) { response.etag = res.headers.etag; }
|
||||
|
||||
response.data = body;
|
||||
|
||||
req.respond(null, response);
|
||||
} else {
|
||||
req.respond(new Error(JSON.parse(body).message));
|
||||
}
|
||||
});
|
||||
},
|
||||
ratio: 1.0
|
||||
});
|
||||
```
|
||||
|
||||
Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of `request` supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3.
|
||||
|
||||
## Mapbox API Access tokens
|
||||
|
||||
To use styles that rely on Mapbox vector tiles, you must pass an [API access token](https://www.mapbox.com/developers/api/#access-tokens) in your `request` implementation with requests to `mapbox://` protocols.
|
||||
|
||||
```js
|
||||
var mbgl = require('mapbox-gl-native');
|
||||
var request = require('request');
|
||||
var url = require('url');
|
||||
|
||||
var map = new mbgl.Map({
|
||||
request: function(req) {
|
||||
var opts = {
|
||||
url: req.url,
|
||||
encoding: null,
|
||||
gzip: true
|
||||
};
|
||||
|
||||
if (url.parse(req.url).protocol === 'mapbox:') {
|
||||
opts.qs = { access_token: process.env.MAPBOX_ACCESS_TOKEN};
|
||||
}
|
||||
|
||||
request(opts, function (err, res, body) {
|
||||
if (err) {
|
||||
req.respond(err);
|
||||
} else if (res.statusCode == 200) {
|
||||
var response = {};
|
||||
|
||||
if (res.headers.modified) { response.modified = new Date(res.headers.modified); }
|
||||
if (res.headers.expires) { response.expires = new Date(res.headers.expires); }
|
||||
if (res.headers.etag) { response.etag = res.headers.etag; }
|
||||
|
||||
response.data = body;
|
||||
|
||||
req.respond(null, response);
|
||||
} else {
|
||||
req.respond(new Error(JSON.parse(body).message));
|
||||
}
|
||||
});
|
||||
},
|
||||
ratio: 1.0
|
||||
});
|
||||
|
||||
// includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6`
|
||||
var style = mapboxStyle;
|
||||
|
||||
map.load(style);
|
||||
map.render({}, function(err, image) {
|
||||
if (err) throw err;
|
||||
fs.writeFileSync('image.png', image);
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
161
platform/node/src/node_file_source.cpp
Normal file
161
platform/node/src/node_file_source.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "node_file_source.hpp"
|
||||
#include "node_request.hpp"
|
||||
#include "util/async_queue.hpp"
|
||||
|
||||
#include <mbgl/storage/request.hpp>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
struct NodeFileSource::Action {
|
||||
const enum : bool { Add, Cancel } type;
|
||||
mbgl::Resource const resource;
|
||||
};
|
||||
|
||||
NodeFileSource::NodeFileSource(v8::Handle<v8::Object> options_) :
|
||||
queue(new Queue(uv_default_loop(), [this](Action &action) {
|
||||
if (action.type == Action::Add) {
|
||||
processAdd(action.resource);
|
||||
} else if (action.type == Action::Cancel) {
|
||||
processCancel(action.resource);
|
||||
}
|
||||
}))
|
||||
{
|
||||
NanAssignPersistent(options, options_->ToObject());
|
||||
|
||||
// Make sure that the queue doesn't block the loop from exiting.
|
||||
queue->unref();
|
||||
}
|
||||
|
||||
NodeFileSource::~NodeFileSource() {
|
||||
queue->stop();
|
||||
queue = nullptr;
|
||||
|
||||
NanDisposePersistent(options);
|
||||
}
|
||||
|
||||
mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) {
|
||||
auto req = new mbgl::Request(resource, loop, std::move(callback));
|
||||
|
||||
std::lock_guard<std::mutex> lock(observersMutex);
|
||||
|
||||
assert(observers.find(resource) == observers.end());
|
||||
observers[resource] = req;
|
||||
|
||||
// This function can be called from any thread. Make sure we're executing the actual call in the
|
||||
// file source loop by sending it over the queue. It will be processed in processAction().
|
||||
queue->send(Action{ Action::Add, resource });
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
void NodeFileSource::cancel(mbgl::Request* req) {
|
||||
req->cancel();
|
||||
|
||||
std::lock_guard<std::mutex> lock(observersMutex);
|
||||
|
||||
auto it = observers.find(req->resource);
|
||||
if (it == observers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
observers.erase(it);
|
||||
|
||||
// This function can be called from any thread. Make sure we're executing the actual call in the
|
||||
// file source loop by sending it over the queue. It will be processed in processAction().
|
||||
queue->send(Action{ Action::Cancel, req->resource });
|
||||
|
||||
req->destruct();
|
||||
}
|
||||
|
||||
void NodeFileSource::processAdd(const mbgl::Resource& resource) {
|
||||
NanScope();
|
||||
|
||||
// Make sure the loop stays alive as long as request is pending.
|
||||
if (pending.empty()) {
|
||||
queue->ref();
|
||||
}
|
||||
|
||||
auto requestHandle = NanNew<v8::Object>(NodeRequest::Create(this, resource));
|
||||
|
||||
v8::Persistent<v8::Object> requestPersistent;
|
||||
NanAssignPersistent(requestPersistent, requestHandle);
|
||||
pending.emplace(resource, std::move(requestPersistent));
|
||||
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
auto requestFunction = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), options)->Get(NanNew("request")).As<v8::Function>();
|
||||
#else
|
||||
auto requestFunction = options->Get(NanNew("request")).As<v8::Function>();
|
||||
#endif
|
||||
|
||||
v8::Local<v8::Value> argv[] = { requestHandle };
|
||||
NanMakeCallback(NanGetCurrentContext()->Global(), requestFunction, 1, argv);
|
||||
}
|
||||
|
||||
void NodeFileSource::processCancel(const mbgl::Resource& resource) {
|
||||
NanScope();
|
||||
|
||||
auto it = pending.find(resource);
|
||||
if (it == pending.end()) {
|
||||
// The response callback was already fired. There is no point in calling the cancelation
|
||||
// callback because the request is already completed.
|
||||
} else {
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
auto requestHandle = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), it->second);
|
||||
it->second.Reset();
|
||||
#else
|
||||
auto requestHandle = NanNew<v8::Object>(it->second);
|
||||
NanDisposePersistent(it->second);
|
||||
#endif
|
||||
pending.erase(it);
|
||||
|
||||
// Make sure the the loop can exit when there are no pending requests.
|
||||
if (pending.empty()) {
|
||||
queue->unref();
|
||||
}
|
||||
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
auto optionsObject = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), options);
|
||||
if (optionsObject->Has(NanNew("cancel"))) {
|
||||
auto cancelFunction = optionsObject->Get(NanNew("cancel")).As<v8::Function>();
|
||||
#else
|
||||
if (options->Has(NanNew("cancel"))) {
|
||||
auto cancelFunction = options->Get(NanNew("cancel")).As<v8::Function>();
|
||||
#endif
|
||||
v8::Local<v8::Value> argv[] = { requestHandle };
|
||||
NanMakeCallback(NanGetCurrentContext()->Global(), cancelFunction, 1, argv);
|
||||
}
|
||||
|
||||
// Set the request handle in the request wrapper handle to null
|
||||
node::ObjectWrap::Unwrap<NodeRequest>(requestHandle)->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_ptr<const mbgl::Response>& response) {
|
||||
// First, remove the request, since it might be destructed at any point now.
|
||||
auto it = pending.find(resource);
|
||||
if (it != pending.end()) {
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
it->second.Reset();
|
||||
#else
|
||||
NanDisposePersistent(it->second);
|
||||
#endif
|
||||
pending.erase(it);
|
||||
|
||||
// Make sure the the loop can exit when there are no pending requests.
|
||||
if (pending.empty()) {
|
||||
queue->unref();
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(observersMutex);
|
||||
|
||||
auto observersIt = observers.find(resource);
|
||||
if (observersIt == observers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
observersIt->second->notify(response);
|
||||
observers.erase(observersIt);
|
||||
}
|
||||
|
||||
}
|
||||
60
platform/node/src/node_file_source.hpp
Normal file
60
platform/node/src/node_file_source.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <node.h>
|
||||
#include <node_version.h>
|
||||
#include <nan.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
namespace util { template <typename T> class AsyncQueue; }
|
||||
|
||||
class NodeFileSource : public mbgl::FileSource {
|
||||
public:
|
||||
NodeFileSource(v8::Handle<v8::Object>);
|
||||
~NodeFileSource();
|
||||
|
||||
mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback);
|
||||
void cancel(mbgl::Request*);
|
||||
|
||||
// visiblity?
|
||||
void notify(const mbgl::Resource&, const std::shared_ptr<const mbgl::Response>&);
|
||||
|
||||
private:
|
||||
struct Action;
|
||||
using Queue = util::AsyncQueue<Action>;
|
||||
|
||||
void processAdd(const mbgl::Resource&);
|
||||
void processCancel(const mbgl::Resource&);
|
||||
|
||||
v8::Persistent<v8::Object> options;
|
||||
|
||||
private:
|
||||
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
|
||||
std::unordered_map<mbgl::Resource, v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object>>, mbgl::Resource::Hash> pending;
|
||||
#else
|
||||
std::unordered_map<mbgl::Resource, v8::Persistent<v8::Object>, mbgl::Resource::Hash> pending;
|
||||
#endif
|
||||
|
||||
// The observers list will hold pointers to all the requests waiting
|
||||
// for a particular resource. The access must be guarded by a mutex
|
||||
// because the list is also accessed by a thread from the mbgl::Map
|
||||
// object and from the main thread when notifying requests of
|
||||
// completion. Concurrent access is specially needed when
|
||||
// canceling a request to avoid a deadlock (see #129).
|
||||
std::unordered_map<mbgl::Resource, mbgl::Request*, mbgl::Resource::Hash> observers;
|
||||
std::mutex observersMutex;
|
||||
|
||||
Queue *queue = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
54
platform/node/src/node_log.cpp
Normal file
54
platform/node/src/node_log.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "node_log.hpp"
|
||||
#include "util/async_queue.hpp"
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
struct NodeLogObserver::LogMessage {
|
||||
mbgl::EventSeverity severity;
|
||||
mbgl::Event event;
|
||||
int64_t code;
|
||||
std::string text;
|
||||
|
||||
LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_)
|
||||
: severity(severity_),
|
||||
event(event_),
|
||||
code(code_),
|
||||
text(text_) {}
|
||||
};
|
||||
|
||||
NodeLogObserver::NodeLogObserver(v8::Handle<v8::Object> target)
|
||||
: queue(new Queue(uv_default_loop(), [this](LogMessage &message) {
|
||||
NanScope();
|
||||
|
||||
auto msg = NanNew<v8::Object>();
|
||||
msg->Set(NanNew("class"), NanNew(mbgl::EventClass(message.event).c_str()));
|
||||
msg->Set(NanNew("severity"), NanNew(mbgl::EventSeverityClass(message.severity).c_str()));
|
||||
if (message.code != -1) {
|
||||
msg->Set(NanNew("code"), NanNew<v8::Number>(message.code));
|
||||
}
|
||||
if (!message.text.empty()) {
|
||||
msg->Set(NanNew("text"), NanNew(message.text));
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> argv[] = { NanNew("message"), msg };
|
||||
auto handle = NanNew<v8::Object>(module);
|
||||
auto emit = handle->Get(NanNew("emit"))->ToObject();
|
||||
emit->CallAsFunction(handle, 2, argv);
|
||||
})) {
|
||||
NanScope();
|
||||
NanAssignPersistent(module, target);
|
||||
|
||||
// Don't keep the event loop alive.
|
||||
queue->unref();
|
||||
}
|
||||
|
||||
NodeLogObserver::~NodeLogObserver() {
|
||||
queue->stop();
|
||||
}
|
||||
|
||||
bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) {
|
||||
queue->send({ severity, event, code, text });
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
32
platform/node/src/node_log.hpp
Normal file
32
platform/node/src/node_log.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/platform/log.hpp>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
namespace util { template <typename T> class AsyncQueue; }
|
||||
|
||||
class NodeLogObserver : public mbgl::Log::Observer {
|
||||
public:
|
||||
NodeLogObserver(v8::Handle<v8::Object> target);
|
||||
~NodeLogObserver();
|
||||
|
||||
// Log::Observer implementation
|
||||
virtual bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) override;
|
||||
|
||||
private:
|
||||
v8::Persistent<v8::Object> module;
|
||||
|
||||
struct LogMessage;
|
||||
using Queue = util::AsyncQueue<LogMessage>;
|
||||
Queue *queue = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
347
platform/node/src/node_map.cpp
Normal file
347
platform/node/src/node_map.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#include "node_map.hpp"
|
||||
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
struct NodeMap::RenderOptions {
|
||||
double zoom = 0;
|
||||
double bearing = 0;
|
||||
double latitude = 0;
|
||||
double longitude = 0;
|
||||
unsigned int width = 512;
|
||||
unsigned int height = 512;
|
||||
std::vector<std::string> classes;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> NodeMap::constructorTemplate;
|
||||
|
||||
static std::shared_ptr<mbgl::HeadlessDisplay> sharedDisplay() {
|
||||
static auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
return display;
|
||||
}
|
||||
|
||||
const static char* releasedMessage() {
|
||||
return "Map resources have already been released";
|
||||
}
|
||||
|
||||
void NodeMap::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("Map"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "load", Load);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "render", Render);
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "release", Release);
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("Map"), t->GetFunction());
|
||||
|
||||
// Initialize display connection on module load.
|
||||
sharedDisplay();
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeMap::New) {
|
||||
NanScope();
|
||||
|
||||
if (!args.IsConstructCall()) {
|
||||
return NanThrowTypeError("Use the new operator to create new Map objects");
|
||||
}
|
||||
|
||||
if (args.Length() < 1 || !args[0]->IsObject()) {
|
||||
return NanThrowTypeError("Requires an options object as first argument");
|
||||
}
|
||||
|
||||
auto options = args[0]->ToObject();
|
||||
|
||||
// Check that 'request', 'cancel' and 'ratio' are defined.
|
||||
if (!options->Has(NanNew("request")) || !options->Get(NanNew("request"))->IsFunction()) {
|
||||
return NanThrowError("Options object must have a 'request' method");
|
||||
}
|
||||
if (options->Has(NanNew("cancel")) && !options->Get(NanNew("cancel"))->IsFunction()) {
|
||||
return NanThrowError("Options object 'cancel' property must be a function");
|
||||
}
|
||||
|
||||
if (!options->Has(NanNew("ratio")) || !options->Get(NanNew("ratio"))->IsNumber()) {
|
||||
return NanThrowError("Options object must have a numerical 'ratio' property");
|
||||
}
|
||||
|
||||
try {
|
||||
auto nodeMap = new NodeMap(options);
|
||||
nodeMap->Wrap(args.This());
|
||||
} catch(std::exception &ex) {
|
||||
return NanThrowError(ex.what());
|
||||
}
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
const std::string StringifyStyle(v8::Handle<v8::Value> styleHandle) {
|
||||
NanScope();
|
||||
|
||||
auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject();
|
||||
auto stringify = v8::Handle<v8::Function>::Cast(JSON->Get(NanNew("stringify")));
|
||||
|
||||
return *NanUtf8String(stringify->Call(JSON, 1, &styleHandle));
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeMap::Load) {
|
||||
NanScope();
|
||||
|
||||
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
|
||||
|
||||
if (!nodeMap->isValid()) return NanThrowError(releasedMessage());
|
||||
|
||||
// Reset the flag as this could be the second time
|
||||
// we are calling this (being the previous successful).
|
||||
nodeMap->loaded = false;
|
||||
|
||||
if (args.Length() < 1) {
|
||||
return NanThrowError("Requires a map style as first argument");
|
||||
}
|
||||
|
||||
std::string style;
|
||||
|
||||
if (args[0]->IsObject()) {
|
||||
style = StringifyStyle(args[0]);
|
||||
} else if (args[0]->IsString()) {
|
||||
style = *NanUtf8String(args[0]);
|
||||
} else {
|
||||
return NanThrowTypeError("First argument must be a string or object");
|
||||
}
|
||||
|
||||
try {
|
||||
nodeMap->map->setStyleJSON(style, ".");
|
||||
} catch (const std::exception &ex) {
|
||||
return NanThrowError(ex.what());
|
||||
}
|
||||
|
||||
nodeMap->loaded = true;
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
std::unique_ptr<NodeMap::RenderOptions> NodeMap::ParseOptions(v8::Local<v8::Object> obj) {
|
||||
NanScope();
|
||||
|
||||
auto options = std::make_unique<RenderOptions>();
|
||||
|
||||
if (obj->Has(NanNew("zoom"))) { options->zoom = obj->Get(NanNew("zoom"))->NumberValue(); }
|
||||
if (obj->Has(NanNew("bearing"))) { options->bearing = obj->Get(NanNew("bearing"))->NumberValue(); }
|
||||
if (obj->Has(NanNew("center"))) {
|
||||
auto center = obj->Get(NanNew("center")).As<v8::Array>();
|
||||
if (center->Length() > 0) { options->latitude = center->Get(0)->NumberValue(); }
|
||||
if (center->Length() > 1) { options->longitude = center->Get(1)->NumberValue(); }
|
||||
}
|
||||
if (obj->Has(NanNew("width"))) { options->width = obj->Get(NanNew("width"))->IntegerValue(); }
|
||||
if (obj->Has(NanNew("height"))) { options->height = obj->Get(NanNew("height"))->IntegerValue(); }
|
||||
|
||||
if (obj->Has(NanNew("classes"))) {
|
||||
auto classes = obj->Get(NanNew("classes"))->ToObject().As<v8::Array>();
|
||||
const int length = classes->Length();
|
||||
options->classes.reserve(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
options->classes.push_back(std::string { *NanUtf8String(classes->Get(i)->ToString()) });
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeMap::Render) {
|
||||
NanScope();
|
||||
|
||||
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
|
||||
|
||||
if (!nodeMap->isValid()) return NanThrowError(releasedMessage());
|
||||
|
||||
if (args.Length() <= 0 || !args[0]->IsObject()) {
|
||||
return NanThrowTypeError("First argument must be an options object");
|
||||
}
|
||||
|
||||
if (args.Length() <= 1 || !args[1]->IsFunction()) {
|
||||
return NanThrowTypeError("Second argument must be a callback function");
|
||||
}
|
||||
|
||||
if (!nodeMap->isLoaded()) {
|
||||
return NanThrowTypeError("Style is not loaded");
|
||||
}
|
||||
|
||||
auto options = ParseOptions(args[0]->ToObject());
|
||||
|
||||
assert(!nodeMap->callback);
|
||||
assert(!nodeMap->image);
|
||||
nodeMap->callback = std::unique_ptr<NanCallback>(new NanCallback(args[1].As<v8::Function>()));
|
||||
|
||||
try {
|
||||
nodeMap->startRender(std::move(options));
|
||||
} catch (mbgl::util::Exception &ex) {
|
||||
return NanThrowError(ex.what());
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
void NodeMap::startRender(std::unique_ptr<NodeMap::RenderOptions> options) {
|
||||
view.resize(options->width, options->height);
|
||||
map->update(mbgl::Update::Dimensions);
|
||||
map->setClasses(options->classes);
|
||||
map->setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom);
|
||||
map->setBearing(options->bearing);
|
||||
|
||||
map->renderStill([this](const std::exception_ptr eptr, std::unique_ptr<const mbgl::StillImage> result) {
|
||||
if (eptr) {
|
||||
error = std::move(eptr);
|
||||
uv_async_send(async);
|
||||
} else {
|
||||
assert(!image);
|
||||
image = std::move(result);
|
||||
uv_async_send(async);
|
||||
}
|
||||
});
|
||||
|
||||
// Retain this object, otherwise it might get destructed before we are finished rendering the
|
||||
// still image.
|
||||
Ref();
|
||||
|
||||
// Similarly, we're now waiting for the async to be called, so we need to make sure that it
|
||||
// keeps the loop alive.
|
||||
uv_ref(reinterpret_cast<uv_handle_t *>(async));
|
||||
}
|
||||
|
||||
void NodeMap::renderFinished() {
|
||||
NanScope();
|
||||
|
||||
// We're done with this render call, so we're unrefing so that the loop could close.
|
||||
uv_unref(reinterpret_cast<uv_handle_t *>(async));
|
||||
|
||||
// There is no render pending anymore, we the GC could now delete this object if it went out
|
||||
// of scope.
|
||||
Unref();
|
||||
|
||||
// Move the callback and image out of the way so that the callback can start a new render call.
|
||||
auto cb = std::move(callback);
|
||||
auto img = std::move(image);
|
||||
assert(cb);
|
||||
|
||||
// These have to be empty to be prepared for the next render call.
|
||||
assert(!callback);
|
||||
assert(!image);
|
||||
|
||||
if (error) {
|
||||
std::string errorMessage;
|
||||
|
||||
try {
|
||||
std::rethrow_exception(error);
|
||||
} catch (const std::exception& ex) {
|
||||
errorMessage = ex.what();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> argv[] = {
|
||||
NanError(errorMessage.c_str())
|
||||
};
|
||||
|
||||
// This must be empty to be prepared for the next render call.
|
||||
error = nullptr;
|
||||
assert(!error);
|
||||
|
||||
cb->Call(1, argv);
|
||||
} else if (img) {
|
||||
auto result = NanNew<v8::Object>();
|
||||
result->Set(NanNew("width"), NanNew(img->width));
|
||||
result->Set(NanNew("height"), NanNew(img->height));
|
||||
|
||||
v8::Local<v8::Object> pixels = NanNewBufferHandle(
|
||||
reinterpret_cast<char *>(img->pixels.get()),
|
||||
size_t(img->width) * size_t(img->height) * sizeof(mbgl::StillImage::Pixel),
|
||||
|
||||
// Retain the StillImage object until the buffer is deleted.
|
||||
[](char *, void *hint) {
|
||||
delete reinterpret_cast<const mbgl::StillImage *>(hint);
|
||||
},
|
||||
const_cast<mbgl::StillImage *>(img.get())
|
||||
);
|
||||
img.release();
|
||||
|
||||
result->Set(NanNew("pixels"), pixels);
|
||||
|
||||
v8::Local<v8::Value> argv[] = {
|
||||
NanNull(),
|
||||
result,
|
||||
};
|
||||
cb->Call(2, argv);
|
||||
} else {
|
||||
v8::Local<v8::Value> argv[] = {
|
||||
NanError("Didn't get an image")
|
||||
};
|
||||
cb->Call(1, argv);
|
||||
}
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeMap::Release) {
|
||||
NanScope();
|
||||
|
||||
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
|
||||
|
||||
if (!nodeMap->isValid()) return NanThrowError(releasedMessage());
|
||||
|
||||
try {
|
||||
nodeMap->release();
|
||||
} catch (const std::exception &ex) {
|
||||
return NanThrowError(ex.what());
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
void NodeMap::release() {
|
||||
if (!isValid()) throw mbgl::util::Exception(releasedMessage());
|
||||
|
||||
valid = false;
|
||||
|
||||
uv_close(reinterpret_cast<uv_handle_t *>(async), [] (uv_handle_t *handle) {
|
||||
delete reinterpret_cast<uv_async_t *>(handle);
|
||||
});
|
||||
|
||||
map.reset(nullptr);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
NodeMap::NodeMap(v8::Handle<v8::Object> options) :
|
||||
view(sharedDisplay(), options->Get(NanNew("ratio"))->NumberValue()),
|
||||
fs(options),
|
||||
map(std::make_unique<mbgl::Map>(view, fs, mbgl::MapMode::Still)),
|
||||
async(new uv_async_t) {
|
||||
|
||||
async->data = this;
|
||||
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
|
||||
uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
|
||||
#else
|
||||
uv_async_init(uv_default_loop(), async, [](uv_async_t *as) {
|
||||
#endif
|
||||
reinterpret_cast<NodeMap *>(as->data)->renderFinished();
|
||||
});
|
||||
|
||||
// Make sure the async handle doesn't keep the loop alive.
|
||||
uv_unref(reinterpret_cast<uv_handle_t *>(async));
|
||||
}
|
||||
|
||||
NodeMap::~NodeMap() {
|
||||
if (valid) release();
|
||||
}
|
||||
|
||||
}
|
||||
66
platform/node/src/node_map.hpp
Normal file
66
platform/node/src/node_map.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "node_file_source.hpp"
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/platform/default/headless_view.hpp>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
class NodeMap : public node::ObjectWrap {
|
||||
struct RenderOptions;
|
||||
class RenderWorker;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(Load);
|
||||
static NAN_METHOD(Render);
|
||||
static NAN_METHOD(Release);
|
||||
|
||||
void startRender(std::unique_ptr<NodeMap::RenderOptions> options);
|
||||
void renderFinished();
|
||||
|
||||
void release();
|
||||
|
||||
inline bool isLoaded() { return loaded; }
|
||||
inline bool isValid() { return valid; }
|
||||
|
||||
static std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
private:
|
||||
NodeMap(v8::Handle<v8::Object>);
|
||||
~NodeMap();
|
||||
|
||||
private:
|
||||
mbgl::HeadlessView view;
|
||||
NodeFileSource fs;
|
||||
std::unique_ptr<mbgl::Map> map;
|
||||
|
||||
std::exception_ptr error;
|
||||
std::unique_ptr<const mbgl::StillImage> image;
|
||||
std::unique_ptr<NanCallback> callback;
|
||||
|
||||
// Async for delivering the notifications of render completion.
|
||||
uv_async_t *async;
|
||||
|
||||
bool loaded = false;
|
||||
bool valid = true;
|
||||
};
|
||||
|
||||
}
|
||||
38
platform/node/src/node_mapbox_gl_native.cpp
Normal file
38
platform/node/src/node_mapbox_gl_native.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include "node_map.hpp"
|
||||
#include "node_log.hpp"
|
||||
#include "node_request.hpp"
|
||||
|
||||
void RegisterModule(v8::Handle<v8::Object> exports) {
|
||||
NanScope();
|
||||
|
||||
node_mbgl::NodeMap::Init(exports);
|
||||
node_mbgl::NodeRequest::Init(exports);
|
||||
|
||||
// Exports Resource constants.
|
||||
auto ConstantProperty = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
|
||||
auto resource = NanNew<v8::Object>();
|
||||
resource->ForceSet(NanNew("Unknown"), NanNew(mbgl::Resource::Unknown), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Style"), NanNew(mbgl::Resource::Style), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Source"), NanNew(mbgl::Resource::Source), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Tile"), NanNew(mbgl::Resource::Tile), ConstantProperty);
|
||||
resource->ForceSet(NanNew("Glyphs"), NanNew(mbgl::Resource::Glyphs), ConstantProperty);
|
||||
resource->ForceSet(NanNew("SpriteImage"), NanNew(mbgl::Resource::SpriteImage), ConstantProperty);
|
||||
resource->ForceSet(NanNew("SpriteJSON"), NanNew(mbgl::Resource::SpriteJSON), ConstantProperty);
|
||||
exports->ForceSet(NanNew("Resource"), resource, ConstantProperty);
|
||||
|
||||
// Make the exported object inerhit from process.EventEmitter
|
||||
auto process = NanGetCurrentContext()->Global()->Get(NanNew("process"))->ToObject();
|
||||
auto EventEmitter = process->Get(NanNew("EventEmitter"))->ToObject();
|
||||
exports->SetPrototype(EventEmitter->Get(NanNew("prototype")));
|
||||
|
||||
mbgl::Log::setObserver(std::make_unique<node_mbgl::NodeLogObserver>(exports));
|
||||
}
|
||||
|
||||
NODE_MODULE(mapbox_gl_native, RegisterModule)
|
||||
144
platform/node/src/node_request.cpp
Normal file
144
platform/node/src/node_request.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "node_request.hpp"
|
||||
#include "node_file_source.hpp"
|
||||
#include <mbgl/storage/request.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
|
||||
v8::Persistent<v8::FunctionTemplate> NodeRequest::constructorTemplate;
|
||||
|
||||
void NodeRequest::Init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
|
||||
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
|
||||
|
||||
t->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
t->SetClassName(NanNew("Request"));
|
||||
|
||||
NODE_SET_PROTOTYPE_METHOD(t, "respond", Respond);
|
||||
|
||||
NanAssignPersistent(constructorTemplate, t);
|
||||
|
||||
target->Set(NanNew("Request"), t->GetFunction());
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeRequest::New) {
|
||||
NanScope();
|
||||
|
||||
// Extract the pointer from the first argument
|
||||
if (args.Length() < 2 || !args[0]->IsExternal() || !args[1]->IsExternal()) {
|
||||
return NanThrowTypeError("Cannot create Request objects explicitly");
|
||||
}
|
||||
|
||||
auto source = reinterpret_cast<NodeFileSource*>(args[0].As<v8::External>()->Value());
|
||||
auto resource = reinterpret_cast<mbgl::Resource*>(args[1].As<v8::External>()->Value());
|
||||
auto req = new NodeRequest(source, *resource);
|
||||
req->Wrap(args.This());
|
||||
|
||||
NanReturnValue(args.This());
|
||||
}
|
||||
|
||||
v8::Handle<v8::Object> NodeRequest::Create(NodeFileSource* source, const mbgl::Resource& resource) {
|
||||
NanEscapableScope();
|
||||
|
||||
v8::Local<v8::Value> argv[] = { NanNew<v8::External>(const_cast<NodeFileSource*>(source)),
|
||||
NanNew<v8::External>(const_cast<mbgl::Resource*>(&resource)) };
|
||||
auto instance = NanNew<v8::FunctionTemplate>(constructorTemplate)->GetFunction()->NewInstance(2, argv);
|
||||
|
||||
instance->ForceSet(NanNew("url"), NanNew(resource.url), v8::ReadOnly);
|
||||
instance->ForceSet(NanNew("kind"), NanNew<v8::Integer>(int(resource.kind)), v8::ReadOnly);
|
||||
|
||||
return NanEscapeScope(instance);
|
||||
}
|
||||
|
||||
NAN_METHOD(NodeRequest::Respond) {
|
||||
auto nodeRequest = ObjectWrap::Unwrap<NodeRequest>(args.Holder());
|
||||
|
||||
// Request has already been responded to, or was canceled, fail silently.
|
||||
if (!nodeRequest->resource) NanReturnUndefined();
|
||||
|
||||
auto source = nodeRequest->source;
|
||||
auto resource = std::move(nodeRequest->resource);
|
||||
|
||||
if (args.Length() < 1) {
|
||||
return NanThrowTypeError("First argument must be an error object");
|
||||
} else if (args[0]->BooleanValue()) {
|
||||
auto response = std::make_shared<mbgl::Response>();
|
||||
|
||||
response->status = mbgl::Response::Error;
|
||||
|
||||
// Store the error string.
|
||||
const NanUtf8String message { args[0]->ToString() };
|
||||
response->message = std::string { *message, size_t(message.length()) };
|
||||
|
||||
source->notify(*resource, response);
|
||||
} else if (args.Length() < 2 || !args[1]->IsObject()) {
|
||||
return NanThrowTypeError("Second argument must be a response object");
|
||||
} else {
|
||||
auto response = std::make_shared<mbgl::Response>();
|
||||
auto res = args[1]->ToObject();
|
||||
|
||||
response->status = mbgl::Response::Successful;
|
||||
|
||||
if (res->Has(NanNew("modified"))) {
|
||||
const double modified = res->Get(NanNew("modified"))->ToNumber()->Value();
|
||||
if (!std::isnan(modified)) {
|
||||
response->modified = modified / 1000; // JS timestamps are milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("expires"))) {
|
||||
const double expires = res->Get(NanNew("expires"))->ToNumber()->Value();
|
||||
if (!std::isnan(expires)) {
|
||||
response->expires = expires / 1000; // JS timestamps are milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("etag"))) {
|
||||
auto etagHandle = res->Get(NanNew("etag"));
|
||||
if (etagHandle->BooleanValue()) {
|
||||
const NanUtf8String etag { etagHandle->ToString() };
|
||||
response->etag = std::string { *etag, size_t(etag.length()) };
|
||||
}
|
||||
}
|
||||
|
||||
if (res->Has(NanNew("data"))) {
|
||||
auto dataHandle = res->Get(NanNew("data"));
|
||||
if (node::Buffer::HasInstance(dataHandle)) {
|
||||
response->data = std::string {
|
||||
node::Buffer::Data(dataHandle),
|
||||
node::Buffer::Length(dataHandle)
|
||||
};
|
||||
} else {
|
||||
return NanThrowTypeError("Response data must be a Buffer");
|
||||
}
|
||||
}
|
||||
|
||||
// Send the response object to the NodeFileSource object
|
||||
source->notify(*resource, response);
|
||||
}
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
|
||||
NodeRequest::NodeRequest(NodeFileSource* source_, const mbgl::Resource& resource_)
|
||||
: source(source_),
|
||||
resource(std::make_unique<mbgl::Resource>(resource_)) {}
|
||||
|
||||
NodeRequest::~NodeRequest() {
|
||||
}
|
||||
|
||||
void NodeRequest::cancel() {
|
||||
resource.reset();
|
||||
}
|
||||
|
||||
}
|
||||
43
platform/node/src/node_request.hpp
Normal file
43
platform/node/src/node_request.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
#include <node.h>
|
||||
#include <nan.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace node_mbgl {
|
||||
|
||||
class NodeFileSource;
|
||||
|
||||
class NodeRequest : public node::ObjectWrap {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Static Node Methods
|
||||
public:
|
||||
static void Init(v8::Handle<v8::Object> target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(Respond);
|
||||
|
||||
static v8::Handle<v8::Object> Create(NodeFileSource*, const mbgl::Resource&);
|
||||
|
||||
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Instance
|
||||
public:
|
||||
NodeRequest(NodeFileSource* source, const mbgl::Resource& resource);
|
||||
~NodeRequest();
|
||||
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
NodeFileSource* source;
|
||||
std::unique_ptr<mbgl::Resource> resource;
|
||||
};
|
||||
|
||||
}
|
||||
90
platform/node/src/util/async_queue.hpp
Normal file
90
platform/node/src/util/async_queue.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
|
||||
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
|
||||
#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int
|
||||
#else
|
||||
#define UV_ASYNC_PARAMS(handle) uv_async_t *handle
|
||||
#endif
|
||||
|
||||
namespace node_mbgl {
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
class AsyncQueue {
|
||||
public:
|
||||
AsyncQueue(uv_loop_t *loop, std::function<void(T &)> fn) :
|
||||
callback(fn) {
|
||||
async.data = this;
|
||||
uv_async_init(loop, &async, [](UV_ASYNC_PARAMS(handle)) {
|
||||
auto q = reinterpret_cast<AsyncQueue *>(handle->data);
|
||||
q->process();
|
||||
});
|
||||
}
|
||||
|
||||
void send(T &&data) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
queue.push(std::make_unique<T>(std::move(data)));
|
||||
}
|
||||
uv_async_send(&async);
|
||||
}
|
||||
|
||||
void send(std::unique_ptr<T> data) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
queue.push(std::move(data));
|
||||
}
|
||||
uv_async_send(&async);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
uv_close((uv_handle_t *)&async, [](uv_handle_t *handle) {
|
||||
delete reinterpret_cast<AsyncQueue *>(handle->data);
|
||||
});
|
||||
}
|
||||
|
||||
void ref() {
|
||||
uv_ref((uv_handle_t *)&async);
|
||||
}
|
||||
|
||||
void unref() {
|
||||
uv_unref((uv_handle_t *)&async);
|
||||
}
|
||||
|
||||
private:
|
||||
~AsyncQueue() {
|
||||
}
|
||||
|
||||
void process() {
|
||||
std::unique_ptr<T> item;
|
||||
while (true) {
|
||||
mutex.lock();
|
||||
if (queue.empty()) {
|
||||
mutex.unlock();
|
||||
break;
|
||||
}
|
||||
item = std::move(queue.front());
|
||||
queue.pop();
|
||||
mutex.unlock();
|
||||
callback(*item);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
uv_async_t async;
|
||||
std::queue<std::unique_ptr<T>> queue;
|
||||
std::function<void(T &)> callback;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
27
platform/node/test/compare.js
Normal file
27
platform/node/test/compare.js
Normal file
@@ -0,0 +1,27 @@
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
module.exports = function compare(actual, expected, diff, t, callback) {
|
||||
var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]);
|
||||
var error = '';
|
||||
|
||||
compare.stderr.on('data', function(data) {
|
||||
error += data.toString();
|
||||
});
|
||||
|
||||
compare.on('error', function(err) {
|
||||
t.error(err);
|
||||
});
|
||||
|
||||
compare.on('exit', function(code) {
|
||||
// The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar.
|
||||
if (code === 2) {
|
||||
callback(error.trim(), Infinity);
|
||||
} else {
|
||||
var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/);
|
||||
var difference = match ? parseFloat(match[1]) : Infinity;
|
||||
callback(match ? '' : error, difference);
|
||||
}
|
||||
});
|
||||
|
||||
compare.stdin.end();
|
||||
};
|
||||
BIN
platform/node/test/expected/gzip/success.png
Normal file
BIN
platform/node/test/expected/gzip/success.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
platform/node/test/expected/gzip/unhandled.png
Normal file
BIN
platform/node/test/expected/gzip/unhandled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
platform/node/test/expected/map/image.png
Normal file
BIN
platform/node/test/expected/map/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
31
platform/node/test/fixtures/style.json
vendored
Normal file
31
platform/node/test/fixtures/style.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 8,
|
||||
"name": "Empty",
|
||||
"sources": {
|
||||
"mapbox": {
|
||||
"type": "vector",
|
||||
"maxzoom": 15,
|
||||
"tiles": [
|
||||
"./fixtures/tiles/{z}-{x}-{y}.vector.pbf"
|
||||
]
|
||||
}
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "water",
|
||||
"type": "fill",
|
||||
"source": "mapbox",
|
||||
"source-layer": "water",
|
||||
"paint": {
|
||||
"fill-color": "blue"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
platform/node/test/fixtures/tiles/0-0-0.vector.pbf
vendored
Normal file
BIN
platform/node/test/fixtures/tiles/0-0-0.vector.pbf
vendored
Normal file
Binary file not shown.
82
platform/node/test/js/consecutive.test.js
Normal file
82
platform/node/test/js/consecutive.test.js
Normal file
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint node:true */
|
||||
|
||||
var test = require('tape');
|
||||
var mbgl = require('../../../..');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var suitePath = path.join(__dirname, '../../../../test/suite');
|
||||
|
||||
function renderTest(style, info, dir, key) {
|
||||
return function (t) {
|
||||
var completed = 0;
|
||||
var remaining = 10;
|
||||
var start = +new Date;
|
||||
|
||||
var options = {};
|
||||
options.request = function(req) {
|
||||
fs.readFile(path.join(suitePath, decodeURIComponent(req.url)), function(err, data) {
|
||||
req.respond(err, { data: data });
|
||||
t.error(err);
|
||||
});
|
||||
};
|
||||
options.cancel = function() {};
|
||||
options.ratio = 1.0;
|
||||
|
||||
var map = new mbgl.Map(options);
|
||||
map.load(style);
|
||||
|
||||
function render() {
|
||||
map.render(info[key], function(err, image) {
|
||||
t.error(err);
|
||||
|
||||
t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms');
|
||||
if (++completed === remaining) {
|
||||
map.release();
|
||||
t.end();
|
||||
} else {
|
||||
render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render();
|
||||
};
|
||||
}
|
||||
|
||||
function rewriteLocalSchema(uri) {
|
||||
return uri.replace(/^local:\/\//, '');
|
||||
}
|
||||
|
||||
test('Consecutive', function(t) {
|
||||
var dir = 'line-join';
|
||||
var k = 'round';
|
||||
|
||||
var style = require(path.join(suitePath, 'tests', dir, 'style.json')),
|
||||
info = require(path.join(suitePath, 'tests', dir, 'info.json'));
|
||||
|
||||
for (var k in style.sources) {
|
||||
var source = style.sources[k];
|
||||
|
||||
if (source.tiles) {
|
||||
source.tiles = source.tiles.map(rewriteLocalSchema);
|
||||
}
|
||||
|
||||
if (source.url) {
|
||||
source.url = rewriteLocalSchema(source.url);
|
||||
}
|
||||
}
|
||||
|
||||
if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite);
|
||||
if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs);
|
||||
|
||||
style = JSON.stringify(style);
|
||||
|
||||
for (k in info) {
|
||||
t.test(dir + ' ' + k, renderTest(style, info, dir, k));
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
125
platform/node/test/js/gzip.test.js
Normal file
125
platform/node/test/js/gzip.test.js
Normal file
@@ -0,0 +1,125 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var test = require('tape').test;
|
||||
var mbgl = require('../../../..');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var http = require('http');
|
||||
var request = require('request');
|
||||
var st = require('st');
|
||||
var style = require('../fixtures/style.json');
|
||||
var PNG = require('pngjs').PNG;
|
||||
var compare = require('../compare.js');
|
||||
|
||||
var server = http.createServer(st({ path: path.join(__dirname, '..') }));
|
||||
server.listen(0);
|
||||
|
||||
function filePath(name) {
|
||||
return ['expected', 'actual', 'diff'].reduce(function(prev, key) {
|
||||
var dir = path.join(__dirname, '..', key, 'gzip');
|
||||
mkdirp.sync(dir);
|
||||
prev[key] = path.join(dir, name);
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function setup(options, callback) {
|
||||
callback(new mbgl.Map(options));
|
||||
}
|
||||
|
||||
function getOptions(gzip, t) {
|
||||
return {
|
||||
request: function(req) {
|
||||
var parts = req.url.split('.');
|
||||
var filetype = parts[parts.length - 1];
|
||||
|
||||
request({
|
||||
url: 'http://localhost:' + server.address().port + path.join('/', req.url),
|
||||
encoding: null,
|
||||
gzip: filetype === 'pbf' ? gzip : true,
|
||||
headers: {
|
||||
'Accept-Encoding': 'gzip'
|
||||
}
|
||||
}, function (err, res, body) {
|
||||
t.error(err);
|
||||
var response = {};
|
||||
response.data = res.body;
|
||||
req.respond(null, response);
|
||||
});
|
||||
},
|
||||
ratio: 1.0
|
||||
};
|
||||
}
|
||||
|
||||
test('gzip', function(t) {
|
||||
t.test('success', function(t) {
|
||||
mbgl.on('message', function(msg) {
|
||||
if (msg.severity == 'ERROR') t.error(msg);
|
||||
});
|
||||
|
||||
setup(getOptions(true, t), function(map) {
|
||||
map.load(style);
|
||||
map.render({}, function(err, data) {
|
||||
mbgl.removeAllListeners('message');
|
||||
map.release();
|
||||
|
||||
t.error(err);
|
||||
|
||||
var filename = filePath('success.png');
|
||||
|
||||
var png = new PNG({
|
||||
width: data.width,
|
||||
height: data.height
|
||||
});
|
||||
|
||||
png.data = data.pixels;
|
||||
|
||||
if (process.env.UPDATE) {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(filename.expected))
|
||||
.on('finish', t.end);
|
||||
} else {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(filename.actual))
|
||||
.on('finish', function() {
|
||||
compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) {
|
||||
t.error(err);
|
||||
t.ok(diff <= 0.01, 'actual matches expected');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
t.test('unhandled', function(t) {
|
||||
mbgl.once('message', function(msg) {
|
||||
if (msg.severity == 'ERROR') {
|
||||
t.ok(msg, 'emits error');
|
||||
t.equal(msg.class, 'Style');
|
||||
t.equal(msg.severity, 'ERROR');
|
||||
t.ok(msg.text.match(/pbf unknown field type exception/), 'error text matches');
|
||||
}
|
||||
});
|
||||
|
||||
setup(getOptions(false, t), function(map) {
|
||||
map.load(style);
|
||||
map.render({}, function(err, data) {
|
||||
map.release();
|
||||
|
||||
t.ok(err, 'returns error');
|
||||
t.ok(err.message.match(/Failed to parse/), 'error text matches');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
t.test('teardown', function(t) {
|
||||
server.close(t.end);
|
||||
});
|
||||
});
|
||||
280
platform/node/test/js/map.test.js
Normal file
280
platform/node/test/js/map.test.js
Normal file
@@ -0,0 +1,280 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint node: true */
|
||||
|
||||
var test = require('tape');
|
||||
var mbgl = require('../../../..');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var style = require('../fixtures/style.json');
|
||||
var PNG = require('pngjs').PNG;
|
||||
var compare = require('../compare.js');
|
||||
|
||||
function filePath(name) {
|
||||
return ['expected', 'actual', 'diff'].reduce(function(prev, key) {
|
||||
var dir = path.join(__dirname, '..', key, 'map');
|
||||
mkdirp.sync(dir);
|
||||
prev[key] = path.join(dir, name);
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
test('Map', function(t) {
|
||||
t.test('must be constructed with new', function(t) {
|
||||
t.throws(function() {
|
||||
mbgl.Map();
|
||||
}, /Use the new operator to create new Map objects/);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('must be constructed with options object', function(t) {
|
||||
t.throws(function() {
|
||||
new mbgl.Map();
|
||||
}, /Requires an options object as first argument/);
|
||||
|
||||
t.throws(function() {
|
||||
new mbgl.Map('options');
|
||||
}, /Requires an options object as first argument/);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('requires request and ratio options', function(t) {
|
||||
var options = {};
|
||||
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a 'request' method/);
|
||||
|
||||
options.request = 'test';
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a 'request' method/);
|
||||
|
||||
options.request = function() {};
|
||||
options.cancel = 'test';
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object 'cancel' property must be a function/);
|
||||
|
||||
options.cancel = function() {};
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a numerical 'ratio' property/);
|
||||
|
||||
options.ratio = 'test';
|
||||
t.throws(function() {
|
||||
new mbgl.Map(options);
|
||||
}, /Options object must have a numerical 'ratio' property/);
|
||||
|
||||
options.ratio = 1.0;
|
||||
t.doesNotThrow(function() {
|
||||
new mbgl.Map(options);
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('.load', function(t) {
|
||||
var options = {
|
||||
request: function() {},
|
||||
ratio: 1
|
||||
};
|
||||
|
||||
t.test('requires a map style as first argument', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.throws(function() {
|
||||
map.load();
|
||||
}, /Requires a map style as first argument/);
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('expect either an object or array at root', { timeout: 1000 }, function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
mbgl.once('message', function(msg) {
|
||||
t.equal(msg.severity, 'ERROR');
|
||||
t.equal(msg.class, 'ParseStyle');
|
||||
t.ok(msg.text.match(/Expect either an object or array at root/));
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
map.load('invalid');
|
||||
});
|
||||
|
||||
t.test('accepts an empty stylesheet string', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.doesNotThrow(function() {
|
||||
map.load('{}');
|
||||
});
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('accepts a JSON stylesheet', { timeout: 1000 }, function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.doesNotThrow(function() {
|
||||
map.load(style);
|
||||
});
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('accepts a stringified stylesheet', { timeout: 1000 }, function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.doesNotThrow(function() {
|
||||
map.load(JSON.stringify(style));
|
||||
});
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('does not immediately trigger any tile loads', function(t) {
|
||||
var map = new mbgl.Map({
|
||||
request: function(req) {
|
||||
t.fail('unexpected request ' + req.url);
|
||||
},
|
||||
ratio: 1
|
||||
});
|
||||
|
||||
map.load(style);
|
||||
|
||||
setTimeout(function() {
|
||||
map.release();
|
||||
t.end();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
t.test('.render', function(t) {
|
||||
var options = {
|
||||
request: function(req) {
|
||||
fs.readFile(path.join(__dirname, '..', req.url), function(err, data) {
|
||||
req.respond(err, { data: data });
|
||||
});
|
||||
},
|
||||
ratio: 1
|
||||
};
|
||||
|
||||
t.test('requires an object as the first parameter', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.throws(function() {
|
||||
map.render();
|
||||
}, /First argument must be an options object/);
|
||||
|
||||
t.throws(function() {
|
||||
map.render('invalid');
|
||||
}, /First argument must be an options object/);
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('requires a callback as the second parameter', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.throws(function() {
|
||||
map.render({});
|
||||
}, /Second argument must be a callback function/);
|
||||
|
||||
t.throws(function() {
|
||||
map.render({}, 'invalid');
|
||||
}, /Second argument must be a callback function/);
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('requires a style to be set', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
|
||||
t.throws(function() {
|
||||
map.render({}, function() {});
|
||||
}, /Style is not loaded/);
|
||||
|
||||
map.release();
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('returns an error', function(t) {
|
||||
mbgl.on('message', function(msg) {
|
||||
t.ok(msg, 'emits error');
|
||||
t.equal(msg.class, 'Style');
|
||||
t.equal(msg.severity, 'ERROR');
|
||||
t.ok(msg.text.match(/Failed to load/), 'error text matches');
|
||||
});
|
||||
|
||||
var map = new mbgl.Map(options);
|
||||
map.load(style);
|
||||
map.render({ zoom: 1 }, function(err, data) {
|
||||
mbgl.removeAllListeners('message');
|
||||
map.release();
|
||||
|
||||
t.ok(err, 'returns error');
|
||||
t.ok(err.message.match(/Failed to load/), 'error text matches');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
t.test('double release', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
map.release();
|
||||
|
||||
t.throws(function() {
|
||||
map.release();
|
||||
}, /Map resources have already been released/);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('returns an image', function(t) {
|
||||
var map = new mbgl.Map(options);
|
||||
map.load(style);
|
||||
map.render({}, function(err, data) {
|
||||
t.error(err);
|
||||
|
||||
map.release();
|
||||
|
||||
var filename = filePath('image.png');
|
||||
|
||||
var png = new PNG({
|
||||
width: data.width,
|
||||
height: data.height
|
||||
});
|
||||
|
||||
png.data = data.pixels;
|
||||
|
||||
if (process.env.UPDATE) {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(filename.expected))
|
||||
.on('finish', t.end);
|
||||
} else {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(filename.actual))
|
||||
.on('finish', function() {
|
||||
compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) {
|
||||
t.error(err);
|
||||
t.ok(diff <= 0.01, 'actual matches expected');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
175
platform/node/test/render.test.js
Normal file
175
platform/node/test/render.test.js
Normal file
@@ -0,0 +1,175 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint node:true */
|
||||
|
||||
var test = require('tape');
|
||||
var mbgl = require('../../..');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var mkdirp = require('mkdirp');
|
||||
var PNG = require('pngjs').PNG;
|
||||
var compare = require('./compare.js');
|
||||
var suitePath = path.join(__dirname, '../../../test/suite');
|
||||
|
||||
function template(name) {
|
||||
return fs.readFileSync(path.join(suitePath, 'templates', name + '.html.tmpl')).toString();
|
||||
}
|
||||
|
||||
var results = '';
|
||||
var resultTemplate = template('result');
|
||||
|
||||
function format(tmpl, kwargs) {
|
||||
return tmpl.replace(/\{\{|\}\}|\{([^}]+)\}/g, function(match, key) {
|
||||
if (match === '{{') return '{';
|
||||
if (match === '}}') return '}';
|
||||
return kwargs[key];
|
||||
});
|
||||
}
|
||||
|
||||
function renderTest(style, info, base, key) {
|
||||
var dir = path.join(suitePath, 'tests', base, key);
|
||||
mkdirp.sync(dir);
|
||||
|
||||
return function(t) {
|
||||
var watchdog = setTimeout(function() {
|
||||
t.fail('timed out after 20 seconds');
|
||||
}, 20000);
|
||||
|
||||
t.once('end', function() {
|
||||
clearTimeout(watchdog);
|
||||
|
||||
if (map) {
|
||||
map.release();
|
||||
map = null;
|
||||
}
|
||||
});
|
||||
|
||||
var options = {};
|
||||
options.request = function(req) {
|
||||
var url = decodeURIComponent(req.url);
|
||||
fs.readFile(path.join(suitePath, url), function(err, data) {
|
||||
req.respond(err, { data: data });
|
||||
});
|
||||
};
|
||||
options.ratio = info[key].pixelRatio || 1;
|
||||
|
||||
var map = new mbgl.Map(options);
|
||||
map.load(style);
|
||||
|
||||
map.render(info[key], function(err, data) {
|
||||
if (err) {
|
||||
t.error(err);
|
||||
return t.end();
|
||||
}
|
||||
|
||||
var expected = path.join(dir, 'expected.png');
|
||||
var actual = path.join(dir, 'actual.png');
|
||||
var diff = path.join(dir, 'diff.png');
|
||||
|
||||
var png = new PNG({
|
||||
width: data.width,
|
||||
height: data.height
|
||||
});
|
||||
|
||||
png.data = data.pixels;
|
||||
|
||||
if (process.env.UPDATE) {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(expected))
|
||||
.on('finish', t.end);
|
||||
} else {
|
||||
png.pack()
|
||||
.pipe(fs.createWriteStream(actual))
|
||||
.on('finish', function() {
|
||||
compare(actual, expected, diff, t, function(err, diff) {
|
||||
t.error(err);
|
||||
|
||||
var allowed = 0.001;
|
||||
|
||||
if ('diff' in info[key]) {
|
||||
if (typeof info[key].diff === 'number') {
|
||||
allowed = info[key].diff;
|
||||
} else if ('native' in info[key].diff) {
|
||||
allowed = info[key].diff.native;
|
||||
}
|
||||
}
|
||||
|
||||
results += format(resultTemplate, {
|
||||
name: base,
|
||||
key: key,
|
||||
color: diff <= allowed ? 'green' : 'red',
|
||||
error: err ? '<p>' + err + '</p>' : '',
|
||||
difference: diff,
|
||||
zoom: info.zoom || 0,
|
||||
center: info.center || [0, 0],
|
||||
bearing: info.bearing || 0,
|
||||
width: info.width || 512,
|
||||
height: info.height || 512
|
||||
});
|
||||
|
||||
if (!info[key].ignored || !('native' in info[key].ignored)) {
|
||||
t.ok(diff <= allowed, 'expected ' + diff + ' to be less than ' + allowed);
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function rewriteLocalSchema(url) {
|
||||
var regex = /^local:\/\//;
|
||||
if (url instanceof Array) {
|
||||
return url.map(function(str) {
|
||||
return str.replace(regex, '');
|
||||
});
|
||||
} else if (typeof url === "string") {
|
||||
return url.replace(regex, '');
|
||||
}
|
||||
}
|
||||
|
||||
var tests;
|
||||
|
||||
if (process.argv[1] === __filename) {
|
||||
tests = process.argv.slice(2);
|
||||
}
|
||||
|
||||
test('Render', function(t) {
|
||||
fs.readdirSync(path.join(suitePath, 'tests')).forEach(function(dir) {
|
||||
if (dir === 'index.html' || dir[0] === '.') return;
|
||||
if (tests && tests.length && tests.indexOf(dir) < 0) return;
|
||||
|
||||
var style = require(path.join(suitePath, 'tests', dir, 'style.json')),
|
||||
info = require(path.join(suitePath, 'tests', dir, 'info.json'));
|
||||
|
||||
for (var k in style.sources) {
|
||||
var source = style.sources[k];
|
||||
|
||||
if (source.tiles) {
|
||||
source.tiles = source.tiles.map(rewriteLocalSchema);
|
||||
}
|
||||
|
||||
if (source.url) {
|
||||
source.url = rewriteLocalSchema(source.url);
|
||||
}
|
||||
}
|
||||
|
||||
if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite);
|
||||
if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs);
|
||||
|
||||
style = JSON.stringify(style);
|
||||
|
||||
for (k in info) {
|
||||
(info[k].native === false ? t.skip : t.test)(dir + ' ' + k, renderTest(style, info, dir, k));
|
||||
}
|
||||
});
|
||||
|
||||
t.test('results', function(t) {
|
||||
var p = path.join(suitePath, 'tests', 'index.html');
|
||||
fs.writeFileSync(p, format(template('results'), {results: results}));
|
||||
console.warn('Results at: ' + p);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
@@ -85,15 +85,27 @@ GYP_FLAGS += --generator-output=./build/$(HOST_SLUG)
|
||||
.PHONY: Makefile/__project__
|
||||
Makefile/__project__: print-env $(SUBMODULES) config/$(HOST_SLUG).gypi
|
||||
@printf "$(TEXT_BOLD)$(COLOR_GREEN)* Recreating project...$(FORMAT_END)\n"
|
||||
$(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) -f make$(GYP_FLAVOR_SUFFIX)
|
||||
$(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) \
|
||||
-f make$(GYP_FLAVOR_SUFFIX)
|
||||
|
||||
.PHONY: Xcode/__project__
|
||||
Xcode/__project__: print-env $(SUBMODULES) config/$(HOST_SLUG).gypi
|
||||
@printf "$(TEXT_BOLD)$(COLOR_GREEN)* Recreating project...$(FORMAT_END)\n"
|
||||
$(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) -f xcode$(GYP_FLAVOR_SUFFIX)
|
||||
$(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) \
|
||||
-f xcode$(GYP_FLAVOR_SUFFIX)
|
||||
|
||||
#### Build individual targets ##################################################
|
||||
|
||||
NODE_PRE_GYP = $(shell npm bin)/node-pre-gyp
|
||||
node/configure:
|
||||
$(QUIET)$(ENV) $(NODE_PRE_GYP) configure --clang -- \
|
||||
$(GYP_FLAGS) -Dlibuv_ldflags= -Dlibuv_static_libs=
|
||||
|
||||
Makefile/node: Makefile/__project__ node/configure
|
||||
@printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target node...$(FORMAT_END)\n"
|
||||
$(QUIET)$(ENV) $(NODE_PRE_GYP) build --clang -- \
|
||||
-j$(JOBS)
|
||||
|
||||
Makefile/%: Makefile/__project__
|
||||
@printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target $*...$(FORMAT_END)\n"
|
||||
$(QUIET)$(ENV) $(MAKE) -C build/$(HOST_SLUG) BUILDTYPE=$(BUILDTYPE) $*
|
||||
|
||||
37
scripts/node/after_script.sh
Executable file
37
scripts/node/after_script.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
# Inspect binary.
|
||||
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
|
||||
ldd ./lib/mapbox-gl-native.node
|
||||
else
|
||||
otool -L ./lib/mapbox-gl-native.node
|
||||
fi
|
||||
|
||||
COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n')
|
||||
|
||||
if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then
|
||||
source ~/.nvm/nvm.sh
|
||||
nvm use $NODE_VERSION
|
||||
|
||||
npm install aws-sdk
|
||||
|
||||
./node_modules/.bin/node-pre-gyp package
|
||||
|
||||
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
|
||||
./node_modules/.bin/node-pre-gyp testpackage
|
||||
fi
|
||||
|
||||
./node_modules/.bin/node-pre-gyp publish info
|
||||
|
||||
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
|
||||
source ./scripts/${TRAVIS_OS_NAME}/setup.sh
|
||||
|
||||
rm -rf build
|
||||
rm -rf lib
|
||||
npm install --fallback-to-build=false
|
||||
npm test
|
||||
fi
|
||||
fi
|
||||
33
scripts/node/install.sh
Executable file
33
scripts/node/install.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
mapbox_time "checkout_mason" \
|
||||
git submodule update --init .mason
|
||||
|
||||
export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason"
|
||||
|
||||
if [ ${TRAVIS_OS_NAME} == "linux" ]; then
|
||||
mapbox_time "install_mesa" \
|
||||
mason install mesa 10.4.3
|
||||
fi
|
||||
|
||||
if [ ! -d ~/.nvm ]; then
|
||||
git clone https://github.com/creationix/nvm.git ~/.nvm
|
||||
|
||||
pushd ~/.nvm
|
||||
git fetch
|
||||
git checkout `git describe --abbrev=0 --tags`
|
||||
popd
|
||||
fi
|
||||
|
||||
source ~/.nvm/nvm.sh
|
||||
|
||||
mapbox_time $NODE_VERSION \
|
||||
nvm install $NODE_VERSION
|
||||
|
||||
nvm alias default $NODE_VERSION
|
||||
|
||||
node --version
|
||||
npm --version
|
||||
49
scripts/node/run.sh
Executable file
49
scripts/node/run.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
source ./scripts/${TRAVIS_OS_NAME}/setup.sh
|
||||
|
||||
BUILDTYPE=${BUILDTYPE:-Release}
|
||||
|
||||
################################################################################
|
||||
# Build
|
||||
################################################################################
|
||||
|
||||
source ~/.nvm/nvm.sh
|
||||
nvm use $NODE_VERSION
|
||||
|
||||
mapbox_time "checkout_styles" \
|
||||
git submodule update --init styles
|
||||
|
||||
mapbox_time "compile_program" \
|
||||
npm install --build-from-source
|
||||
|
||||
################################################################################
|
||||
# Test
|
||||
################################################################################
|
||||
|
||||
# Travis OS X has no GPU
|
||||
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
|
||||
mapbox_time "checkout_test_suite" \
|
||||
git submodule update --init test/suite
|
||||
|
||||
mapbox_time "run_tests" \
|
||||
npm test
|
||||
|
||||
mapbox_time "run_render_tests" \
|
||||
npm run test-suite
|
||||
|
||||
if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then
|
||||
# Install and add awscli to PATH for uploading the results
|
||||
mapbox_time "install_awscli" \
|
||||
pip install --user awscli
|
||||
export PATH="`python -m site --user-base`/bin:${PATH}"
|
||||
|
||||
pushd test/suite
|
||||
mapbox_time "deploy_results" \
|
||||
./bin/deploy_results.sh
|
||||
popd
|
||||
fi
|
||||
fi
|
||||
@@ -259,7 +259,7 @@ void MapContext::update() {
|
||||
updateFlags = Update::Nothing;
|
||||
}
|
||||
|
||||
if (updateFlags == Update::Nothing) {
|
||||
if (updateFlags == Update::Nothing || (data.mode == MapMode::Still && !callback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,8 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame)
|
||||
return false;
|
||||
}
|
||||
|
||||
view.beforeRender();
|
||||
|
||||
transformState = state;
|
||||
|
||||
// Cleanup OpenGL objects that we abandoned since the last render call.
|
||||
@@ -344,7 +346,7 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame)
|
||||
callback = nullptr;
|
||||
}
|
||||
|
||||
view.swap();
|
||||
view.afterRender();
|
||||
viewInvalidated = false;
|
||||
data.setNeedsRepaint(style->hasTransitions() || painter->needsAnimation());
|
||||
|
||||
|
||||
3
test/fixtures/mock_view.hpp
vendored
3
test/fixtures/mock_view.hpp
vendored
@@ -20,7 +20,8 @@ public:
|
||||
void deactivate() override {};
|
||||
void notify() override {};
|
||||
void invalidate() override {}
|
||||
void swap() override {}
|
||||
void beforeRender() override {}
|
||||
void afterRender() override {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user