move node-mbgl to platform/node/ directory

This commit is contained in:
Mike Morris
2015-08-21 15:53:24 -04:00
parent a8d9b921d7
commit 6e6387e089
39 changed files with 0 additions and 4 deletions

View File

@@ -0,0 +1,161 @@
#include "node_file_source.hpp"
#include "node_request.hpp"
#include "util/async_queue.hpp"
#include <mbgl/storage/request.hpp>
namespace node_mbgl {
struct NodeFileSource::Action {
const enum : bool { Add, Cancel } type;
mbgl::Resource const resource;
};
NodeFileSource::NodeFileSource(v8::Handle<v8::Object> options_) :
queue(new Queue(uv_default_loop(), [this](Action &action) {
if (action.type == Action::Add) {
processAdd(action.resource);
} else if (action.type == Action::Cancel) {
processCancel(action.resource);
}
}))
{
NanAssignPersistent(options, options_->ToObject());
// Make sure that the queue doesn't block the loop from exiting.
queue->unref();
}
NodeFileSource::~NodeFileSource() {
queue->stop();
queue = nullptr;
NanDisposePersistent(options);
}
mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) {
auto req = new mbgl::Request(resource, loop, std::move(callback));
std::lock_guard<std::mutex> lock(observersMutex);
assert(observers.find(resource) == observers.end());
observers[resource] = req;
// This function can be called from any thread. Make sure we're executing the actual call in the
// file source loop by sending it over the queue. It will be processed in processAction().
queue->send(Action{ Action::Add, resource });
return req;
}
void NodeFileSource::cancel(mbgl::Request* req) {
req->cancel();
std::lock_guard<std::mutex> lock(observersMutex);
auto it = observers.find(req->resource);
if (it == observers.end()) {
return;
}
observers.erase(it);
// This function can be called from any thread. Make sure we're executing the actual call in the
// file source loop by sending it over the queue. It will be processed in processAction().
queue->send(Action{ Action::Cancel, req->resource });
req->destruct();
}
void NodeFileSource::processAdd(const mbgl::Resource& resource) {
NanScope();
// Make sure the loop stays alive as long as request is pending.
if (pending.empty()) {
queue->ref();
}
auto requestHandle = NanNew<v8::Object>(NodeRequest::Create(this, resource));
v8::Persistent<v8::Object> requestPersistent;
NanAssignPersistent(requestPersistent, requestHandle);
pending.emplace(resource, std::move(requestPersistent));
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
auto requestFunction = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), options)->Get(NanNew("request")).As<v8::Function>();
#else
auto requestFunction = options->Get(NanNew("request")).As<v8::Function>();
#endif
v8::Local<v8::Value> argv[] = { requestHandle };
NanMakeCallback(NanGetCurrentContext()->Global(), requestFunction, 1, argv);
}
void NodeFileSource::processCancel(const mbgl::Resource& resource) {
NanScope();
auto it = pending.find(resource);
if (it == pending.end()) {
// The response callback was already fired. There is no point in calling the cancelation
// callback because the request is already completed.
} else {
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
auto requestHandle = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), it->second);
it->second.Reset();
#else
auto requestHandle = NanNew<v8::Object>(it->second);
NanDisposePersistent(it->second);
#endif
pending.erase(it);
// Make sure the the loop can exit when there are no pending requests.
if (pending.empty()) {
queue->unref();
}
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
auto optionsObject = v8::Local<v8::Object>::New(v8::Isolate::GetCurrent(), options);
if (optionsObject->Has(NanNew("cancel"))) {
auto cancelFunction = optionsObject->Get(NanNew("cancel")).As<v8::Function>();
#else
if (options->Has(NanNew("cancel"))) {
auto cancelFunction = options->Get(NanNew("cancel")).As<v8::Function>();
#endif
v8::Local<v8::Value> argv[] = { requestHandle };
NanMakeCallback(NanGetCurrentContext()->Global(), cancelFunction, 1, argv);
}
// Set the request handle in the request wrapper handle to null
node::ObjectWrap::Unwrap<NodeRequest>(requestHandle)->cancel();
}
}
void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_ptr<const mbgl::Response>& response) {
// First, remove the request, since it might be destructed at any point now.
auto it = pending.find(resource);
if (it != pending.end()) {
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
it->second.Reset();
#else
NanDisposePersistent(it->second);
#endif
pending.erase(it);
// Make sure the the loop can exit when there are no pending requests.
if (pending.empty()) {
queue->unref();
}
}
std::lock_guard<std::mutex> lock(observersMutex);
auto observersIt = observers.find(resource);
if (observersIt == observers.end()) {
return;
}
observersIt->second->notify(response);
observers.erase(observersIt);
}
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <mbgl/storage/file_source.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#include <node.h>
#include <node_version.h>
#include <nan.h>
#pragma GCC diagnostic pop
#include <memory>
#include <mutex>
#include <unordered_map>
namespace node_mbgl {
namespace util { template <typename T> class AsyncQueue; }
class NodeFileSource : public mbgl::FileSource {
public:
NodeFileSource(v8::Handle<v8::Object>);
~NodeFileSource();
mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback);
void cancel(mbgl::Request*);
// visiblity?
void notify(const mbgl::Resource&, const std::shared_ptr<const mbgl::Response>&);
private:
struct Action;
using Queue = util::AsyncQueue<Action>;
void processAdd(const mbgl::Resource&);
void processCancel(const mbgl::Resource&);
v8::Persistent<v8::Object> options;
private:
#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION)
std::unordered_map<mbgl::Resource, v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object>>, mbgl::Resource::Hash> pending;
#else
std::unordered_map<mbgl::Resource, v8::Persistent<v8::Object>, mbgl::Resource::Hash> pending;
#endif
// The observers list will hold pointers to all the requests waiting
// for a particular resource. The access must be guarded by a mutex
// because the list is also accessed by a thread from the mbgl::Map
// object and from the main thread when notifying requests of
// completion. Concurrent access is specially needed when
// canceling a request to avoid a deadlock (see #129).
std::unordered_map<mbgl::Resource, mbgl::Request*, mbgl::Resource::Hash> observers;
std::mutex observersMutex;
Queue *queue = nullptr;
};
}

View File

@@ -0,0 +1,54 @@
#include "node_log.hpp"
#include "util/async_queue.hpp"
namespace node_mbgl {
struct NodeLogObserver::LogMessage {
mbgl::EventSeverity severity;
mbgl::Event event;
int64_t code;
std::string text;
LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_)
: severity(severity_),
event(event_),
code(code_),
text(text_) {}
};
NodeLogObserver::NodeLogObserver(v8::Handle<v8::Object> target)
: queue(new Queue(uv_default_loop(), [this](LogMessage &message) {
NanScope();
auto msg = NanNew<v8::Object>();
msg->Set(NanNew("class"), NanNew(mbgl::EventClass(message.event).c_str()));
msg->Set(NanNew("severity"), NanNew(mbgl::EventSeverityClass(message.severity).c_str()));
if (message.code != -1) {
msg->Set(NanNew("code"), NanNew<v8::Number>(message.code));
}
if (!message.text.empty()) {
msg->Set(NanNew("text"), NanNew(message.text));
}
v8::Local<v8::Value> argv[] = { NanNew("message"), msg };
auto handle = NanNew<v8::Object>(module);
auto emit = handle->Get(NanNew("emit"))->ToObject();
emit->CallAsFunction(handle, 2, argv);
})) {
NanScope();
NanAssignPersistent(module, target);
// Don't keep the event loop alive.
queue->unref();
}
NodeLogObserver::~NodeLogObserver() {
queue->stop();
}
bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) {
queue->send({ severity, event, code, text });
return true;
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include <mbgl/platform/log.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#include <node.h>
#include <nan.h>
#pragma GCC diagnostic pop
namespace node_mbgl {
namespace util { template <typename T> class AsyncQueue; }
class NodeLogObserver : public mbgl::Log::Observer {
public:
NodeLogObserver(v8::Handle<v8::Object> target);
~NodeLogObserver();
// Log::Observer implementation
virtual bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) override;
private:
v8::Persistent<v8::Object> module;
struct LogMessage;
using Queue = util::AsyncQueue<LogMessage>;
Queue *queue = nullptr;
};
}

View File

@@ -0,0 +1,349 @@
#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;
float ratio = 1.0f;
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("ratio"))) { options->ratio = obj->Get(NanNew("ratio"))->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();
}
}

View File

@@ -0,0 +1,66 @@
#pragma once
#include "node_file_source.hpp"
#include <mbgl/map/map.hpp>
#include <mbgl/platform/default/headless_view.hpp>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#include <node.h>
#include <nan.h>
#pragma GCC diagnostic pop
#include <queue>
namespace node_mbgl {
class NodeMap : public node::ObjectWrap {
struct RenderOptions;
class RenderWorker;
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
public:
static void Init(v8::Handle<v8::Object> target);
static NAN_METHOD(New);
static NAN_METHOD(Load);
static NAN_METHOD(Render);
static NAN_METHOD(Release);
void startRender(std::unique_ptr<NodeMap::RenderOptions> options);
void renderFinished();
void release();
inline bool isLoaded() { return loaded; }
inline bool isValid() { return valid; }
static std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
private:
NodeMap(v8::Handle<v8::Object>);
~NodeMap();
private:
mbgl::HeadlessView view;
NodeFileSource fs;
std::unique_ptr<mbgl::Map> map;
std::exception_ptr error;
std::unique_ptr<const mbgl::StillImage> image;
std::unique_ptr<NanCallback> callback;
// Async for delivering the notifications of render completion.
uv_async_t *async;
bool loaded = false;
bool valid = true;
};
}

View File

@@ -0,0 +1,36 @@
#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("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);
// 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)

View File

@@ -0,0 +1,142 @@
#include "node_request.hpp"
#include "node_file_source.hpp"
#include <mbgl/storage/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 || !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();
}
}

View File

@@ -0,0 +1,43 @@
#pragma once
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wshadow"
#include <node.h>
#include <nan.h>
#pragma GCC diagnostic pop
#include <mbgl/storage/resource.hpp>
#include <memory>
namespace node_mbgl {
class NodeFileSource;
class NodeRequest : public node::ObjectWrap {
////////////////////////////////////////////////////////////////////////////////////////////////
// Static Node Methods
public:
static void Init(v8::Handle<v8::Object> target);
static NAN_METHOD(New);
static NAN_METHOD(Respond);
static v8::Handle<v8::Object> Create(NodeFileSource*, const mbgl::Resource&);
static v8::Persistent<v8::FunctionTemplate> constructorTemplate;
////////////////////////////////////////////////////////////////////////////////////////////////
// Instance
public:
NodeRequest(NodeFileSource* source, const mbgl::Resource& resource);
~NodeRequest();
void cancel();
private:
NodeFileSource* source;
std::unique_ptr<mbgl::Resource> resource;
};
}

View File

@@ -0,0 +1,90 @@
#pragma once
#include <uv.h>
#include <thread>
#include <mutex>
#include <functional>
#include <queue>
#include <string>
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int
#else
#define UV_ASYNC_PARAMS(handle) uv_async_t *handle
#endif
namespace node_mbgl {
namespace util {
template <typename T>
class AsyncQueue {
public:
AsyncQueue(uv_loop_t *loop, std::function<void(T &)> fn) :
callback(fn) {
async.data = this;
uv_async_init(loop, &async, [](UV_ASYNC_PARAMS(handle)) {
auto q = reinterpret_cast<AsyncQueue *>(handle->data);
q->process();
});
}
void send(T &&data) {
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::make_unique<T>(std::move(data)));
}
uv_async_send(&async);
}
void send(std::unique_ptr<T> data) {
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(data));
}
uv_async_send(&async);
}
void stop() {
uv_close((uv_handle_t *)&async, [](uv_handle_t *handle) {
delete reinterpret_cast<AsyncQueue *>(handle->data);
});
}
void ref() {
uv_ref((uv_handle_t *)&async);
}
void unref() {
uv_unref((uv_handle_t *)&async);
}
private:
~AsyncQueue() {
}
void process() {
std::unique_ptr<T> item;
while (true) {
mutex.lock();
if (queue.empty()) {
mutex.unlock();
break;
}
item = std::move(queue.front());
queue.pop();
mutex.unlock();
callback(*item);
}
}
private:
std::mutex mutex;
uv_async_t async;
std::queue<std::unique_ptr<T>> queue;
std::function<void(T &)> callback;
};
}
}