initial version with updated build system

This commit is contained in:
Konstantin Käfer
2015-02-04 18:21:39 +01:00
commit 07867edd47
20 changed files with 1161 additions and 0 deletions

18
.clang-format Normal file
View File

@@ -0,0 +1,18 @@
Standard: Cpp11
IndentWidth: 4
AccessModifierOffset: -4
UseTab: Never
BinPackParameters: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
AlwaysBreakTemplateDeclarations: true
NamespaceIndentation: None
PointerBindsToType: false
SpacesInParentheses: false
BreakBeforeBraces: Attach
ColumnLimit: 100
Cpp11BracedListStyle: false
SpacesBeforeTrailingComments: 1

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/build
/lib
/node_modules

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "vendor/mbgl"]
path = vendor/mbgl
url = https://github.com/mapbox/mapbox-gl-native.git

43
Makefile Normal file
View File

@@ -0,0 +1,43 @@
BUILDTYPE ?= Release
MBGL ?= vendor/mbgl
DEBUG_FLAG =
ifeq ($(BUILDTYPE), Debug)
DEBUG_FLAG = -d
endif
ifeq ($(shell uname -s), Darwin)
HOST ?= osx
endif
HOST ?= linux
include $(MBGL)/config/defaults.mk
global: build
.PHONY: build
build: build/Makefile
@node-gyp build $(DEBUG_FLAG) -- -j8
.PHONY: build/Makefile
build/Makefile: $(MBGL)/config/$(HOST).gypi
@node-gyp configure -- \
-Dmbgl=$(MBGL) \
-Dhost=osx \
-I$(MBGL)/config/$(HOST).gypi \
$(LIBS_$(HOST)) \
-Duv_static_libs= -Duv_ldflags= \
-Goutput_dir=.
$(MBGL)/config/%.gypi: $(MBGL)/configure
make -C $(MBGL) config/$*.gypi
.PHONY: test
test: build
@`npm bin`/mocha -R spec
.PHONY: clean
clean:
rm -rf build
rm $(MBGL)/config.gypi

59
binding.gyp Normal file
View File

@@ -0,0 +1,59 @@
{
'targets': [
{ 'target_name': 'mbgl',
'dependencies': [
'./<(mbgl)/mbgl.gyp:core',
'./<(mbgl)/mbgl.gyp:platform-<(platform_lib)',
'./<(mbgl)/mbgl.gyp:headless-<(headless_lib)',
],
'include_dirs': [ "<!(node -e \"require('nan')\")" ],
'sources': [
'src/mbgl.cpp',
'src/node_file_source.hpp',
'src/node_file_source.cpp',
'src/node_map.hpp',
'src/node_map.cpp',
'src/node_map_render_worker.hpp',
'src/node_map_render_worker.cpp',
'src/node_request.hpp',
'src/node_request.cpp',
],
'variables': {
'cflags_cc': [
'-std=c++11',
'-stdlib=libc++',
'-Wno-unused-parameter'
],
},
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
}
}, {
'cflags_cc': [
'<@(cflags_cc)',
'-fexceptions',
],
}]
],
},
{ 'target_name': 'action_after_build',
'type': 'none',
'dependencies': [ 'mbgl' ],
'copies': [
{
'files': [ '<(PRODUCT_DIR)/mbgl.node' ],
'destination': 'lib'
}
]
}
]
}

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "mbgl",
"version": "0.0.1",
"main": "lib/mbgl.node",
"dependencies": {
"mocha": "^2.1.0",
"nan": "^1.4.1",
"request": "^2.51.0"
},
"devDependencies": {},
"scripts": {
"install": "make",
"test": "mocha -R spec test"
},
"gypfile": true
}

26
src/mbgl.cpp Normal file
View File

@@ -0,0 +1,26 @@
#include <node.h>
#include <nan.h>
#include "node_file_source.hpp"
#include "node_map.hpp"
#include "node_request.hpp"
void RegisterModule(v8::Handle<v8::Object> exports) {
NanScope();
node_mbgl::NodeFileSource::Init(exports);
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 = v8::Object::New();
resource->ForceSet(NanNew("Unknown"), NanNew(mbgl::Resource::Unknown), ConstantProperty);
resource->ForceSet(NanNew("Tile"), NanNew(mbgl::Resource::Tile), ConstantProperty);
resource->ForceSet(NanNew("Glyphs"), NanNew(mbgl::Resource::Glyphs), ConstantProperty);
resource->ForceSet(NanNew("Image"), NanNew(mbgl::Resource::Image), ConstantProperty);
resource->ForceSet(NanNew("JSON"), NanNew(mbgl::Resource::JSON), ConstantProperty);
exports->ForceSet(NanNew("Resource"), resource, ConstantProperty);
}
NODE_MODULE(mbgl, RegisterModule)

156
src/node_file_source.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "node_file_source.hpp"
#include "node_request.hpp"
#include <mbgl/storage/default/request.hpp>
#include <mbgl/util/async_queue.hpp>
namespace node_mbgl {
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
v8::Persistent<v8::FunctionTemplate> NodeFileSource::constructorTemplate;
void NodeFileSource::Init(v8::Handle<v8::Object> target) {
NanScope();
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(NanNew("FileSource"));
NanAssignPersistent(constructorTemplate, t);
target->Set(NanNew("FileSource"), t->GetFunction());
}
NAN_METHOD(NodeFileSource::New) {
NanScope();
if (!args.IsConstructCall()) {
return NanThrowTypeError("Use the new operator to create new FileSource objects");
}
auto fs = new NodeFileSource();
fs->Wrap(args.This());
NanReturnValue(args.This());
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
struct NodeFileSource::Action {
const enum : bool { Add, Cancel } type;
mbgl::Request *const request;
};
NodeFileSource::NodeFileSource() :
queue(new Queue(uv_default_loop(), [this](Action &action) {
if (action.type == Action::Add) {
processAdd(action.request);
} else if (action.type == Action::Cancel) {
processCancel(action.request);
}
}))
{
// Make sure that the queue doesn't block the loop from exiting.
queue->unref();
}
NodeFileSource::~NodeFileSource() {
queue->stop();
queue = nullptr;
}
mbgl::Request *NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t *loop, Callback callback) {
auto request = new mbgl::Request(resource, loop, std::move(callback));
// 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, request });
return request;
}
void NodeFileSource::request(const mbgl::Resource &resource, Callback callback) {
auto request = new mbgl::Request(resource, nullptr, std::move(callback));
// 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, request });
}
void NodeFileSource::cancel(mbgl::Request *request) {
request->cancel();
// 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, request });
}
void NodeFileSource::processAdd(mbgl::Request *request) {
NanScope();
// Make sure the loop stays alive as long as request is pending.
if (pending.empty()) {
queue->ref();
}
auto requestHandle = v8::Local<v8::Object>::New(NodeRequest::Create(handle_, request));
pending.emplace(request, std::move(v8::Persistent<v8::Object>::New(requestHandle)));
v8::Local<v8::Value> argv[] = { requestHandle };
NanMakeCallback(handle_, NanNew("request"), 1, argv);
}
void NodeFileSource::processCancel(mbgl::Request *request) {
NanScope();
auto it = pending.find(request);
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 {
auto requestHandle = v8::Local<v8::Object>::New(it->second);
// Dispose and remove the persistent handle
it->second.Dispose();
pending.erase(it);
// Make sure the the loop can exit when there are no pending requests.
if (pending.empty()) {
queue->unref();
}
if (handle_->Has(NanNew("cancel"))) {
v8::Local<v8::Value> argv[] = { requestHandle };
NanMakeCallback(handle_, NanNew("cancel"), 1, argv);
}
// Set the request handle in the request wrapper handle to null
ObjectWrap::Unwrap<NodeRequest>(requestHandle)->cancel();
}
// Finally, destruct the request object
request->destruct();
}
void NodeFileSource::notify(mbgl::Request *request, 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(request);
if (it != pending.end()) {
it->second.Dispose();
pending.erase(it);
// Make sure the the loop can exit when there are no pending requests.
if (pending.empty()) {
queue->unref();
}
}
request->notify(response);
}
}

49
src/node_file_source.hpp Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <mbgl/storage/file_source.hpp>
#include <node.h>
#include <nan.h>
#include <map>
namespace mbgl { namespace util { template <typename T> class AsyncQueue; } }
namespace node_mbgl {
class NodeFileSource : public node::ObjectWrap, public mbgl::FileSource {
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
public:
static void Init(v8::Handle<v8::Object> target);
static NAN_METHOD(New);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
public:
NodeFileSource();
~NodeFileSource();
mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, Callback callback);
void request(const mbgl::Resource &resource, Callback callback);
void cancel(mbgl::Request *request);
// visiblity?
void notify(mbgl::Request *request, const std::shared_ptr<const mbgl::Response> &response);
private:
struct Action;
using Queue = mbgl::util::AsyncQueue<Action>;
void processAdd(mbgl::Request *request);
void processCancel(mbgl::Request *request);
private:
std::map<mbgl::Request *, v8::Persistent<v8::Object>> pending;
Queue *queue = nullptr;
};
}

185
src/node_map.cpp Normal file
View File

@@ -0,0 +1,185 @@
#include "node_map.hpp"
#include "node_map_render_worker.hpp"
#include "node_file_source.hpp"
#include <mbgl/platform/default/headless_display.hpp>
#include <unistd.h>
namespace node_mbgl {
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
v8::Persistent<v8::FunctionTemplate> NodeMap::constructorTemplate;
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);
NanAssignPersistent(constructorTemplate, t);
target->Set(NanNew("Map"), t->GetFunction());
}
NAN_METHOD(NodeMap::New) {
NanScope();
if (!args.IsConstructCall()) {
return NanThrowTypeError("Use the new operator to create new Map objects");
}
if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) {
return NanThrowTypeError("Requires a FileSource as first argument");
}
auto source = args[0]->ToObject();
// Check that request() and cancel() are defined.
if (!source->Has(NanNew("request")) || !source->Get(NanNew("request"))->IsFunction()) {
return NanThrowError("FileSource must have a request member function");
}
if (!source->Has(NanNew("cancel")) || !source->Get(NanNew("cancel"))->IsFunction()) {
return NanThrowError("FileSource must have a cancel member function");
}
auto map = new NodeMap(args[0]->ToObject());
map->Wrap(args.This());
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();
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()) {
v8::Local<v8::String> toStr = args[0]->ToString();
style.resize(toStr->Utf8Length());
toStr->WriteUtf8(const_cast<char *>(style.data()));
} else {
return NanThrowTypeError("First argument must be a string or object");
}
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
try {
nodeMap->map.setStyleJSON(style, ".");
} catch (const std::exception &ex) {
NanThrowError(ex.what());
}
NanReturnUndefined();
}
std::unique_ptr<NodeMap::RenderOptions> NodeMap::ParseOptions(v8::Local<v8::Object> obj) {
NanScope();
auto options = mbgl::util::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("ratio"))) { options->ratio = obj->Get(NanNew("ratio"))->IntegerValue(); }
if (obj->Has(NanNew("accessToken"))) {
options->accessToken = *NanUtf8String(obj->Get(NanNew("accessToken")->ToString()));
}
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();
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");
}
auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder());
const bool wasEmpty = nodeMap->queue_.empty();
nodeMap->queue_.push(mbgl::util::make_unique<RenderWorker>(
nodeMap,
ParseOptions(args[0]->ToObject()),
new NanCallback(args[1].As<v8::Function>())));
if (wasEmpty) {
// When the queue was empty, there was no action in progress, so we can start a new one.
NanAsyncQueueWorker(nodeMap->queue_.front().release());
}
NanReturnUndefined();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
std::shared_ptr<mbgl::HeadlessDisplay> sharedDisplay() {
static auto display = std::make_shared<mbgl::HeadlessDisplay>();
return display;
}
NodeMap::NodeMap(v8::Handle<v8::Object> source_) :
view(sharedDisplay()),
fs(*ObjectWrap::Unwrap<NodeFileSource>(source_)),
map(view, fs) {
source = v8::Persistent<v8::Object>::New(source_);
}
NodeMap::~NodeMap() {
source.Dispose();
}
void NodeMap::processNext() {
assert(!queue_.empty());
queue_.pop();
if (!queue_.empty()) {
// When the queue was empty, there was no action in progress, so we can start a new one.
NanAsyncQueueWorker(queue_.front().release());
}
}
}

54
src/node_map.hpp Normal file
View File

@@ -0,0 +1,54 @@
#ifndef NODE_MBGL_NODE_MAP
#define NODE_MBGL_NODE_MAP
#include <mbgl/map/map.hpp>
#include <mbgl/platform/default/headless_view.hpp>
#include <node.h>
#include <nan.h>
#include <queue>
namespace node_mbgl {
class NodeFileSource;
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 std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
private:
NodeMap(v8::Handle<v8::Object> source);
~NodeMap();
void processNext();
private:
v8::Persistent<v8::Object> source;
v8::Persistent<v8::Object> self;
mbgl::HeadlessView view;
NodeFileSource &fs;
mbgl::Map map;
std::queue<std::unique_ptr<RenderWorker>> queue_;
};
} // end ns node_mbgl
#endif

View File

@@ -0,0 +1,59 @@
#include "node_map_render_worker.hpp"
#include "node_file_source.hpp"
#include <mbgl/util/image.hpp>
namespace node_mbgl {
NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr<RenderOptions> options_,
NanCallback *callback_)
: NanAsyncWorker(callback_), nodeMap(nodeMap_), options(std::move(options_)) {
nodeMap->Ref();
}
NodeMap::RenderWorker::~RenderWorker() {
nodeMap->Unref();
}
void NodeMap::RenderWorker::Execute() {
fprintf(stderr, "executing render worker\n");
try {
nodeMap->view.resize(options->width, options->height, options->ratio);
nodeMap->map.setAccessToken(options->accessToken);
nodeMap->map.resize(options->width, options->height, options->ratio);
nodeMap->map.setClasses(options->classes);
nodeMap->map.setLonLatZoom(options->longitude, options->latitude, options->zoom);
nodeMap->map.setBearing(options->bearing);
// Run the loop. It will terminate when we don't have any further listeners.
nodeMap->map.run();
fprintf(stderr, "run completed\n");
const unsigned int width = options->width * options->ratio;
const unsigned int height = options->height * options->ratio;
image = mbgl::util::compress_png(width, height, nodeMap->view.readPixels().get());
fprintf(stderr, "png compressed\n");
} catch (const std::exception &ex) {
SetErrorMessage(ex.what());
}
}
void NodeMap::RenderWorker::WorkComplete() {
NanAsyncWorker::WorkComplete();
// Continue processing remaining items in the queue.
nodeMap->processNext();
}
void NodeMap::RenderWorker::HandleOKCallback() {
NanScope();
v8::Local<v8::Value> argv[] = {
NanNull(),
NanNewBufferHandle(image.data(), image.length())
};
callback->Call(2, argv);
};
} // ns node_mbgl

View File

@@ -0,0 +1,39 @@
#ifndef NODE_MBGL_NODE_MAP_RENDER_WORKER
#define NODE_MBGL_NODE_MAP_RENDER_WORKER
#include <nan.h>
#include "node_map.hpp"
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;
float ratio = 1.0f;
std::string accessToken;
std::vector<std::string> classes;
};
class NodeMap::RenderWorker : public NanAsyncWorker {
public:
RenderWorker(NodeMap *map, std::unique_ptr<RenderOptions> options, NanCallback *callback);
~RenderWorker();
void Execute();
void HandleOKCallback();
void WorkComplete();
private:
NodeMap *nodeMap;
std::unique_ptr<RenderOptions> options;
std::string image;
};
} // ns node_mbgl
#endif // NODE_MBGL_RENDER_WORKER

148
src/node_request.cpp Normal file
View File

@@ -0,0 +1,148 @@
#include "node_request.hpp"
#include "node_file_source.hpp"
#include <mbgl/storage/default/request.hpp>
#include <mbgl/storage/response.hpp>
#include <cmath>
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 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0]) || !args[1]->IsExternal()) {
return NanThrowTypeError("Cannot create Request objects explicitly");
}
auto source = v8::Persistent<v8::Object>::New(args[0]->ToObject());
auto request = reinterpret_cast<mbgl::Request *>(args[1].As<v8::External>()->Value());
auto req = new NodeRequest(source, request);
req->Wrap(args.This());
NanReturnValue(args.This());
}
// TODO: retain the NodeFileSource in a permanent handle!
v8::Handle<v8::Object> NodeRequest::Create(v8::Handle<v8::Object> source, mbgl::Request *request) {
NanScope();
v8::Local<v8::Value> argv[] = { v8::Local<v8::Object>::New(source), NanNew<v8::External>(request) };
auto instance = constructorTemplate->GetFunction()->NewInstance(2, argv);
instance->Set(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly);
instance->Set(NanNew("kind"), NanNew<v8::Integer>(int(request->resource.kind)), v8::ReadOnly);
NanReturnValue(instance);
}
NAN_METHOD(NodeRequest::Respond) {
NanScope();
auto nodeRequest = ObjectWrap::Unwrap<NodeRequest>(args.Holder());
if (!nodeRequest->request) {
return NanThrowError("Request has already been responded to, or was canceled.");
}
auto source = ObjectWrap::Unwrap<NodeFileSource>(nodeRequest->source);
auto request = nodeRequest->request;
nodeRequest->request = nullptr;
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(request, 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(request, response);
}
NanReturnUndefined();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
NodeRequest::NodeRequest(v8::Persistent<v8::Object> source_, mbgl::Request *request_)
: source(source_), request(request_) {
}
NodeRequest::~NodeRequest() {
source.Dispose();
}
void NodeRequest::cancel() {
request = nullptr;
}
}

36
src/node_request.hpp Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <node.h>
#include <nan.h>
class NodeFileSource;
namespace mbgl { class Request; }
namespace node_mbgl {
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(v8::Handle<v8::Object> source, mbgl::Request *request);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
public:
NodeRequest(v8::Persistent<v8::Object> source, mbgl::Request *request);
~NodeRequest();
void cancel();
private:
v8::Persistent<v8::Object> source;
mbgl::Request *request = nullptr;
};
}

85
src/thread_object.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "thread_object.hpp"
#include "node_file_source.hpp"
#include <unistd.h>
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
v8::Persistent<v8::FunctionTemplate> ThreadObject::constructorTemplate;
void ThreadObject::Init(v8::Handle<v8::Object> target) {
NanScope();
v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(NanNew("ThreadObject"));
NanAssignPersistent(constructorTemplate, t);
target->Set(NanNew("ThreadObject"), t->GetFunction());
}
NAN_METHOD(ThreadObject::New) {
NanScope();
if (!args.IsConstructCall()) {
return NanThrowTypeError("Use the new operator to create new ThreadObject objects");
}
if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) {
return NanThrowTypeError("Requires a FileSource as first argument");
}
auto fs = ObjectWrap::Unwrap<NodeFileSource>(args[0]->ToObject());
auto to = new ThreadObject(fs);
to->Wrap(args.This());
NanReturnValue(args.This());
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
ThreadObject::ThreadObject(NodeFileSource *fs) {
self = v8::Persistent<v8::Object>::New(handle_);
auto async = new uv_async_t;
async->data = this;
uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
NanScope();
auto to = reinterpret_cast<ThreadObject *>(as->data);
to->self.Dispose();
uv_close((uv_handle_t *)as, [](uv_handle_t *handle) {
delete reinterpret_cast<uv_async_t *>(handle);
});
});
thread = std::thread([this, fs, async]() {
auto loop = uv_loop_new();
fs->request({ mbgl::Resource::Image, "http://example.com" }, loop, [&](const mbgl::Response &res) {
fprintf(stderr, "response.status: %d\n", res.status);
fprintf(stderr, "response.etag: %s\n", res.etag.c_str());
fprintf(stderr, "response.modified: %lld\n", res.modified);
fprintf(stderr, "response.expires: %lld\n", res.expires);
fprintf(stderr, "response.data.size: %ld\n", res.data.size());
fprintf(stderr, "response.message: %s\n", res.message.c_str());
});
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_delete(loop);
// Notify that the loop has finished executing.
uv_async_send(async);
});
}
ThreadObject::~ThreadObject() {
thread.join();
}

28
src/thread_object.hpp Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <node.h>
#include <nan.h>
#include <thread>
class NodeFileSource;
class ThreadObject : public node::ObjectWrap {
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
public:
static void Init(v8::Handle<v8::Object> target);
static NAN_METHOD(New);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
private:
ThreadObject(NodeFileSource *fs);
~ThreadObject();
private:
std::thread thread;
v8::Persistent<v8::Object> self;
};

19
test/file_source.test.js Normal file
View File

@@ -0,0 +1,19 @@
/* jshint node: true, unused: false */
/* global describe, it */
'use strict';
var assert = require('assert');
var mbgl = require('..');
describe('FileSource', function() {
describe('constructor', function() {
it('must be called with new', function() {
assert.throws(function() {
mbgl.FileSource();
}, /Use the new operator to create new FileSource objects/);
});
});
});

134
test/map.test.js Normal file
View File

@@ -0,0 +1,134 @@
/* jshint node: true, unused: false */
/* global describe, it, beforeEach, afterEach */
'use strict';
var assert = require('assert');
var mbgl = require('..');
describe('Map', function() {
describe('constructor', function() {
it('must be called with new', function() {
assert.throws(function() {
mbgl.Map();
}, /Use the new operator to create new Map objects/);
});
it('should require a FileSource object as first parameter', function() {
assert.throws(function() {
new mbgl.Map();
}, /Requires a FileSource as first argument/);
assert.throws(function() {
new mbgl.Map('fileSource');
}, /Requires a FileSource as first argument/);
assert.throws(function() {
new mbgl.Map({});
}, /Requires a FileSource as first argument/);
});
it('should require the FileSource object to have request and cancel methods', function() {
var fs = new mbgl.FileSource();
assert.throws(function() {
new mbgl.Map(fs);
}, /FileSource must have a request member function/);
fs.request = 'test';
assert.throws(function() {
new mbgl.Map(fs);
}, /FileSource must have a request member function/);
fs.request = function() {};
assert.throws(function() {
new mbgl.Map(fs);
}, /FileSource must have a cancel member function/);
fs.cancel = 'test';
assert.throws(function() {
new mbgl.Map(fs);
}, /FileSource must have a cancel member function/);
fs.cancel = function() {};
assert.doesNotThrow(function() {
new mbgl.Map(fs);
});
});
});
describe('load styles', function() {
var map;
beforeEach(function() {
var fs = new mbgl.FileSource();
fs.request = function() {};
fs.cancel = function() {};
map = new mbgl.Map(fs);
});
afterEach(function() {
map = null;
});
it('requires a string or object as the first parameter', function() {
assert.throws(function() {
map.load();
}, /Requires a map style as first argument/);
assert.throws(function() {
map.load('invalid');
}, /Expect either an object or array at root/);
});
it('accepts a stylesheet string', function() {
map.load('{}');
});
});
describe('render arguments', function() {
var map;
beforeEach(function() {
var fs = new mbgl.FileSource();
fs.request = function() {};
fs.cancel = function() {};
map = new mbgl.Map(fs);
});
afterEach(function() {
map = null;
});
it('requires a string or object as the first parameter', function() {
assert.throws(function() {
map.render();
}, /First argument must be an options object/);
assert.throws(function() {
map.render('invalid');
}, /First argument must be an options object/);
});
it('requires a callback as the second parameter', function() {
assert.throws(function() {
map.render({});
}, /Second argument must be a callback function/);
assert.throws(function() {
map.render({}, 'invalid');
}, /Second argument must be a callback function/);
});
it('requires a callback as the second parameter', function(done) {
map.render({}, function(err) {
done(err);
});
});
});
});

1
vendor/mbgl vendored Submodule

Submodule vendor/mbgl added at da981e7825