diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2015-02-04 18:21:39 +0100 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2015-02-04 18:21:39 +0100 |
commit | 07867edd4764e0801f715bff2de899ae1620eb8c (patch) | |
tree | 6615f572457ff03ffed8758c856c7a658435d57a | |
download | qtlocation-mapboxgl-07867edd4764e0801f715bff2de899ae1620eb8c.tar.gz |
initial version with updated build system
-rw-r--r-- | .clang-format | 18 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | Makefile | 43 | ||||
-rw-r--r-- | binding.gyp | 59 | ||||
-rw-r--r-- | package.json | 16 | ||||
-rw-r--r-- | src/mbgl.cpp | 26 | ||||
-rw-r--r-- | src/node_file_source.cpp | 156 | ||||
-rw-r--r-- | src/node_file_source.hpp | 49 | ||||
-rw-r--r-- | src/node_map.cpp | 185 | ||||
-rw-r--r-- | src/node_map.hpp | 54 | ||||
-rw-r--r-- | src/node_map_render_worker.cpp | 59 | ||||
-rw-r--r-- | src/node_map_render_worker.hpp | 39 | ||||
-rw-r--r-- | src/node_request.cpp | 148 | ||||
-rw-r--r-- | src/node_request.hpp | 36 | ||||
-rw-r--r-- | src/thread_object.cpp | 85 | ||||
-rw-r--r-- | src/thread_object.hpp | 28 | ||||
-rw-r--r-- | test/file_source.test.js | 19 | ||||
-rw-r--r-- | test/map.test.js | 134 | ||||
m--------- | vendor/mbgl | 6 |
20 files changed, 1166 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..86d562e74e --- /dev/null +++ b/.clang-format @@ -0,0 +1,18 @@ +Standard: Cpp11 +IndentWidth: 4 +AccessModifierOffset: -4 +UseTab: Never +BinPackParameters: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +AlwaysBreakTemplateDeclarations: true +NamespaceIndentation: None +PointerBindsToType: false +SpacesInParentheses: false +BreakBeforeBraces: Attach +ColumnLimit: 100 +Cpp11BracedListStyle: false +SpacesBeforeTrailingComments: 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b8de80b584 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build +/lib +/node_modules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..863c7f66f4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/mbgl"] + path = vendor/mbgl + url = https://github.com/mapbox/mapbox-gl-native.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..ef65d4cf4a --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +BUILDTYPE ?= Release +MBGL ?= vendor/mbgl + +DEBUG_FLAG = +ifeq ($(BUILDTYPE), Debug) +DEBUG_FLAG = -d +endif + +ifeq ($(shell uname -s), Darwin) +HOST ?= osx +endif +HOST ?= linux + + +include $(MBGL)/config/defaults.mk + +global: build + +.PHONY: build +build: build/Makefile + @node-gyp build $(DEBUG_FLAG) -- -j8 + +.PHONY: build/Makefile +build/Makefile: $(MBGL)/config/$(HOST).gypi + @node-gyp configure -- \ + -Dmbgl=$(MBGL) \ + -Dhost=osx \ + -I$(MBGL)/config/$(HOST).gypi \ + $(LIBS_$(HOST)) \ + -Duv_static_libs= -Duv_ldflags= \ + -Goutput_dir=. + +$(MBGL)/config/%.gypi: $(MBGL)/configure + make -C $(MBGL) config/$*.gypi + +.PHONY: test +test: build + @`npm bin`/mocha -R spec + +.PHONY: clean +clean: + rm -rf build + rm $(MBGL)/config.gypi diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000000..0d4922fad5 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,59 @@ +{ + 'targets': [ + { 'target_name': 'mbgl', + 'dependencies': [ + './<(mbgl)/mbgl.gyp:core', + './<(mbgl)/mbgl.gyp:platform-<(platform_lib)', + './<(mbgl)/mbgl.gyp:headless-<(headless_lib)', + ], + + 'include_dirs': [ "<!(node -e \"require('nan')\")" ], + + 'sources': [ + 'src/mbgl.cpp', + 'src/node_file_source.hpp', + 'src/node_file_source.cpp', + 'src/node_map.hpp', + 'src/node_map.cpp', + 'src/node_map_render_worker.hpp', + 'src/node_map_render_worker.cpp', + 'src/node_request.hpp', + 'src/node_request.cpp', + ], + + 'variables': { + 'cflags_cc': [ + '-std=c++11', + '-stdlib=libc++', + '-Wno-unused-parameter' + ], + }, + + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' + } + }, { + 'cflags_cc': [ + '<@(cflags_cc)', + '-fexceptions', + ], + }] + ], + }, + + { 'target_name': 'action_after_build', + 'type': 'none', + 'dependencies': [ 'mbgl' ], + 'copies': [ + { + 'files': [ '<(PRODUCT_DIR)/mbgl.node' ], + 'destination': 'lib' + } + ] + } + ] +}
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..c93a4cb8a0 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "mbgl", + "version": "0.0.1", + "main": "lib/mbgl.node", + "dependencies": { + "mocha": "^2.1.0", + "nan": "^1.4.1", + "request": "^2.51.0" + }, + "devDependencies": {}, + "scripts": { + "install": "make", + "test": "mocha -R spec test" + }, + "gypfile": true +} diff --git a/src/mbgl.cpp b/src/mbgl.cpp new file mode 100644 index 0000000000..677e72b2d1 --- /dev/null +++ b/src/mbgl.cpp @@ -0,0 +1,26 @@ +#include <node.h> +#include <nan.h> + +#include "node_file_source.hpp" +#include "node_map.hpp" +#include "node_request.hpp" + +void RegisterModule(v8::Handle<v8::Object> exports) { + NanScope(); + + node_mbgl::NodeFileSource::Init(exports); + node_mbgl::NodeMap::Init(exports); + node_mbgl::NodeRequest::Init(exports); + + // Exports Resource constants. + auto ConstantProperty = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete); + auto resource = v8::Object::New(); + resource->ForceSet(NanNew("Unknown"), NanNew(mbgl::Resource::Unknown), ConstantProperty); + resource->ForceSet(NanNew("Tile"), NanNew(mbgl::Resource::Tile), ConstantProperty); + resource->ForceSet(NanNew("Glyphs"), NanNew(mbgl::Resource::Glyphs), ConstantProperty); + resource->ForceSet(NanNew("Image"), NanNew(mbgl::Resource::Image), ConstantProperty); + resource->ForceSet(NanNew("JSON"), NanNew(mbgl::Resource::JSON), ConstantProperty); + exports->ForceSet(NanNew("Resource"), resource, ConstantProperty); +} + +NODE_MODULE(mbgl, RegisterModule) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp new file mode 100644 index 0000000000..6ce81c0647 --- /dev/null +++ b/src/node_file_source.cpp @@ -0,0 +1,156 @@ +#include "node_file_source.hpp" +#include "node_request.hpp" + +#include <mbgl/storage/default/request.hpp> +#include <mbgl/util/async_queue.hpp> + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent<v8::FunctionTemplate> NodeFileSource::constructorTemplate; + +void NodeFileSource::Init(v8::Handle<v8::Object> target) { + NanScope(); + + v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(NanNew("FileSource")); + + NanAssignPersistent(constructorTemplate, t); + + target->Set(NanNew("FileSource"), t->GetFunction()); +} + +NAN_METHOD(NodeFileSource::New) { + NanScope(); + + if (!args.IsConstructCall()) { + return NanThrowTypeError("Use the new operator to create new FileSource objects"); + } + + auto fs = new NodeFileSource(); + fs->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +struct NodeFileSource::Action { + const enum : bool { Add, Cancel } type; + mbgl::Request *const request; +}; + +NodeFileSource::NodeFileSource() : + queue(new Queue(uv_default_loop(), [this](Action &action) { + if (action.type == Action::Add) { + processAdd(action.request); + } else if (action.type == Action::Cancel) { + processCancel(action.request); + } + })) +{ + // Make sure that the queue doesn't block the loop from exiting. + queue->unref(); +} + +NodeFileSource::~NodeFileSource() { + queue->stop(); + queue = nullptr; +} + +mbgl::Request *NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t *loop, Callback callback) { + auto request = new mbgl::Request(resource, loop, std::move(callback)); + + // This function can be called from any thread. Make sure we're executing the actual call in the + // file source loop by sending it over the queue. It will be processed in processAction(). + queue->send(Action{ Action::Add, request }); + return request; +} + +void NodeFileSource::request(const mbgl::Resource &resource, Callback callback) { + auto request = new mbgl::Request(resource, nullptr, std::move(callback)); + + // This function can be called from any thread. Make sure we're executing the actual call in the + // file source loop by sending it over the queue. It will be processed in processAction(). + queue->send(Action{ Action::Add, request }); +} + + +void NodeFileSource::cancel(mbgl::Request *request) { + request->cancel(); + + // This function can be called from any thread. Make sure we're executing the actual call in the + // file source loop by sending it over the queue. It will be processed in processAction(). + queue->send(Action{ Action::Cancel, request }); +} + + +void NodeFileSource::processAdd(mbgl::Request *request) { + NanScope(); + + // Make sure the loop stays alive as long as request is pending. + if (pending.empty()) { + queue->ref(); + } + + auto requestHandle = v8::Local<v8::Object>::New(NodeRequest::Create(handle_, request)); + pending.emplace(request, std::move(v8::Persistent<v8::Object>::New(requestHandle))); + + v8::Local<v8::Value> argv[] = { requestHandle }; + NanMakeCallback(handle_, NanNew("request"), 1, argv); +} + +void NodeFileSource::processCancel(mbgl::Request *request) { + NanScope(); + + auto it = pending.find(request); + if (it == pending.end()) { + // The response callback was already fired. There is no point in calling the cancelation + // callback because the request is already completed. + } else { + auto requestHandle = v8::Local<v8::Object>::New(it->second); + + // Dispose and remove the persistent handle + it->second.Dispose(); + pending.erase(it); + + // Make sure the the loop can exit when there are no pending requests. + if (pending.empty()) { + queue->unref(); + } + + if (handle_->Has(NanNew("cancel"))) { + v8::Local<v8::Value> argv[] = { requestHandle }; + NanMakeCallback(handle_, NanNew("cancel"), 1, argv); + } + + // Set the request handle in the request wrapper handle to null + ObjectWrap::Unwrap<NodeRequest>(requestHandle)->cancel(); + } + + // Finally, destruct the request object + request->destruct(); +} + +void NodeFileSource::notify(mbgl::Request *request, const std::shared_ptr<const mbgl::Response>& response) { + // First, remove the request, since it might be destructed at any point now. + auto it = pending.find(request); + if (it != pending.end()) { + it->second.Dispose(); + pending.erase(it); + + // Make sure the the loop can exit when there are no pending requests. + if (pending.empty()) { + queue->unref(); + } + } + + request->notify(response); +} + +} diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp new file mode 100644 index 0000000000..293dcf63ce --- /dev/null +++ b/src/node_file_source.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include <mbgl/storage/file_source.hpp> + +#include <node.h> +#include <nan.h> + +#include <map> + +namespace mbgl { namespace util { template <typename T> class AsyncQueue; } } + +namespace node_mbgl { + +class NodeFileSource : public node::ObjectWrap, public mbgl::FileSource { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle<v8::Object> target); + static NAN_METHOD(New); + + static v8::Persistent<v8::FunctionTemplate> constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +public: + NodeFileSource(); + ~NodeFileSource(); + + mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, Callback callback); + void request(const mbgl::Resource &resource, Callback callback); + void cancel(mbgl::Request *request); + + + // visiblity? + void notify(mbgl::Request *request, const std::shared_ptr<const mbgl::Response> &response); + +private: + struct Action; + using Queue = mbgl::util::AsyncQueue<Action>; + + void processAdd(mbgl::Request *request); + void processCancel(mbgl::Request *request); + +private: + std::map<mbgl::Request *, v8::Persistent<v8::Object>> pending; + Queue *queue = nullptr; +}; + +} diff --git a/src/node_map.cpp b/src/node_map.cpp new file mode 100644 index 0000000000..24672af66a --- /dev/null +++ b/src/node_map.cpp @@ -0,0 +1,185 @@ +#include "node_map.hpp" +#include "node_map_render_worker.hpp" +#include "node_file_source.hpp" + +#include <mbgl/platform/default/headless_display.hpp> + + #include <unistd.h> + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent<v8::FunctionTemplate> NodeMap::constructorTemplate; + +void NodeMap::Init(v8::Handle<v8::Object> target) { + NanScope(); + + v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(NanNew("Map")); + + NODE_SET_PROTOTYPE_METHOD(t, "load", Load); + NODE_SET_PROTOTYPE_METHOD(t, "render", Render); + + NanAssignPersistent(constructorTemplate, t); + + target->Set(NanNew("Map"), t->GetFunction()); +} + +NAN_METHOD(NodeMap::New) { + NanScope(); + + if (!args.IsConstructCall()) { + return NanThrowTypeError("Use the new operator to create new Map objects"); + } + + if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) { + return NanThrowTypeError("Requires a FileSource as first argument"); + } + + auto source = args[0]->ToObject(); + + // Check that request() and cancel() are defined. + if (!source->Has(NanNew("request")) || !source->Get(NanNew("request"))->IsFunction()) { + return NanThrowError("FileSource must have a request member function"); + } + if (!source->Has(NanNew("cancel")) || !source->Get(NanNew("cancel"))->IsFunction()) { + return NanThrowError("FileSource must have a cancel member function"); + } + + auto map = new NodeMap(args[0]->ToObject()); + map->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +const std::string StringifyStyle(v8::Handle<v8::Value> styleHandle) { + NanScope(); + + auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject(); + auto stringify = v8::Handle<v8::Function>::Cast(JSON->Get(NanNew("stringify"))); + + return *NanUtf8String(stringify->Call(JSON, 1, &styleHandle)); +} + +NAN_METHOD(NodeMap::Load) { + NanScope(); + + if (args.Length() != 1) { + return NanThrowError("Requires a map style as first argument"); + } + + std::string style; + + if (args[0]->IsObject()) { + style = StringifyStyle(args[0]); + } else if (args[0]->IsString()) { + v8::Local<v8::String> toStr = args[0]->ToString(); + style.resize(toStr->Utf8Length()); + toStr->WriteUtf8(const_cast<char *>(style.data())); + } else { + return NanThrowTypeError("First argument must be a string or object"); + } + + auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder()); + + try { + nodeMap->map.setStyleJSON(style, "."); + } catch (const std::exception &ex) { + NanThrowError(ex.what()); + } + + NanReturnUndefined(); +} + +std::unique_ptr<NodeMap::RenderOptions> NodeMap::ParseOptions(v8::Local<v8::Object> obj) { + NanScope(); + + auto options = mbgl::util::make_unique<RenderOptions>(); + + if (obj->Has(NanNew("zoom"))) { options->zoom = obj->Get(NanNew("zoom"))->NumberValue(); } + if (obj->Has(NanNew("bearing"))) { options->bearing = obj->Get(NanNew("bearing"))->NumberValue(); } + if (obj->Has(NanNew("center"))) { + auto center = obj->Get(NanNew("center")).As<v8::Array>(); + if (center->Length() > 0) { options->latitude = center->Get(0)->NumberValue(); } + if (center->Length() > 1) { options->longitude = center->Get(1)->NumberValue(); } + } + if (obj->Has(NanNew("width"))) { options->width = obj->Get(NanNew("width"))->IntegerValue(); } + if (obj->Has(NanNew("height"))) { options->height = obj->Get(NanNew("height"))->IntegerValue(); } + if (obj->Has(NanNew("ratio"))) { options->ratio = obj->Get(NanNew("ratio"))->IntegerValue(); } + if (obj->Has(NanNew("accessToken"))) { + options->accessToken = *NanUtf8String(obj->Get(NanNew("accessToken")->ToString())); + } + + if (obj->Has(NanNew("classes"))) { + auto classes = obj->Get(NanNew("classes"))->ToObject().As<v8::Array>(); + const int length = classes->Length(); + options->classes.reserve(length); + for (int i = 0; i < length; i++) { + options->classes.push_back(std::string { *NanUtf8String(classes->Get(i)->ToString()) }); + } + } + + return options; +} + +NAN_METHOD(NodeMap::Render) { + NanScope(); + + if (args.Length() <= 0 || !args[0]->IsObject()) { + return NanThrowTypeError("First argument must be an options object"); + } + + if (args.Length() <= 1 || !args[1]->IsFunction()) { + return NanThrowTypeError("Second argument must be a callback function"); + } + + auto nodeMap = node::ObjectWrap::Unwrap<NodeMap>(args.Holder()); + + const bool wasEmpty = nodeMap->queue_.empty(); + + nodeMap->queue_.push(mbgl::util::make_unique<RenderWorker>( + nodeMap, + ParseOptions(args[0]->ToObject()), + new NanCallback(args[1].As<v8::Function>()))); + + if (wasEmpty) { + // When the queue was empty, there was no action in progress, so we can start a new one. + NanAsyncQueueWorker(nodeMap->queue_.front().release()); + } + + NanReturnUndefined(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +std::shared_ptr<mbgl::HeadlessDisplay> sharedDisplay() { + static auto display = std::make_shared<mbgl::HeadlessDisplay>(); + return display; +} + +NodeMap::NodeMap(v8::Handle<v8::Object> source_) : + view(sharedDisplay()), + fs(*ObjectWrap::Unwrap<NodeFileSource>(source_)), + map(view, fs) { + source = v8::Persistent<v8::Object>::New(source_); +} + +NodeMap::~NodeMap() { + source.Dispose(); +} + +void NodeMap::processNext() { + assert(!queue_.empty()); + queue_.pop(); + if (!queue_.empty()) { + // When the queue was empty, there was no action in progress, so we can start a new one. + NanAsyncQueueWorker(queue_.front().release()); + } +} + +} diff --git a/src/node_map.hpp b/src/node_map.hpp new file mode 100644 index 0000000000..fdc625c038 --- /dev/null +++ b/src/node_map.hpp @@ -0,0 +1,54 @@ +#ifndef NODE_MBGL_NODE_MAP +#define NODE_MBGL_NODE_MAP + +#include <mbgl/map/map.hpp> +#include <mbgl/platform/default/headless_view.hpp> + +#include <node.h> +#include <nan.h> + +#include <queue> + +namespace node_mbgl { + + +class NodeFileSource; + +class NodeMap : public node::ObjectWrap { + struct RenderOptions; + class RenderWorker; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle<v8::Object> target); + static NAN_METHOD(New); + static NAN_METHOD(Load); + static NAN_METHOD(Render); + + static std::unique_ptr<NodeMap::RenderOptions> ParseOptions(v8::Local<v8::Object> obj); + + static v8::Persistent<v8::FunctionTemplate> constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +private: + NodeMap(v8::Handle<v8::Object> source); + ~NodeMap(); + + void processNext(); + +private: + v8::Persistent<v8::Object> source; + v8::Persistent<v8::Object> self; + + mbgl::HeadlessView view; + NodeFileSource &fs; + mbgl::Map map; + + std::queue<std::unique_ptr<RenderWorker>> queue_; +}; + +} // end ns node_mbgl + +#endif diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp new file mode 100644 index 0000000000..d11b5a0097 --- /dev/null +++ b/src/node_map_render_worker.cpp @@ -0,0 +1,59 @@ +#include "node_map_render_worker.hpp" +#include "node_file_source.hpp" + +#include <mbgl/util/image.hpp> + +namespace node_mbgl { + +NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr<RenderOptions> options_, + NanCallback *callback_) + : NanAsyncWorker(callback_), nodeMap(nodeMap_), options(std::move(options_)) { + nodeMap->Ref(); +} + +NodeMap::RenderWorker::~RenderWorker() { + nodeMap->Unref(); +} + +void NodeMap::RenderWorker::Execute() { + fprintf(stderr, "executing render worker\n"); + try { + nodeMap->view.resize(options->width, options->height, options->ratio); + nodeMap->map.setAccessToken(options->accessToken); + nodeMap->map.resize(options->width, options->height, options->ratio); + nodeMap->map.setClasses(options->classes); + nodeMap->map.setLonLatZoom(options->longitude, options->latitude, options->zoom); + nodeMap->map.setBearing(options->bearing); + + // Run the loop. It will terminate when we don't have any further listeners. + nodeMap->map.run(); + + fprintf(stderr, "run completed\n"); + const unsigned int width = options->width * options->ratio; + const unsigned int height = options->height * options->ratio; + image = mbgl::util::compress_png(width, height, nodeMap->view.readPixels().get()); + fprintf(stderr, "png compressed\n"); + } catch (const std::exception &ex) { + SetErrorMessage(ex.what()); + } +} + +void NodeMap::RenderWorker::WorkComplete() { + NanAsyncWorker::WorkComplete(); + + // Continue processing remaining items in the queue. + nodeMap->processNext(); +} + +void NodeMap::RenderWorker::HandleOKCallback() { + NanScope(); + + v8::Local<v8::Value> argv[] = { + NanNull(), + NanNewBufferHandle(image.data(), image.length()) + }; + + callback->Call(2, argv); +}; + +} // ns node_mbgl diff --git a/src/node_map_render_worker.hpp b/src/node_map_render_worker.hpp new file mode 100644 index 0000000000..920332f7d9 --- /dev/null +++ b/src/node_map_render_worker.hpp @@ -0,0 +1,39 @@ +#ifndef NODE_MBGL_NODE_MAP_RENDER_WORKER +#define NODE_MBGL_NODE_MAP_RENDER_WORKER + +#include <nan.h> + +#include "node_map.hpp" + +namespace node_mbgl { + +struct NodeMap::RenderOptions { + double zoom = 0; + double bearing = 0; + double latitude = 0; + double longitude = 0; + unsigned int width = 512; + unsigned int height = 512; + float ratio = 1.0f; + std::string accessToken; + std::vector<std::string> classes; +}; + +class NodeMap::RenderWorker : public NanAsyncWorker { +public: + RenderWorker(NodeMap *map, std::unique_ptr<RenderOptions> options, NanCallback *callback); + ~RenderWorker(); + + void Execute(); + void HandleOKCallback(); + void WorkComplete(); + +private: + NodeMap *nodeMap; + std::unique_ptr<RenderOptions> options; + std::string image; +}; + +} // ns node_mbgl + +#endif // NODE_MBGL_RENDER_WORKER diff --git a/src/node_request.cpp b/src/node_request.cpp new file mode 100644 index 0000000000..9d5b543880 --- /dev/null +++ b/src/node_request.cpp @@ -0,0 +1,148 @@ +#include "node_request.hpp" +#include "node_file_source.hpp" +#include <mbgl/storage/default/request.hpp> +#include <mbgl/storage/response.hpp> + +#include <cmath> + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent<v8::FunctionTemplate> NodeRequest::constructorTemplate; + +void NodeRequest::Init(v8::Handle<v8::Object> target) { + NanScope(); + + v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(NanNew("Request")); + + NODE_SET_PROTOTYPE_METHOD(t, "respond", Respond); + + NanAssignPersistent(constructorTemplate, t); + + target->Set(NanNew("Request"), t->GetFunction()); +} + +NAN_METHOD(NodeRequest::New) { + NanScope(); + + // Extract the pointer from the first argument + if (args.Length() < 2 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0]) || !args[1]->IsExternal()) { + return NanThrowTypeError("Cannot create Request objects explicitly"); + } + + auto source = v8::Persistent<v8::Object>::New(args[0]->ToObject()); + auto request = reinterpret_cast<mbgl::Request *>(args[1].As<v8::External>()->Value()); + auto req = new NodeRequest(source, request); + req->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +// TODO: retain the NodeFileSource in a permanent handle! + +v8::Handle<v8::Object> NodeRequest::Create(v8::Handle<v8::Object> source, mbgl::Request *request) { + NanScope(); + v8::Local<v8::Value> argv[] = { v8::Local<v8::Object>::New(source), NanNew<v8::External>(request) }; + auto instance = constructorTemplate->GetFunction()->NewInstance(2, argv); + + instance->Set(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly); + instance->Set(NanNew("kind"), NanNew<v8::Integer>(int(request->resource.kind)), v8::ReadOnly); + + NanReturnValue(instance); +} + +NAN_METHOD(NodeRequest::Respond) { + NanScope(); + + auto nodeRequest = ObjectWrap::Unwrap<NodeRequest>(args.Holder()); + if (!nodeRequest->request) { + return NanThrowError("Request has already been responded to, or was canceled."); + } + + auto source = ObjectWrap::Unwrap<NodeFileSource>(nodeRequest->source); + + auto request = nodeRequest->request; + nodeRequest->request = nullptr; + + if (args.Length() < 1) { + return NanThrowTypeError("First argument must be an error object"); + } else if (args[0]->BooleanValue()) { + auto response = std::make_shared<mbgl::Response>(); + + response->status = mbgl::Response::Error; + + // Store the error string. + const NanUtf8String message { args[0]->ToString() }; + response->message = std::string { *message, size_t(message.length()) }; + + source->notify(request, response); + } else if (args.Length() < 2 || !args[1]->IsObject()) { + return NanThrowTypeError("Second argument must be a response object"); + } else { + auto response = std::make_shared<mbgl::Response>(); + auto res = args[1]->ToObject(); + + response->status = mbgl::Response::Successful; + + if (res->Has(NanNew("modified"))) { + const double modified = res->Get(NanNew("modified"))->ToNumber()->Value(); + if (!std::isnan(modified)) { + response->modified = modified / 1000; // JS timestamps are milliseconds + } + } + + if (res->Has(NanNew("expires"))) { + const double expires = res->Get(NanNew("expires"))->ToNumber()->Value(); + if (!std::isnan(expires)) { + response->expires = expires / 1000; // JS timestamps are milliseconds + } + } + + if (res->Has(NanNew("etag"))) { + auto etagHandle = res->Get(NanNew("etag")); + if (etagHandle->BooleanValue()) { + const NanUtf8String etag { etagHandle->ToString() }; + response->etag = std::string { *etag, size_t(etag.length()) }; + } + } + + if (res->Has(NanNew("data"))) { + auto dataHandle = res->Get(NanNew("data")); + if (node::Buffer::HasInstance(dataHandle)) { + response->data = std::string { + node::Buffer::Data(dataHandle), + node::Buffer::Length(dataHandle) + }; + } else { + return NanThrowTypeError("Response data must be a Buffer"); + } + } + + // Send the response object to the NodeFileSource object + source->notify(request, response); + } + + NanReturnUndefined(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +NodeRequest::NodeRequest(v8::Persistent<v8::Object> source_, mbgl::Request *request_) + : source(source_), request(request_) { +} + +NodeRequest::~NodeRequest() { + source.Dispose(); +} + +void NodeRequest::cancel() { + request = nullptr; +} + +} diff --git a/src/node_request.hpp b/src/node_request.hpp new file mode 100644 index 0000000000..d07e93d42d --- /dev/null +++ b/src/node_request.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include <node.h> +#include <nan.h> + +class NodeFileSource; +namespace mbgl { class Request; } + +namespace node_mbgl { + +class NodeRequest : public node::ObjectWrap { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle<v8::Object> target); + static NAN_METHOD(New); + static NAN_METHOD(Respond); + + static v8::Handle<v8::Object> Create(v8::Handle<v8::Object> source, mbgl::Request *request); + + static v8::Persistent<v8::FunctionTemplate> constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +public: + NodeRequest(v8::Persistent<v8::Object> source, mbgl::Request *request); + ~NodeRequest(); + + void cancel(); + +private: + v8::Persistent<v8::Object> source; + mbgl::Request *request = nullptr; +}; + +} diff --git a/src/thread_object.cpp b/src/thread_object.cpp new file mode 100644 index 0000000000..ea2e83130d --- /dev/null +++ b/src/thread_object.cpp @@ -0,0 +1,85 @@ +#include "thread_object.hpp" +#include "node_file_source.hpp" + + + #include <unistd.h> + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent<v8::FunctionTemplate> ThreadObject::constructorTemplate; + +void ThreadObject::Init(v8::Handle<v8::Object> target) { + NanScope(); + + v8::Local<v8::FunctionTemplate> t = NanNew<v8::FunctionTemplate>(New); + + t->InstanceTemplate()->SetInternalFieldCount(1); + t->SetClassName(NanNew("ThreadObject")); + + NanAssignPersistent(constructorTemplate, t); + + target->Set(NanNew("ThreadObject"), t->GetFunction()); +} + +NAN_METHOD(ThreadObject::New) { + NanScope(); + + if (!args.IsConstructCall()) { + return NanThrowTypeError("Use the new operator to create new ThreadObject objects"); + } + + if (args.Length() < 1 || !NanHasInstance(NodeFileSource::constructorTemplate, args[0])) { + return NanThrowTypeError("Requires a FileSource as first argument"); + } + + auto fs = ObjectWrap::Unwrap<NodeFileSource>(args[0]->ToObject()); + auto to = new ThreadObject(fs); + to->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +ThreadObject::ThreadObject(NodeFileSource *fs) { + self = v8::Persistent<v8::Object>::New(handle_); + + auto async = new uv_async_t; + async->data = this; + uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { + NanScope(); + auto to = reinterpret_cast<ThreadObject *>(as->data); + + to->self.Dispose(); + + uv_close((uv_handle_t *)as, [](uv_handle_t *handle) { + delete reinterpret_cast<uv_async_t *>(handle); + }); + }); + + thread = std::thread([this, fs, async]() { + auto loop = uv_loop_new(); + + fs->request({ mbgl::Resource::Image, "http://example.com" }, loop, [&](const mbgl::Response &res) { + fprintf(stderr, "response.status: %d\n", res.status); + fprintf(stderr, "response.etag: %s\n", res.etag.c_str()); + fprintf(stderr, "response.modified: %lld\n", res.modified); + fprintf(stderr, "response.expires: %lld\n", res.expires); + fprintf(stderr, "response.data.size: %ld\n", res.data.size()); + fprintf(stderr, "response.message: %s\n", res.message.c_str()); + }); + + uv_run(loop, UV_RUN_DEFAULT); + + uv_loop_delete(loop); + + // Notify that the loop has finished executing. + uv_async_send(async); + }); +} + +ThreadObject::~ThreadObject() { + thread.join(); +} diff --git a/src/thread_object.hpp b/src/thread_object.hpp new file mode 100644 index 0000000000..3fbdb0400e --- /dev/null +++ b/src/thread_object.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <node.h> +#include <nan.h> + +#include <thread> + +class NodeFileSource; + +class ThreadObject : public node::ObjectWrap { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle<v8::Object> target); + static NAN_METHOD(New); + + static v8::Persistent<v8::FunctionTemplate> constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +private: + ThreadObject(NodeFileSource *fs); + ~ThreadObject(); + +private: + std::thread thread; + v8::Persistent<v8::Object> self; +}; diff --git a/test/file_source.test.js b/test/file_source.test.js new file mode 100644 index 0000000000..1d7d86fe9a --- /dev/null +++ b/test/file_source.test.js @@ -0,0 +1,19 @@ +/* jshint node: true, unused: false */ +/* global describe, it */ +'use strict'; +var assert = require('assert'); + +var mbgl = require('..'); + +describe('FileSource', function() { + + describe('constructor', function() { + it('must be called with new', function() { + assert.throws(function() { + mbgl.FileSource(); + }, /Use the new operator to create new FileSource objects/); + }); + + }); + +}); diff --git a/test/map.test.js b/test/map.test.js new file mode 100644 index 0000000000..e719e789e5 --- /dev/null +++ b/test/map.test.js @@ -0,0 +1,134 @@ +/* jshint node: true, unused: false */ +/* global describe, it, beforeEach, afterEach */ +'use strict'; +var assert = require('assert'); + +var mbgl = require('..'); + +describe('Map', function() { + + describe('constructor', function() { + it('must be called with new', function() { + assert.throws(function() { + mbgl.Map(); + }, /Use the new operator to create new Map objects/); + }); + + it('should require a FileSource object as first parameter', function() { + assert.throws(function() { + new mbgl.Map(); + }, /Requires a FileSource as first argument/); + + assert.throws(function() { + new mbgl.Map('fileSource'); + }, /Requires a FileSource as first argument/); + + assert.throws(function() { + new mbgl.Map({}); + }, /Requires a FileSource as first argument/); + }); + + it('should require the FileSource object to have request and cancel methods', function() { + var fs = new mbgl.FileSource(); + + assert.throws(function() { + new mbgl.Map(fs); + }, /FileSource must have a request member function/); + + fs.request = 'test'; + assert.throws(function() { + new mbgl.Map(fs); + }, /FileSource must have a request member function/); + + fs.request = function() {}; + assert.throws(function() { + new mbgl.Map(fs); + }, /FileSource must have a cancel member function/); + + fs.cancel = 'test'; + assert.throws(function() { + new mbgl.Map(fs); + }, /FileSource must have a cancel member function/); + + fs.cancel = function() {}; + assert.doesNotThrow(function() { + new mbgl.Map(fs); + }); + }); + + }); + + describe('load styles', function() { + var map; + + beforeEach(function() { + var fs = new mbgl.FileSource(); + fs.request = function() {}; + fs.cancel = function() {}; + map = new mbgl.Map(fs); + }); + + afterEach(function() { + map = null; + }); + + it('requires a string or object as the first parameter', function() { + assert.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + + assert.throws(function() { + map.load('invalid'); + }, /Expect either an object or array at root/); + }); + + it('accepts a stylesheet string', function() { + map.load('{}'); + }); + + }); + + + describe('render arguments', function() { + var map; + + beforeEach(function() { + var fs = new mbgl.FileSource(); + fs.request = function() {}; + fs.cancel = function() {}; + map = new mbgl.Map(fs); + }); + + afterEach(function() { + map = null; + }); + + it('requires a string or object as the first parameter', function() { + assert.throws(function() { + map.render(); + }, /First argument must be an options object/); + + assert.throws(function() { + map.render('invalid'); + }, /First argument must be an options object/); + }); + + it('requires a callback as the second parameter', function() { + assert.throws(function() { + map.render({}); + }, /Second argument must be a callback function/); + + assert.throws(function() { + map.render({}, 'invalid'); + }, /Second argument must be a callback function/); + }); + + it('requires a callback as the second parameter', function(done) { + map.render({}, function(err) { + done(err); + }); + }); + + }); + +}); diff --git a/vendor/mbgl b/vendor/mbgl new file mode 160000 +Subproject da981e78253ab2b8715f4e10e175be4bea43c13 |