summaryrefslogtreecommitdiff
path: root/platform/node/src
diff options
context:
space:
mode:
Diffstat (limited to 'platform/node/src')
-rw-r--r--platform/node/src/node_file_source.cpp161
-rw-r--r--platform/node/src/node_file_source.hpp60
-rw-r--r--platform/node/src/node_log.cpp54
-rw-r--r--platform/node/src/node_log.hpp32
-rw-r--r--platform/node/src/node_map.cpp347
-rw-r--r--platform/node/src/node_map.hpp66
-rw-r--r--platform/node/src/node_mapbox_gl_native.cpp38
-rw-r--r--platform/node/src/node_request.cpp144
-rw-r--r--platform/node/src/node_request.hpp43
-rw-r--r--platform/node/src/util/async_queue.hpp90
10 files changed, 1035 insertions, 0 deletions
diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp
new file mode 100644
index 0000000000..204e85a126
--- /dev/null
+++ b/platform/node/src/node_file_source.cpp
@@ -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);
+}
+
+}
diff --git a/platform/node/src/node_file_source.hpp b/platform/node/src/node_file_source.hpp
new file mode 100644
index 0000000000..f412bdee16
--- /dev/null
+++ b/platform/node/src/node_file_source.hpp
@@ -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;
+};
+
+}
diff --git a/platform/node/src/node_log.cpp b/platform/node/src/node_log.cpp
new file mode 100644
index 0000000000..6375348070
--- /dev/null
+++ b/platform/node/src/node_log.cpp
@@ -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;
+}
+
+}
diff --git a/platform/node/src/node_log.hpp b/platform/node/src/node_log.hpp
new file mode 100644
index 0000000000..5c0048d261
--- /dev/null
+++ b/platform/node/src/node_log.hpp
@@ -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;
+};
+
+}
diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp
new file mode 100644
index 0000000000..6fd044daec
--- /dev/null
+++ b/platform/node/src/node_map.cpp
@@ -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();
+}
+
+}
diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp
new file mode 100644
index 0000000000..2c87900d4d
--- /dev/null
+++ b/platform/node/src/node_map.hpp
@@ -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;
+};
+
+}
diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp
new file mode 100644
index 0000000000..14a0c9ad26
--- /dev/null
+++ b/platform/node/src/node_mapbox_gl_native.cpp
@@ -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)
diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp
new file mode 100644
index 0000000000..ea9fc4d732
--- /dev/null
+++ b/platform/node/src/node_request.cpp
@@ -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();
+}
+
+}
diff --git a/platform/node/src/node_request.hpp b/platform/node/src/node_request.hpp
new file mode 100644
index 0000000000..a690904ef2
--- /dev/null
+++ b/platform/node/src/node_request.hpp
@@ -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;
+};
+
+}
diff --git a/platform/node/src/util/async_queue.hpp b/platform/node/src/util/async_queue.hpp
new file mode 100644
index 0000000000..81319da8c7
--- /dev/null
+++ b/platform/node/src/util/async_queue.hpp
@@ -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;
+};
+
+}
+}