diff options
author | Mike Morris <michael.patrick.morris@gmail.com> | 2015-09-02 15:46:43 -0400 |
---|---|---|
committer | Mike Morris <michael.patrick.morris@gmail.com> | 2015-09-02 15:46:43 -0400 |
commit | 322e1aa2c3c2570554a2fe2bbfa733ab7bbd4e81 (patch) | |
tree | c6d665f13f1342b8a5adaff14a7a676a6b0c4a85 /platform/node/src | |
parent | 6929dcec966c5c9eb2f2409fb4568f55253116f9 (diff) | |
parent | 6634adee398c7e84c0892042751fa299546a9965 (diff) | |
download | qtlocation-mapboxgl-322e1aa2c3c2570554a2fe2bbfa733ab7bbd4e81.tar.gz |
Merge pull request #2179 from mapbox/node
Node bindings
Diffstat (limited to 'platform/node/src')
-rw-r--r-- | platform/node/src/node_file_source.cpp | 161 | ||||
-rw-r--r-- | platform/node/src/node_file_source.hpp | 60 | ||||
-rw-r--r-- | platform/node/src/node_log.cpp | 54 | ||||
-rw-r--r-- | platform/node/src/node_log.hpp | 32 | ||||
-rw-r--r-- | platform/node/src/node_map.cpp | 347 | ||||
-rw-r--r-- | platform/node/src/node_map.hpp | 66 | ||||
-rw-r--r-- | platform/node/src/node_mapbox_gl_native.cpp | 38 | ||||
-rw-r--r-- | platform/node/src/node_request.cpp | 144 | ||||
-rw-r--r-- | platform/node/src/node_request.hpp | 43 | ||||
-rw-r--r-- | platform/node/src/util/async_queue.hpp | 90 |
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; +}; + +} +} |