From 07867edd4764e0801f715bff2de899ae1620eb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 4 Feb 2015 18:21:39 +0100 Subject: [PATCH 001/760] initial version with updated build system --- .clang-format | 18 ++++ .gitignore | 3 + .gitmodules | 3 + Makefile | 43 ++++++++ binding.gyp | 59 +++++++++++ package.json | 16 +++ src/mbgl.cpp | 26 +++++ src/node_file_source.cpp | 156 +++++++++++++++++++++++++++ src/node_file_source.hpp | 49 +++++++++ src/node_map.cpp | 185 +++++++++++++++++++++++++++++++++ src/node_map.hpp | 54 ++++++++++ src/node_map_render_worker.cpp | 59 +++++++++++ src/node_map_render_worker.hpp | 39 +++++++ src/node_request.cpp | 148 ++++++++++++++++++++++++++ src/node_request.hpp | 36 +++++++ src/thread_object.cpp | 85 +++++++++++++++ src/thread_object.hpp | 28 +++++ test/file_source.test.js | 19 ++++ test/map.test.js | 134 ++++++++++++++++++++++++ vendor/mbgl | 1 + 20 files changed, 1161 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 binding.gyp create mode 100644 package.json create mode 100644 src/mbgl.cpp create mode 100644 src/node_file_source.cpp create mode 100644 src/node_file_source.hpp create mode 100644 src/node_map.cpp create mode 100644 src/node_map.hpp create mode 100644 src/node_map_render_worker.cpp create mode 100644 src/node_map_render_worker.hpp create mode 100644 src/node_request.cpp create mode 100644 src/node_request.hpp create mode 100644 src/thread_object.cpp create mode 100644 src/thread_object.hpp create mode 100644 test/file_source.test.js create mode 100644 test/map.test.js create mode 160000 vendor/mbgl diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..86d562e74 --- /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 000000000..b8de80b58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build +/lib +/node_modules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..863c7f66f --- /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 000000000..ef65d4cf4 --- /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 000000000..0d4922fad --- /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': [ " +#include + +#include "node_file_source.hpp" +#include "node_map.hpp" +#include "node_request.hpp" + +void RegisterModule(v8::Handle 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::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 000000000..6ce81c064 --- /dev/null +++ b/src/node_file_source.cpp @@ -0,0 +1,156 @@ +#include "node_file_source.hpp" +#include "node_request.hpp" + +#include +#include + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent NodeFileSource::constructorTemplate; + +void NodeFileSource::Init(v8::Handle target) { + NanScope(); + + v8::Local t = NanNew(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::New(NodeRequest::Create(handle_, request)); + pending.emplace(request, std::move(v8::Persistent::New(requestHandle))); + + v8::Local 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::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 argv[] = { requestHandle }; + NanMakeCallback(handle_, NanNew("cancel"), 1, argv); + } + + // Set the request handle in the request wrapper handle to null + ObjectWrap::Unwrap(requestHandle)->cancel(); + } + + // Finally, destruct the request object + request->destruct(); +} + +void NodeFileSource::notify(mbgl::Request *request, const std::shared_ptr& 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 000000000..293dcf63c --- /dev/null +++ b/src/node_file_source.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace mbgl { namespace util { template class AsyncQueue; } } + +namespace node_mbgl { + +class NodeFileSource : public node::ObjectWrap, public mbgl::FileSource { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle target); + static NAN_METHOD(New); + + static v8::Persistent 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 &response); + +private: + struct Action; + using Queue = mbgl::util::AsyncQueue; + + void processAdd(mbgl::Request *request); + void processCancel(mbgl::Request *request); + +private: + std::map> pending; + Queue *queue = nullptr; +}; + +} diff --git a/src/node_map.cpp b/src/node_map.cpp new file mode 100644 index 000000000..24672af66 --- /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 + + #include + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent NodeMap::constructorTemplate; + +void NodeMap::Init(v8::Handle target) { + NanScope(); + + v8::Local t = NanNew(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 styleHandle) { + NanScope(); + + auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject(); + auto stringify = v8::Handle::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 toStr = args[0]->ToString(); + style.resize(toStr->Utf8Length()); + toStr->WriteUtf8(const_cast(style.data())); + } else { + return NanThrowTypeError("First argument must be a string or object"); + } + + auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); + + try { + nodeMap->map.setStyleJSON(style, "."); + } catch (const std::exception &ex) { + NanThrowError(ex.what()); + } + + NanReturnUndefined(); +} + +std::unique_ptr NodeMap::ParseOptions(v8::Local obj) { + NanScope(); + + auto options = mbgl::util::make_unique(); + + 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(); + 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(); + 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(args.Holder()); + + const bool wasEmpty = nodeMap->queue_.empty(); + + nodeMap->queue_.push(mbgl::util::make_unique( + nodeMap, + ParseOptions(args[0]->ToObject()), + new NanCallback(args[1].As()))); + + 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 sharedDisplay() { + static auto display = std::make_shared(); + return display; +} + +NodeMap::NodeMap(v8::Handle source_) : + view(sharedDisplay()), + fs(*ObjectWrap::Unwrap(source_)), + map(view, fs) { + source = v8::Persistent::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 000000000..fdc625c03 --- /dev/null +++ b/src/node_map.hpp @@ -0,0 +1,54 @@ +#ifndef NODE_MBGL_NODE_MAP +#define NODE_MBGL_NODE_MAP + +#include +#include + +#include +#include + +#include + +namespace node_mbgl { + + +class NodeFileSource; + +class NodeMap : public node::ObjectWrap { + struct RenderOptions; + class RenderWorker; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle target); + static NAN_METHOD(New); + static NAN_METHOD(Load); + static NAN_METHOD(Render); + + static std::unique_ptr ParseOptions(v8::Local obj); + + static v8::Persistent constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +private: + NodeMap(v8::Handle source); + ~NodeMap(); + + void processNext(); + +private: + v8::Persistent source; + v8::Persistent self; + + mbgl::HeadlessView view; + NodeFileSource &fs; + mbgl::Map map; + + std::queue> 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 000000000..d11b5a009 --- /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 + +namespace node_mbgl { + +NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr 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 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 000000000..920332f7d --- /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 + +#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 classes; +}; + +class NodeMap::RenderWorker : public NanAsyncWorker { +public: + RenderWorker(NodeMap *map, std::unique_ptr options, NanCallback *callback); + ~RenderWorker(); + + void Execute(); + void HandleOKCallback(); + void WorkComplete(); + +private: + NodeMap *nodeMap; + std::unique_ptr 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 000000000..9d5b54388 --- /dev/null +++ b/src/node_request.cpp @@ -0,0 +1,148 @@ +#include "node_request.hpp" +#include "node_file_source.hpp" +#include +#include + +#include + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent NodeRequest::constructorTemplate; + +void NodeRequest::Init(v8::Handle target) { + NanScope(); + + v8::Local t = NanNew(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::New(args[0]->ToObject()); + auto request = reinterpret_cast(args[1].As()->Value()); + auto req = new NodeRequest(source, request); + req->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +// TODO: retain the NodeFileSource in a permanent handle! + +v8::Handle NodeRequest::Create(v8::Handle source, mbgl::Request *request) { + NanScope(); + v8::Local argv[] = { v8::Local::New(source), NanNew(request) }; + auto instance = constructorTemplate->GetFunction()->NewInstance(2, argv); + + instance->Set(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly); + instance->Set(NanNew("kind"), NanNew(int(request->resource.kind)), v8::ReadOnly); + + NanReturnValue(instance); +} + +NAN_METHOD(NodeRequest::Respond) { + NanScope(); + + auto nodeRequest = ObjectWrap::Unwrap(args.Holder()); + if (!nodeRequest->request) { + return NanThrowError("Request has already been responded to, or was canceled."); + } + + auto source = ObjectWrap::Unwrap(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(); + + 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(); + 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 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 000000000..d07e93d42 --- /dev/null +++ b/src/node_request.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class NodeFileSource; +namespace mbgl { class Request; } + +namespace node_mbgl { + +class NodeRequest : public node::ObjectWrap { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle target); + static NAN_METHOD(New); + static NAN_METHOD(Respond); + + static v8::Handle Create(v8::Handle source, mbgl::Request *request); + + static v8::Persistent constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +public: + NodeRequest(v8::Persistent source, mbgl::Request *request); + ~NodeRequest(); + + void cancel(); + +private: + v8::Persistent source; + mbgl::Request *request = nullptr; +}; + +} diff --git a/src/thread_object.cpp b/src/thread_object.cpp new file mode 100644 index 000000000..ea2e83130 --- /dev/null +++ b/src/thread_object.cpp @@ -0,0 +1,85 @@ +#include "thread_object.hpp" +#include "node_file_source.hpp" + + + #include + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +v8::Persistent ThreadObject::constructorTemplate; + +void ThreadObject::Init(v8::Handle target) { + NanScope(); + + v8::Local t = NanNew(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(args[0]->ToObject()); + auto to = new ThreadObject(fs); + to->Wrap(args.This()); + + NanReturnValue(args.This()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +ThreadObject::ThreadObject(NodeFileSource *fs) { + self = v8::Persistent::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(as->data); + + to->self.Dispose(); + + uv_close((uv_handle_t *)as, [](uv_handle_t *handle) { + delete reinterpret_cast(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 000000000..3fbdb0400 --- /dev/null +++ b/src/thread_object.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include + +class NodeFileSource; + +class ThreadObject : public node::ObjectWrap { + //////////////////////////////////////////////////////////////////////////////////////////////// + // Static Node Methods +public: + static void Init(v8::Handle target); + static NAN_METHOD(New); + + static v8::Persistent constructorTemplate; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Instance +private: + ThreadObject(NodeFileSource *fs); + ~ThreadObject(); + +private: + std::thread thread; + v8::Persistent self; +}; diff --git a/test/file_source.test.js b/test/file_source.test.js new file mode 100644 index 000000000..1d7d86fe9 --- /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 000000000..e719e789e --- /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 index 000000000..da981e782 --- /dev/null +++ b/vendor/mbgl @@ -0,0 +1 @@ +Subproject commit da981e78253ab2b8715f4e10e175be4bea43c133 From c4c45a5ff869ab16206a1a7a784fde6a648d5ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 4 Feb 2015 18:28:02 +0100 Subject: [PATCH 002/760] don't force osx --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ef65d4cf4..d26f9f032 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ build: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi @node-gyp configure -- \ -Dmbgl=$(MBGL) \ - -Dhost=osx \ + -Dhost=$(HOST) \ -I$(MBGL)/config/$(HOST).gypi \ $(LIBS_$(HOST)) \ -Duv_static_libs= -Duv_ldflags= \ From 66ca191ab2ba4e75da5e25d848ee4c404ceb68e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 4 Feb 2015 20:09:02 +0100 Subject: [PATCH 003/760] add first render test --- .gitignore | 1 + Makefile | 4 ++ package.json | 1 + src/node_map_render_worker.cpp | 8 +-- test/fixtures/tiles.tilejson | 13 ++++ test/fixtures/tiles/0-0-0.vector.pbf | Bin 0 -> 9660 bytes test/map.test.js | 103 ++++++++++++++++++++------- vendor/mbgl | 2 +- 8 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 test/fixtures/tiles.tilejson create mode 100644 test/fixtures/tiles/0-0-0.vector.pbf diff --git a/.gitignore b/.gitignore index b8de80b58..c27d12d2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /build /lib /node_modules +/test/results \ No newline at end of file diff --git a/Makefile b/Makefile index d26f9f032..5d53a8b44 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ HOST ?= osx endif HOST ?= linux +# Explicitly disable the default FileSource implementation +ASSET = none +HTTP = none +CACHE = none include $(MBGL)/config/defaults.mk diff --git a/package.json b/package.json index c93a4cb8a..e55d38804 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "main": "lib/mbgl.node", "dependencies": { + "mkdirp": "^0.5.0", "mocha": "^2.1.0", "nan": "^1.4.1", "request": "^2.51.0" diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp index d11b5a009..d117a6c9a 100644 --- a/src/node_map_render_worker.cpp +++ b/src/node_map_render_worker.cpp @@ -16,7 +16,6 @@ NodeMap::RenderWorker::~RenderWorker() { } 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); @@ -28,12 +27,11 @@ void NodeMap::RenderWorker::Execute() { // 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) { + auto pixels = nodeMap->view.readPixels(); + image = mbgl::util::compress_png(width, height, pixels.get()); + } catch (mbgl::exception &ex) { SetErrorMessage(ex.what()); } } diff --git a/test/fixtures/tiles.tilejson b/test/fixtures/tiles.tilejson new file mode 100644 index 000000000..320c17500 --- /dev/null +++ b/test/fixtures/tiles.tilejson @@ -0,0 +1,13 @@ +{ + "bounds": [ -180, -85.0511, 180, 85.0511 ], + "center": [ 0, 0, 0 ], + "format": "pbf", + "id": "mapbox.mapbox-streets-v6-dev", + "maskLevel": 8, + "maxzoom": 15, + "minzoom": 0, + "name": "Mapbox Streets V6", + "scheme": "xyz", + "tilejson": "2.0.0", + "tiles": [ "./fixtures/tiles/{z}-{x}-{y}.vector.pbf" ] +} diff --git a/test/fixtures/tiles/0-0-0.vector.pbf b/test/fixtures/tiles/0-0-0.vector.pbf new file mode 100644 index 0000000000000000000000000000000000000000..87628e06cf9e686a28c9af98221e50a698e3b2a7 GIT binary patch literal 9660 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 HLE`@fxf*~; literal 0 HcmV?d00001 diff --git a/test/map.test.js b/test/map.test.js index e719e789e..dff28b003 100644 --- a/test/map.test.js +++ b/test/map.test.js @@ -4,6 +4,38 @@ var assert = require('assert'); var mbgl = require('..'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); + +mkdirp.sync('test/results'); + +var style = { + 'version': 7, + 'name': 'Empty', + 'sources': { + 'mapbox': { + 'type': 'vector', + 'url': './fixtures/tiles.tilejson', + 'maxzoom': 15 + } + }, + 'layers': [{ + 'id': 'background', + 'type': 'background', + 'paint': { + 'background-color': 'white' + } + }, { + 'id': 'water', + 'type': 'fill', + 'source': 'mapbox', + 'source-layer': 'water', + 'paint': { + 'fill-color': 'blue' + } + }] +}; describe('Map', function() { @@ -29,30 +61,30 @@ describe('Map', function() { }); it('should require the FileSource object to have request and cancel methods', function() { - var fs = new mbgl.FileSource(); + var fileSource = new mbgl.FileSource(); assert.throws(function() { - new mbgl.Map(fs); + new mbgl.Map(fileSource); }, /FileSource must have a request member function/); - fs.request = 'test'; + fileSource.request = 'test'; assert.throws(function() { - new mbgl.Map(fs); + new mbgl.Map(fileSource); }, /FileSource must have a request member function/); - fs.request = function() {}; + fileSource.request = function() {}; assert.throws(function() { - new mbgl.Map(fs); + new mbgl.Map(fileSource); }, /FileSource must have a cancel member function/); - fs.cancel = 'test'; + fileSource.cancel = 'test'; assert.throws(function() { - new mbgl.Map(fs); + new mbgl.Map(fileSource); }, /FileSource must have a cancel member function/); - fs.cancel = function() {}; + fileSource.cancel = function() {}; assert.doesNotThrow(function() { - new mbgl.Map(fs); + new mbgl.Map(fileSource); }); }); @@ -61,11 +93,12 @@ describe('Map', function() { describe('load styles', function() { var map; + var fileSource = new mbgl.FileSource(); + fileSource.request = function() {}; + fileSource.cancel = function() {}; + beforeEach(function() { - var fs = new mbgl.FileSource(); - fs.request = function() {}; - fs.cancel = function() {}; - map = new mbgl.Map(fs); + map = new mbgl.Map(fileSource); }); afterEach(function() { @@ -82,28 +115,40 @@ describe('Map', function() { }, /Expect either an object or array at root/); }); - it('accepts a stylesheet string', function() { + it('accepts an empty stylesheet string', function() { map.load('{}'); }); + it('accepts a JSON stylesheet', function() { + map.load(style); + }); + + it('accepts a stringified stylesheet', function() { + map.load(JSON.stringify(style)); + }); }); - - describe('render arguments', function() { + describe('render argument requirements', function() { var map; + var fileSource = new mbgl.FileSource(); + fileSource.request = function(req) { + fs.readFile(path.join('test', req.url), function(err, data) { + req.respond(err, { data: data }); + assert.ifError(err); + }); + }; + fileSource.cancel = function() {}; + beforeEach(function() { - var fs = new mbgl.FileSource(); - fs.request = function() {}; - fs.cancel = function() {}; - map = new mbgl.Map(fs); + map = new mbgl.Map(fileSource); }); afterEach(function() { map = null; }); - it('requires a string or object as the first parameter', function() { + it('requires an object as the first parameter', function() { assert.throws(function() { map.render(); }, /First argument must be an options object/); @@ -123,12 +168,22 @@ describe('Map', function() { }, /Second argument must be a callback function/); }); - it('requires a callback as the second parameter', function(done) { + it('requires a style to be set', function(done) { map.render({}, function(err) { - done(err); + assert.ok(err); + assert.equal(err.message, 'Style is not set'); + done(); }); }); + it('returns an image', function(done) { + map.load(style); + map.render({}, function(err, data) { + assert.ifError(err); + fs.writeFileSync('test/results/image.png', data); + done(); + }); + }); }); }); diff --git a/vendor/mbgl b/vendor/mbgl index da981e782..8422b8b8a 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit da981e78253ab2b8715f4e10e175be4bea43c133 +Subproject commit 8422b8b8a84d10f9c69c9295d1070eeb22d46db8 From 339ebca759e3662e76553a2e7451c9664371986b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 10:46:49 +0100 Subject: [PATCH 004/760] allow more arguments, but require at least one --- src/node_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 24672af66..8cf9ee9b6 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -68,7 +68,7 @@ const std::string StringifyStyle(v8::Handle styleHandle) { NAN_METHOD(NodeMap::Load) { NanScope(); - if (args.Length() != 1) { + if (args.Length() < 1) { return NanThrowError("Requires a map style as first argument"); } From fc4df7992f7099ec2fe3c4d1fd8dcd08af064764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 10:51:16 +0100 Subject: [PATCH 005/760] this has been implemented --- src/node_request.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/node_request.cpp b/src/node_request.cpp index 9d5b54388..415d27d82 100644 --- a/src/node_request.cpp +++ b/src/node_request.cpp @@ -43,8 +43,6 @@ NAN_METHOD(NodeRequest::New) { NanReturnValue(args.This()); } -// TODO: retain the NodeFileSource in a permanent handle! - v8::Handle NodeRequest::Create(v8::Handle source, mbgl::Request *request) { NanScope(); v8::Local argv[] = { v8::Local::New(source), NanNew(request) }; From 90fe724afd20c3c677e68250b2e493828b99e17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 10:58:11 +0100 Subject: [PATCH 006/760] use actual number of CPUs for building --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5d53a8b44..74e30e826 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,12 @@ endif ifeq ($(shell uname -s), Darwin) HOST ?= osx +JOBS = $(shell sysctl -n hw.ncpu) endif +ifeq ($(shell uname -s), Linux) HOST ?= linux +JOBS = $(shell nproc) +endif # Explicitly disable the default FileSource implementation ASSET = none @@ -22,7 +26,7 @@ global: build .PHONY: build build: build/Makefile - @node-gyp build $(DEBUG_FLAG) -- -j8 + @node-gyp build $(DEBUG_FLAG) -- -j$(JOBS) .PHONY: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi From 50461b18121759b4f8292039074a8f021bd51f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 11:02:20 +0100 Subject: [PATCH 007/760] move style out of test file into fixtures directory --- test/fixtures/style.json | 26 ++++++++++++++++++++++++++ test/map.test.js | 27 +-------------------------- 2 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 test/fixtures/style.json diff --git a/test/fixtures/style.json b/test/fixtures/style.json new file mode 100644 index 000000000..47a1990eb --- /dev/null +++ b/test/fixtures/style.json @@ -0,0 +1,26 @@ +{ + "version": 7, + "name": "Empty", + "sources": { + "mapbox": { + "type": "vector", + "url": "./fixtures/tiles.tilejson", + "maxzoom": 15 + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + }] +} diff --git a/test/map.test.js b/test/map.test.js index dff28b003..1d542c7b3 100644 --- a/test/map.test.js +++ b/test/map.test.js @@ -10,32 +10,7 @@ var mkdirp = require('mkdirp'); mkdirp.sync('test/results'); -var style = { - 'version': 7, - 'name': 'Empty', - 'sources': { - 'mapbox': { - 'type': 'vector', - 'url': './fixtures/tiles.tilejson', - 'maxzoom': 15 - } - }, - 'layers': [{ - 'id': 'background', - 'type': 'background', - 'paint': { - 'background-color': 'white' - } - }, { - 'id': 'water', - 'type': 'fill', - 'source': 'mapbox', - 'source-layer': 'water', - 'paint': { - 'fill-color': 'blue' - } - }] -}; +var style = require('./fixtures/style.json'); describe('Map', function() { From 6eb51bea4d8519515ef2e913b5bf0c867216ad05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 12:04:49 +0100 Subject: [PATCH 008/760] initialize the submodule on build --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 74e30e826..71586c843 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ global: build build: build/Makefile @node-gyp build $(DEBUG_FLAG) -- -j$(JOBS) +.PHONY: vendor/mbgl +vendor/mbgl: + git submodule update --init + .PHONY: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi @node-gyp configure -- \ @@ -38,7 +42,7 @@ build/Makefile: $(MBGL)/config/$(HOST).gypi -Duv_static_libs= -Duv_ldflags= \ -Goutput_dir=. -$(MBGL)/config/%.gypi: $(MBGL)/configure +$(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure make -C $(MBGL) config/$*.gypi .PHONY: test From d55b65d2c982383389c24ba9317627717527ecdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 12:05:04 +0100 Subject: [PATCH 009/760] always build before running tests to ensure freshness --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e55d38804..ff2566bb3 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "devDependencies": {}, "scripts": { "install": "make", - "test": "mocha -R spec test" + "test": "make test" }, "gypfile": true } From cc3ac49b57ec236f5c39b1f28fdb62589d81ebff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 12:05:23 +0100 Subject: [PATCH 010/760] add readme --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..ad9b46509 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +This module renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). + +# Loading the module + +To compile this module, make sure all submodules are initialized + +```js +var mbgl = require('mbgl'); +``` + +# Rendering a map tile + +```js +var map = new mbgl.Map(fileSource); +map.load(require('./test/fixtures/style.json')); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); +}); +``` + +# Implementing a file source + +When creating a `Map` object, you need to pass a `FileSource` object as the first parameter + +```js +var request = require('request'); + +var fileSource = new mbgl.FileSource(); +fileSource.request = function(req) { + // TODO +}; + +fileSource.cancel = function(req) { + // TODO +}; +``` + +The `request()` method starts a new request to a file, while `cancel()` tells your FileSource implementation to cancel the request (if possible). The `req` parameter has two properties: + +```json +{ + "url": "http://example.com", + "kind": 1 +} +``` + +The `kind` is an enum and defined in `mbgl.Resource`: + +```json +{ + "Unknown": 0, + "Tile": 1, + "Glyphs": 2, + "Image": 3, + "JSON": 4 +} +``` + +It has no significance for anything but serves as a hint to your implemention as to what sort of resource to expect. E.g., your implementation could choose caching strategies based on the expected file type. + +A sample implementation that reads files from disk would look like the following: + +```js +fileSource.request = function(req) { + fs.readFile(path.join('base/path', req.url), function(err, data) { + req.respond(err, { data: data }); + }); +}; +fileSource.cancel = function() {}; +``` + +This is a very barebones implementation and you'll probably want a better implementation. E.g. it passes the url verbatim to the file system, but you'd want add some logic that normalizes `http` URLs. You'll notice that once your implementation has obtained the requested file, you have to deliver it to the requestee by calling `req.respond()`, which takes either an error object or `null` and an object with several settings: + +```js +{ + modified: new Date(), + expires: new Date(), + etag: "string", + data: new Buffer(), +}; +``` + +A sample implementation that uses [`request`](https://github.com/request/request) to query data from HTTP: + +```js +var mbgl = require('./'); +var request = require('request'); + +var fileSource = new mbgl.FileSource(); +fileSource.request = function(req) { + request({ + url: req.url, + encoding: null + }, function (err, res, body) { + if (req.canceled) { + return; + } + + if (err) { + req.respond(err); + } else if (res.statusCode == 200) { + var response = {}; + + if (res.headers.modified) { response.modified = new Date(res.headers.modified); } + if (res.headers.expires) { response.expires = new Date(res.headers.expires); } + if (res.headers.etag) { response.etag = res.headers.etag; } + response.data = body; + req.respond(null, response); + } else { + console.warn(new Date(res.headers.expires)); + } + }); +}; + +fileSource.cancel = function(req) { + req.canceled = true; +}; +``` + +Note that in reality, Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of FileSource supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3. From f8a7a4425d1c97e2ff5dce554d390698bc0e389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 6 Feb 2015 12:12:51 +0100 Subject: [PATCH 011/760] add example script --- examples/load-from-fs.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/load-from-fs.js diff --git a/examples/load-from-fs.js b/examples/load-from-fs.js new file mode 100644 index 000000000..1d8ef30ed --- /dev/null +++ b/examples/load-from-fs.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node +/* jshint node: true, unused: false */ +'use strict'; + +var mbgl = require('..'); +var fs = require('fs'); +var path = require('path'); + +var base = path.join(path.dirname(process.mainModule.filename), '../test'); + +var fileSource = new mbgl.FileSource(); +fileSource.request = function(req) { + fs.readFile(path.join(base, req.url), function(err, data) { + req.respond(err, { data: data }); + }); +}; + +fileSource.cancel = function(req) { +}; + +var map = new mbgl.Map(fileSource); +map.load(require('../test/fixtures/style.json')); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); + console.warn('Written image.png'); +}); From df396d7d89bc986ad16620a18b2025c3d20b41c4 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 5 Feb 2015 17:19:26 -0500 Subject: [PATCH 012/760] get tests passing on travis add iam template with mapbox-gl-testing and mapbox-node-binary permissions add travis build scripts and .travis.yml rm -f platform gypi map.resize is private now, called by view.resize set stdlib and c++11 per platform, link glfw3 ldflags on linux pass --clang to node-gyp per https://github.com/mapbox/mapbox-gl-native/pull/364#issuecomment-51735692 --- .gitmodules | 2 +- .travis.yml | 42 ++++++++++++ Makefile | 10 ++- binding.gyp | 15 +++-- scripts/deploy_results.sh | 8 +++ scripts/install_nvm.sh | 11 ++++ scripts/travis_publish.sh | 31 +++++++++ scripts/travis_test.sh | 10 +++ src/node_map_render_worker.cpp | 1 - travis/travis-resources.template | 108 +++++++++++++++++++++++++++++++ vendor/mbgl | 2 +- 11 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 .travis.yml create mode 100755 scripts/deploy_results.sh create mode 100755 scripts/install_nvm.sh create mode 100755 scripts/travis_publish.sh create mode 100755 scripts/travis_test.sh create mode 100644 travis/travis-resources.template diff --git a/.gitmodules b/.gitmodules index 863c7f66f..ac1fc38e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vendor/mbgl"] path = vendor/mbgl - url = https://github.com/mapbox/mapbox-gl-native.git + url = git@github.com:mapbox/mapbox-gl-native.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..b1cbefbf3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: cpp + +matrix: + include: + - os: linux + compiler: clang + - os: linux + compiler: gcc + env: JOBS=8 + - os: osx + compiler: clang + +env: + global: + - NODE_NVM_VERSION: "0.10" + - LD_LIBRARY_PATH: "/usr/local/lib" + - secure: jkwZnKr5dcM9MQ6mOytnp6FHkbydSfa1L8d4jFp4v4K6Ee/iQh1YIq9c/J1zC0WLyuGokVGaijkKr6CW4nGZ5H8ygBtPo09imnRyH8X0bO5mEjl+KhbpdFqkXVLerpnAGMO7D6+6BtWJlK5uTDWhpeS7YayB3YfETVkllf4n6F8= + - secure: ZBiq+KPVM72MF0rtvpui5l7curGXckz4Pefc1L04U6dd/MPa22StvCgXBG7DKhLwI5OgHE1da769asQ3rub/pDOonxAi7fHo3+r/ocbSBZeeaj+p5i5wavppOzD27gCLrpOnSQBk0DfoD+xOM8ZCfIttBbOBMRZ1dpWFG98bJ4c= + +before_install: +- ./scripts/install_nvm.sh +- cd ./vendor/mbgl && source ./scripts/local_mason.sh && cd ../../ +- source ./vendor/mbgl/scripts/travis_helper.sh +- source ./vendor/mbgl/scripts/flags.sh +- source ./vendor/mbgl/scripts/travis_before_install.sh +- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; glxinfo; fi + +install: +- npm install + +before_script: +# Set the core file limit to unlimited so a core file is generated upon crash +- ulimit -c unlimited -S + +script: +- ./scripts/travis_test.sh + +after_success: +- ./scripts/travis_publish.sh + +git: + submodules: true diff --git a/Makefile b/Makefile index 71586c843..b1756f017 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,16 @@ endif ifeq ($(shell uname -s), Darwin) HOST ?= osx +ifeq ($(JOBS),) JOBS = $(shell sysctl -n hw.ncpu) endif +endif ifeq ($(shell uname -s), Linux) HOST ?= linux +ifeq ($(JOBS),) JOBS = $(shell nproc) endif +endif # Explicitly disable the default FileSource implementation ASSET = none @@ -26,7 +30,7 @@ global: build .PHONY: build build: build/Makefile - @node-gyp build $(DEBUG_FLAG) -- -j$(JOBS) + @node-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS) .PHONY: vendor/mbgl vendor/mbgl: @@ -34,7 +38,7 @@ vendor/mbgl: .PHONY: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi - @node-gyp configure -- \ + @node-gyp configure --clang -- \ -Dmbgl=$(MBGL) \ -Dhost=$(HOST) \ -I$(MBGL)/config/$(HOST).gypi \ @@ -52,4 +56,4 @@ test: build .PHONY: clean clean: rm -rf build - rm $(MBGL)/config.gypi + rm -f $(MBGL)/config/$(HOST).gypi diff --git a/binding.gyp b/binding.gyp index 0d4922fad..fb1839390 100644 --- a/binding.gyp +++ b/binding.gyp @@ -23,24 +23,27 @@ 'variables': { 'cflags_cc': [ - '-std=c++11', - '-stdlib=libc++', - '-Wno-unused-parameter' + '-Wno-unused-parameter', ], }, 'conditions': [ ['OS == "mac"', { 'xcode_settings': { + "CLANG_CXX_LIBRARY": "libc++", + "CLANG_CXX_LANGUAGE_STANDARD":"c++11", + "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0", 'MACOSX_DEPLOYMENT_TARGET': '10.7', 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], - 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', } }, { 'cflags_cc': [ - '<@(cflags_cc)', + '-std=c++11', '-fexceptions', + '<@(cflags_cc)', ], + 'libraries': [ '<@(glfw3_ldflags)' ], }] ], }, @@ -56,4 +59,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/scripts/deploy_results.sh b/scripts/deploy_results.sh new file mode 100755 index 000000000..6a5bbd0ff --- /dev/null +++ b/scripts/deploy_results.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then + (cd ./node_modules/mapbox-gl-test-suite/ && ./bin/deploy_results.sh) +fi diff --git a/scripts/install_nvm.sh b/scripts/install_nvm.sh new file mode 100755 index 000000000..1f68af710 --- /dev/null +++ b/scripts/install_nvm.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +git clone git@github.com:creationix/nvm.git ../.nvm +source ../.nvm/nvm.sh +nvm install $NODE_NVM_VERSION +nvm use $NODE_NVM_VERSION +node --version +npm --version diff --git a/scripts/travis_publish.sh b/scripts/travis_publish.sh new file mode 100755 index 000000000..17f4b1f17 --- /dev/null +++ b/scripts/travis_publish.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +# Inspect binary. +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + ldd ./lib/mbgl.node +else + otool -L ./lib/mbgl.node +fi + +COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') + +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX == "clang++" ]]; then + npm install aws-sdk + + ./node_modules/.bin/node-pre-gyp package + + if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + ./node_modules/.bin/node-pre-gyp testpackage + fi + + ./node_modules/.bin/node-pre-gyp publish info + + if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + rm -rf build + rm -rf lib + npm install --fallback-to-build=false + npm test + fi +fi diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh new file mode 100755 index 000000000..707293505 --- /dev/null +++ b/scripts/travis_test.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +# Travis OS X has no GPU +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + npm test + # ./scripts/deploy_results.sh +fi diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp index d117a6c9a..6079e8a8c 100644 --- a/src/node_map_render_worker.cpp +++ b/src/node_map_render_worker.cpp @@ -19,7 +19,6 @@ void NodeMap::RenderWorker::Execute() { 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); diff --git a/travis/travis-resources.template b/travis/travis-resources.template new file mode 100644 index 000000000..8f07f1cf2 --- /dev/null +++ b/travis/travis-resources.template @@ -0,0 +1,108 @@ + +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "node-mapbox-gl-native travis resources", + "Resources": { + "BuildUser": { + "Type": "AWS::IAM::User", + "Properties": { + "Policies": [ + { + "PolicyName": "build", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": [ + "arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*" + ], + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "build-testing", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::mapbox-gl-testing/*" + ] + } + ] + } + }, + "PolicyName": "list", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::mapbox-node-binary" + ], + "Effect": "Allow" + } + ] + } + { + "PolicyName": "list-testing", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::mapbox-gl-testing" + ] + } + ] + } + } + ] + } + }, + "BuildUserKey": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": { + "Ref": "BuildUser" + } + } + } + }, + "Outputs": { + "AccessKeyId": { + "Value": { + "Ref": "BuildUserKey" + } + }, + "SecretAccessKey": { + "Value": { + "Fn::GetAtt": [ + "BuildUserKey", + "SecretAccessKey" + ] + } + } + } +} + diff --git a/vendor/mbgl b/vendor/mbgl index 8422b8b8a..eac5f6f45 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 8422b8b8a84d10f9c69c9295d1070eeb22d46db8 +Subproject commit eac5f6f4540d8adb29230ef07ac46a3306abb421 From 83b32ec1db3dce493d79303bae9289b8e652f01b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 10 Feb 2015 17:07:49 -0800 Subject: [PATCH 013/760] move tests from mocha to tape --- Makefile | 2 +- package.json | 10 +-- test/file_source.test.js | 16 ++-- test/map.test.js | 185 ++++++++++++++++++++++----------------- 4 files changed, 119 insertions(+), 94 deletions(-) diff --git a/Makefile b/Makefile index b1756f017..4f83d2867 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test test: build - @`npm bin`/mocha -R spec + @`npm bin`/tape test/**/*.test.js .PHONY: clean clean: diff --git a/package.json b/package.json index ff2566bb3..0f53fe51e 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,12 @@ "version": "0.0.1", "main": "lib/mbgl.node", "dependencies": { - "mkdirp": "^0.5.0", - "mocha": "^2.1.0", - "nan": "^1.4.1", - "request": "^2.51.0" + "nan": "^1.4.1" + }, + "devDependencies": { + "mkdirp": "^0.5.0", + "tape": "^3.5.0" }, - "devDependencies": {}, "scripts": { "install": "make", "test": "make test" diff --git a/test/file_source.test.js b/test/file_source.test.js index 1d7d86fe9..90a91de97 100644 --- a/test/file_source.test.js +++ b/test/file_source.test.js @@ -1,19 +1,21 @@ /* jshint node: true, unused: false */ -/* global describe, it */ 'use strict'; -var assert = require('assert'); + +var test = require('tape').test; var mbgl = require('..'); -describe('FileSource', function() { - - describe('constructor', function() { - it('must be called with new', function() { - assert.throws(function() { +test('FileSource', function(t) { + t.test('constructor', function(t) { + t.test('must be called with new', function(t) { + t.throws(function() { mbgl.FileSource(); }, /Use the new operator to create new FileSource objects/); + t.end(); }); + t.end(); }); + t.end(); }); diff --git a/test/map.test.js b/test/map.test.js index 1d542c7b3..c61263694 100644 --- a/test/map.test.js +++ b/test/map.test.js @@ -1,7 +1,7 @@ /* jshint node: true, unused: false */ -/* global describe, it, beforeEach, afterEach */ 'use strict'; -var assert = require('assert'); + +var test = require('tape').test; var mbgl = require('..'); var fs = require('fs'); @@ -12,153 +12,176 @@ mkdirp.sync('test/results'); var style = require('./fixtures/style.json'); -describe('Map', function() { +function setup(fileSource, callback) { + callback(new mbgl.Map(fileSource)); +} - describe('constructor', function() { - it('must be called with new', function() { - assert.throws(function() { +test('Map', function(t) { + t.test('constructor', function(t) { + t.test('must be called with new', function(t) { + t.throws(function() { mbgl.Map(); }, /Use the new operator to create new Map objects/); + + t.end(); }); - it('should require a FileSource object as first parameter', function() { - assert.throws(function() { + t.test('should require a FileSource object as first parameter', function(t) { + t.throws(function() { new mbgl.Map(); }, /Requires a FileSource as first argument/); - assert.throws(function() { + t.throws(function() { new mbgl.Map('fileSource'); }, /Requires a FileSource as first argument/); - assert.throws(function() { + t.throws(function() { new mbgl.Map({}); }, /Requires a FileSource as first argument/); + + t.end(); }); - it('should require the FileSource object to have request and cancel methods', function() { + t.test('should require the FileSource object to have request and cancel methods', function(t) { var fileSource = new mbgl.FileSource(); - assert.throws(function() { + t.throws(function() { new mbgl.Map(fileSource); }, /FileSource must have a request member function/); fileSource.request = 'test'; - assert.throws(function() { + t.throws(function() { new mbgl.Map(fileSource); }, /FileSource must have a request member function/); fileSource.request = function() {}; - assert.throws(function() { + t.throws(function() { new mbgl.Map(fileSource); }, /FileSource must have a cancel member function/); fileSource.cancel = 'test'; - assert.throws(function() { + t.throws(function() { new mbgl.Map(fileSource); }, /FileSource must have a cancel member function/); fileSource.cancel = function() {}; - assert.doesNotThrow(function() { + t.doesNotThrow(function() { new mbgl.Map(fileSource); }); + + t.end(); }); + t.end(); }); - describe('load styles', function() { - var map; - + t.test('load styles', function(t) { var fileSource = new mbgl.FileSource(); fileSource.request = function() {}; fileSource.cancel = function() {}; - beforeEach(function() { - map = new mbgl.Map(fileSource); + t.test('requires a string or object as the first parameter', function(t) { + setup(fileSource, function(map) { + t.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + + t.throws(function() { + map.load('invalid'); + }, /Expect either an object or array at root/); + t.end(); + }); }); - afterEach(function() { - map = null; + t.test('accepts an empty stylesheet string', function(t) { + setup(fileSource, function(map) { + t.doesNotThrow(function() { + map.load('{}'); + }); + t.end(); + }); }); - 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/); + t.test('accepts a JSON stylesheet', function(t) { + setup(fileSource, function(map) { + t.doesNotThrow(function() { + map.load(style); + }); + t.end(); + }); }); - it('accepts an empty stylesheet string', function() { - map.load('{}'); + t.test('accepts a stringified stylesheet', function(t) { + setup(fileSource, function(map) { + t.doesNotThrow(function() { + map.load(JSON.stringify(style)); + }); + t.end(); + }); }); - it('accepts a JSON stylesheet', function() { - map.load(style); - }); - - it('accepts a stringified stylesheet', function() { - map.load(JSON.stringify(style)); - }); + t.end(); }); - describe('render argument requirements', function() { - var map; - + t.test('render argument requirements', function(t) { var fileSource = new mbgl.FileSource(); fileSource.request = function(req) { fs.readFile(path.join('test', req.url), function(err, data) { req.respond(err, { data: data }); - assert.ifError(err); + t.error(err); }); }; fileSource.cancel = function() {}; - beforeEach(function() { - map = new mbgl.Map(fileSource); - }); + t.test('requires an object as the first parameter', function(t) { + setup(fileSource, function(map) { + t.throws(function() { + map.render(); + }, /First argument must be an options object/); - afterEach(function() { - map = null; - }); + t.throws(function() { + map.render('invalid'); + }, /First argument must be an options object/); - it('requires an 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 style to be set', function(done) { - map.render({}, function(err) { - assert.ok(err); - assert.equal(err.message, 'Style is not set'); - done(); + t.end(); }); }); - it('returns an image', function(done) { - map.load(style); - map.render({}, function(err, data) { - assert.ifError(err); - fs.writeFileSync('test/results/image.png', data); - done(); + t.test('requires a callback as the second parameter', function(t) { + setup(fileSource, function(map) { + t.throws(function() { + map.render({}); + }, /Second argument must be a callback function/); + + t.throws(function() { + map.render({}, 'invalid'); + }, /Second argument must be a callback function/); + + t.end(); + }); + }); + + t.test('requires a style to be set', function(t) { + setup(fileSource, function(map) { + map.render({}, function(err) { + t.ok(err); + t.equal(err.message, 'Style is not set'); + t.end(); + }); + }); + }); + + t.test('returns an image', function(t) { + setup(fileSource, function(map) { + map.load(style); + map.render({}, function(err, data) { + t.error(err); + fs.writeFileSync('test/results/image.png', data); + t.end(); + }); }); }); }); + t.end(); }); From b87cff289c1b65757ab0d652f09a5a12470c9cc2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 10 Feb 2015 19:18:32 -0800 Subject: [PATCH 014/760] add mapbox-gl-test-suite render tests --- Makefile | 11 +- package.json | 2 + scripts/travis_test.sh | 2 +- test/{ => js}/file_source.test.js | 6 +- test/{ => js}/map.test.js | 8 +- test/render.test.js | 161 ++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 10 deletions(-) rename test/{ => js}/file_source.test.js (78%) rename test/{ => js}/map.test.js (97%) create mode 100644 test/render.test.js diff --git a/Makefile b/Makefile index 4f83d2867..3df03e09d 100644 --- a/Makefile +++ b/Makefile @@ -49,9 +49,16 @@ build/Makefile: $(MBGL)/config/$(HOST).gypi $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure make -C $(MBGL) config/$*.gypi +.PHONY: test-suite +test-suite: build + @(`npm bin`/tape test/render.test.js || true) + +.PHONY: test-js +test-js: build + @`npm bin`/tape test/js/**/*.test.js + .PHONY: test -test: build - @`npm bin`/tape test/**/*.test.js +test: test-js test-suite .PHONY: clean clean: diff --git a/package.json b/package.json index 0f53fe51e..be6604529 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "nan": "^1.4.1" }, "devDependencies": { + "aws-sdk": "^2.1.9", + "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite", "mkdirp": "^0.5.0", "tape": "^3.5.0" }, diff --git a/scripts/travis_test.sh b/scripts/travis_test.sh index 707293505..70fff88aa 100755 --- a/scripts/travis_test.sh +++ b/scripts/travis_test.sh @@ -6,5 +6,5 @@ set -o pipefail # Travis OS X has no GPU if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then npm test - # ./scripts/deploy_results.sh + ./scripts/deploy_results.sh fi diff --git a/test/file_source.test.js b/test/js/file_source.test.js similarity index 78% rename from test/file_source.test.js rename to test/js/file_source.test.js index 90a91de97..72f2180f2 100644 --- a/test/file_source.test.js +++ b/test/js/file_source.test.js @@ -1,9 +1,9 @@ -/* jshint node: true, unused: false */ 'use strict'; -var test = require('tape').test; +/* jshint node: true */ -var mbgl = require('..'); +var test = require('tape'); +var mbgl = require('../..'); test('FileSource', function(t) { t.test('constructor', function(t) { diff --git a/test/map.test.js b/test/js/map.test.js similarity index 97% rename from test/map.test.js rename to test/js/map.test.js index c61263694..97daa8d52 100644 --- a/test/map.test.js +++ b/test/js/map.test.js @@ -1,16 +1,16 @@ -/* jshint node: true, unused: false */ 'use strict'; -var test = require('tape').test; +/* jshint node: true */ -var mbgl = require('..'); +var test = require('tape'); +var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); mkdirp.sync('test/results'); -var style = require('./fixtures/style.json'); +var style = require('../fixtures/style.json'); function setup(fileSource, callback) { callback(new mbgl.Map(fileSource)); diff --git a/test/render.test.js b/test/render.test.js new file mode 100644 index 000000000..1329b7ef5 --- /dev/null +++ b/test/render.test.js @@ -0,0 +1,161 @@ +'use strict'; + +/* jshint node:true */ + +var test = require('tape'); +var mbgl = require('..'); +var fs = require('fs'); +var path = require('path'); +var mkdirp = require('mkdirp'); +var spawn = require('child_process').spawn; + +var suitePath = path.dirname(require.resolve('mapbox-gl-test-suite/package.json')); + +function template(name) { + return fs.readFileSync(require.resolve('mapbox-gl-test-suite/templates/' + name + '.html.tmpl')).toString(); +} + +var results = ''; +var resultTemplate = template('result'); + +function format(tmpl, kwargs) { + return tmpl.replace(/\{\{|\}\}|\{([^}]+)\}/g, function(match, key) { + if (match === '{{') return '{'; + if (match === '}}') return '}'; + return kwargs[key]; + }); +} + +function renderTest(style, info, base, key) { + var dir = path.join(suitePath, 'tests', base, key); + return function(t) { + var watchdog = setTimeout(function() { + t.fail('timed out after 20 seconds'); + }, 20000); + + t.once('end', function() { + clearTimeout(watchdog); + }); + + var fileSource = new mbgl.FileSource(); + fileSource.request = function(req) { + fs.readFile(path.join(suitePath, decodeURIComponent(req.url)), function(err, data) { + req.respond(err, { data: data }); + t.error(err); + }); + }; + fileSource.cancel = function() {}; + + var map = new mbgl.Map(fileSource); + map.load(style); + + map.render(info[key], function(err, image) { + t.error(err); + mkdirp.sync(dir); + + var expected = path.join(dir, 'expected.png'); + var actual = path.join(dir, 'actual.png'); + var diff = path.join(dir, 'diff.png'); + + if (process.env.UPDATE) { + fs.writeFile(expected, image, function(err) { + t.error(err); + t.end(); + }); + } else { + fs.writeFile(actual, image, function(err) { + t.error(err); + + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; + + compare.stderr.on('data', function (data) { + error += data.toString(); + }); + + compare.on('exit', function (code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + writeResult(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + writeResult(match ? '' : error, difference); + } + }); + + compare.stdin.end(); + + function writeResult(error, difference) { + var allowedDifference = ('diff' in info) ? info.diff : 0.001; + var color = difference <= allowedDifference ? 'green' : 'red'; + + results += format(resultTemplate, { + name: base, + key: key, + color: color, + error: error ? '

' + error + '

' : '', + difference: difference, + zoom: info.zoom || 0, + center: info.center || [0, 0], + bearing: info.bearing || 0, + width: info.width || 512, + height: info.height || 512 + }); + + t.ok(difference <= allowedDifference); + t.end(); + } + }); + } + }); + }; +} + +function rewriteLocalSchema(uri) { + return uri.replace(/^local:\/\//, ''); +} + +var tests; + +if (process.argv[1] === __filename) { + tests = process.argv.slice(2); +} + +test('Render', function(t) { + fs.readdirSync(path.join(suitePath, 'tests')).forEach(function(dir) { + if (dir === 'index.html' || dir[0] === '.') return; + if (tests && tests.length && tests.indexOf(dir) < 0) return; + + var style = require(path.join(suitePath, 'tests', dir, 'style.json')), + info = require(path.join(suitePath, 'tests', dir, 'info.json')); + + for (var k in style.sources) { + var source = style.sources[k]; + + if (source.tiles) { + source.tiles = source.tiles.map(rewriteLocalSchema); + } + + if (source.url) { + source.url = rewriteLocalSchema(source.url); + } + } + + if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite); + if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs); + + style = JSON.stringify(style); + + for (k in info) { + (info[k].native === false ? t.skip : t.test)(dir + ' ' + k, renderTest(style, info, dir, k)); + } + }); + + t.test('results', function(t) { + var p = path.join(suitePath, 'tests', 'index.html'); + fs.writeFileSync(p, format(template('results'), {results: results})); + console.warn('Results at: ' + p); + t.end(); + }); +}); From 2e7dc629565886a30304bebf374a8fd39f7076e2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 13:34:02 -0800 Subject: [PATCH 015/760] actual matches expected --- test/render.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/render.test.js b/test/render.test.js index 1329b7ef5..17f9bd228 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -103,7 +103,7 @@ function renderTest(style, info, base, key) { height: info.height || 512 }); - t.ok(difference <= allowedDifference); + t.ok(difference <= allowedDifference, 'actual matches expected'); t.end(); } }); From 2c5bd218de7b62a49fa44597d6dc24726472b930 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 13:49:51 -0800 Subject: [PATCH 016/760] add concurrency test --- test/js/concurrency.test.js | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/js/concurrency.test.js diff --git a/test/js/concurrency.test.js b/test/js/concurrency.test.js new file mode 100644 index 000000000..6a2ee32ca --- /dev/null +++ b/test/js/concurrency.test.js @@ -0,0 +1,74 @@ +'use strict'; + +/* jshint node:true */ + +var test = require('tape'); +var mbgl = require('../..'); +var fs = require('fs'); +var path = require('path'); + +var suitePath = path.dirname(require.resolve('mapbox-gl-test-suite/package.json')); + + +function renderTest(style, info, dir, key) { + return function (t) { + var completed = 0; + var remaining = 10; + var start = +new Date; + + var fileSource = new mbgl.FileSource(); + fileSource.request = function(req) { + fs.readFile(path.join(suitePath, decodeURIComponent(req.url)), function(err, data) { + req.respond(err, { data: data }); + t.error(err); + }); + }; + fileSource.cancel = function() {}; + + var map = new mbgl.Map(fileSource); + map.load(style); + + for (var i = 0; i < remaining; i++) { + map.render(info[key], function(err, image) { + t.error(err); + t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms'); + if (++completed === remaining) t.end(); + }); + } + }; +} + +function rewriteLocalSchema(uri) { + return uri.replace(/^local:\/\//, ''); +} + +test('Concurrency', function(t) { + var dir = 'line-join'; + var k = 'round'; + + var style = require(path.join(suitePath, 'tests', dir, 'style.json')), + info = require(path.join(suitePath, 'tests', dir, 'info.json')); + + for (var k in style.sources) { + var source = style.sources[k]; + + if (source.tiles) { + source.tiles = source.tiles.map(rewriteLocalSchema); + } + + if (source.url) { + source.url = rewriteLocalSchema(source.url); + } + } + + if (style.sprite) style.sprite = rewriteLocalSchema(style.sprite); + if (style.glyphs) style.glyphs = rewriteLocalSchema(style.glyphs); + + style = JSON.stringify(style); + + for (k in info) { + t.test(dir + ' ' + k, renderTest(style, info, dir, k)); + } + + t.end(); +}); From 36e25f389691761b2b5de44d12cfaa49a928e92b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 14:04:44 -0800 Subject: [PATCH 017/760] add bsd license [skip ci] --- LICENSE.txt | 27 +++++++++++++++++++++++++++ package.json | 14 ++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..c9018a139 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2014, Mapbox +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of [project] nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/package.json b/package.json index ff2566bb3..7781e1b82 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,21 @@ { "name": "mbgl", "version": "0.0.1", + "keywords": [ + "mapbox", + "gl" + ], "main": "lib/mbgl.node", + "repository": { + "type": "git", + "url": "git://github.com/mapbox/node-mapbox-gl-native.git" + }, + "licenses": [ + { + "type": "BSD", + "url": "https://github.com/mapbox/node-mapbox-gl-native/blob/master/LICENSE.txt" + } + ], "dependencies": { "mkdirp": "^0.5.0", "mocha": "^2.1.0", From d27879a9d4910844148fe7e2370a2b7f0c0fbaee Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 14:25:34 -0800 Subject: [PATCH 018/760] add CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..166929e33 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Please run [`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) and verify that code formatting will remain consistent before submitting a pull request. From 4f283a00dc7a8c3ea9ff98d77e0324a657a678dc Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 14:34:18 -0800 Subject: [PATCH 019/760] update README --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad9b46509..1b274f946 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -This module renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). +# node-mapbox-gl-native -# Loading the module +Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). + +[![Build Status](https://api.travis-ci.com/mapbox/node-mapbox-gl-native.svg?token=Phdq58g7NsfstW6gyeYW)](https://magnum.travis-ci.com/mapbox/node-mapbox-gl-native) + +## Loading the module To compile this module, make sure all submodules are initialized @@ -8,7 +12,7 @@ To compile this module, make sure all submodules are initialized var mbgl = require('mbgl'); ``` -# Rendering a map tile +## Rendering a map tile ```js var map = new mbgl.Map(fileSource); @@ -19,7 +23,13 @@ map.render({}, function(err, image) { }); ``` -# Implementing a file source +## Testing + +``` +npm test +``` + +## Implementing a file source When creating a `Map` object, you need to pass a `FileSource` object as the first parameter From 8b3ce58da0d7378ae0637d5e7ea83fa7ea8c0efc Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 15:16:57 -0800 Subject: [PATCH 020/760] add description to package.json [skip ci] --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 0618acb10..01e499e43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "mbgl", "version": "0.0.1", + "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", "gl" From eb5314d20975437b10f6a1df91893165df9de41e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Feb 2015 15:19:16 -0800 Subject: [PATCH 021/760] prefix test suite with - to allow failures --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3df03e09d..035f1e381 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: build - @(`npm bin`/tape test/render.test.js || true) + -@`npm bin`/tape test/render.test.js .PHONY: test-js test-js: build From 8c6f71fdb26a4d7a4e2743d3661ff53adbf1658b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 11 Feb 2015 19:35:11 -0800 Subject: [PATCH 022/760] adapt to static-render branch changes [skip ci] still missing updated tests --- .clang-format | 2 +- Makefile | 1 - binding.gyp | 4 +- src/compress_png.cpp | 108 +++++++++++++++++++++ src/compress_png.hpp | 8 ++ src/mbgl.cpp | 2 + src/node_map.cpp | 168 ++++++++++++++++++++++++++++----- src/node_map.hpp | 14 ++- src/node_map_render_worker.cpp | 56 ----------- src/node_map_render_worker.hpp | 39 -------- vendor/mbgl | 2 +- 11 files changed, 275 insertions(+), 129 deletions(-) create mode 100644 src/compress_png.cpp create mode 100644 src/compress_png.hpp delete mode 100644 src/node_map_render_worker.cpp delete mode 100644 src/node_map_render_worker.hpp diff --git a/.clang-format b/.clang-format index 86d562e74..109b562b5 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Standard: Cpp11 IndentWidth: 4 AccessModifierOffset: -4 UseTab: Never -BinPackParameters: false +BinPackParameters: true AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortBlocksOnASingleLine: false diff --git a/Makefile b/Makefile index 035f1e381..97b16cfea 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,6 @@ global: build build: build/Makefile @node-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS) -.PHONY: vendor/mbgl vendor/mbgl: git submodule update --init diff --git a/binding.gyp b/binding.gyp index fb1839390..dc030205b 100644 --- a/binding.gyp +++ b/binding.gyp @@ -11,12 +11,12 @@ 'sources': [ 'src/mbgl.cpp', + 'src/compress_png.hpp', + 'src/compress_png.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', ], diff --git a/src/compress_png.cpp b/src/compress_png.cpp new file mode 100644 index 000000000..699df5346 --- /dev/null +++ b/src/compress_png.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include + +namespace node_mbgl { + +class CompressPNGWorker : public NanAsyncWorker { +public: + CompressPNGWorker(NanCallback *callback, v8::Local buffer_, uint32_t width_, + uint32_t height_) + : NanAsyncWorker(callback), + buffer(v8::Persistent::New(buffer_)), + data(node::Buffer::Data(buffer_)), + width(width_), + height(height_) { + assert(width * height * 4 == node::Buffer::Length(buffer_)); + } + + ~CompressPNGWorker() { + buffer.Dispose(); + } + + void Execute() { + result = mbgl::util::compress_png(width, height, data); + } + + void HandleOKCallback() { + NanScope(); + + auto img = new std::string(std::move(result)); + + v8::Local argv[] = { + NanNull(), + NanNewBufferHandle( + const_cast(img->data()), + img->size(), + // Retain the std::string until the buffer is deleted. + [](char *, void *hint) { + delete reinterpret_cast(hint); + }, + img + ) + }; + + callback->Call(2, argv); + }; + +private: + // Retains the buffer while this worker is processing. The user may not modify the buffer. + v8::Persistent buffer; + void *data; + const uint32_t width; + const uint32_t height; + + // Stores the compressed PNG. + std::string result; +}; + +NAN_METHOD(CompressPNG) { + NanScope(); + + if (args.Length() <= 0 || !args[0]->IsObject()) { + return NanThrowTypeError("First argument must be the data object"); + } + + uint32_t width = 0; + uint32_t height = 0; + v8::Local buffer; + + auto options = args[0]->ToObject(); + if (options->Has(NanNew("width"))) { + width = options->Get(NanNew("width"))->Uint32Value(); + } + if (!width) { + NanThrowRangeError("Image width must be greater than 0"); + } + if (options->Has(NanNew("height"))) { + height = options->Get(NanNew("height"))->Uint32Value(); + } + if (!height) { + NanThrowRangeError("Image height must be greater than 0"); + } + if (options->Has(NanNew("pixels"))) { + buffer = options->Get(NanNew("pixels")).As(); + } + if (!node::Buffer::HasInstance(buffer)) { + NanThrowTypeError("Pixels must be a Buffer object"); + } + if (width * height * 4 != node::Buffer::Length(buffer)) { + NanThrowError("Pixel buffer doesn't match image dimensions"); + } + + if (args.Length() < 2) { + NanThrowTypeError("Second argument must be a callback function"); + } + NanCallback *callback = new NanCallback(args[1].As()); + + NanAsyncQueueWorker(new CompressPNGWorker(callback, buffer, width, height)); + NanReturnUndefined(); +} + +void InitCompressPNG(v8::Handle target) { + target->Set(NanNew("compressPNG"), + NanNew(CompressPNG)->GetFunction()); +} + +} diff --git a/src/compress_png.hpp b/src/compress_png.hpp new file mode 100644 index 000000000..754b383e7 --- /dev/null +++ b/src/compress_png.hpp @@ -0,0 +1,8 @@ +#include +#include + +namespace node_mbgl { + +void InitCompressPNG(v8::Handle target); + +} \ No newline at end of file diff --git a/src/mbgl.cpp b/src/mbgl.cpp index 677e72b2d..b218382ab 100644 --- a/src/mbgl.cpp +++ b/src/mbgl.cpp @@ -4,6 +4,7 @@ #include "node_file_source.hpp" #include "node_map.hpp" #include "node_request.hpp" +#include "compress_png.hpp" void RegisterModule(v8::Handle exports) { NanScope(); @@ -11,6 +12,7 @@ void RegisterModule(v8::Handle exports) { node_mbgl::NodeFileSource::Init(exports); node_mbgl::NodeMap::Init(exports); node_mbgl::NodeRequest::Init(exports); + node_mbgl::InitCompressPNG(exports); // Exports Resource constants. auto ConstantProperty = static_cast(v8::ReadOnly | v8::DontDelete); diff --git a/src/node_map.cpp b/src/node_map.cpp index 8cf9ee9b6..8a0634a90 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -1,13 +1,25 @@ #include "node_map.hpp" -#include "node_map_render_worker.hpp" #include "node_file_source.hpp" #include +#include +#include #include 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 classes; +}; + //////////////////////////////////////////////////////////////////////////////////////////////// // Static Node Methods @@ -21,6 +33,7 @@ void NodeMap::Init(v8::Handle target) { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(NanNew("Map")); + NODE_SET_PROTOTYPE_METHOD(t, "setAccessToken", SetAccessToken); NODE_SET_PROTOTYPE_METHOD(t, "load", Load); NODE_SET_PROTOTYPE_METHOD(t, "render", Render); @@ -56,6 +69,32 @@ NAN_METHOD(NodeMap::New) { NanReturnValue(args.This()); } + +NAN_METHOD(NodeMap::SetAccessToken) { + NanScope(); + + if (args.Length() < 1) { + return NanThrowError("Requires a string as first argument"); + } + + NanUtf8String token(args[0]); + + auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); + + if (nodeMap->map.isRendering()) { + return NanThrowError("Map object is currently in use"); + } + + try { + nodeMap->map.setAccessToken(std::string { *token, size_t(token.length()) }); + } catch (const std::exception &ex) { + return NanThrowError(ex.what()); + } + + + NanReturnUndefined(); +} + const std::string StringifyStyle(v8::Handle styleHandle) { NanScope(); @@ -77,19 +116,22 @@ NAN_METHOD(NodeMap::Load) { if (args[0]->IsObject()) { style = StringifyStyle(args[0]); } else if (args[0]->IsString()) { - v8::Local toStr = args[0]->ToString(); - style.resize(toStr->Utf8Length()); - toStr->WriteUtf8(const_cast(style.data())); + NanUtf8String string(args[0]); + style = { *string, size_t(string.length()) }; } else { return NanThrowTypeError("First argument must be a string or object"); } auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); + if (nodeMap->map.isRendering()) { + return NanThrowError("Map object is currently in use"); + } + try { nodeMap->map.setStyleJSON(style, "."); } catch (const std::exception &ex) { - NanThrowError(ex.what()); + return NanThrowError(ex.what()); } NanReturnUndefined(); @@ -110,9 +152,6 @@ std::unique_ptr NodeMap::ParseOptions(v8::LocalHas(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(); @@ -137,23 +176,99 @@ NAN_METHOD(NodeMap::Render) { return NanThrowTypeError("Second argument must be a callback function"); } + auto options = ParseOptions(args[0]->ToObject()); + auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - const bool wasEmpty = nodeMap->queue_.empty(); + if (nodeMap->map.isRendering()) { + return NanThrowError("Map object is currently in use"); + } - nodeMap->queue_.push(mbgl::util::make_unique( - nodeMap, - ParseOptions(args[0]->ToObject()), - new NanCallback(args[1].As()))); + assert(!nodeMap->callback); + assert(!nodeMap->image); + nodeMap->callback = std::unique_ptr(new NanCallback(args[1].As())); - 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()); + try { + nodeMap->startRender(std::move(options)); + } catch (mbgl::util::Exception &ex) { + return NanThrowError(ex.what()); } NanReturnUndefined(); } +void NodeMap::startRender(std::unique_ptr options) { + view.resize(options->width, options->height, options->ratio); + map.setClasses(options->classes); + map.setLonLatZoom(options->longitude, options->latitude, options->zoom); + map.setBearing(options->bearing); + + map.renderStill([this](std::unique_ptr result) { + 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(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(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 (img) { + v8::Local result = v8::Object::New(); + result->Set(NanNew("width"), NanNew(img->width)); + result->Set(NanNew("height"), NanNew(img->height)); + + v8::Local pixels = NanNewBufferHandle( + reinterpret_cast(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 *>(hint); + }, + new std::unique_ptr(std::move(img)) + ); + + result->Set(NanNew("pixels"), pixels); + + v8::Local argv[] = { + NanNull(), + result, + }; + cb->Call(2, argv); + } else { + v8::Local argv[] = { + NanError("Didn't get an image") + }; + cb->Call(1, argv); + } +} + + //////////////////////////////////////////////////////////////////////////////////////////////// // Instance @@ -165,21 +280,24 @@ std::shared_ptr sharedDisplay() { NodeMap::NodeMap(v8::Handle source_) : view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), - map(view, fs) { + map(view, fs, mbgl::Map::RenderMode::Still), + async(new uv_async_t) { source = v8::Persistent::New(source_); + async->data = this; + uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { + reinterpret_cast(as->data)->renderFinished(); + }); + + // Make sure the async handle doesn't keep the loop alive. + uv_unref(reinterpret_cast(async)); } 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()); - } + uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { + delete reinterpret_cast(handle); + }); } } diff --git a/src/node_map.hpp b/src/node_map.hpp index fdc625c03..614eab280 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -23,9 +23,13 @@ class NodeMap : public node::ObjectWrap { public: static void Init(v8::Handle target); static NAN_METHOD(New); + static NAN_METHOD(SetAccessToken); static NAN_METHOD(Load); static NAN_METHOD(Render); + void startRender(std::unique_ptr options); + void renderFinished(); + static std::unique_ptr ParseOptions(v8::Local obj); static v8::Persistent constructorTemplate; @@ -36,17 +40,19 @@ private: NodeMap(v8::Handle source); ~NodeMap(); - void processNext(); - private: + // For retaining the FileSource object. v8::Persistent source; - v8::Persistent self; mbgl::HeadlessView view; NodeFileSource &fs; mbgl::Map map; - std::queue> queue_; + std::unique_ptr image; + std::unique_ptr callback; + + // Async for delivering the notifications of render completion. + uv_async_t *async; }; } // end ns node_mbgl diff --git a/src/node_map_render_worker.cpp b/src/node_map_render_worker.cpp deleted file mode 100644 index 6079e8a8c..000000000 --- a/src/node_map_render_worker.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "node_map_render_worker.hpp" -#include "node_file_source.hpp" - -#include - -namespace node_mbgl { - -NodeMap::RenderWorker::RenderWorker(NodeMap *nodeMap_, std::unique_ptr options_, - NanCallback *callback_) - : NanAsyncWorker(callback_), nodeMap(nodeMap_), options(std::move(options_)) { - nodeMap->Ref(); -} - -NodeMap::RenderWorker::~RenderWorker() { - nodeMap->Unref(); -} - -void NodeMap::RenderWorker::Execute() { - try { - nodeMap->view.resize(options->width, options->height, options->ratio); - nodeMap->map.setAccessToken(options->accessToken); - 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(); - - const unsigned int width = options->width * options->ratio; - const unsigned int height = options->height * options->ratio; - auto pixels = nodeMap->view.readPixels(); - image = mbgl::util::compress_png(width, height, pixels.get()); - } catch (mbgl::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 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 deleted file mode 100644 index 920332f7d..000000000 --- a/src/node_map_render_worker.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef NODE_MBGL_NODE_MAP_RENDER_WORKER -#define NODE_MBGL_NODE_MAP_RENDER_WORKER - -#include - -#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 classes; -}; - -class NodeMap::RenderWorker : public NanAsyncWorker { -public: - RenderWorker(NodeMap *map, std::unique_ptr options, NanCallback *callback); - ~RenderWorker(); - - void Execute(); - void HandleOKCallback(); - void WorkComplete(); - -private: - NodeMap *nodeMap; - std::unique_ptr options; - std::string image; -}; - -} // ns node_mbgl - -#endif // NODE_MBGL_RENDER_WORKER diff --git a/vendor/mbgl b/vendor/mbgl index eac5f6f45..634de692d 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit eac5f6f4540d8adb29230ef07ac46a3306abb421 +Subproject commit 634de692d70a52706602b2516226ba404cf51cac From caae73a33aa6dae1fecf30ccddcb01a4c0eb1d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Thu, 12 Feb 2015 13:04:06 -0800 Subject: [PATCH 023/760] expose log messages to node.js --- binding.gyp | 2 ++ src/mbgl.cpp | 9 +++++++ src/node_log.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ src/node_log.hpp | 32 +++++++++++++++++++++++++ vendor/mbgl | 2 +- 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/node_log.cpp create mode 100644 src/node_log.hpp diff --git a/binding.gyp b/binding.gyp index dc030205b..c97efcf4b 100644 --- a/binding.gyp +++ b/binding.gyp @@ -13,6 +13,8 @@ 'src/mbgl.cpp', 'src/compress_png.hpp', 'src/compress_png.cpp', + 'src/node_log.hpp', + 'src/node_log.cpp', 'src/node_file_source.hpp', 'src/node_file_source.cpp', 'src/node_map.hpp', diff --git a/src/mbgl.cpp b/src/mbgl.cpp index b218382ab..f113fcd5a 100644 --- a/src/mbgl.cpp +++ b/src/mbgl.cpp @@ -3,9 +3,11 @@ #include "node_file_source.hpp" #include "node_map.hpp" +#include "node_log.hpp" #include "node_request.hpp" #include "compress_png.hpp" + void RegisterModule(v8::Handle exports) { NanScope(); @@ -23,6 +25,13 @@ void RegisterModule(v8::Handle exports) { 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::Set(exports); } NODE_MODULE(mbgl, RegisterModule) diff --git a/src/node_log.cpp b/src/node_log.cpp new file mode 100644 index 000000000..b7076457c --- /dev/null +++ b/src/node_log.cpp @@ -0,0 +1,61 @@ +#include "node_log.hpp" + +#include + +namespace node_mbgl { + +struct NodeLogBackend::Message { + mbgl::EventSeverity severity; + mbgl::Event event; + int64_t code; + std::string text; +}; + +NodeLogBackend::NodeLogBackend(v8::Handle target) + : queue(new Queue(uv_default_loop(), [this](Message &message) { + NanScope(); + + auto msg = v8::Object::New(); + msg->Set(NanNew("class"), NanNew(mbgl::EventClass(message.event).c_str())); + msg->Set(NanNew("severity"), NanNew(mbgl::EventSeverityClass(message.severity).c_str())); + if (!message.text.empty()) { + msg->Set(NanNew("text"), NanNew(message.text)); + } + if (message.code != 0) { + msg->Set(NanNew("code"), NanNew(double(message.code))); + } + + v8::Local argv[] = { NanNew("message"), msg }; + auto emit = module->Get(NanNew("emit"))->ToObject(); + emit->CallAsFunction(module, 2, argv); + })) { + NanScope(); + module = v8::Persistent::New(target); +} + +NodeLogBackend::~NodeLogBackend() { + queue->stop(); +} + +void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, const std::string &msg) { + queue->send({ severity, event, 0, msg }); +} + +void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, const char* format, ...) { + char msg[512]; + va_list args; + va_start(args, format); + vsnprintf(msg, 512, format, args); + va_end(args); + queue->send({ severity, event, 0, msg }); +} + +void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code) { + queue->send({ severity, event, code, "" }); +} + +void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) { + queue->send({ severity, event, code, msg }); +} + +} diff --git a/src/node_log.hpp b/src/node_log.hpp new file mode 100644 index 000000000..5cee4dde9 --- /dev/null +++ b/src/node_log.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include + +namespace mbgl { namespace util { template class AsyncQueue; } } + +namespace node_mbgl { + +class NodeLogBackend : public mbgl::LogBackend { +public: + NodeLogBackend(v8::Handle target); + ~NodeLogBackend(); + + void record(mbgl::EventSeverity severity, mbgl::Event event, const std::string &msg); + void record(mbgl::EventSeverity severity, mbgl::Event event, const char* format, ...); + void record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code); + void record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg); + +private: + v8::Persistent module; + + struct Message; + using Queue = mbgl::util::AsyncQueue; + Queue *queue = nullptr; +}; + + +} diff --git a/vendor/mbgl b/vendor/mbgl index 634de692d..3a6f095e4 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 634de692d70a52706602b2516226ba404cf51cac +Subproject commit 3a6f095e4e05fffb62c70d154a40d55a52912c88 From 81c45ab39ac2d0d4380053996e455249df20c58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Thu, 12 Feb 2015 18:18:43 -0800 Subject: [PATCH 024/760] no need to use a pointer to a unique_ptr when we can just use the pointer --- src/node_map.cpp | 4 ++-- vendor/mbgl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 8a0634a90..d8b3d3e70 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -248,9 +248,9 @@ void NodeMap::renderFinished() { // Retain the StillImage object until the buffer is deleted. [](char *, void *hint) { - delete reinterpret_cast *>(hint); + delete reinterpret_cast(hint); }, - new std::unique_ptr(std::move(img)) + const_cast(img.release()) ); result->Set(NanNew("pixels"), pixels); diff --git a/vendor/mbgl b/vendor/mbgl index 3a6f095e4..590133379 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 3a6f095e4e05fffb62c70d154a40d55a52912c88 +Subproject commit 5901333797f05ac861b6242f0f9d7edf0875b5cd From 464ecaf7c7287c84656052d3a9f57e1b6158922d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Thu, 12 Feb 2015 18:45:44 -0800 Subject: [PATCH 025/760] make sure the log notifications don't keep the threadloop alive --- src/node_log.cpp | 3 +++ vendor/mbgl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/node_log.cpp b/src/node_log.cpp index b7076457c..f39536a5a 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -31,6 +31,9 @@ NodeLogBackend::NodeLogBackend(v8::Handle target) })) { NanScope(); module = v8::Persistent::New(target); + + // Don't keep the event loop alive. + queue->unref(); } NodeLogBackend::~NodeLogBackend() { diff --git a/vendor/mbgl b/vendor/mbgl index 590133379..1de315e91 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 5901333797f05ac861b6242f0f9d7edf0875b5cd +Subproject commit 1de315e91ff25d4bf162c4d3bb4c453d89391c1a From a8837d92e472178e17675eb8745af65975121052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 11:52:04 -0800 Subject: [PATCH 026/760] convert tests to new return value --- test/js/concurrency.test.js | 10 +++- test/js/map.test.js | 18 ++++--- test/render.test.js | 103 +++++++++++++++++++----------------- vendor/mbgl | 2 +- 4 files changed, 73 insertions(+), 60 deletions(-) diff --git a/test/js/concurrency.test.js b/test/js/concurrency.test.js index 6a2ee32ca..e8c373adc 100644 --- a/test/js/concurrency.test.js +++ b/test/js/concurrency.test.js @@ -28,13 +28,19 @@ function renderTest(style, info, dir, key) { var map = new mbgl.Map(fileSource); map.load(style); - for (var i = 0; i < remaining; i++) { + function render() { map.render(info[key], function(err, image) { t.error(err); t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms'); - if (++completed === remaining) t.end(); + if (++completed === remaining) { + t.end(); + } else { + render(); + } }); } + + render(); }; } diff --git a/test/js/map.test.js b/test/js/map.test.js index 97daa8d52..50bab644d 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -163,21 +163,23 @@ test('Map', function(t) { t.test('requires a style to be set', function(t) { setup(fileSource, function(map) { - map.render({}, function(err) { - t.ok(err); - t.equal(err.message, 'Style is not set'); - t.end(); - }); + t.throws(function() { + map.render({}, function() {}); + }, /Style is not set/); + t.end(); }); }); t.test('returns an image', function(t) { setup(fileSource, function(map) { map.load(style); - map.render({}, function(err, data) { + map.render({}, function(err, image) { t.error(err); - fs.writeFileSync('test/results/image.png', data); - t.end(); + mbgl.compressPNG(image, function(err, data) { + t.error(err); + fs.writeFileSync('test/results/image.png', data); + t.end(); + }); }); }); }); diff --git a/test/render.test.js b/test/render.test.js index 17f9bd228..3e9c2becf 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -51,63 +51,68 @@ function renderTest(style, info, base, key) { map.render(info[key], function(err, image) { t.error(err); - mkdirp.sync(dir); + mbgl.compressPNG(image, function(err, image) { + t.error(err); + mkdirp.sync(dir); - var expected = path.join(dir, 'expected.png'); - var actual = path.join(dir, 'actual.png'); - var diff = path.join(dir, 'diff.png'); + var expected = path.join(dir, 'expected.png'); + var actual = path.join(dir, 'actual.png'); + var diff = path.join(dir, 'diff.png'); - if (process.env.UPDATE) { - fs.writeFile(expected, image, function(err) { - t.error(err); - t.end(); - }); - } else { - fs.writeFile(actual, image, function(err) { - t.error(err); + console.warn(actual, expected); - var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); - var error = ''; - - compare.stderr.on('data', function (data) { - error += data.toString(); + if (process.env.UPDATE) { + fs.writeFile(expected, image, function(err) { + t.error(err); + t.end(); }); + } else { + fs.writeFile(actual, image, function(err) { + t.error(err); - compare.on('exit', function (code) { - // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. - if (code === 2) { - writeResult(error.trim(), Infinity); - } else { - var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); - var difference = match ? parseFloat(match[1]) : Infinity; - writeResult(match ? '' : error, difference); - } - }); + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; - compare.stdin.end(); - - function writeResult(error, difference) { - var allowedDifference = ('diff' in info) ? info.diff : 0.001; - var color = difference <= allowedDifference ? 'green' : 'red'; - - results += format(resultTemplate, { - name: base, - key: key, - color: color, - error: error ? '

' + error + '

' : '', - difference: difference, - zoom: info.zoom || 0, - center: info.center || [0, 0], - bearing: info.bearing || 0, - width: info.width || 512, - height: info.height || 512 + compare.stderr.on('data', function (data) { + error += data.toString(); }); - t.ok(difference <= allowedDifference, 'actual matches expected'); - t.end(); - } - }); - } + compare.on('exit', function (code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + writeResult(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + writeResult(match ? '' : error, difference); + } + }); + + compare.stdin.end(); + + function writeResult(error, difference) { + var allowedDifference = ('diff' in info) ? info.diff : 0.001; + var color = difference <= allowedDifference ? 'green' : 'red'; + + results += format(resultTemplate, { + name: base, + key: key, + color: color, + error: error ? '

' + error + '

' : '', + difference: difference, + zoom: info.zoom || 0, + center: info.center || [0, 0], + bearing: info.bearing || 0, + width: info.width || 512, + height: info.height || 512 + }); + + t.ok(difference <= allowedDifference, 'actual matches expected'); + t.end(); + } + }); + } + }); }); }; } diff --git a/vendor/mbgl b/vendor/mbgl index 1de315e91..88f14c71f 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 1de315e91ff25d4bf162c4d3bb4c453d89391c1a +Subproject commit 88f14c71f9b942fa13ed27d91caa5b834f2955a8 From 63dfba065dfdd68e4f9cfd536565744ed64399ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 11:52:20 -0800 Subject: [PATCH 027/760] add tap-spec to format output --- package.json | 1 + test/js/concurrency.test.js | 6 ++++++ test/js/file_source.test.js | 6 ++++++ test/js/map.test.js | 6 ++++++ test/render.test.js | 6 ++++++ 5 files changed, 25 insertions(+) diff --git a/package.json b/package.json index 01e499e43..ff8b301d6 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "aws-sdk": "^2.1.9", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite", "mkdirp": "^0.5.0", + "tap-spec": "^2.2.1", "tape": "^3.5.0" }, "scripts": { diff --git a/test/js/concurrency.test.js b/test/js/concurrency.test.js index e8c373adc..e5f96623e 100644 --- a/test/js/concurrency.test.js +++ b/test/js/concurrency.test.js @@ -3,6 +3,12 @@ /* jshint node:true */ var test = require('tape'); +var tapSpec = require('tap-spec'); + +test.createStream() + .pipe(tapSpec()) + .pipe(process.stdout); + var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); diff --git a/test/js/file_source.test.js b/test/js/file_source.test.js index 72f2180f2..1970ee980 100644 --- a/test/js/file_source.test.js +++ b/test/js/file_source.test.js @@ -3,6 +3,12 @@ /* jshint node: true */ var test = require('tape'); +var tapSpec = require('tap-spec'); + +test.createStream() + .pipe(tapSpec()) + .pipe(process.stdout); + var mbgl = require('../..'); test('FileSource', function(t) { diff --git a/test/js/map.test.js b/test/js/map.test.js index 50bab644d..94a8e720e 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -3,6 +3,12 @@ /* jshint node: true */ var test = require('tape'); +var tapSpec = require('tap-spec'); + +test.createStream() + .pipe(tapSpec()) + .pipe(process.stdout); + var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); diff --git a/test/render.test.js b/test/render.test.js index 3e9c2becf..2c1226f0b 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -3,6 +3,12 @@ /* jshint node:true */ var test = require('tape'); +var tapSpec = require('tap-spec'); + +test.createStream() + .pipe(tapSpec()) + .pipe(process.stdout); + var mbgl = require('..'); var fs = require('fs'); var path = require('path'); From 3aa590d0aff0ffceca2fb5d970eb5bf7573932d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 12:01:46 -0800 Subject: [PATCH 028/760] update mbgl rev --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index 88f14c71f..f60c1d7cf 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 88f14c71f9b942fa13ed27d91caa5b834f2955a8 +Subproject commit f60c1d7cfa65fc0388fb09ad83399c942585adab From 3a1123a1ef39461d6096019675ad9b0905ddfbad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 13:54:09 -0800 Subject: [PATCH 029/760] don't print filenames, but show imagemagick errors --- test/render.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/render.test.js b/test/render.test.js index 2c1226f0b..9643dfef7 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -65,8 +65,6 @@ function renderTest(style, info, base, key) { var actual = path.join(dir, 'actual.png'); var diff = path.join(dir, 'diff.png'); - console.warn(actual, expected); - if (process.env.UPDATE) { fs.writeFile(expected, image, function(err) { t.error(err); @@ -83,6 +81,10 @@ function renderTest(style, info, base, key) { error += data.toString(); }); + compare.on('error', function(err) { + t.error(err); + }); + compare.on('exit', function (code) { // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. if (code === 2) { From a30a377851f03a44ef403c920c22584f9b773729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 13:54:33 -0800 Subject: [PATCH 030/760] update mbgl --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index f60c1d7cf..a43d2f824 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit f60c1d7cfa65fc0388fb09ad83399c942585adab +Subproject commit a43d2f824dcb50eeb35775cbe7d656ff607bf4eb From b1deba8d95bf949899d9746e6a041b35144ebce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 13 Feb 2015 15:09:32 -0800 Subject: [PATCH 031/760] do not .release() in function call; execution order is undefined --- src/node_map.cpp | 3 ++- vendor/mbgl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index d8b3d3e70..a493e3020 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -250,8 +250,9 @@ void NodeMap::renderFinished() { [](char *, void *hint) { delete reinterpret_cast(hint); }, - const_cast(img.release()) + const_cast(img.get()) ); + img.release(); result->Set(NanNew("pixels"), pixels); diff --git a/vendor/mbgl b/vendor/mbgl index a43d2f824..79422fefa 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit a43d2f824dcb50eeb35775cbe7d656ff607bf4eb +Subproject commit 79422fefa3af657a0f004fc924a84f30cf1aeea6 From c5f380f45dc4ddb789fb2b354a2f8c71b96cd754 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 13 Feb 2015 17:23:28 -0800 Subject: [PATCH 032/760] Move to mapbox bucket --- package.json | 2 +- travis/travis-resources.template | 78 ++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index ff8b301d6..c60a03c38 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "aws-sdk": "^2.1.9", - "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite", + "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", "mkdirp": "^0.5.0", "tap-spec": "^2.2.1", "tape": "^3.5.0" diff --git a/travis/travis-resources.template b/travis/travis-resources.template index 8f07f1cf2..ad62b03f8 100644 --- a/travis/travis-resources.template +++ b/travis/travis-resources.template @@ -28,7 +28,7 @@ } }, { - "PolicyName": "build-testing", + "PolicyName": "build-testing-old", "PolicyDocument": { "Statement": [ { @@ -47,20 +47,38 @@ ] } }, - "PolicyName": "list", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::mapbox-node-binary" - ], - "Effect": "Allow" - } - ] - } + { + "PolicyName": "list", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::mapbox-node-binary" + ], + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "list-testing-old", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::mapbox-gl-testing" + ] + } + ] + } + }, { "PolicyName": "list-testing", "PolicyDocument": { @@ -71,7 +89,32 @@ ], "Effect": "Allow", "Resource": [ - "arn:aws:s3:::mapbox-gl-testing" + "arn:aws:s3:::mapbox" + ], + "Condition": { + "StringLike": { + "s3:prefix": "node-mapbox-gl-native/*" + } + } + } + ] + } + }, + { + "PolicyName": "build-testing", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::mapbox/node-mapbox-gl-native/*" ] } ] @@ -97,7 +140,7 @@ }, "SecretAccessKey": { "Value": { - "Fn::GetAtt": [ + "Fn::GetAtt": [ "BuildUserKey", "SecretAccessKey" ] @@ -105,4 +148,3 @@ } } } - From cc1783298c7e5f8ded8135667e29332c7a47ce03 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 12 Feb 2015 18:34:52 -0800 Subject: [PATCH 033/760] add node-pre-gyp --- Makefile | 4 ++-- binding.gyp | 8 ++++---- package.json | 15 +++++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 97b16cfea..f680ec803 100644 --- a/Makefile +++ b/Makefile @@ -30,14 +30,14 @@ global: build .PHONY: build build: build/Makefile - @node-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS) + @node-pre-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS) vendor/mbgl: git submodule update --init .PHONY: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi - @node-gyp configure --clang -- \ + @node-pre-gyp configure --clang -- \ -Dmbgl=$(MBGL) \ -Dhost=$(HOST) \ -I$(MBGL)/config/$(HOST).gypi \ diff --git a/binding.gyp b/binding.gyp index c97efcf4b..263249bc9 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,6 +1,6 @@ { 'targets': [ - { 'target_name': 'mbgl', + { 'target_name': '<(module_name)', 'dependencies': [ './<(mbgl)/mbgl.gyp:core', './<(mbgl)/mbgl.gyp:platform-<(platform_lib)', @@ -52,11 +52,11 @@ { 'target_name': 'action_after_build', 'type': 'none', - 'dependencies': [ 'mbgl' ], + 'dependencies': [ '<(module_name)' ], 'copies': [ { - 'files': [ '<(PRODUCT_DIR)/mbgl.node' ], - 'destination': 'lib' + 'files': [ '<(PRODUCT_DIR)/<(module_name).node' ], + 'destination': '<(module_path)' } ] } diff --git a/package.json b/package.json index c60a03c38..46b54b191 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,12 @@ } ], "dependencies": { - "nan": "^1.4.1" + "nan": "^1.4.1", + "node-pre-gyp": "^0.6.4" }, + "bundledDependencies": [ + "node-pre-gyp" + ], "devDependencies": { "aws-sdk": "^2.1.9", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", @@ -31,5 +35,12 @@ "install": "make", "test": "make test" }, - "gypfile": true + "gypfile": true, + "binary": { + "module_name": "mbgl", + "module_path": "./lib/", + "host": "https://mapbox-node-binary.s3.amazonaws.com", + "remote_path": "./{name}/v{version}", + "package_name": "{node_abi}-{platform}-{arch}.tar.gz" + } } From 5562fb5e4e9a9a0dfd1cec669285c6261d47d100 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 17 Feb 2015 14:49:41 -0500 Subject: [PATCH 034/760] swap nvm for mason node --- .travis.yml | 4 ++-- scripts/install_node.sh | 9 +++++++++ scripts/install_nvm.sh | 11 ----------- 3 files changed, 11 insertions(+), 13 deletions(-) create mode 100755 scripts/install_node.sh delete mode 100755 scripts/install_nvm.sh diff --git a/.travis.yml b/.travis.yml index b1cbefbf3..88559a99a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,14 +12,14 @@ matrix: env: global: - - NODE_NVM_VERSION: "0.10" + - NODE_VERSION: "0.10.35" - LD_LIBRARY_PATH: "/usr/local/lib" - secure: jkwZnKr5dcM9MQ6mOytnp6FHkbydSfa1L8d4jFp4v4K6Ee/iQh1YIq9c/J1zC0WLyuGokVGaijkKr6CW4nGZ5H8ygBtPo09imnRyH8X0bO5mEjl+KhbpdFqkXVLerpnAGMO7D6+6BtWJlK5uTDWhpeS7YayB3YfETVkllf4n6F8= - secure: ZBiq+KPVM72MF0rtvpui5l7curGXckz4Pefc1L04U6dd/MPa22StvCgXBG7DKhLwI5OgHE1da769asQ3rub/pDOonxAi7fHo3+r/ocbSBZeeaj+p5i5wavppOzD27gCLrpOnSQBk0DfoD+xOM8ZCfIttBbOBMRZ1dpWFG98bJ4c= before_install: -- ./scripts/install_nvm.sh - cd ./vendor/mbgl && source ./scripts/local_mason.sh && cd ../../ +- source ./scripts/install_node.sh - source ./vendor/mbgl/scripts/travis_helper.sh - source ./vendor/mbgl/scripts/flags.sh - source ./vendor/mbgl/scripts/travis_before_install.sh diff --git a/scripts/install_node.sh b/scripts/install_node.sh new file mode 100755 index 000000000..dcdab3521 --- /dev/null +++ b/scripts/install_node.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +mason install node $NODE_VERSION +export PATH="`mason prefix node $NODE_VERSION`/bin":"$PATH" +node --version +npm --version diff --git a/scripts/install_nvm.sh b/scripts/install_nvm.sh deleted file mode 100755 index 1f68af710..000000000 --- a/scripts/install_nvm.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -git clone git@github.com:creationix/nvm.git ../.nvm -source ../.nvm/nvm.sh -nvm install $NODE_NVM_VERSION -nvm use $NODE_NVM_VERSION -node --version -npm --version From 4b15b16c7b448dc5877325c31a632cfa9a214588 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 17 Feb 2015 15:32:47 -0500 Subject: [PATCH 035/760] [publish binary] From 5330d9c417a8049129675bce1bc134f071cab859 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 17 Feb 2015 16:11:11 -0500 Subject: [PATCH 036/760] update cfn template [publish binary] --- travis/travis-resources.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis/travis-resources.template b/travis/travis-resources.template index ad62b03f8..2573e3320 100644 --- a/travis/travis-resources.template +++ b/travis/travis-resources.template @@ -20,7 +20,7 @@ "s3:PutObjectAcl" ], "Resource": [ - "arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*" + "arn:aws:s3:::mapbox-node-binary/mbgl/*" ], "Effect": "Allow" } From 24946b29faf2349ef2d8ed7c4fd844c777676926 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 17 Feb 2015 16:56:16 -0500 Subject: [PATCH 037/760] use node-pre-gyp binary in npm install --- Makefile | 2 +- README.md | 15 +++++++++++---- package.json | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index f680ec803..8f79ab04a 100644 --- a/Makefile +++ b/Makefile @@ -61,5 +61,5 @@ test: test-js test-suite .PHONY: clean clean: - rm -rf build + rm -rf build lib rm -f $(MBGL)/config/$(HOST).gypi diff --git a/README.md b/README.md index 1b274f946..738a30930 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,20 @@ Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). [![Build Status](https://api.travis-ci.com/mapbox/node-mapbox-gl-native.svg?token=Phdq58g7NsfstW6gyeYW)](https://magnum.travis-ci.com/mapbox/node-mapbox-gl-native) -## Loading the module +## Installing -To compile this module, make sure all submodules are initialized +By default, installs binaries. On these platforms no external dependencies are needed. + +- 64 bit OS X or 64 bit Linux +- Node v0.10.x + +Just run: -```js -var mbgl = require('mbgl'); ``` +npm install mbgl +``` + +However, other platforms will fall back to a source compile with `make build`. To compile this module, make sure all submodules are initialized. ## Rendering a map tile diff --git a/package.json b/package.json index 46b54b191..5bf9725ce 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "tape": "^3.5.0" }, "scripts": { - "install": "make", + "install": "node-pre-gyp install || make", "test": "make test" }, "gypfile": true, From 6e7278d72d29b428174eaf6e9b537c6bd5b79377 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 12:21:18 -0500 Subject: [PATCH 038/760] add npm script rebuild, always build from source on travis --- .travis.yml | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 88559a99a..069c51aa2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: - if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; glxinfo; fi install: -- npm install +- npm run-script rebuild before_script: # Set the core file limit to unlimited so a core file is generated upon crash diff --git a/package.json b/package.json index 5bf9725ce..e3884bed2 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ }, "scripts": { "install": "node-pre-gyp install || make", - "test": "make test" + "test": "make test", + "rebuild": "make clean && make" }, "gypfile": true, "binary": { From 19e84c5e59ff3bf160aa3a5770e364e3c0906c14 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 12:24:02 -0500 Subject: [PATCH 039/760] remove mapbox-gl-testing permissions from cfn config --- travis/travis-resources.template | 50 +++++--------------------------- 1 file changed, 7 insertions(+), 43 deletions(-) diff --git a/travis/travis-resources.template b/travis/travis-resources.template index 2573e3320..ea7f12585 100644 --- a/travis/travis-resources.template +++ b/travis/travis-resources.template @@ -27,26 +27,6 @@ ] } }, - { - "PolicyName": "build-testing-old", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:DeleteObject", - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::mapbox-gl-testing/*" - ] - } - ] - } - }, { "PolicyName": "list", "PolicyDocument": { @@ -64,16 +44,20 @@ } }, { - "PolicyName": "list-testing-old", + "PolicyName": "build-testing", "PolicyDocument": { "Statement": [ { "Action": [ - "s3:ListBucket" + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" ], "Effect": "Allow", "Resource": [ - "arn:aws:s3:::mapbox-gl-testing" + "arn:aws:s3:::mapbox/node-mapbox-gl-native/*" ] } ] @@ -99,26 +83,6 @@ } ] } - }, - { - "PolicyName": "build-testing", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:DeleteObject", - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::mapbox/node-mapbox-gl-native/*" - ] - } - ] - } } ] } From c3453a9bd6828da68ba51afac09026bbe5ea0c4d Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 16:11:40 -0500 Subject: [PATCH 040/760] bump mbgl --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index 79422fefa..18454f2cd 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 79422fefa3af657a0f004fc924a84f30cf1aeea6 +Subproject commit 18454f2cd697e1d7606b8ecbcc684c083b6bd008 From 10334522ada0d105344923e9a7c615acaca23b9b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 16:22:26 -0500 Subject: [PATCH 041/760] fix setLatLngZooom --- src/node_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index a493e3020..93ab7b5fb 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -200,7 +200,7 @@ NAN_METHOD(NodeMap::Render) { void NodeMap::startRender(std::unique_ptr options) { view.resize(options->width, options->height, options->ratio); map.setClasses(options->classes); - map.setLonLatZoom(options->longitude, options->latitude, options->zoom); + map.setLatLngZoom(mbgl::LatLng(options->longitude, options->latitude), options->zoom); map.setBearing(options->bearing); map.renderStill([this](std::unique_ptr result) { From 030c88ce10544a84b99576c0b249c03ea39231d2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 16:41:11 -0500 Subject: [PATCH 042/760] oops, fix lat, lng order --- src/node_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 93ab7b5fb..2e2ef7f98 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -200,7 +200,7 @@ NAN_METHOD(NodeMap::Render) { void NodeMap::startRender(std::unique_ptr options) { view.resize(options->width, options->height, options->ratio); map.setClasses(options->classes); - map.setLatLngZoom(mbgl::LatLng(options->longitude, options->latitude), options->zoom); + map.setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); map.setBearing(options->bearing); map.renderStill([this](std::unique_ptr result) { From 5666e0c95c010c978209ac5990f44efe39366947 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 16:55:07 -0500 Subject: [PATCH 043/760] install node modules before rebuild on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 069c51aa2..b1cda65c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: - if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; glxinfo; fi install: -- npm run-script rebuild +- npm install && npm run-script rebuild before_script: # Set the core file limit to unlimited so a core file is generated upon crash From d2fd82c0324ec8a954064bd8b114e8bf31c5ddca Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 17:02:43 -0500 Subject: [PATCH 044/760] Revert "add tap-spec to format output" This reverts commit 63dfba065dfdd68e4f9cfd536565744ed64399ce. --- package.json | 1 - test/js/concurrency.test.js | 6 ------ test/js/file_source.test.js | 6 ------ test/js/map.test.js | 6 ------ test/render.test.js | 6 ------ 5 files changed, 25 deletions(-) diff --git a/package.json b/package.json index e3884bed2..e4c2a767f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "aws-sdk": "^2.1.9", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", "mkdirp": "^0.5.0", - "tap-spec": "^2.2.1", "tape": "^3.5.0" }, "scripts": { diff --git a/test/js/concurrency.test.js b/test/js/concurrency.test.js index e5f96623e..e8c373adc 100644 --- a/test/js/concurrency.test.js +++ b/test/js/concurrency.test.js @@ -3,12 +3,6 @@ /* jshint node:true */ var test = require('tape'); -var tapSpec = require('tap-spec'); - -test.createStream() - .pipe(tapSpec()) - .pipe(process.stdout); - var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); diff --git a/test/js/file_source.test.js b/test/js/file_source.test.js index 1970ee980..72f2180f2 100644 --- a/test/js/file_source.test.js +++ b/test/js/file_source.test.js @@ -3,12 +3,6 @@ /* jshint node: true */ var test = require('tape'); -var tapSpec = require('tap-spec'); - -test.createStream() - .pipe(tapSpec()) - .pipe(process.stdout); - var mbgl = require('../..'); test('FileSource', function(t) { diff --git a/test/js/map.test.js b/test/js/map.test.js index 94a8e720e..50bab644d 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -3,12 +3,6 @@ /* jshint node: true */ var test = require('tape'); -var tapSpec = require('tap-spec'); - -test.createStream() - .pipe(tapSpec()) - .pipe(process.stdout); - var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); diff --git a/test/render.test.js b/test/render.test.js index 9643dfef7..b48f52908 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -3,12 +3,6 @@ /* jshint node:true */ var test = require('tape'); -var tapSpec = require('tap-spec'); - -test.createStream() - .pipe(tapSpec()) - .pipe(process.stdout); - var mbgl = require('..'); var fs = require('fs'); var path = require('path'); From 7bb6651bf5e8ead439a34e5a120c263157f263c0 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 17:07:12 -0500 Subject: [PATCH 045/760] pipe make test to tap-spec, fixes #84 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e4c2a767f..10eda356a 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "scripts": { "install": "node-pre-gyp install || make", - "test": "make test", + "test": "make test | tap-spec", "rebuild": "make clean && make" }, "gypfile": true, From ebbb6c9875464eebde0292418b9dded41f673f0e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 17:18:30 -0500 Subject: [PATCH 046/760] pipe to tap-spec in Makefile --- Makefile | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8f79ab04a..e0944498e 100644 --- a/Makefile +++ b/Makefile @@ -50,11 +50,11 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: build - -@`npm bin`/tape test/render.test.js + -@(`npm bin`/tape test/render.test.js | `npm bin`/tap-spec) .PHONY: test-js test-js: build - @`npm bin`/tape test/js/**/*.test.js + @(`npm bin`/tape test/js/**/*.test.js | `npm bin`/tap-spec) .PHONY: test test: test-js test-suite diff --git a/package.json b/package.json index 10eda356a..e4c2a767f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "scripts": { "install": "node-pre-gyp install || make", - "test": "make test | tap-spec", + "test": "make test", "rebuild": "make clean && make" }, "gypfile": true, From 3fa4ae882523a259682c8bee35efec38ac65a4d2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 17:33:35 -0500 Subject: [PATCH 047/760] add tap-spec to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e4c2a767f..e3884bed2 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "aws-sdk": "^2.1.9", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", "mkdirp": "^0.5.0", + "tap-spec": "^2.2.1", "tape": "^3.5.0" }, "scripts": { From 55c8e2ff77f43bd3e8825a04eefcd81c8358c6c9 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 18:34:47 -0500 Subject: [PATCH 048/760] assume node-pre-gyp install --build-from-source failover to make install --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b1cda65c3..31c69b46e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: - if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; glxinfo; fi install: -- npm install && npm run-script rebuild +- npm install --build-from-source before_script: # Set the core file limit to unlimited so a core file is generated upon crash From 6d8789a38b01d067930412635fd6e10d609e0fad Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 18 Feb 2015 18:35:26 -0500 Subject: [PATCH 049/760] drop build dep from test make tasks --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e0944498e..52ce7db88 100644 --- a/Makefile +++ b/Makefile @@ -49,11 +49,11 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure make -C $(MBGL) config/$*.gypi .PHONY: test-suite -test-suite: build +test-suite: -@(`npm bin`/tape test/render.test.js | `npm bin`/tap-spec) .PHONY: test-js -test-js: build +test-js: @(`npm bin`/tape test/js/**/*.test.js | `npm bin`/tap-spec) .PHONY: test From 23a8587e5f923a5e9b2e0bceb4bb1c7fb555e9cd Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 20 Feb 2015 13:46:15 -0500 Subject: [PATCH 050/760] bump mbgl for safe thread.join() --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index 18454f2cd..bb8cedfdb 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 18454f2cd697e1d7606b8ecbcc684c083b6bd008 +Subproject commit bb8cedfdbf805b80688bba04e24970e9868858af From 82317df050fdbea94cd86abbd3248b6b47a3ba87 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 24 Feb 2015 12:16:21 -0500 Subject: [PATCH 051/760] move back to node-mapbox-gl-native --- README.md | 4 ++-- binding.gyp | 2 +- package.json | 6 +++--- scripts/travis_publish.sh | 4 ++-- src/node_map.hpp | 8 ++------ src/{mbgl.cpp => node_mapbox_gl_native.cpp} | 2 +- travis/travis-resources.template | 2 +- 7 files changed, 12 insertions(+), 16 deletions(-) rename src/{mbgl.cpp => node_mapbox_gl_native.cpp} (96%) diff --git a/README.md b/README.md index 738a30930..dc1e3c4ba 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ By default, installs binaries. On these platforms no external dependencies are n Just run: ``` -npm install mbgl +npm install mapbox-gl-native ``` However, other platforms will fall back to a source compile with `make build`. To compile this module, make sure all submodules are initialized. @@ -101,7 +101,7 @@ This is a very barebones implementation and you'll probably want a better implem A sample implementation that uses [`request`](https://github.com/request/request) to query data from HTTP: ```js -var mbgl = require('./'); +var mbgl = require('mapbox-gl-native'); var request = require('request'); var fileSource = new mbgl.FileSource(); diff --git a/binding.gyp b/binding.gyp index 263249bc9..05e9347c3 100644 --- a/binding.gyp +++ b/binding.gyp @@ -10,7 +10,7 @@ 'include_dirs': [ " #include @@ -11,7 +10,6 @@ namespace node_mbgl { - class NodeFileSource; class NodeMap : public node::ObjectWrap { @@ -55,6 +53,4 @@ private: uv_async_t *async; }; -} // end ns node_mbgl - -#endif +} diff --git a/src/mbgl.cpp b/src/node_mapbox_gl_native.cpp similarity index 96% rename from src/mbgl.cpp rename to src/node_mapbox_gl_native.cpp index f113fcd5a..435c446a6 100644 --- a/src/mbgl.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -34,4 +34,4 @@ void RegisterModule(v8::Handle exports) { mbgl::Log::Set(exports); } -NODE_MODULE(mbgl, RegisterModule) +NODE_MODULE(mapbox_gl_native, RegisterModule) diff --git a/travis/travis-resources.template b/travis/travis-resources.template index ea7f12585..3ba9b9625 100644 --- a/travis/travis-resources.template +++ b/travis/travis-resources.template @@ -20,7 +20,7 @@ "s3:PutObjectAcl" ], "Resource": [ - "arn:aws:s3:::mapbox-node-binary/mbgl/*" + "arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*" ], "Effect": "Allow" } From 04c815c6e2dccb9297a300ee78be5bb8198b82ed Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 24 Feb 2015 12:58:53 -0500 Subject: [PATCH 052/760] update travis button to public --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc1e3c4ba..39a556999 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). -[![Build Status](https://api.travis-ci.com/mapbox/node-mapbox-gl-native.svg?token=Phdq58g7NsfstW6gyeYW)](https://magnum.travis-ci.com/mapbox/node-mapbox-gl-native) +[![Build Status](https://travis-ci.org/mapbox/node-mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/node-mapbox-gl-native) ## Installing From 2dab9d4700ef8760abbe4b1ec8fdd6c5eb1c2218 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 24 Feb 2015 13:09:32 -0500 Subject: [PATCH 053/760] switch back to https submodule from ssh --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index ac1fc38e6..863c7f66f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "vendor/mbgl"] path = vendor/mbgl - url = git@github.com:mapbox/mapbox-gl-native.git + url = https://github.com/mapbox/mapbox-gl-native.git From 8adc0f238e013cc67073b248b5ce58e82ab0f860 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 24 Feb 2015 13:40:12 -0500 Subject: [PATCH 054/760] update travis keys --- .travis.yml | 13 ++++++------- scripts/setup_mesa.sh | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100755 scripts/setup_mesa.sh diff --git a/.travis.yml b/.travis.yml index 31c69b46e..fa16fd0c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,10 @@ matrix: env: global: - - NODE_VERSION: "0.10.35" + - NODE_VERSION: 0.10.35 - LD_LIBRARY_PATH: "/usr/local/lib" - - secure: jkwZnKr5dcM9MQ6mOytnp6FHkbydSfa1L8d4jFp4v4K6Ee/iQh1YIq9c/J1zC0WLyuGokVGaijkKr6CW4nGZ5H8ygBtPo09imnRyH8X0bO5mEjl+KhbpdFqkXVLerpnAGMO7D6+6BtWJlK5uTDWhpeS7YayB3YfETVkllf4n6F8= - - secure: ZBiq+KPVM72MF0rtvpui5l7curGXckz4Pefc1L04U6dd/MPa22StvCgXBG7DKhLwI5OgHE1da769asQ3rub/pDOonxAi7fHo3+r/ocbSBZeeaj+p5i5wavppOzD27gCLrpOnSQBk0DfoD+xOM8ZCfIttBbOBMRZ1dpWFG98bJ4c= + - secure: pz/HAMQpnde//JJi3f+RcW32APN6g3QyFAH41JlZwgsM5Daj9RRoXqUcNg4hEyTvlThtc5t+wQQ2ejYAjMwDu00GAzRFJ03Sm45w2fPvith9fu4crXsyPUvWUfWPC0ajTXzorN4cwFwOoMoeo9DihLwb0EC0n4T6jWdBCae3k+s= + - secure: jYFAOQoMZkZVyc5AFPBKhR9oDqp5CciwCxFhVEHVjdImM+8V60loKazyw+bVLIjzKLnQbKKdiDudWL9TE1ylK/XSlF3K7o4gU2vIh5WzosKnU70Sanxd6tHt/Ui5eK+bAymKHbLdGiXIZtBZE0tML6+wgJ9vhV4ZKna5dM9tps4= before_install: - cd ./vendor/mbgl && source ./scripts/local_mason.sh && cd ../../ @@ -23,20 +23,19 @@ before_install: - source ./vendor/mbgl/scripts/travis_helper.sh - source ./vendor/mbgl/scripts/flags.sh - source ./vendor/mbgl/scripts/travis_before_install.sh -- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; glxinfo; fi +- source ./scripts/setup_mesa.sh install: - npm install --build-from-source before_script: -# Set the core file limit to unlimited so a core file is generated upon crash - ulimit -c unlimited -S script: -- ./scripts/travis_test.sh +- "./scripts/travis_test.sh" after_success: -- ./scripts/travis_publish.sh +- "./scripts/travis_publish.sh" git: submodules: true diff --git a/scripts/setup_mesa.sh b/scripts/setup_mesa.sh new file mode 100755 index 000000000..74baffba0 --- /dev/null +++ b/scripts/setup_mesa.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; + glxinfo; +fi From fc6f0dda148ef75b7348c45138964c48c94cc5a2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 25 Feb 2015 14:49:08 -0500 Subject: [PATCH 055/760] 1.0.0 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90f70b63a..0998cc15f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "0.0.1", + "version": "1.0.0", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 848fa663a60f3576a7d0d067a035e122d9e0154e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 26 Feb 2015 14:35:39 -0500 Subject: [PATCH 056/760] update example with compressPNG --- .gitignore | 3 ++- examples/load-from-fs.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c27d12d2f..cf1aea98a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /build /lib /node_modules -/test/results \ No newline at end of file +/test/results +image.png diff --git a/examples/load-from-fs.js b/examples/load-from-fs.js index 1d8ef30ed..5760f024f 100644 --- a/examples/load-from-fs.js +++ b/examples/load-from-fs.js @@ -22,6 +22,8 @@ var map = new mbgl.Map(fileSource); map.load(require('../test/fixtures/style.json')); map.render({}, function(err, image) { if (err) throw err; - fs.writeFileSync('image.png', image); - console.warn('Written image.png'); + mbgl.compressPNG(image, function(err, png) { + fs.writeFileSync('image.png', png); + console.warn('Written image.png'); + }); }); From fc1a907a178ee18362296502dd77219c0a145357 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Tue, 3 Mar 2015 18:57:13 -0500 Subject: [PATCH 057/760] document render options --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 39a556999..975d6ff65 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,20 @@ map.render({}, function(err, image) { }); ``` +The first argument passed to `map.render` is an options object, all key are optional: + +```js +{ + zoom: {zoom}, // number, defaults to 0 + width: {width}, // number (px), defaults to 256 + height: {height}, // number (px), defaults to 256 + latitude: {latitude}, // number (coordinate), defaults to 0 + longitude: {longitude}, // number (coordinate), defaults to 0 + bearing: {bearing}, // number (degree), defaults to 0 + ratio: {ratio} // number (scale factor), defaults to 1.0 +} +``` + ## Testing ``` From ae4831d75fc54fb05cecdf8f1407faea56275aef Mon Sep 17 00:00:00 2001 From: Saman Bemel-Benrud Date: Tue, 3 Mar 2015 22:02:58 -0500 Subject: [PATCH 058/760] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 975d6ff65..d8a2353bf 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ map.render({}, function(err, image) { }); ``` -The first argument passed to `map.render` is an options object, all key are optional: +The first argument passed to `map.render` is an options object, all keys are optional: ```js { From 2734e2c3f9d8773a52f5b20f856a02ba2268b6a8 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Wed, 4 Mar 2015 15:04:06 -0500 Subject: [PATCH 059/760] fix options documentation, document setAccessToken method, refs #93 --- README.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d8a2353bf..618f99f57 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,34 @@ The first argument passed to `map.render` is an options object, all keys are opt ```js { zoom: {zoom}, // number, defaults to 0 - width: {width}, // number (px), defaults to 256 - height: {height}, // number (px), defaults to 256 + width: {width}, // number (px), defaults to 512 + height: {height}, // number (px), defaults to 512 latitude: {latitude}, // number (coordinate), defaults to 0 longitude: {longitude}, // number (coordinate), defaults to 0 - bearing: {bearing}, // number (degree), defaults to 0 - ratio: {ratio} // number (scale factor), defaults to 1.0 + bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0 + ratio: {ratio} // number (scale factor), defaults to 1.0, + classes: {classes} // array of strings, optional * } ``` +* More about classes: https://github.com/mapbox/mapbox-gl-js/blob/master/API.md#working-with-style-classes + +## Mapbox API Access tokens + +To use styles that rely on Mapbox vector tiles, you must register a [API access token](https://www.mapbox.com/developers/api/#access-tokens) on the map. Use the `setAccessToken` method before load: + +```js +var map = mbgl.Map(fileSource); +var style = mapboxStyle; // includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` +map.setAccessToken(process.env.MAPBOX_ACCESS_TOKEN); +map.load(style); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); +}); + +``` + ## Testing ``` From 219a3ac518b774ab3d6ecbb3e775ea701410d927 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Wed, 4 Mar 2015 15:15:33 -0500 Subject: [PATCH 060/760] fix example --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 618f99f57..4aa9d42b9 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,8 @@ var fileSource = new mbgl.FileSource(); fileSource.request = function(req) { request({ url: req.url, - encoding: null + encoding: null, + gzip: true }, function (err, res, body) { if (req.canceled) { return; From fffb0f7df5e1a5b1ce1f75ecc466820328f86f4c Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Wed, 4 Mar 2015 15:15:33 -0500 Subject: [PATCH 061/760] fix example blah real test Still not writing tests correctly --- README.md | 3 +- package.json | 4 +- test/fixtures/style-gz.json | 26 +++++++ test/fixtures/tiles-gz.tilejson | 13 ++++ test/fixtures/tiles/0-0-0.vector.pbf.gz | Bin 0 -> 7638 bytes test/js/map.test.js | 1 + test/map-gz.test.js | 96 ++++++++++++++++++++++++ 7 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/style-gz.json create mode 100644 test/fixtures/tiles-gz.tilejson create mode 100644 test/fixtures/tiles/0-0-0.vector.pbf.gz create mode 100644 test/map-gz.test.js diff --git a/README.md b/README.md index 618f99f57..4aa9d42b9 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,8 @@ var fileSource = new mbgl.FileSource(); fileSource.request = function(req) { request({ url: req.url, - encoding: null + encoding: null, + gzip: true }, function (err, res, body) { if (req.canceled) { return; diff --git a/package.json b/package.json index 0998cc15f..1e99cdc64 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "aws-sdk": "^2.1.9", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", "mkdirp": "^0.5.0", + "request": "^2.53.0", "tap-spec": "^2.2.1", - "tape": "^3.5.0" + "tape": "^3.5.0", + "st": "^0.5.3" }, "scripts": { "install": "node-pre-gyp install || make", diff --git a/test/fixtures/style-gz.json b/test/fixtures/style-gz.json new file mode 100644 index 000000000..c47715490 --- /dev/null +++ b/test/fixtures/style-gz.json @@ -0,0 +1,26 @@ +{ + "version": 7, + "name": "Empty", + "sources": { + "mapbox": { + "type": "vector", + "url": "./fixtures/tiles-gz.tilejson", + "maxzoom": 15 + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "aqua" + } + }, { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "red" + } + }] +} diff --git a/test/fixtures/tiles-gz.tilejson b/test/fixtures/tiles-gz.tilejson new file mode 100644 index 000000000..55dd96d6c --- /dev/null +++ b/test/fixtures/tiles-gz.tilejson @@ -0,0 +1,13 @@ +{ + "bounds": [ -180, -85.0511, 180, 85.0511 ], + "center": [ 0, 0, 0 ], + "format": "pbf", + "id": "mapbox.mapbox-streets-v6-dev", + "maskLevel": 8, + "maxzoom": 15, + "minzoom": 0, + "name": "Mapbox Streets V6", + "scheme": "xyz", + "tilejson": "2.0.0", + "tiles": [ "./fixtures/tiles/{z}-{x}-{y}.vector.pbf.gz" ] +} diff --git a/test/fixtures/tiles/0-0-0.vector.pbf.gz b/test/fixtures/tiles/0-0-0.vector.pbf.gz new file mode 100644 index 0000000000000000000000000000000000000000..711659c101a8a0aa1d5e1d20bb8fa6daf5ca369a GIT binary patch literal 7638 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 Date: Sat, 7 Mar 2015 18:04:09 -0500 Subject: [PATCH 062/760] add working tests for gzip handling --- Makefile | 2 +- README.md | 37 ++++++++++++------------ test/fixtures/style-gz.json | 26 ----------------- test/fixtures/tiles-gz.tilejson | 13 --------- test/fixtures/tiles/0-0-0.vector.pbf.gz | Bin 7638 -> 0 bytes test/{map-gz.test.js => gz.test.js} | 35 +++++++++------------- 6 files changed, 33 insertions(+), 80 deletions(-) delete mode 100644 test/fixtures/style-gz.json delete mode 100644 test/fixtures/tiles-gz.tilejson delete mode 100644 test/fixtures/tiles/0-0-0.vector.pbf.gz rename test/{map-gz.test.js => gz.test.js} (68%) diff --git a/Makefile b/Makefile index 52ce7db88..b2029c442 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: - -@(`npm bin`/tape test/render.test.js | `npm bin`/tap-spec) + -@(`npm bin`/tape test/*.test.js | `npm bin`/tap-spec) .PHONY: test-js test-js: diff --git a/README.md b/README.md index 4aa9d42b9..ede241bc3 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,11 @@ The first argument passed to `map.render` is an options object, all keys are opt longitude: {longitude}, // number (coordinate), defaults to 0 bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0 ratio: {ratio} // number (scale factor), defaults to 1.0, - classes: {classes} // array of strings, optional * + classes: {classes} // array of strings, optional } ``` -* More about classes: https://github.com/mapbox/mapbox-gl-js/blob/master/API.md#working-with-style-classes - -## Mapbox API Access tokens - -To use styles that rely on Mapbox vector tiles, you must register a [API access token](https://www.mapbox.com/developers/api/#access-tokens) on the map. Use the `setAccessToken` method before load: - -```js -var map = mbgl.Map(fileSource); -var style = mapboxStyle; // includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` -map.setAccessToken(process.env.MAPBOX_ACCESS_TOKEN); -map.load(style); -map.render({}, function(err, image) { - if (err) throw err; - fs.writeFileSync('image.png', image); -}); - -``` +_More about classes: https://github.com/mapbox/mapbox-gl-js/blob/master/API.md#working-with-style-classes_ ## Testing @@ -170,3 +154,20 @@ fileSource.cancel = function(req) { ``` Note that in reality, Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of FileSource supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3. + +## Mapbox API Access tokens + +To use styles that rely on Mapbox vector tiles, you must register a [API access token](https://www.mapbox.com/developers/api/#access-tokens) on the map. Use the `setAccessToken` method before load: + +```js +var map = mbgl.Map(fileSource); +var style = mapboxStyle; // includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` +map.setAccessToken(process.env.MAPBOX_ACCESS_TOKEN); +map.load(style); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); +}); + +``` + diff --git a/test/fixtures/style-gz.json b/test/fixtures/style-gz.json deleted file mode 100644 index c47715490..000000000 --- a/test/fixtures/style-gz.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": 7, - "name": "Empty", - "sources": { - "mapbox": { - "type": "vector", - "url": "./fixtures/tiles-gz.tilejson", - "maxzoom": 15 - } - }, - "layers": [{ - "id": "background", - "type": "background", - "paint": { - "background-color": "aqua" - } - }, { - "id": "water", - "type": "fill", - "source": "mapbox", - "source-layer": "water", - "paint": { - "fill-color": "red" - } - }] -} diff --git a/test/fixtures/tiles-gz.tilejson b/test/fixtures/tiles-gz.tilejson deleted file mode 100644 index 55dd96d6c..000000000 --- a/test/fixtures/tiles-gz.tilejson +++ /dev/null @@ -1,13 +0,0 @@ -{ - "bounds": [ -180, -85.0511, 180, 85.0511 ], - "center": [ 0, 0, 0 ], - "format": "pbf", - "id": "mapbox.mapbox-streets-v6-dev", - "maskLevel": 8, - "maxzoom": 15, - "minzoom": 0, - "name": "Mapbox Streets V6", - "scheme": "xyz", - "tilejson": "2.0.0", - "tiles": [ "./fixtures/tiles/{z}-{x}-{y}.vector.pbf.gz" ] -} diff --git a/test/fixtures/tiles/0-0-0.vector.pbf.gz b/test/fixtures/tiles/0-0-0.vector.pbf.gz deleted file mode 100644 index 711659c101a8a0aa1d5e1d20bb8fa6daf5ca369a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7638 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 fs.statSync('test/results/image-gz-fail.png')['size'], 'render is smaller than unpacked render'); server.close(t.end); }); }); From 7864dca80ad3e3522dfbd663b3d9a35cb431c244 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Sat, 7 Mar 2015 18:30:39 -0500 Subject: [PATCH 063/760] remove unused --- test/js/map.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/js/map.test.js b/test/js/map.test.js index 63a9d3919..50bab644d 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -7,7 +7,6 @@ var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); -var suitePath = path.dirname(require.resolve('mapbox-gl-test-suite/package.json')); mkdirp.sync('test/results'); From 71c724eaf394213d146459234b00e447af18e52c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 9 Mar 2015 22:08:13 -0400 Subject: [PATCH 064/760] only fail on pbf request, check exact error --- test/{ => js}/gz.test.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) rename test/{ => js}/gz.test.js (80%) diff --git a/test/gz.test.js b/test/js/gz.test.js similarity index 80% rename from test/gz.test.js rename to test/js/gz.test.js index 299b61dfe..a38f505af 100644 --- a/test/gz.test.js +++ b/test/js/gz.test.js @@ -3,18 +3,18 @@ /* jshint node: true */ var test = require('tape'); -var mbgl = require('../'); +var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); var http = require('http'); var request = require('request'); var st = require('st'); -var style = require('../test/fixtures/style.json'); +var style = require('../../test/fixtures/style.json'); mkdirp.sync('test/results'); -var server = http.createServer(st({path: __dirname})); +var server = http.createServer(st({path: __dirname + '/..'})); server.listen(2900); function setup(fileSource, callback) { @@ -25,10 +25,13 @@ function getFileSource(gzip, t) { var fileSource = new mbgl.FileSource(); fileSource.request = function(req) { + var parts = req.url.split('.'); + var filetype = parts[parts.length - 1]; + request({ url: 'http://localhost:2900' + path.join('/', req.url), encoding: null, - gzip: gzip, + gzip: filetype === 'pbf' ? gzip : true, headers: { 'Accept-Encoding': 'gzip' } @@ -52,7 +55,7 @@ test('before tests', function(t) { test('unpacked gzip', function(t) { mbgl.on('message', function(msg) { - t.error(msg, 'doesn\'t emit warning'); + t.error(msg); }); setup(getFileSource(true, t), function(map) { @@ -70,7 +73,10 @@ test('unpacked gzip', function(t) { test('unhandled gzip', function(t) { mbgl.on('message', function(msg) { - t.ok(msg, 'emits warning'); + t.ok(msg, 'emits error'); + t.equal(msg.class, 'ParseTile'); + t.equal(msg.severity, 'ERROR'); + t.equal(msg.text, 'Parsing [0/0/0] failed: pbf unknown field type exception'); }); setup(getFileSource(false, t), function(map) { From 0cf80f34b255c4ab2dbd895184f29c2121019ecc Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 10 Mar 2015 00:45:43 -0400 Subject: [PATCH 065/760] EventEmitter.once in unhandled gzip --- test/js/gz.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/gz.test.js b/test/js/gz.test.js index a38f505af..e6ca13a9b 100644 --- a/test/js/gz.test.js +++ b/test/js/gz.test.js @@ -72,7 +72,7 @@ test('unpacked gzip', function(t) { test('unhandled gzip', function(t) { - mbgl.on('message', function(msg) { + mbgl.once('message', function(msg) { t.ok(msg, 'emits error'); t.equal(msg.class, 'ParseTile'); t.equal(msg.severity, 'ERROR'); From 39694e5bf041e725009516573570d8d8f95ddfb7 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Tue, 10 Mar 2015 17:46:00 -0400 Subject: [PATCH 066/760] cleanup and add image comparisons to gz tests --- .gitignore | 3 +- test/js/gz.test.js | 115 +++++++++++++++++++++++++++--- test/results/gz-fail-expected.png | Bin 0 -> 5636 bytes 3 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 test/results/gz-fail-expected.png diff --git a/.gitignore b/.gitignore index cf1aea98a..80e156730 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /build /lib /node_modules -/test/results +/test/results/* +!/test/results/*-expected.png image.png diff --git a/test/js/gz.test.js b/test/js/gz.test.js index e6ca13a9b..228897edc 100644 --- a/test/js/gz.test.js +++ b/test/js/gz.test.js @@ -10,9 +10,11 @@ var mkdirp = require('mkdirp'); var http = require('http'); var request = require('request'); var st = require('st'); +var spawn = require('child_process').spawn; var style = require('../../test/fixtures/style.json'); +var dir = 'test/results'; -mkdirp.sync('test/results'); +mkdirp.sync(dir); var server = http.createServer(st({path: __dirname + '/..'})); server.listen(2900); @@ -61,10 +63,54 @@ test('unpacked gzip', function(t) { setup(getFileSource(true, t), function(map) { map.load(style); map.render({}, function(err, image) { - mbgl.compressPNG(image, function(err, data) { - fs.writeFileSync('test/results/image-gz-success.png', data); - mbgl.removeAllListeners('message'); - t.end(); + mbgl.compressPNG(image, function(err, image) { + t.error(err); + + var expected = path.join(dir, 'gz-success-expected.png'); + var actual = path.join(dir, 'gz-success-actual.png'); + var diff = path.join(dir, 'gz-success-diff.png'); + + if (process.env.UPDATE) { + fs.writeFile(expected, image, function(err) { + t.error(err); + mbgl.removeAllListeners('message'); + t.end(); + }); + } else { + fs.writeFile(actual, image, function(err) { + + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; + + compare.stderr.on('data', function(data) { + error += data.toString(); + }); + + compare.on('error', function(err) { + t.error(err); + }); + + compare.on('exit', function (code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + writeResult(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + writeResult(match ? '' : error, difference); + } + }); + + compare.stdin.end(); + + function writeResult(error, difference) { + t.ok(difference <= 0.001, 'actual matches expected'); + mbgl.removeAllListeners('message'); + t.end(); + } + + }); + } }); }); }); @@ -72,22 +118,69 @@ test('unpacked gzip', function(t) { test('unhandled gzip', function(t) { - mbgl.once('message', function(msg) { + mbgl.on('message', function(msg) { t.ok(msg, 'emits error'); t.equal(msg.class, 'ParseTile'); t.equal(msg.severity, 'ERROR'); - t.equal(msg.text, 'Parsing [0/0/0] failed: pbf unknown field type exception'); + t.equal(msg.text.indexOf('failed: pbf unknown field type exception') !== -1, true); }); setup(getFileSource(false, t), function(map) { map.load(style); map.render({}, function(err, image) { - mbgl.compressPNG(image, function(err, data) { - fs.writeFileSync('test/results/image-gz-fail.png', data); - t.ok(fs.statSync('test/results/image-gz-success.png')['size'] > fs.statSync('test/results/image-gz-fail.png')['size'], 'render is smaller than unpacked render'); - server.close(t.end); + mbgl.compressPNG(image, function(err, image) { + t.error(err); + + var expected = path.join(dir, 'gz-fail-expected.png'); + var actual = path.join(dir, 'gz-fail-actual.png'); + var diff = path.join(dir, 'gz-fail-diff.png'); + + if (process.env.UPDATE) { + fs.writeFile(expected, image, function(err) { + t.error(err); + server.close(t.end); + }); + } else { + fs.writeFile(actual, image, function(err) { + + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; + + compare.stderr.on('data', function(data) { + error += data.toString(); + }); + + compare.on('error', function(err) { + t.error(err); + }); + + compare.on('exit', function (code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + writeResult(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + writeResult(match ? '' : error, difference); + } + }); + + compare.stdin.end(); + + function writeResult(error, difference) { + t.ok(difference <= 0.001, 'actual matches expected'); + mbgl.removeAllListeners('message'); + server.close(t.end); + } + + }); + } }); }); }); }); + + + + diff --git a/test/results/gz-fail-expected.png b/test/results/gz-fail-expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb90d8b8fd296d0f46fff1bf364d55cec915dac GIT binary patch literal 5636 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4Yzkn2Dage(c!@6@aFBup__j$TF zhEy=Vz2+#$puofIcwqnYLkW+!@ojpkTzya1y@7#|iG^bci=Woo4J;f23Jwhi#0?m1 zm_VWe99$9%2QRs*Ie=8LHOK>Hfec4RW`&4dC%~!?FnnMJGZZuu7+Ac;XM&BBV5ozb z%fiajknmDT7_5=`06)llhQ5bDIgmZ5*U7ftx^yWF<@v~+U*0@$irX{wu+^R ziOpd{$wW?&#s|hr<1RP-Cn5Jh0)@Z=+2W;o4V@E@FG|i4?u+h?Bw44FVdQ&MBb@0Qlf}hyVZp literal 0 HcmV?d00001 From b5f894298342003075d8c1f7e1406cab23d806c2 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Tue, 10 Mar 2015 19:51:59 -0400 Subject: [PATCH 067/760] cleanup tests --- .gitignore | 4 +- test/compare.js | 31 +++++++ test/expected/expected.png | Bin 0 -> 2381 bytes .../gz-fail-expected.png | Bin test/expected/gz-success.png | Bin 0 -> 41488 bytes test/js/gz.test.js | 86 ++++-------------- test/js/map.test.js | 4 +- test/render.test.js | 44 ++------- 8 files changed, 64 insertions(+), 105 deletions(-) create mode 100644 test/compare.js create mode 100644 test/expected/expected.png rename test/{results => expected}/gz-fail-expected.png (100%) create mode 100644 test/expected/gz-success.png diff --git a/.gitignore b/.gitignore index 80e156730..247901be6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /build /lib /node_modules -/test/results/* -!/test/results/*-expected.png +/test/actual/ +/test/results image.png diff --git a/test/compare.js b/test/compare.js new file mode 100644 index 000000000..1f9dc234e --- /dev/null +++ b/test/compare.js @@ -0,0 +1,31 @@ +var spawn = require('child_process').spawn; +var fs = require('fs'); + +function compare(actual, expected, diff, t, callback) { + var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); + var error = ''; + + compare.stderr.on('data', function(data) { + error += data.toString(); + }); + + compare.on('error', function(err) { + t.error(err); + }); + + compare.on('exit', function (code) { + // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. + if (code === 2) { + callback(error.trim(), Infinity); + } else { + var match = error.match(/^\d+(?:\.\d+)?\s+\(([^\)]+)\)\s*$/); + var difference = match ? parseFloat(match[1]) : Infinity; + callback(match ? '' : error, difference); + } + }); + + compare.stdin.end(); + +} + +module.exports = compare; diff --git a/test/expected/expected.png b/test/expected/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a18f56aff39b8b6a0ed02d4ff51c59b60984caf9 GIT binary patch literal 2381 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 z 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 Date: Tue, 10 Mar 2015 20:24:56 -0400 Subject: [PATCH 068/760] more cleanup --- .gitignore | 2 +- test/compare.js | 1 - test/expected/expected.png | Bin 2381 -> 0 bytes test/render.test.js | 12 +++++------- 4 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 test/expected/expected.png diff --git a/.gitignore b/.gitignore index 247901be6..8334ef326 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /build /lib /node_modules -/test/actual/ +/test/actual /test/results image.png diff --git a/test/compare.js b/test/compare.js index 1f9dc234e..12a46f5cd 100644 --- a/test/compare.js +++ b/test/compare.js @@ -1,5 +1,4 @@ var spawn = require('child_process').spawn; -var fs = require('fs'); function compare(actual, expected, diff, t, callback) { var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); diff --git a/test/expected/expected.png b/test/expected/expected.png deleted file mode 100644 index a18f56aff39b8b6a0ed02d4ff51c59b60984caf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2381 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(m}l9Aa^H*b?0PWGB9wmd%8G= zR4~51& Date: Tue, 10 Mar 2015 20:38:19 -0400 Subject: [PATCH 069/760] fix names --- test/expected/{gz-fail-expected.png => gz-fail.png} | Bin test/js/gz.test.js | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename test/expected/{gz-fail-expected.png => gz-fail.png} (100%) diff --git a/test/expected/gz-fail-expected.png b/test/expected/gz-fail.png similarity index 100% rename from test/expected/gz-fail-expected.png rename to test/expected/gz-fail.png diff --git a/test/js/gz.test.js b/test/js/gz.test.js index 8c39a93ef..4c198061d 100644 --- a/test/js/gz.test.js +++ b/test/js/gz.test.js @@ -108,8 +108,8 @@ test('unhandled gzip', function(t) { mbgl.compressPNG(image, function(err, image) { t.error(err); - var expected = path.join(expectedDir, 'gz-fail-expected.png'); - var actual = path.join(actualDir, 'gz-fail-actual.png'); + var expected = path.join(expectedDir, 'gz-fail.png'); + var actual = path.join(actualDir, 'gz-fail.png'); var diff = path.join(actualDir, 'gz-fail-diff.png'); if (process.env.UPDATE) { From 204517a6027c88ef7c5d85dfe12a0a8d648df311 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Tue, 10 Mar 2015 20:46:49 -0400 Subject: [PATCH 070/760] try less strict difference? --- test/js/gz.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/js/gz.test.js b/test/js/gz.test.js index 4c198061d..75b569ab6 100644 --- a/test/js/gz.test.js +++ b/test/js/gz.test.js @@ -82,7 +82,7 @@ test('unpacked gzip', function(t) { fs.writeFile(actual, image, function(err) { t.error(err); compare(actual, expected, diff, t, function(error, difference) { - t.ok(difference <= 0.001, 'actual matches expected'); + t.ok(difference <= 0.1, 'actual matches expected'); mbgl.removeAllListeners('message'); t.end(); }); @@ -121,7 +121,7 @@ test('unhandled gzip', function(t) { fs.writeFile(actual, image, function(err) { t.error(err); compare(actual, expected, diff, t, function(error, difference) { - t.ok(difference <= 0.001, 'actual matches expected'); + t.ok(difference <= 0.1, 'actual matches expected'); mbgl.removeAllListeners('message'); server.close(t.end); }); From 7ef8550feafc31c48362c6e84f72457954fc48e1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 11 Mar 2015 16:47:40 -0400 Subject: [PATCH 071/760] minor organizational changes, compare Map test image --- .gitignore | 11 +- Makefile | 2 +- test/compare.js | 7 +- .../{gz-success.png => gzip/success.png} | Bin .../{gz-fail.png => gzip/unhandled.png} | Bin test/expected/map/image.png | Bin 0 -> 41488 bytes test/js/gz.test.js | 138 ------------------ test/js/gzip.test.js | 130 +++++++++++++++++ test/js/map.test.js | 34 ++++- 9 files changed, 166 insertions(+), 156 deletions(-) rename test/expected/{gz-success.png => gzip/success.png} (100%) rename test/expected/{gz-fail.png => gzip/unhandled.png} (100%) create mode 100644 test/expected/map/image.png delete mode 100644 test/js/gz.test.js create mode 100644 test/js/gzip.test.js diff --git a/.gitignore b/.gitignore index 8334ef326..2f53e5d17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -/build -/lib -/node_modules -/test/actual -/test/results -image.png +build +lib +node_modules +test/actual +test/diff diff --git a/Makefile b/Makefile index b2029c442..52ce7db88 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: - -@(`npm bin`/tape test/*.test.js | `npm bin`/tap-spec) + -@(`npm bin`/tape test/render.test.js | `npm bin`/tap-spec) .PHONY: test-js test-js: diff --git a/test/compare.js b/test/compare.js index 12a46f5cd..aaac14f36 100644 --- a/test/compare.js +++ b/test/compare.js @@ -1,6 +1,6 @@ var spawn = require('child_process').spawn; -function compare(actual, expected, diff, t, callback) { +module.exports = function compare(actual, expected, diff, t, callback) { var compare = spawn('compare', ['-metric', 'MAE', actual, expected, diff]); var error = ''; @@ -24,7 +24,4 @@ function compare(actual, expected, diff, t, callback) { }); compare.stdin.end(); - -} - -module.exports = compare; +}; diff --git a/test/expected/gz-success.png b/test/expected/gzip/success.png similarity index 100% rename from test/expected/gz-success.png rename to test/expected/gzip/success.png diff --git a/test/expected/gz-fail.png b/test/expected/gzip/unhandled.png similarity index 100% rename from test/expected/gz-fail.png rename to test/expected/gzip/unhandled.png diff --git a/test/expected/map/image.png b/test/expected/map/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b39617e1f6a3d54d2d8b527c2fac500170703392 GIT binary patch literal 41488 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 z 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 Date: Thu, 12 Mar 2015 09:24:36 -0700 Subject: [PATCH 072/760] reduce diff sensitivity so tests pass on linux, mkdirp.sync --- test/js/gzip.test.js | 4 ++-- test/js/map.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index f401f4059..6866db059 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -20,7 +20,7 @@ server.listen(0); function filePath(name) { return ['expected', 'actual', 'diff'].reduce(function(prev, key) { var dir = path.join('test', key, 'gzip'); - mkdirp(dir); + mkdirp.sync(dir); prev[key] = path.join(dir, name); return prev; }, {}); @@ -81,7 +81,7 @@ test('gzip', function(t) { fs.writeFile(filename.actual, image, function(err) { t.error(err); compare(filename.actual, filename.expected, filename.diff, t, function(error, difference) { - t.ok(difference <= 0.001, 'actual matches expected'); + t.ok(difference <= 0.01, 'actual matches expected'); t.end(); }); }); diff --git a/test/js/map.test.js b/test/js/map.test.js index 38729413e..5b9a751a5 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -13,7 +13,7 @@ var compare = require('../compare.js'); function filePath(name) { return ['expected', 'actual', 'diff'].reduce(function(prev, key) { var dir = path.join('test', key, 'map'); - mkdirp(dir); + mkdirp.sync(dir); prev[key] = path.join(dir, name); return prev; }, {}); @@ -196,7 +196,7 @@ test('Map', function(t) { fs.writeFile(filename.actual, image, function(err) { t.error(err); compare(filename.actual, filename.expected, filename.diff, t, function(error, difference) { - t.ok(difference <= 0.001, 'actual matches expected'); + t.ok(difference <= 0.01, 'actual matches expected'); t.end(); }); }); From c0c0f2667bc798246d18cfa3f545a8db40fb2de2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 13 Mar 2015 12:57:06 -0400 Subject: [PATCH 073/760] accept string or array of strings for source url --- test/render.test.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/render.test.js b/test/render.test.js index 0dea16a9d..23fa455f5 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -93,8 +93,15 @@ function renderTest(style, info, base, key) { }; } -function rewriteLocalSchema(uri) { - return uri.replace(/^local:\/\//, ''); +function rewriteLocalSchema(url) { + var regex = /^local:\/\//; + if (url instanceof Array) { + return url.map(function(str) { + return str.replace(regex, ''); + }); + } else if (url instanceof String) { + return url.replace(regex, ''); + } } var tests; From 0c745d68259b40d3f0d8995f8bcc4874fb03cf0f Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 12 Mar 2015 16:18:29 -0400 Subject: [PATCH 074/760] catch errors only in gzip tests, not all log messages --- test/js/gzip.test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 6866db059..6fabc09bd 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -60,7 +60,7 @@ function getFileSource(gzip, t) { test('gzip', function(t) { t.test('success', function(t) { mbgl.on('message', function(msg) { - t.error(msg); + if (msg.severity == 'ERROR') t.error(msg); }); setup(getFileSource(true, t), function(map) { @@ -92,16 +92,19 @@ test('gzip', function(t) { }); t.test('unhandled', function(t) { - mbgl.once('message', function(msg) { - t.ok(msg, 'emits error'); - t.equal(msg.class, 'ParseTile'); - t.equal(msg.severity, 'ERROR'); - t.ok(msg.text.match(/failed: pbf unknown field type exception/), 'pbf unknown field type exception'); + mbgl.on('message', function(msg) { + if (msg.severity == 'ERROR') { + t.ok(msg, 'emits error'); + t.equal(msg.class, 'ParseTile'); + t.equal(msg.severity, 'ERROR'); + t.ok(msg.text.match(/failed: pbf unknown field type exception/), 'pbf unknown field type exception'); + } }); setup(getFileSource(false, t), function(map) { map.load(style); map.render({}, function(err, image) { + mbgl.removeAllListeners('message'); mbgl.compressPNG(image, function(err, image) { t.error(err); From 99edf8eb722988b88d6c233fee8447f3ab1b6a90 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 13 Mar 2015 13:07:33 -0400 Subject: [PATCH 075/760] fail if no error is emitted in gzip unhandled test --- test/js/gzip.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 6fabc09bd..6a7fda6ea 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -92,8 +92,11 @@ test('gzip', function(t) { }); t.test('unhandled', function(t) { + var errorEmitted = false; + mbgl.on('message', function(msg) { if (msg.severity == 'ERROR') { + errorEmitted = true; t.ok(msg, 'emits error'); t.equal(msg.class, 'ParseTile'); t.equal(msg.severity, 'ERROR'); @@ -104,6 +107,7 @@ test('gzip', function(t) { setup(getFileSource(false, t), function(map) { map.load(style); map.render({}, function(err, image) { + if (!errorEmitted) t.fail('no error emitted'); mbgl.removeAllListeners('message'); mbgl.compressPNG(image, function(err, image) { t.error(err); From ffc937b18476304a4833ba6728a94c5f6f178973 Mon Sep 17 00:00:00 2001 From: samanpwbb Date: Sat, 14 Mar 2015 23:25:46 -0400 Subject: [PATCH 076/760] center not lat lng --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ede241bc3..1cf870919 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,7 @@ The first argument passed to `map.render` is an options object, all keys are opt zoom: {zoom}, // number, defaults to 0 width: {width}, // number (px), defaults to 512 height: {height}, // number (px), defaults to 512 - latitude: {latitude}, // number (coordinate), defaults to 0 - longitude: {longitude}, // number (coordinate), defaults to 0 + center: [{latitude}, {longitude}], // array of numbers (coordinates), defaults to [0,0] bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0 ratio: {ratio} // number (scale factor), defaults to 1.0, classes: {classes} // array of strings, optional From d42076528ee9f4e17acdaecb68d3e95155db63ec Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Mar 2015 17:07:52 -0400 Subject: [PATCH 077/760] string literal is not instanceof String JavaScript is dumb. --- test/render.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/render.test.js b/test/render.test.js index 23fa455f5..cb62bf0a9 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -99,7 +99,7 @@ function rewriteLocalSchema(url) { return url.map(function(str) { return str.replace(regex, ''); }); - } else if (url instanceof String) { + } else if (typeof url === "string") { return url.replace(regex, ''); } } From 9a297051c7fca8e6f48c9365253d22885c9f78e1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 5 Mar 2015 16:23:11 -0500 Subject: [PATCH 078/760] adapt NodeFileSource around mbgl::Environment additions --- package.json | 3 ++- src/node_file_source.cpp | 11 +++++++---- src/node_file_source.hpp | 5 +++-- vendor/mbgl | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 1e99cdc64..698d8d1fc 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "scripts": { "install": "node-pre-gyp install || make", "test": "make test", - "rebuild": "make clean && make" + "rebuild": "make clean && make", + "build": "make" }, "gypfile": true, "binary": { diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 6ce81c064..9621132a8 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -63,8 +63,8 @@ NodeFileSource::~NodeFileSource() { 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)); +mbgl::Request *NodeFileSource::request(const mbgl::Resource &resource, uv_loop_t *loop, const mbgl::Environment &env, Callback callback) { + auto request = new mbgl::Request(resource, loop, env, 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(). @@ -72,8 +72,8 @@ mbgl::Request *NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t return request; } -void NodeFileSource::request(const mbgl::Resource &resource, Callback callback) { - auto request = new mbgl::Request(resource, nullptr, std::move(callback)); +void NodeFileSource::request(const mbgl::Resource &resource, const mbgl::Environment &env, Callback callback) { + auto request = new mbgl::Request(resource, nullptr, env, 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(). @@ -89,6 +89,9 @@ void NodeFileSource::cancel(mbgl::Request *request) { queue->send(Action{ Action::Cancel, request }); } +void NodeFileSource::abort(const mbgl::Environment &env) { + // no-op +} void NodeFileSource::processAdd(mbgl::Request *request) { NanScope(); diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 293dcf63c..5c030180a 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -26,9 +26,10 @@ public: NodeFileSource(); ~NodeFileSource(); - mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, Callback callback); - void request(const mbgl::Resource &resource, Callback callback); + mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, const mbgl::Environment &env, Callback callback); + void request(const mbgl::Resource &resource, const mbgl::Environment &env, Callback callback); void cancel(mbgl::Request *request); + void abort(const mbgl::Environment &env); // visiblity? diff --git a/vendor/mbgl b/vendor/mbgl index bb8cedfdb..c5b124f72 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit bb8cedfdbf805b80688bba04e24970e9868858af +Subproject commit c5b124f7275625993c9cb9369166490d520440ac From d9494f7366e4e565947f4c24d6775c745bffd804 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Mar 2015 17:59:03 -0400 Subject: [PATCH 079/760] add changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..27866532e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# 1.0.1 + +- Adapts NodeFileSource around mbgl::Environment additions. +- Adapts to minor changes in mapbox-gl-test-suite. +- Adds tests for gzipped vector tile handling. +- Cleans up documentation. + +# 1.0.0 + +- Initial release. From 993d760675260ed18fc758cb6e48d5672e617117 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Mar 2015 22:57:20 -0400 Subject: [PATCH 080/760] 1.0.1 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 698d8d1fc..4fbc31229 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.0.0", + "version": "1.0.1", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From d9c446a075fa492c1b45a4b012714cf1f3ab503f Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 1 Apr 2015 16:20:06 -0400 Subject: [PATCH 081/760] init display connection on module load add sudo: required move source up to initialization on NodeMap static sharedDisplay method --- .travis.yml | 2 ++ src/node_map.cpp | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index fa16fd0c2..c2e1ef493 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,3 +39,5 @@ after_success: git: submodules: true + +sudo: required diff --git a/src/node_map.cpp b/src/node_map.cpp index 2e2ef7f98..e3acbeea7 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -5,7 +5,7 @@ #include #include - #include +#include namespace node_mbgl { @@ -25,6 +25,11 @@ struct NodeMap::RenderOptions { v8::Persistent NodeMap::constructorTemplate; +static std::shared_ptr sharedDisplay() { + static auto display = std::make_shared(); + return display; +} + void NodeMap::Init(v8::Handle target) { NanScope(); @@ -40,6 +45,9 @@ void NodeMap::Init(v8::Handle target) { NanAssignPersistent(constructorTemplate, t); target->Set(NanNew("Map"), t->GetFunction()); + + // Initialize display connection on module load. + sharedDisplay(); } NAN_METHOD(NodeMap::New) { @@ -63,8 +71,13 @@ NAN_METHOD(NodeMap::New) { return NanThrowError("FileSource must have a cancel member function"); } - auto map = new NodeMap(args[0]->ToObject()); - map->Wrap(args.This()); + + try { + auto map = new NodeMap(source); + map->Wrap(args.This()); + } catch(std::exception &ex) { + return NanThrowError(ex.what()); + } NanReturnValue(args.This()); } @@ -273,17 +286,13 @@ void NodeMap::renderFinished() { //////////////////////////////////////////////////////////////////////////////////////////////// // Instance -std::shared_ptr sharedDisplay() { - static auto display = std::make_shared(); - return display; -} - NodeMap::NodeMap(v8::Handle source_) : + source(source_), view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), map(view, fs, mbgl::Map::RenderMode::Still), async(new uv_async_t) { - source = v8::Persistent::New(source_); + async->data = this; uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { reinterpret_cast(as->data)->renderFinished(); From 3090f498fde8acdc2f2d4f6657bcffe93640a204 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 2 Apr 2015 17:55:23 -0400 Subject: [PATCH 082/760] 1.0.2 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4fbc31229..8581716c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.0.1", + "version": "1.0.2", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 186fbad2365b281281baf2d748ce583c5b37ccc9 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 2 Apr 2015 17:59:00 -0400 Subject: [PATCH 083/760] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27866532e..533e7df40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.2 + +- Initialize shared display connection at module load time to avoid race condition when display connection is initialized on-demand. + # 1.0.1 - Adapts NodeFileSource around mbgl::Environment additions. From ca0da9626e890dfee709b1ca31b4fc2c8aa2c36e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 3 Apr 2015 15:43:40 -0400 Subject: [PATCH 084/760] NanAssignPersistent source in NodeMap constructor, fixes #109 --- src/node_map.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index e3acbeea7..2dd46e549 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -287,12 +287,13 @@ void NodeMap::renderFinished() { // Instance NodeMap::NodeMap(v8::Handle source_) : - source(source_), view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), map(view, fs, mbgl::Map::RenderMode::Still), async(new uv_async_t) { + NanAssignPersistent(source, source_); + async->data = this; uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { reinterpret_cast(as->data)->renderFinished(); From 97c7912f021ac74c9474e83a0a5e5c2795c793ab Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 3 Apr 2015 15:46:24 -0400 Subject: [PATCH 085/760] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 533e7df40..6d604b678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.0.3 + +- Fixes crash during garbage collection by assigning FileSource handle to a v8::Persistent in NodeMap constructor. + # 1.0.2 - Initialize shared display connection at module load time to avoid race condition when display connection is initialized on-demand. From 5091259c9df558a609c505f0afedb05c4604492b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 3 Apr 2015 15:55:26 -0400 Subject: [PATCH 086/760] 1.0.3 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8581716c0..65e68b610 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.0.2", + "version": "1.0.3", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 40d47bc2c6a1815e590c8203a5539b46d811e2a5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 8 Apr 2015 18:00:15 -0400 Subject: [PATCH 087/760] bump mbgl to fix glyph range end --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index c5b124f72..c64f67369 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit c5b124f7275625993c9cb9369166490d520440ac +Subproject commit c64f67369ebc5c7e5b0159bf340e183817680b25 From 9363d94c36d082a3c410534b7ce34c77cb63c86d Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 8 Apr 2015 18:17:08 -0400 Subject: [PATCH 088/760] respond with error message for non-200 responses in example FileSource --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cf870919..f554e4d1f 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ fileSource.request = function(req) { response.data = body; req.respond(null, response); } else { - console.warn(new Date(res.headers.expires)); + req.respond(new Error(JSON.parse(body).message)); } }); }; From 89e226e10fe75bf16724ec4d7e786fdad544b277 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 13 Apr 2015 11:44:41 +0200 Subject: [PATCH 089/760] cherry pick fix for #110 --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index c64f67369..21ccf4374 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit c64f67369ebc5c7e5b0159bf340e183817680b25 +Subproject commit 21ccf4374cb723c9eb1e9e9d40624a49aeed71ba From ba12e514ccc83c782af40bdad5cda65eb321e012 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 16 Apr 2015 12:40:53 +0200 Subject: [PATCH 090/760] move travis template to cloudformation directroy --- .../travis-resources.template => cloudformation/travis.template | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename travis/travis-resources.template => cloudformation/travis.template (100%) diff --git a/travis/travis-resources.template b/cloudformation/travis.template similarity index 100% rename from travis/travis-resources.template rename to cloudformation/travis.template From 8671015119b416c7318ee25673d830231209c46e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 14 Apr 2015 14:43:58 +0200 Subject: [PATCH 091/760] move node log from backend to observer pattern back to gl-native master branch --- binding.gyp | 2 +- src/node_log.cpp | 38 +++++++++-------------------------- src/node_log.hpp | 17 +++++++--------- src/node_mapbox_gl_native.cpp | 2 +- vendor/mbgl | 2 +- 5 files changed, 20 insertions(+), 41 deletions(-) diff --git a/binding.gyp b/binding.gyp index 05e9347c3..a359f72bd 100644 --- a/binding.gyp +++ b/binding.gyp @@ -35,7 +35,7 @@ "CLANG_CXX_LIBRARY": "libc++", "CLANG_CXX_LANGUAGE_STANDARD":"c++11", "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0", - 'MACOSX_DEPLOYMENT_TARGET': '10.7', + 'MACOSX_DEPLOYMENT_TARGET': '10.9', 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', } diff --git a/src/node_log.cpp b/src/node_log.cpp index f39536a5a..16cfdbdf4 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -1,29 +1,27 @@ #include "node_log.hpp" -#include - namespace node_mbgl { -struct NodeLogBackend::Message { +struct NodeLogObserver::LogMessage { mbgl::EventSeverity severity; mbgl::Event event; int64_t code; std::string text; }; -NodeLogBackend::NodeLogBackend(v8::Handle target) - : queue(new Queue(uv_default_loop(), [this](Message &message) { +NodeLogObserver::NodeLogObserver(v8::Handle target) + : queue(new Queue(uv_default_loop(), [this](LogMessage &message) { NanScope(); auto msg = v8::Object::New(); 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(double(message.code))); + } if (!message.text.empty()) { msg->Set(NanNew("text"), NanNew(message.text)); } - if (message.code != 0) { - msg->Set(NanNew("code"), NanNew(double(message.code))); - } v8::Local argv[] = { NanNew("message"), msg }; auto emit = module->Get(NanNew("emit"))->ToObject(); @@ -36,29 +34,13 @@ NodeLogBackend::NodeLogBackend(v8::Handle target) queue->unref(); } -NodeLogBackend::~NodeLogBackend() { +NodeLogObserver::~NodeLogObserver() { queue->stop(); } -void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, const std::string &msg) { - queue->send({ severity, event, 0, msg }); -} - -void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, const char* format, ...) { - char msg[512]; - va_list args; - va_start(args, format); - vsnprintf(msg, 512, format, args); - va_end(args); - queue->send({ severity, event, 0, msg }); -} - -void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code) { - queue->send({ severity, event, code, "" }); -} - -void NodeLogBackend::record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) { - queue->send({ severity, event, code, msg }); +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/src/node_log.hpp b/src/node_log.hpp index 5cee4dde9..9cbd88094 100644 --- a/src/node_log.hpp +++ b/src/node_log.hpp @@ -10,23 +10,20 @@ namespace mbgl { namespace util { template class AsyncQueue; } } namespace node_mbgl { -class NodeLogBackend : public mbgl::LogBackend { +class NodeLogObserver : public mbgl::Log::Observer { public: - NodeLogBackend(v8::Handle target); - ~NodeLogBackend(); + NodeLogObserver(v8::Handle target); + ~NodeLogObserver(); - void record(mbgl::EventSeverity severity, mbgl::Event event, const std::string &msg); - void record(mbgl::EventSeverity severity, mbgl::Event event, const char* format, ...); - void record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code); - void record(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg); + // Log::Observer implementation + virtual bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) override; private: v8::Persistent module; - struct Message; - using Queue = mbgl::util::AsyncQueue; + struct LogMessage; + using Queue = mbgl::util::AsyncQueue; Queue *queue = nullptr; }; - } diff --git a/src/node_mapbox_gl_native.cpp b/src/node_mapbox_gl_native.cpp index 435c446a6..09250b784 100644 --- a/src/node_mapbox_gl_native.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -31,7 +31,7 @@ void RegisterModule(v8::Handle exports) { auto EventEmitter = process->Get(NanNew("EventEmitter"))->ToObject(); exports->SetPrototype(EventEmitter->Get(NanNew("prototype"))); - mbgl::Log::Set(exports); + mbgl::Log::setObserver(mbgl::util::make_unique(exports)); } NODE_MODULE(mapbox_gl_native, RegisterModule) diff --git a/vendor/mbgl b/vendor/mbgl index 21ccf4374..88b8d452b 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 21ccf4374cb723c9eb1e9e9d40624a49aeed71ba +Subproject commit 88b8d452b9ed2cc6c156b6b446904b86afd3c7a2 From 8e790ca322d191aeef9368bcae5d3c006da58c8c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 15 Apr 2015 17:15:45 +0200 Subject: [PATCH 092/760] log environment ID and threadName if in scope --- src/node_log.cpp | 20 ++++++++++++++++++-- vendor/mbgl | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/node_log.cpp b/src/node_log.cpp index 16cfdbdf4..1418158d1 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -1,5 +1,7 @@ #include "node_log.hpp" +#include + namespace node_mbgl { struct NodeLogObserver::LogMessage { @@ -7,6 +9,8 @@ struct NodeLogObserver::LogMessage { mbgl::Event event; int64_t code; std::string text; + int environment; + std::string threadName; }; NodeLogObserver::NodeLogObserver(v8::Handle target) @@ -17,11 +21,17 @@ NodeLogObserver::NodeLogObserver(v8::Handle target) 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(double(message.code))); + msg->Set(NanNew("code"), NanNew(message.code)); } if (!message.text.empty()) { msg->Set(NanNew("text"), NanNew(message.text)); } + if (message.environment != -1) { + msg->Set(NanNew("environment"), NanNew(message.environment)); + } + if (!message.threadName.empty()) { + msg->Set(NanNew("threadName"), NanNew(message.threadName)); + } v8::Local argv[] = { NanNew("message"), msg }; auto emit = module->Get(NanNew("emit"))->ToObject(); @@ -39,7 +49,13 @@ NodeLogObserver::~NodeLogObserver() { } bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) { - queue->send({ severity, event, code, text }); + if (mbgl::Environment::inScope()) { + int env = static_cast(mbgl::Environment::Get().getID()); + std::string threadName = mbgl::Environment::threadName(); + queue->send({ severity, event, code, text, env, threadName }); + } else { + queue->send({ severity, event, code, text, -1, "" }); + } return true; } diff --git a/vendor/mbgl b/vendor/mbgl index 88b8d452b..5972a7d16 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 88b8d452b9ed2cc6c156b6b446904b86afd3c7a2 +Subproject commit 5972a7d16e6988a856daeafd14c6751332f22bf5 From a11a92f9a480f349494be6141948db71ba7453db Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 15 Apr 2015 17:36:41 +0200 Subject: [PATCH 093/760] add LogMessage constructors for default values --- src/node_log.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/node_log.cpp b/src/node_log.cpp index 1418158d1..793a07664 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -9,8 +9,22 @@ struct NodeLogObserver::LogMessage { mbgl::Event event; int64_t code; std::string text; - int environment; - std::string threadName; + int environment = -1; + std::string threadName = ""; + + LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_, int environment_, std::string threadName_) + : severity(severity_), + event(event_), + code(code_), + text(text_), + environment(environment_), + threadName(threadName_) {}; + + 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 target) @@ -54,7 +68,7 @@ bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, std::string threadName = mbgl::Environment::threadName(); queue->send({ severity, event, code, text, env, threadName }); } else { - queue->send({ severity, event, code, text, -1, "" }); + queue->send({ severity, event, code, text }); } return true; } From 417741b13fc194249cbb764f424bbc7dca9e9145 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 16 Apr 2015 13:32:31 +0200 Subject: [PATCH 094/760] adapt to still image render mode changes https://github.com/mapbox/mapbox-gl-native/pull/1272 Some tests are hanging with no errors or log messages when map.load is passed a style but never rendered. --- src/node_map.cpp | 18 +++++------------ test/js/map.test.js | 47 +++++++++++++++++++++++++++++++++------------ vendor/mbgl | 2 +- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 2dd46e549..e717be869 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -94,10 +94,6 @@ NAN_METHOD(NodeMap::SetAccessToken) { auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - if (nodeMap->map.isRendering()) { - return NanThrowError("Map object is currently in use"); - } - try { nodeMap->map.setAccessToken(std::string { *token, size_t(token.length()) }); } catch (const std::exception &ex) { @@ -137,10 +133,6 @@ NAN_METHOD(NodeMap::Load) { auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - if (nodeMap->map.isRendering()) { - return NanThrowError("Map object is currently in use"); - } - try { nodeMap->map.setStyleJSON(style, "."); } catch (const std::exception &ex) { @@ -193,10 +185,6 @@ NAN_METHOD(NodeMap::Render) { auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - if (nodeMap->map.isRendering()) { - return NanThrowError("Map object is currently in use"); - } - assert(!nodeMap->callback); assert(!nodeMap->image); nodeMap->callback = std::unique_ptr(new NanCallback(args[1].As())); @@ -289,9 +277,11 @@ void NodeMap::renderFinished() { NodeMap::NodeMap(v8::Handle source_) : view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), - map(view, fs, mbgl::Map::RenderMode::Still), + map(view, fs), async(new uv_async_t) { + map.start(mbgl::Map::Mode::Static); + NanAssignPersistent(source, source_); async->data = this; @@ -304,6 +294,8 @@ NodeMap::NodeMap(v8::Handle source_) : } NodeMap::~NodeMap() { + map.stop(); + source.Dispose(); uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { diff --git a/test/js/map.test.js b/test/js/map.test.js index 5b9a751a5..4222f9e9a 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -88,16 +88,29 @@ test('Map', function(t) { fileSource.cancel = function() {}; t.test('requires a string or object as the first parameter', function(t) { - setup(fileSource, function(map) { - t.throws(function() { - map.load(); - }, /Requires a map style as first argument/); - - t.throws(function() { - map.load('invalid'); - }, /Expect either an object or array at root/); - t.end(); + t.test('requires a map style as first argument', function(t) { + setup(fileSource, function(map) { + t.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + t.end(); + }); }); + + t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { + mbgl.once('message', function(msg) { + t.equal(msg.severity, 'ERROR'); + t.equal(msg.class, 'ParseStyle'); + t.ok(msg.text.match(/Expect either an object or array at root/)); + t.end(); + }); + + setup(fileSource, function(map) { + map.load('invalid'); + }); + }); + + t.end(); }); t.test('accepts an empty stylesheet string', function(t) { @@ -109,7 +122,11 @@ test('Map', function(t) { }); }); - t.test('accepts a JSON stylesheet', function(t) { + t.skip('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { + mbgl.once('message', function(msg) { + console.log(msg); + }); + setup(fileSource, function(map) { t.doesNotThrow(function() { map.load(style); @@ -118,7 +135,11 @@ test('Map', function(t) { }); }); - t.test('accepts a stringified stylesheet', function(t) { + t.skip('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { + mbgl.once('message', function(msg) { + console.log(msg); + }); + setup(fileSource, function(map) { t.doesNotThrow(function() { map.load(JSON.stringify(style)); @@ -168,7 +189,7 @@ test('Map', function(t) { }); }); - t.test('requires a style to be set', function(t) { + t.skip('requires a style to be set', function(t) { setup(fileSource, function(map) { t.throws(function() { map.render({}, function() {}); @@ -205,6 +226,8 @@ test('Map', function(t) { }); }); }); + + t.end(); }); t.end(); diff --git a/vendor/mbgl b/vendor/mbgl index 5972a7d16..f221fc7af 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 5972a7d16e6988a856daeafd14c6751332f22bf5 +Subproject commit f221fc7af5b29edf87cf670acdfc9e7b775a34e1 From f2c9e9fb67854b700b61c9f80ffc26404b8606f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 17 Apr 2015 11:43:42 +0200 Subject: [PATCH 095/760] add xcode project target Use locally installed node-pre-gyp if there's no global one. This is necessary to be able to run make outside of npm. Make example executable. Change path to the request include file. Bump mbgl to master. --- Makefile | 29 +++++++++++++++++++++-------- examples/load-from-fs.js | 0 src/node_file_source.cpp | 2 +- src/node_request.cpp | 2 +- vendor/mbgl | 2 +- 5 files changed, 24 insertions(+), 11 deletions(-) mode change 100644 => 100755 examples/load-from-fs.js diff --git a/Makefile b/Makefile index 52ce7db88..d66f6ad1c 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,11 @@ JOBS = $(shell nproc) endif endif +NODE_PRE_GYP = $(shell which node-pre-gyp) +ifeq ($(NODE_PRE_GYP),) +NODE_PRE_GYP = $(shell npm bin)/node-pre-gyp +endif + # Explicitly disable the default FileSource implementation ASSET = none HTTP = none @@ -30,20 +35,28 @@ global: build .PHONY: build build: build/Makefile - @node-pre-gyp build $(DEBUG_FLAG) --clang -- -j$(JOBS) + @$(NODE_PRE_GYP) build $(DEBUG_FLAG) --clang -- -j$(JOBS) vendor/mbgl: git submodule update --init +GYP_SETTINGS = -Dmbgl=$(MBGL) \ + -Dhost=$(HOST) \ + -I$(MBGL)/config/$(HOST).gypi \ + $(LIBS_$(HOST)) \ + -Duv_static_libs= -Duv_ldflags= + .PHONY: build/Makefile build/Makefile: $(MBGL)/config/$(HOST).gypi - @node-pre-gyp configure --clang -- \ - -Dmbgl=$(MBGL) \ - -Dhost=$(HOST) \ - -I$(MBGL)/config/$(HOST).gypi \ - $(LIBS_$(HOST)) \ - -Duv_static_libs= -Duv_ldflags= \ - -Goutput_dir=. + @$(NODE_PRE_GYP) configure --clang -- $(GYP_SETTINGS) -f make + +.PHONY: build/binding.xcodeproj +build/binding.xcodeproj: $(MBGL)/config/$(HOST).gypi + @$(NODE_PRE_GYP) configure --clang -- $(GYP_SETTINGS) -f xcode + +.PHONY: xproj +xproj: build/binding.xcodeproj + open build/binding.xcodeproj $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure make -C $(MBGL) config/$*.gypi diff --git a/examples/load-from-fs.js b/examples/load-from-fs.js old mode 100644 new mode 100755 diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 9621132a8..e50237f48 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -1,7 +1,7 @@ #include "node_file_source.hpp" #include "node_request.hpp" -#include +#include #include namespace node_mbgl { diff --git a/src/node_request.cpp b/src/node_request.cpp index 415d27d82..b67fd2097 100644 --- a/src/node_request.cpp +++ b/src/node_request.cpp @@ -1,6 +1,6 @@ #include "node_request.hpp" #include "node_file_source.hpp" -#include +#include #include #include diff --git a/vendor/mbgl b/vendor/mbgl index f221fc7af..7827d4954 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit f221fc7af5b29edf87cf670acdfc9e7b775a34e1 +Subproject commit 7827d4954cea5b4fcfce3f245aae2e8c66e0ccb2 From ca897ce40786601103631b0d50ea0b5cd49afd95 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 29 Apr 2015 15:26:29 -0400 Subject: [PATCH 096/760] bump mbgl drop mbgl::Environment from NodeFileSource switch to mbgl::Map::Mode::Still --- src/node_file_source.cpp | 23 +++++++++-------------- src/node_file_source.hpp | 5 ++--- src/node_map.cpp | 2 +- test/compare.js | 2 +- vendor/mbgl | 2 +- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index e50237f48..936dcf716 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -63,8 +63,8 @@ NodeFileSource::~NodeFileSource() { queue = nullptr; } -mbgl::Request *NodeFileSource::request(const mbgl::Resource &resource, uv_loop_t *loop, const mbgl::Environment &env, Callback callback) { - auto request = new mbgl::Request(resource, loop, env, std::move(callback)); +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(). @@ -72,16 +72,7 @@ mbgl::Request *NodeFileSource::request(const mbgl::Resource &resource, uv_loop_t return request; } -void NodeFileSource::request(const mbgl::Resource &resource, const mbgl::Environment &env, Callback callback) { - auto request = new mbgl::Request(resource, nullptr, env, 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) { +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 @@ -89,8 +80,12 @@ void NodeFileSource::cancel(mbgl::Request *request) { queue->send(Action{ Action::Cancel, request }); } -void NodeFileSource::abort(const mbgl::Environment &env) { - // no-op +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::processAdd(mbgl::Request *request) { diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 5c030180a..4e2f967d6 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -26,11 +26,10 @@ public: NodeFileSource(); ~NodeFileSource(); - mbgl::Request *request(const mbgl::Resource &resource, uv_loop_t *loop, const mbgl::Environment &env, Callback callback); - void request(const mbgl::Resource &resource, const mbgl::Environment &env, Callback callback); + mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); void cancel(mbgl::Request *request); - void abort(const mbgl::Environment &env); + void request(const mbgl::Resource&, Callback); // visiblity? void notify(mbgl::Request *request, const std::shared_ptr &response); diff --git a/src/node_map.cpp b/src/node_map.cpp index e717be869..9b833fe9d 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -280,7 +280,7 @@ NodeMap::NodeMap(v8::Handle source_) : map(view, fs), async(new uv_async_t) { - map.start(mbgl::Map::Mode::Static); + map.start(mbgl::Map::Mode::Still); NanAssignPersistent(source, source_); diff --git a/test/compare.js b/test/compare.js index aaac14f36..3e5221de5 100644 --- a/test/compare.js +++ b/test/compare.js @@ -12,7 +12,7 @@ module.exports = function compare(actual, expected, diff, t, callback) { t.error(err); }); - compare.on('exit', function (code) { + compare.on('exit', function(code) { // The compare program returns 2 on error otherwise 0 if the images are similar or 1 if they are dissimilar. if (code === 2) { callback(error.trim(), Infinity); diff --git a/vendor/mbgl b/vendor/mbgl index 7827d4954..5edb52568 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 7827d4954cea5b4fcfce3f245aae2e8c66e0ccb2 +Subproject commit 5edb52568b8b7a9042ba09a85796d3f560cd1d30 From 840e22bf90bc94d31f63917c33bca761cf07ff84 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 24 Apr 2015 16:57:25 +0200 Subject: [PATCH 097/760] adapt to changes from https://github.com/mapbox/mapbox-gl-native/pull/1065 --- src/node_map.cpp | 6 +----- vendor/mbgl | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 9b833fe9d..8f75cbf17 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -277,11 +277,9 @@ void NodeMap::renderFinished() { NodeMap::NodeMap(v8::Handle source_) : view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), - map(view, fs), + map(view, fs, mbgl::MapMode::Still), async(new uv_async_t) { - map.start(mbgl::Map::Mode::Still); - NanAssignPersistent(source, source_); async->data = this; @@ -294,8 +292,6 @@ NodeMap::NodeMap(v8::Handle source_) : } NodeMap::~NodeMap() { - map.stop(); - source.Dispose(); uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { diff --git a/vendor/mbgl b/vendor/mbgl index 5edb52568..97fe50f35 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 5edb52568b8b7a9042ba09a85796d3f560cd1d30 +Subproject commit 97fe50f35dbaa653fce44aa2918ba20b65c6b704 From 3eb8ec9ca6d7519498fbc8599b3221da8bb2daab Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 27 Apr 2015 15:19:37 -0400 Subject: [PATCH 098/760] map.resize, close server after tests, not inside --- src/node_map.cpp | 2 +- test/js/gzip.test.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 8f75cbf17..b6aae1302 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -199,7 +199,7 @@ NAN_METHOD(NodeMap::Render) { } void NodeMap::startRender(std::unique_ptr options) { - view.resize(options->width, options->height, options->ratio); + map.resize(options->width, options->height, options->ratio); map.setClasses(options->classes); map.setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); map.setBearing(options->bearing); diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 6a7fda6ea..ac651a4e9 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -2,7 +2,7 @@ /* jshint node: true */ -var test = require('tape'); +var test = require('tape').test; var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); @@ -117,14 +117,14 @@ test('gzip', function(t) { if (process.env.UPDATE) { fs.writeFile(filename.expected, image, function(err) { t.error(err); - server.close(t.end); + t.end(); }); } else { fs.writeFile(filename.actual, image, function(err) { t.error(err); compare(filename.actual, filename.expected, filename.diff, t, function(error, difference) { t.ok(difference <= 0.001, 'actual matches expected'); - server.close(t.end); + t.end(); }); }); } @@ -133,5 +133,7 @@ test('gzip', function(t) { }); }); - t.end(); + t.test('teardown', function(t) { + server.close(t.end); + }); }); From 62816a9476ab6626a7201b636bf295a7823f4c60 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 5 May 2015 13:42:13 -0400 Subject: [PATCH 099/760] skip unhandled bad gzip test --- test/js/gzip.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index ac651a4e9..785c31250 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -91,7 +91,9 @@ test('gzip', function(t) { }); }); - t.test('unhandled', function(t) { + // skip this for now because map.render doesn't error properly on bad + // vector tiles + t.skip('unhandled', function(t) { var errorEmitted = false; mbgl.on('message', function(msg) { From 1eeb7b65e9acdc5f75c61a560f22c9297014f1d3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 5 May 2015 15:06:15 -0400 Subject: [PATCH 100/760] update travis script for https://github.com/mapbox/mapbox-gl-native/pull/1339 refactor mbgl travis scripts source travis_helper.sh, run setup.sh pushd/popd sudo false travis matrix with c++14 support? drop setup_mesa.sh update binding.gyp language: c drop thread_object, add async_queue fix async_queue.hpp includes std::make_unique pip install awscli --- .travis.yml | 31 +++++++++----- binding.gyp | 49 ++++++++++++++------- scripts/deploy_results.sh | 7 +++ scripts/install_node.sh | 4 ++ scripts/setup_mesa.sh | 9 ---- src/node_file_source.cpp | 2 +- src/node_file_source.hpp | 16 +++---- src/node_log.cpp | 1 + src/node_log.hpp | 7 ++- src/thread_object.cpp | 85 ------------------------------------ src/thread_object.hpp | 28 ------------ src/util/async_queue.hpp | 90 +++++++++++++++++++++++++++++++++++++++ vendor/mbgl | 2 +- 13 files changed, 169 insertions(+), 162 deletions(-) delete mode 100755 scripts/setup_mesa.sh delete mode 100644 src/thread_object.cpp delete mode 100644 src/thread_object.hpp create mode 100644 src/util/async_queue.hpp diff --git a/.travis.yml b/.travis.yml index c2e1ef493..7edbcad90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,42 @@ -language: cpp +# to prevent Travis from exporting CXX after matrix env +language: c + +sudo: false matrix: include: - os: linux compiler: clang + env: FLAVOR=linux CXX=clang++-3.5 BUILDTYPE=Release + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux compiler: gcc - env: JOBS=8 + env: FLAVOR=linux CXX=g++-4.9 BUILDTYPE=Release + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test' ] + packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: osx compiler: clang + env: FLAVOR=osx BUILDTYPE=Release env: global: - - NODE_VERSION: 0.10.35 + - NODE_VERSION: 0.10.36 - LD_LIBRARY_PATH: "/usr/local/lib" - secure: pz/HAMQpnde//JJi3f+RcW32APN6g3QyFAH41JlZwgsM5Daj9RRoXqUcNg4hEyTvlThtc5t+wQQ2ejYAjMwDu00GAzRFJ03Sm45w2fPvith9fu4crXsyPUvWUfWPC0ajTXzorN4cwFwOoMoeo9DihLwb0EC0n4T6jWdBCae3k+s= - secure: jYFAOQoMZkZVyc5AFPBKhR9oDqp5CciwCxFhVEHVjdImM+8V60loKazyw+bVLIjzKLnQbKKdiDudWL9TE1ylK/XSlF3K7o4gU2vIh5WzosKnU70Sanxd6tHt/Ui5eK+bAymKHbLdGiXIZtBZE0tML6+wgJ9vhV4ZKna5dM9tps4= before_install: -- cd ./vendor/mbgl && source ./scripts/local_mason.sh && cd ../../ -- source ./scripts/install_node.sh - source ./vendor/mbgl/scripts/travis_helper.sh -- source ./vendor/mbgl/scripts/flags.sh -- source ./vendor/mbgl/scripts/travis_before_install.sh -- source ./scripts/setup_mesa.sh +- pushd ./vendor/mbgl +- source ./scripts/${TRAVIS_OS_NAME}/install.sh +- source ./scripts/${TRAVIS_OS_NAME}/setup.sh +- popd +- source ./scripts/install_node.sh install: - npm install --build-from-source @@ -39,5 +52,3 @@ after_success: git: submodules: true - -sudo: required diff --git a/binding.gyp b/binding.gyp index a359f72bd..c138be552 100644 --- a/binding.gyp +++ b/binding.gyp @@ -21,32 +21,49 @@ 'src/node_map.cpp', 'src/node_request.hpp', 'src/node_request.cpp', + 'src/util/async_queue.hpp', ], - 'variables': { - 'cflags_cc': [ - '-Wno-unused-parameter', - ], - }, - 'conditions': [ - ['OS == "mac"', { + ['OS=="mac"', { 'xcode_settings': { - "CLANG_CXX_LIBRARY": "libc++", - "CLANG_CXX_LANGUAGE_STANDARD":"c++11", - "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0", - 'MACOSX_DEPLOYMENT_TARGET': '10.9', - 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], + 'CLANG_CXX_LIBRARY': 'libc++', + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++14', + 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', - } + 'GCC_ENABLE_CPP_RTTI': 'YES', + 'OTHER_CPLUSPLUSFLAGS': [ + '-Werror', + '-Wall', + '-Wextra', + '-Wshadow', + '-Wno-variadic-macros', + '-Wno-error=unused-parameter', + '-frtti', + '-fexceptions', + ], + 'GCC_WARN_PEDANTIC': 'YES', + 'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE', + 'MACOSX_DEPLOYMENT_TARGET': '10.9', + }, }, { 'cflags_cc': [ - '-std=c++11', + '-std=c++14', + '-Werror', + '-Wall', + '-Wextra', + '-Wno-variadic-macros', + '-Wno-error=unused-parameter', + '-frtti', '-fexceptions', - '<@(cflags_cc)', ], 'libraries': [ '<@(glfw3_ldflags)' ], - }] + }], + ['OS=="linux"', { + 'cflags_cc': [ + '-Wno-unknown-pragmas', # We are using '#pragma mark', but it is only available on Darwin. + ], + }], ], }, diff --git a/scripts/deploy_results.sh b/scripts/deploy_results.sh index 6a5bbd0ff..adbed4c2d 100755 --- a/scripts/deploy_results.sh +++ b/scripts/deploy_results.sh @@ -4,5 +4,12 @@ set -e set -o pipefail if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then + # Install and add awscli to PATH for uploading the results + mapbox_time "install_awscli" \ + pip install --user awscli + export PATH="`python -m site --user-base`/bin:${PATH}" + + mapbox_time_start "deploy_results" (cd ./node_modules/mapbox-gl-test-suite/ && ./bin/deploy_results.sh) + mapbox_time_finish fi diff --git a/scripts/install_node.sh b/scripts/install_node.sh index dcdab3521..49f81a4aa 100755 --- a/scripts/install_node.sh +++ b/scripts/install_node.sh @@ -3,7 +3,11 @@ set -e set -o pipefail +# Mason exists on PATH from sourcing mbgl install script +mapbox_time "node" \ mason install node $NODE_VERSION + export PATH="`mason prefix node $NODE_VERSION`/bin":"$PATH" + node --version npm --version diff --git a/scripts/setup_mesa.sh b/scripts/setup_mesa.sh deleted file mode 100755 index 74baffba0..000000000 --- a/scripts/setup_mesa.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then - export LD_LIBRARY_PATH=`mason prefix mesa 10.4.3`/lib:$LD_LIBRARY_PATH; - glxinfo; -fi diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 936dcf716..2406264fc 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -1,8 +1,8 @@ #include "node_file_source.hpp" #include "node_request.hpp" +#include "util/async_queue.hpp" #include -#include namespace node_mbgl { diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 4e2f967d6..4221f9875 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -7,10 +7,10 @@ #include -namespace mbgl { namespace util { template class AsyncQueue; } } - namespace node_mbgl { +namespace util { template class AsyncQueue; } + class NodeFileSource : public node::ObjectWrap, public mbgl::FileSource { //////////////////////////////////////////////////////////////////////////////////////////////// // Static Node Methods @@ -27,22 +27,22 @@ public: ~NodeFileSource(); mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); - void cancel(mbgl::Request *request); + void cancel(mbgl::Request*); void request(const mbgl::Resource&, Callback); // visiblity? - void notify(mbgl::Request *request, const std::shared_ptr &response); + void notify(mbgl::Request*, const std::shared_ptr&); private: struct Action; - using Queue = mbgl::util::AsyncQueue; + using Queue = util::AsyncQueue; - void processAdd(mbgl::Request *request); - void processCancel(mbgl::Request *request); + void processAdd(mbgl::Request*); + void processCancel(mbgl::Request*); private: - std::map> pending; + std::map> pending; Queue *queue = nullptr; }; diff --git a/src/node_log.cpp b/src/node_log.cpp index 793a07664..f10d52d44 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -1,4 +1,5 @@ #include "node_log.hpp" +#include "util/async_queue.hpp" #include diff --git a/src/node_log.hpp b/src/node_log.hpp index 9cbd88094..c9a8ef360 100644 --- a/src/node_log.hpp +++ b/src/node_log.hpp @@ -1,15 +1,14 @@ #pragma once #include -#include #include #include -namespace mbgl { namespace util { template class AsyncQueue; } } - namespace node_mbgl { +namespace util { template class AsyncQueue; } + class NodeLogObserver : public mbgl::Log::Observer { public: NodeLogObserver(v8::Handle target); @@ -22,7 +21,7 @@ private: v8::Persistent module; struct LogMessage; - using Queue = mbgl::util::AsyncQueue; + using Queue = util::AsyncQueue; Queue *queue = nullptr; }; diff --git a/src/thread_object.cpp b/src/thread_object.cpp deleted file mode 100644 index ea2e83130..000000000 --- a/src/thread_object.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "thread_object.hpp" -#include "node_file_source.hpp" - - - #include - -//////////////////////////////////////////////////////////////////////////////////////////////// -// Static Node Methods - -v8::Persistent ThreadObject::constructorTemplate; - -void ThreadObject::Init(v8::Handle target) { - NanScope(); - - v8::Local t = NanNew(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(args[0]->ToObject()); - auto to = new ThreadObject(fs); - to->Wrap(args.This()); - - NanReturnValue(args.This()); -} - -//////////////////////////////////////////////////////////////////////////////////////////////// -// Instance - -ThreadObject::ThreadObject(NodeFileSource *fs) { - self = v8::Persistent::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(as->data); - - to->self.Dispose(); - - uv_close((uv_handle_t *)as, [](uv_handle_t *handle) { - delete reinterpret_cast(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 deleted file mode 100644 index 3fbdb0400..000000000 --- a/src/thread_object.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -#include - -class NodeFileSource; - -class ThreadObject : public node::ObjectWrap { - //////////////////////////////////////////////////////////////////////////////////////////////// - // Static Node Methods -public: - static void Init(v8::Handle target); - static NAN_METHOD(New); - - static v8::Persistent constructorTemplate; - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Instance -private: - ThreadObject(NodeFileSource *fs); - ~ThreadObject(); - -private: - std::thread thread; - v8::Persistent self; -}; diff --git a/src/util/async_queue.hpp b/src/util/async_queue.hpp new file mode 100644 index 000000000..81319da8c --- /dev/null +++ b/src/util/async_queue.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + + +#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 +class AsyncQueue { +public: + AsyncQueue(uv_loop_t *loop, std::function fn) : + callback(fn) { + async.data = this; + uv_async_init(loop, &async, [](UV_ASYNC_PARAMS(handle)) { + auto q = reinterpret_cast(handle->data); + q->process(); + }); + } + + void send(T &&data) { + { + std::lock_guard lock(mutex); + queue.push(std::make_unique(std::move(data))); + } + uv_async_send(&async); + } + + void send(std::unique_ptr data) { + { + std::lock_guard 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(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 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> queue; + std::function callback; +}; + +} +} diff --git a/vendor/mbgl b/vendor/mbgl index 97fe50f35..4c89323f6 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 97fe50f35dbaa653fce44aa2918ba20b65c6b704 +Subproject commit 4c89323f6d669d9d5f93469e677477b125242a5d From 1b9f98440aa271f248b9d1c5a2d33561d34b8773 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 12 May 2015 15:15:53 -0400 Subject: [PATCH 101/760] ignore GCC diagnostics for node.h --- binding.gyp | 1 + src/compress_png.cpp | 8 +++----- src/compress_png.hpp | 8 +++++++- src/node_file_source.cpp | 34 +++++++++++++++++----------------- src/node_file_source.hpp | 4 ++++ src/node_log.hpp | 4 ++++ src/node_map.hpp | 4 ++++ src/node_mapbox_gl_native.cpp | 4 ++++ src/node_request.hpp | 4 ++++ 9 files changed, 48 insertions(+), 23 deletions(-) diff --git a/binding.gyp b/binding.gyp index c138be552..2fbd8f4d4 100644 --- a/binding.gyp +++ b/binding.gyp @@ -52,6 +52,7 @@ '-Werror', '-Wall', '-Wextra', + '-Wshadow', '-Wno-variadic-macros', '-Wno-error=unused-parameter', '-frtti', diff --git a/src/compress_png.cpp b/src/compress_png.cpp index 699df5346..068abe65f 100644 --- a/src/compress_png.cpp +++ b/src/compress_png.cpp @@ -1,15 +1,13 @@ -#include -#include - +#include "compress_png.hpp" #include namespace node_mbgl { class CompressPNGWorker : public NanAsyncWorker { public: - CompressPNGWorker(NanCallback *callback, v8::Local buffer_, uint32_t width_, + CompressPNGWorker(NanCallback *callback_, v8::Local buffer_, uint32_t width_, uint32_t height_) - : NanAsyncWorker(callback), + : NanAsyncWorker(callback_), buffer(v8::Persistent::New(buffer_)), data(node::Buffer::Data(buffer_)), width(width_), diff --git a/src/compress_png.hpp b/src/compress_png.hpp index 754b383e7..805181a63 100644 --- a/src/compress_png.hpp +++ b/src/compress_png.hpp @@ -1,8 +1,14 @@ +#pragma once + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop namespace node_mbgl { void InitCompressPNG(v8::Handle target); -} \ No newline at end of file +} diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 2406264fc..e0b78008b 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -64,31 +64,31 @@ NodeFileSource::~NodeFileSource() { } mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) { - auto request = new mbgl::Request(resource, loop, std::move(callback)); + auto req = 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; + queue->send(Action{ Action::Add, req }); + return req; } -void NodeFileSource::cancel(mbgl::Request* request) { - request->cancel(); +void NodeFileSource::cancel(mbgl::Request* req) { + req->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 }); + queue->send(Action{ Action::Cancel, req }); } void NodeFileSource::request(const mbgl::Resource& resource, Callback callback) { - auto request = new mbgl::Request(resource, nullptr, std::move(callback)); + auto req = 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 }); + queue->send(Action{ Action::Add, req }); } -void NodeFileSource::processAdd(mbgl::Request *request) { +void NodeFileSource::processAdd(mbgl::Request *req) { NanScope(); // Make sure the loop stays alive as long as request is pending. @@ -96,17 +96,17 @@ void NodeFileSource::processAdd(mbgl::Request *request) { queue->ref(); } - auto requestHandle = v8::Local::New(NodeRequest::Create(handle_, request)); - pending.emplace(request, std::move(v8::Persistent::New(requestHandle))); + auto requestHandle = v8::Local::New(NodeRequest::Create(handle_, req)); + pending.emplace(req, std::move(v8::Persistent::New(requestHandle))); v8::Local argv[] = { requestHandle }; NanMakeCallback(handle_, NanNew("request"), 1, argv); } -void NodeFileSource::processCancel(mbgl::Request *request) { +void NodeFileSource::processCancel(mbgl::Request *req) { NanScope(); - auto it = pending.find(request); + auto it = pending.find(req); 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. @@ -132,12 +132,12 @@ void NodeFileSource::processCancel(mbgl::Request *request) { } // Finally, destruct the request object - request->destruct(); + req->destruct(); } -void NodeFileSource::notify(mbgl::Request *request, const std::shared_ptr& response) { +void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptr& response) { // First, remove the request, since it might be destructed at any point now. - auto it = pending.find(request); + auto it = pending.find(req); if (it != pending.end()) { it->second.Dispose(); pending.erase(it); @@ -148,7 +148,7 @@ void NodeFileSource::notify(mbgl::Request *request, const std::shared_ptrnotify(response); + req->notify(response); } } diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 4221f9875..791b96969 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -2,8 +2,12 @@ #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop #include diff --git a/src/node_log.hpp b/src/node_log.hpp index c9a8ef360..5c0048d26 100644 --- a/src/node_log.hpp +++ b/src/node_log.hpp @@ -2,8 +2,12 @@ #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop namespace node_mbgl { diff --git a/src/node_map.hpp b/src/node_map.hpp index 9b52df520..39784fef5 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -3,8 +3,12 @@ #include #include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop #include diff --git a/src/node_mapbox_gl_native.cpp b/src/node_mapbox_gl_native.cpp index 09250b784..e784681a0 100644 --- a/src/node_mapbox_gl_native.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -1,5 +1,9 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop #include "node_file_source.hpp" #include "node_map.hpp" diff --git a/src/node_request.hpp b/src/node_request.hpp index d07e93d42..ebbd962d4 100644 --- a/src/node_request.hpp +++ b/src/node_request.hpp @@ -1,7 +1,11 @@ #pragma once +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" #include #include +#pragma GCC diagnostic pop class NodeFileSource; namespace mbgl { class Request; } From 99b37c64f90ed1de0e8f56d7dc0133c68a36dbe8 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 12 May 2015 17:07:54 -0400 Subject: [PATCH 102/760] compile OS X with --std=c++1y because Travis uses Apple LLVM 6.0 and full c++14 support landed in 6.1 https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/WhatsNewXcode/WhatsNewXcode.pdf --- binding.gyp | 2 +- vendor/mbgl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binding.gyp b/binding.gyp index 2fbd8f4d4..1f0f81c1f 100644 --- a/binding.gyp +++ b/binding.gyp @@ -28,11 +28,11 @@ ['OS=="mac"', { 'xcode_settings': { 'CLANG_CXX_LIBRARY': 'libc++', - 'CLANG_CXX_LANGUAGE_STANDARD': 'c++14', 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES', 'OTHER_CPLUSPLUSFLAGS': [ + '-std=c++1y', '-Werror', '-Wall', '-Wextra', diff --git a/vendor/mbgl b/vendor/mbgl index 4c89323f6..1d38b6364 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 4c89323f6d669d9d5f93469e677477b125242a5d +Subproject commit 1d38b636402a6581c491185090f5c81495a07d13 From 1942333685e49f50258a237a15b07df461494dc5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 11:14:47 -0400 Subject: [PATCH 103/760] bump mbgl --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index 1d38b6364..9cd749b55 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 1d38b636402a6581c491185090f5c81495a07d13 +Subproject commit 9cd749b55af8ca62f55726018189baeb856fa892 From 78df9002840fbf534d77f6b2dd87575985d99f1a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Feb 2015 12:36:51 -0500 Subject: [PATCH 104/760] break out NODE_EXE env var --- .travis.yml | 18 ++++++++++++++---- scripts/install_node.sh | 8 ++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7edbcad90..e8babddf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,25 +7,35 @@ matrix: include: - os: linux compiler: clang - env: FLAVOR=linux CXX=clang++-3.5 BUILDTYPE=Release + env: + - FLAVOR: linux + - CXX: clang++-3.5 + - NODE_EXE: "node" + - NODE_VERSION: 0.10.36 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux compiler: gcc - env: FLAVOR=linux CXX=g++-4.9 BUILDTYPE=Release + env: + - FLAVOR: linux + - CXX: g++-4.9 + - NODE_EXE: "node" + - NODE_VERSION: 0.10.36 addons: apt: sources: [ 'ubuntu-toolchain-r-test' ] packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: osx compiler: clang - env: FLAVOR=osx BUILDTYPE=Release + env: + - FLAVOR: osx + - NODE_EXE: "node" + - NODE_VERSION: 0.10.36 env: global: - - NODE_VERSION: 0.10.36 - LD_LIBRARY_PATH: "/usr/local/lib" - secure: pz/HAMQpnde//JJi3f+RcW32APN6g3QyFAH41JlZwgsM5Daj9RRoXqUcNg4hEyTvlThtc5t+wQQ2ejYAjMwDu00GAzRFJ03Sm45w2fPvith9fu4crXsyPUvWUfWPC0ajTXzorN4cwFwOoMoeo9DihLwb0EC0n4T6jWdBCae3k+s= - secure: jYFAOQoMZkZVyc5AFPBKhR9oDqp5CciwCxFhVEHVjdImM+8V60loKazyw+bVLIjzKLnQbKKdiDudWL9TE1ylK/XSlF3K7o4gU2vIh5WzosKnU70Sanxd6tHt/Ui5eK+bAymKHbLdGiXIZtBZE0tML6+wgJ9vhV4ZKna5dM9tps4= diff --git a/scripts/install_node.sh b/scripts/install_node.sh index 49f81a4aa..311b00424 100755 --- a/scripts/install_node.sh +++ b/scripts/install_node.sh @@ -4,10 +4,10 @@ set -e set -o pipefail # Mason exists on PATH from sourcing mbgl install script -mapbox_time "node" \ -mason install node $NODE_VERSION +mapbox_time $NODE_EXE \ +mason install $NODE_EXE $NODE_VERSION -export PATH="`mason prefix node $NODE_VERSION`/bin":"$PATH" +export PATH="`mason prefix $NODE_EXE $NODE_VERSION`/bin":"$PATH" -node --version +$NODE_EXE --version npm --version From 1455822111cbdd47f1d0a82a97398d3ef0b59f8e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Feb 2015 12:43:56 -0500 Subject: [PATCH 105/760] BIG matrix with multiple node versions and iojs --- .travis.yml | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/.travis.yml b/.travis.yml index e8babddf7..1e2c5527d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,28 @@ sudo: false matrix: include: + - os: linux + compiler: clang + env: + - FLAVOR: linux + - CXX: clang++-3.5 + - NODE_EXE: "iojs" + - NODE_VERSION: 1.2.0 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + compiler: clang + env: + - FLAVOR: linux + - CXX: clang++-3.5 + - NODE_EXE: "node" + - NODE_VERSION: 0.12.0 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux compiler: clang env: @@ -16,6 +38,28 @@ matrix: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + compiler: gcc + env: + - FLAVOR: linux + - CXX: g++-4.9 + - NODE_EXE: "iojs" + - NODE_VERSION: 1.2.0 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test' ] + packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + compiler: gcc + env: + - FLAVOR: linux + - CXX: g++-4.9 + - NODE_EXE: "node" + - NODE_VERSION: 0.12.0 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test' ] + packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux compiler: gcc env: @@ -27,6 +71,18 @@ matrix: apt: sources: [ 'ubuntu-toolchain-r-test' ] packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: osx + compiler: clang + env: + - FLAVOR: osx + - NODE_EXE: "iojs" + - NODE_VERSION: 1.2.0 + - os: osx + compiler: clang + env: + - FLAVOR: osx + - NODE_EXE: "node" + - NODE_VERSION: 0.12.0 - os: osx compiler: clang env: From c0fe624ecd4ed6cbfc46da59bd62bb1917cb6df7 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 19 Feb 2015 16:19:31 -0500 Subject: [PATCH 106/760] add node v0.12 and io.js compatibility --- src/compress_png.cpp | 4 ++-- src/node_file_source.cpp | 36 +++++++++++++++++++++++++++-------- src/node_file_source.hpp | 4 ++++ src/node_log.cpp | 9 +++++---- src/node_map.cpp | 8 ++++++-- src/node_mapbox_gl_native.cpp | 3 +-- src/node_request.cpp | 30 +++++++++++++++-------------- src/node_request.hpp | 2 +- 8 files changed, 63 insertions(+), 33 deletions(-) diff --git a/src/compress_png.cpp b/src/compress_png.cpp index 068abe65f..99cec5b1b 100644 --- a/src/compress_png.cpp +++ b/src/compress_png.cpp @@ -8,15 +8,15 @@ public: CompressPNGWorker(NanCallback *callback_, v8::Local buffer_, uint32_t width_, uint32_t height_) : NanAsyncWorker(callback_), - buffer(v8::Persistent::New(buffer_)), data(node::Buffer::Data(buffer_)), width(width_), height(height_) { + NanAssignPersistent(buffer, buffer_); assert(width * height * 4 == node::Buffer::Length(buffer_)); } ~CompressPNGWorker() { - buffer.Dispose(); + NanDisposePersistent(buffer); } void Execute() { diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index e0b78008b..acf96bf12 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -96,11 +96,18 @@ void NodeFileSource::processAdd(mbgl::Request *req) { queue->ref(); } - auto requestHandle = v8::Local::New(NodeRequest::Create(handle_, req)); - pending.emplace(req, std::move(v8::Persistent::New(requestHandle))); + auto handle = NanObjectWrapHandle(this); + auto requestHandle = NanNew(NodeRequest::Create(handle, request)); +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + v8::Persistent requestPersistent; + NanAssignPersistent(requestPersistent, requestHandle); +#else + const v8::UniquePersistent requestPersistent(v8::Isolate::GetCurrent(), requestHandle); +#endif + pending.emplace(request, std::move(requestPersistent)); v8::Local argv[] = { requestHandle }; - NanMakeCallback(handle_, NanNew("request"), 1, argv); + NanMakeCallback(handle, NanNew("request"), 1, argv); } void NodeFileSource::processCancel(mbgl::Request *req) { @@ -111,10 +118,18 @@ void NodeFileSource::processCancel(mbgl::Request *req) { // 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::New(it->second); +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + auto requestHandle = NanNew(it->second); +#else + auto requestHandle = v8::Local::New(v8::Isolate::GetCurrent(), it->second); +#endif // Dispose and remove the persistent handle - it->second.Dispose(); +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + NanDisposePersistent(it->second); +#else + // it->second.Reset(); +#endif pending.erase(it); // Make sure the the loop can exit when there are no pending requests. @@ -122,9 +137,10 @@ void NodeFileSource::processCancel(mbgl::Request *req) { queue->unref(); } - if (handle_->Has(NanNew("cancel"))) { + auto handle = NanObjectWrapHandle(this); + if (handle->Has(NanNew("cancel"))) { v8::Local argv[] = { requestHandle }; - NanMakeCallback(handle_, NanNew("cancel"), 1, argv); + NanMakeCallback(handle, NanNew("cancel"), 1, argv); } // Set the request handle in the request wrapper handle to null @@ -139,7 +155,11 @@ void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptrsecond.Dispose(); +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + NanDisposePersistent(it->second); +#else + // it->second.Dispose(); +#endif pending.erase(it); // Make sure the the loop can exit when there are no pending requests. diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 791b96969..c0dbe58d2 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -46,7 +46,11 @@ private: void processCancel(mbgl::Request*); private: +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 std::map> pending; +#else + std::map &> pending; +#endif Queue *queue = nullptr; }; diff --git a/src/node_log.cpp b/src/node_log.cpp index f10d52d44..d7cf0e0d8 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -32,7 +32,7 @@ NodeLogObserver::NodeLogObserver(v8::Handle target) : queue(new Queue(uv_default_loop(), [this](LogMessage &message) { NanScope(); - auto msg = v8::Object::New(); + auto msg = NanNew(); 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) { @@ -49,11 +49,12 @@ NodeLogObserver::NodeLogObserver(v8::Handle target) } v8::Local argv[] = { NanNew("message"), msg }; - auto emit = module->Get(NanNew("emit"))->ToObject(); - emit->CallAsFunction(module, 2, argv); + auto handle = NanNew(module); + auto emit = handle->Get(NanNew("emit"))->ToObject(); + emit->CallAsFunction(handle, 2, argv); })) { NanScope(); - module = v8::Persistent::New(target); + NanAssignPersistent(module, target); // Don't keep the event loop alive. queue->unref(); diff --git a/src/node_map.cpp b/src/node_map.cpp index b6aae1302..e5b3fda5f 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -239,7 +239,7 @@ void NodeMap::renderFinished() { assert(!image); if (img) { - v8::Local result = v8::Object::New(); + auto result = NanNew(); result->Set(NanNew("width"), NanNew(img->width)); result->Set(NanNew("height"), NanNew(img->height)); @@ -283,7 +283,11 @@ NodeMap::NodeMap(v8::Handle source_) : NanAssignPersistent(source, source_); 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(as->data)->renderFinished(); }); @@ -292,7 +296,7 @@ NodeMap::NodeMap(v8::Handle source_) : } NodeMap::~NodeMap() { - source.Dispose(); + NanDisposePersistent(source); uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { delete reinterpret_cast(handle); diff --git a/src/node_mapbox_gl_native.cpp b/src/node_mapbox_gl_native.cpp index e784681a0..a7a0f0fcf 100644 --- a/src/node_mapbox_gl_native.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -11,7 +11,6 @@ #include "node_request.hpp" #include "compress_png.hpp" - void RegisterModule(v8::Handle exports) { NanScope(); @@ -22,7 +21,7 @@ void RegisterModule(v8::Handle exports) { // Exports Resource constants. auto ConstantProperty = static_cast(v8::ReadOnly | v8::DontDelete); - auto resource = v8::Object::New(); + auto resource = NanNew(); 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); diff --git a/src/node_request.cpp b/src/node_request.cpp index b67fd2097..0fdc05bff 100644 --- a/src/node_request.cpp +++ b/src/node_request.cpp @@ -35,34 +35,35 @@ NAN_METHOD(NodeRequest::New) { return NanThrowTypeError("Cannot create Request objects explicitly"); } - auto source = v8::Persistent::New(args[0]->ToObject()); + v8::Persistent source; + NanAssignPersistent(source, args[0]->ToObject()); + auto request = reinterpret_cast(args[1].As()->Value()); - auto req = new NodeRequest(source, request); + auto req = new NodeRequest(NanNew(std::move(source)), request); req->Wrap(args.This()); NanReturnValue(args.This()); } v8::Handle NodeRequest::Create(v8::Handle source, mbgl::Request *request) { - NanScope(); - v8::Local argv[] = { v8::Local::New(source), NanNew(request) }; - auto instance = constructorTemplate->GetFunction()->NewInstance(2, argv); + NanEscapableScope(); - instance->Set(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly); - instance->Set(NanNew("kind"), NanNew(int(request->resource.kind)), v8::ReadOnly); + v8::Local argv[] = { NanNew(source), NanNew(request) }; + auto instance = NanNew(constructorTemplate)->GetFunction()->NewInstance(2, argv); - NanReturnValue(instance); + instance->ForceSet(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly); + instance->ForceSet(NanNew("kind"), NanNew(int(request->resource.kind)), v8::ReadOnly); + + return NanEscapeScope(instance); } NAN_METHOD(NodeRequest::Respond) { - NanScope(); - auto nodeRequest = ObjectWrap::Unwrap(args.Holder()); if (!nodeRequest->request) { return NanThrowError("Request has already been responded to, or was canceled."); } - auto source = ObjectWrap::Unwrap(nodeRequest->source); + auto source = ObjectWrap::Unwrap(NanNew(nodeRequest->source)); auto request = nodeRequest->request; nodeRequest->request = nullptr; @@ -131,12 +132,13 @@ NAN_METHOD(NodeRequest::Respond) { //////////////////////////////////////////////////////////////////////////////////////////////// // Instance -NodeRequest::NodeRequest(v8::Persistent source_, mbgl::Request *request_) - : source(source_), request(request_) { +NodeRequest::NodeRequest(v8::Local source_, mbgl::Request *request_) + : request(request_) { + NanAssignPersistent(source, source_); } NodeRequest::~NodeRequest() { - source.Dispose(); + NanDisposePersistent(source); } void NodeRequest::cancel() { diff --git a/src/node_request.hpp b/src/node_request.hpp index ebbd962d4..009855066 100644 --- a/src/node_request.hpp +++ b/src/node_request.hpp @@ -27,7 +27,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////// // Instance public: - NodeRequest(v8::Persistent source, mbgl::Request *request); + NodeRequest(v8::Local source, mbgl::Request *request); ~NodeRequest(); void cancel(); From 11b214fed6529a3f05f23883fc797d90ad5ed15a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 12:55:12 -0400 Subject: [PATCH 107/760] if/else on NODE_MODULE_VERSION instead of UV_VERSION --- src/node_file_source.cpp | 28 ++++++++++++++-------------- src/node_file_source.hpp | 7 ++++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index acf96bf12..298c30c0b 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -97,14 +97,14 @@ void NodeFileSource::processAdd(mbgl::Request *req) { } auto handle = NanObjectWrapHandle(this); - auto requestHandle = NanNew(NodeRequest::Create(handle, request)); -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + auto requestHandle = NanNew(NodeRequest::Create(handle, req)); +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) + const v8::UniquePersistent requestPersistent(v8::Isolate::GetCurrent(), requestHandle); +#else v8::Persistent requestPersistent; NanAssignPersistent(requestPersistent, requestHandle); -#else - const v8::UniquePersistent requestPersistent(v8::Isolate::GetCurrent(), requestHandle); #endif - pending.emplace(request, std::move(requestPersistent)); + pending.emplace(req, std::move(requestPersistent)); v8::Local argv[] = { requestHandle }; NanMakeCallback(handle, NanNew("request"), 1, argv); @@ -118,17 +118,17 @@ void NodeFileSource::processCancel(mbgl::Request *req) { // The response callback was already fired. There is no point in calling the cancelation // callback because the request is already completed. } else { -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - auto requestHandle = NanNew(it->second); -#else +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) auto requestHandle = v8::Local::New(v8::Isolate::GetCurrent(), it->second); +#else + auto requestHandle = NanNew(it->second); #endif // Dispose and remove the persistent handle -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - NanDisposePersistent(it->second); -#else +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) // it->second.Reset(); +#else + NanDisposePersistent(it->second); #endif pending.erase(it); @@ -155,10 +155,10 @@ void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptrsecond); -#else +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) // it->second.Dispose(); +#else + NanDisposePersistent(it->second); #endif pending.erase(it); diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index c0dbe58d2..49ff27e14 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -6,6 +6,7 @@ #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" #include +#include #include #pragma GCC diagnostic pop @@ -46,10 +47,10 @@ private: void processCancel(mbgl::Request*); private: -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - std::map> pending; -#else +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) std::map &> pending; +#else + std::map> pending; #endif Queue *queue = nullptr; }; From b7ab9485d415e5b1c3af1f61da1f01a4e8826d49 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 13:53:05 -0400 Subject: [PATCH 108/760] upgrade node modules, switch from tap-spec to faucet --- Makefile | 4 ++-- package.json | 17 +++++++++-------- src/node_file_source.cpp | 8 ++------ 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index d66f6ad1c..4884d5a5f 100644 --- a/Makefile +++ b/Makefile @@ -63,11 +63,11 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: - -@(`npm bin`/tape test/render.test.js | `npm bin`/tap-spec) + -@(`npm bin`/tape test/render.test.js | `npm bin`/faucet) .PHONY: test-js test-js: - @(`npm bin`/tape test/js/**/*.test.js | `npm bin`/tap-spec) + @(`npm bin`/tape test/js/**/*.test.js | `npm bin`/faucet) .PHONY: test test: test-js test-suite diff --git a/package.json b/package.json index 65e68b610..e8e2f13fc 100644 --- a/package.json +++ b/package.json @@ -18,20 +18,21 @@ } ], "dependencies": { - "nan": "^1.4.1", - "node-pre-gyp": "^0.6.4" + "nan": "^1.8.4", + "node-pre-gyp": "^0.6.7" }, "bundledDependencies": [ "node-pre-gyp" ], "devDependencies": { - "aws-sdk": "^2.1.9", + "aws-sdk": "^2.1.27", + "faucet": "0.0.1", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", - "mkdirp": "^0.5.0", - "request": "^2.53.0", - "tap-spec": "^2.2.1", - "tape": "^3.5.0", - "st": "^0.5.3" + "mkdirp": "^0.5.1", + "request": "^2.55.0", + "st": "^0.5.3", + "tap-spec": "^3.0.0", + "tape": "^4.0.0" }, "scripts": { "install": "node-pre-gyp install || make", diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 298c30c0b..866e6d478 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -125,9 +125,7 @@ void NodeFileSource::processCancel(mbgl::Request *req) { #endif // Dispose and remove the persistent handle -#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - // it->second.Reset(); -#else +#if (NODE_MODULE_VERSION <= NODE_0_10_MODULE_VERSION) NanDisposePersistent(it->second); #endif pending.erase(it); @@ -155,9 +153,7 @@ void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptr NODE_0_10_MODULE_VERSION) - // it->second.Dispose(); -#else +#if (NODE_MODULE_VERSION <= NODE_0_10_MODULE_VERSION) NanDisposePersistent(it->second); #endif pending.erase(it); From 068491711b936ff267a4e34713a12e10722b7606 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 13:56:47 -0400 Subject: [PATCH 109/760] test against io.js 2.0.1 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1e2c5527d..0495d8b30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: - FLAVOR: linux - CXX: clang++-3.5 - NODE_EXE: "iojs" - - NODE_VERSION: 1.2.0 + - NODE_VERSION: 2.0.1 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] @@ -44,7 +44,7 @@ matrix: - FLAVOR: linux - CXX: g++-4.9 - NODE_EXE: "iojs" - - NODE_VERSION: 1.2.0 + - NODE_VERSION: 2.0.1 addons: apt: sources: [ 'ubuntu-toolchain-r-test' ] @@ -76,7 +76,7 @@ matrix: env: - FLAVOR: osx - NODE_EXE: "iojs" - - NODE_VERSION: 1.2.0 + - NODE_VERSION: 2.0.1 - os: osx compiler: clang env: From 99e24102c57b0c8515789e503cc118710f936859 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 13:57:44 -0400 Subject: [PATCH 110/760] drop FLAVOR env var --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0495d8b30..8a7f5c345 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,6 @@ matrix: - os: linux compiler: clang env: - - FLAVOR: linux - CXX: clang++-3.5 - NODE_EXE: "iojs" - NODE_VERSION: 2.0.1 @@ -19,7 +18,6 @@ matrix: - os: linux compiler: clang env: - - FLAVOR: linux - CXX: clang++-3.5 - NODE_EXE: "node" - NODE_VERSION: 0.12.0 @@ -30,7 +28,6 @@ matrix: - os: linux compiler: clang env: - - FLAVOR: linux - CXX: clang++-3.5 - NODE_EXE: "node" - NODE_VERSION: 0.10.36 @@ -41,7 +38,6 @@ matrix: - os: linux compiler: gcc env: - - FLAVOR: linux - CXX: g++-4.9 - NODE_EXE: "iojs" - NODE_VERSION: 2.0.1 @@ -52,7 +48,6 @@ matrix: - os: linux compiler: gcc env: - - FLAVOR: linux - CXX: g++-4.9 - NODE_EXE: "node" - NODE_VERSION: 0.12.0 @@ -63,7 +58,6 @@ matrix: - os: linux compiler: gcc env: - - FLAVOR: linux - CXX: g++-4.9 - NODE_EXE: "node" - NODE_VERSION: 0.10.36 @@ -74,19 +68,16 @@ matrix: - os: osx compiler: clang env: - - FLAVOR: osx - NODE_EXE: "iojs" - NODE_VERSION: 2.0.1 - os: osx compiler: clang env: - - FLAVOR: osx - NODE_EXE: "node" - NODE_VERSION: 0.12.0 - os: osx compiler: clang env: - - FLAVOR: osx - NODE_EXE: "node" - NODE_VERSION: 0.10.36 From 2da70779c60ba412a3387f1ba5c9147e68c7d89b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 14:46:06 -0400 Subject: [PATCH 111/760] gcc -Wshadow is annoying --- binding.gyp | 1 - 1 file changed, 1 deletion(-) diff --git a/binding.gyp b/binding.gyp index 1f0f81c1f..9c7cade1d 100644 --- a/binding.gyp +++ b/binding.gyp @@ -52,7 +52,6 @@ '-Werror', '-Wall', '-Wextra', - '-Wshadow', '-Wno-variadic-macros', '-Wno-error=unused-parameter', '-frtti', From 830a9b3972a1934e6b059da54a0ff282a8581088 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 15 May 2015 13:24:04 -0400 Subject: [PATCH 112/760] drop CompressPNG --- binding.gyp | 2 - src/compress_png.cpp | 106 ---------------------------------- src/compress_png.hpp | 14 ----- src/node_mapbox_gl_native.cpp | 2 - 4 files changed, 124 deletions(-) delete mode 100644 src/compress_png.cpp delete mode 100644 src/compress_png.hpp diff --git a/binding.gyp b/binding.gyp index 9c7cade1d..a5fb58339 100644 --- a/binding.gyp +++ b/binding.gyp @@ -11,8 +11,6 @@ 'sources': [ 'src/node_mapbox_gl_native.cpp', - 'src/compress_png.hpp', - 'src/compress_png.cpp', 'src/node_log.hpp', 'src/node_log.cpp', 'src/node_file_source.hpp', diff --git a/src/compress_png.cpp b/src/compress_png.cpp deleted file mode 100644 index 99cec5b1b..000000000 --- a/src/compress_png.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "compress_png.hpp" -#include - -namespace node_mbgl { - -class CompressPNGWorker : public NanAsyncWorker { -public: - CompressPNGWorker(NanCallback *callback_, v8::Local buffer_, uint32_t width_, - uint32_t height_) - : NanAsyncWorker(callback_), - data(node::Buffer::Data(buffer_)), - width(width_), - height(height_) { - NanAssignPersistent(buffer, buffer_); - assert(width * height * 4 == node::Buffer::Length(buffer_)); - } - - ~CompressPNGWorker() { - NanDisposePersistent(buffer); - } - - void Execute() { - result = mbgl::util::compress_png(width, height, data); - } - - void HandleOKCallback() { - NanScope(); - - auto img = new std::string(std::move(result)); - - v8::Local argv[] = { - NanNull(), - NanNewBufferHandle( - const_cast(img->data()), - img->size(), - // Retain the std::string until the buffer is deleted. - [](char *, void *hint) { - delete reinterpret_cast(hint); - }, - img - ) - }; - - callback->Call(2, argv); - }; - -private: - // Retains the buffer while this worker is processing. The user may not modify the buffer. - v8::Persistent buffer; - void *data; - const uint32_t width; - const uint32_t height; - - // Stores the compressed PNG. - std::string result; -}; - -NAN_METHOD(CompressPNG) { - NanScope(); - - if (args.Length() <= 0 || !args[0]->IsObject()) { - return NanThrowTypeError("First argument must be the data object"); - } - - uint32_t width = 0; - uint32_t height = 0; - v8::Local buffer; - - auto options = args[0]->ToObject(); - if (options->Has(NanNew("width"))) { - width = options->Get(NanNew("width"))->Uint32Value(); - } - if (!width) { - NanThrowRangeError("Image width must be greater than 0"); - } - if (options->Has(NanNew("height"))) { - height = options->Get(NanNew("height"))->Uint32Value(); - } - if (!height) { - NanThrowRangeError("Image height must be greater than 0"); - } - if (options->Has(NanNew("pixels"))) { - buffer = options->Get(NanNew("pixels")).As(); - } - if (!node::Buffer::HasInstance(buffer)) { - NanThrowTypeError("Pixels must be a Buffer object"); - } - if (width * height * 4 != node::Buffer::Length(buffer)) { - NanThrowError("Pixel buffer doesn't match image dimensions"); - } - - if (args.Length() < 2) { - NanThrowTypeError("Second argument must be a callback function"); - } - NanCallback *callback = new NanCallback(args[1].As()); - - NanAsyncQueueWorker(new CompressPNGWorker(callback, buffer, width, height)); - NanReturnUndefined(); -} - -void InitCompressPNG(v8::Handle target) { - target->Set(NanNew("compressPNG"), - NanNew(CompressPNG)->GetFunction()); -} - -} diff --git a/src/compress_png.hpp b/src/compress_png.hpp deleted file mode 100644 index 805181a63..000000000 --- a/src/compress_png.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wshadow" -#include -#include -#pragma GCC diagnostic pop - -namespace node_mbgl { - -void InitCompressPNG(v8::Handle target); - -} diff --git a/src/node_mapbox_gl_native.cpp b/src/node_mapbox_gl_native.cpp index a7a0f0fcf..f4788802e 100644 --- a/src/node_mapbox_gl_native.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -9,7 +9,6 @@ #include "node_map.hpp" #include "node_log.hpp" #include "node_request.hpp" -#include "compress_png.hpp" void RegisterModule(v8::Handle exports) { NanScope(); @@ -17,7 +16,6 @@ void RegisterModule(v8::Handle exports) { node_mbgl::NodeFileSource::Init(exports); node_mbgl::NodeMap::Init(exports); node_mbgl::NodeRequest::Init(exports); - node_mbgl::InitCompressPNG(exports); // Exports Resource constants. auto ConstantProperty = static_cast(v8::ReadOnly | v8::DontDelete); From 7ed36670925d1dd902e46ffb6247a3b7cba6ebf0 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 15 May 2015 16:40:26 -0400 Subject: [PATCH 113/760] pack images with pngjs https://www.npmjs.com/package/pngjs --- examples/load-from-fs.js | 18 ++++++-- package.json | 2 +- test/expected/gzip/success.png | Bin 41488 -> 42195 bytes test/expected/map/image.png | Bin 41488 -> 42195 bytes test/js/gzip.test.js | 78 +++++++++++++++++++-------------- test/js/map.test.js | 38 +++++++++------- test/render.test.js | 56 +++++++++++++---------- 7 files changed, 115 insertions(+), 77 deletions(-) diff --git a/examples/load-from-fs.js b/examples/load-from-fs.js index 5760f024f..9337ed854 100755 --- a/examples/load-from-fs.js +++ b/examples/load-from-fs.js @@ -5,6 +5,7 @@ var mbgl = require('..'); var fs = require('fs'); var path = require('path'); +var PNG = require('pngjs').PNG; var base = path.join(path.dirname(process.mainModule.filename), '../test'); @@ -20,10 +21,19 @@ fileSource.cancel = function(req) { var map = new mbgl.Map(fileSource); map.load(require('../test/fixtures/style.json')); -map.render({}, function(err, image) { +map.render({}, function(err, data) { if (err) throw err; - mbgl.compressPNG(image, function(err, png) { - fs.writeFileSync('image.png', png); - console.warn('Written image.png'); + + var png = new PNG({ + width: data.width, + height: data.height }); + + png.data = data.pixels; + + png.pack() + .pipe(fs.createWriteStream('image.png')) + .on('finish', function() { + console.warn('Written image.png'); + }); }); diff --git a/package.json b/package.json index e8e2f13fc..8da4582f6 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "faucet": "0.0.1", "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#master", "mkdirp": "^0.5.1", + "pngjs": "^0.4.0", "request": "^2.55.0", "st": "^0.5.3", - "tap-spec": "^3.0.0", "tape": "^4.0.0" }, "scripts": { diff --git a/test/expected/gzip/success.png b/test/expected/gzip/success.png index b39617e1f6a3d54d2d8b527c2fac500170703392..de41e0fe2bf7eed4a6caba1cd37fab041a9c9fa5 100644 GIT binary patch literal 42195 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

E1VzHEYCH5^t{_O7 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 literal 41488 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 z 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

E1VzHEYCH5^t{_O7 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 literal 41488 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 z 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' + error + '

' : '', - difference: difference, + error: err ? '

' + err + '

' : '', + difference: diff, zoom: info.zoom || 0, center: info.center || [0, 0], bearing: info.bearing || 0, @@ -83,12 +92,11 @@ function renderTest(style, info, base, key) { height: info.height || 512 }); - t.ok(difference <= allowedDifference, 'actual matches expected'); + t.ok(diff <= allowed, 'actual matches expected'); t.end(); }); - }); - } - }); + }); + } }); }; } From 16ec628d8e6570ff09d501baa9f75400b0b8f8c3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 20 May 2015 13:06:32 -0400 Subject: [PATCH 114/760] return an error if map.renderStill returns a std::exception_ptr store exception_ptr on NodeMap add test for map.render returning an error --- src/node_map.cpp | 24 +++++++++++++++++++----- src/node_map.hpp | 1 + test/js/map.test.js | 12 +++++++++++- vendor/mbgl | 2 +- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index e5b3fda5f..3ebed2e20 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -204,10 +204,15 @@ void NodeMap::startRender(std::unique_ptr options) { map.setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); map.setBearing(options->bearing); - map.renderStill([this](std::unique_ptr result) { - assert(!image); - image = std::move(result); - uv_async_send(async); + map.renderStill([this](const std::exception_ptr eptr, std::unique_ptr 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 @@ -238,7 +243,16 @@ void NodeMap::renderFinished() { assert(!callback); assert(!image); - if (img) { + if (error) { + // This must be empty to be prepared for the next render call. + error = nullptr; + assert(!error); + + v8::Local argv[] = { + NanError("Error rendering image") + }; + cb->Call(1, argv); + } else if (img) { auto result = NanNew(); result->Set(NanNew("width"), NanNew(img->width)); result->Set(NanNew("height"), NanNew(img->height)); diff --git a/src/node_map.hpp b/src/node_map.hpp index 39784fef5..1cb32e40d 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -50,6 +50,7 @@ private: NodeFileSource &fs; mbgl::Map map; + std::exception_ptr error; std::unique_ptr image; std::unique_ptr callback; diff --git a/test/js/map.test.js b/test/js/map.test.js index 2d913b40a..de5126dc9 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -157,7 +157,6 @@ test('Map', function(t) { fileSource.request = function(req) { fs.readFile(path.join('test', req.url), function(err, data) { req.respond(err, { data: data }); - t.error(err); }); }; fileSource.cancel = function() {}; @@ -199,6 +198,17 @@ test('Map', function(t) { }); }); + t.test('returns an error', function(t) { + setup(fileSource, function(map) { + map.load(style); + map.render({ zoom: 1 }, function(err, data) { + t.ok(err); + t.equal(err.message, 'Error rendering image'); + t.end(); + }); + }); + }); + t.test('returns an image', function(t) { setup(fileSource, function(map) { map.load(style); diff --git a/vendor/mbgl b/vendor/mbgl index 9cd749b55..a20b89e4f 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 9cd749b55af8ca62f55726018189baeb856fa892 +Subproject commit a20b89e4ff991b650c7b09e203c2fee5c8e0c62b From 2e73682de01554e8b31e12505c7f8ab887be9713 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 27 May 2015 15:40:59 -0400 Subject: [PATCH 115/760] std::make_unique --- src/node_map.cpp | 2 +- src/node_mapbox_gl_native.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 3ebed2e20..c5dde6053 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -145,7 +145,7 @@ NAN_METHOD(NodeMap::Load) { std::unique_ptr NodeMap::ParseOptions(v8::Local obj) { NanScope(); - auto options = mbgl::util::make_unique(); + auto options = std::make_unique(); if (obj->Has(NanNew("zoom"))) { options->zoom = obj->Get(NanNew("zoom"))->NumberValue(); } if (obj->Has(NanNew("bearing"))) { options->bearing = obj->Get(NanNew("bearing"))->NumberValue(); } diff --git a/src/node_mapbox_gl_native.cpp b/src/node_mapbox_gl_native.cpp index f4788802e..7a161b2ed 100644 --- a/src/node_mapbox_gl_native.cpp +++ b/src/node_mapbox_gl_native.cpp @@ -32,7 +32,7 @@ void RegisterModule(v8::Handle exports) { auto EventEmitter = process->Get(NanNew("EventEmitter"))->ToObject(); exports->SetPrototype(EventEmitter->Get(NanNew("prototype"))); - mbgl::Log::setObserver(mbgl::util::make_unique(exports)); + mbgl::Log::setObserver(std::make_unique(exports)); } NODE_MODULE(mapbox_gl_native, RegisterModule) From 44490f620cd19624a9689a45450f9fb33e33c06c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 27 May 2015 18:24:43 -0400 Subject: [PATCH 116/760] drop SetAccessToken from NodeMap Handle this is JavaScript where necessary. --- src/node_map.cpp | 25 +------------------------ src/node_map.hpp | 1 - vendor/mbgl | 2 +- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index c5dde6053..4dc39b729 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -38,7 +38,6 @@ void NodeMap::Init(v8::Handle target) { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(NanNew("Map")); - NODE_SET_PROTOTYPE_METHOD(t, "setAccessToken", SetAccessToken); NODE_SET_PROTOTYPE_METHOD(t, "load", Load); NODE_SET_PROTOTYPE_METHOD(t, "render", Render); @@ -83,27 +82,6 @@ NAN_METHOD(NodeMap::New) { } -NAN_METHOD(NodeMap::SetAccessToken) { - NanScope(); - - if (args.Length() < 1) { - return NanThrowError("Requires a string as first argument"); - } - - NanUtf8String token(args[0]); - - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - - try { - nodeMap->map.setAccessToken(std::string { *token, size_t(token.length()) }); - } catch (const std::exception &ex) { - return NanThrowError(ex.what()); - } - - - NanReturnUndefined(); -} - const std::string StringifyStyle(v8::Handle styleHandle) { NanScope(); @@ -125,8 +103,7 @@ NAN_METHOD(NodeMap::Load) { if (args[0]->IsObject()) { style = StringifyStyle(args[0]); } else if (args[0]->IsString()) { - NanUtf8String string(args[0]); - style = { *string, size_t(string.length()) }; + style = *NanUtf8String(args[0]); } else { return NanThrowTypeError("First argument must be a string or object"); } diff --git a/src/node_map.hpp b/src/node_map.hpp index 1cb32e40d..ecba02867 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -25,7 +25,6 @@ class NodeMap : public node::ObjectWrap { public: static void Init(v8::Handle target); static NAN_METHOD(New); - static NAN_METHOD(SetAccessToken); static NAN_METHOD(Load); static NAN_METHOD(Render); diff --git a/vendor/mbgl b/vendor/mbgl index a20b89e4f..9dd8d2672 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit a20b89e4ff991b650c7b09e203c2fee5c8e0c62b +Subproject commit 9dd8d26729eba5ecb8d788b8ba2150cf5b4bdeca From cb47f902e195398fe04e8ab00184bbc642ba3a77 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 27 May 2015 18:40:53 -0400 Subject: [PATCH 117/760] #include in node_file_source.hpp --- src/node_file_source.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 49ff27e14..a56c0f7c0 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -11,6 +11,7 @@ #pragma GCC diagnostic pop #include +#include namespace node_mbgl { From fe3d6bbbd5ac82b7c3b5ae51e33a84268333dfde Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 20 May 2015 17:19:36 -0400 Subject: [PATCH 118/760] expose map.release() method --- .travis.yml | 1 + src/node_map.cpp | 72 +++++++++++++++++++++++++++---------- src/node_map.hpp | 8 ++++- test/js/concurrency.test.js | 2 ++ test/js/gzip.test.js | 4 +++ test/js/map.test.js | 55 ++++++++++++++++++++++++---- test/render.test.js | 2 ++ 7 files changed, 117 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a7f5c345..d2a5b8530 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,6 +100,7 @@ install: before_script: - ulimit -c unlimited -S +- ulimit -a script: - "./scripts/travis_test.sh" diff --git a/src/node_map.cpp b/src/node_map.cpp index 4dc39b729..89ba8d3b9 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -30,6 +30,10 @@ static std::shared_ptr sharedDisplay() { return display; } +const static char* releasedMessage() { + return "Map resources have already been released"; +} + void NodeMap::Init(v8::Handle target) { NanScope(); @@ -40,6 +44,7 @@ void NodeMap::Init(v8::Handle target) { NODE_SET_PROTOTYPE_METHOD(t, "load", Load); NODE_SET_PROTOTYPE_METHOD(t, "render", Render); + NODE_SET_PROTOTYPE_METHOD(t, "release", Release); NanAssignPersistent(constructorTemplate, t); @@ -72,8 +77,8 @@ NAN_METHOD(NodeMap::New) { try { - auto map = new NodeMap(source); - map->Wrap(args.This()); + auto nodeMap = new NodeMap(source); + nodeMap->Wrap(args.This()); } catch(std::exception &ex) { return NanThrowError(ex.what()); } @@ -81,7 +86,6 @@ NAN_METHOD(NodeMap::New) { NanReturnValue(args.This()); } - const std::string StringifyStyle(v8::Handle styleHandle) { NanScope(); @@ -94,6 +98,10 @@ const std::string StringifyStyle(v8::Handle styleHandle) { NAN_METHOD(NodeMap::Load) { NanScope(); + auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); + + if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); + if (args.Length() < 1) { return NanThrowError("Requires a map style as first argument"); } @@ -108,10 +116,8 @@ NAN_METHOD(NodeMap::Load) { return NanThrowTypeError("First argument must be a string or object"); } - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - try { - nodeMap->map.setStyleJSON(style, "."); + nodeMap->map->setStyleJSON(style, "."); } catch (const std::exception &ex) { return NanThrowError(ex.what()); } @@ -150,6 +156,10 @@ std::unique_ptr NodeMap::ParseOptions(v8::Local(args.Holder()); + + if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); + if (args.Length() <= 0 || !args[0]->IsObject()) { return NanThrowTypeError("First argument must be an options object"); } @@ -160,8 +170,6 @@ NAN_METHOD(NodeMap::Render) { auto options = ParseOptions(args[0]->ToObject()); - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - assert(!nodeMap->callback); assert(!nodeMap->image); nodeMap->callback = std::unique_ptr(new NanCallback(args[1].As())); @@ -176,12 +184,12 @@ NAN_METHOD(NodeMap::Render) { } void NodeMap::startRender(std::unique_ptr options) { - map.resize(options->width, options->height, options->ratio); - map.setClasses(options->classes); - map.setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); - map.setBearing(options->bearing); + map->resize(options->width, options->height, options->ratio); + 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 result) { + map->renderStill([this](const std::exception_ptr eptr, std::unique_ptr result) { if (eptr) { error = std::move(eptr); uv_async_send(async); @@ -261,6 +269,36 @@ void NodeMap::renderFinished() { } } +NAN_METHOD(NodeMap::Release) { + NanScope(); + + auto nodeMap = node::ObjectWrap::Unwrap(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; + + NanDisposePersistent(source); + + uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { + delete reinterpret_cast(handle); + }); + + map.reset(nullptr); +} + //////////////////////////////////////////////////////////////////////////////////////////////// // Instance @@ -268,7 +306,7 @@ void NodeMap::renderFinished() { NodeMap::NodeMap(v8::Handle source_) : view(sharedDisplay()), fs(*ObjectWrap::Unwrap(source_)), - map(view, fs, mbgl::MapMode::Still), + map(std::make_unique(view, fs, mbgl::MapMode::Still)), async(new uv_async_t) { NanAssignPersistent(source, source_); @@ -287,11 +325,7 @@ NodeMap::NodeMap(v8::Handle source_) : } NodeMap::~NodeMap() { - NanDisposePersistent(source); - - uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { - delete reinterpret_cast(handle); - }); + if (valid) release(); } } diff --git a/src/node_map.hpp b/src/node_map.hpp index ecba02867..bb238ea88 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -27,10 +27,14 @@ public: static NAN_METHOD(New); static NAN_METHOD(Load); static NAN_METHOD(Render); + static NAN_METHOD(Release); void startRender(std::unique_ptr options); void renderFinished(); + void release(); + inline bool isValid() { return valid; } + static std::unique_ptr ParseOptions(v8::Local obj); static v8::Persistent constructorTemplate; @@ -47,7 +51,7 @@ private: mbgl::HeadlessView view; NodeFileSource &fs; - mbgl::Map map; + std::unique_ptr map; std::exception_ptr error; std::unique_ptr image; @@ -55,6 +59,8 @@ private: // Async for delivering the notifications of render completion. uv_async_t *async; + + bool valid = true; }; } diff --git a/test/js/concurrency.test.js b/test/js/concurrency.test.js index e8c373adc..e57767fa7 100644 --- a/test/js/concurrency.test.js +++ b/test/js/concurrency.test.js @@ -31,8 +31,10 @@ function renderTest(style, info, dir, key) { function render() { map.render(info[key], function(err, image) { t.error(err); + t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms'); if (++completed === remaining) { + map.release(); t.end(); } else { render(); diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 00f827b68..9b8d3e297 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -69,6 +69,8 @@ test('gzip', function(t) { map.render({}, function(err, data) { t.error(err); + map.release(); + mbgl.removeAllListeners('message'); var filename = filePath('success.png'); @@ -119,6 +121,8 @@ test('gzip', function(t) { map.render({}, function(err, data) { if (!errorEmitted) t.fail('no error emitted'); + map.release(); + mbgl.removeAllListeners('message'); var filename = filePath('unhandled.png'); diff --git a/test/js/map.test.js b/test/js/map.test.js index de5126dc9..cdb3ffe54 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -94,19 +94,25 @@ test('Map', function(t) { t.throws(function() { map.load(); }, /Requires a map style as first argument/); + + map.release(); + t.end(); }); }); t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { - mbgl.once('message', function(msg) { - t.equal(msg.severity, 'ERROR'); - t.equal(msg.class, 'ParseStyle'); - t.ok(msg.text.match(/Expect either an object or array at root/)); - t.end(); - }); - setup(fileSource, function(map) { + mbgl.once('message', function(msg) { + t.equal(msg.severity, 'ERROR'); + t.equal(msg.class, 'ParseStyle'); + t.ok(msg.text.match(/Expect either an object or array at root/)); + + map.release(); + + t.end(); + }); + map.load('invalid'); }); }); @@ -119,6 +125,9 @@ test('Map', function(t) { t.doesNotThrow(function() { map.load('{}'); }); + + map.release(); + t.end(); }); }); @@ -132,6 +141,9 @@ test('Map', function(t) { t.doesNotThrow(function() { map.load(style); }); + + map.release(); + t.end(); }); }); @@ -145,6 +157,9 @@ test('Map', function(t) { t.doesNotThrow(function() { map.load(JSON.stringify(style)); }); + + map.release(); + t.end(); }); }); @@ -171,6 +186,8 @@ test('Map', function(t) { map.render('invalid'); }, /First argument must be an options object/); + map.release(); + t.end(); }); }); @@ -185,6 +202,8 @@ test('Map', function(t) { map.render({}, 'invalid'); }, /Second argument must be a callback function/); + map.release(); + t.end(); }); }); @@ -194,14 +213,22 @@ test('Map', function(t) { t.throws(function() { map.render({}, function() {}); }, /Style is not set/); + + map.release(); + t.end(); }); }); t.test('returns an error', function(t) { + mbgl.once('message', function(msg) { + console.log(msg); + }); + setup(fileSource, function(map) { map.load(style); map.render({ zoom: 1 }, function(err, data) { + map.release(); t.ok(err); t.equal(err.message, 'Error rendering image'); t.end(); @@ -209,12 +236,26 @@ test('Map', function(t) { }); }); + t.test('double release', function(t) { + setup(fileSource, function(map) { + map.release(); + + t.throws(function() { + map.release(); + }, /Map resources have already been released/); + + t.end(); + }); + }); + t.test('returns an image', function(t) { setup(fileSource, function(map) { map.load(style); map.render({}, function(err, data) { t.error(err); + map.release(); + var filename = filePath('image.png'); var png = new PNG({ diff --git a/test/render.test.js b/test/render.test.js index 7249cbb7b..f55fa5608 100644 --- a/test/render.test.js +++ b/test/render.test.js @@ -54,6 +54,8 @@ function renderTest(style, info, base, key) { map.render(info[key], function(err, data) { t.error(err); + map.release(); + var expected = path.join(dir, 'expected.png'); var actual = path.join(dir, 'actual.png'); var diff = path.join(dir, 'diff.png'); From 9214584efb1b2777fb9d90885bde78173367113e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 27 May 2015 13:25:57 -0400 Subject: [PATCH 119/760] drop extraneous NodeFileSource v8::Persistent Remove from NodeRequest to stop preventing garbage collection. --- src/node_request.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/node_request.cpp b/src/node_request.cpp index 0fdc05bff..ccac813d2 100644 --- a/src/node_request.cpp +++ b/src/node_request.cpp @@ -35,11 +35,8 @@ NAN_METHOD(NodeRequest::New) { return NanThrowTypeError("Cannot create Request objects explicitly"); } - v8::Persistent source; - NanAssignPersistent(source, args[0]->ToObject()); - auto request = reinterpret_cast(args[1].As()->Value()); - auto req = new NodeRequest(NanNew(std::move(source)), request); + auto req = new NodeRequest(args[0]->ToObject(), request); req->Wrap(args.This()); NanReturnValue(args.This()); From ed648f9effc57c19c1091661b752d4ce2e05eff9 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 28 May 2015 12:09:07 -0400 Subject: [PATCH 120/760] skip failed request test --- test/js/map.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/map.test.js b/test/js/map.test.js index cdb3ffe54..928090183 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -220,7 +220,7 @@ test('Map', function(t) { }); }); - t.test('returns an error', function(t) { + t.skip('returns an error', function(t) { mbgl.once('message', function(msg) { console.log(msg); }); From 691ccff97892666199f1ad4860fb6c9c23ebb12e Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 27 May 2015 16:13:28 -0400 Subject: [PATCH 121/760] fix unhandled gzip test Refs https://github.com/mapbox/mapbox-gl-native/pull/1632 --- test/js/gzip.test.js | 48 +++++++++----------------------------------- test/js/map.test.js | 12 ++++++++--- vendor/mbgl | 2 +- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 9b8d3e297..ed1bb3c92 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -67,11 +67,10 @@ test('gzip', function(t) { setup(getFileSource(true, t), function(map) { map.load(style); map.render({}, function(err, data) { - t.error(err); - + mbgl.removeAllListeners('message'); map.release(); - mbgl.removeAllListeners('message'); + t.error(err); var filename = filePath('success.png'); @@ -101,54 +100,25 @@ test('gzip', function(t) { }); }); - // skip this for now because map.render doesn't error properly on bad - // vector tiles - t.skip('unhandled', function(t) { - var errorEmitted = false; - - mbgl.on('message', function(msg) { + t.test('unhandled', function(t) { + mbgl.once('message', function(msg) { if (msg.severity == 'ERROR') { - errorEmitted = true; t.ok(msg, 'emits error'); - t.equal(msg.class, 'ParseTile'); + t.equal(msg.class, 'ResourceLoader'); t.equal(msg.severity, 'ERROR'); - t.ok(msg.text.match(/failed: pbf unknown field type exception/), 'pbf unknown field type exception'); + t.ok(msg.text.match(/pbf unknown field type exception/), 'error text matches'); } }); setup(getFileSource(false, t), function(map) { map.load(style); map.render({}, function(err, data) { - if (!errorEmitted) t.fail('no error emitted'); - map.release(); - mbgl.removeAllListeners('message'); + t.ok(err, 'returns error'); + t.equal(err.message, 'Error rendering image'); - var filename = filePath('unhandled.png'); - - var png = new PNG({ - width: data.width, - height: data.height - }); - - png.data = data.pixels; - - if (process.env.UPDATE) { - png.pack() - .pipe(fs.createWriteStream(filename.expected)) - .on('finish', t.end); - } else { - png.pack() - .pipe(fs.createWriteStream(filename.actual)) - .on('finish', function() { - compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) { - t.error(err); - t.ok(diff <= 0.01, 'actual matches expected'); - t.end(); - }); - }); - } + t.end(); }); }); }); diff --git a/test/js/map.test.js b/test/js/map.test.js index 928090183..a29f661d8 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -221,16 +221,22 @@ test('Map', function(t) { }); t.skip('returns an error', function(t) { - mbgl.once('message', function(msg) { - console.log(msg); + mbgl.on('message', function(msg) { + t.ok(msg, 'emits error'); + t.equal(msg.class, 'ResourceLoader'); + t.equal(msg.severity, 'ERROR'); + t.ok(msg.text.match(/Failed to load/), 'error text matches'); }); setup(fileSource, function(map) { map.load(style); map.render({ zoom: 1 }, function(err, data) { + mbgl.removeAllListeners('message'); map.release(); - t.ok(err); + + t.ok(err, 'returns error'); t.equal(err.message, 'Error rendering image'); + t.end(); }); }); diff --git a/vendor/mbgl b/vendor/mbgl index 9dd8d2672..a96a251de 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 9dd8d26729eba5ecb8d788b8ba2150cf5b4bdeca +Subproject commit a96a251de9cad66c60fbc3f3cb8376c5d53df25e From b4f21b73d082521d657f229d3a04e810ee3034df Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Mon, 8 Jun 2015 15:40:45 +0300 Subject: [PATCH 122/760] Removed unused method on NodeFileSource --- src/node_file_source.cpp | 8 -------- src/node_file_source.hpp | 2 -- 2 files changed, 10 deletions(-) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 866e6d478..6f4a05bca 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -80,14 +80,6 @@ void NodeFileSource::cancel(mbgl::Request* req) { queue->send(Action{ Action::Cancel, req }); } -void NodeFileSource::request(const mbgl::Resource& resource, Callback callback) { - auto req = 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, req }); -} - void NodeFileSource::processAdd(mbgl::Request *req) { NanScope(); diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index a56c0f7c0..e69a8fcf1 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -35,8 +35,6 @@ public: mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); void cancel(mbgl::Request*); - void request(const mbgl::Resource&, Callback); - // visiblity? void notify(mbgl::Request*, const std::shared_ptr&); From d515f8d77db11146d1548642e6bc17b8e3c0ab3d Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 4 Jun 2015 16:20:31 +0300 Subject: [PATCH 123/760] Bump Mapbox GL Native to v0.3.3 --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index a96a251de..14083700e 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit a96a251de9cad66c60fbc3f3cb8376c5d53df25e +Subproject commit 14083700e5e85119adf134e6edbcbfd54cfd33b7 From 34ef7ac003b1f44fd1a1a1db052b0905b9a4a77c Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 11 Jun 2015 17:42:48 +0300 Subject: [PATCH 124/760] Implement request coalescing and synchronous request cancellation When requests are made to the FileSource, we now group them by resource, similarly to what is done on the native implementation. The file source will reuse the same Response for all the requests on the observer list. The NodeRequest no longer holds a reference to the mbgl::Request, so we can cancel it synchronously. This is needed to avoid a deadlock when releasing the NodeMap resources with pending requests. Before: - Main thread | NodeMap::release() - Main thread | mbgl::~Map() - Main thread | Blocks waiting for the Map thread to join - Map thread | mbgl::Request::cancel() - Map thread | NodeFileSource posts the "cancel" message to main thread - *deadlock* | Map thread cannot join because mbgl::Request holds a reference to the Map thread main loop. Main thread is blocked, never gets the "cancel" message. Now: - Main thread | NodeMap::release() - Main thread | mbgl::~Map() - Main thread | Blocks waiting for the Map thread to join - Map thread | Request::cancel() - Map thread | NodeFileSource::cancel(), destroys the mbgl::Request synchronously, mutex guarded access. We also send a notification to cancel the NodeRequest on the main thread. - Map thread | Map thread joins. - Main thread | NodeMap::release() returns - Main thread | NodeRequest gets canceled. If NodeRequest replies before it gets the "cancel" message, nothing happens, notification gets discarded. --- src/node_file_source.cpp | 84 ++++++++++++++++++++++++++++++---------- src/node_file_source.hpp | 25 +++++++++--- src/node_request.cpp | 29 +++++++------- src/node_request.hpp | 11 ++++-- 4 files changed, 104 insertions(+), 45 deletions(-) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 6f4a05bca..935bac427 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -4,6 +4,8 @@ #include +#include + namespace node_mbgl { //////////////////////////////////////////////////////////////////////////////////////////////// @@ -42,15 +44,15 @@ NAN_METHOD(NodeFileSource::New) { struct NodeFileSource::Action { const enum : bool { Add, Cancel } type; - mbgl::Request *const request; + mbgl::Resource const resource; }; NodeFileSource::NodeFileSource() : queue(new Queue(uv_default_loop(), [this](Action &action) { if (action.type == Action::Add) { - processAdd(action.request); + processAdd(action.resource); } else if (action.type == Action::Cancel) { - processCancel(action.request); + processCancel(action.resource); } })) { @@ -66,21 +68,50 @@ NodeFileSource::~NodeFileSource() { mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) { auto req = 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, req }); + std::lock_guard lock(observersMutex); + auto it = observers.find(resource); + if (it == 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 }); + } else { + it->second.emplace_back(req); + } + return req; } void NodeFileSource::cancel(mbgl::Request* req) { req->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, req }); + std::lock_guard lock(observersMutex); + + auto observersIter = observers.find(req->resource); + if (observersIter == observers.end()) { + return; + } + + auto& observersList = observersIter->second; + auto observersListIter = std::find(observersList.begin(), observersList.end(), req); + if (observersListIter == observersList.end()) { + return; + } + + observersList.erase(observersListIter); + if (observersList.empty()) { + observers.erase(observersIter); + + // 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(mbgl::Request *req) { +void NodeFileSource::processAdd(const mbgl::Resource& resource) { NanScope(); // Make sure the loop stays alive as long as request is pending. @@ -89,23 +120,23 @@ void NodeFileSource::processAdd(mbgl::Request *req) { } auto handle = NanObjectWrapHandle(this); - auto requestHandle = NanNew(NodeRequest::Create(handle, req)); + auto requestHandle = NanNew(NodeRequest::Create(handle, resource)); #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) const v8::UniquePersistent requestPersistent(v8::Isolate::GetCurrent(), requestHandle); #else v8::Persistent requestPersistent; NanAssignPersistent(requestPersistent, requestHandle); #endif - pending.emplace(req, std::move(requestPersistent)); + pending.emplace(resource, std::move(requestPersistent)); v8::Local argv[] = { requestHandle }; NanMakeCallback(handle, NanNew("request"), 1, argv); } -void NodeFileSource::processCancel(mbgl::Request *req) { +void NodeFileSource::processCancel(const mbgl::Resource& resource) { NanScope(); - auto it = pending.find(req); + 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. @@ -136,14 +167,11 @@ void NodeFileSource::processCancel(mbgl::Request *req) { // Set the request handle in the request wrapper handle to null ObjectWrap::Unwrap(requestHandle)->cancel(); } - - // Finally, destruct the request object - req->destruct(); } -void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptr& response) { +void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_ptr& response) { // First, remove the request, since it might be destructed at any point now. - auto it = pending.find(req); + auto it = pending.find(resource); if (it != pending.end()) { #if (NODE_MODULE_VERSION <= NODE_0_10_MODULE_VERSION) NanDisposePersistent(it->second); @@ -156,7 +184,23 @@ void NodeFileSource::notify(mbgl::Request *req, const std::shared_ptrnotify(response); + std::vector observersList; + + { + std::lock_guard lock(observersMutex); + + auto observersIter = observers.find(resource); + if (observersIter == observers.end()) { + return; + } + + observersList.swap(observersIter->second); + observers.erase(observersIter); + } + + for (auto request : observersList) { + request->notify(response); + } } } diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index e69a8fcf1..5220cd235 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -10,8 +10,10 @@ #include #pragma GCC diagnostic pop -#include #include +#include +#include +#include namespace node_mbgl { @@ -36,21 +38,32 @@ public: void cancel(mbgl::Request*); // visiblity? - void notify(mbgl::Request*, const std::shared_ptr&); + void notify(const mbgl::Resource&, const std::shared_ptr&); private: struct Action; using Queue = util::AsyncQueue; - void processAdd(mbgl::Request*); - void processCancel(mbgl::Request*); + void processAdd(const mbgl::Resource&); + void processCancel(const mbgl::Resource&); private: #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - std::map &> pending; + std::unordered_map &, mbgl::Resource::Hash> pending; #else - std::map> pending; + std::unordered_map, mbgl::Resource::Hash> pending; #endif + + // The observers list will hold pointers to all the requests waiting + // for a particular resource. It is also used for coalescing requests, + // so we don't ask for the same resources twice. 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::Hash> observers; + std::mutex observersMutex; + Queue *queue = nullptr; }; diff --git a/src/node_request.cpp b/src/node_request.cpp index ccac813d2..e01162ff5 100644 --- a/src/node_request.cpp +++ b/src/node_request.cpp @@ -35,35 +35,34 @@ NAN_METHOD(NodeRequest::New) { return NanThrowTypeError("Cannot create Request objects explicitly"); } - auto request = reinterpret_cast(args[1].As()->Value()); - auto req = new NodeRequest(args[0]->ToObject(), request); + auto resource = reinterpret_cast(args[1].As()->Value()); + auto req = new NodeRequest(args[0]->ToObject(), *resource); req->Wrap(args.This()); NanReturnValue(args.This()); } -v8::Handle NodeRequest::Create(v8::Handle source, mbgl::Request *request) { +v8::Handle NodeRequest::Create(v8::Handle source, const mbgl::Resource& resource) { NanEscapableScope(); - v8::Local argv[] = { NanNew(source), NanNew(request) }; + v8::Local argv[] = { NanNew(source), + NanNew(const_cast(&resource)) }; auto instance = NanNew(constructorTemplate)->GetFunction()->NewInstance(2, argv); - instance->ForceSet(NanNew("url"), NanNew(request->resource.url), v8::ReadOnly); - instance->ForceSet(NanNew("kind"), NanNew(int(request->resource.kind)), v8::ReadOnly); + instance->ForceSet(NanNew("url"), NanNew(resource.url), v8::ReadOnly); + instance->ForceSet(NanNew("kind"), NanNew(int(resource.kind)), v8::ReadOnly); return NanEscapeScope(instance); } NAN_METHOD(NodeRequest::Respond) { auto nodeRequest = ObjectWrap::Unwrap(args.Holder()); - if (!nodeRequest->request) { + if (!nodeRequest->resource) { return NanThrowError("Request has already been responded to, or was canceled."); } auto source = ObjectWrap::Unwrap(NanNew(nodeRequest->source)); - - auto request = nodeRequest->request; - nodeRequest->request = nullptr; + auto resource = std::move(nodeRequest->resource); if (args.Length() < 1) { return NanThrowTypeError("First argument must be an error object"); @@ -76,7 +75,7 @@ NAN_METHOD(NodeRequest::Respond) { const NanUtf8String message { args[0]->ToString() }; response->message = std::string { *message, size_t(message.length()) }; - source->notify(request, response); + source->notify(*resource, response); } else if (args.Length() < 2 || !args[1]->IsObject()) { return NanThrowTypeError("Second argument must be a response object"); } else { @@ -120,7 +119,7 @@ NAN_METHOD(NodeRequest::Respond) { } // Send the response object to the NodeFileSource object - source->notify(request, response); + source->notify(*resource, response); } NanReturnUndefined(); @@ -129,8 +128,8 @@ NAN_METHOD(NodeRequest::Respond) { //////////////////////////////////////////////////////////////////////////////////////////////// // Instance -NodeRequest::NodeRequest(v8::Local source_, mbgl::Request *request_) - : request(request_) { +NodeRequest::NodeRequest(v8::Local source_, const mbgl::Resource& resource_) + : resource(std::make_unique(resource_)) { NanAssignPersistent(source, source_); } @@ -139,7 +138,7 @@ NodeRequest::~NodeRequest() { } void NodeRequest::cancel() { - request = nullptr; + resource.reset(); } } diff --git a/src/node_request.hpp b/src/node_request.hpp index 009855066..0fbf91fc6 100644 --- a/src/node_request.hpp +++ b/src/node_request.hpp @@ -7,8 +7,11 @@ #include #pragma GCC diagnostic pop +#include + +#include + class NodeFileSource; -namespace mbgl { class Request; } namespace node_mbgl { @@ -20,21 +23,21 @@ public: static NAN_METHOD(New); static NAN_METHOD(Respond); - static v8::Handle Create(v8::Handle source, mbgl::Request *request); + static v8::Handle Create(v8::Handle source, const mbgl::Resource& resource); static v8::Persistent constructorTemplate; //////////////////////////////////////////////////////////////////////////////////////////////// // Instance public: - NodeRequest(v8::Local source, mbgl::Request *request); + NodeRequest(v8::Local source, const mbgl::Resource& resource); ~NodeRequest(); void cancel(); private: v8::Persistent source; - mbgl::Request *request = nullptr; + std::unique_ptr resource; }; } From b602835626dfc1532bf5c26b9d2c34054a2d8fac Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Mon, 15 Jun 2015 01:55:20 +0300 Subject: [PATCH 125/760] Fix invalid NodeRequest reference On the code path built with node v0.12.4, we are storing an invalid reference to NodeRequest on the "pending" list. It will crash when we need to access it like we do when canceling a request. Ideally we should just have a "std::unordered_map" but that is not possible because the move semantics are not fully implemented until V8 v4.3.22. It can be used on a std::vector but not on a std::map. See: https://code.google.com/p/v8/issues/detail?id=3669 The solution for keep using C++11 move semantics is to declare the v8::CopyablePersistentTraits shenanigans and explicitly clear the v8::Persistent (like we do already for node v0.10). When Node gets V8 v4.3.22+ we can go back to v8::UniquePersistent with a more clear implementation. --- src/node_file_source.cpp | 14 +++++--------- src/node_file_source.hpp | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/node_file_source.cpp b/src/node_file_source.cpp index 935bac427..3409f03af 100644 --- a/src/node_file_source.cpp +++ b/src/node_file_source.cpp @@ -121,12 +121,9 @@ void NodeFileSource::processAdd(const mbgl::Resource& resource) { auto handle = NanObjectWrapHandle(this); auto requestHandle = NanNew(NodeRequest::Create(handle, resource)); -#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - const v8::UniquePersistent requestPersistent(v8::Isolate::GetCurrent(), requestHandle); -#else + v8::Persistent requestPersistent; NanAssignPersistent(requestPersistent, requestHandle); -#endif pending.emplace(resource, std::move(requestPersistent)); v8::Local argv[] = { requestHandle }; @@ -143,12 +140,9 @@ void NodeFileSource::processCancel(const mbgl::Resource& resource) { } else { #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) auto requestHandle = v8::Local::New(v8::Isolate::GetCurrent(), it->second); + it->second.Reset(); #else auto requestHandle = NanNew(it->second); -#endif - - // Dispose and remove the persistent handle -#if (NODE_MODULE_VERSION <= NODE_0_10_MODULE_VERSION) NanDisposePersistent(it->second); #endif pending.erase(it); @@ -173,7 +167,9 @@ void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_pt // 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) +#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) + it->second.Reset(); +#else NanDisposePersistent(it->second); #endif pending.erase(it); diff --git a/src/node_file_source.hpp b/src/node_file_source.hpp index 5220cd235..06b1c3d32 100644 --- a/src/node_file_source.hpp +++ b/src/node_file_source.hpp @@ -49,7 +49,7 @@ private: private: #if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - std::unordered_map &, mbgl::Resource::Hash> pending; + std::unordered_map>, mbgl::Resource::Hash> pending; #else std::unordered_map, mbgl::Resource::Hash> pending; #endif From 0d2543675fe1b1ce94147629fff2e90137f1d49f Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 3 Jun 2015 16:33:50 +0300 Subject: [PATCH 126/760] Update the tests Reflect the changes on the previous MBGL version bump and unskip tests that should pass now. --- test/js/gzip.test.js | 2 +- test/js/map.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index ed1bb3c92..2ed0080ac 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -104,7 +104,7 @@ test('gzip', function(t) { mbgl.once('message', function(msg) { if (msg.severity == 'ERROR') { t.ok(msg, 'emits error'); - t.equal(msg.class, 'ResourceLoader'); + t.equal(msg.class, 'Style'); t.equal(msg.severity, 'ERROR'); t.ok(msg.text.match(/pbf unknown field type exception/), 'error text matches'); } diff --git a/test/js/map.test.js b/test/js/map.test.js index a29f661d8..ce6017beb 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -220,10 +220,10 @@ test('Map', function(t) { }); }); - t.skip('returns an error', function(t) { + t.test('returns an error', function(t) { mbgl.on('message', function(msg) { t.ok(msg, 'emits error'); - t.equal(msg.class, 'ResourceLoader'); + t.equal(msg.class, 'Style'); t.equal(msg.severity, 'ERROR'); t.ok(msg.text.match(/Failed to load/), 'error text matches'); }); From a6ea979ec5dc4383b90fcf7d7c69c1718a7baf84 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Fri, 12 Jun 2015 17:08:57 +0300 Subject: [PATCH 127/760] Add test case for the request coalescing --- test/js/map.test.js | 138 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/test/js/map.test.js b/test/js/map.test.js index ce6017beb..57283c4dd 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -167,6 +167,144 @@ test('Map', function(t) { t.end(); }); + t.test('request coalesting', function(t) { + var requestList = []; + var fileSourceDoNotReply = new mbgl.FileSource(); + + // This file source will never reply to any + // request other than "./fixtures/tiles.tilejson" which + // will force the cancellation of the pending requests. + fileSourceDoNotReply.request = function(req) { + if (req.url != "./fixtures/tiles.tilejson") { + requestList.push(req); + return; + } + + fs.readFile(path.join('test', req.url), function(err, data) { + req.respond(err, { data: data }); + }); + }; + + fileSourceDoNotReply.cancel = function() { + requestList.pop(); + }; + + t.test('merge requests/cancels for the same resource', function(t) { + var map1 = new mbgl.Map(fileSourceDoNotReply); + var map2 = new mbgl.Map(fileSourceDoNotReply); + var map3 = new mbgl.Map(fileSourceDoNotReply); + var map4 = new mbgl.Map(fileSourceDoNotReply); + var map5 = new mbgl.Map(fileSourceDoNotReply); + + map1.load(style); + map2.load(style); + map3.load(style); + map4.load(style); + map5.load(style); + + var renderCallback = function(err, data) {} + + map1.render({}, renderCallback); + map2.render({}, renderCallback); + map3.render({}, renderCallback); + map4.render({}, renderCallback); + map5.render({}, renderCallback); + + var checkCancel = function() { + t.equal(requestList.length, 0); + t.end(); + }; + + var checkCoalescing = function() { + // Check if all the 5 vector tile + // requests are going to merge in + // a single request. + t.equal(requestList.length, 1); + + // This should cause the pending + // resources to get canceled, but + // only one cancel is emitted, because + // all map objects requested the same + // resource. + map1.release(); + map2.release(); + map3.release(); + map4.release(); + map5.release(); + + setTimeout(checkCancel, 500); + }; + + setTimeout(checkCoalescing, 500); + }); + + var vectorTileRequestCount = 0; + var fileSourceDelay = new mbgl.FileSource(); + + // This file source will add a little delay before + // replying so we make sure that all the map objects + // request the same resource will get a reply from the + // same request. + fileSourceDelay.request = function(req) { + var timeout = 0; + + if (req.url != "./fixtures/tiles.tilejson") { + timeout = 500; + t.ok(++vectorTileRequestCount == 1, "Should make only one request for tiles"); + } + + var readFile = function() { + fs.readFile(path.join('test', req.url), function(err, data) { + req.respond(err, { data: data }); + }); + }; + + setTimeout(readFile, timeout); + }; + + fileSourceDelay.cancel = function() { + t.fail("Should never cancel"); + }; + + t.test('one request for the same resource notifies multiple map objects', function(t) { + var map1 = new mbgl.Map(fileSourceDelay); + var map2 = new mbgl.Map(fileSourceDelay); + var map3 = new mbgl.Map(fileSourceDelay); + var map4 = new mbgl.Map(fileSourceDelay); + var map5 = new mbgl.Map(fileSourceDelay); + + map1.load(style); + map2.load(style); + map3.load(style); + map4.load(style); + map5.load(style); + + var renderFinishedCount = 0; + var renderCallback = function(err, data) { + // We make only one request for tiles, but that + // should be enough for all these map objects because + // they all want the same resource. + if (++renderFinishedCount == 5) { + map1.release(); + map2.release(); + map3.release(); + map4.release(); + map5.release(); + + t.end(); + } + } + + map1.render({}, renderCallback); + map2.render({}, renderCallback); + map3.render({}, renderCallback); + map4.render({}, renderCallback); + map5.render({}, renderCallback); + }); + + t.end(); + }); + t.test('render argument requirements', function(t) { var fileSource = new mbgl.FileSource(); fileSource.request = function(req) { From e06bf232f44e09db4c628375441586cb994cc6a1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 15 Jun 2015 14:52:35 -0400 Subject: [PATCH 128/760] request coalescing fixes #114 --- test/js/map.test.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/js/map.test.js b/test/js/map.test.js index 57283c4dd..427cf586b 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -132,11 +132,7 @@ test('Map', function(t) { }); }); - t.skip('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { - mbgl.once('message', function(msg) { - console.log(msg); - }); - + t.test('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { setup(fileSource, function(map) { t.doesNotThrow(function() { map.load(style); @@ -148,11 +144,7 @@ test('Map', function(t) { }); }); - t.skip('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { - mbgl.once('message', function(msg) { - console.log(msg); - }); - + t.test('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { setup(fileSource, function(map) { t.doesNotThrow(function() { map.load(JSON.stringify(style)); From 73abfc3d08e84c805ea5b6cd60548bad665dbaed Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 14 May 2015 11:40:18 -0400 Subject: [PATCH 129/760] update changelog and readme [skip ci] - Document Node.js v0.12.x and io.js support - Document map.release functionality. - Add notes on garbage collection and request handling. - Update binary platforms and build instructions. - Document API token FileSource implementation from https://github.com/mapbox/mapbox-gl-native/pull/1607 - Update Kind enum values. --- CHANGELOG.md | 14 +++++++++++++ README.md | 55 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d604b678..b8c40b946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# 1.1.0 + +- Adds Node.js v0.12.x and io.js support. +- Adds `map.release()` method for manual cleanup of map resources. +- Fixes garbage collection of NodeMap objects. +- Returns an error callback for failed NodeFileSource requests. +- Fixes handling of corrupt NodeFileSource request data. +- Implements request coalescing to fix NodeRequest cancellation. +- Updates build scripts and Travis CI configuration. +- Logs Environment ID and thread name when in an Environment scope. +- Refactors NodeLog to implement mbgl::Log::Observer. +- Fixes uncaught exception from missing sprites. +- Fixes Unicode glyph range end. + # 1.0.3 - Fixes crash during garbage collection by assigning FileSource handle to a v8::Persistent in NodeMap constructor. diff --git a/README.md b/README.md index f554e4d1f..6f0285f86 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). By default, installs binaries. On these platforms no external dependencies are needed. - 64 bit OS X or 64 bit Linux -- Node v0.10.x +- Node.js v0.10.x, Node.js v0.12.x, io.js v2.x Just run: @@ -17,7 +17,7 @@ Just run: npm install mapbox-gl-native ``` -However, other platforms will fall back to a source compile with `make build`. To compile this module, make sure all submodules are initialized. +However, other platforms will fall back to a source compile with `npm run build`. To compile this module, make sure all submodules are initialized with `git submodule update --init`. ## Rendering a map tile @@ -46,6 +46,8 @@ The first argument passed to `map.render` is an options object, all keys are opt _More about classes: https://github.com/mapbox/mapbox-gl-js/blob/master/API.md#working-with-style-classes_ +When you are finished using a map object, you can call `map.release()` to dispose the internal map resources manually. This is not necessary, but can be helpful to optimize resource usage (memory, file sockets) on a more granualar level than v8's garbage collector. + ## Testing ``` @@ -83,10 +85,12 @@ The `kind` is an enum and defined in `mbgl.Resource`: ```json { "Unknown": 0, - "Tile": 1, - "Glyphs": 2, - "Image": 3, - "JSON": 4 + "Style": 1, + "Source": 2, + "Tile": 3, + "Glyphs": 4, + "JSON": 5, + "Image": 6 } ``` @@ -110,7 +114,7 @@ This is a very barebones implementation and you'll probably want a better implem modified: new Date(), expires: new Date(), etag: "string", - data: new Buffer(), + data: new Buffer() }; ``` @@ -156,12 +160,45 @@ Note that in reality, Mapbox GL uses two types of protocols: `asset://` for file ## Mapbox API Access tokens -To use styles that rely on Mapbox vector tiles, you must register a [API access token](https://www.mapbox.com/developers/api/#access-tokens) on the map. Use the `setAccessToken` method before load: +To use styles that rely on Mapbox vector tiles, you must pass an [API access token](https://www.mapbox.com/developers/api/#access-tokens) in your `FileSource` implementation with requests to `mapbox://` protocols. ```js +var mbgl = require('mapbox-gl-native'); +var request = require('request'); +var url = require('url'); + +var fileSource = new mbgl.FileSource(); +fileSource.request = function(req) { + var opts = { + url: req.url, + encoding: null, + gzip: true + }; + + if (url.parse(req.url).protocol === 'mapbox:') { + opts.qs = { access_token: process.env.MAPBOX_ACCESS_TOKEN}; + } + + request(opts, function (err, res, body) { + if (err) { + req.respond(err); + } else if (res.statusCode == 200) { + var response = {}; + + if (res.headers.modified) { response.modified = new Date(res.headers.modified); } + if (res.headers.expires) { response.expires = new Date(res.headers.expires); } + if (res.headers.etag) { response.etag = res.headers.etag; } + response.data = body; + req.respond(null, response); + } else { + req.respond(new Error(JSON.parse(body).message)); + } + }); +} + var map = mbgl.Map(fileSource); var style = mapboxStyle; // includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` -map.setAccessToken(process.env.MAPBOX_ACCESS_TOKEN); + map.load(style); map.render({}, function(err, image) { if (err) throw err; From 5c8107ee3bb4f794bcf18f1072c40c75398db0b4 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 15 Jun 2015 17:04:41 -0400 Subject: [PATCH 130/760] v1.1.0 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8da4582f6..16e086a54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.0.3", + "version": "1.1.0", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 6f4e72765408e1266cc7b5d4e8fcd8b57b7f1498 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 15 Jun 2015 17:15:58 -0400 Subject: [PATCH 131/760] add npm badge [skip ci] --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6f0285f86..53e82434a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). + +[![NPM](https://nodei.co/npm/mapbox-gl-native.png)](https://npmjs.org/package/mapbox-gl-native) + [![Build Status](https://travis-ci.org/mapbox/node-mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/node-mapbox-gl-native) ## Installing From b8c45b62739d2886e4130c4fe06cd6750a3d9070 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 15 Jun 2015 17:56:22 -0400 Subject: [PATCH 132/760] match all clang++ versions [publish binary] --- scripts/travis_publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_publish.sh b/scripts/travis_publish.sh index 124795757..da24832b4 100755 --- a/scripts/travis_publish.sh +++ b/scripts/travis_publish.sh @@ -11,7 +11,7 @@ fi COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') -if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX == "clang++" ]]; then +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX == ^"clang++"* ]]; then npm install aws-sdk ./node_modules/.bin/node-pre-gyp package From 7be5f15e500af3dac7833b9a70b69fe8748c31dc Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 15 Jun 2015 18:19:44 -0400 Subject: [PATCH 133/760] fix clang CXX matching [publish binary] explicitly set CXX on OS X --- .travis.yml | 3 +++ scripts/travis_publish.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2a5b8530..891496e03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,16 +68,19 @@ matrix: - os: osx compiler: clang env: + - CXX: clang++ - NODE_EXE: "iojs" - NODE_VERSION: 2.0.1 - os: osx compiler: clang env: + - CXX: clang++ - NODE_EXE: "node" - NODE_VERSION: 0.12.0 - os: osx compiler: clang env: + - CXX: clang++ - NODE_EXE: "node" - NODE_VERSION: 0.10.36 diff --git a/scripts/travis_publish.sh b/scripts/travis_publish.sh index da24832b4..d06a2a8a3 100755 --- a/scripts/travis_publish.sh +++ b/scripts/travis_publish.sh @@ -11,7 +11,7 @@ fi COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') -if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX == ^"clang++"* ]]; then +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX =~ ^clang* ]]; then npm install aws-sdk ./node_modules/.bin/node-pre-gyp package From a25fa2e076b2c1f03b0c4d9b0c4210edddd608a3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 16 Jun 2015 13:52:32 -0400 Subject: [PATCH 134/760] bump mbgl to v0.3.5 add geojsonvt dependency in Makefile update changelog --- CHANGELOG.md | 4 ++++ Makefile | 2 +- vendor/mbgl | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c40b946..4464e383b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.1 + +- Bumps mbgl submodule to v0.3.5 + # 1.1.0 - Adds Node.js v0.12.x and io.js support. diff --git a/Makefile b/Makefile index 4884d5a5f..74b911a14 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ xproj: build/binding.xcodeproj open build/binding.xcodeproj $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure - make -C $(MBGL) config/$*.gypi + make -C $(MBGL) config/$*.gypi geojsonvt .PHONY: test-suite test-suite: diff --git a/vendor/mbgl b/vendor/mbgl index 14083700e..d9f09596f 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 14083700e5e85119adf134e6edbcbfd54cfd33b7 +Subproject commit d9f09596fbaa44af9b748ff67dcf2d247e72eddb From 6081f15237a7991b1cf1f11e089ceb0e544cb52a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 16 Jun 2015 15:35:52 -0400 Subject: [PATCH 135/760] v1.1.1 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16e086a54..66a1d76bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.1.0", + "version": "1.1.1", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 6d17467dbc01bac2217b3b0853103d1b68b6c55c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 17 Jun 2015 12:15:50 -0400 Subject: [PATCH 136/760] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4464e383b..32e153c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Returns an error callback for failed NodeFileSource requests. - Fixes handling of corrupt NodeFileSource request data. - Implements request coalescing to fix NodeRequest cancellation. +- Removes `setAccessToken` method from NodeMap, `mapbox://` URLs + should be handled through `NodeFileSource` now. - Updates build scripts and Travis CI configuration. - Logs Environment ID and thread name when in an Environment scope. - Refactors NodeLog to implement mbgl::Log::Observer. From 27fc2f18e30a92234b997cb6a16ce462821e5c5b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 17 Jun 2015 14:22:32 -0400 Subject: [PATCH 137/760] clarify C++14 runtime support and external dependencies to build from source --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 53e82434a..568e6f8cc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). ## Installing -By default, installs binaries. On these platforms no external dependencies are needed. +Requires a modern C++ runtime that supports C++14. + +By default, installs binaries. On these platforms no additional dependencies are needed. - 64 bit OS X or 64 bit Linux - Node.js v0.10.x, Node.js v0.12.x, io.js v2.x @@ -20,7 +22,7 @@ Just run: npm install mapbox-gl-native ``` -However, other platforms will fall back to a source compile with `npm run build`. To compile this module, make sure all submodules are initialized with `git submodule update --init`. +Other platforms will fall back to a source compile with `npm run build`. To compile this module, make sure all submodules are initialized with `git submodule update --init` and install the [external dependencies required to build from source](https://github.com/mapbox/mapbox-gl-native#depends). ## Rendering a map tile From d434746a4649b69c57a2f749b4f7e848b63cedf6 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Tue, 16 Jun 2015 16:42:33 +0300 Subject: [PATCH 138/760] Better error messages Use the error message passed by the error exception instead of always reporting "Error rendering image". --- src/node_map.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/node_map.cpp b/src/node_map.cpp index 89ba8d3b9..9a21decd5 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -229,13 +229,22 @@ void NodeMap::renderFinished() { assert(!image); if (error) { + std::string errorMessage; + + try { + std::rethrow_exception(error); + } catch (const std::exception& ex) { + errorMessage = ex.what(); + } + + v8::Local argv[] = { + NanError(errorMessage.c_str()) + }; + // This must be empty to be prepared for the next render call. error = nullptr; assert(!error); - v8::Local argv[] = { - NanError("Error rendering image") - }; cb->Call(1, argv); } else if (img) { auto result = NanNew(); From ef31fc5aed4e07b3d9e98b25180b8f809d51a5cf Mon Sep 17 00:00:00 2001 From: Nicholas Hallahan Date: Sat, 20 Jun 2015 16:21:41 -0700 Subject: [PATCH 139/760] attempting to put one annotation on the map - gets to the native call and then nothing happens --- android/cpp/jni.cpp | 22 +++++++++++++ .../com/mapbox/mapboxgl/views/MapView.java | 4 +++ .../mapbox/mapboxgl/views/NativeMapView.java | 6 ++++ .../mapbox/mapboxgl/testapp/MainActivity.java | 33 +++++++++++++++++++ .../src/main/res/menu/menu_main.xml | 12 ++++--- .../src/main/res/values/strings.xml | 2 ++ 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 64e72e149..bfa07a2ff 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -419,6 +419,26 @@ void JNICALL nativeSetLatLng(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, j nativeMapView->getMap().setLatLng(mbgl::LatLng(latitude, longitude), std::chrono::milliseconds(duration)); } +void JNICALL nativeAddAnnotation(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject latLng) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddAnnotation"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + jdouble latitude = env->GetDoubleField(latLng, latLngLatitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + jdouble longitude = env->GetDoubleField(latLng, latLngLongitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + nativeMapView->getMap().addPointAnnotation(mbgl::LatLng(latitude, longitude), std::string("")); +} + jobject JNICALL nativeGetLatLng(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetLatLng"); assert(nativeMapViewPtr != 0); @@ -952,6 +972,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { {"nativeMoveBy", "(JDDJ)V", reinterpret_cast(&nativeMoveBy)}, {"nativeSetLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;J)V", reinterpret_cast(&nativeSetLatLng)}, + {"nativeAddAnnotation", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)V", + reinterpret_cast(&nativeAddAnnotation)}, {"nativeGetLatLng", "(J)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeGetLatLng)}, {"nativeResetPosition", "(J)V", reinterpret_cast(&nativeResetPosition)}, diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index e482c0be5..9588f1e5a 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -204,6 +204,10 @@ public class MapView extends SurfaceView { } } + public void addAnnotation(LatLng marker) { + mNativeMapView.addAnnotation(marker); + } + // // Property methods // diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java index 7f074c6db..9a2baa447 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java @@ -197,6 +197,10 @@ class NativeMapView { nativeSetLatLng(mNativeMapViewPtr, latLng, duration); } + public void addAnnotation(LatLng latLng) { + nativeAddAnnotation(mNativeMapViewPtr, latLng); + } + public LatLng getLatLng() { return nativeGetLatLng(mNativeMapViewPtr); } @@ -422,6 +426,8 @@ class NativeMapView { private native void nativeSetLatLng(long nativeMapViewPtr, LatLng latLng, long duration); + private native void nativeAddAnnotation(long nativeMapViewPtr, LatLng latLng); + private native LatLng nativeGetLatLng(long nativeMapViewPtr); private native void nativeResetPosition(long nativeMapViewPtr); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 65cb17462..8bef3392c 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -77,6 +77,9 @@ public class MainActivity extends ActionBarActivity { private float mCompassBearing; private boolean mCompassValid = false; + // Used for point annotations + private boolean mIsPointAnnotationsOn = false; + // // Lifecycle events // @@ -210,6 +213,10 @@ public class MainActivity extends ActionBarActivity { } return true; + case R.id.action_point_annotations: + // Toggle point annotations + togglePointAnnotations(!mIsPointAnnotationsOn); + default: return super.onOptionsItemSelected(item); } @@ -249,6 +256,32 @@ public class MainActivity extends ActionBarActivity { } } + /** + * Enable / Disable Point annotations. + * + * TODO: Implement updating the UI and removing point annotations from the map. + * + * @param enablePointAnnotations + */ + private void togglePointAnnotations(boolean enablePointAnnotations) { + if (enablePointAnnotations) { + if (!mIsPointAnnotationsOn) { + mIsPointAnnotationsOn = true; + addPointAnnotations(); + } + } else { + if (mIsPointAnnotationsOn) { + mIsPointAnnotationsOn = false; + + } + } + } + + private void addPointAnnotations() { + LatLng backLot = new LatLng(38.649441, -121.369064); + mMapFragment.getMap().addAnnotation(backLot); + } + // This class forwards location updates to updateLocation() private class GpsListener implements LocationListener { diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml index bf6dbbf36..965f88254 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml @@ -1,11 +1,15 @@ - + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 901e23192..cddf228bf 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -7,6 +7,8 @@ Toggle debug mode + Toggle point annotations + FPS: Map compass. Click to reset the map rotation to North. From d37e22b9c26e4665cb173d055a1d525a5e1209ce Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 17 Jun 2015 10:41:49 +0300 Subject: [PATCH 140/760] Bump Mapbox GL Native to v0.4.0 Pull the fixes for the "rendering without setting a style" crash. --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index d9f09596f..0955dfc19 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit d9f09596fbaa44af9b748ff67dcf2d247e72eddb +Subproject commit 0955dfc19f30aedcc95e7e3c03c73d3dcc149c44 From 1ae7dd585de1f5cbdfd1cdd11c05d653e26bf0e5 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Mon, 22 Jun 2015 08:25:23 +0300 Subject: [PATCH 141/760] Throw when style is not loaded mbgl::Map won't throw if the style is not set but we can track that on NodeMap. --- src/node_map.cpp | 10 ++++++++++ src/node_map.hpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/node_map.cpp b/src/node_map.cpp index 9a21decd5..e8dc28110 100644 --- a/src/node_map.cpp +++ b/src/node_map.cpp @@ -102,6 +102,10 @@ NAN_METHOD(NodeMap::Load) { 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"); } @@ -122,6 +126,8 @@ NAN_METHOD(NodeMap::Load) { return NanThrowError(ex.what()); } + nodeMap->loaded = true; + NanReturnUndefined(); } @@ -168,6 +174,10 @@ NAN_METHOD(NodeMap::Render) { 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); diff --git a/src/node_map.hpp b/src/node_map.hpp index bb238ea88..d442d93bb 100644 --- a/src/node_map.hpp +++ b/src/node_map.hpp @@ -33,6 +33,8 @@ public: void renderFinished(); void release(); + + inline bool isLoaded() { return loaded; } inline bool isValid() { return valid; } static std::unique_ptr ParseOptions(v8::Local obj); @@ -60,6 +62,7 @@ private: // Async for delivering the notifications of render completion. uv_async_t *async; + bool loaded = false; bool valid = true; }; From e6c442bf4ee78cfb3be68d694c669b1cde86ecd9 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Tue, 16 Jun 2015 15:59:55 +0300 Subject: [PATCH 142/760] Adjusts tests for the new API Unskip tests that should be passing now and adjust error messages to reflect recent changes. --- test/js/gzip.test.js | 2 +- test/js/map.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/js/gzip.test.js b/test/js/gzip.test.js index 2ed0080ac..c839dd5c2 100644 --- a/test/js/gzip.test.js +++ b/test/js/gzip.test.js @@ -116,7 +116,7 @@ test('gzip', function(t) { map.release(); t.ok(err, 'returns error'); - t.equal(err.message, 'Error rendering image'); + t.ok(err.message.match(/Failed to parse/), 'error text matches'); t.end(); }); diff --git a/test/js/map.test.js b/test/js/map.test.js index 427cf586b..edeab8370 100644 --- a/test/js/map.test.js +++ b/test/js/map.test.js @@ -338,11 +338,11 @@ test('Map', function(t) { }); }); - t.skip('requires a style to be set', function(t) { + t.test('requires a style to be set', function(t) { setup(fileSource, function(map) { t.throws(function() { map.render({}, function() {}); - }, /Style is not set/); + }, /Style is not loaded/); map.release(); @@ -365,7 +365,7 @@ test('Map', function(t) { map.release(); t.ok(err, 'returns error'); - t.equal(err.message, 'Error rendering image'); + t.ok(err.message.match(/Failed to load/), 'error text matches'); t.end(); }); From e2db5c46b52f9764856f8e15b2416efdfee7a447 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 22 Jun 2015 12:06:00 -0400 Subject: [PATCH 143/760] update changelog --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32e153c66..8af25563b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 1.1.2 + +- Fixes segfault, throws JavaScript error instead when attempting to + render without first loading a style. +- Bumps mbgl submodule to v0.4.0 + # 1.1.1 - Bumps mbgl submodule to v0.3.5 @@ -20,11 +26,13 @@ # 1.0.3 -- Fixes crash during garbage collection by assigning FileSource handle to a v8::Persistent in NodeMap constructor. +- Fixes crash during garbage collection by assigning FileSource handle + to a v8::Persistent in NodeMap constructor. # 1.0.2 -- Initialize shared display connection at module load time to avoid race condition when display connection is initialized on-demand. +- Initialize shared display connection at module load time to avoid + race condition when display connection is initialized on-demand. # 1.0.1 From 22c2076c5e53528a0d90f12f8132f0b17c8a00cd Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 22 Jun 2015 12:06:23 -0400 Subject: [PATCH 144/760] 1.1.2 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66a1d76bb..ab1982d57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.1.1", + "version": "1.1.2", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From a107680bc9108e6e7b8a343d9a83eb41f3f5af04 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Mon, 22 Jun 2015 13:35:15 -0400 Subject: [PATCH 145/760] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af25563b..381780528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # 1.1.2 +- Check libuv version semver-ishly, fixes segfaults in Node.js 0.12.x + and iojs. - Fixes segfault, throws JavaScript error instead when attempting to render without first loading a style. - Bumps mbgl submodule to v0.4.0 From b99831198eb0c882a88d382e18fc385e02b74b65 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 25 Jun 2015 18:22:47 +0300 Subject: [PATCH 146/760] Bump Mapbox GL Native to v0.4.0+ Removed mbgl::Environment. --- vendor/mbgl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/mbgl b/vendor/mbgl index 0955dfc19..7a30a0d31 160000 --- a/vendor/mbgl +++ b/vendor/mbgl @@ -1 +1 @@ -Subproject commit 0955dfc19f30aedcc95e7e3c03c73d3dcc149c44 +Subproject commit 7a30a0d316501d8dd606ece0ab220e7ec2e64088 From d41d2c63dd9a37576376a4a6d8a4fc4a5ac5f63b Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 25 Jun 2015 18:23:28 +0300 Subject: [PATCH 147/760] Remove mbgl::Environment from logging mbgl::Environment was dropped and since most of the errors are being forwarded to the NodeMap render callback, we can handle them there and pinpoint exactly what NodeMap object is failing. --- src/node_log.cpp | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/node_log.cpp b/src/node_log.cpp index d7cf0e0d8..637534807 100644 --- a/src/node_log.cpp +++ b/src/node_log.cpp @@ -1,8 +1,6 @@ #include "node_log.hpp" #include "util/async_queue.hpp" -#include - namespace node_mbgl { struct NodeLogObserver::LogMessage { @@ -10,16 +8,6 @@ struct NodeLogObserver::LogMessage { mbgl::Event event; int64_t code; std::string text; - int environment = -1; - std::string threadName = ""; - - LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_, int environment_, std::string threadName_) - : severity(severity_), - event(event_), - code(code_), - text(text_), - environment(environment_), - threadName(threadName_) {}; LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_) : severity(severity_), @@ -41,12 +29,6 @@ NodeLogObserver::NodeLogObserver(v8::Handle target) if (!message.text.empty()) { msg->Set(NanNew("text"), NanNew(message.text)); } - if (message.environment != -1) { - msg->Set(NanNew("environment"), NanNew(message.environment)); - } - if (!message.threadName.empty()) { - msg->Set(NanNew("threadName"), NanNew(message.threadName)); - } v8::Local argv[] = { NanNew("message"), msg }; auto handle = NanNew(module); @@ -65,13 +47,7 @@ NodeLogObserver::~NodeLogObserver() { } bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) { - if (mbgl::Environment::inScope()) { - int env = static_cast(mbgl::Environment::Get().getID()); - std::string threadName = mbgl::Environment::threadName(); - queue->send({ severity, event, code, text, env, threadName }); - } else { - queue->send({ severity, event, code, text }); - } + queue->send({ severity, event, code, text }); return true; } From f550a11ff5f29ba51ce6a5714ecfa80b2620b2d5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 25 Jun 2015 13:59:43 -0400 Subject: [PATCH 148/760] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 381780528..a86be910e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.3 + +- Removes deprecated mbgl::Environment from NodeLogObserver. + # 1.1.2 - Check libuv version semver-ishly, fixes segfaults in Node.js 0.12.x From 396fcc8b8ccd26fc4d2ac3bcc1345548cce47e0c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 25 Jun 2015 13:59:54 -0400 Subject: [PATCH 149/760] 1.1.3 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab1982d57..0e7daa555 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.1.2", + "version": "1.1.3", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From b6a0450ef6394da5c5123be41dab51d8af75fb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Tue, 30 Jun 2015 15:35:04 -0700 Subject: [PATCH 150/760] Go dormant before termination Speculative fix for #1460 per https://github.com/mapbox/mapbox-gl-native/issues/1460#issuecomment-99959434. --- platform/ios/MGLMapView.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 4957a0329..982a3e297 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -367,6 +367,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // observe app activity // + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil]; @@ -709,6 +710,18 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) #pragma mark - Life Cycle - +- (void)willTerminate +{ + MGLAssertIsMainThread(); + + if ( ! self.isDormant) + { + self.dormant = YES; + _mbglMap->pause(); + [self.glView deleteDrawable]; + } +} + - (void)sleepGL:(__unused NSNotification *)notification { MGLAssertIsMainThread(); From c7f90e4004ce8a161dd6d82ca8ece0fec340c27e Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 30 Jun 2015 23:45:17 -0400 Subject: [PATCH 151/760] bump podspec to 0.4.3 --- ios/MapboxGL.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/MapboxGL.podspec b/ios/MapboxGL.podspec index da1960b72..b8367d2e3 100644 --- a/ios/MapboxGL.podspec +++ b/ios/MapboxGL.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |m| m.name = 'MapboxGL' - m.version = '0.4.2-symbols' + m.version = '0.4.3-symbols' m.summary = 'Open source vector map solution for iOS with full styling capabilities.' m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.' From ad91368c812da71ab0f1ce0f06574abc4fa76515 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 30 Jun 2015 23:45:45 -0400 Subject: [PATCH 152/760] [publish ios-v0.4.3] From 176e42cb61a19ee2f7e7d5b3d4c8b0fb3df0a1ed Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 1 Jul 2015 13:16:16 +0200 Subject: [PATCH 153/760] show symbols at z0, fix #1279 --- src/mbgl/renderer/symbol_bucket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index 526539474..9f05209ff 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -425,7 +425,7 @@ template void SymbolBucket::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, const bool keepUpright, const bool alongLine) { const float zoom = collision.zoom; - const float placementZoom = std::log(scale) / std::log(2) + zoom; + const float placementZoom = std::fmax(std::log(scale) / std::log(2) + zoom, 0); for (const auto& symbol : symbols) { const auto &tl = symbol.tl; From 295ea0856becc35fe77ee7c207f533be3b7589ee Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 1 Jul 2015 10:07:12 +0300 Subject: [PATCH 154/760] Cancel pending Style requests before making a new one Calling setStyleURL() multiple times in a short period of time will enqueue requests on the FileSource. The order they get replied is not guaranteed to be the same as the order we make the requests. The side effect was the last style set not always being the one rendered. Now we cancel the current request if we make a new one (and there was no point on keeping it around anyway). --- src/mbgl/map/map_context.cpp | 17 +++++++++++++++-- src/mbgl/map/map_context.hpp | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 9dadd05d3..fa06a4267 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -50,6 +50,12 @@ MapContext::~MapContext() { void MapContext::cleanup() { view.notify(); + if (styleRequest) { + FileSource* fs = util::ThreadContext::getFileSource(); + fs->cancel(styleRequest); + styleRequest = nullptr; + } + // Explicit resets currently necessary because these abandon resources that need to be // cleaned up by glObjectStore.performCleanup(); style.reset(); @@ -85,6 +91,12 @@ void MapContext::triggerUpdate(const TransformState& state, const Update u) { } void MapContext::setStyleURL(const std::string& url) { + FileSource* fs = util::ThreadContext::getFileSource(); + + if (styleRequest) { + fs->cancel(styleRequest); + } + styleURL = url; styleJSON.clear(); @@ -94,8 +106,9 @@ void MapContext::setStyleURL(const std::string& url) { base = styleURL.substr(0, pos + 1); } - FileSource* fs = util::ThreadContext::getFileSource(); - fs->request({ Resource::Kind::Style, styleURL }, util::RunLoop::current.get()->get(), [this, base](const Response &res) { + styleRequest = fs->request({ Resource::Kind::Style, styleURL }, util::RunLoop::current.get()->get(), [this, base](const Response &res) { + styleRequest = nullptr; + if (res.status == Response::Successful) { loadStyleJSON(res.data, base); } else { diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index 715a55bf1..141a8ee96 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -92,6 +92,8 @@ private: std::string styleURL; std::string styleJSON; + Request* styleRequest = nullptr; + StillImageCallback callback; size_t sourceCacheSize; TransformState transformState; From 81d88755df16be60c5b14223989f39ff99c56943 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 30 Jun 2015 22:56:49 -0400 Subject: [PATCH 155/760] Remove tracking mode reset from resetNorthAnimated --- platform/ios/MGLMapView.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 982a3e297..f9c8b54ce 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1397,8 +1397,6 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)resetNorthAnimated:(BOOL)animated { - self.userTrackingMode = MGLUserTrackingModeNone; - CGFloat duration = (animated ? MGLAnimationDuration : 0); _mbglMap->setBearing(0, secondsAsDuration(duration)); From 76333dfc0c85452a216220e2e98cacc131af56a7 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Wed, 1 Jul 2015 12:09:33 -0400 Subject: [PATCH 156/760] fixes #1770: rename & expose compass, logo, and attribution --- include/mbgl/ios/MGLMapView.h | 9 +++++ platform/ios/MGLMapView.mm | 75 +++++++++++++++++------------------ 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 918f521a8..d0704a41c 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -73,6 +73,15 @@ IB_DESIGNABLE * The default value of this property is `YES`. */ @property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled; +/** The compass image view shown in the upper-right when the map is rotated. */ +@property (nonatomic, readonly) UIImageView *compassView; + +/** The Mapbox logo image view shown in the lower-left of the map. */ +@property (nonatomic, readonly) UIImageView *logoView; + +/** The button shown in the lower-right of the map which when pressed displays the map attribution information. */ +@property (nonatomic, readonly) UIButton *attributionButton; + #pragma mark - Accessing the Delegate /** @name Accessing the Delegate */ diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 982a3e297..2aa4e772e 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -69,10 +69,10 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) @property (nonatomic) GLKView *glView; @property (nonatomic) UIImageView *glSnapshotView; @property (nonatomic) NSOperationQueue *regionChangeDelegateQueue; -@property (nonatomic) UIImageView *compass; -@property (nonatomic) UIImageView *logoBug; -@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *logoBugConstraints; -@property (nonatomic) UIButton *attributionButton; +@property (nonatomic, readwrite) UIImageView *compassView; +@property (nonatomic, readwrite) UIImageView *logoView; +@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *logoViewConstraints; +@property (nonatomic, readwrite) UIButton *attributionButton; @property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *attributionButtonConstraints; @property (nonatomic) UIActionSheet *attributionSheet; @property (nonatomic) UIPanGestureRecognizer *pan; @@ -281,11 +281,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // setup logo bug // UIImage *logo = [[MGLMapView resourceImageNamed:@"mapbox.png"] imageWithAlignmentRectInsets:UIEdgeInsetsMake(1.5, 4, 3.5, 2)]; - _logoBug = [[UIImageView alloc] initWithImage:logo]; - _logoBug.accessibilityLabel = @"Mapbox logo"; - _logoBug.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_logoBug]; - _logoBugConstraints = [NSMutableArray array]; + _logoView = [[UIImageView alloc] initWithImage:logo]; + _logoView.accessibilityLabel = @"Mapbox logo"; + _logoView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_logoView]; + _logoViewConstraints = [NSMutableArray array]; // setup attribution // @@ -314,13 +314,12 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // setup compass // - _compass = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"Compass.png"]]; - _compass.accessibilityLabel = @"Compass"; - UIImage *compassImage = [MGLMapView resourceImageNamed:@"Compass.png"]; - _compass.frame = CGRectMake(0, 0, compassImage.size.width, compassImage.size.height); - _compass.alpha = 0; + _compassView = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"Compass.png"]]; + _compassView.accessibilityLabel = @"Compass"; + _compassView.frame = CGRectMake(0, 0, _compassView.image.size.width, _compassView.image.size.height); + _compassView.alpha = 0; UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; - [container addSubview:_compass]; + [container addSubview:_compassView]; container.translatesAutoresizingMaskIntoConstraints = NO; [container addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCompassTapGesture:)]]; [self addSubview:container]; @@ -437,8 +436,8 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [EAGLContext setCurrentContext:nil]; } - [self.logoBugConstraints removeAllObjects]; - self.logoBugConstraints = nil; + [self.logoViewConstraints removeAllObjects]; + self.logoViewConstraints = nil; [self.attributionButtonConstraints removeAllObjects]; self.attributionButtonConstraints = nil; } @@ -500,7 +499,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // compass // - UIView *compassContainer = self.compass.superview; + UIView *compassContainer = self.compassView.superview; if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) { [NSLayoutConstraint deactivateConstraints:compassContainer.constraints]; @@ -547,7 +546,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 - constant:self.compass.image.size.width]]; + constant:self.compassView.image.size.width]]; [compassContainerConstraints addObject: [NSLayoutConstraint constraintWithItem:compassContainer @@ -556,7 +555,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 - constant:self.compass.image.size.height]]; + constant:self.compassView.image.size.height]]; if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) { [NSLayoutConstraint activateConstraints:compassContainerConstraints]; @@ -570,35 +569,35 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) { - [NSLayoutConstraint deactivateConstraints:self.logoBugConstraints]; + [NSLayoutConstraint deactivateConstraints:self.logoViewConstraints]; } else { - [self.logoBug removeConstraints:self.logoBugConstraints]; + [self.logoView removeConstraints:self.logoViewConstraints]; } - [self.logoBugConstraints removeAllObjects]; + [self.logoViewConstraints removeAllObjects]; if (viewController) { - [self.logoBugConstraints addObject: + [self.logoViewConstraints addObject: [NSLayoutConstraint constraintWithItem:viewController.bottomLayoutGuide attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:self.logoBug + toItem:self.logoView attribute:NSLayoutAttributeBaseline multiplier:1 constant:8]]; } - [self.logoBugConstraints addObject: + [self.logoViewConstraints addObject: [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:self.logoBug + toItem:self.logoView attribute:NSLayoutAttributeBaseline multiplier:1 constant:8]]; - [self.logoBugConstraints addObject: - [NSLayoutConstraint constraintWithItem:self.logoBug + [self.logoViewConstraints addObject: + [NSLayoutConstraint constraintWithItem:self.logoView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self @@ -607,11 +606,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) constant:8]]; if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) { - [NSLayoutConstraint activateConstraints:self.logoBugConstraints]; + [NSLayoutConstraint activateConstraints:self.logoViewConstraints]; } else { - [constraintParentView addConstraints:self.logoBugConstraints]; + [constraintParentView addConstraints:self.logoViewConstraints]; } // attribution button @@ -701,7 +700,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [self.attributionSheet dismissWithClickedButtonIndex:self.attributionSheet.cancelButtonIndex animated:YES]; } - if (self.compass.alpha) + if (self.compassView.alpha) { [self updateHeadingForDeviceOrientation]; [self updateCompass]; @@ -1406,7 +1405,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [UIView animateWithDuration:duration animations:^ { - self.compass.transform = CGAffineTransformIdentity; + self.compassView.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { @@ -2630,27 +2629,27 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) while (degrees >= 360) degrees -= 360; while (degrees < 0) degrees += 360; - self.compass.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(degrees)); + self.compassView.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(degrees)); - if (_mbglMap->getBearing() && self.compass.alpha < 1) + if (_mbglMap->getBearing() && self.compassView.alpha < 1) { [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { - self.compass.alpha = 1; + self.compassView.alpha = 1; } completion:nil]; } - else if (_mbglMap->getBearing() == 0 && self.compass.alpha > 0) + else if (_mbglMap->getBearing() == 0 && self.compassView.alpha > 0) { [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { - self.compass.alpha = 0; + self.compassView.alpha = 0; } completion:nil]; } From 4eff674c1d4caaf0ce624fe5db086f0d79d0d31f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 18 Jun 2015 18:23:03 -0700 Subject: [PATCH 157/760] Revert "Do not hold a reference to the Style at the [Live|Vector]TileData" This reverts commit 044454417b61bdd102a376c1125ad6ee3a5eacd4. --- src/mbgl/map/live_tile_data.cpp | 7 +++---- src/mbgl/map/live_tile_data.hpp | 3 +-- src/mbgl/map/source.cpp | 4 ++-- src/mbgl/map/tile_parser.cpp | 7 ++++--- src/mbgl/map/tile_parser.hpp | 7 +++---- src/mbgl/map/vector_tile_data.cpp | 16 ++++++++-------- src/mbgl/map/vector_tile_data.hpp | 7 +++---- 7 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index ccae542ee..4935c0134 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -10,8 +10,7 @@ using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, AnnotationManager& annotationManager_, - const std::vector>& layers_, - Worker& workers_, + Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, @@ -19,7 +18,7 @@ LiveTileData::LiveTileData(const TileID& id_, const SourceInfo& source_, float angle_, bool collisionDebug_) - : VectorTileData::VectorTileData(id_, layers_, workers_, glyphAtlas_, glyphStore_, + : VectorTileData::VectorTileData(id_, style_, glyphAtlas_, glyphStore_, spriteAtlas_, sprite_, source_, angle_, collisionDebug_), annotationManager(annotationManager_) { // live features are always ready @@ -44,7 +43,7 @@ void LiveTileData::parse() { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. - TileParser parser(*tile, *this, layers, glyphAtlas, glyphStore, spriteAtlas, sprite); + TileParser parser(*tile, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); parser.parse(); } catch (const std::exception& ex) { Log::Error(Event::ParseTile, "Live-parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index 57d2e8c0d..162067123 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -11,8 +11,7 @@ class LiveTileData : public VectorTileData { public: LiveTileData(const TileID&, AnnotationManager&, - const std::vector>&, - Worker&, + Style&, GlyphAtlas&, GlyphStore&, SpriteAtlas&, diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index b073321a7..7dd91804b 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -295,7 +295,7 @@ TileData::State Source::addTile(MapData& data, // If we don't find working tile data, we're just going to load it. if (info.type == SourceType::Vector) { new_tile.data = - std::make_shared(normalized_id, style.layers, style.workers, glyphAtlas, + std::make_shared(normalized_id, style, glyphAtlas, glyphStore, spriteAtlas, sprite, info, transformState.getAngle(), data.getCollisionDebug()); new_tile.data->request(style.workers, transformState.getPixelRatio(), callback); @@ -305,7 +305,7 @@ TileData::State Source::addTile(MapData& data, style.workers, transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Annotations) { new_tile.data = std::make_shared(normalized_id, data.annotationManager, - style.layers, style.workers, glyphAtlas, + style, glyphAtlas, glyphStore, spriteAtlas, sprite, info, transformState.getAngle(), data.getCollisionDebug()); new_tile.data->reparse(style.workers, callback); diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp index 1ddf07ae9..61af22703 100644 --- a/src/mbgl/map/tile_parser.cpp +++ b/src/mbgl/map/tile_parser.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -20,14 +21,14 @@ TileParser::~TileParser() = default; TileParser::TileParser(const GeometryTile& geometryTile_, VectorTileData& tile_, - const std::vector>& layers_, + const Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, const util::ptr& sprite_) : geometryTile(geometryTile_), tile(tile_), - layers(layers_), + style(style_), glyphAtlas(glyphAtlas_), glyphStore(glyphStore_), spriteAtlas(spriteAtlas_), @@ -41,7 +42,7 @@ bool TileParser::obsolete() const { } void TileParser::parse() { - for (const auto& layer_desc : layers) { + for (const auto& layer_desc : style.layers) { // Cancel early when parsing. if (obsolete()) { return; diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp index 7eadc45d4..88e568780 100644 --- a/src/mbgl/map/tile_parser.hpp +++ b/src/mbgl/map/tile_parser.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,7 @@ class GlyphAtlas; class GlyphStore; class SpriteAtlas; class Sprite; +class Style; class StyleBucket; class StyleLayoutFill; class StyleLayoutRaster; @@ -34,7 +34,7 @@ class TileParser : private util::noncopyable { public: TileParser(const GeometryTile& geometryTile, VectorTileData& tile, - const std::vector>& layers, + const Style& style, GlyphAtlas& glyphAtlas, GlyphStore& glyphStore, SpriteAtlas& spriteAtlas, @@ -61,9 +61,8 @@ private: const GeometryTile& geometryTile; VectorTileData& tile; - std::vector> layers; - // Cross-thread shared data. + const Style& style; GlyphAtlas& glyphAtlas; GlyphStore& glyphStore; SpriteAtlas& spriteAtlas; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 017d3ecb7..6dc6e8e9b 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -9,12 +9,12 @@ #include #include #include +#include using namespace mbgl; VectorTileData::VectorTileData(const TileID& id_, - const std::vector>& layers_, - Worker& workers_, + Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, @@ -23,12 +23,11 @@ VectorTileData::VectorTileData(const TileID& id_, float angle, bool collisionDebug) : TileData(id_, source_), - layers(layers_), - workers(workers_), glyphAtlas(glyphAtlas_), glyphStore(glyphStore_), spriteAtlas(spriteAtlas_), sprite(sprite_), + style(style_), collision(std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, angle, collisionDebug)), lastAngle(angle), currentAngle(angle) { @@ -52,7 +51,7 @@ void VectorTileData::parse() { // is going to be discarded afterwards. VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); const VectorTile* vt = &vectorTile; - TileParser parser(*vt, *this, layers, glyphAtlas, glyphStore, spriteAtlas, sprite); + TileParser parser(*vt, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); parser.parse(); if (getState() == State::obsolete) { @@ -129,14 +128,15 @@ void VectorTileData::redoPlacement(float angle, bool collisionDebug) { currentCollisionDebug = collisionDebug; auto callback = std::bind(&VectorTileData::endRedoPlacement, this); - workRequest = workers.send([this, angle, collisionDebug] { workerRedoPlacement(angle, collisionDebug); }, callback); + workRequest = style.workers.send([this, angle, collisionDebug] { workerRedoPlacement(angle, collisionDebug); }, callback); + } } void VectorTileData::workerRedoPlacement(float angle, bool collisionDebug) { collision->reset(angle, 0); collision->setDebug(collisionDebug); - for (const auto& layer_desc : layers) { + for (const auto& layer_desc : style.layers) { auto bucket = getBucket(*layer_desc); if (bucket) { bucket->placeFeatures(); @@ -145,7 +145,7 @@ void VectorTileData::workerRedoPlacement(float angle, bool collisionDebug) { } void VectorTileData::endRedoPlacement() { - for (const auto& layer_desc : layers) { + for (const auto& layer_desc : style.layers) { auto bucket = getBucket(*layer_desc); if (bucket) { bucket->swapRenderData(); diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 51a627d7b..5a6f448c6 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -26,14 +26,14 @@ class GlyphAtlas; class GlyphStore; class SpriteAtlas; class Sprite; +class Style; class VectorTileData : public TileData { friend class TileParser; public: VectorTileData(const TileID&, - const std::vector>&, - Worker&, + Style&, GlyphAtlas&, GlyphStore&, SpriteAtlas&, @@ -66,12 +66,11 @@ protected: TriangleElementsBuffer triangleElementsBuffer; LineElementsBuffer lineElementsBuffer; - std::vector> layers; - Worker& workers; GlyphAtlas& glyphAtlas; GlyphStore& glyphStore; SpriteAtlas& spriteAtlas; util::ptr sprite; + Style& style; private: // Contains all the Bucket objects for the tile. Buckets are render From ee463785832343b60b2525126052d3ce65fea5a7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 15:19:06 -0700 Subject: [PATCH 158/760] Reduce number of arguments in Source::update pathway --- src/mbgl/map/live_tile_data.cpp | 9 ++------- src/mbgl/map/live_tile_data.hpp | 4 ---- src/mbgl/map/source.cpp | 17 +++-------------- src/mbgl/map/source.hpp | 12 ------------ src/mbgl/map/tile_parser.cpp | 16 ++++------------ src/mbgl/map/tile_parser.hpp | 18 ++---------------- src/mbgl/map/vector_tile_data.cpp | 12 ++---------- src/mbgl/map/vector_tile_data.hpp | 12 ------------ src/mbgl/style/style.cpp | 3 +-- 9 files changed, 14 insertions(+), 89 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 4935c0134..4a90ea786 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -11,15 +11,10 @@ using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, AnnotationManager& annotationManager_, Style& style_, - GlyphAtlas& glyphAtlas_, - GlyphStore& glyphStore_, - SpriteAtlas& spriteAtlas_, - util::ptr sprite_, const SourceInfo& source_, float angle_, bool collisionDebug_) - : VectorTileData::VectorTileData(id_, style_, glyphAtlas_, glyphStore_, - spriteAtlas_, sprite_, source_, angle_, collisionDebug_), + : VectorTileData::VectorTileData(id_, style_, source_, angle_, collisionDebug_), annotationManager(annotationManager_) { // live features are always ready setState(State::loaded); @@ -43,7 +38,7 @@ void LiveTileData::parse() { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. - TileParser parser(*tile, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); + TileParser parser(*tile, *this, style); parser.parse(); } catch (const std::exception& ex) { Log::Error(Event::ParseTile, "Live-parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index 162067123..f4f541208 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -12,10 +12,6 @@ public: LiveTileData(const TileID&, AnnotationManager&, Style&, - GlyphAtlas&, - GlyphStore&, - SpriteAtlas&, - util::ptr, const SourceInfo&, float angle_, bool collisionDebug_); diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 7dd91804b..0bac4c3c7 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -254,10 +254,6 @@ bool Source::handlePartialTile(const TileID& id, Worker& worker) { TileData::State Source::addTile(MapData& data, const TransformState& transformState, Style& style, - GlyphAtlas& glyphAtlas, - GlyphStore& glyphStore, - SpriteAtlas& spriteAtlas, - util::ptr sprite, TexturePool& texturePool, const TileID& id) { const TileData::State state = hasTile(id); @@ -295,8 +291,7 @@ TileData::State Source::addTile(MapData& data, // If we don't find working tile data, we're just going to load it. if (info.type == SourceType::Vector) { new_tile.data = - std::make_shared(normalized_id, style, glyphAtlas, - glyphStore, spriteAtlas, sprite, info, + std::make_shared(normalized_id, style, info, transformState.getAngle(), data.getCollisionDebug()); new_tile.data->request(style.workers, transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Raster) { @@ -305,8 +300,7 @@ TileData::State Source::addTile(MapData& data, style.workers, transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Annotations) { new_tile.data = std::make_shared(normalized_id, data.annotationManager, - style, glyphAtlas, - glyphStore, spriteAtlas, sprite, info, + style, info, transformState.getAngle(), data.getCollisionDebug()); new_tile.data->reparse(style.workers, callback); } else { @@ -405,10 +399,6 @@ bool Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::fo bool Source::update(MapData& data, const TransformState& transformState, Style& style, - GlyphAtlas& glyphAtlas, - GlyphStore& glyphStore, - SpriteAtlas& spriteAtlas, - util::ptr sprite, TexturePool& texturePool, bool shouldReparsePartialTiles) { bool allTilesUpdated = true; @@ -442,8 +432,7 @@ bool Source::update(MapData& data, } break; case TileData::State::invalid: - state = addTile(data, transformState, style, glyphAtlas, glyphStore, - spriteAtlas, sprite, texturePool, id); + state = addTile(data, transformState, style, texturePool, id); break; default: break; diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 6b0b23f26..336caa5b9 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -22,10 +22,6 @@ namespace mbgl { class MapData; -class GlyphAtlas; -class GlyphStore; -class SpriteAtlas; -class Sprite; class TexturePool; class Style; class Painter; @@ -78,10 +74,6 @@ public: bool update(MapData&, const TransformState&, Style&, - GlyphAtlas&, - GlyphStore&, - SpriteAtlas&, - util::ptr, TexturePool&, bool shouldReparsePartialTiles); @@ -119,10 +111,6 @@ private: TileData::State addTile(MapData&, const TransformState&, Style&, - GlyphAtlas&, - GlyphStore&, - SpriteAtlas&, - util::ptr, TexturePool&, const TileID&); diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp index 61af22703..56b34acdd 100644 --- a/src/mbgl/map/tile_parser.cpp +++ b/src/mbgl/map/tile_parser.cpp @@ -21,20 +21,12 @@ TileParser::~TileParser() = default; TileParser::TileParser(const GeometryTile& geometryTile_, VectorTileData& tile_, - const Style& style_, - GlyphAtlas& glyphAtlas_, - GlyphStore& glyphStore_, - SpriteAtlas& spriteAtlas_, - const util::ptr& sprite_) + Style& style_) : geometryTile(geometryTile_), tile(tile_), style(style_), - glyphAtlas(glyphAtlas_), - glyphStore(glyphStore_), - spriteAtlas(spriteAtlas_), - sprite(sprite_), partialParse(false) { - assert(sprite); + assert(style.sprite); } bool TileParser::obsolete() const { @@ -222,7 +214,7 @@ std::unique_ptr TileParser::createSymbolBucket(const GeometryTileLayer& applyLayoutProperty(PropertyKey::TextOffset, bucket_desc.layout, layout.text.offset, z); applyLayoutProperty(PropertyKey::TextAllowOverlap, bucket_desc.layout, layout.text.allow_overlap, z); - if (bucket->needsDependencies(layer, bucket_desc.filter, glyphStore, *sprite)) { + if (bucket->needsDependencies(layer, bucket_desc.filter, *style.glyphStore, *style.sprite)) { partialParse = true; } @@ -235,7 +227,7 @@ std::unique_ptr TileParser::createSymbolBucket(const GeometryTileLayer& } bucket->addFeatures( - reinterpret_cast(&tile), spriteAtlas, *sprite, glyphAtlas, glyphStore); + reinterpret_cast(&tile), *style.spriteAtlas, *style.sprite, *style.glyphAtlas, *style.glyphStore); return bucket->hasData() ? std::move(bucket) : nullptr; } diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp index 88e568780..b716721a5 100644 --- a/src/mbgl/map/tile_parser.hpp +++ b/src/mbgl/map/tile_parser.hpp @@ -18,10 +18,6 @@ namespace mbgl { class Bucket; class FontStack; -class GlyphAtlas; -class GlyphStore; -class SpriteAtlas; -class Sprite; class Style; class StyleBucket; class StyleLayoutFill; @@ -32,13 +28,7 @@ class VectorTileData; class TileParser : private util::noncopyable { public: - TileParser(const GeometryTile& geometryTile, - VectorTileData& tile, - const Style& style, - GlyphAtlas& glyphAtlas, - GlyphStore& glyphStore, - SpriteAtlas& spriteAtlas, - const util::ptr& sprite); + TileParser(const GeometryTile&, VectorTileData&, Style&); ~TileParser(); public: @@ -62,11 +52,7 @@ private: VectorTileData& tile; // Cross-thread shared data. - const Style& style; - GlyphAtlas& glyphAtlas; - GlyphStore& glyphStore; - SpriteAtlas& spriteAtlas; - util::ptr sprite; + Style& style; bool partialParse; }; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 6dc6e8e9b..840e21b8a 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -15,18 +15,10 @@ using namespace mbgl; VectorTileData::VectorTileData(const TileID& id_, Style& style_, - GlyphAtlas& glyphAtlas_, - GlyphStore& glyphStore_, - SpriteAtlas& spriteAtlas_, - util::ptr sprite_, const SourceInfo& source_, float angle, bool collisionDebug) : TileData(id_, source_), - glyphAtlas(glyphAtlas_), - glyphStore(glyphStore_), - spriteAtlas(spriteAtlas_), - sprite(sprite_), style(style_), collision(std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, angle, collisionDebug)), lastAngle(angle), @@ -37,7 +29,7 @@ VectorTileData::~VectorTileData() { // Cancel in most derived class destructor so that worker tasks are joined before // any member data goes away. cancel(); - glyphAtlas.removeGlyphs(reinterpret_cast(this)); + style.glyphAtlas->removeGlyphs(reinterpret_cast(this)); } void VectorTileData::parse() { @@ -51,7 +43,7 @@ void VectorTileData::parse() { // is going to be discarded afterwards. VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); const VectorTile* vt = &vectorTile; - TileParser parser(*vt, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); + TileParser parser(*vt, *this, style); parser.parse(); if (getState() == State::obsolete) { diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 5a6f448c6..697a054e0 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -22,10 +22,6 @@ class Painter; class SourceInfo; class StyleLayer; class TileParser; -class GlyphAtlas; -class GlyphStore; -class SpriteAtlas; -class Sprite; class Style; class VectorTileData : public TileData { @@ -34,10 +30,6 @@ class VectorTileData : public TileData { public: VectorTileData(const TileID&, Style&, - GlyphAtlas&, - GlyphStore&, - SpriteAtlas&, - util::ptr, const SourceInfo&, float angle_, bool collisionDebug_); @@ -66,10 +58,6 @@ protected: TriangleElementsBuffer triangleElementsBuffer; LineElementsBuffer lineElementsBuffer; - GlyphAtlas& glyphAtlas; - GlyphStore& glyphStore; - SpriteAtlas& spriteAtlas; - util::ptr sprite; Style& style; private: diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 15dffd113..35a94adbc 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -78,8 +78,7 @@ void Style::update(MapData& data, bool allTilesUpdated = true; for (const auto& source : sources) { - if (!source->update(data, transform, *this, *glyphAtlas, *glyphStore, - *spriteAtlas, sprite, texturePool, shouldReparsePartialTiles)) { + if (!source->update(data, transform, *this, texturePool, shouldReparsePartialTiles)) { allTilesUpdated = false; } } From 7e71145a89f0377736312c3b818a41ffb25bdf9a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 15:54:42 -0700 Subject: [PATCH 159/760] Make request and reparse pure virtual --- src/mbgl/map/raster_tile_data.cpp | 44 ++++++++++++++++++++++++++++++- src/mbgl/map/raster_tile_data.hpp | 10 ++++--- src/mbgl/map/tile_data.cpp | 42 ----------------------------- src/mbgl/map/tile_data.hpp | 10 ++++--- src/mbgl/map/vector_tile_data.cpp | 38 ++++++++++++++++++++++++++ src/mbgl/map/vector_tile_data.hpp | 8 +++++- 6 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index 0f8efe3b4..ee53a6a28 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -1,5 +1,12 @@ #include -#include +#include +#include +#include +#include +#include +#include + +#include using namespace mbgl; @@ -14,6 +21,41 @@ RasterTileData::~RasterTileData() { cancel(); } +void RasterTileData::request(Worker& worker, + float pixelRatio, + const std::function& callback) { + std::string url = source.tileURL(id, pixelRatio); + state = State::loading; + + FileSource* fs = util::ThreadContext::getFileSource(); + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, &worker, this](const Response &res) { + req = nullptr; + + if (res.status != Response::Successful) { + std::stringstream message; + message << "Failed to load [" << url << "]: " << res.message; + setError(message.str()); + callback(); + return; + } + + state = State::loaded; + data = res.data; + + // Schedule tile parsing in another thread + reparse(worker, callback); + }); +} + +bool RasterTileData::reparse(Worker& worker, std::function callback) { + if (!mayStartParsing()) { + return false; + } + + workRequest = worker.send([this] { parse(); endParsing(); }, callback); + return true; +} + void RasterTileData::parse() { if (getState() != State::loaded) { return; diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index 45aee8f74..fb59482ef 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -13,13 +13,17 @@ class StyleLayer; class TexturePool; class RasterTileData : public TileData { - friend class TileParser; - public: RasterTileData(const TileID&, TexturePool&, const SourceInfo&); ~RasterTileData(); - void parse() override; + void request(Worker&, + float pixelRatio, + const std::function& callback) override; + + bool reparse(Worker&, std::function callback) override; + + void parse(); Bucket* getBucket(StyleLayer const &layer_desc) override; protected: diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index f1c661300..4a30639cb 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -1,13 +1,6 @@ #include - -#include -#include -#include #include #include -#include - -#include using namespace mbgl; @@ -35,32 +28,6 @@ void TileData::setState(const State& state_) { state = state_; } -void TileData::request(Worker& worker, - float pixelRatio, - const std::function& callback) { - std::string url = source.tileURL(id, pixelRatio); - state = State::loading; - - FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, &worker, this](const Response &res) { - req = nullptr; - - if (res.status != Response::Successful) { - std::stringstream message; - message << "Failed to load [" << url << "]: " << res.message; - setError(message.str()); - callback(); - return; - } - - state = State::loaded; - data = res.data; - - // Schedule tile parsing in another thread - reparse(worker, callback); - }); -} - void TileData::cancel() { if (state != State::obsolete) { state = State::obsolete; @@ -80,15 +47,6 @@ void TileData::endParsing() { parsing.clear(std::memory_order_release); } -bool TileData::reparse(Worker& worker, std::function callback) { - if (!mayStartParsing()) { - return false; - } - - workRequest = worker.send([this] { parse(); endParsing(); }, callback); - return true; -} - void TileData::setError(const std::string& message) { error = message; setState(State::obsolete); diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index aa302a4fa..a3aaf1dfd 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -77,13 +77,16 @@ public: TileData(const TileID&, const SourceInfo&); ~TileData(); - void request(Worker&, float pixelRatio, const std::function& callback); + virtual void request(Worker&, + float pixelRatio, + const std::function& callback) = 0; // Schedule a tile reparse on a worker thread and call the callback on // completion. It will return true if the work was schedule or false it was // not, which can occur if the tile is already being parsed by another // worker (see "mayStartParsing()"). - bool reparse(Worker&, std::function callback); + virtual bool reparse(Worker&, + std::function callback) = 0; void cancel(); const std::string toString() const; @@ -116,7 +119,6 @@ public: } // Override this in the child class. - virtual void parse() = 0; virtual Bucket* getBucket(StyleLayer const &layer_desc) = 0; virtual void redoPlacement(float, bool) {} @@ -141,9 +143,9 @@ protected: std::unique_ptr workRequest; -private: std::atomic state; +private: std::string error; protected: diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 840e21b8a..34cfe4311 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -32,6 +35,41 @@ VectorTileData::~VectorTileData() { style.glyphAtlas->removeGlyphs(reinterpret_cast(this)); } +void VectorTileData::request(Worker& worker, + float pixelRatio, + const std::function& callback) { + std::string url = source.tileURL(id, pixelRatio); + state = State::loading; + + FileSource* fs = util::ThreadContext::getFileSource(); + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, &worker, this](const Response &res) { + req = nullptr; + + if (res.status != Response::Successful) { + std::stringstream message; + message << "Failed to load [" << url << "]: " << res.message; + setError(message.str()); + callback(); + return; + } + + state = State::loaded; + data = res.data; + + // Schedule tile parsing in another thread + reparse(worker, callback); + }); +} + +bool VectorTileData::reparse(Worker& worker, std::function callback) { + if (!mayStartParsing()) { + return false; + } + + workRequest = worker.send([this] { parse(); endParsing(); }, callback); + return true; +} + void VectorTileData::parse() { if (getState() != State::loaded && getState() != State::partial) { return; diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 697a054e0..faddb1dc7 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -35,7 +35,7 @@ public: bool collisionDebug_); ~VectorTileData(); - void parse() override; + virtual void parse(); void redoPlacement(float angle, bool collisionDebug) override; virtual Bucket* getBucket(StyleLayer const &layer_desc) override; @@ -48,6 +48,12 @@ public: return collision.get(); } + void request(Worker&, + float pixelRatio, + const std::function& callback) override; + + bool reparse(Worker&, std::function callback) override; + protected: void redoPlacement(); From 29a2cd908f6ed3d62ecfd113457074d1524d4f49 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 16:36:37 -0700 Subject: [PATCH 160/760] Introduce TileWorker --- src/mbgl/map/live_tile_data.cpp | 61 +++++++++------ src/mbgl/map/live_tile_data.hpp | 2 +- src/mbgl/map/tile_parser.cpp | 4 +- src/mbgl/map/tile_parser.hpp | 6 +- src/mbgl/map/tile_worker.cpp | 78 +++++++++++++++++++ src/mbgl/map/tile_worker.hpp | 75 +++++++++++++++++++ src/mbgl/map/vector_tile_data.cpp | 120 +++++++++++------------------- src/mbgl/map/vector_tile_data.hpp | 50 ++----------- 8 files changed, 244 insertions(+), 152 deletions(-) create mode 100644 src/mbgl/map/tile_worker.cpp create mode 100644 src/mbgl/map/tile_worker.hpp diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 4a90ea786..74d939f35 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -1,10 +1,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include + +#include using namespace mbgl; @@ -26,28 +26,43 @@ LiveTileData::~LiveTileData() { cancel(); } -void LiveTileData::parse() { - if (getState() != State::loaded) { - return; +bool LiveTileData::reparse(Worker&, std::function callback) { + if (!mayStartParsing()) { + return false; } - const LiveTile* tile = annotationManager.getTile(id); - - if (tile) { - try { - // Parsing creates state that is encapsulated in TileParser. While parsing, - // the TileParser object writes results into this objects. All other state - // is going to be discarded afterwards. - TileParser parser(*tile, *this, style); - parser.parse(); - } catch (const std::exception& ex) { - Log::Error(Event::ParseTile, "Live-parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); - setState(State::obsolete); + workRequest = worker.send([this] { + if (getState() != State::loaded) { return; } - } - if (getState() != State::obsolete) { - setState(State::parsed); - } + const LiveTile* tile = annotationManager.getTile(id); + + if (!tile) { + setState(State::parsed); + return; + } + + TileParseResult result; + + try { + result = workerData.parse(*tile); + } catch (const std::exception& ex) { + std::stringstream message; + message << "Failed to parse [" << int(id.sourceZ) << "/" << id.x << "/" << id.y << "]: " << ex.what(); + result = message.str(); + } + + if (getState() == TileData::State::obsolete) { + return; + } else if (result.is()) { + setState(result.get()); + } else { + setError(result.get()); + } + + endParsing(); + }, callback); + + return true; } diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index f4f541208..c837057e8 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -17,7 +17,7 @@ public: bool collisionDebug_); ~LiveTileData(); - void parse() override; + bool reparse(Worker&, std::function callback) override; private: AnnotationManager& annotationManager; diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp index 56b34acdd..a88084391 100644 --- a/src/mbgl/map/tile_parser.cpp +++ b/src/mbgl/map/tile_parser.cpp @@ -20,7 +20,7 @@ namespace mbgl { TileParser::~TileParser() = default; TileParser::TileParser(const GeometryTile& geometryTile_, - VectorTileData& tile_, + TileWorker& tile_, Style& style_) : geometryTile(geometryTile_), tile(tile_), @@ -98,7 +98,7 @@ void applyLayoutProperty(PropertyKey key, const ClassProperties &classProperties std::unique_ptr TileParser::createBucket(const StyleBucket &bucketDesc) { // Skip this bucket if we are to not render this - if (tile.id.z < std::floor(bucketDesc.min_zoom) && std::floor(bucketDesc.min_zoom) < tile.source.max_zoom) return nullptr; + if (tile.id.z < std::floor(bucketDesc.min_zoom) && std::floor(bucketDesc.min_zoom) < tile.maxZoom) return nullptr; if (tile.id.z >= std::ceil(bucketDesc.max_zoom)) return nullptr; if (bucketDesc.visibility == mbgl::VisibilityType::None) return nullptr; diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp index b716721a5..63c1fd72a 100644 --- a/src/mbgl/map/tile_parser.hpp +++ b/src/mbgl/map/tile_parser.hpp @@ -24,11 +24,11 @@ class StyleLayoutFill; class StyleLayoutRaster; class StyleLayoutLine; class StyleLayoutSymbol; -class VectorTileData; +class TileWorker; class TileParser : private util::noncopyable { public: - TileParser(const GeometryTile&, VectorTileData&, Style&); + TileParser(const GeometryTile&, TileWorker&, Style&); ~TileParser(); public: @@ -49,7 +49,7 @@ private: void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&); const GeometryTile& geometryTile; - VectorTileData& tile; + TileWorker& tile; // Cross-thread shared data. Style& style; diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp new file mode 100644 index 000000000..1991ab188 --- /dev/null +++ b/src/mbgl/map/tile_worker.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +using namespace mbgl; + +TileWorker::TileWorker(const TileID& id_, + Style& style_, + const uint16_t maxZoom_, + const std::atomic& state_, + std::unique_ptr collision_) + : id(id_), + style(style_), + maxZoom(maxZoom_), + state(state_), + collision(std::move(collision_)) { +} + +TileWorker::~TileWorker() { + style.glyphAtlas->removeGlyphs(reinterpret_cast(this)); +} + +void TileWorker::setBucket(const StyleLayer& layer, std::unique_ptr bucket) { + assert(layer.bucket); + + std::lock_guard lock(bucketsMutex); + + if (buckets.find(layer.bucket->name) != buckets.end()) { + return; + } + + buckets[layer.bucket->name] = std::move(bucket); +} + +Bucket* TileWorker::getBucket(const StyleLayer& layer) const { + std::lock_guard lock(bucketsMutex); + + const auto it = buckets.find(layer.bucket->name); + if (it == buckets.end()) { + return nullptr; + } + + assert(it->second); + return it->second.get(); +} + +size_t TileWorker::countBuckets() const { + std::lock_guard lock(bucketsMutex); + return buckets.size(); +} + +TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { + // Parsing creates state that is encapsulated in TileParser. While parsing, + // the TileParser object writes results into this objects. All other state + // is going to be discarded afterwards. + TileParser parser(geometryTile, *this, style); + parser.parse(); + + if (parser.isPartialParse()) { + return TileData::State::partial; + } else { + return TileData::State::parsed; + } +} + +void TileWorker::redoPlacement(float angle, bool collisionDebug) { + collision->reset(angle, 0); + collision->setDebug(collisionDebug); + for (const auto& layer_desc : style.layers) { + auto bucket = getBucket(*layer_desc); + if (bucket) { + bucket->placeFeatures(); + } + } +} diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp new file mode 100644 index 000000000..420b0bf9f --- /dev/null +++ b/src/mbgl/map/tile_worker.hpp @@ -0,0 +1,75 @@ +#ifndef MBGL_MAP_TILE_WORKER +#define MBGL_MAP_TILE_WORKER + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { + +class CollisionTile; +class GeometryTile; +class Style; +class Bucket; + +using TileParseResult = mapbox::util::variant< + TileData::State, // success + std::string>; // error + +class TileWorker : public util::noncopyable { +public: + TileWorker(const TileID&, + Style&, + const uint16_t maxZoom, + const std::atomic&, + std::unique_ptr); + ~TileWorker(); + + void setBucket(const StyleLayer&, std::unique_ptr); + Bucket* getBucket(const StyleLayer&) const; + size_t countBuckets() const; + + inline TileData::State getState() const { + return state; + } + + inline CollisionTile* getCollision() const { + return collision.get(); + } + + TileParseResult parse(const GeometryTile&); + void redoPlacement(float angle, bool collisionDebug); + + const TileID id; + Style& style; + const uint16_t maxZoom; + const std::atomic& state; + + FillVertexBuffer fillVertexBuffer; + LineVertexBuffer lineVertexBuffer; + + TriangleElementsBuffer triangleElementsBuffer; + LineElementsBuffer lineElementsBuffer; + + std::unique_ptr collision; + + // Contains all the Bucket objects for the tile. Buckets are render + // objects and they get added to this std::map<> by the workers doing + // the actual tile parsing as they get processed. Tiles partially + // parsed can get new buckets at any moment but are also fit for + // rendering. That said, access to this list needs locking unless + // the tile is completely parsed. + std::unordered_map> buckets; + mutable std::mutex bucketsMutex; +}; + +} + +#endif diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 34cfe4311..f485a2495 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -1,10 +1,8 @@ #include -#include #include #include #include -#include -#include +#include #include #include #include @@ -22,8 +20,14 @@ VectorTileData::VectorTileData(const TileID& id_, float angle, bool collisionDebug) : TileData(id_, source_), - style(style_), - collision(std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, angle, collisionDebug)), + worker(style_.workers), + workerData(id_, + style_, + source.max_zoom, + state, + std::make_unique(id_.z, 4096, + source_.tile_size * id.overscaling, + angle, collisionDebug)), lastAngle(angle), currentAngle(angle) { } @@ -32,17 +36,16 @@ VectorTileData::~VectorTileData() { // Cancel in most derived class destructor so that worker tasks are joined before // any member data goes away. cancel(); - style.glyphAtlas->removeGlyphs(reinterpret_cast(this)); } -void VectorTileData::request(Worker& worker, +void VectorTileData::request(Worker&, float pixelRatio, const std::function& callback) { std::string url = source.tileURL(id, pixelRatio); state = State::loading; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, &worker, this](const Response &res) { + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, this](const Response &res) { req = nullptr; if (res.status != Response::Successful) { @@ -56,89 +59,62 @@ void VectorTileData::request(Worker& worker, state = State::loaded; data = res.data; - // Schedule tile parsing in another thread reparse(worker, callback); }); } -bool VectorTileData::reparse(Worker& worker, std::function callback) { +bool VectorTileData::reparse(Worker&, std::function callback) { if (!mayStartParsing()) { return false; } - workRequest = worker.send([this] { parse(); endParsing(); }, callback); - return true; -} - -void VectorTileData::parse() { - if (getState() != State::loaded && getState() != State::partial) { - return; - } - - try { - // Parsing creates state that is encapsulated in TileParser. While parsing, - // the TileParser object writes results into this objects. All other state - // is going to be discarded afterwards. - VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); - const VectorTile* vt = &vectorTile; - TileParser parser(*vt, *this, style); - parser.parse(); - - if (getState() == State::obsolete) { + workRequest = worker.send([this] { + if (getState() != TileData::State::loaded && getState() != TileData::State::partial) { return; } - if (parser.isPartialParse()) { - setState(State::partial); - } else { - setState(State::parsed); + TileParseResult result; + + try { + VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); + result = workerData.parse(vectorTile); + } catch (const std::exception& ex) { + std::stringstream message; + message << "Failed to parse [" << int(id.sourceZ) << "/" << id.x << "/" << id.y << "]: " << ex.what(); + result = message.str(); } - } catch (const std::exception& ex) { - std::stringstream message; - message << "Failed to parse [" << int(id.sourceZ) << "/" << id.x << "/" << id.y << "]: " << ex.what(); - setError(message.str()); - } + + if (getState() == TileData::State::obsolete) { + return; + } else if (result.is()) { + setState(result.get()); + } else { + setError(result.get()); + } + + endParsing(); + }, callback); + + return true; } -Bucket* VectorTileData::getBucket(StyleLayer const& layer) { +Bucket* VectorTileData::getBucket(const StyleLayer& layer) { if (!isReady() || !layer.bucket) { return nullptr; } - std::lock_guard lock(bucketsMutex); - - const auto it = buckets.find(layer.bucket->name); - if (it == buckets.end()) { - return nullptr; - } - - assert(it->second); - return it->second.get(); + return workerData.getBucket(layer); } size_t VectorTileData::countBuckets() const { - std::lock_guard lock(bucketsMutex); - - return buckets.size(); -} - -void VectorTileData::setBucket(StyleLayer const& layer, std::unique_ptr bucket) { - assert(layer.bucket); - - std::lock_guard lock(bucketsMutex); - - if (buckets.find(layer.bucket->name) != buckets.end()) { - return; - } - - buckets[layer.bucket->name] = std::move(bucket); + return workerData.countBuckets(); } void VectorTileData::setState(const State& state_) { TileData::setState(state_); if (isImmutable()) { - collision->reset(0, 0); + workerData.collision->reset(0, 0); } } @@ -158,24 +134,12 @@ void VectorTileData::redoPlacement(float angle, bool collisionDebug) { currentCollisionDebug = collisionDebug; auto callback = std::bind(&VectorTileData::endRedoPlacement, this); - workRequest = style.workers.send([this, angle, collisionDebug] { workerRedoPlacement(angle, collisionDebug); }, callback); - - } -} - -void VectorTileData::workerRedoPlacement(float angle, bool collisionDebug) { - collision->reset(angle, 0); - collision->setDebug(collisionDebug); - for (const auto& layer_desc : style.layers) { - auto bucket = getBucket(*layer_desc); - if (bucket) { - bucket->placeFeatures(); - } + workRequest = worker.send([this, angle, collisionDebug] { workerData.redoPlacement(angle, collisionDebug); }, callback); } } void VectorTileData::endRedoPlacement() { - for (const auto& layer_desc : style.layers) { + for (const auto& layer_desc : workerData.style.layers) { auto bucket = getBucket(*layer_desc); if (bucket) { bucket->swapRenderData(); diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index faddb1dc7..4cfe4e12a 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -2,31 +2,16 @@ #define MBGL_MAP_VECTOR_TILE_DATA #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#include namespace mbgl { class Bucket; -class CollisionTile; -class Painter; class SourceInfo; class StyleLayer; -class TileParser; class Style; class VectorTileData : public TileData { - friend class TileParser; - public: VectorTileData(const TileID&, Style&, @@ -35,18 +20,11 @@ public: bool collisionDebug_); ~VectorTileData(); - virtual void parse(); void redoPlacement(float angle, bool collisionDebug) override; - virtual Bucket* getBucket(StyleLayer const &layer_desc) override; - - size_t countBuckets() const; - void setBucket(StyleLayer const &layer_desc, std::unique_ptr bucket); - void setState(const State& state) override; - inline CollisionTile* getCollision() const { - return collision.get(); - } + Bucket* getBucket(const StyleLayer&) override; + size_t countBuckets() const; void request(Worker&, float pixelRatio, @@ -57,34 +35,16 @@ public: protected: void redoPlacement(); - // Holds the actual geometries in this tile. - FillVertexBuffer fillVertexBuffer; - LineVertexBuffer lineVertexBuffer; - - TriangleElementsBuffer triangleElementsBuffer; - LineElementsBuffer lineElementsBuffer; - - Style& style; + Worker& worker; + TileWorker workerData; private: - // Contains all the Bucket objects for the tile. Buckets are render - // objects and they get added to this std::map<> by the workers doing - // the actual tile parsing as they get processed. Tiles partially - // parsed can get new buckets at any moment but are also fit for - // rendering. That said, access to this list needs locking unless - // the tile is completely parsed. - std::unordered_map> buckets; - mutable std::mutex bucketsMutex; - - std::unique_ptr collision; - float lastAngle = 0; float currentAngle; bool lastCollisionDebug = 0; bool currentCollisionDebug = 0; bool redoingPlacement = false; void endRedoPlacement(); - void workerRedoPlacement(float angle, bool collisionDebug); }; } From 217c376291967aecd7c8cc26e485e3a4fe09ee60 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 17:22:47 -0700 Subject: [PATCH 161/760] Push TileMembers members down hierarchy --- src/mbgl/map/raster_tile_data.cpp | 58 +++++++++++++----------- src/mbgl/map/raster_tile_data.hpp | 15 +++++-- src/mbgl/map/tile_data.cpp | 50 ++------------------- src/mbgl/map/tile_data.hpp | 75 +++++++------------------------ src/mbgl/map/vector_tile_data.cpp | 31 ++++++++++++- src/mbgl/map/vector_tile_data.hpp | 40 ++++++++++++++++- 6 files changed, 131 insertions(+), 138 deletions(-) diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index ee53a6a28..ead33dcb0 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -10,14 +10,15 @@ using namespace mbgl; -RasterTileData::RasterTileData(const TileID& id_, TexturePool &texturePool, +RasterTileData::RasterTileData(const TileID& id_, + TexturePool &texturePool, const SourceInfo &source_) - : TileData(id_, source_), bucket(texturePool, layout) { + : TileData(id_), + source(source_), + bucket(texturePool, layout) { } RasterTileData::~RasterTileData() { - // Cancel in most derived class destructor so that worker tasks are joined before - // any member data goes away. cancel(); } @@ -34,7 +35,8 @@ void RasterTileData::request(Worker& worker, if (res.status != Response::Successful) { std::stringstream message; message << "Failed to load [" << url << "]: " << res.message; - setError(message.str()); + error = message.str(); + state = State::obsolete; callback(); return; } @@ -42,32 +44,36 @@ void RasterTileData::request(Worker& worker, state = State::loaded; data = res.data; - // Schedule tile parsing in another thread - reparse(worker, callback); + workRequest = worker.send([this] { + if (getState() != State::loaded) { + return; + } + + if (bucket.setImage(data)) { + state = State::parsed; + } else { + state = State::invalid; + } + }, callback); }); } -bool RasterTileData::reparse(Worker& worker, std::function callback) { - if (!mayStartParsing()) { - return false; - } - - workRequest = worker.send([this] { parse(); endParsing(); }, callback); - return true; -} - -void RasterTileData::parse() { - if (getState() != State::loaded) { - return; - } - - if (bucket.setImage(data)) { - setState(State::parsed); - } else { - setState(State::invalid); - } +bool RasterTileData::reparse(Worker&, std::function) { + assert(false); + return false; } Bucket* RasterTileData::getBucket(StyleLayer const&) { return &bucket; } + +void RasterTileData::cancel() { + if (state != State::obsolete) { + state = State::obsolete; + } + if (req) { + util::ThreadContext::getFileSource()->cancel(req); + req = nullptr; + } + workRequest.reset(); +} diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index fb59482ef..195698865 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -7,10 +7,11 @@ namespace mbgl { -class Painter; class SourceInfo; +class Request; class StyleLayer; class TexturePool; +class WorkRequest; class RasterTileData : public TileData { public: @@ -23,12 +24,20 @@ public: bool reparse(Worker&, std::function callback) override; - void parse(); + void cancel() override; + Bucket* getBucket(StyleLayer const &layer_desc) override; -protected: +private: + const SourceInfo& source; + + Request *req = nullptr; + std::string data; + StyleLayoutRaster layout; RasterBucket bucket; + + std::unique_ptr workRequest; }; } diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index 4a30639cb..453233211 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -1,53 +1,11 @@ #include -#include -#include using namespace mbgl; -TileData::TileData(const TileID& id_, const SourceInfo& source_) +TileData::TileData(const TileID& id_) : id(id_), - name(id), - source(source_), - state(State::initial), - debugBucket(debugFontBuffer) { + debugBucket(debugFontBuffer), + state(State::initial) { // Initialize tile debug coordinates - debugFontBuffer.addText(name.c_str(), 50, 200, 5); -} - -TileData::~TileData() { - cancel(); -} - -const std::string TileData::toString() const { - return std::string { "[tile " } + name + "]"; -} - -void TileData::setState(const State& state_) { - assert(!isImmutable()); - - state = state_; -} - -void TileData::cancel() { - if (state != State::obsolete) { - state = State::obsolete; - } - if (req) { - util::ThreadContext::getFileSource()->cancel(req); - req = nullptr; - } - workRequest.reset(); -} - -bool TileData::mayStartParsing() { - return !parsing.test_and_set(std::memory_order_acquire); -} - -void TileData::endParsing() { - parsing.clear(std::memory_order_release); -} - -void TileData::setError(const std::string& message) { - error = message; - setState(State::obsolete); + debugFontBuffer.addText(std::string(id).c_str(), 50, 200, 5); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index a3aaf1dfd..9006eb6ad 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -1,26 +1,19 @@ #ifndef MBGL_MAP_TILE_DATA #define MBGL_MAP_TILE_DATA +#include #include #include #include -#include -#include - #include #include #include namespace mbgl { -class Painter; -class SourceInfo; class StyleLayer; -class Request; class Worker; -class WorkRequest; -class TransformState; class TileData : private util::noncopyable { public: @@ -74,8 +67,8 @@ public: return state == State::partial || state == State::parsed; } - TileData(const TileID&, const SourceInfo&); - ~TileData(); + TileData(const TileID&); + virtual ~TileData() = default; virtual void request(Worker&, float pixelRatio, @@ -88,72 +81,34 @@ public: virtual bool reparse(Worker&, std::function callback) = 0; - void cancel(); - const std::string toString() const; + // Mark this tile as no longer needed and cancel any pending work. + virtual void cancel() = 0; - inline bool isReady() const { + virtual Bucket* getBucket(const StyleLayer&) = 0; + + virtual void redoPlacement(float, bool) {} + + bool isReady() const { return isReadyState(state); } - // Returns true if the TileData is in a final state and we cannot - // make changes to it anymore. - inline bool isImmutable() const { - return state == State::parsed || state == State::obsolete; - } - - // We let subclasses override setState() so they - // can intercept the state change and react accordingly. - virtual void setState(const State& state); - inline State getState() const { + State getState() const { return state; } - void endParsing(); - - // Error message to be set in case of request - // and parsing errors. - void setError(const std::string& message); - std::string getError() const { return error; } - // Override this in the child class. - virtual Bucket* getBucket(StyleLayer const &layer_desc) = 0; - - virtual void redoPlacement(float, bool) {} - const TileID id; - const std::string name; - std::atomic_flag parsing = ATOMIC_FLAG_INIT; -protected: - // Set the internal parsing state to true so we prevent - // multiple workers to parse the same tile in parallel, - // which can happen if the tile is in the "partial" state. - // It will return true if is possible to start pasing the - // tile or false if not (so some other worker is already - // parsing the tile). - bool mayStartParsing(); - - const SourceInfo& source; - - Request *req = nullptr; - std::string data; - - std::unique_ptr workRequest; - - std::atomic state; - -private: - std::string error; - -protected: // Contains the tile ID string for painting debug information. + DebugBucket debugBucket; DebugFontBuffer debugFontBuffer; -public: - DebugBucket debugBucket; +protected: + std::atomic state; + std::string error; }; } diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index f485a2495..0b241d722 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -19,7 +19,8 @@ VectorTileData::VectorTileData(const TileID& id_, const SourceInfo& source_, float angle, bool collisionDebug) - : TileData(id_, source_), + : TileData(id_), + source(source_), worker(style_.workers), workerData(id_, style_, @@ -111,7 +112,9 @@ size_t VectorTileData::countBuckets() const { } void VectorTileData::setState(const State& state_) { - TileData::setState(state_); + assert(!isImmutable()); + + state = state_; if (isImmutable()) { workerData.collision->reset(0, 0); @@ -148,3 +151,27 @@ void VectorTileData::endRedoPlacement() { redoingPlacement = false; redoPlacement(); } + +void VectorTileData::cancel() { + if (state != State::obsolete) { + state = State::obsolete; + } + if (req) { + util::ThreadContext::getFileSource()->cancel(req); + req = nullptr; + } + workRequest.reset(); +} + +bool VectorTileData::mayStartParsing() { + return !parsing.test_and_set(std::memory_order_acquire); +} + +void VectorTileData::endParsing() { + parsing.clear(std::memory_order_release); +} + +void VectorTileData::setError(const std::string& message) { + error = message; + setState(State::obsolete); +} diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 4cfe4e12a..bb0e316c1 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -4,12 +4,17 @@ #include #include +#include + namespace mbgl { +class SourceInfo; +class Request; class Bucket; class SourceInfo; class StyleLayer; class Style; +class WorkRequest; class VectorTileData : public TileData { public: @@ -21,7 +26,6 @@ public: ~VectorTileData(); void redoPlacement(float angle, bool collisionDebug) override; - void setState(const State& state) override; Bucket* getBucket(const StyleLayer&) override; size_t countBuckets() const; @@ -32,13 +36,47 @@ public: bool reparse(Worker&, std::function callback) override; + void cancel() override; + protected: + // We let subclasses override setState() so they + // can intercept the state change and react accordingly. + void setState(const State&); + + // Set the internal parsing state to true so we prevent + // multiple workers to parse the same tile in parallel, + // which can happen if the tile is in the "partial" state. + // It will return true if is possible to start pasing the + // tile or false if not (so some other worker is already + // parsing the tile). + bool mayStartParsing(); + + void endParsing(); + + // Error message to be set in case of request + // and parsing errors. + void setError(const std::string& message); + void redoPlacement(); + const SourceInfo& source; + + Request *req = nullptr; + std::string data; + Worker& worker; TileWorker workerData; + std::unique_ptr workRequest; + std::atomic_flag parsing = ATOMIC_FLAG_INIT; + private: + // Returns true if the TileData is in a final state and we cannot + // make changes to it anymore. + inline bool isImmutable() const { + return state == State::parsed || state == State::obsolete; + } + float lastAngle = 0; float currentAngle; bool lastCollisionDebug = 0; From 875ef979be0140332ab219e422022ccb38b99a1e Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 19:07:48 -0700 Subject: [PATCH 162/760] Remove indirection --- src/mbgl/map/live_tile_data.cpp | 19 ++++++++-------- src/mbgl/map/tile_data.hpp | 2 +- src/mbgl/map/vector_tile_data.cpp | 37 ++++++------------------------- src/mbgl/map/vector_tile_data.hpp | 27 ---------------------- 4 files changed, 17 insertions(+), 68 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 74d939f35..ab6f5d983 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -14,20 +14,18 @@ LiveTileData::LiveTileData(const TileID& id_, const SourceInfo& source_, float angle_, bool collisionDebug_) - : VectorTileData::VectorTileData(id_, style_, source_, angle_, collisionDebug_), + : VectorTileData(id_, style_, source_, angle_, collisionDebug_), annotationManager(annotationManager_) { - // live features are always ready - setState(State::loaded); + // live features are always loaded + state = State::loaded; } LiveTileData::~LiveTileData() { - // Cancel in most derived class destructor so that worker tasks are joined before - // any member data goes away. cancel(); } bool LiveTileData::reparse(Worker&, std::function callback) { - if (!mayStartParsing()) { + if (parsing.test_and_set(std::memory_order_acquire)) { return false; } @@ -39,7 +37,7 @@ bool LiveTileData::reparse(Worker&, std::function callback) { const LiveTile* tile = annotationManager.getTile(id); if (!tile) { - setState(State::parsed); + state = State::parsed; return; } @@ -56,12 +54,13 @@ bool LiveTileData::reparse(Worker&, std::function callback) { if (getState() == TileData::State::obsolete) { return; } else if (result.is()) { - setState(result.get()); + state = result.get(); } else { - setError(result.get()); + error = result.get(); + state = State::obsolete; } - endParsing(); + parsing.clear(std::memory_order_release); }, callback); return true; diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 9006eb6ad..5ed4ef4fd 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -77,7 +77,7 @@ public: // Schedule a tile reparse on a worker thread and call the callback on // completion. It will return true if the work was schedule or false it was // not, which can occur if the tile is already being parsed by another - // worker (see "mayStartParsing()"). + // worker. virtual bool reparse(Worker&, std::function callback) = 0; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 0b241d722..171fc7cf7 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -34,8 +34,6 @@ VectorTileData::VectorTileData(const TileID& id_, } VectorTileData::~VectorTileData() { - // Cancel in most derived class destructor so that worker tasks are joined before - // any member data goes away. cancel(); } @@ -52,7 +50,8 @@ void VectorTileData::request(Worker&, if (res.status != Response::Successful) { std::stringstream message; message << "Failed to load [" << url << "]: " << res.message; - setError(message.str()); + error = message.str(); + state = State::obsolete; callback(); return; } @@ -65,7 +64,7 @@ void VectorTileData::request(Worker&, } bool VectorTileData::reparse(Worker&, std::function callback) { - if (!mayStartParsing()) { + if (parsing.test_and_set(std::memory_order_acquire)) { return false; } @@ -88,12 +87,13 @@ bool VectorTileData::reparse(Worker&, std::function callback) { if (getState() == TileData::State::obsolete) { return; } else if (result.is()) { - setState(result.get()); + state = result.get(); } else { - setError(result.get()); + error = result.get(); + state = State::obsolete; } - endParsing(); + parsing.clear(std::memory_order_release); }, callback); return true; @@ -111,16 +111,6 @@ size_t VectorTileData::countBuckets() const { return workerData.countBuckets(); } -void VectorTileData::setState(const State& state_) { - assert(!isImmutable()); - - state = state_; - - if (isImmutable()) { - workerData.collision->reset(0, 0); - } -} - void VectorTileData::redoPlacement() { redoPlacement(lastAngle, lastCollisionDebug); } @@ -162,16 +152,3 @@ void VectorTileData::cancel() { } workRequest.reset(); } - -bool VectorTileData::mayStartParsing() { - return !parsing.test_and_set(std::memory_order_acquire); -} - -void VectorTileData::endParsing() { - parsing.clear(std::memory_order_release); -} - -void VectorTileData::setError(const std::string& message) { - error = message; - setState(State::obsolete); -} diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index bb0e316c1..4157a19d9 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -39,44 +39,17 @@ public: void cancel() override; protected: - // We let subclasses override setState() so they - // can intercept the state change and react accordingly. - void setState(const State&); - - // Set the internal parsing state to true so we prevent - // multiple workers to parse the same tile in parallel, - // which can happen if the tile is in the "partial" state. - // It will return true if is possible to start pasing the - // tile or false if not (so some other worker is already - // parsing the tile). - bool mayStartParsing(); - - void endParsing(); - - // Error message to be set in case of request - // and parsing errors. - void setError(const std::string& message); - void redoPlacement(); const SourceInfo& source; - Request *req = nullptr; std::string data; - Worker& worker; TileWorker workerData; - std::unique_ptr workRequest; std::atomic_flag parsing = ATOMIC_FLAG_INIT; private: - // Returns true if the TileData is in a final state and we cannot - // make changes to it anymore. - inline bool isImmutable() const { - return state == State::parsed || state == State::obsolete; - } - float lastAngle = 0; float currentAngle; bool lastCollisionDebug = 0; From 4676553a3e31bef3094e8be5d6f9dce2809ef5ae Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 19:32:07 -0700 Subject: [PATCH 163/760] Merge TileParser into TileWorker --- src/mbgl/map/tile_parser.cpp | 235 ----------------------------------- src/mbgl/map/tile_parser.hpp | 62 --------- src/mbgl/map/tile_worker.cpp | 218 ++++++++++++++++++++++++++++++-- src/mbgl/map/tile_worker.hpp | 24 +++- 4 files changed, 232 insertions(+), 307 deletions(-) delete mode 100644 src/mbgl/map/tile_parser.cpp delete mode 100644 src/mbgl/map/tile_parser.hpp diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp deleted file mode 100644 index a88084391..000000000 --- a/src/mbgl/map/tile_parser.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace mbgl { - -// Note: This destructor is seemingly empty, but we need to declare it anyway -// because this object has a std::unique_ptr<> of a forward-declare type in -// its header file. -TileParser::~TileParser() = default; - -TileParser::TileParser(const GeometryTile& geometryTile_, - TileWorker& tile_, - Style& style_) - : geometryTile(geometryTile_), - tile(tile_), - style(style_), - partialParse(false) { - assert(style.sprite); -} - -bool TileParser::obsolete() const { - return tile.getState() == TileData::State::obsolete; -} - -void TileParser::parse() { - for (const auto& layer_desc : style.layers) { - // Cancel early when parsing. - if (obsolete()) { - return; - } - - if (layer_desc->isBackground()) { - // background is a special, fake bucket - continue; - } - - if (layer_desc->bucket) { - // This is a singular layer. Check if this bucket already exists. If not, - // parse this bucket. - if (tile.getBucket(*layer_desc)) { - continue; - } - - std::unique_ptr bucket = createBucket(*layer_desc->bucket); - if (bucket) { - // Bucket creation might fail because the data tile may not - // contain any data that falls into this bucket. - tile.setBucket(*layer_desc, std::move(bucket)); - } - } else { - Log::Warning(Event::ParseTile, "layer '%s' does not have buckets", layer_desc->id.c_str()); - } - } -} - -template -struct PropertyEvaluator { - typedef T result_type; - PropertyEvaluator(float z_) : z(z_) {} - - template ::value, int>::type = 0> - T operator()(const P &value) const { - return value; - } - - T operator()(const Function &value) const { - return mapbox::util::apply_visitor(FunctionEvaluator(z), value); - } - - template ::value, int>::type = 0> - T operator()(const P &) const { - return T(); - } - -private: - const float z; -}; - -template -void applyLayoutProperty(PropertyKey key, const ClassProperties &classProperties, T &target, const float z) { - auto it = classProperties.properties.find(key); - if (it != classProperties.properties.end()) { - const PropertyEvaluator evaluator(z); - target = mapbox::util::apply_visitor(evaluator, it->second); - } -} - -std::unique_ptr TileParser::createBucket(const StyleBucket &bucketDesc) { - // Skip this bucket if we are to not render this - if (tile.id.z < std::floor(bucketDesc.min_zoom) && std::floor(bucketDesc.min_zoom) < tile.maxZoom) return nullptr; - if (tile.id.z >= std::ceil(bucketDesc.max_zoom)) return nullptr; - if (bucketDesc.visibility == mbgl::VisibilityType::None) return nullptr; - - auto layer = geometryTile.getLayer(bucketDesc.source_layer); - if (layer) { - if (bucketDesc.type == StyleLayerType::Fill) { - return createFillBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Line) { - return createLineBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Symbol) { - return createSymbolBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Raster) { - return nullptr; - } else { - Log::Warning(Event::ParseTile, "unknown bucket render type for layer '%s' (source layer '%s')", - bucketDesc.name.c_str(), bucketDesc.source_layer.c_str()); - } - } else { - // The layer specified in the bucket does not exist. Do nothing. - if (debug::tileParseWarnings) { - Log::Warning(Event::ParseTile, "layer '%s' does not exist in tile %d/%d/%d", - bucketDesc.source_layer.c_str(), tile.id.z, tile.id.x, tile.id.y); - } - } - - return nullptr; -} - -template -void TileParser::addBucketGeometries(Bucket& bucket, const GeometryTileLayer& layer, const FilterExpression &filter) { - for (std::size_t i = 0; i < layer.featureCount(); i++) { - auto feature = layer.getFeature(i); - - if (obsolete()) - return; - - GeometryTileFeatureExtractor extractor(*feature); - if (!evaluate(filter, extractor)) - continue; - - bucket->addGeometry(feature->getGeometries()); - } -} - -std::unique_ptr TileParser::createFillBucket(const GeometryTileLayer& layer, - const StyleBucket& bucket_desc) { - auto bucket = std::make_unique(tile.fillVertexBuffer, - tile.triangleElementsBuffer, - tile.lineElementsBuffer); - addBucketGeometries(bucket, layer, bucket_desc.filter); - return bucket->hasData() ? std::move(bucket) : nullptr; -} - -std::unique_ptr TileParser::createLineBucket(const GeometryTileLayer& layer, - const StyleBucket& bucket_desc) { - auto bucket = std::make_unique(tile.lineVertexBuffer, - tile.triangleElementsBuffer); - - const float z = tile.id.z; - auto& layout = bucket->layout; - - applyLayoutProperty(PropertyKey::LineCap, bucket_desc.layout, layout.cap, z); - applyLayoutProperty(PropertyKey::LineJoin, bucket_desc.layout, layout.join, z); - applyLayoutProperty(PropertyKey::LineMiterLimit, bucket_desc.layout, layout.miter_limit, z); - applyLayoutProperty(PropertyKey::LineRoundLimit, bucket_desc.layout, layout.round_limit, z); - - addBucketGeometries(bucket, layer, bucket_desc.filter); - return bucket->hasData() ? std::move(bucket) : nullptr; -} - -std::unique_ptr TileParser::createSymbolBucket(const GeometryTileLayer& layer, - const StyleBucket& bucket_desc) { - auto bucket = std::make_unique(*tile.getCollision(), tile.id.overscaling); - - const float z = tile.id.z; - auto& layout = bucket->layout; - - applyLayoutProperty(PropertyKey::SymbolPlacement, bucket_desc.layout, layout.placement, z); - if (layout.placement == PlacementType::Line) { - layout.icon.rotation_alignment = RotationAlignmentType::Map; - layout.text.rotation_alignment = RotationAlignmentType::Map; - }; - applyLayoutProperty(PropertyKey::SymbolMinDistance, bucket_desc.layout, layout.min_distance, z); - applyLayoutProperty(PropertyKey::SymbolAvoidEdges, bucket_desc.layout, layout.avoid_edges, z); - - applyLayoutProperty(PropertyKey::IconAllowOverlap, bucket_desc.layout, layout.icon.allow_overlap, z); - applyLayoutProperty(PropertyKey::IconIgnorePlacement, bucket_desc.layout, layout.icon.ignore_placement, z); - applyLayoutProperty(PropertyKey::IconOptional, bucket_desc.layout, layout.icon.optional, z); - applyLayoutProperty(PropertyKey::IconRotationAlignment, bucket_desc.layout, layout.icon.rotation_alignment, z); - applyLayoutProperty(PropertyKey::IconMaxSize, bucket_desc.layout, layout.icon.max_size, z); - applyLayoutProperty(PropertyKey::IconImage, bucket_desc.layout, layout.icon.image, z); - applyLayoutProperty(PropertyKey::IconPadding, bucket_desc.layout, layout.icon.padding, z); - applyLayoutProperty(PropertyKey::IconRotate, bucket_desc.layout, layout.icon.rotate, z); - applyLayoutProperty(PropertyKey::IconKeepUpright, bucket_desc.layout, layout.icon.keep_upright, z); - applyLayoutProperty(PropertyKey::IconOffset, bucket_desc.layout, layout.icon.offset, z); - - applyLayoutProperty(PropertyKey::TextRotationAlignment, bucket_desc.layout, layout.text.rotation_alignment, z); - applyLayoutProperty(PropertyKey::TextField, bucket_desc.layout, layout.text.field, z); - applyLayoutProperty(PropertyKey::TextFont, bucket_desc.layout, layout.text.font, z); - applyLayoutProperty(PropertyKey::TextMaxSize, bucket_desc.layout, layout.text.max_size, z); - applyLayoutProperty(PropertyKey::TextMaxWidth, bucket_desc.layout, layout.text.max_width, z); - applyLayoutProperty(PropertyKey::TextLineHeight, bucket_desc.layout, layout.text.line_height, z); - applyLayoutProperty(PropertyKey::TextLetterSpacing, bucket_desc.layout, layout.text.letter_spacing, z); - applyLayoutProperty(PropertyKey::TextMaxAngle, bucket_desc.layout, layout.text.max_angle, z); - applyLayoutProperty(PropertyKey::TextRotate, bucket_desc.layout, layout.text.rotate, z); - applyLayoutProperty(PropertyKey::TextPadding, bucket_desc.layout, layout.text.padding, z); - applyLayoutProperty(PropertyKey::TextIgnorePlacement, bucket_desc.layout, layout.text.ignore_placement, z); - applyLayoutProperty(PropertyKey::TextOptional, bucket_desc.layout, layout.text.optional, z); - applyLayoutProperty(PropertyKey::TextJustify, bucket_desc.layout, layout.text.justify, z); - applyLayoutProperty(PropertyKey::TextAnchor, bucket_desc.layout, layout.text.anchor, z); - applyLayoutProperty(PropertyKey::TextKeepUpright, bucket_desc.layout, layout.text.keep_upright, z); - applyLayoutProperty(PropertyKey::TextTransform, bucket_desc.layout, layout.text.transform, z); - applyLayoutProperty(PropertyKey::TextOffset, bucket_desc.layout, layout.text.offset, z); - applyLayoutProperty(PropertyKey::TextAllowOverlap, bucket_desc.layout, layout.text.allow_overlap, z); - - if (bucket->needsDependencies(layer, bucket_desc.filter, *style.glyphStore, *style.sprite)) { - partialParse = true; - } - - // We do not proceed if the parser is in a "partial" state because - // the layer ordering needs to be respected when calculating text - // collisions. Although, at this point, we requested all the resources - // needed by this tile. - if (partialParse) { - return nullptr; - } - - bucket->addFeatures( - reinterpret_cast(&tile), *style.spriteAtlas, *style.sprite, *style.glyphAtlas, *style.glyphStore); - - return bucket->hasData() ? std::move(bucket) : nullptr; -} - -} diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp deleted file mode 100644 index 63c1fd72a..000000000 --- a/src/mbgl/map/tile_parser.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef MBGL_MAP_TILE_PARSER -#define MBGL_MAP_TILE_PARSER - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace mbgl { - -class Bucket; -class FontStack; -class Style; -class StyleBucket; -class StyleLayoutFill; -class StyleLayoutRaster; -class StyleLayoutLine; -class StyleLayoutSymbol; -class TileWorker; - -class TileParser : private util::noncopyable { -public: - TileParser(const GeometryTile&, TileWorker&, Style&); - ~TileParser(); - -public: - void parse(); - inline bool isPartialParse() const { - return partialParse; - } - -private: - bool obsolete() const; - - std::unique_ptr createBucket(const StyleBucket&); - std::unique_ptr createFillBucket(const GeometryTileLayer&, const StyleBucket&); - std::unique_ptr createLineBucket(const GeometryTileLayer&, const StyleBucket&); - std::unique_ptr createSymbolBucket(const GeometryTileLayer&, const StyleBucket&); - - template - void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&); - - const GeometryTile& geometryTile; - TileWorker& tile; - - // Cross-thread shared data. - Style& style; - - bool partialParse; -}; - -} - -#endif diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index 1991ab188..a70e5a772 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -1,9 +1,13 @@ #include #include -#include #include #include #include +#include +#include +#include +#include +#include using namespace mbgl; @@ -12,11 +16,12 @@ TileWorker::TileWorker(const TileID& id_, const uint16_t maxZoom_, const std::atomic& state_, std::unique_ptr collision_) - : id(id_), - style(style_), + : style(style_), + id(id_), maxZoom(maxZoom_), state(state_), collision(std::move(collision_)) { + assert(style.sprite); } TileWorker::~TileWorker() { @@ -53,13 +58,36 @@ size_t TileWorker::countBuckets() const { } TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { - // Parsing creates state that is encapsulated in TileParser. While parsing, - // the TileParser object writes results into this objects. All other state - // is going to be discarded afterwards. - TileParser parser(geometryTile, *this, style); - parser.parse(); + for (const auto& layer_desc : style.layers) { + // Cancel early when parsing. + if (obsolete()) { + return TileData::State::obsolete; + } - if (parser.isPartialParse()) { + if (layer_desc->isBackground()) { + // background is a special, fake bucket + continue; + } + + if (layer_desc->bucket) { + // This is a singular layer. Check if this bucket already exists. If not, + // parse this bucket. + if (getBucket(*layer_desc)) { + continue; + } + + std::unique_ptr bucket = createBucket(*layer_desc->bucket, geometryTile); + if (bucket) { + // Bucket creation might fail because the data tile may not + // contain any data that falls into this bucket. + setBucket(*layer_desc, std::move(bucket)); + } + } else { + Log::Warning(Event::ParseTile, "layer '%s' does not have buckets", layer_desc->id.c_str()); + } + } + + if (isPartialParse()) { return TileData::State::partial; } else { return TileData::State::parsed; @@ -76,3 +104,175 @@ void TileWorker::redoPlacement(float angle, bool collisionDebug) { } } } + +bool TileWorker::obsolete() const { + return getState() == TileData::State::obsolete; +} + +template +struct PropertyEvaluator { + typedef T result_type; + PropertyEvaluator(float z_) : z(z_) {} + + template ::value, int>::type = 0> + T operator()(const P &value) const { + return value; + } + + T operator()(const Function &value) const { + return mapbox::util::apply_visitor(FunctionEvaluator(z), value); + } + + template ::value, int>::type = 0> + T operator()(const P &) const { + return T(); + } + +private: + const float z; +}; + +template +void applyLayoutProperty(PropertyKey key, const ClassProperties &classProperties, T &target, const float z) { + auto it = classProperties.properties.find(key); + if (it != classProperties.properties.end()) { + const PropertyEvaluator evaluator(z); + target = mapbox::util::apply_visitor(evaluator, it->second); + } +} + +std::unique_ptr TileWorker::createBucket(const StyleBucket& bucketDesc, const GeometryTile& geometryTile) { + // Skip this bucket if we are to not render this + if (id.z < std::floor(bucketDesc.min_zoom) && std::floor(bucketDesc.min_zoom) < maxZoom) return nullptr; + if (id.z >= std::ceil(bucketDesc.max_zoom)) return nullptr; + if (bucketDesc.visibility == mbgl::VisibilityType::None) return nullptr; + + auto layer = geometryTile.getLayer(bucketDesc.source_layer); + if (layer) { + if (bucketDesc.type == StyleLayerType::Fill) { + return createFillBucket(*layer, bucketDesc); + } else if (bucketDesc.type == StyleLayerType::Line) { + return createLineBucket(*layer, bucketDesc); + } else if (bucketDesc.type == StyleLayerType::Symbol) { + return createSymbolBucket(*layer, bucketDesc); + } else if (bucketDesc.type == StyleLayerType::Raster) { + return nullptr; + } else { + Log::Warning(Event::ParseTile, "unknown bucket render type for layer '%s' (source layer '%s')", + bucketDesc.name.c_str(), bucketDesc.source_layer.c_str()); + } + } else { + // The layer specified in the bucket does not exist. Do nothing. + if (debug::tileParseWarnings) { + Log::Warning(Event::ParseTile, "layer '%s' does not exist in tile %d/%d/%d", + bucketDesc.source_layer.c_str(), id.z, id.x, id.y); + } + } + + return nullptr; +} + +template +void TileWorker::addBucketGeometries(Bucket& bucket, const GeometryTileLayer& layer, const FilterExpression &filter) { + for (std::size_t i = 0; i < layer.featureCount(); i++) { + auto feature = layer.getFeature(i); + + if (obsolete()) + return; + + GeometryTileFeatureExtractor extractor(*feature); + if (!evaluate(filter, extractor)) + continue; + + bucket->addGeometry(feature->getGeometries()); + } +} + +std::unique_ptr TileWorker::createFillBucket(const GeometryTileLayer& layer, + const StyleBucket& bucket_desc) { + auto bucket = std::make_unique(fillVertexBuffer, + triangleElementsBuffer, + lineElementsBuffer); + addBucketGeometries(bucket, layer, bucket_desc.filter); + return bucket->hasData() ? std::move(bucket) : nullptr; +} + +std::unique_ptr TileWorker::createLineBucket(const GeometryTileLayer& layer, + const StyleBucket& bucket_desc) { + auto bucket = std::make_unique(lineVertexBuffer, + triangleElementsBuffer); + + const float z = id.z; + auto& layout = bucket->layout; + + applyLayoutProperty(PropertyKey::LineCap, bucket_desc.layout, layout.cap, z); + applyLayoutProperty(PropertyKey::LineJoin, bucket_desc.layout, layout.join, z); + applyLayoutProperty(PropertyKey::LineMiterLimit, bucket_desc.layout, layout.miter_limit, z); + applyLayoutProperty(PropertyKey::LineRoundLimit, bucket_desc.layout, layout.round_limit, z); + + addBucketGeometries(bucket, layer, bucket_desc.filter); + return bucket->hasData() ? std::move(bucket) : nullptr; +} + +std::unique_ptr TileWorker::createSymbolBucket(const GeometryTileLayer& layer, + const StyleBucket& bucket_desc) { + auto bucket = std::make_unique(*getCollision(), id.overscaling); + + const float z = id.z; + auto& layout = bucket->layout; + + applyLayoutProperty(PropertyKey::SymbolPlacement, bucket_desc.layout, layout.placement, z); + if (layout.placement == PlacementType::Line) { + layout.icon.rotation_alignment = RotationAlignmentType::Map; + layout.text.rotation_alignment = RotationAlignmentType::Map; + }; + applyLayoutProperty(PropertyKey::SymbolMinDistance, bucket_desc.layout, layout.min_distance, z); + applyLayoutProperty(PropertyKey::SymbolAvoidEdges, bucket_desc.layout, layout.avoid_edges, z); + + applyLayoutProperty(PropertyKey::IconAllowOverlap, bucket_desc.layout, layout.icon.allow_overlap, z); + applyLayoutProperty(PropertyKey::IconIgnorePlacement, bucket_desc.layout, layout.icon.ignore_placement, z); + applyLayoutProperty(PropertyKey::IconOptional, bucket_desc.layout, layout.icon.optional, z); + applyLayoutProperty(PropertyKey::IconRotationAlignment, bucket_desc.layout, layout.icon.rotation_alignment, z); + applyLayoutProperty(PropertyKey::IconMaxSize, bucket_desc.layout, layout.icon.max_size, z); + applyLayoutProperty(PropertyKey::IconImage, bucket_desc.layout, layout.icon.image, z); + applyLayoutProperty(PropertyKey::IconPadding, bucket_desc.layout, layout.icon.padding, z); + applyLayoutProperty(PropertyKey::IconRotate, bucket_desc.layout, layout.icon.rotate, z); + applyLayoutProperty(PropertyKey::IconKeepUpright, bucket_desc.layout, layout.icon.keep_upright, z); + applyLayoutProperty(PropertyKey::IconOffset, bucket_desc.layout, layout.icon.offset, z); + + applyLayoutProperty(PropertyKey::TextRotationAlignment, bucket_desc.layout, layout.text.rotation_alignment, z); + applyLayoutProperty(PropertyKey::TextField, bucket_desc.layout, layout.text.field, z); + applyLayoutProperty(PropertyKey::TextFont, bucket_desc.layout, layout.text.font, z); + applyLayoutProperty(PropertyKey::TextMaxSize, bucket_desc.layout, layout.text.max_size, z); + applyLayoutProperty(PropertyKey::TextMaxWidth, bucket_desc.layout, layout.text.max_width, z); + applyLayoutProperty(PropertyKey::TextLineHeight, bucket_desc.layout, layout.text.line_height, z); + applyLayoutProperty(PropertyKey::TextLetterSpacing, bucket_desc.layout, layout.text.letter_spacing, z); + applyLayoutProperty(PropertyKey::TextMaxAngle, bucket_desc.layout, layout.text.max_angle, z); + applyLayoutProperty(PropertyKey::TextRotate, bucket_desc.layout, layout.text.rotate, z); + applyLayoutProperty(PropertyKey::TextPadding, bucket_desc.layout, layout.text.padding, z); + applyLayoutProperty(PropertyKey::TextIgnorePlacement, bucket_desc.layout, layout.text.ignore_placement, z); + applyLayoutProperty(PropertyKey::TextOptional, bucket_desc.layout, layout.text.optional, z); + applyLayoutProperty(PropertyKey::TextJustify, bucket_desc.layout, layout.text.justify, z); + applyLayoutProperty(PropertyKey::TextAnchor, bucket_desc.layout, layout.text.anchor, z); + applyLayoutProperty(PropertyKey::TextKeepUpright, bucket_desc.layout, layout.text.keep_upright, z); + applyLayoutProperty(PropertyKey::TextTransform, bucket_desc.layout, layout.text.transform, z); + applyLayoutProperty(PropertyKey::TextOffset, bucket_desc.layout, layout.text.offset, z); + applyLayoutProperty(PropertyKey::TextAllowOverlap, bucket_desc.layout, layout.text.allow_overlap, z); + + if (bucket->needsDependencies(layer, bucket_desc.filter, *style.glyphStore, *style.sprite)) { + partialParse = true; + } + + // We do not proceed if the parser is in a "partial" state because + // the layer ordering needs to be respected when calculating text + // collisions. Although, at this point, we requested all the resources + // needed by this tile. + if (partialParse) { + return nullptr; + } + + bucket->addFeatures( + reinterpret_cast(this), *style.spriteAtlas, *style.sprite, *style.glyphAtlas, *style.glyphStore); + + return bucket->hasData() ? std::move(bucket) : nullptr; +} diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index 420b0bf9f..c3f131ccd 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -7,7 +7,9 @@ #include #include #include +#include +#include #include #include #include @@ -18,6 +20,8 @@ class CollisionTile; class GeometryTile; class Style; class Bucket; +class StyleBucket; +class GeometryTileLayer; using TileParseResult = mapbox::util::variant< TileData::State, // success @@ -44,14 +48,32 @@ public: return collision.get(); } + inline bool isPartialParse() const { + return partialParse; + } + TileParseResult parse(const GeometryTile&); void redoPlacement(float angle, bool collisionDebug); - const TileID id; Style& style; + +private: + bool obsolete() const; + + std::unique_ptr createBucket(const StyleBucket&, const GeometryTile&); + std::unique_ptr createFillBucket(const GeometryTileLayer&, const StyleBucket&); + std::unique_ptr createLineBucket(const GeometryTileLayer&, const StyleBucket&); + std::unique_ptr createSymbolBucket(const GeometryTileLayer&, const StyleBucket&); + + template + void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&); + + const TileID id; const uint16_t maxZoom; const std::atomic& state; + bool partialParse = false; + FillVertexBuffer fillVertexBuffer; LineVertexBuffer lineVertexBuffer; From 9e0b241ddefa8e22b1b5c115b3ffdc1a19e9d624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Wed, 10 Jun 2015 11:34:38 -0400 Subject: [PATCH 164/760] reset partialParse when starting a new parsing run --- src/mbgl/map/tile_worker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index a70e5a772..de6c71898 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -58,6 +58,8 @@ size_t TileWorker::countBuckets() const { } TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { + partialParse = false; + for (const auto& layer_desc : style.layers) { // Cancel early when parsing. if (obsolete()) { From 24f50fdd4f888b5524b6a95dbe9d920404a6bd64 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 19:33:53 -0700 Subject: [PATCH 165/760] Remove indirection --- src/mbgl/map/tile_worker.cpp | 12 ++++-------- src/mbgl/map/tile_worker.hpp | 14 -------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index de6c71898..ae291b04e 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -62,7 +62,7 @@ TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { for (const auto& layer_desc : style.layers) { // Cancel early when parsing. - if (obsolete()) { + if (state == TileData::State::obsolete) { return TileData::State::obsolete; } @@ -89,7 +89,7 @@ TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { } } - if (isPartialParse()) { + if (partialParse) { return TileData::State::partial; } else { return TileData::State::parsed; @@ -107,10 +107,6 @@ void TileWorker::redoPlacement(float angle, bool collisionDebug) { } } -bool TileWorker::obsolete() const { - return getState() == TileData::State::obsolete; -} - template struct PropertyEvaluator { typedef T result_type; @@ -179,7 +175,7 @@ void TileWorker::addBucketGeometries(Bucket& bucket, const GeometryTileLayer& la for (std::size_t i = 0; i < layer.featureCount(); i++) { auto feature = layer.getFeature(i); - if (obsolete()) + if (state == TileData::State::obsolete) return; GeometryTileFeatureExtractor extractor(*feature); @@ -218,7 +214,7 @@ std::unique_ptr TileWorker::createLineBucket(const GeometryTileLayer& la std::unique_ptr TileWorker::createSymbolBucket(const GeometryTileLayer& layer, const StyleBucket& bucket_desc) { - auto bucket = std::make_unique(*getCollision(), id.overscaling); + auto bucket = std::make_unique(*collision, id.overscaling); const float z = id.z; auto& layout = bucket->layout; diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index c3f131ccd..ae97e08cb 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -40,26 +40,12 @@ public: Bucket* getBucket(const StyleLayer&) const; size_t countBuckets() const; - inline TileData::State getState() const { - return state; - } - - inline CollisionTile* getCollision() const { - return collision.get(); - } - - inline bool isPartialParse() const { - return partialParse; - } - TileParseResult parse(const GeometryTile&); void redoPlacement(float angle, bool collisionDebug); Style& style; private: - bool obsolete() const; - std::unique_ptr createBucket(const StyleBucket&, const GeometryTile&); std::unique_ptr createFillBucket(const GeometryTileLayer&, const StyleBucket&); std::unique_ptr createLineBucket(const GeometryTileLayer&, const StyleBucket&); From d596d9dc381170013563348b7af46812f6fcf790 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 4 Jun 2015 19:58:43 -0700 Subject: [PATCH 166/760] Clean up bucket creation loop --- src/mbgl/map/tile_worker.cpp | 122 ++++++++++++++++------------------- src/mbgl/map/tile_worker.hpp | 13 ++-- 2 files changed, 62 insertions(+), 73 deletions(-) diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index ae291b04e..b69698689 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -28,18 +28,6 @@ TileWorker::~TileWorker() { style.glyphAtlas->removeGlyphs(reinterpret_cast(this)); } -void TileWorker::setBucket(const StyleLayer& layer, std::unique_ptr bucket) { - assert(layer.bucket); - - std::lock_guard lock(bucketsMutex); - - if (buckets.find(layer.bucket->name) != buckets.end()) { - return; - } - - buckets[layer.bucket->name] = std::move(bucket); -} - Bucket* TileWorker::getBucket(const StyleLayer& layer) const { std::lock_guard lock(bucketsMutex); @@ -60,40 +48,11 @@ size_t TileWorker::countBuckets() const { TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { partialParse = false; - for (const auto& layer_desc : style.layers) { - // Cancel early when parsing. - if (state == TileData::State::obsolete) { - return TileData::State::obsolete; - } - - if (layer_desc->isBackground()) { - // background is a special, fake bucket - continue; - } - - if (layer_desc->bucket) { - // This is a singular layer. Check if this bucket already exists. If not, - // parse this bucket. - if (getBucket(*layer_desc)) { - continue; - } - - std::unique_ptr bucket = createBucket(*layer_desc->bucket, geometryTile); - if (bucket) { - // Bucket creation might fail because the data tile may not - // contain any data that falls into this bucket. - setBucket(*layer_desc, std::move(bucket)); - } - } else { - Log::Warning(Event::ParseTile, "layer '%s' does not have buckets", layer_desc->id.c_str()); - } + for (const auto& layer : style.layers) { + parseLayer(*layer, geometryTile); } - if (partialParse) { - return TileData::State::partial; - } else { - return TileData::State::parsed; - } + return partialParse ? TileData::State::partial : TileData::State::parsed; } void TileWorker::redoPlacement(float angle, bool collisionDebug) { @@ -139,35 +98,66 @@ void applyLayoutProperty(PropertyKey key, const ClassProperties &classProperties } } -std::unique_ptr TileWorker::createBucket(const StyleBucket& bucketDesc, const GeometryTile& geometryTile) { - // Skip this bucket if we are to not render this - if (id.z < std::floor(bucketDesc.min_zoom) && std::floor(bucketDesc.min_zoom) < maxZoom) return nullptr; - if (id.z >= std::ceil(bucketDesc.max_zoom)) return nullptr; - if (bucketDesc.visibility == mbgl::VisibilityType::None) return nullptr; +void TileWorker::parseLayer(const StyleLayer& layer, const GeometryTile& geometryTile) { + // Cancel early when parsing. + if (state == TileData::State::obsolete) + return; - auto layer = geometryTile.getLayer(bucketDesc.source_layer); - if (layer) { - if (bucketDesc.type == StyleLayerType::Fill) { - return createFillBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Line) { - return createLineBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Symbol) { - return createSymbolBucket(*layer, bucketDesc); - } else if (bucketDesc.type == StyleLayerType::Raster) { - return nullptr; - } else { - Log::Warning(Event::ParseTile, "unknown bucket render type for layer '%s' (source layer '%s')", - bucketDesc.name.c_str(), bucketDesc.source_layer.c_str()); - } - } else { + // Background is a special case. + if (layer.isBackground()) + return; + + if (!layer.bucket) { + Log::Warning(Event::ParseTile, "layer '%s' does not have buckets", layer.id.c_str()); + return; + } + + // This is a singular layer. Check if this bucket already exists. + if (getBucket(layer)) + return; + + const StyleBucket& styleBucket = *layer.bucket; + + // Skip this bucket if we are to not render this + if (id.z < std::floor(styleBucket.min_zoom) && std::floor(styleBucket.min_zoom) < maxZoom) + return; + if (id.z >= std::ceil(styleBucket.max_zoom)) + return; + if (styleBucket.visibility == mbgl::VisibilityType::None) + return; + + auto geometryLayer = geometryTile.getLayer(styleBucket.source_layer); + if (!geometryLayer) { // The layer specified in the bucket does not exist. Do nothing. if (debug::tileParseWarnings) { Log::Warning(Event::ParseTile, "layer '%s' does not exist in tile %d/%d/%d", - bucketDesc.source_layer.c_str(), id.z, id.x, id.y); + styleBucket.source_layer.c_str(), id.z, id.x, id.y); } + return; } - return nullptr; + std::unique_ptr bucket; + + if (styleBucket.type == StyleLayerType::Fill) { + bucket = createFillBucket(*geometryLayer, styleBucket); + } else if (styleBucket.type == StyleLayerType::Line) { + bucket = createLineBucket(*geometryLayer, styleBucket); + } else if (styleBucket.type == StyleLayerType::Symbol) { + bucket = createSymbolBucket(*geometryLayer, styleBucket); + } else if (styleBucket.type == StyleLayerType::Raster) { + return; + } else { + Log::Warning(Event::ParseTile, "unknown bucket render type for layer '%s' (source layer '%s')", + styleBucket.name.c_str(), styleBucket.source_layer.c_str()); + } + + // Bucket creation might fail because the data tile may not + // contain any data that falls into this bucket. + if (!bucket) + return; + + std::lock_guard lock(bucketsMutex); + buckets[styleBucket.name] = std::move(bucket); } template diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index ae97e08cb..7fb474a5e 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -36,7 +36,6 @@ public: std::unique_ptr); ~TileWorker(); - void setBucket(const StyleLayer&, std::unique_ptr); Bucket* getBucket(const StyleLayer&) const; size_t countBuckets() const; @@ -46,7 +45,8 @@ public: Style& style; private: - std::unique_ptr createBucket(const StyleBucket&, const GeometryTile&); + void parseLayer(const StyleLayer&, const GeometryTile&); + std::unique_ptr createFillBucket(const GeometryTileLayer&, const StyleBucket&); std::unique_ptr createLineBucket(const GeometryTileLayer&, const StyleBucket&); std::unique_ptr createSymbolBucket(const GeometryTileLayer&, const StyleBucket&); @@ -69,11 +69,10 @@ private: std::unique_ptr collision; // Contains all the Bucket objects for the tile. Buckets are render - // objects and they get added to this std::map<> by the workers doing - // the actual tile parsing as they get processed. Tiles partially - // parsed can get new buckets at any moment but are also fit for - // rendering. That said, access to this list needs locking unless - // the tile is completely parsed. + // objects and they get added to this map as they get processed. + // Tiles partially parsed can get new buckets at any moment but are + // also fit for rendering. That said, access to this list needs locking + // unless the tile is completely parsed. std::unordered_map> buckets; mutable std::mutex bucketsMutex; }; From 5fadda53e5cf7044ead3a3e688c557c7af290453 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 8 Jun 2015 18:40:11 -0400 Subject: [PATCH 167/760] Move WorkerTask functionality into RunLoop core As a byproduct, this makes FileCache get requests properly cancelable. --- include/mbgl/storage/file_cache.hpp | 3 +- include/mbgl/storage/sqlite_cache.hpp | 2 +- platform/default/sqlite_cache.cpp | 4 +- src/mbgl/storage/default_file_source.cpp | 37 ++-- src/mbgl/storage/default_file_source_impl.hpp | 8 +- src/mbgl/util/run_loop.hpp | 165 ++++++++++++++---- src/mbgl/util/thread.hpp | 16 +- src/mbgl/util/work_task.cpp | 37 ---- src/mbgl/util/work_task.hpp | 20 +-- src/mbgl/util/worker.cpp | 30 +--- test/miscellaneous/thread.cpp | 31 ++-- 11 files changed, 201 insertions(+), 152 deletions(-) diff --git a/include/mbgl/storage/file_cache.hpp b/include/mbgl/storage/file_cache.hpp index f815d5b8c..a9071b1b6 100644 --- a/include/mbgl/storage/file_cache.hpp +++ b/include/mbgl/storage/file_cache.hpp @@ -10,6 +10,7 @@ namespace mbgl { struct Resource; class Response; +class WorkRequest; class FileCache : private util::noncopyable { public: @@ -18,7 +19,7 @@ public: enum class Hint : uint8_t { Full, Refresh, No }; using Callback = std::function)>; - virtual void get(const Resource &resource, Callback callback) = 0; + virtual std::unique_ptr get(const Resource &resource, Callback callback) = 0; virtual void put(const Resource &resource, std::shared_ptr response, Hint hint) = 0; }; diff --git a/include/mbgl/storage/sqlite_cache.hpp b/include/mbgl/storage/sqlite_cache.hpp index fd67d48bb..65328652f 100644 --- a/include/mbgl/storage/sqlite_cache.hpp +++ b/include/mbgl/storage/sqlite_cache.hpp @@ -17,7 +17,7 @@ public: ~SQLiteCache() override; // FileCache API - void get(const Resource &resource, Callback callback) override; + std::unique_ptr get(const Resource &resource, Callback callback) override; void put(const Resource &resource, std::shared_ptr response, Hint hint) override; class Impl; diff --git a/platform/default/sqlite_cache.cpp b/platform/default/sqlite_cache.cpp index a2d2c6815..3c23d167a 100644 --- a/platform/default/sqlite_cache.cpp +++ b/platform/default/sqlite_cache.cpp @@ -126,12 +126,12 @@ void SQLiteCache::Impl::createSchema() { } } -void SQLiteCache::get(const Resource &resource, Callback callback) { +std::unique_ptr SQLiteCache::get(const Resource &resource, Callback callback) { // Can be called from any thread, but most likely from the file source thread. // Will try to load the URL from the SQLite database and call the callback when done. // Note that the callback is probably going to invoked from another thread, so the caller // must make sure that it can run in that thread. - thread->invokeWithResult(&Impl::get, std::move(callback), resource); + return thread->invokeWithResult(&Impl::get, std::move(callback), resource); } std::unique_ptr SQLiteCache::Impl::get(const Resource &resource) { diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp index dcd0090da..5f6bdcb6a 100644 --- a/src/mbgl/storage/default_file_source.cpp +++ b/src/mbgl/storage/default_file_source.cpp @@ -100,26 +100,16 @@ void DefaultFileSource::Impl::add(Request* req) { request->observers.insert(req); if (cache) { - startCacheRequest(resource); + startCacheRequest(request); } else { - startRealRequest(resource); + startRealRequest(request); } } -void DefaultFileSource::Impl::startCacheRequest(const Resource& resource) { +void DefaultFileSource::Impl::startCacheRequest(DefaultFileRequest* request) { // Check the cache for existing data so that we can potentially // revalidate the information without having to redownload everything. - cache->get(resource, [this, resource](std::unique_ptr response) { - DefaultFileRequest* request = find(resource); - - if (!request || request->request) { - // There is no request for this URL anymore. Likely, the request was canceled - // before we got around to process the cache result. - // The second possibility is that a request has already been started by another cache - // request. In this case, we don't have to do anything either. - return; - } - + request->cacheRequest = cache->get(request->resource, [this, request](std::unique_ptr response) { auto expired = [&response] { const int64_t now = std::chrono::duration_cast( SystemClock::now().time_since_epoch()).count(); @@ -128,7 +118,7 @@ void DefaultFileSource::Impl::startCacheRequest(const Resource& resource) { if (!response || expired()) { // No response or stale cache. Run the real request. - startRealRequest(resource, std::move(response)); + startRealRequest(request, std::move(response)); } else { // The response is fresh. We're good to notify the caller. notify(request, std::move(response), FileCache::Hint::No); @@ -136,17 +126,15 @@ void DefaultFileSource::Impl::startCacheRequest(const Resource& resource) { }); } -void DefaultFileSource::Impl::startRealRequest(const Resource& resource, std::shared_ptr response) { - DefaultFileRequest* request = find(resource); - +void DefaultFileSource::Impl::startRealRequest(DefaultFileRequest* request, std::shared_ptr response) { auto callback = [request, this] (std::shared_ptr res, FileCache::Hint hint) { notify(request, res, hint); }; - if (algo::starts_with(resource.url, "asset://")) { - request->request = assetContext->createRequest(resource, callback, loop, assetRoot); + if (algo::starts_with(request->resource.url, "asset://")) { + request->realRequest = assetContext->createRequest(request->resource, callback, loop, assetRoot); } else { - request->request = httpContext->createRequest(resource, callback, loop, response); + request->realRequest = httpContext->createRequest(request->resource, callback, loop, response); } } @@ -158,8 +146,11 @@ void DefaultFileSource::Impl::cancel(Request* req) { // cancel the request and remove it from the pending list. request->observers.erase(req); if (request->observers.empty()) { - if (request->request) { - request->request->cancel(); + if (request->cacheRequest) { + request->cacheRequest.reset(); + } + if (request->realRequest) { + request->realRequest->cancel(); } pending.erase(request->resource); } diff --git a/src/mbgl/storage/default_file_source_impl.hpp b/src/mbgl/storage/default_file_source_impl.hpp index 3845014e9..22ca5b6d1 100644 --- a/src/mbgl/storage/default_file_source_impl.hpp +++ b/src/mbgl/storage/default_file_source_impl.hpp @@ -17,7 +17,9 @@ class RequestBase; struct DefaultFileRequest { const Resource resource; std::set observers; - RequestBase* request = nullptr; + + std::unique_ptr cacheRequest; + RequestBase* realRequest = nullptr; inline DefaultFileRequest(const Resource& resource_) : resource(resource_) {} @@ -39,8 +41,8 @@ public: private: DefaultFileRequest* find(const Resource&); - void startCacheRequest(const Resource&); - void startRealRequest(const Resource&, std::shared_ptr = nullptr); + void startCacheRequest(DefaultFileRequest*); + void startRealRequest(DefaultFileRequest*, std::shared_ptr = nullptr); void notify(DefaultFileRequest*, std::shared_ptr, FileCache::Hint); std::unordered_map pending; diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp index 1c92847b6..3b753aca8 100644 --- a/src/mbgl/util/run_loop.hpp +++ b/src/mbgl/util/run_loop.hpp @@ -2,6 +2,8 @@ #define MBGL_UTIL_RUN_LOOP #include +#include +#include #include #include @@ -23,11 +25,48 @@ public: template void invoke(Fn&& fn, Args&&... args) { auto tuple = std::make_tuple(std::move(args)...); - auto invokable = std::make_unique>(std::move(fn), std::move(tuple)); - withMutex([&] { queue.push(std::move(invokable)); }); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple)); + + withMutex([&] { queue.push(task); }); async.send(); } + // Invoke fn(args...) on this RunLoop, then invoke callback() on the current RunLoop. + template + std::unique_ptr + invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { + auto tuple = std::make_tuple(std::move(args)...); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple)); + + task->bind(callback); + + withMutex([&] { queue.push(task); }); + async.send(); + + return std::make_unique(task); + } + + // Invoke fn(args...) on this RunLoop, then invoke callback(result) on the current RunLoop. + template + std::unique_ptr + invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { + auto tuple = std::make_tuple(std::move(args)...); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple)); + + task->bind(callback); + + withMutex([&] { queue.push(task); }); + async.send(); + + return std::make_unique(task); + } + // Return a function that invokes the given function on this RunLoop. template auto bind(std::function fn) { @@ -36,57 +75,119 @@ public: }; } - // Invoke fn(args...) on this RunLoop, then invoke callback(result) on the current RunLoop. - template - void invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { - invoke([fn = std::move(fn), callback = current.get()->bind(callback)] (Args&&... a) mutable { - callback(fn(std::forward(a)...)); - }, std::forward(args)...); - } - - // Invoke fn(args...) on this RunLoop, then invoke callback() on the current RunLoop. - template - void invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { - invoke([fn = std::move(fn), callback = current.get()->bind(callback)] (Args&&... a) mutable { - fn(std::forward(a)...); - callback(); - }, std::forward(args)...); - } - uv_loop_t* get() { return async.get()->loop; } static uv::tls current; private: - // A movable type-erasing invokable entity wrapper. This allows to store arbitrary invokable - // things (like std::function<>, or the result of a movable-only std::bind()) in the queue. - // Source: http://stackoverflow.com/a/29642072/331379 - struct Message { - virtual void operator()() = 0; - virtual ~Message() = default; - }; - template - struct Invoker : Message { + class Invoker : public WorkTask, public std::enable_shared_from_this> { + public: Invoker(F&& f, P&& p) : func(std::move(f)), params(std::move(p)) { } - void operator()() override { - invoke(std::index_sequence_for{}); + using C = std::function; + + void bind(C after) { + auto task = this->shared_from_this(); + callback = RunLoop::current.get()->bind(C([task, after] { + if (!task->canceled) { + after(); + } + })); } + void operator()() override { + // We are only running the task when there's an after callback to call. This means that an + // empty after callback will be treated as a cancelled request. The mutex will be locked while + // processing so that the cancel() callback will block. + std::lock_guard lock(mutex); + if (!canceled) { + invoke(std::index_sequence_for{}); + } + } + + void cancel() override { + // Remove the after callback to indicate that this callback has been canceled. The mutex will + // block when the task is currently in progres. When the task has not begun yet, the runTask() + // method will not do anything. When the task has been completed already, and the after callback + // was run as well, this will also do nothing. + std::lock_guard lock(mutex); + canceled = true; + } + + private: template void invoke(std::index_sequence) { - func(std::forward(std::get(params))...); + func(std::forward(std::get(params))...); + if (callback) { + callback(); + } } + std::mutex mutex; + bool canceled = false; + F func; P params; + C callback; }; - using Queue = std::queue>; + template + class InvokerWithResult : public WorkTask, public std::enable_shared_from_this> { + public: + InvokerWithResult(F&& f, P&& p) + : func(std::move(f)), + params(std::move(p)) { + } + + using C = std::function; + + void bind(C after) { + auto task = this->shared_from_this(); + callback = RunLoop::current.get()->bind(C([task, after] (R result) { + if (!task->canceled) { + after(std::move(result)); + } + })); + } + + void operator()() override { + // We are only running the task when there's an after callback to call. This means that an + // empty after callback will be treated as a cancelled request. The mutex will be locked while + // processing so that the cancel() callback will block. + std::lock_guard lock(mutex); + if (!canceled) { + invoke(std::index_sequence_for{}); + } + } + + void cancel() override { + // Remove the after callback to indicate that this callback has been canceled. The mutex will + // block when the task is currently in progres. When the task has not begun yet, the runTask() + // method will not do anything. When the task has been completed already, and the after callback + // was run as well, this will also do nothing. + std::lock_guard lock(mutex); + canceled = true; + } + + private: + template + void invoke(std::index_sequence) { + callback(func(std::forward(std::get(params))...)); + } + + std::mutex mutex; + bool canceled = false; + + F func; + P params; + C callback; + }; + + using Queue = std::queue>; void withMutex(std::function&&); void process(); diff --git a/src/mbgl/util/thread.hpp b/src/mbgl/util/thread.hpp index 5bd856cbb..16bb81db1 100644 --- a/src/mbgl/util/thread.hpp +++ b/src/mbgl/util/thread.hpp @@ -36,15 +36,17 @@ public: } // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread. - template - void invokeWithResult(Fn fn, std::function callback, Args&&... args) { - loop->invokeWithResult(bind(fn), callback, std::forward(args)...); + template + std::unique_ptr + invokeWithResult(Fn fn, std::function callback, Args&&... args) { + return loop->invokeWithResult(bind(fn), callback, std::forward(args)...); } - // Invoke object->fn(args...) in the runloop thread, then invoke callback() in the current thread. - template - void invokeWithResult(Fn fn, std::function callback, Args&&... args) { - loop->invokeWithResult(bind(fn), callback, std::forward(args)...); + // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread. + template + std::unique_ptr + invokeWithResult(Fn fn, std::function callback, Args&&... args) { + return loop->invokeWithResult(bind(fn), callback, std::forward(args)...); } // Invoke object->fn(args...) in the runloop thread, and wait for the result. diff --git a/src/mbgl/util/work_task.cpp b/src/mbgl/util/work_task.cpp index ebec420de..e69de29bb 100644 --- a/src/mbgl/util/work_task.cpp +++ b/src/mbgl/util/work_task.cpp @@ -1,37 +0,0 @@ -#include - -#include - -namespace mbgl { - -WorkTask::WorkTask(std::function task_, std::function after_) - : task(task_), after(after_) { - assert(after); -} - -void WorkTask::runTask() { - // We are only running the task when there's an after callback to call. This means that an - // empty after callback will be treated as a cancelled request. The mutex will be locked while - // processing so that the cancel() callback will block. - std::lock_guard lock(mutex); - if (after) { - task(); - } -} - -void WorkTask::runAfter() { - if (after) { - after(); - } -} - -void WorkTask::cancel() { - // Remove the after callback to indicate that this callback has been canceled. The mutex will - // block when the task is currently in progres. When the task has not begun yet, the runTask() - // method will not do anything. When the task has been completed already, and the after callback - // was run as well, this will also do nothing. - std::lock_guard lock(mutex); - after = {}; -} - -} // end namespace mbgl diff --git a/src/mbgl/util/work_task.hpp b/src/mbgl/util/work_task.hpp index f730a31c5..2224b211c 100644 --- a/src/mbgl/util/work_task.hpp +++ b/src/mbgl/util/work_task.hpp @@ -3,25 +3,19 @@ #include -#include -#include - namespace mbgl { +// A movable type-erasing function wrapper. This allows to store arbitrary invokable +// things (like std::function<>, or the result of a movable-only std::bind()) in the queue. +// Source: http://stackoverflow.com/a/29642072/331379 class WorkTask : private util::noncopyable { public: - WorkTask(std::function task, std::function after); + virtual ~WorkTask() = default; - void runTask(); - void runAfter(); - void cancel(); - -private: - const std::function task; - std::function after; - std::mutex mutex; + virtual void operator()() = 0; + virtual void cancel() = 0; }; -} // end namespace mbgl +} #endif diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 4c5605734..331fea19b 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -10,41 +10,29 @@ namespace mbgl { class Worker::Impl { public: - Impl(uv_loop_t*) {} + Impl(uv_loop_t*, FileSource* fs) { + // FIXME: Workers should not access the FileSource but it + // is currently needed because of GlyphsPBF. See #1664. + util::ThreadContext::setFileSource(fs); + } - void doWork(std::shared_ptr& task) { - task->runTask(); + void doWork(Fn work) { + work(); } }; Worker::Worker(std::size_t count) { util::ThreadContext context = {"Worker", util::ThreadType::Worker, util::ThreadPriority::Low}; for (std::size_t i = 0; i < count; i++) { - std::unique_ptr> worker(new util::Thread(context)); - - // FIXME: Workers should not access the FileSource but it - // is currently needed because of GlyphsPBF. See #1664. - auto task = std::make_shared([fs = util::ThreadContext::getFileSource()]{ - util::ThreadContext::setFileSource(fs); - }, []{}); - - worker->invoke(&Worker::Impl::doWork, task); - threads.emplace_back(std::move(worker)); + threads.emplace_back(std::make_unique>(context, util::ThreadContext::getFileSource())); } } Worker::~Worker() = default; std::unique_ptr Worker::send(Fn work, Fn after) { - auto task = std::make_shared(work, after); - auto request = std::make_unique(task); - - threads[current]->invokeWithResult(&Worker::Impl::doWork, [task] { - task->runAfter(); - }, task); - current = (current + 1) % threads.size(); - return request; + return threads[current]->invokeWithResult(&Worker::Impl::doWork, after, work); } } // end namespace mbgl diff --git a/test/miscellaneous/thread.cpp b/test/miscellaneous/thread.cpp index f8d91bf97..1206afc14 100644 --- a/test/miscellaneous/thread.cpp +++ b/test/miscellaneous/thread.cpp @@ -67,40 +67,46 @@ TEST(Thread, invoke) { const std::thread::id tid = std::this_thread::get_id(); RunLoop loop(uv_default_loop()); + std::vector> requests; loop.invoke([&] { EXPECT_EQ(tid, std::this_thread::get_id()); Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}, tid); thread.invoke(&TestObject::fn1, 1); - thread.invokeWithResult(&TestObject::fn2, [&] (int result) { + requests.push_back(thread.invokeWithResult(&TestObject::fn2, [&] (int result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(result, 1); - }); + })); thread.invoke(&TestObject::transferIn, std::make_unique(1)); - thread.invokeWithResult>(&TestObject::transferOut, [&] (std::unique_ptr result) { + requests.push_back(thread.invokeWithResult>(&TestObject::transferOut, [&] (std::unique_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); - }); + })); - thread.invokeWithResult>(&TestObject::transferInOut, [&] (std::unique_ptr result) { + requests.push_back(thread.invokeWithResult>(&TestObject::transferInOut, [&] (std::unique_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); - }, std::make_unique(1)); + }, std::make_unique(1))); thread.invoke(&TestObject::transferInShared, std::make_shared(1)); - thread.invokeWithResult>(&TestObject::transferOutShared, [&] (std::shared_ptr result) { + requests.push_back(thread.invokeWithResult>(&TestObject::transferOutShared, [&] (std::shared_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); - }); + })); + + // Cancelled request + thread.invokeWithResult(&TestObject::fn1, [&] { + ADD_FAILURE(); + }, 1); std::string test("test"); - thread.invokeWithResult(&TestObject::transferString, [&] (std::string result){ + requests.push_back(thread.invokeWithResult(&TestObject::transferString, [&] (std::string result){ EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(result, "test"); loop.stop(); - }, test); + }, test)); test.clear(); }); @@ -117,14 +123,15 @@ TEST(Thread, context) { const std::thread::id tid = std::this_thread::get_id(); RunLoop loop(uv_default_loop()); + std::vector> requests; loop.invoke([&] { Thread thread({"Test", ThreadType::Worker, ThreadPriority::Low}, tid); - thread.invokeWithResult(&TestObject::checkContext, [&] (bool inTestThreadContext) { + requests.push_back(thread.invokeWithResult(&TestObject::checkContext, [&] (bool inTestThreadContext) { EXPECT_EQ(inTestThreadContext, true); loop.stop(); - }); + })); }); uv_run(uv_default_loop(), UV_RUN_DEFAULT); From f14e4c67fdea1796f1cce105256f181e54d8473f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 8 Jun 2015 19:19:48 -0400 Subject: [PATCH 168/760] Add comment about a subtlety --- src/mbgl/util/run_loop.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp index 3b753aca8..14021ef42 100644 --- a/src/mbgl/util/run_loop.hpp +++ b/src/mbgl/util/run_loop.hpp @@ -42,6 +42,17 @@ public: std::move(fn), std::move(tuple)); + // `task` is a shared pointer with ownership in the following three places: + // 1. In the `queue` of pending invocations. + // 2. In the `WorkRequest` result. + // 3. In the lambda binding of the callback to be executed on the invoking + // RunLoop. This last shared ownership is necessary in the case where + // callback execution has been scheduled (queued on the invoking RunLoop), + // but the other two places have released ownership -- i.e. the task was + // cancelled after the work is completed, but before the callback is + // executed. In this case, the lambda binding checks the cancellation flag + // and does not execute the original callback. + task->bind(callback); withMutex([&] { queue.push(task); }); From eee02425fa4a38b51067ea8d325340ec699e47fa Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 8 Jun 2015 20:08:48 -0400 Subject: [PATCH 169/760] Single-purpose Worker functions --- src/mbgl/map/live_tile_data.cpp | 43 +++++++--------- src/mbgl/map/raster_tile_data.cpp | 11 +++-- src/mbgl/map/raster_tile_data.hpp | 4 +- src/mbgl/map/vector_tile_data.cpp | 82 ++++++++++++------------------- src/mbgl/map/vector_tile_data.hpp | 14 +++--- src/mbgl/util/worker.cpp | 40 +++++++++++++-- src/mbgl/util/worker.hpp | 34 ++++++++++--- 7 files changed, 126 insertions(+), 102 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index ab6f5d983..a760e33fc 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -24,44 +24,35 @@ LiveTileData::~LiveTileData() { cancel(); } -bool LiveTileData::reparse(Worker&, std::function callback) { - if (parsing.test_and_set(std::memory_order_acquire)) { +bool LiveTileData::reparse(Worker& worker, std::function callback) { + if (parsing || (state != State::loaded && state != State::partial)) { return false; } - workRequest = worker.send([this] { - if (getState() != State::loaded) { + const LiveTile* tile = annotationManager.getTile(id); + if (!tile) { + state = State::parsed; + return true; + } + + parsing = true; + + workRequest = worker.parseLiveTile(workerData, *tile, [this, callback] (TileParseResult result) { + parsing = false; + + if (state == State::obsolete) { return; } - const LiveTile* tile = annotationManager.getTile(id); - - if (!tile) { - state = State::parsed; - return; - } - - TileParseResult result; - - try { - result = workerData.parse(*tile); - } catch (const std::exception& ex) { - std::stringstream message; - message << "Failed to parse [" << int(id.sourceZ) << "/" << id.x << "/" << id.y << "]: " << ex.what(); - result = message.str(); - } - - if (getState() == TileData::State::obsolete) { - return; - } else if (result.is()) { + if (result.is()) { state = result.get(); } else { error = result.get(); state = State::obsolete; } - parsing.clear(std::memory_order_release); - }, callback); + callback(); + }); return true; } diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index ead33dcb0..e5a262603 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -42,19 +42,20 @@ void RasterTileData::request(Worker& worker, } state = State::loaded; - data = res.data; - workRequest = worker.send([this] { - if (getState() != State::loaded) { + workRequest = worker.parseRasterTile(bucket, res.data, [this, callback] (bool result) { + if (state != State::loaded) { return; } - if (bucket.setImage(data)) { + if (result) { state = State::parsed; } else { state = State::invalid; } - }, callback); + + callback(); + }); }); } diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index 195698865..e519874c0 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -30,9 +30,7 @@ public: private: const SourceInfo& source; - - Request *req = nullptr; - std::string data; + Request* req = nullptr; StyleLayoutRaster layout; RasterBucket bucket; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 171fc7cf7..be2640849 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -2,9 +2,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -20,15 +18,15 @@ VectorTileData::VectorTileData(const TileID& id_, float angle, bool collisionDebug) : TileData(id_), - source(source_), - worker(style_.workers), workerData(id_, style_, - source.max_zoom, + source_.max_zoom, state, std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, angle, collisionDebug)), + source(source_), + worker(style_.workers), lastAngle(angle), currentAngle(angle) { } @@ -37,9 +35,7 @@ VectorTileData::~VectorTileData() { cancel(); } -void VectorTileData::request(Worker&, - float pixelRatio, - const std::function& callback) { +void VectorTileData::request(Worker&, float pixelRatio, const std::function& callback) { std::string url = source.tileURL(id, pixelRatio); state = State::loading; @@ -64,37 +60,28 @@ void VectorTileData::request(Worker&, } bool VectorTileData::reparse(Worker&, std::function callback) { - if (parsing.test_and_set(std::memory_order_acquire)) { + if (parsing || (state != State::loaded && state != State::partial)) { return false; } - workRequest = worker.send([this] { - if (getState() != TileData::State::loaded && getState() != TileData::State::partial) { + parsing = true; + + workRequest = worker.parseVectorTile(workerData, data, [this, callback] (TileParseResult result) { + parsing = false; + + if (state == State::obsolete) { return; } - TileParseResult result; - - try { - VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); - result = workerData.parse(vectorTile); - } catch (const std::exception& ex) { - std::stringstream message; - message << "Failed to parse [" << int(id.sourceZ) << "/" << id.x << "/" << id.y << "]: " << ex.what(); - result = message.str(); - } - - if (getState() == TileData::State::obsolete) { - return; - } else if (result.is()) { + if (result.is()) { state = result.get(); } else { error = result.get(); state = State::obsolete; } - parsing.clear(std::memory_order_release); - }, callback); + callback(); + }); return true; } @@ -111,35 +98,30 @@ size_t VectorTileData::countBuckets() const { return workerData.countBuckets(); } -void VectorTileData::redoPlacement() { - redoPlacement(lastAngle, lastCollisionDebug); -} - void VectorTileData::redoPlacement(float angle, bool collisionDebug) { - if (angle != currentAngle || collisionDebug != currentCollisionDebug) { - lastAngle = angle; - lastCollisionDebug = collisionDebug; + if (angle == currentAngle && collisionDebug == currentCollisionDebug) + return; - if (getState() != State::parsed || redoingPlacement) return; + lastAngle = angle; + lastCollisionDebug = collisionDebug; - redoingPlacement = true; - currentAngle = angle; - currentCollisionDebug = collisionDebug; + if (state != State::parsed || redoingPlacement) + return; - auto callback = std::bind(&VectorTileData::endRedoPlacement, this); - workRequest = worker.send([this, angle, collisionDebug] { workerData.redoPlacement(angle, collisionDebug); }, callback); - } -} + redoingPlacement = true; + currentAngle = angle; + currentCollisionDebug = collisionDebug; -void VectorTileData::endRedoPlacement() { - for (const auto& layer_desc : workerData.style.layers) { - auto bucket = getBucket(*layer_desc); - if (bucket) { - bucket->swapRenderData(); + workRequest = worker.redoPlacement(workerData, angle, collisionDebug, [this] { + for (const auto& layer : workerData.style.layers) { + auto bucket = getBucket(*layer); + if (bucket) { + bucket->swapRenderData(); + } } - } - redoingPlacement = false; - redoPlacement(); + redoingPlacement = false; + redoPlacement(lastAngle, lastCollisionDebug); + }); } void VectorTileData::cancel() { diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 4157a19d9..e12554d85 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -39,23 +39,21 @@ public: void cancel() override; protected: - void redoPlacement(); - - const SourceInfo& source; - Request *req = nullptr; - std::string data; - Worker& worker; TileWorker workerData; std::unique_ptr workRequest; - std::atomic_flag parsing = ATOMIC_FLAG_INIT; + bool parsing = false; private: + const SourceInfo& source; + Request* req = nullptr; + std::string data; + Worker& worker; + float lastAngle = 0; float currentAngle; bool lastCollisionDebug = 0; bool currentCollisionDebug = 0; bool redoingPlacement = false; - void endRedoPlacement(); }; } diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 331fea19b..74408a7da 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -2,6 +2,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -16,8 +20,21 @@ public: util::ThreadContext::setFileSource(fs); } - void doWork(Fn work) { - work(); + bool parseRasterTile(RasterBucket* bucket, std::string data) { + return bucket->setImage(data); + } + + TileParseResult parseVectorTile(TileWorker* worker, std::string data) { + VectorTile vectorTile(pbf(reinterpret_cast(data.data()), data.size())); + return worker->parse(vectorTile); + } + + TileParseResult parseLiveTile(TileWorker* worker, const LiveTile* tile) { + return worker->parse(*tile); + } + + void redoPlacement(TileWorker* worker, float angle, bool collisionDebug) { + worker->redoPlacement(angle, collisionDebug); } }; @@ -30,9 +47,24 @@ Worker::Worker(std::size_t count) { Worker::~Worker() = default; -std::unique_ptr Worker::send(Fn work, Fn after) { +std::unique_ptr Worker::parseRasterTile(RasterBucket& bucket, std::string data, std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithResult(&Worker::Impl::doWork, after, work); + return threads[current]->invokeWithResult(&Worker::Impl::parseRasterTile, callback, &bucket, data); +} + +std::unique_ptr Worker::parseVectorTile(TileWorker& worker, std::string data, std::function callback) { + current = (current + 1) % threads.size(); + return threads[current]->invokeWithResult(&Worker::Impl::parseVectorTile, callback, &worker, data); +} + +std::unique_ptr Worker::parseLiveTile(TileWorker& worker, const LiveTile& tile, std::function callback) { + current = (current + 1) % threads.size(); + return threads[current]->invokeWithResult(&Worker::Impl::parseLiveTile, callback, &worker, &tile); +} + +std::unique_ptr Worker::redoPlacement(TileWorker& worker, float angle, bool collisionDebug, std::function callback) { + current = (current + 1) % threads.size(); + return threads[current]->invokeWithResult(&Worker::Impl::redoPlacement, callback, &worker, angle, collisionDebug); } } // end namespace mbgl diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 0c001e419..1c302d585 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,17 +11,16 @@ namespace mbgl { class WorkRequest; +class RasterBucket; +class LiveTile; class Worker : public mbgl::util::noncopyable { public: - using Fn = std::function; - Worker(std::size_t count); ~Worker(); - // Request work be done on a thread pool. The optional after callback is - // executed on the invoking thread, which must have a run loop, after the - // work is complete. + // Request work be done on a thread pool. Callbacks are executed on the invoking + // thread, which must have a run loop, after the work is complete. // // The return value represents the request to perform the work asynchronously. // Its destructor guarantees that the work function has finished executing, and @@ -28,7 +28,29 @@ public: // Together, this means that an object may make a work request with lambdas which // bind references to itself, and if and when those lambdas execute, the references // will still be valid. - std::unique_ptr send(Fn work, Fn after); + + using Request = std::unique_ptr; + + Request parseRasterTile( + RasterBucket&, + std::string data, + std::function callback); + + Request parseVectorTile( + TileWorker&, + std::string data, + std::function callback); + + Request parseLiveTile( + TileWorker&, + const LiveTile&, + std::function callback); + + Request redoPlacement( + TileWorker&, + float angle, + bool collisionDebug, + std::function callback); private: class Impl; From 3670a78569bd9f7f92b768a9c761b4d8d4b278e3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 9 Jun 2015 10:45:51 -0400 Subject: [PATCH 170/760] Convert Worker tests to Thread tests --- test/miscellaneous/thread.cpp | 83 +++++++++++++++++++++++++++ test/miscellaneous/worker.cpp | 104 ---------------------------------- test/test.gypi | 1 - 3 files changed, 83 insertions(+), 105 deletions(-) delete mode 100644 test/miscellaneous/worker.cpp diff --git a/test/miscellaneous/thread.cpp b/test/miscellaneous/thread.cpp index 1206afc14..68f8ddccd 100644 --- a/test/miscellaneous/thread.cpp +++ b/test/miscellaneous/thread.cpp @@ -136,3 +136,86 @@ TEST(Thread, context) { uv_run(uv_default_loop(), UV_RUN_DEFAULT); } + +class TestWorker { +public: + TestWorker(uv_loop_t*) {} + void send(std::function fn) { fn(); } +}; + +TEST(Thread, ExecutesAfter) { + RunLoop loop(uv_default_loop()); + Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}); + + bool didWork = false; + bool didAfter = false; + + auto request = thread.invokeWithResult(&TestWorker::send, [&] { + didAfter = true; + loop.stop(); + }, [&] { + didWork = true; + }); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); + + EXPECT_TRUE(didWork); + EXPECT_TRUE(didAfter); +} + +TEST(Thread, WorkRequestDeletionWaitsForWorkToComplete) { + RunLoop loop(uv_default_loop()); + Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}); + + std::promise started; + bool didWork = false; + + auto request = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + started.set_value(); + usleep(10000); + didWork = true; + }); + + started.get_future().get(); + request.reset(); + EXPECT_TRUE(didWork); +} + +TEST(Thread, WorkRequestDeletionCancelsAfter) { + RunLoop loop(uv_default_loop()); + Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}); + + std::promise started; + bool didAfter = false; + + auto request = thread.invokeWithResult(&TestWorker::send, [&] { + didAfter = true; + }, [&] { + started.set_value(); + }); + + started.get_future().get(); + request.reset(); + uv_run(uv_default_loop(), UV_RUN_ONCE); + EXPECT_FALSE(didAfter); +} + +TEST(Thread, WorkRequestDeletionCancelsImmediately) { + RunLoop loop(uv_default_loop()); + Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}); + + std::promise started; + + auto request1 = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + usleep(10000); + started.set_value(); + }); + + auto request2 = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + ADD_FAILURE() << "Second work item should not be invoked"; + }); + request2.reset(); + + started.get_future().get(); + request1.reset(); +} diff --git a/test/miscellaneous/worker.cpp b/test/miscellaneous/worker.cpp deleted file mode 100644 index 3d3c781b8..000000000 --- a/test/miscellaneous/worker.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "../fixtures/util.hpp" - -#include -#include -#include - -using namespace mbgl; -using namespace mbgl::util; - -TEST(Worker, ExecutesWorkAndAfter) { - RunLoop loop(uv_default_loop()); - - Worker worker(1); - std::unique_ptr request; - - bool didWork = false; - bool didAfter = false; - - loop.invoke([&] { - request = worker.send([&] { - didWork = true; - }, [&] { - didAfter = true; - loop.stop(); - }); - }); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); - EXPECT_TRUE(didWork); - EXPECT_TRUE(didAfter); -} - -TEST(Worker, WorkRequestDeletionWaitsForWorkToComplete) { - RunLoop loop(uv_default_loop()); - - Worker worker(1); - std::promise started; - bool didWork = false; - - loop.invoke([&] { - auto request = worker.send([&] { - started.set_value(); - usleep(10000); - didWork = true; - }, [&] {}); - started.get_future().get(); - - request.reset(); - EXPECT_TRUE(didWork); - loop.stop(); - }); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); -} - -TEST(Worker, WorkRequestJoinCancelsAfter) { - RunLoop loop(uv_default_loop()); - - Worker worker(1); - std::promise started; - bool didAfter = false; - - loop.invoke([&] { - auto request = worker.send([&] { - started.set_value(); - }, [&] { - didAfter = true; - }); - started.get_future().get(); - - request.reset(); - loop.stop(); - }); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); - EXPECT_FALSE(didAfter); -} - -TEST(Worker, WorkRequestCancelsImmediately) { - RunLoop loop(uv_default_loop()); - - Worker worker(1); - - loop.invoke([&] { - std::promise started; - // First worker item. - auto request1 = worker.send([&] { - usleep(10000); - started.set_value(); - }, [&] {}); - - auto request2 = worker.send([&] { - ADD_FAILURE() << "Second work item should not be invoked"; - }, [&] {}); - request2.reset(); - - started.get_future().get(); - request1.reset(); - - loop.stop(); - }); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); -} diff --git a/test/test.gypi b/test/test.gypi index f3d6976dd..ddbc53245 100644 --- a/test/test.gypi +++ b/test/test.gypi @@ -59,7 +59,6 @@ 'miscellaneous/tile.cpp', 'miscellaneous/transform.cpp', 'miscellaneous/variant.cpp', - 'miscellaneous/worker.cpp', 'storage/storage.hpp', 'storage/storage.cpp', From d30fce5f7dbc42b46e020a9d7b45347473097d6d Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 10 Jun 2015 21:50:35 +0300 Subject: [PATCH 171/760] Catch exceptions when parsing tile PBFs Also improved the error message and updated the test case. --- src/mbgl/map/vector_tile_data.cpp | 4 +++- src/mbgl/util/worker.cpp | 8 ++++++-- test/style/resource_loading.cpp | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index be2640849..bf4c0f581 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -76,7 +76,9 @@ bool VectorTileData::reparse(Worker&, std::function callback) { if (result.is()) { state = result.get(); } else { - error = result.get(); + std::stringstream message; + message << "Failed to parse [" << std::string(id) << "]: " << result.get(); + error = message.str(); state = State::obsolete; } diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 74408a7da..0d83bc49f 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -25,8 +25,12 @@ public: } TileParseResult parseVectorTile(TileWorker* worker, std::string data) { - VectorTile vectorTile(pbf(reinterpret_cast(data.data()), data.size())); - return worker->parse(vectorTile); + try { + pbf tilePBF(reinterpret_cast(data.data()), data.size()); + return worker->parse(VectorTile(tilePBF)); + } catch (const std::exception& ex) { + return TileParseResult(ex.what()); + } } TileParseResult parseLiveTile(TileWorker* worker, const LiveTile* tile) { diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index 92d479b51..c37e17d71 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -159,7 +159,7 @@ TEST_P(ResourceLoading, RequestWithCorruptedData) { message << "Failed to parse "; if (param == "vector.pbf") { - message << "\\[15\\/1638(3|4)\\/1638(3|4)\\]\\: pbf unknown field type exception"; + message << "\\[1(5|6)\\/1638(3|4)\\/1638(3|4)\\]\\: pbf unknown field type exception"; } else { message << "\\[test\\/fixtures\\/resources\\/" << param << "\\]"; } From abdf205510b1f2d7b3e0c9b432f17c8c4d71e895 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 8 Jun 2015 20:16:40 -0400 Subject: [PATCH 172/760] =?UTF-8?q?Rename=20workerData=20=E2=87=A2=20tileW?= =?UTF-8?q?orker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mbgl/map/live_tile_data.cpp | 2 +- src/mbgl/map/vector_tile_data.cpp | 12 ++++++------ src/mbgl/map/vector_tile_data.hpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index a760e33fc..22f9a54b4 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -37,7 +37,7 @@ bool LiveTileData::reparse(Worker& worker, std::function callback) { parsing = true; - workRequest = worker.parseLiveTile(workerData, *tile, [this, callback] (TileParseResult result) { + workRequest = worker.parseLiveTile(tileWorker, *tile, [this, callback] (TileParseResult result) { parsing = false; if (state == State::obsolete) { diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index bf4c0f581..d0f4b131f 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -18,7 +18,7 @@ VectorTileData::VectorTileData(const TileID& id_, float angle, bool collisionDebug) : TileData(id_), - workerData(id_, + tileWorker(id_, style_, source_.max_zoom, state, @@ -66,7 +66,7 @@ bool VectorTileData::reparse(Worker&, std::function callback) { parsing = true; - workRequest = worker.parseVectorTile(workerData, data, [this, callback] (TileParseResult result) { + workRequest = worker.parseVectorTile(tileWorker, data, [this, callback] (TileParseResult result) { parsing = false; if (state == State::obsolete) { @@ -93,11 +93,11 @@ Bucket* VectorTileData::getBucket(const StyleLayer& layer) { return nullptr; } - return workerData.getBucket(layer); + return tileWorker.getBucket(layer); } size_t VectorTileData::countBuckets() const { - return workerData.countBuckets(); + return tileWorker.countBuckets(); } void VectorTileData::redoPlacement(float angle, bool collisionDebug) { @@ -114,8 +114,8 @@ void VectorTileData::redoPlacement(float angle, bool collisionDebug) { currentAngle = angle; currentCollisionDebug = collisionDebug; - workRequest = worker.redoPlacement(workerData, angle, collisionDebug, [this] { - for (const auto& layer : workerData.style.layers) { + workRequest = worker.redoPlacement(tileWorker, angle, collisionDebug, [this] { + for (const auto& layer : tileWorker.style.layers) { auto bucket = getBucket(*layer); if (bucket) { bucket->swapRenderData(); diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index e12554d85..6b866de37 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -39,7 +39,7 @@ public: void cancel() override; protected: - TileWorker workerData; + TileWorker tileWorker; std::unique_ptr workRequest; bool parsing = false; From db1202e8e335be540ded48029216ad9884fc5bb9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 9 Jun 2015 10:08:20 -0400 Subject: [PATCH 173/760] Don't pass Worker& around so much --- src/mbgl/map/live_tile_data.cpp | 2 +- src/mbgl/map/live_tile_data.hpp | 2 +- src/mbgl/map/raster_tile_data.cpp | 13 +++++++------ src/mbgl/map/raster_tile_data.hpp | 8 ++++---- src/mbgl/map/source.cpp | 13 ++++++------- src/mbgl/map/tile_data.hpp | 6 ++---- src/mbgl/map/vector_tile_data.cpp | 8 ++++---- src/mbgl/map/vector_tile_data.hpp | 11 +++++------ 8 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 22f9a54b4..ad28c6c62 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -24,7 +24,7 @@ LiveTileData::~LiveTileData() { cancel(); } -bool LiveTileData::reparse(Worker& worker, std::function callback) { +bool LiveTileData::reparse(std::function callback) { if (parsing || (state != State::loaded && state != State::partial)) { return false; } diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index c837057e8..2d6b01061 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -17,7 +17,7 @@ public: bool collisionDebug_); ~LiveTileData(); - bool reparse(Worker&, std::function callback) override; + bool reparse(std::function callback) override; private: AnnotationManager& annotationManager; diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index e5a262603..837f4c0f1 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -12,9 +12,11 @@ using namespace mbgl; RasterTileData::RasterTileData(const TileID& id_, TexturePool &texturePool, - const SourceInfo &source_) + const SourceInfo &source_, + Worker& worker_) : TileData(id_), source(source_), + worker(worker_), bucket(texturePool, layout) { } @@ -22,14 +24,13 @@ RasterTileData::~RasterTileData() { cancel(); } -void RasterTileData::request(Worker& worker, - float pixelRatio, - const std::function& callback) { +void RasterTileData::request(float pixelRatio, + const std::function& callback) { std::string url = source.tileURL(id, pixelRatio); state = State::loading; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, &worker, this](const Response &res) { + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, this](const Response &res) { req = nullptr; if (res.status != Response::Successful) { @@ -59,7 +60,7 @@ void RasterTileData::request(Worker& worker, }); } -bool RasterTileData::reparse(Worker&, std::function) { +bool RasterTileData::reparse(std::function) { assert(false); return false; } diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index e519874c0..ce0286f5a 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -15,14 +15,13 @@ class WorkRequest; class RasterTileData : public TileData { public: - RasterTileData(const TileID&, TexturePool&, const SourceInfo&); + RasterTileData(const TileID&, TexturePool&, const SourceInfo&, Worker&); ~RasterTileData(); - void request(Worker&, - float pixelRatio, + void request(float pixelRatio, const std::function& callback) override; - bool reparse(Worker&, std::function callback) override; + bool reparse(std::function callback) override; void cancel() override; @@ -30,6 +29,7 @@ public: private: const SourceInfo& source; + Worker& worker; Request* req = nullptr; StyleLayoutRaster layout; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 0bac4c3c7..55761eb26 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -225,7 +225,7 @@ TileData::State Source::hasTile(const TileID& id) { return TileData::State::invalid; } -bool Source::handlePartialTile(const TileID& id, Worker& worker) { +bool Source::handlePartialTile(const TileID& id, Worker&) { const TileID normalized_id = id.normalized(); auto it = tile_data.find(normalized_id); @@ -248,7 +248,7 @@ bool Source::handlePartialTile(const TileID& id, Worker& worker) { } }; - return data->reparse(worker, callback); + return data->reparse(callback); } TileData::State Source::addTile(MapData& data, @@ -293,16 +293,15 @@ TileData::State Source::addTile(MapData& data, new_tile.data = std::make_shared(normalized_id, style, info, transformState.getAngle(), data.getCollisionDebug()); - new_tile.data->request(style.workers, transformState.getPixelRatio(), callback); + new_tile.data->request(transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Raster) { - new_tile.data = std::make_shared(normalized_id, texturePool, info); - new_tile.data->request( - style.workers, transformState.getPixelRatio(), callback); + new_tile.data = std::make_shared(normalized_id, texturePool, info, style.workers); + new_tile.data->request(transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Annotations) { new_tile.data = std::make_shared(normalized_id, data.annotationManager, style, info, transformState.getAngle(), data.getCollisionDebug()); - new_tile.data->reparse(style.workers, callback); + new_tile.data->reparse(callback); } else { throw std::runtime_error("source type not implemented"); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 5ed4ef4fd..552525695 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -70,16 +70,14 @@ public: TileData(const TileID&); virtual ~TileData() = default; - virtual void request(Worker&, - float pixelRatio, + virtual void request(float pixelRatio, const std::function& callback) = 0; // Schedule a tile reparse on a worker thread and call the callback on // completion. It will return true if the work was schedule or false it was // not, which can occur if the tile is already being parsed by another // worker. - virtual bool reparse(Worker&, - std::function callback) = 0; + virtual bool reparse(std::function callback) = 0; // Mark this tile as no longer needed and cancel any pending work. virtual void cancel() = 0; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index d0f4b131f..9b04ab371 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -18,6 +18,7 @@ VectorTileData::VectorTileData(const TileID& id_, float angle, bool collisionDebug) : TileData(id_), + worker(style_.workers), tileWorker(id_, style_, source_.max_zoom, @@ -26,7 +27,6 @@ VectorTileData::VectorTileData(const TileID& id_, source_.tile_size * id.overscaling, angle, collisionDebug)), source(source_), - worker(style_.workers), lastAngle(angle), currentAngle(angle) { } @@ -35,7 +35,7 @@ VectorTileData::~VectorTileData() { cancel(); } -void VectorTileData::request(Worker&, float pixelRatio, const std::function& callback) { +void VectorTileData::request(float pixelRatio, const std::function& callback) { std::string url = source.tileURL(id, pixelRatio); state = State::loading; @@ -55,11 +55,11 @@ void VectorTileData::request(Worker&, float pixelRatio, const std::function callback) { +bool VectorTileData::reparse(std::function callback) { if (parsing || (state != State::loaded && state != State::partial)) { return false; } diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 6b866de37..a035015f4 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -25,20 +25,20 @@ public: bool collisionDebug_); ~VectorTileData(); - void redoPlacement(float angle, bool collisionDebug) override; - Bucket* getBucket(const StyleLayer&) override; size_t countBuckets() const; - void request(Worker&, - float pixelRatio, + void request(float pixelRatio, const std::function& callback) override; - bool reparse(Worker&, std::function callback) override; + bool reparse(std::function callback) override; + + void redoPlacement(float angle, bool collisionDebug) override; void cancel() override; protected: + Worker& worker; TileWorker tileWorker; std::unique_ptr workRequest; bool parsing = false; @@ -47,7 +47,6 @@ private: const SourceInfo& source; Request* req = nullptr; std::string data; - Worker& worker; float lastAngle = 0; float currentAngle; From 3ab682e627fa70d926908632ffbdb53694adc0d5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Tue, 9 Jun 2015 11:37:00 -0400 Subject: [PATCH 174/760] Fix name shadowing --- src/mbgl/text/collision_tile.cpp | 4 ++-- src/mbgl/text/collision_tile.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp index 0d1b25af5..3c2d22a5b 100644 --- a/src/mbgl/text/collision_tile.cpp +++ b/src/mbgl/text/collision_tile.cpp @@ -91,11 +91,11 @@ void CollisionTile::insertFeature(CollisionFeature &feature, const float minPlac Box CollisionTile::getTreeBox(const vec2 &anchor, const CollisionBox &box) { return Box{ - Point{ + CollisionPoint{ anchor.x + box.x1, anchor.y + box.y1 * yStretch }, - Point{ + CollisionPoint{ anchor.x + box.x2, anchor.y + box.y2 * yStretch } diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp index 882f347b4..aed9b59f3 100644 --- a/src/mbgl/text/collision_tile.hpp +++ b/src/mbgl/text/collision_tile.hpp @@ -27,8 +27,8 @@ namespace mbgl { namespace bg = boost::geometry; namespace bgm = bg::model; namespace bgi = bg::index; - typedef bgm::point Point; - typedef bgm::box Box; + typedef bgm::point CollisionPoint; + typedef bgm::box Box; typedef std::pair CollisionTreeBox; typedef bgi::rtree> Tree; From fc64e97bce3e082b0396e68bd6493457defe4ec7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 11 Jun 2015 19:03:19 -0400 Subject: [PATCH 175/760] Simplify RunLoop Instead of transforming between return value and callback, pass the wrapped callback on to the invoked function as the last argument. This eliminates the need for multiple invokeWithArgument overloads and permits invoked functions to be asynchronous. --- platform/default/sqlite_cache.cpp | 10 +- platform/default/sqlite_cache_impl.hpp | 2 +- src/mbgl/map/source.cpp | 14 +-- src/mbgl/util/run_loop.hpp | 165 ++++++------------------- src/mbgl/util/thread.hpp | 21 ++-- src/mbgl/util/worker.cpp | 29 +++-- test/miscellaneous/thread.cpp | 61 ++++----- test/storage/database.cpp | 63 ++++++---- 8 files changed, 149 insertions(+), 216 deletions(-) diff --git a/platform/default/sqlite_cache.cpp b/platform/default/sqlite_cache.cpp index 3c23d167a..c1b1daf23 100644 --- a/platform/default/sqlite_cache.cpp +++ b/platform/default/sqlite_cache.cpp @@ -131,10 +131,10 @@ std::unique_ptr SQLiteCache::get(const Resource &resource, Callback // Will try to load the URL from the SQLite database and call the callback when done. // Note that the callback is probably going to invoked from another thread, so the caller // must make sure that it can run in that thread. - return thread->invokeWithResult(&Impl::get, std::move(callback), resource); + return thread->invokeWithCallback(&Impl::get, callback, resource); } -std::unique_ptr SQLiteCache::Impl::get(const Resource &resource) { +void SQLiteCache::Impl::get(const Resource &resource, Callback callback) { try { // This is called in the SQLite event loop. if (!db) { @@ -167,14 +167,14 @@ std::unique_ptr SQLiteCache::Impl::get(const Resource &resource) { if (getStmt->get(5)) { // == compressed response->data = util::decompress(response->data); } - return std::move(response); + callback(std::move(response)); } else { // There is no data. - return nullptr; + callback(nullptr); } } catch (mapbox::sqlite::Exception& ex) { Log::Error(Event::Database, ex.code, ex.what()); - return nullptr; + callback(nullptr); } } diff --git a/platform/default/sqlite_cache_impl.hpp b/platform/default/sqlite_cache_impl.hpp index e01400a58..86c7c07bd 100644 --- a/platform/default/sqlite_cache_impl.hpp +++ b/platform/default/sqlite_cache_impl.hpp @@ -19,7 +19,7 @@ public: Impl(uv_loop_t*, const std::string &path = ":memory:"); ~Impl(); - std::unique_ptr get(const Resource&); + void get(const Resource&, Callback); void put(const Resource& resource, std::shared_ptr response); void refresh(const Resource& resource, int64_t expires); diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 55761eb26..f468ab33e 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -233,7 +233,9 @@ bool Source::handlePartialTile(const TileID& id, Worker&) { return true; } - util::ptr data = it->second.lock(); + // Note: this uses a raw pointer; we don't want the callback binding to have a + // shared pointer. + VectorTileData* data = static_cast(it->second.lock().get()); if (!data) { return true; } @@ -241,14 +243,12 @@ bool Source::handlePartialTile(const TileID& id, Worker&) { // The signal is only emitted if there was an actual change on the tile. The // tile can be in a "partial" state waiting for resources and get reparsed on // the arrival of new resources that were needed by another tile. - size_t bucketCount = static_cast(data.get())->countBuckets(); - auto callback = [this, data, bucketCount]() { - if (static_cast(data.get())->countBuckets() > bucketCount) { + size_t bucketCount = data->countBuckets(); + return data->reparse([this, data, bucketCount]() { + if (data->countBuckets() > bucketCount) { emitTileLoaded(false); } - }; - - return data->reparse(callback); + }); } TileData::State Source::addTile(MapData& data, diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp index 14021ef42..c0a56e956 100644 --- a/src/mbgl/util/run_loop.hpp +++ b/src/mbgl/util/run_loop.hpp @@ -25,7 +25,7 @@ public: template void invoke(Fn&& fn, Args&&... args) { auto tuple = std::make_tuple(std::move(args)...); - auto task = std::make_shared>( + auto task = std::make_shared>( std::move(fn), std::move(tuple)); @@ -33,44 +33,24 @@ public: async.send(); } - // Invoke fn(args...) on this RunLoop, then invoke callback() on the current RunLoop. - template + // Invoke fn(args...) on this RunLoop, then invoke callback(results...) on the current RunLoop. + template std::unique_ptr - invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { - auto tuple = std::make_tuple(std::move(args)...); - auto task = std::make_shared>( + invokeWithCallback(Fn&& fn, Cb&& callback, Args&&... args) { + auto flag = std::make_shared(); + *flag = false; + + auto after = RunLoop::current.get()->bind([flag, callback] (auto&&... results) { + if (!*flag) { + callback(std::move(results)...); + } + }); + + auto tuple = std::make_tuple(std::move(args)..., after); + auto task = std::make_shared>( std::move(fn), - std::move(tuple)); - - // `task` is a shared pointer with ownership in the following three places: - // 1. In the `queue` of pending invocations. - // 2. In the `WorkRequest` result. - // 3. In the lambda binding of the callback to be executed on the invoking - // RunLoop. This last shared ownership is necessary in the case where - // callback execution has been scheduled (queued on the invoking RunLoop), - // but the other two places have released ownership -- i.e. the task was - // cancelled after the work is completed, but before the callback is - // executed. In this case, the lambda binding checks the cancellation flag - // and does not execute the original callback. - - task->bind(callback); - - withMutex([&] { queue.push(task); }); - async.send(); - - return std::make_unique(task); - } - - // Invoke fn(args...) on this RunLoop, then invoke callback(result) on the current RunLoop. - template - std::unique_ptr - invokeWithResult(Fn&& fn, std::function callback, Args&&... args) { - auto tuple = std::make_tuple(std::move(args)...); - auto task = std::make_shared>( - std::move(fn), - std::move(tuple)); - - task->bind(callback); + std::move(tuple), + flag); withMutex([&] { queue.push(task); }); async.send(); @@ -79,10 +59,11 @@ public: } // Return a function that invokes the given function on this RunLoop. - template - auto bind(std::function fn) { - return [this, fn = std::move(fn)] (Args&&... args) { - invoke(std::move(fn), std::move(args)...); + template + auto bind(Fn&& fn) { + return [this, fn = std::move(fn)] (auto&&... args) { + // `this->` is a workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636 + this->invoke(std::move(fn), std::move(args)...); }; } @@ -91,111 +72,47 @@ public: static uv::tls current; private: - template - class Invoker : public WorkTask, public std::enable_shared_from_this> { + template + class Invoker : public WorkTask { public: - Invoker(F&& f, P&& p) - : func(std::move(f)), + Invoker(F&& f, P&& p, std::shared_ptr canceled_ = nullptr) + : canceled(canceled_), + func(std::move(f)), params(std::move(p)) { } - using C = std::function; - - void bind(C after) { - auto task = this->shared_from_this(); - callback = RunLoop::current.get()->bind(C([task, after] { - if (!task->canceled) { - after(); - } - })); - } - void operator()() override { - // We are only running the task when there's an after callback to call. This means that an - // empty after callback will be treated as a cancelled request. The mutex will be locked while - // processing so that the cancel() callback will block. + // Lock the mutex while processing so that cancel() will block. std::lock_guard lock(mutex); - if (!canceled) { - invoke(std::index_sequence_for{}); + if (!canceled || !*canceled) { + invoke(std::make_index_sequence::value>{}); } } + // If the task has not yet begun, this will cancel it. + // If the task is in progress, this will block until it completed. (Currently + // necessary because of shared state, but should be removed.) It will also + // cancel the after callback. + // If the task has completed, but the after callback has not executed, this + // will cancel the after callback. + // If the task has completed and the after callback has executed, this will + // do nothing. void cancel() override { - // Remove the after callback to indicate that this callback has been canceled. The mutex will - // block when the task is currently in progres. When the task has not begun yet, the runTask() - // method will not do anything. When the task has been completed already, and the after callback - // was run as well, this will also do nothing. std::lock_guard lock(mutex); - canceled = true; + *canceled = true; } private: template void invoke(std::index_sequence) { - func(std::forward(std::get(params))...); - if (callback) { - callback(); - } + func(std::get(std::forward

(params))...); } std::mutex mutex; - bool canceled = false; + std::shared_ptr canceled; F func; P params; - C callback; - }; - - template - class InvokerWithResult : public WorkTask, public std::enable_shared_from_this> { - public: - InvokerWithResult(F&& f, P&& p) - : func(std::move(f)), - params(std::move(p)) { - } - - using C = std::function; - - void bind(C after) { - auto task = this->shared_from_this(); - callback = RunLoop::current.get()->bind(C([task, after] (R result) { - if (!task->canceled) { - after(std::move(result)); - } - })); - } - - void operator()() override { - // We are only running the task when there's an after callback to call. This means that an - // empty after callback will be treated as a cancelled request. The mutex will be locked while - // processing so that the cancel() callback will block. - std::lock_guard lock(mutex); - if (!canceled) { - invoke(std::index_sequence_for{}); - } - } - - void cancel() override { - // Remove the after callback to indicate that this callback has been canceled. The mutex will - // block when the task is currently in progres. When the task has not begun yet, the runTask() - // method will not do anything. When the task has been completed already, and the after callback - // was run as well, this will also do nothing. - std::lock_guard lock(mutex); - canceled = true; - } - - private: - template - void invoke(std::index_sequence) { - callback(func(std::forward(std::get(params))...)); - } - - std::mutex mutex; - bool canceled = false; - - F func; - P params; - C callback; }; using Queue = std::queue>; diff --git a/src/mbgl/util/thread.hpp b/src/mbgl/util/thread.hpp index 16bb81db1..88a709cfa 100644 --- a/src/mbgl/util/thread.hpp +++ b/src/mbgl/util/thread.hpp @@ -32,21 +32,14 @@ public: // Invoke object->fn(args...) in the runloop thread. template void invoke(Fn fn, Args&&... args) { - loop->invoke(bind(fn), std::forward(args)...); + loop->invoke(bind(fn), std::forward(args)...); } // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread. - template + template std::unique_ptr - invokeWithResult(Fn fn, std::function callback, Args&&... args) { - return loop->invokeWithResult(bind(fn), callback, std::forward(args)...); - } - - // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread. - template - std::unique_ptr - invokeWithResult(Fn fn, std::function callback, Args&&... args) { - return loop->invokeWithResult(bind(fn), callback, std::forward(args)...); + invokeWithCallback(Fn fn, Cb&& callback, Args&&... args) { + return loop->invokeWithCallback(bind(fn), callback, std::forward(args)...); } // Invoke object->fn(args...) in the runloop thread, and wait for the result. @@ -73,9 +66,11 @@ private: Thread& operator=(const Thread&) = delete; Thread& operator=(Thread&&) = delete; - template + template auto bind(Fn fn) { - return [fn, this] (Args&&... a) { return (object->*fn)(std::forward(a)...); }; + return [fn, this] (auto &&... args) { + return (object->*fn)(std::forward(args)...); + }; } template diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 0d83bc49f..cc9fa1566 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -20,25 +20,30 @@ public: util::ThreadContext::setFileSource(fs); } - bool parseRasterTile(RasterBucket* bucket, std::string data) { - return bucket->setImage(data); + void parseRasterTile(RasterBucket* bucket, std::string data, std::function callback) { + callback(bucket->setImage(data)); } - TileParseResult parseVectorTile(TileWorker* worker, std::string data) { + void parseVectorTile(TileWorker* worker, std::string data, std::function callback) { try { pbf tilePBF(reinterpret_cast(data.data()), data.size()); - return worker->parse(VectorTile(tilePBF)); + callback(worker->parse(VectorTile(tilePBF))); } catch (const std::exception& ex) { - return TileParseResult(ex.what()); + callback(TileParseResult(ex.what())); } } - TileParseResult parseLiveTile(TileWorker* worker, const LiveTile* tile) { - return worker->parse(*tile); + void parseLiveTile(TileWorker* worker, const LiveTile* tile, std::function callback) { + try { + callback(worker->parse(*tile)); + } catch (const std::exception& ex) { + callback(TileParseResult(ex.what())); + } } - void redoPlacement(TileWorker* worker, float angle, bool collisionDebug) { + void redoPlacement(TileWorker* worker, float angle, bool collisionDebug, std::function callback) { worker->redoPlacement(angle, collisionDebug); + callback(); } }; @@ -53,22 +58,22 @@ Worker::~Worker() = default; std::unique_ptr Worker::parseRasterTile(RasterBucket& bucket, std::string data, std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithResult(&Worker::Impl::parseRasterTile, callback, &bucket, data); + return threads[current]->invokeWithCallback(&Worker::Impl::parseRasterTile, callback, &bucket, data); } std::unique_ptr Worker::parseVectorTile(TileWorker& worker, std::string data, std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithResult(&Worker::Impl::parseVectorTile, callback, &worker, data); + return threads[current]->invokeWithCallback(&Worker::Impl::parseVectorTile, callback, &worker, data); } std::unique_ptr Worker::parseLiveTile(TileWorker& worker, const LiveTile& tile, std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithResult(&Worker::Impl::parseLiveTile, callback, &worker, &tile); + return threads[current]->invokeWithCallback(&Worker::Impl::parseLiveTile, callback, &worker, &tile); } std::unique_ptr Worker::redoPlacement(TileWorker& worker, float angle, bool collisionDebug, std::function callback) { current = (current + 1) % threads.size(); - return threads[current]->invokeWithResult(&Worker::Impl::redoPlacement, callback, &worker, angle, collisionDebug); + return threads[current]->invokeWithCallback(&Worker::Impl::redoPlacement, callback, &worker, angle, collisionDebug); } } // end namespace mbgl diff --git a/test/miscellaneous/thread.cpp b/test/miscellaneous/thread.cpp index 68f8ddccd..78e6ffee6 100644 --- a/test/miscellaneous/thread.cpp +++ b/test/miscellaneous/thread.cpp @@ -17,9 +17,9 @@ public: EXPECT_EQ(val, 1); } - int fn2() { + void fn2(std::function cb) { EXPECT_EQ(tid, std::this_thread::get_id()); - return 1; + cb(1); } void transferIn(std::unique_ptr val) { @@ -27,15 +27,15 @@ public: EXPECT_EQ(*val, 1); } - std::unique_ptr transferOut() { + void transferOut(std::function)> cb) { EXPECT_EQ(tid, std::this_thread::get_id()); - return std::make_unique(1); + cb(std::make_unique(1)); } - std::unique_ptr transferInOut(std::unique_ptr val) { + void transferInOut(std::unique_ptr val, std::function)> cb) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*val, 1); - return std::move(val); + cb(std::move(val)); } void transferInShared(std::shared_ptr val) { @@ -43,21 +43,21 @@ public: EXPECT_EQ(*val, 1); } - std::shared_ptr transferOutShared() { + void transferOutShared(std::function)> cb) { EXPECT_EQ(tid, std::this_thread::get_id()); - return std::make_shared(1); + cb(std::make_shared(1)); } - std::string transferString(const std::string& string) { + void transferString(const std::string& string, std::function cb) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(string, "test"); - return string; + cb(string); } - bool checkContext() const { - return ThreadContext::currentlyOn(ThreadType::Worker) + void checkContext(std::function cb) const { + cb(ThreadContext::currentlyOn(ThreadType::Worker) && ThreadContext::getName() == "Test" - && ThreadContext::getPriority() == ThreadPriority::Low; + && ThreadContext::getPriority() == ThreadPriority::Low); } const std::thread::id tid; @@ -74,35 +74,35 @@ TEST(Thread, invoke) { Thread thread({"Test", ThreadType::Map, ThreadPriority::Regular}, tid); thread.invoke(&TestObject::fn1, 1); - requests.push_back(thread.invokeWithResult(&TestObject::fn2, [&] (int result) { + requests.push_back(thread.invokeWithCallback(&TestObject::fn2, [&] (int result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(result, 1); })); thread.invoke(&TestObject::transferIn, std::make_unique(1)); - requests.push_back(thread.invokeWithResult>(&TestObject::transferOut, [&] (std::unique_ptr result) { + requests.push_back(thread.invokeWithCallback(&TestObject::transferOut, [&] (std::unique_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); })); - requests.push_back(thread.invokeWithResult>(&TestObject::transferInOut, [&] (std::unique_ptr result) { + requests.push_back(thread.invokeWithCallback(&TestObject::transferInOut, [&] (std::unique_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); }, std::make_unique(1))); thread.invoke(&TestObject::transferInShared, std::make_shared(1)); - requests.push_back(thread.invokeWithResult>(&TestObject::transferOutShared, [&] (std::shared_ptr result) { + requests.push_back(thread.invokeWithCallback(&TestObject::transferOutShared, [&] (std::shared_ptr result) { EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(*result, 1); })); // Cancelled request - thread.invokeWithResult(&TestObject::fn1, [&] { + thread.invokeWithCallback(&TestObject::fn2, [&] (int) { ADD_FAILURE(); - }, 1); + }); std::string test("test"); - requests.push_back(thread.invokeWithResult(&TestObject::transferString, [&] (std::string result){ + requests.push_back(thread.invokeWithCallback(&TestObject::transferString, [&] (std::string result){ EXPECT_EQ(tid, std::this_thread::get_id()); EXPECT_EQ(result, "test"); loop.stop(); @@ -128,7 +128,7 @@ TEST(Thread, context) { loop.invoke([&] { Thread thread({"Test", ThreadType::Worker, ThreadPriority::Low}, tid); - requests.push_back(thread.invokeWithResult(&TestObject::checkContext, [&] (bool inTestThreadContext) { + requests.push_back(thread.invokeWithCallback(&TestObject::checkContext, [&] (bool inTestThreadContext) { EXPECT_EQ(inTestThreadContext, true); loop.stop(); })); @@ -139,8 +139,13 @@ TEST(Thread, context) { class TestWorker { public: - TestWorker(uv_loop_t*) {} - void send(std::function fn) { fn(); } + TestWorker(uv_loop_t*) { + } + + void send(std::function fn, std::function cb) { + fn(); + cb(); + } }; TEST(Thread, ExecutesAfter) { @@ -150,7 +155,7 @@ TEST(Thread, ExecutesAfter) { bool didWork = false; bool didAfter = false; - auto request = thread.invokeWithResult(&TestWorker::send, [&] { + auto request = thread.invokeWithCallback(&TestWorker::send, [&] { didAfter = true; loop.stop(); }, [&] { @@ -170,7 +175,7 @@ TEST(Thread, WorkRequestDeletionWaitsForWorkToComplete) { std::promise started; bool didWork = false; - auto request = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + auto request = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] { started.set_value(); usleep(10000); didWork = true; @@ -188,7 +193,7 @@ TEST(Thread, WorkRequestDeletionCancelsAfter) { std::promise started; bool didAfter = false; - auto request = thread.invokeWithResult(&TestWorker::send, [&] { + auto request = thread.invokeWithCallback(&TestWorker::send, [&] { didAfter = true; }, [&] { started.set_value(); @@ -206,12 +211,12 @@ TEST(Thread, WorkRequestDeletionCancelsImmediately) { std::promise started; - auto request1 = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + auto request1 = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] { usleep(10000); started.set_value(); }); - auto request2 = thread.invokeWithResult(&TestWorker::send, [&] {}, [&] { + auto request2 = thread.invokeWithCallback(&TestWorker::send, [&] {}, [&] { ADD_FAILURE() << "Second work item should not be invoked"; }); request2.reset(); diff --git a/test/storage/database.cpp b/test/storage/database.cpp index b54b69a13..a7560262f 100644 --- a/test/storage/database.cpp +++ b/test/storage/database.cpp @@ -15,8 +15,9 @@ TEST_F(Storage, DatabaseDoesNotExist) { SQLiteCache::Impl cache(nullptr, "test/fixtures/404/cache.db"); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); auto observer = Log::removeObserver(); EXPECT_EQ(1ul, dynamic_cast(observer.get())->count({ EventSeverity::Error, Event::Database, 14, "unable to open database file" })); @@ -56,8 +57,9 @@ TEST_F(Storage, DatabaseCreate) { SQLiteCache::Impl cache(nullptr, "test/fixtures/database/cache.db"); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); Log::removeObserver(); } @@ -115,8 +117,9 @@ TEST_F(Storage, DatabaseLockedRead) { // First request should fail. Log::setObserver(std::make_unique()); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); // Make sure that we got a few "database locked" errors auto observer = Log::removeObserver(); @@ -131,8 +134,9 @@ TEST_F(Storage, DatabaseLockedRead) { // First, try getting a file (the cache value should not exist). Log::setObserver(std::make_unique()); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); // Make sure that we got a no errors Log::removeObserver(); @@ -157,8 +161,9 @@ TEST_F(Storage, DatabaseLockedWrite) { auto response = std::make_shared(); cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); auto observer = Log::removeObserver(); auto flo = dynamic_cast(observer.get()); @@ -175,9 +180,10 @@ TEST_F(Storage, DatabaseLockedWrite) { auto response = std::make_shared(); response->data = "Demo"; cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_NE(nullptr, res.get()); - EXPECT_EQ("Demo", res->data); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_NE(nullptr, res.get()); + EXPECT_EQ("Demo", res->data); + }); // Make sure that we got a no errors Log::removeObserver(); @@ -206,8 +212,9 @@ TEST_F(Storage, DatabaseLockedRefresh) { auto response = std::make_shared(); response->data = "Demo"; cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); auto observer = Log::removeObserver(); auto flo = dynamic_cast(observer.get()); @@ -221,8 +228,9 @@ TEST_F(Storage, DatabaseLockedRefresh) { auto response = std::make_shared(); response->data = "Demo"; cache.refresh({ Resource::Unknown, "mapbox://test" }, response->expires); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_EQ(nullptr, res.get()); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_EQ(nullptr, res.get()); + }); // Make sure that we got the right errors. auto observer = Log::removeObserver(); @@ -249,9 +257,10 @@ TEST_F(Storage, DatabaseDeleted) { auto response = std::make_shared(); response->data = "Demo"; cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_NE(nullptr, res.get()); - EXPECT_EQ("Demo", res->data); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_NE(nullptr, res.get()); + EXPECT_EQ("Demo", res->data); + }); Log::removeObserver(); } @@ -265,9 +274,10 @@ TEST_F(Storage, DatabaseDeleted) { auto response = std::make_shared(); response->data = "Demo"; cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_NE(nullptr, res.get()); - EXPECT_EQ("Demo", res->data); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_NE(nullptr, res.get()); + EXPECT_EQ("Demo", res->data); + }); auto observer = Log::removeObserver(); auto flo = dynamic_cast(observer.get()); @@ -294,9 +304,10 @@ TEST_F(Storage, DatabaseInvalid) { auto response = std::make_shared(); response->data = "Demo"; cache.put({ Resource::Unknown, "mapbox://test" }, response); - std::unique_ptr res = cache.get({ Resource::Unknown, "mapbox://test" }); - EXPECT_NE(nullptr, res.get()); - EXPECT_EQ("Demo", res->data); + cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { + EXPECT_NE(nullptr, res.get()); + EXPECT_EQ("Demo", res->data); + }); auto observer = Log::removeObserver(); auto flo = dynamic_cast(observer.get()); From c0af683f2eaa8963103721284dbf0a08b3d10f77 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 18 Jun 2015 18:33:29 -0700 Subject: [PATCH 176/760] Reapply style layer copy technique --- src/mbgl/map/tile_worker.cpp | 8 +++++--- src/mbgl/map/tile_worker.hpp | 7 ++++++- src/mbgl/map/vector_tile_data.cpp | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index b69698689..790a34cf5 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -13,10 +13,12 @@ using namespace mbgl; TileWorker::TileWorker(const TileID& id_, Style& style_, + std::vector> layers_, const uint16_t maxZoom_, const std::atomic& state_, std::unique_ptr collision_) - : style(style_), + : layers(std::move(layers_)), + style(style_), id(id_), maxZoom(maxZoom_), state(state_), @@ -48,7 +50,7 @@ size_t TileWorker::countBuckets() const { TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { partialParse = false; - for (const auto& layer : style.layers) { + for (const auto& layer : layers) { parseLayer(*layer, geometryTile); } @@ -58,7 +60,7 @@ TileParseResult TileWorker::parse(const GeometryTile& geometryTile) { void TileWorker::redoPlacement(float angle, bool collisionDebug) { collision->reset(angle, 0); collision->setDebug(collisionDebug); - for (const auto& layer_desc : style.layers) { + for (const auto& layer_desc : layers) { auto bucket = getBucket(*layer_desc); if (bucket) { bucket->placeFeatures(); diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index 7fb474a5e..d8c8aa3e0 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ class CollisionTile; class GeometryTile; class Style; class Bucket; +class StyleLayer; class StyleBucket; class GeometryTileLayer; @@ -31,6 +33,7 @@ class TileWorker : public util::noncopyable { public: TileWorker(const TileID&, Style&, + std::vector>, const uint16_t maxZoom, const std::atomic&, std::unique_ptr); @@ -42,7 +45,7 @@ public: TileParseResult parse(const GeometryTile&); void redoPlacement(float angle, bool collisionDebug); - Style& style; + std::vector> layers; private: void parseLayer(const StyleLayer&, const GeometryTile&); @@ -54,6 +57,8 @@ private: template void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&); + Style& style; + const TileID id; const uint16_t maxZoom; const std::atomic& state; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 9b04ab371..04ef339c4 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -21,6 +21,7 @@ VectorTileData::VectorTileData(const TileID& id_, worker(style_.workers), tileWorker(id_, style_, + style_.layers, source_.max_zoom, state, std::make_unique(id_.z, 4096, @@ -115,7 +116,7 @@ void VectorTileData::redoPlacement(float angle, bool collisionDebug) { currentCollisionDebug = collisionDebug; workRequest = worker.redoPlacement(tileWorker, angle, collisionDebug, [this] { - for (const auto& layer : tileWorker.style.layers) { + for (const auto& layer : tileWorker.layers) { auto bucket = getBucket(*layer); if (bucket) { bucket->swapRenderData(); From e466348dea67df7da06a9548f8c364a7e1b7a51c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 1 Jul 2015 17:12:21 -0700 Subject: [PATCH 177/760] Remove unnecessary use of optional --- test/fixtures/fixture_log_observer.cpp | 19 +++++++------------ test/fixtures/fixture_log_observer.hpp | 10 ++++------ test/style/resource_loading.cpp | 2 +- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/test/fixtures/fixture_log_observer.cpp b/test/fixtures/fixture_log_observer.cpp index b3b8e56fd..1d3517ac6 100644 --- a/test/fixtures/fixture_log_observer.cpp +++ b/test/fixtures/fixture_log_observer.cpp @@ -8,14 +8,11 @@ FixtureLogObserver::LogMessage::LogMessage(EventSeverity severity_, Event event_ : severity(severity_), event(event_), code(code_), msg(msg_) { } -FixtureLogObserver::LogMessage::LogMessage() : severity(), event(), code(), msg() { -} - bool FixtureLogObserver::LogMessage::operator==(const LogMessage &rhs) const { - return (!severity || !rhs.severity || severity.get() == rhs.severity.get()) && - (!event || !rhs.event || event.get() == rhs.event.get()) && - (!code || !rhs.code || code.get() == rhs.code.get()) && - (!msg || !rhs.msg || msg.get() == rhs.msg.get()); + return severity == rhs.severity && + event == rhs.event && + code == rhs.code && + msg == rhs.msg; } FixtureLogObserver::~FixtureLogObserver() { @@ -60,11 +57,9 @@ std::vector FixtureLogObserver::unchecked() cons } ::std::ostream &operator<<(::std::ostream &os, const FixtureLogObserver::LogMessage &message) { - os << "[\"" << message.severity.get() << "\", \"" << message.event.get() << "\""; - if (message.code) - os << ", " << message.code.get(); - if (message.msg) - os << ", \"" << message.msg.get() << "\""; + os << "[\"" << message.severity << "\", \"" << message.event << "\""; + os << ", " << message.code; + os << ", \"" << message.msg << "\""; return os << "]" << std::endl; } diff --git a/test/fixtures/fixture_log_observer.hpp b/test/fixtures/fixture_log_observer.hpp index 7e419a617..cadff84d3 100644 --- a/test/fixtures/fixture_log_observer.hpp +++ b/test/fixtures/fixture_log_observer.hpp @@ -2,7 +2,6 @@ #define MBGL_TEST_FIXTURE_LOG_OBSERVER #include -#include #include #include @@ -14,14 +13,13 @@ class FixtureLogObserver : public Log::Observer { public: struct LogMessage { LogMessage(EventSeverity severity_, Event event_, int64_t code_, const std::string &msg_); - LogMessage(); bool operator==(const LogMessage &rhs) const; - const mapbox::util::optional severity; - const mapbox::util::optional event; - const mapbox::util::optional code; - const mapbox::util::optional msg; + const EventSeverity severity; + const Event event; + const int64_t code; + const std::string msg; mutable bool checked = false; }; diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index c37e17d71..700a42c42 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -124,7 +124,7 @@ void runTestCase(MockFileSource::Type type, std::vector logMessages = log->unchecked(); for (auto& logMessage : logMessages) { - if (std::regex_match(*logMessage.msg, std::regex(message))) { + if (std::regex_match(logMessage.msg, std::regex(message))) { match++; } } From 28fa8952fca7d834ee4708c1b5d8ee8516ea8c9f Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Thu, 2 Jul 2015 01:58:24 -0400 Subject: [PATCH 178/760] bump podspec to 0.4.4 --- ios/MapboxGL.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/MapboxGL.podspec b/ios/MapboxGL.podspec index b8367d2e3..0b1cfddb5 100644 --- a/ios/MapboxGL.podspec +++ b/ios/MapboxGL.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |m| m.name = 'MapboxGL' - m.version = '0.4.3-symbols' + m.version = '0.4.4-symbols' m.summary = 'Open source vector map solution for iOS with full styling capabilities.' m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.' From 4a44b70407fb003fb051e50d46367cd03883210a Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Thu, 2 Jul 2015 01:58:53 -0400 Subject: [PATCH 179/760] [publish ios-v0.4.4] From 45e4938f9e19af8ddd389015d84067eb536da0ca Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 2 Jul 2015 12:31:50 +0200 Subject: [PATCH 180/760] fix cross-tile label collisions fix #1816 fix #1689 The missing label was the only one in it's bucket. Since the label was outside the tile boundaries it wouldn't need to be drawn by the bucket. Since the bucket had no features to draw, it was dropped completely. Even if a bucket doesn't have any features that need to be rendered, it may have features that need to impact the placement of features in other layers. --- src/mbgl/renderer/painter_symbol.cpp | 4 +--- src/mbgl/renderer/symbol_bucket.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 4f4999b3e..6762bcd4d 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -122,9 +122,7 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c config.depthTest = true; config.depthMask = GL_FALSE; - if (bucket.hasCollisionBoxData() && ( - (bucket.hasIconData() && properties.icon.opacity) || - (bucket.hasTextData() && properties.text.opacity))) { + if (bucket.hasCollisionBoxData()) { config.stencilTest = true; useProgram(collisionBoxShader->program); diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index 9f05209ff..d52561d3e 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -80,7 +80,7 @@ void SymbolBucket::render(Painter& painter, painter.renderSymbol(*this, layer_desc, id, matrix); } -bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); } +bool SymbolBucket::hasData() const { return hasTextData() || hasIconData() || symbolInstances.size(); } bool SymbolBucket::hasTextData() const { return renderData && !renderData->text.groups.empty(); } From a8f77d23cc6e1716be867f42cca0046a6a1add27 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 2 Jul 2015 12:35:15 +0200 Subject: [PATCH 181/760] reduce collision box linewidth to 1 https://github.com/mapbox/mapbox-gl-js/commit/06eece43a88141c281b7664cc225ed7753dbfb98 --- src/mbgl/renderer/painter_symbol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 6762bcd4d..2a02a30f6 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -130,7 +130,7 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z); collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10; collisionBoxShader->u_maxzoom = (id.z + 1) * 10; - lineWidth(3.0f); + lineWidth(1.0f); config.depthRange = { strata, 1.0f }; bucket.drawCollisionBoxes(*collisionBoxShader); From 9d714dcf83c03f20d9860cf463275d7e28b117d2 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 2 Jul 2015 13:18:59 +0200 Subject: [PATCH 182/760] fix line tessellation, fix #1814 --- src/mbgl/renderer/line_bucket.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index a0ad5ac3c..0ba91af34 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -183,8 +183,8 @@ void LineBucket::addGeometry(const std::vector& vertices) { } else { const float direction = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0 ? -1 : 1; const float bevelLength = miterLength * util::mag(prevNormal + nextNormal) / - util::mag(prevNormal - nextNormal * direction); - joinNormal = util::perp(joinNormal) * bevelLength; + util::mag(prevNormal - nextNormal); + joinNormal = util::perp(joinNormal) * bevelLength * direction; } addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex, From dba1e44f551cbd270eac55993dd14d30d8896013 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 30 Jun 2015 20:12:31 -0400 Subject: [PATCH 183/760] Add collision box debugging to the global debug toggle --- platform/ios/MGLMapView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 57d2c56f9..06a2ef36b 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1424,6 +1424,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)toggleDebug { _mbglMap->toggleDebug(); + _mbglMap->toggleCollisionDebug(); } - (void)emptyMemoryCache From 1da2c3f840c83cab72a94c7af29bfc1da2f4d9bd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 1 Jul 2015 17:38:49 -0700 Subject: [PATCH 184/760] Remove unnecessary virtual methods from TileData --- src/mbgl/map/raster_tile_data.cpp | 5 ----- src/mbgl/map/raster_tile_data.hpp | 4 +--- src/mbgl/map/source.cpp | 16 +++++++++------- src/mbgl/map/tile_data.hpp | 9 --------- src/mbgl/map/vector_tile_data.hpp | 4 ++-- 5 files changed, 12 insertions(+), 26 deletions(-) diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index 837f4c0f1..b0fa4d171 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -60,11 +60,6 @@ void RasterTileData::request(float pixelRatio, }); } -bool RasterTileData::reparse(std::function) { - assert(false); - return false; -} - Bucket* RasterTileData::getBucket(StyleLayer const&) { return &bucket; } diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index ce0286f5a..93d018bce 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -19,9 +19,7 @@ public: ~RasterTileData(); void request(float pixelRatio, - const std::function& callback) override; - - bool reparse(std::function callback) override; + const std::function& callback); void cancel() override; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index f468ab33e..7b6a35ed2 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -290,18 +290,20 @@ TileData::State Source::addTile(MapData& data, // If we don't find working tile data, we're just going to load it. if (info.type == SourceType::Vector) { - new_tile.data = - std::make_shared(normalized_id, style, info, + auto tileData = std::make_shared(normalized_id, style, info, transformState.getAngle(), data.getCollisionDebug()); - new_tile.data->request(transformState.getPixelRatio(), callback); + tileData->request(transformState.getPixelRatio(), callback); + new_tile.data = tileData; } else if (info.type == SourceType::Raster) { - new_tile.data = std::make_shared(normalized_id, texturePool, info, style.workers); - new_tile.data->request(transformState.getPixelRatio(), callback); + auto tileData = std::make_shared(normalized_id, texturePool, info, style.workers); + tileData->request(transformState.getPixelRatio(), callback); + new_tile.data = tileData; } else if (info.type == SourceType::Annotations) { - new_tile.data = std::make_shared(normalized_id, data.annotationManager, + auto tileData = std::make_shared(normalized_id, data.annotationManager, style, info, transformState.getAngle(), data.getCollisionDebug()); - new_tile.data->reparse(callback); + tileData->reparse(callback); + new_tile.data = tileData; } else { throw std::runtime_error("source type not implemented"); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 552525695..476172ce5 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -70,15 +70,6 @@ public: TileData(const TileID&); virtual ~TileData() = default; - virtual void request(float pixelRatio, - const std::function& callback) = 0; - - // Schedule a tile reparse on a worker thread and call the callback on - // completion. It will return true if the work was schedule or false it was - // not, which can occur if the tile is already being parsed by another - // worker. - virtual bool reparse(std::function callback) = 0; - // Mark this tile as no longer needed and cancel any pending work. virtual void cancel() = 0; diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index a035015f4..b0b5ec6e8 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -29,9 +29,9 @@ public: size_t countBuckets() const; void request(float pixelRatio, - const std::function& callback) override; + const std::function& callback); - bool reparse(std::function callback) override; + virtual bool reparse(std::function callback); void redoPlacement(float angle, bool collisionDebug) override; From 117322d349d7ff9a1d0028b6f88dd7369027881f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 1 Jul 2015 17:48:09 -0700 Subject: [PATCH 185/760] Don't inherit for code reuse --- src/mbgl/map/live_tile_data.cpp | 62 +++++++++++++++++-------------- src/mbgl/map/live_tile_data.hpp | 22 +++++++---- src/mbgl/map/source.cpp | 9 ++--- src/mbgl/map/vector_tile_data.hpp | 14 ++----- 4 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index ad28c6c62..ad983343b 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -1,49 +1,40 @@ #include #include #include +#include +#include +#include #include #include +#include #include using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, - AnnotationManager& annotationManager_, + const LiveTile* tile, Style& style_, const SourceInfo& source_, - float angle_, - bool collisionDebug_) - : VectorTileData(id_, style_, source_, angle_, collisionDebug_), - annotationManager(annotationManager_) { - // live features are always loaded + std::function callback) + : TileData(id_), + worker(style_.workers), + tileWorker(id_, + style_, + style_.layers, + source_.max_zoom, + state, + std::make_unique(id_.z, 4096, + source_.tile_size * id.overscaling, + 0, false)) { state = State::loaded; -} -LiveTileData::~LiveTileData() { - cancel(); -} - -bool LiveTileData::reparse(std::function callback) { - if (parsing || (state != State::loaded && state != State::partial)) { - return false; - } - - const LiveTile* tile = annotationManager.getTile(id); if (!tile) { state = State::parsed; - return true; + return; } - parsing = true; - workRequest = worker.parseLiveTile(tileWorker, *tile, [this, callback] (TileParseResult result) { - parsing = false; - - if (state == State::obsolete) { - return; - } - if (result.is()) { state = result.get(); } else { @@ -53,6 +44,21 @@ bool LiveTileData::reparse(std::function callback) { callback(); }); - - return true; +} + +LiveTileData::~LiveTileData() { + cancel(); +} + +Bucket* LiveTileData::getBucket(const StyleLayer& layer) { + if (!isReady() || !layer.bucket) { + return nullptr; + } + + return tileWorker.getBucket(layer); +} + +void LiveTileData::cancel() { + state = State::obsolete; + workRequest.reset(); } diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index 2d6b01061..5c4220fd6 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -1,26 +1,32 @@ #ifndef MBGL_MAP_LIVE_TILE_DATA #define MBGL_MAP_LIVE_TILE_DATA -#include +#include +#include namespace mbgl { -class AnnotationManager; +class Style; +class SourceInfo; +class WorkRequest; +class LiveTile; -class LiveTileData : public VectorTileData { +class LiveTileData : public TileData { public: LiveTileData(const TileID&, - AnnotationManager&, + const LiveTile*, Style&, const SourceInfo&, - float angle_, - bool collisionDebug_); + std::function callback); ~LiveTileData(); - bool reparse(std::function callback) override; + void cancel() override; + Bucket* getBucket(const StyleLayer&) override; private: - AnnotationManager& annotationManager; + Worker& worker; + TileWorker tileWorker; + std::unique_ptr workRequest; }; } diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 7b6a35ed2..f596296fc 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -235,7 +235,7 @@ bool Source::handlePartialTile(const TileID& id, Worker&) { // Note: this uses a raw pointer; we don't want the callback binding to have a // shared pointer. - VectorTileData* data = static_cast(it->second.lock().get()); + VectorTileData* data = dynamic_cast(it->second.lock().get()); if (!data) { return true; } @@ -299,11 +299,8 @@ TileData::State Source::addTile(MapData& data, tileData->request(transformState.getPixelRatio(), callback); new_tile.data = tileData; } else if (info.type == SourceType::Annotations) { - auto tileData = std::make_shared(normalized_id, data.annotationManager, - style, info, - transformState.getAngle(), data.getCollisionDebug()); - tileData->reparse(callback); - new_tile.data = tileData; + new_tile.data = std::make_shared(normalized_id, + data.annotationManager.getTile(normalized_id), style, info, callback); } else { throw std::runtime_error("source type not implemented"); } diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index b0b5ec6e8..3b73ded0c 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -8,13 +8,10 @@ namespace mbgl { -class SourceInfo; -class Request; -class Bucket; -class SourceInfo; -class StyleLayer; class Style; +class SourceInfo; class WorkRequest; +class Request; class VectorTileData : public TileData { public: @@ -31,23 +28,20 @@ public: void request(float pixelRatio, const std::function& callback); - virtual bool reparse(std::function callback); + bool reparse(std::function callback); void redoPlacement(float angle, bool collisionDebug) override; void cancel() override; -protected: +private: Worker& worker; TileWorker tileWorker; std::unique_ptr workRequest; bool parsing = false; - -private: const SourceInfo& source; Request* req = nullptr; std::string data; - float lastAngle = 0; float currentAngle; bool lastCollisionDebug = 0; From f5f19d0eb0deefd64d2107eb081e4035ad1a747c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 29 Jun 2015 17:51:52 -0700 Subject: [PATCH 186/760] External synchronization for AnnotationManager --- src/mbgl/map/annotation.cpp | 17 ----------------- src/mbgl/map/annotation.hpp | 2 -- src/mbgl/map/map.cpp | 12 ++++++------ src/mbgl/map/map_context.cpp | 11 ++++++----- src/mbgl/map/map_data.hpp | 11 ++++++++++- src/mbgl/map/source.cpp | 2 +- 6 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 5b857e9b5..a0f65cd7b 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -49,17 +49,14 @@ AnnotationManager::~AnnotationManager() { } void AnnotationManager::markStaleTiles(std::unordered_set ids) { - std::lock_guard lock(mtx); std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin())); } std::unordered_set AnnotationManager::resetStaleTiles() { - std::lock_guard lock(mtx); return std::move(staleTiles); } void AnnotationManager::setDefaultPointAnnotationSymbol(const std::string& symbol) { - std::lock_guard lock(mtx); defaultPointAnnotationSymbol = symbol; } @@ -261,8 +258,6 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, std::pair, AnnotationIDs> AnnotationManager::addPointAnnotations(const std::vector& points, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - // We pre-generate tiles to contain each annotation up to the map's max zoom. // We do this for fast rendering without projection conversions on the fly, as well as // to simplify bounding box queries of annotations later. Tiles get invalidated when @@ -310,8 +305,6 @@ AnnotationManager::addPointAnnotations(const std::vector& point std::pair, AnnotationIDs> AnnotationManager::addShapeAnnotations(const std::vector& shapes, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - // We pre-generate tiles to contain each annotation up to the map's max zoom. // We do this for fast rendering without projection conversions on the fly, as well as // to simplify bounding box queries of annotations later. Tiles get invalidated when @@ -348,8 +341,6 @@ AnnotationManager::addShapeAnnotations(const std::vector& shape std::unordered_set AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - std::unordered_set affectedTiles; std::vector z2s; @@ -416,8 +407,6 @@ std::unordered_set AnnotationManager::removeAnnotations(co } const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t annotationID) const { - std::lock_guard lock(mtx); - auto anno_it = annotations.find(annotationID); assert(anno_it != annotations.end()); @@ -427,8 +416,6 @@ const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t a AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds, const uint8_t maxZoom, const AnnotationType& type) const { - std::lock_guard lock(mtx); - const uint8_t z = maxZoom; const uint32_t z2 = 1 << z; const vec2 swPoint = projectPoint(queryBounds.sw); @@ -493,8 +480,6 @@ AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& quer } LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids) const { - std::lock_guard lock(mtx); - LatLngBounds bounds; for (auto id : ids) { const auto annotation_it = annotations.find(id); @@ -507,8 +492,6 @@ LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids } const LiveTile* AnnotationManager::getTile(const TileID& id) { - std::lock_guard lock(mtx); - // look up any existing annotation tile LiveTile *renderTile = nullptr; const auto tile_lookup_it = tiles.find(id); diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index e75c57f1f..745fc779c 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -90,7 +89,6 @@ private: const uint8_t maxZoom); private: - mutable std::mutex mtx; std::string defaultPointAnnotationSymbol; std::unordered_map> annotations; std::vector orderedShapeAnnotations; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 91017d89c..94d3e57d4 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -304,7 +304,7 @@ const LatLng Map::latLngForPixel(const vec2 pixel) const { #pragma mark - Annotations void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) { - data->annotationManager.setDefaultPointAnnotationSymbol(symbol); + data->getAnnotationManager()->setDefaultPointAnnotationSymbol(symbol); } double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) { @@ -316,7 +316,7 @@ uint32_t Map::addPointAnnotation(const PointAnnotation& annotation) { } AnnotationIDs Map::addPointAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.addPointAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->addPointAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result.first); return result.second; } @@ -326,7 +326,7 @@ uint32_t Map::addShapeAnnotation(const ShapeAnnotation& annotation) { } AnnotationIDs Map::addShapeAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.addShapeAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->addShapeAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result.first); return result.second; } @@ -336,16 +336,16 @@ void Map::removeAnnotation(uint32_t annotation) { } void Map::removeAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.removeAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result); } std::vector Map::getAnnotationsInBounds(const LatLngBounds& bounds, const AnnotationType& type) { - return data->annotationManager.getAnnotationsInBounds(bounds, getMaxZoom(), type); + return data->getAnnotationManager()->getAnnotationsInBounds(bounds, getMaxZoom(), type); } LatLngBounds Map::getBoundsForAnnotations(const std::vector& annotations) { - return data->annotationManager.getBoundsForAnnotations(annotations); + return data->getAnnotationManager()->getBoundsForAnnotations(annotations); } diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index fa06a4267..fc4766f3a 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -136,7 +136,7 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base) updated |= static_cast(Update::Zoom); asyncUpdate->send(); - auto staleTiles = data.annotationManager.resetStaleTiles(); + auto staleTiles = data.getAnnotationManager()->resetStaleTiles(); if (staleTiles.size()) { updateAnnotationTiles(staleTiles); } @@ -145,7 +145,8 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base) void MapContext::updateAnnotationTiles(const std::unordered_set& ids) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); - data.annotationManager.markStaleTiles(ids); + util::exclusive annotationManager = data.getAnnotationManager(); + annotationManager->markStaleTiles(ids); if (!style) return; @@ -154,7 +155,7 @@ void MapContext::updateAnnotationTiles(const std::unordered_setgetSource(shapeID)->enabled = true; // create (if necessary) layers and buckets for each shape - for (const auto &shapeAnnotationID : data.annotationManager.getOrderedShapeAnnotations()) { + for (const auto &shapeAnnotationID : annotationManager->getOrderedShapeAnnotations()) { const std::string shapeLayerID = shapeID + "." + std::to_string(shapeAnnotationID); const auto layer_it = std::find_if(style->layers.begin(), style->layers.end(), @@ -164,7 +165,7 @@ void MapContext::updateAnnotationTiles(const std::unordered_setlayers.end()) { // query shape styling - auto& shapeStyle = data.annotationManager.getAnnotationStyleProperties(shapeAnnotationID); + auto& shapeStyle = annotationManager->getAnnotationStyleProperties(shapeAnnotationID); // apply shape paint properties ClassProperties paintProperties; @@ -233,7 +234,7 @@ void MapContext::updateAnnotationTiles(const std::unordered_set(Update::Classes); asyncUpdate->send(); - data.annotationManager.resetStaleTiles(); + annotationManager->resetStaleTiles(); } void MapContext::cascadeClasses() { diff --git a/src/mbgl/map/map_data.hpp b/src/mbgl/map/map_data.hpp index a1ee3c06d..7e5955d74 100644 --- a/src/mbgl/map/map_data.hpp +++ b/src/mbgl/map/map_data.hpp @@ -12,6 +12,7 @@ #include #include +#include namespace mbgl { @@ -78,11 +79,19 @@ public: defaultTransitionDuration = duration; }; + util::exclusive getAnnotationManager() { + return util::exclusive( + &annotationManager, + std::make_unique>(annotationManagerMutex)); + } + public: - AnnotationManager annotationManager; const MapMode mode; private: + mutable std::mutex annotationManagerMutex; + AnnotationManager annotationManager; + mutable std::mutex mtx; std::vector classes; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index f596296fc..58f3924ec 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -300,7 +300,7 @@ TileData::State Source::addTile(MapData& data, new_tile.data = tileData; } else if (info.type == SourceType::Annotations) { new_tile.data = std::make_shared(normalized_id, - data.annotationManager.getTile(normalized_id), style, info, callback); + data.getAnnotationManager()->getTile(normalized_id), style, info, callback); } else { throw std::runtime_error("source type not implemented"); } From 9361cbfd5fc92538cfd5d7ef8bdaf83394861f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 2 Jul 2015 10:21:15 -0700 Subject: [PATCH 187/760] Fixed round-tripping between pixelForLatLng() / latLngForPixel() Followup to cfa2eaf5c21858483cd12ee0768b43315ef29e95 for #1406. --- src/mbgl/map/transform_state.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 3c4acc7e0..6a0c2a0d6 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -218,8 +218,8 @@ const LatLng TransformState::latLngForPixel(const vec2 pixel) const { LatLng ll = getLatLng(); double zoom = getZoom(); - const double centerX = width / 2; - const double centerY = height / 2; + const double centerX = static_cast(width) / 2.0; + const double centerY = static_cast(height) / 2.0; const double m = Projection::getMetersPerPixelAtLatitude(0, zoom); From c357ce540ad712194ca70d606d3fd1ef1cf4cc41 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 2 Jul 2015 12:27:22 -0700 Subject: [PATCH 188/760] Use unique_ptr for Source objects (fixes #904) --- src/mbgl/renderer/painter.cpp | 2 +- src/mbgl/style/style.cpp | 8 ++++---- src/mbgl/style/style.hpp | 4 ++-- src/mbgl/style/style_parser.cpp | 18 +++++++++--------- src/mbgl/style/style_parser.hpp | 8 ++++---- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index e7dc28ad7..742572a35 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -321,7 +321,7 @@ std::vector Painter::determineRenderOrder(const Style& style) { continue; } - util::ptr source = style.getSource(layer.bucket->source); + Source* source = style.getSource(layer.bucket->source); if (!source) { Log::Warning(Event::Render, "can't find source for layer '%s'", layer.id.c_str()); continue; diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 35a94adbc..23d32689e 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -113,7 +113,7 @@ void Style::recalculate(float z, TimePoint now) { continue; } - util::ptr source = getSource(layer->bucket->source); + Source* source = getSource(layer->bucket->source); if (!source) { continue; } @@ -122,12 +122,12 @@ void Style::recalculate(float z, TimePoint now) { } } -util::ptr Style::getSource(const std::string& id) const { - const auto it = std::find_if(sources.begin(), sources.end(), [&](util::ptr source) { +Source* Style::getSource(const std::string& id) const { + const auto it = std::find_if(sources.begin(), sources.end(), [&](const auto& source) { return source->info.source_id == id; }); - return it != sources.end() ? *it : nullptr; + return it != sources.end() ? it->get() : nullptr; } void Style::setDefaultTransitionDuration(Duration duration) { diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index 003183e3c..d96833da2 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -62,7 +62,7 @@ public: return lastError; } - util::ptr getSource(const std::string& id) const; + Source* getSource(const std::string& id) const; std::unique_ptr glyphStore; std::unique_ptr glyphAtlas; @@ -70,7 +70,7 @@ public: std::unique_ptr spriteAtlas; std::unique_ptr lineAtlas; - std::vector> sources; + std::vector> sources; std::vector> layers; private: diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index dba737828..53313a548 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -39,11 +39,11 @@ void StyleParser::parse(JSVal document) { // create shape annotations source const std::string& shapeID = AnnotationManager::ShapeLayerID; - util::ptr shapeAnnotationsSource = std::make_shared(); - sourcesMap.emplace(shapeID, shapeAnnotationsSource); - sources.emplace_back(shapeAnnotationsSource); + std::unique_ptr shapeAnnotationsSource = std::make_unique(); shapeAnnotationsSource->info.type = SourceType::Annotations; shapeAnnotationsSource->info.source_id = shapeID; + sourcesMap.emplace(shapeID, shapeAnnotationsSource.get()); + sources.emplace_back(std::move(shapeAnnotationsSource)); // create point annotations layer const std::string& pointID = AnnotationManager::PointLayerID; @@ -70,12 +70,12 @@ void StyleParser::parse(JSVal document) { parseLayout(iconOverlap, pointBucket); // create point annotations source & connect to bucket & layer - util::ptr pointAnnotationsSource = std::make_shared(); - sourcesMap.emplace(pointID, pointAnnotationsSource); - sources.emplace_back(pointAnnotationsSource); + std::unique_ptr pointAnnotationsSource = std::make_unique(); pointAnnotationsSource->info.type = SourceType::Annotations; pointAnnotationsSource->info.source_id = pointID; pointAnnotationsLayer->bucket = pointBucket; + sourcesMap.emplace(pointID, pointAnnotationsSource.get()); + sources.emplace_back(std::move(pointAnnotationsSource)); } if (document.HasMember("sprite")) { @@ -227,14 +227,14 @@ void StyleParser::parseSources(JSVal value) { rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); for (; itr != value.MemberEnd(); ++itr) { std::string name { itr->name.GetString(), itr->name.GetStringLength() }; - util::ptr source = std::make_shared(); + std::unique_ptr source = std::make_unique(); parseRenderProperty(itr->value, source->info.type, "type"); parseRenderProperty(itr->value, source->info.url, "url"); parseRenderProperty(itr->value, source->info.tile_size, "tileSize"); source->info.source_id = name; source->info.parseTileJSONProperties(itr->value); - sources.emplace_back(source); - sourcesMap.emplace(name, source); + sourcesMap.emplace(name, source.get()); + sources.emplace_back(std::move(source)); } } else { Log::Warning(Event::ParseStyle, "sources must be an object"); diff --git a/src/mbgl/style/style_parser.hpp b/src/mbgl/style/style_parser.hpp index 0cbc3a136..2daa5e14d 100644 --- a/src/mbgl/style/style_parser.hpp +++ b/src/mbgl/style/style_parser.hpp @@ -35,8 +35,8 @@ public: void parse(JSVal document); - std::vector> getSources() { - return sources; + std::vector>&& getSources() { + return std::move(sources); } std::vector> getLayers() { @@ -103,10 +103,10 @@ private: private: std::unordered_map constants; - std::vector> sources; + std::vector> sources; std::vector> layers; - std::unordered_map> sourcesMap; + std::unordered_map sourcesMap; std::unordered_map>> layersMap; // Store a stack of layers we're parsing right now. This is to prevent reference cycles. From 9cc986e758da9c19cdd078c1acfd44efee8f33f6 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 2 Jul 2015 16:04:30 +0300 Subject: [PATCH 189/760] Report errors when parsing raster tiles Use the same API/pattern for parsing raster tiles as we use for vector tiles. Forward the error as an exception that will ultimately reach the StillImageCallback. Also set the tile to State::obsolete on error like we do for vector tiles. --- src/mbgl/map/raster_tile_data.cpp | 11 +++++++---- src/mbgl/map/tile_data.hpp | 3 --- src/mbgl/renderer/raster_bucket.cpp | 4 ++-- src/mbgl/renderer/raster_bucket.hpp | 2 +- src/mbgl/util/raster.cpp | 4 ++-- src/mbgl/util/raster.hpp | 3 +-- src/mbgl/util/worker.cpp | 15 ++++++++++++--- src/mbgl/util/worker.hpp | 2 +- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index b0fa4d171..a8a1ab03d 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -44,15 +44,18 @@ void RasterTileData::request(float pixelRatio, state = State::loaded; - workRequest = worker.parseRasterTile(bucket, res.data, [this, callback] (bool result) { + workRequest = worker.parseRasterTile(bucket, res.data, [this, callback] (TileParseResult result) { if (state != State::loaded) { return; } - if (result) { - state = State::parsed; + if (result.is()) { + state = result.get(); } else { - state = State::invalid; + std::stringstream message; + message << "Failed to parse [" << std::string(id) << "]: " << result.get(); + error = message.str(); + state = State::obsolete; } callback(); diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 476172ce5..e78d88841 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -28,9 +28,6 @@ public: // the TileData object is not effectively on the 'invalid' state and will // cause tiles on 'invalid' state to get reloaded. // - // Raster tiles (only) are changing to 'invalid' on error, which will cause a - // re-request of the tile. - // // loading: // A request to the FileSource was made for the actual tile data and TileData // is waiting for it to arrive. diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp index c8a42f46a..70cc1065d 100644 --- a/src/mbgl/renderer/raster_bucket.cpp +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -23,8 +23,8 @@ void RasterBucket::render(Painter& painter, painter.renderRaster(*this, layer_desc, id, matrix); } -bool RasterBucket::setImage(const std::string &data) { - return raster.load(data); +bool RasterBucket::setImage(std::unique_ptr image) { + return raster.load(std::move(image)); } void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array) { diff --git a/src/mbgl/renderer/raster_bucket.hpp b/src/mbgl/renderer/raster_bucket.hpp index 70434b7d6..3c8730e61 100644 --- a/src/mbgl/renderer/raster_bucket.hpp +++ b/src/mbgl/renderer/raster_bucket.hpp @@ -20,7 +20,7 @@ public: void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; bool hasData() const; - bool setImage(const std::string &data); + bool setImage(std::unique_ptr image); const StyleLayoutRaster &layout; diff --git a/src/mbgl/util/raster.cpp b/src/mbgl/util/raster.cpp index b6b5df27f..9b8254089 100644 --- a/src/mbgl/util/raster.cpp +++ b/src/mbgl/util/raster.cpp @@ -25,8 +25,8 @@ bool Raster::isLoaded() const { return loaded; } -bool Raster::load(const std::string &data) { - img = std::make_unique(data); +bool Raster::load(std::unique_ptr image) { + img = std::move(image); width = img->getWidth(); height = img->getHeight(); diff --git a/src/mbgl/util/raster.hpp b/src/mbgl/util/raster.hpp index 1789ae87f..39ab26ffc 100644 --- a/src/mbgl/util/raster.hpp +++ b/src/mbgl/util/raster.hpp @@ -6,7 +6,6 @@ #include #include -#include #include typedef struct uv_loop_s uv_loop_t; @@ -20,7 +19,7 @@ public: ~Raster(); // load image data - bool load(const std::string &img); + bool load(std::unique_ptr image); // bind current texture void bind(bool linear = false); diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index cc9fa1566..9277daccc 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -20,8 +20,17 @@ public: util::ThreadContext::setFileSource(fs); } - void parseRasterTile(RasterBucket* bucket, std::string data, std::function callback) { - callback(bucket->setImage(data)); + void parseRasterTile(RasterBucket* bucket, std::string data, std::function callback) { + std::unique_ptr image(new util::Image(data)); + if (!(*image)) { + callback(TileParseResult("error parsing raster image")); + } + + if (!bucket->setImage(std::move(image))) { + callback(TileParseResult("error setting raster image to bucket")); + } + + callback(TileParseResult(TileData::State::parsed)); } void parseVectorTile(TileWorker* worker, std::string data, std::function callback) { @@ -56,7 +65,7 @@ Worker::Worker(std::size_t count) { Worker::~Worker() = default; -std::unique_ptr Worker::parseRasterTile(RasterBucket& bucket, std::string data, std::function callback) { +std::unique_ptr Worker::parseRasterTile(RasterBucket& bucket, std::string data, std::function callback) { current = (current + 1) % threads.size(); return threads[current]->invokeWithCallback(&Worker::Impl::parseRasterTile, callback, &bucket, data); } diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 1c302d585..08fd56497 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -34,7 +34,7 @@ public: Request parseRasterTile( RasterBucket&, std::string data, - std::function callback); + std::function callback); Request parseVectorTile( TileWorker&, From e4cc06b798d74cf353f641c833941fd864582a22 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 2 Jul 2015 13:35:23 +0300 Subject: [PATCH 190/760] Add test fixtures for testing raster tiles Add a raster source for the style we use for testing that will force requests for raster tiles. --- test/fixtures/resources/raster.png | Bin 0 -> 12146 bytes test/fixtures/resources/source_raster.json | 1 + .../{source.json => source_vector.json} | 0 test/fixtures/resources/style.json | 13 +++++++++---- 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/resources/raster.png create mode 100644 test/fixtures/resources/source_raster.json rename test/fixtures/resources/{source.json => source_vector.json} (100%) diff --git a/test/fixtures/resources/raster.png b/test/fixtures/resources/raster.png new file mode 100644 index 0000000000000000000000000000000000000000..78ad885baf21855f33fec333f54e72d6f6d82e67 GIT binary patch literal 12146 zcmbWdbyO5i^zgmp(g@OxOLuqog4EKf(#z5fOQ#~alp@_o$5MjQDIndQO9|45`1t)j zZ=Cb~^}g?&bN`w-bM9y6&fJOnck%Bg;F+cxSPg)N1^}S_D}cW%0A&C+CKfgpCN?$} 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�Ur_<}{%29)8&d1fn`(2Dso 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© Mapbox © OpenStreetMap Improve this map © DigitalGlobe","autoscale":true,"bounds":[-180,-85,180,85],"center":[0,0,3],"description":"","id":"mapbox.satellite","maxzoom":19,"minzoom":0,"name":"Mapbox Satellite","private":true,"scheme":"xyz","tilejson":"2.0.0","tiles":["test/fixtures/resources/raster.png"]} diff --git a/test/fixtures/resources/source.json b/test/fixtures/resources/source_vector.json similarity index 100% rename from test/fixtures/resources/source.json rename to test/fixtures/resources/source_vector.json diff --git a/test/fixtures/resources/style.json b/test/fixtures/resources/style.json index 698f3fb1e..fb67afc66 100644 --- a/test/fixtures/resources/style.json +++ b/test/fixtures/resources/style.json @@ -2,9 +2,14 @@ "version": 1, "name": "Test", "sources": { - "compositedsource": { - "url": "test/fixtures/resources/source.json", + "vectorsource": { + "url": "test/fixtures/resources/source_vector.json", "type": "vector" + }, + "rastersource": { + "url": "test/fixtures/resources/source_raster.json", + "type": "raster", + "tileSize": 256 } }, "sprite": "test/fixtures/resources/sprite", @@ -12,7 +17,7 @@ "layers": [{ "id": "road", "type": "symbol", - "source": "compositedsource", + "source": "vectorsource", "source-layer": "road_label", "layout": { "text-font": "Open Sans Regular, Arial Unicode MS Regular", @@ -21,7 +26,7 @@ }, { "id": "poi", "type": "symbol", - "source": "compositedsource", + "source": "vectorsource", "source-layer": "poi_label", "layout": { "text-font": "Open Sans Regular, Arial Unicode MS Regular", From f74bc9d8ceff754200d4e8d922a1a0107403db2b Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Thu, 2 Jul 2015 14:53:45 +0300 Subject: [PATCH 191/760] Update the unit tests to check for raster tiles failures --- test/style/pending_resources.cpp | 13 +++++++++++-- test/style/resource_loading.cpp | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/test/style/pending_resources.cpp b/test/style/pending_resources.cpp index 5d13d49a7..4614c187b 100644 --- a/test/style/pending_resources.cpp +++ b/test/style/pending_resources.cpp @@ -55,7 +55,16 @@ TEST_P(PendingResources, DeleteMapObjectWithPendingRequest) { // In the test data below, "sprite" will match both "sprite.json" and "sprite.png" and cause two // requests to be canceled. "resources" will match everything but in practice will only test the -// cancellation of the sprites and "source.json" because we only load the rest after "source.json" +// cancellation of the sprites and "source_*.json" because we only load the rest after "source_*.json" // gets parsed. INSTANTIATE_TEST_CASE_P(Style, PendingResources, - ::testing::Values("source.json", "sprite.json", "sprite.png", "sprite", "vector.pbf", "glyphs.pbf", "resources")); + ::testing::Values( + "source_raster.json", + "source_vector.json", + "sprite.json", + "sprite.png", + "sprite", + "raster.png", + "vector.pbf", + "glyphs.pbf", + "resources")); diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index 700a42c42..f65c9c1f9 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -98,11 +98,11 @@ void runTestCase(MockFileSource::Type type, } catch (const util::GlyphRangeLoadingException&) { EXPECT_EQ(param, "glyphs.pbf"); } catch (const util::SourceLoadingException&) { - EXPECT_EQ(param, "source.json"); + EXPECT_TRUE(param == "source_raster.json" || param == "source_vector.json"); } catch (const util::SpriteLoadingException&) { EXPECT_TRUE(param == "sprite.png" || param == "sprite.json"); } catch (const util::TileLoadingException&) { - EXPECT_EQ(param, "vector.pbf"); + EXPECT_TRUE(param == "raster.png" || param == "vector.pbf"); } catch (const std::exception&) { EXPECT_TRUE(false) << "Unhandled exception."; } @@ -160,6 +160,8 @@ TEST_P(ResourceLoading, RequestWithCorruptedData) { if (param == "vector.pbf") { message << "\\[1(5|6)\\/1638(3|4)\\/1638(3|4)\\]\\: pbf unknown field type exception"; + } else if (param == "raster.png") { + message << "\\[17\\/6553(4|5|6|7)\\/6553(4|5|6|7)\\]\\: error parsing raster image"; } else { message << "\\[test\\/fixtures\\/resources\\/" << param << "\\]"; } @@ -172,4 +174,11 @@ TEST_P(ResourceLoading, RequestWithCorruptedData) { } INSTANTIATE_TEST_CASE_P(Style, ResourceLoading, - ::testing::Values("source.json", "sprite.json", "sprite.png", "vector.pbf", "glyphs.pbf")); + ::testing::Values( + "source_raster.json", + "source_vector.json", + "sprite.json", + "sprite.png", + "raster.png", + "vector.pbf", + "glyphs.pbf")); From 4870bccabaf952ae076850e550ca232526695510 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 29 Apr 2015 09:03:17 -0400 Subject: [PATCH 192/760] Use gdb to get backtraces Core dumps don't work on Travis docker containers. --- scripts/run_tests.sh | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index e4c9e77ea..b8691caa4 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -24,21 +24,5 @@ if [ ! -d "test/node_modules/express" ]; then (cd test; npm install express@4.11.1) fi -RESULT=0 -${CMD} "$@" || RESULT=$? - -if [[ ${RESULT} != 0 ]]; then - echo "The program crashed with exit code ${RESULT}. We're now trying to output the core dump." -fi - -# output core dump if we got one -for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do - gdb ${CMD} ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch - rm -rf ${DUMP} -done - -# now we should present travis with the original -# error code so the run cleanly stops -if [[ ${RESULT} != 0 ]]; then - exit $RESULT -fi +gdb -batch -return-child-result -ex 'set print thread-events off' \ + -ex 'run' -ex 'thread apply all bt' ${CMD} From 108792420c1907f4ecf1d7b03f28e4a5ac33b27f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 2 Jul 2015 15:56:02 -0700 Subject: [PATCH 193/760] Check for matching source when parsing tiles (fixes #1698) --- src/mbgl/map/live_tile_data.cpp | 3 ++- src/mbgl/map/tile_worker.cpp | 10 +++++++--- src/mbgl/map/tile_worker.hpp | 10 ++++++---- src/mbgl/map/vector_tile_data.cpp | 3 ++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index ad983343b..8be918d6d 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -20,9 +20,10 @@ LiveTileData::LiveTileData(const TileID& id_, : TileData(id_), worker(style_.workers), tileWorker(id_, + source_.source_id, + source_.max_zoom, style_, style_.layers, - source_.max_zoom, state, std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index 790a34cf5..e8c8e15b2 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -11,16 +11,18 @@ using namespace mbgl; -TileWorker::TileWorker(const TileID& id_, +TileWorker::TileWorker(TileID id_, + std::string sourceID_, + const uint16_t maxZoom_, Style& style_, std::vector> layers_, - const uint16_t maxZoom_, const std::atomic& state_, std::unique_ptr collision_) : layers(std::move(layers_)), - style(style_), id(id_), + sourceID(sourceID_), maxZoom(maxZoom_), + style(style_), state(state_), collision(std::move(collision_)) { assert(style.sprite); @@ -121,6 +123,8 @@ void TileWorker::parseLayer(const StyleLayer& layer, const GeometryTile& geometr const StyleBucket& styleBucket = *layer.bucket; // Skip this bucket if we are to not render this + if (styleBucket.source != sourceID) + return; if (id.z < std::floor(styleBucket.min_zoom) && std::floor(styleBucket.min_zoom) < maxZoom) return; if (id.z >= std::ceil(styleBucket.max_zoom)) diff --git a/src/mbgl/map/tile_worker.hpp b/src/mbgl/map/tile_worker.hpp index d8c8aa3e0..2306bf1ac 100644 --- a/src/mbgl/map/tile_worker.hpp +++ b/src/mbgl/map/tile_worker.hpp @@ -31,10 +31,11 @@ using TileParseResult = mapbox::util::variant< class TileWorker : public util::noncopyable { public: - TileWorker(const TileID&, + TileWorker(TileID, + std::string sourceID, + uint16_t maxZoom, Style&, std::vector>, - const uint16_t maxZoom, const std::atomic&, std::unique_ptr); ~TileWorker(); @@ -57,10 +58,11 @@ private: template void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&); - Style& style; - const TileID id; + const std::string sourceID; const uint16_t maxZoom; + + Style& style; const std::atomic& state; bool partialParse = false; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 04ef339c4..c87cc370e 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -20,9 +20,10 @@ VectorTileData::VectorTileData(const TileID& id_, : TileData(id_), worker(style_.workers), tileWorker(id_, + source_.source_id, + source_.max_zoom, style_, style_.layers, - source_.max_zoom, state, std::make_unique(id_.z, 4096, source_.tile_size * id.overscaling, From 505d1cdbf7da4958ccaf32fc6433563d7ff8741f Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 2 Jul 2015 22:24:18 -0400 Subject: [PATCH 194/760] Set collision debug with setDebugActive: Forgot this when collision was added to toggleDebug. isDebugActive still relies soley on plain getDebug(). --- platform/ios/MGLMapView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 06a2ef36b..38ecb6608 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1382,6 +1382,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)setDebugActive:(BOOL)debugActive { _mbglMap->setDebug(debugActive); + _mbglMap->setCollisionDebug(debugActive); } - (BOOL)isDebugActive From 2bb1812ae41a99e427a3b40d4c31a9bc4d42fba9 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 2 Jul 2015 22:41:26 -0400 Subject: [PATCH 195/760] Add collision check to `isDebugActive` --- platform/ios/MGLMapView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 38ecb6608..e74fd7f39 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1387,7 +1387,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (BOOL)isDebugActive { - return _mbglMap->getDebug(); + return (_mbglMap->getDebug() || _mbglMap->getCollisionDebug()); } - (void)resetNorth From d8af7319b49c63f9ff5049abe2fe73cac22e45fc Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Fri, 3 Jul 2015 13:49:38 +0300 Subject: [PATCH 196/760] Fixed test run script - If we cannot find 'gdb' on the system (eg. OSX does not come with gdb by default), just run the command for now; - We were missing passing the whole list of parameters, so I've replaced $1 with $@ and added '--args' for gdb to forward the params to the program call; --- scripts/run_tests.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index b8691caa4..692269e4f 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -5,7 +5,7 @@ set -o pipefail if [ `uname -s` = 'Darwin' ]; then HOST=${HOST:-osx} ; else HOST=${HOST:-linux} ; fi -CMD=$1 +CMD=$@ shift # allow writing core files @@ -24,5 +24,9 @@ if [ ! -d "test/node_modules/express" ]; then (cd test; npm install express@4.11.1) fi -gdb -batch -return-child-result -ex 'set print thread-events off' \ - -ex 'run' -ex 'thread apply all bt' ${CMD} +if which -s 'gdb'; then + gdb -batch -return-child-result -ex 'set print thread-events off' \ + -ex 'run' -ex 'thread apply all bt' --args ${CMD} ; +else + ${CMD} ; +fi From ec93fba84a7ad8440db79e1567dbffd350d5387f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Sun, 5 Jul 2015 01:35:04 -0700 Subject: [PATCH 197/760] Fixed crash on launch on iOS 7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In #1793, I conflated the compass container’s constraints to its parent view with the compass container’s content size constraints, but only in the iOS 7 code path. The result was a crash on launch due to an unprepared view hierarchy. Fixes #1818. --- platform/ios/MGLMapView.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 06a2ef36b..cd6ab6ad9 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -539,7 +539,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) multiplier:1 constant:5]]; - [compassContainerConstraints addObject: + [compassContainer addConstraint: [NSLayoutConstraint constraintWithItem:compassContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual @@ -548,7 +548,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) multiplier:1 constant:self.compassView.image.size.width]]; - [compassContainerConstraints addObject: + [compassContainer addConstraint: [NSLayoutConstraint constraintWithItem:compassContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual @@ -562,7 +562,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } else { - [compassContainer addConstraints:compassContainerConstraints]; + [constraintParentView addConstraints:compassContainerConstraints]; } // logo bug From c15c26eb3d1c3ed70d82f8fd5ad57d80b4839a6b Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Sun, 5 Jul 2015 16:25:37 +0300 Subject: [PATCH 198/760] Update YCM to support OSX Besides a couple file path modifiers, we need to manually add the OSX SDK system root path. --- .ycm_extra_conf.py | 63 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 2f6dc2459..be5e3a5c1 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -80,14 +80,18 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): return new_flags +def FlagsForFileDefault(): + return { + 'flags': default_flags, + 'do_cache': False, + } + + def FlagsForFileLinux(filename): working_directory = os.path.join('build', 'linux') if not os.path.exists(os.path.join(DirectoryOfThisScript(), working_directory)): - return { - 'flags': default_flags, - 'do_cache': False, - } + return FlagsForFileDefault() relative_source_path = filename.replace(DirectoryOfThisScript() + '/', '') relative_obj_path = os.path.splitext(relative_source_path)[0] + '.o' @@ -98,10 +102,7 @@ def FlagsForFileLinux(filename): stdout, stderr = p.communicate() if p.returncode: - return { - 'flags': default_flags, - 'do_cache': False, - } + return FlagsForFileDefault() flags = stdout.split(' ') flags_relative = MakeRelativePathsInFlagsAbsolute(flags, os.path.join(DirectoryOfThisScript(), working_directory)) @@ -113,11 +114,49 @@ def FlagsForFileLinux(filename): } +def FlagsForFileDarwin(filename): + working_directory = os.path.join('build', 'osx') + + if not os.path.exists(os.path.join(DirectoryOfThisScript(), working_directory)): + return FlagsForFileDefault() + + relative_source_path = filename.replace(DirectoryOfThisScript() + '/', '') + relative_obj_path = os.path.splitext(relative_source_path)[0] + '.o' + + make_cmd = 'make -pn |grep "%s :=" |grep "NDEBUG" |tail -n 1 | cut -d\' \' -f4-' % relative_obj_path + + p = subprocess.Popen([make_cmd], shell=True, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + + if p.returncode: + return FlagsForFileDefault() + + flags = stdout.split(' ') + flags_relative = MakeRelativePathsInFlagsAbsolute(flags, os.path.join(DirectoryOfThisScript(), working_directory)) + flags_final = [flag for flag in flags_relative if flag.startswith("-")] + + # Append OSX sysroot SDK path + xcrun_cmd = 'xcrun --show-sdk-path' + + p = subprocess.Popen([xcrun_cmd], shell=True, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + + if p.returncode: + return FlagsForFileDefault() + + flags_final.append('-isysroot ' + stdout) + + return { + 'flags': flags_final, + 'do_cache': True, + } + + def FlagsForFile(filename, **kwargs): if sys.platform.startswith('linux'): return FlagsForFileLinux(filename) - return { - 'flags': default_flags, - 'do_cache': False, - } + if sys.platform.startswith('darwin'): + return FlagsForFileDarwin(filename) + + return FlagsForFileDefault() From f6b8dd4be9677bae18f3d6e50ee706cc3693b933 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Tue, 30 Jun 2015 16:13:40 +0300 Subject: [PATCH 199/760] Add simple static method to retrieve the current loop In the lines of how we do for getting information of the current thread. --- src/mbgl/map/map_context.cpp | 2 +- src/mbgl/map/raster_tile_data.cpp | 2 +- src/mbgl/map/source.cpp | 2 +- src/mbgl/map/sprite.cpp | 4 ++-- src/mbgl/map/vector_tile_data.cpp | 2 +- src/mbgl/text/glyph_pbf.cpp | 2 +- src/mbgl/util/run_loop.hpp | 8 ++++++-- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index fc4766f3a..b4bebb4f6 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -106,7 +106,7 @@ void MapContext::setStyleURL(const std::string& url) { base = styleURL.substr(0, pos + 1); } - styleRequest = fs->request({ Resource::Kind::Style, styleURL }, util::RunLoop::current.get()->get(), [this, base](const Response &res) { + styleRequest = fs->request({ Resource::Kind::Style, styleURL }, util::RunLoop::getLoop(), [this, base](const Response &res) { styleRequest = nullptr; if (res.status == Response::Successful) { diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index a8a1ab03d..ce26586ff 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -30,7 +30,7 @@ void RasterTileData::request(float pixelRatio, state = State::loading; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, this](const Response &res) { + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [url, callback, this](const Response &res) { req = nullptr; if (res.status != Response::Successful) { diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 58f3924ec..d732292e0 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -148,7 +148,7 @@ void Source::load() { } FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Source, info.url }, util::RunLoop::current.get()->get(), [this](const Response &res) { + req = fs->request({ Resource::Kind::Source, info.url }, util::RunLoop::getLoop(), [this](const Response &res) { req = nullptr; if (res.status != Response::Successful) { diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp index 8d05ea9f7..6a310c21c 100644 --- a/src/mbgl/map/sprite.cpp +++ b/src/mbgl/map/sprite.cpp @@ -42,7 +42,7 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_) std::string jsonURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".json"); FileSource* fs = util::ThreadContext::getFileSource(); - jsonRequest = fs->request({ Resource::Kind::JSON, jsonURL }, util::RunLoop::current.get()->get(), [this, jsonURL](const Response &res) { + jsonRequest = fs->request({ Resource::Kind::JSON, jsonURL }, util::RunLoop::getLoop(), [this, jsonURL](const Response &res) { jsonRequest = nullptr; if (res.status == Response::Successful) { body = res.data; @@ -56,7 +56,7 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_) emitSpriteLoadedIfComplete(); }); - spriteRequest = fs->request({ Resource::Kind::Image, spriteURL }, util::RunLoop::current.get()->get(), [this, spriteURL](const Response &res) { + spriteRequest = fs->request({ Resource::Kind::Image, spriteURL }, util::RunLoop::getLoop(), [this, spriteURL](const Response &res) { spriteRequest = nullptr; if (res.status == Response::Successful) { image = res.data; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index c87cc370e..57b42aaaf 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -42,7 +42,7 @@ void VectorTileData::request(float pixelRatio, const std::function& call state = State::loading; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::current.get()->get(), [url, callback, this](const Response &res) { + req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [url, callback, this](const Response &res) { req = nullptr; if (res.status != Response::Successful) { diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index 142461ec7..24993ed72 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -30,7 +30,7 @@ GlyphPBF::GlyphPBF(const std::string& glyphURL, // The prepare call jumps back to the main thread. FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Glyphs, url }, util::RunLoop::current.get()->get(), [&, successCallback, failureCallback](const Response &res) { + req = fs->request({ Resource::Kind::Glyphs, url }, util::RunLoop::getLoop(), [&, successCallback, failureCallback](const Response &res) { req = nullptr; if (res.status != Response::Successful) { diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp index c0a56e956..2175ea977 100644 --- a/src/mbgl/util/run_loop.hpp +++ b/src/mbgl/util/run_loop.hpp @@ -19,6 +19,10 @@ public: RunLoop(uv_loop_t*); ~RunLoop(); + static uv_loop_t* getLoop() { + return current.get()->get(); + } + void stop(); // Invoke fn(args...) on this RunLoop. @@ -69,8 +73,6 @@ public: uv_loop_t* get() { return async.get()->loop; } - static uv::tls current; - private: template class Invoker : public WorkTask { @@ -123,6 +125,8 @@ private: Queue queue; std::mutex mutex; uv::async async; + + static uv::tls current; }; } From ec1b4c43fd711b7f4f924a21e8428868d7a5f78d Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Wed, 1 Jul 2015 08:48:11 +0300 Subject: [PATCH 200/760] Do not force uv_loop_t as the first parameter for a threaded object ctor --- platform/default/sqlite_cache.cpp | 2 +- platform/default/sqlite_cache_impl.hpp | 4 +--- src/mbgl/map/map_context.cpp | 4 ++-- src/mbgl/map/map_context.hpp | 4 +--- src/mbgl/storage/default_file_source.cpp | 8 ++++---- src/mbgl/storage/default_file_source_impl.hpp | 4 +--- src/mbgl/util/thread.hpp | 2 +- src/mbgl/util/worker.cpp | 2 +- test/miscellaneous/thread.cpp | 5 ++--- test/storage/database.cpp | 14 +++++++------- test/style/mock_file_source.cpp | 4 ++-- test/style/resource_loading.cpp | 5 ++--- 12 files changed, 25 insertions(+), 33 deletions(-) diff --git a/platform/default/sqlite_cache.cpp b/platform/default/sqlite_cache.cpp index c1b1daf23..775c2a68a 100644 --- a/platform/default/sqlite_cache.cpp +++ b/platform/default/sqlite_cache.cpp @@ -67,7 +67,7 @@ SQLiteCache::SQLiteCache(const std::string& path_) SQLiteCache::~SQLiteCache() = default; -SQLiteCache::Impl::Impl(uv_loop_t*, const std::string& path_) +SQLiteCache::Impl::Impl(const std::string& path_) : path(path_) { } diff --git a/platform/default/sqlite_cache_impl.hpp b/platform/default/sqlite_cache_impl.hpp index 86c7c07bd..8d90fa881 100644 --- a/platform/default/sqlite_cache_impl.hpp +++ b/platform/default/sqlite_cache_impl.hpp @@ -3,8 +3,6 @@ #include -typedef struct uv_loop_s uv_loop_t; - namespace mapbox { namespace sqlite { class Database; @@ -16,7 +14,7 @@ namespace mbgl { class SQLiteCache::Impl { public: - Impl(uv_loop_t*, const std::string &path = ":memory:"); + Impl(const std::string &path = ":memory:"); ~Impl(); void get(const Resource&, Callback); diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index b4bebb4f6..f6dba408d 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -26,11 +26,11 @@ namespace mbgl { -MapContext::MapContext(uv_loop_t* loop, View& view_, FileSource& fileSource, MapData& data_) +MapContext::MapContext(View& view_, FileSource& fileSource, MapData& data_) : view(view_), data(data_), updated(static_cast(Update::Nothing)), - asyncUpdate(std::make_unique(loop, [this] { update(); })), + asyncUpdate(std::make_unique(util::RunLoop::getLoop(), [this] { update(); })), texturePool(std::make_unique()) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp index 141a8ee96..724920b82 100644 --- a/src/mbgl/map/map_context.hpp +++ b/src/mbgl/map/map_context.hpp @@ -10,8 +10,6 @@ #include -typedef struct uv_loop_s uv_loop_t; - namespace uv { class async; } @@ -30,7 +28,7 @@ struct LatLngBounds; class MapContext : public Style::Observer { public: - MapContext(uv_loop_t*, View&, FileSource&, MapData&); + MapContext(View&, FileSource&, MapData&); ~MapContext(); struct RenderResult { diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp index 5f6bdcb6a..a9908c450 100644 --- a/src/mbgl/storage/default_file_source.cpp +++ b/src/mbgl/storage/default_file_source.cpp @@ -71,12 +71,12 @@ void DefaultFileSource::cancel(Request *req) { // ----- Impl ----- -DefaultFileSource::Impl::Impl(uv_loop_t* loop_, FileCache* cache_, const std::string& root) - : loop(loop_), +DefaultFileSource::Impl::Impl(FileCache* cache_, const std::string& root) + : loop(util::RunLoop::getLoop()), cache(cache_), assetRoot(root.empty() ? platform::assetRoot() : root), - assetContext(AssetContext::createContext(loop_)), - httpContext(HTTPContext::createContext(loop_)) { + assetContext(AssetContext::createContext(loop)), + httpContext(HTTPContext::createContext(loop)) { } DefaultFileRequest* DefaultFileSource::Impl::find(const Resource& resource) { diff --git a/src/mbgl/storage/default_file_source_impl.hpp b/src/mbgl/storage/default_file_source_impl.hpp index 22ca5b6d1..06fe3e39b 100644 --- a/src/mbgl/storage/default_file_source_impl.hpp +++ b/src/mbgl/storage/default_file_source_impl.hpp @@ -8,8 +8,6 @@ #include #include -typedef struct uv_loop_s uv_loop_t; - namespace mbgl { class RequestBase; @@ -33,7 +31,7 @@ struct DefaultFileRequest { class DefaultFileSource::Impl { public: - Impl(uv_loop_t*, FileCache*, const std::string& = ""); + Impl(FileCache*, const std::string& = ""); void add(Request*); void cancel(Request*); diff --git a/src/mbgl/util/thread.hpp b/src/mbgl/util/thread.hpp index 88a709cfa..b6eb48dfb 100644 --- a/src/mbgl/util/thread.hpp +++ b/src/mbgl/util/thread.hpp @@ -118,7 +118,7 @@ void Thread::run(ThreadContext context, P&& params, std::index_sequence< RunLoop loop_(l.get()); loop = &loop_; - Object object_(l.get(), std::get(std::forward

(params))...); + Object object_(std::get(std::forward

(params))...); object = &object_; running.set_value(); diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 9277daccc..1ab6d766f 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -14,7 +14,7 @@ namespace mbgl { class Worker::Impl { public: - Impl(uv_loop_t*, FileSource* fs) { + Impl(FileSource* fs) { // FIXME: Workers should not access the FileSource but it // is currently needed because of GlyphsPBF. See #1664. util::ThreadContext::setFileSource(fs); diff --git a/test/miscellaneous/thread.cpp b/test/miscellaneous/thread.cpp index 78e6ffee6..9d8f6b632 100644 --- a/test/miscellaneous/thread.cpp +++ b/test/miscellaneous/thread.cpp @@ -7,7 +7,7 @@ using namespace mbgl::util; class TestObject { public: - TestObject(uv_loop_t*, std::thread::id otherTid) + TestObject(std::thread::id otherTid) : tid(std::this_thread::get_id()) { EXPECT_NE(tid, otherTid); } @@ -139,8 +139,7 @@ TEST(Thread, context) { class TestWorker { public: - TestWorker(uv_loop_t*) { - } + TestWorker() = default; void send(std::function fn, std::function cb) { fn(); diff --git a/test/storage/database.cpp b/test/storage/database.cpp index a7560262f..b40474c00 100644 --- a/test/storage/database.cpp +++ b/test/storage/database.cpp @@ -13,7 +13,7 @@ TEST_F(Storage, DatabaseDoesNotExist) { Log::setObserver(std::make_unique()); - SQLiteCache::Impl cache(nullptr, "test/fixtures/404/cache.db"); + SQLiteCache::Impl cache("test/fixtures/404/cache.db"); cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { EXPECT_EQ(nullptr, res.get()); @@ -55,7 +55,7 @@ TEST_F(Storage, DatabaseCreate) { Log::setObserver(std::make_unique()); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/cache.db"); + SQLiteCache::Impl cache("test/fixtures/database/cache.db"); cache.get({ Resource::Unknown, "mapbox://test" }, [] (std::unique_ptr res) { EXPECT_EQ(nullptr, res.get()); @@ -111,7 +111,7 @@ TEST_F(Storage, DatabaseLockedRead) { deleteFile("test/fixtures/database/locked.db"); FileLock guard("test/fixtures/database/locked.db"); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/locked.db"); + SQLiteCache::Impl cache("test/fixtures/database/locked.db"); { // First request should fail. @@ -153,7 +153,7 @@ TEST_F(Storage, DatabaseLockedWrite) { deleteFile("test/fixtures/database/locked.db"); FileLock guard("test/fixtures/database/locked.db"); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/locked.db"); + SQLiteCache::Impl cache("test/fixtures/database/locked.db"); { // Adds a file (which should fail). @@ -200,7 +200,7 @@ TEST_F(Storage, DatabaseLockedRefresh) { createDir("test/fixtures/database"); deleteFile("test/fixtures/database/locked.db"); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/locked.db"); + SQLiteCache::Impl cache("test/fixtures/database/locked.db"); // Then, lock the file and try again. FileLock guard("test/fixtures/database/locked.db"); @@ -248,7 +248,7 @@ TEST_F(Storage, DatabaseDeleted) { createDir("test/fixtures/database"); deleteFile("test/fixtures/database/locked.db"); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/locked.db"); + SQLiteCache::Impl cache("test/fixtures/database/locked.db"); { // Adds a file. @@ -295,7 +295,7 @@ TEST_F(Storage, DatabaseInvalid) { deleteFile("test/fixtures/database/invalid.db"); writeFile("test/fixtures/database/invalid.db", "this is an invalid file"); - SQLiteCache::Impl cache(nullptr, "test/fixtures/database/invalid.db"); + SQLiteCache::Impl cache("test/fixtures/database/invalid.db"); { // Adds a file. diff --git a/test/style/mock_file_source.cpp b/test/style/mock_file_source.cpp index 6aa735d0e..6f368bde5 100644 --- a/test/style/mock_file_source.cpp +++ b/test/style/mock_file_source.cpp @@ -17,8 +17,8 @@ namespace mbgl { class MockFileSource::Impl { public: - Impl(uv_loop_t* loop, Type type, const std::string& match) - : type_(type), match_(match), timer_(loop) { + Impl(Type type, const std::string& match) + : type_(type), match_(match), timer_(util::RunLoop::getLoop()) { timer_.start(timeout, timeout, [this] { dispatchPendingRequests(); }); timer_.unref(); } diff --git a/test/style/resource_loading.cpp b/test/style/resource_loading.cpp index f65c9c1f9..fc8905f7b 100644 --- a/test/style/resource_loading.cpp +++ b/test/style/resource_loading.cpp @@ -20,8 +20,7 @@ namespace { class MockMapContext : public Style::Observer { public: - MockMapContext(uv_loop_t* loop, - View& view, + MockMapContext(View& view, FileSource& fileSource, const std::function& callback) : data_(MapMode::Still), @@ -33,7 +32,7 @@ public: transform_.setLatLngZoom({0, 0}, 16); const std::string style = util::read_file("test/fixtures/resources/style.json"); - style_ = std::make_unique + diff --git a/src/mbgl/util/geojsonvt b/src/mbgl/util/geojsonvt index 995ffc72c..ed99a6290 160000 --- a/src/mbgl/util/geojsonvt +++ b/src/mbgl/util/geojsonvt @@ -1 +1 @@ -Subproject commit 995ffc72c556da4b4880a6036fbcf7159fc5ecce +Subproject commit ed99a6290fa42107a982e7f3675aae49d29026b1 From 47b7f3bbae7bdf25e3db5662a75bece1c9736795 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 13:50:05 -0500 Subject: [PATCH 451/760] #1856 - Adding ic_menu.png files --- .../src/main/res/drawable-hdpi/ic_menu.png | Bin 0 -> 116 bytes .../src/main/res/drawable-mdpi/ic_menu.png | Bin 0 -> 99 bytes .../src/main/res/drawable-xhdpi/ic_menu.png | Bin 0 -> 135 bytes .../src/main/res/drawable-xxhdpi/ic_menu.png | Bin 0 -> 183 bytes .../src/main/res/drawable-xxxhdpi/ic_menu.png | Bin 0 -> 235 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_menu.png diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..8962cbd91f75a600915d7277a2f9867f9a6ebc89 GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBtUX;ELn>~)y>gI~!GVMM;FtR) z>o^=+q!0eLIkYTSueJdQzVT=97-cEQUQ(20VgeFCw!(sbNB(*DWRAAR^UMSq!{F)a K=d#Wzp$Pyxy(5hP literal 0 HcmV?d00001 diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..c62db8c865860add1df1b7335b5f70279390fdc5 GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjTAnVBAr-fhfBgS%&#Y^Bcm=ES td8Vw!#?1*PliZan!Ne9jiJ4*y4DzQ`?VKm=-3-*l;OXk;vd$@?2>|EB9IyZY literal 0 HcmV?d00001 diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..36859f094a65ce6c7160049049367832dd46176d GIT binary patch literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCKTj9OkcwMxuO8%NFyLV}e7}E- zwX3(3$O(pDKQ5Ws*8`P+!8?X!vJ3~Z8$_8H7y^#^S4v3Nj^#=^+ WmQDHkYu}7{AQ4YjKbLh*2~7anb|sns literal 0 HcmV?d00001 diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..1a5109828f340e78e63561152bfe6512b334ae54 GIT binary patch literal 183 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!b)GJcAr-gY-f-k)FyLTu{4?85 zB;mmfwd@2t5w(*q_?Q$N8WslAnR%U^e5V(8GFcAoe@Q zV|ySfL24pRA$qGP)C(wpOaZIrgy><|{Ln>~)y?Kz6!GMFs@pt~O zHwx<;l$3LrXHMTZY09%t91ILV#K@q)z`(-Lz`!8Dz`(@d02N%I%J9q`8zGFC}seq&x4!+19w_ Date: Fri, 7 Aug 2015 14:01:25 -0500 Subject: [PATCH 452/760] #1856 - Setting default style to Mapbox Streets --- .../src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 1 - .../src/main/res/layout/activity_main.xml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index a637ee3f2..2005005ce 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -92,7 +92,6 @@ public class MainActivity extends AppCompatActivity { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mapView = (MapView) findViewById(R.id.mainMapView); // Load the access token try { diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 6cae39229..2a8c99f5f 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -53,6 +53,7 @@ android:id="@+id/mainMapView" android:layout_width="match_parent" android:layout_height="match_parent" + mapbox:styleUrl="asset://styles/mapbox-streets-v7.json" /> Date: Fri, 7 Aug 2015 14:31:01 -0500 Subject: [PATCH 453/760] #1856 - Setting up Navigation Drawer --- .../mapbox/mapboxgl/testapp/MainActivity.java | 23 +++++++++++++++++++ .../src/main/res/layout/activity_main.xml | 4 +--- .../src/main/res/menu/menu_drawer.xml | 14 +++++++++++ .../src/main/res/menu/menu_main.xml | 8 ------- 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 2005005ce..646285ceb 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -3,6 +3,8 @@ package com.mapbox.mapboxgl.testapp; import android.graphics.Color; import android.graphics.PointF; import android.os.Bundle; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -92,6 +94,11 @@ public class MainActivity extends AppCompatActivity { mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view); + if (navigationView != null) { + setupDrawerContent(navigationView); + } + mapView = (MapView) findViewById(R.id.mainMapView); // Load the access token try { @@ -241,6 +248,10 @@ public class MainActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case android.R.id.home: + mDrawerLayout.openDrawer(GravityCompat.START); + return true; + case R.id.action_gps: // Toggle GPS position updates toggleGps(!mapView.isMyLocationEnabled()); @@ -275,6 +286,18 @@ public class MainActivity extends AppCompatActivity { } } + private void setupDrawerContent(NavigationView navigationView) { + navigationView.setNavigationItemSelectedListener( + new NavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(MenuItem menuItem) { + menuItem.setChecked(true); + mDrawerLayout.closeDrawers(); + return true; + } + }); + } + /** * Enabled / Disable GPS location updates along with updating the UI * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 2a8c99f5f..bd593ad37 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -4,7 +4,6 @@ android:id="@+id/drawer_layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:mapbox="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> @@ -86,7 +85,6 @@ android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" - tools:headerLayout="@layout/nav_header" - tools:menu="@menu/drawer_view"/> + mapbox:menu="@menu/menu_drawer"/> diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml new file mode 100644 index 000000000..03ab05651 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -0,0 +1,14 @@ + +

+ + + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml index b682fe0cb..8981b7a95 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml @@ -1,13 +1,5 @@ - - Date: Fri, 7 Aug 2015 14:50:18 -0500 Subject: [PATCH 454/760] #1856 - Moving ActionBar actions to Navigation Drawer --- .../mapbox/mapboxgl/testapp/MainActivity.java | 67 ++++++++++--------- .../src/main/res/menu/menu_drawer.xml | 8 +++ .../src/main/res/menu/menu_main.xml | 8 --- 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 646285ceb..ada2fc45b 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -11,8 +11,6 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.GestureDetector; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; @@ -230,6 +228,7 @@ public class MainActivity extends AppCompatActivity { // Other events // +/* // Adds items to the action bar menu @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -243,6 +242,7 @@ public class MainActivity extends AppCompatActivity { } return super.onCreateOptionsMenu(menu); } +*/ // Called when pressing action bar items @Override @@ -251,36 +251,6 @@ public class MainActivity extends AppCompatActivity { case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); return true; - - case R.id.action_gps: - // Toggle GPS position updates - toggleGps(!mapView.isMyLocationEnabled()); - updateMap(); - return true; - - case R.id.action_debug: - // Toggle debug mode - mapView.toggleDebug(); - - // Show the FPS counter - if (mapView.isDebugActive()) { - mFpsTextView.setVisibility(View.VISIBLE); - mFpsTextView.setText(getResources().getString(R.string.label_fps)); - } else { - mFpsTextView.setVisibility(View.INVISIBLE); - } - return true; - - case R.id.action_markers: - // Toggle markers - toggleMarkers(!mIsMarkersOn); - return true; - - case R.id.action_compass: - // Toggle compass - mapView.setCompassEnabled(!mapView.isCompassEnabled()); - return true; - default: return super.onOptionsItemSelected(item); } @@ -293,6 +263,39 @@ public class MainActivity extends AppCompatActivity { public boolean onNavigationItemSelected(MenuItem menuItem) { menuItem.setChecked(true); mDrawerLayout.closeDrawers(); + + // Respond To Selection + switch (menuItem.getItemId()) { + case R.id.action_gps: + // Toggle GPS position updates + toggleGps(!mapView.isMyLocationEnabled()); + updateMap(); + break; + + case R.id.action_debug: + // Toggle debug mode + mapView.toggleDebug(); + + // Show the FPS counter + if (mapView.isDebugActive()) { + mFpsTextView.setVisibility(View.VISIBLE); + mFpsTextView.setText(getResources().getString(R.string.label_fps)); + } else { + mFpsTextView.setVisibility(View.INVISIBLE); + } + break; + + case R.id.action_markers: + // Toggle markers + toggleMarkers(!mIsMarkersOn); + break; + + case R.id.action_compass: + // Toggle compass + mapView.setCompassEnabled(!mapView.isCompassEnabled()); + break; + } + return true; } }); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml index 03ab05651..4a1211716 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -10,5 +10,13 @@ android:icon="@android:drawable/ic_menu_myplaces" android:title="@string/action_point_annotations" /> + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml index 8981b7a95..014fa25d6 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml @@ -1,11 +1,3 @@ - - From bfd039853ca397fc546c04f144c90469d0913c0c Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 15:37:26 -0500 Subject: [PATCH 455/760] #1856 - Adding Styles to Navigation View with separator --- .../mapbox/mapboxgl/testapp/MainActivity.java | 89 ++++++------------- .../src/main/res/menu/menu_drawer.xml | 21 +++++ .../src/main/res/values/strings.xml | 19 ++-- 3 files changed, 57 insertions(+), 72 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index ada2fc45b..ce245daf8 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -3,6 +3,7 @@ package com.mapbox.mapboxgl.testapp; import android.graphics.Color; import android.graphics.PointF; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -294,6 +295,26 @@ public class MainActivity extends AppCompatActivity { // Toggle compass mapView.setCompassEnabled(!mapView.isCompassEnabled()); break; + + case R.id.actionStyleMapboxStreets: + changeMapStyle(getString(R.string.styleURLMapboxStreets)); + break; + + case R.id.actionStyleEmerald: + changeMapStyle(getString(R.string.styleURLEmerald)); + break; + + case R.id.actionStyleLight: + changeMapStyle(getString(R.string.styleURLLight)); + break; + + case R.id.actionStyleDark: + changeMapStyle(getString(R.string.styleURLDark)); + break; + + case R.id.actionStyleSatellite: + changeMapStyle(getString(R.string.styleURLSatellite)); + break; } return true; @@ -301,6 +322,10 @@ public class MainActivity extends AppCompatActivity { }); } + private void changeMapStyle(@NonNull String styleURL) { + mapView.setStyleUrl(styleURL); + } + /** * Enabled / Disable GPS location updates along with updating the UI * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled @@ -400,70 +425,6 @@ public class MainActivity extends AppCompatActivity { mapView.removeAnnotations(); } - // This class handles style change events - private class StyleSpinnerListener implements AdapterView.OnItemSelectedListener { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - switch (position) { - - // Mapbox Streets - case 0: - mapView.setStyleUrl("asset://styles/mapbox-streets-v7.json"); - mapView.removeAllStyleClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Emerald - case 1: - mapView.setStyleUrl("asset://styles/emerald-v7.json"); - mapView.removeAllStyleClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Light - case 2: - mapView.setStyleUrl("asset://styles/light-v7.json"); - mapView.removeAllStyleClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Dark - case 3: - mapView.setStyleUrl("asset://styles/dark-v7.json"); - mapView.removeAllStyleClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Outdoors - case 4: - mapView.setStyleUrl("asset://styles/outdoors-v7.json"); - mapView.removeAllStyleClasses(); - mClassSpinner.setVisibility(View.VISIBLE); - mClassSpinner.setAdapter(mOutdoorsClassAdapter); - mClassSpinner.setOnItemSelectedListener(new OutdoorClassSpinnerListener()); - break; - - default: - onNothingSelected(parent); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - mapView.setStyleUrl(""); - } - } - // This class handles outdoor class change events private class OutdoorClassSpinnerListener implements AdapterView.OnItemSelectedListener { diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml index 4a1211716..1474161e0 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -1,5 +1,6 @@ + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 0022fe045..40bd286e5 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -5,19 +5,22 @@ Toggle GPS location Toggle Compass - Toggle debug mode - Toggle point annotations FPS: - - Mapbox Streets - Emerald - Light - Dark - + Mapbox Streets + Emerald + Light + Dark + Satellite + + asset://styles/mapbox-streets-v7.json + asset://styles/emerald-v7.json + asset://styles/light-v7.json + asset://styles/dark-v7.json + asset://styles/satellite-v7.json Day From 23d88928beb40b230e5900800c83b4b0789b955a Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 15:41:49 -0500 Subject: [PATCH 456/760] #1856 - Setting map style programmatically and setting corresponding menu item checked --- .../main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 3 +++ .../src/main/res/layout/activity_main.xml | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index ce245daf8..61bd74802 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -112,6 +112,9 @@ public class MainActivity extends AppCompatActivity { mapView.setOnFpsChangedListener(new MyOnFpsChangedListener()); mapView.addOnMapChangedListener(new MyOnMapChangedListener()); + changeMapStyle(getString(R.string.styleURLMapboxStreets)); + navigationView.getMenu().findItem(R.id.actionStyleMapboxStreets).setChecked(true); + final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { public void onLongPress(final MotionEvent e) { float x = e.getX(); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index bd593ad37..616e5fe1d 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -52,7 +52,6 @@ android:id="@+id/mainMapView" android:layout_width="match_parent" android:layout_height="match_parent" - mapbox:styleUrl="asset://styles/mapbox-streets-v7.json" /> Date: Fri, 7 Aug 2015 15:44:16 -0500 Subject: [PATCH 457/760] #1856 - Removing old toolbar and spinner setup code --- .../com/mapbox/mapboxgl/testapp/MainActivity.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 61bd74802..8d275d297 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -144,18 +144,6 @@ public class MainActivity extends AppCompatActivity { mMapFrameLayout = (FrameLayout) findViewById(R.id.layout_map); /* - // Add a toolbar as the action bar - Toolbar mainToolbar = (Toolbar) findViewById(R.id.toolbar_main); - setSupportActionBar(mainToolbar); - getSupportActionBar().setDisplayShowTitleEnabled(false); - - // Add the spinner to select map styles - Spinner styleSpinner = (Spinner) findViewById(R.id.spinner_style); - ArrayAdapter styleAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(), - R.array.style_list, android.R.layout.simple_spinner_dropdown_item); - styleSpinner.setAdapter(styleAdapter); - styleSpinner.setOnItemSelectedListener(new StyleSpinnerListener()); - // Add the spinner to select class styles mClassSpinner = (Spinner) findViewById(R.id.spinner_class); mOutdoorsClassAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(), From 30a0617e37c30854cb53d5d1a7f3372a7dbcaaf5 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 15:56:24 -0500 Subject: [PATCH 458/760] #1856 - Cleaning up annotations selection option and centering map on annotation display area --- .../mapbox/mapboxgl/testapp/MainActivity.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 8d275d297..5e0e0002a 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -30,7 +30,7 @@ import com.mapbox.mapboxgl.annotations.PolylineOptions; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.views.MapView; import io.fabric.sdk.android.Fabric; -import org.json.JSONException; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -64,8 +64,8 @@ public class MainActivity extends AppCompatActivity { // Used for GPS private MenuItem mGpsMenuItem; - // Used for markers - private boolean mIsMarkersOn = false; + // Used for Annotations + private boolean mIsAnnotationsOn = false; private Marker marker; @@ -279,7 +279,7 @@ public class MainActivity extends AppCompatActivity { case R.id.action_markers: // Toggle markers - toggleMarkers(!mIsMarkersOn); + toggleAnnotations(!mIsAnnotationsOn); break; case R.id.action_compass: @@ -341,21 +341,23 @@ public class MainActivity extends AppCompatActivity { } /** - * Enable / Disable markers. + * Enable / Disable Annotations. * - * @param enableMarkers + * @param enableAnnotations True to display, False to hide */ - private void toggleMarkers(boolean enableMarkers) { - if (enableMarkers) { - if (!mIsMarkersOn) { - mIsMarkersOn = true; + private void toggleAnnotations(boolean enableAnnotations) { + if (enableAnnotations) { + if (!mIsAnnotationsOn) { + mIsAnnotationsOn = true; addMarkers(); addPolyline(); addPolygon(); + mapView.setZoomLevel(7); + mapView.setCenterCoordinate(new LatLng(38.11727, -122.22839)); } } else { - if (mIsMarkersOn) { - mIsMarkersOn = false; + if (mIsAnnotationsOn) { + mIsAnnotationsOn = false; removeAnnotations(); } } @@ -386,9 +388,8 @@ public class MainActivity extends AppCompatActivity { .add(latLngs) .width(2) .color(Color.RED)); - } catch (IOException e) { - e.printStackTrace(); - } catch (JSONException e) { + } catch (Exception e) { + Log.e(TAG, "Error adding Polyline: "+ e); e.printStackTrace(); } } @@ -405,9 +406,8 @@ public class MainActivity extends AppCompatActivity { .strokeColor(Color.MAGENTA) .fillColor(Color.BLUE)); Polygon polygon = map.addPolygons(opts).get(0); - } catch (IOException e) { - e.printStackTrace(); - } catch (JSONException e) { + } catch (Exception e) { + Log.e(TAG, "Error adding Polygon: "+ e); e.printStackTrace(); } } From c8a399aa2f96d9f460b5b36bcd8767a2c5ea7a5f Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Fri, 7 Aug 2015 16:33:17 -0500 Subject: [PATCH 459/760] #2014 - Commenting out setElevation() --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 953bd6ae0..cd7e84cee 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -309,7 +309,7 @@ public class MapView extends FrameLayout implements LocationListener { trackingModeButton = new FloatingActionButton(mContext); trackingModeButton.setContentDescription(getResources().getString(R.string.trackingModeButtonContentDescription)); trackingModeButton.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.background_material_light))); - trackingModeButton.setElevation(4); +// trackingModeButton.setElevation(4); trackingModeButton.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_my_location_black_24dp)); LayoutParams lp2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); lp2.gravity = Gravity.BOTTOM | Gravity.END; From 76c937f119bdc673fef2675a4db22731cde85dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Thu, 6 Aug 2015 12:41:51 +0200 Subject: [PATCH 460/760] set thread priority on Linux --- platform/default/thread.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/platform/default/thread.cpp b/platform/default/thread.cpp index c0a1069b9..12d1802c2 100644 --- a/platform/default/thread.cpp +++ b/platform/default/thread.cpp @@ -1,11 +1,23 @@ #include +#include + +#include +#include + namespace mbgl { namespace platform { void makeThreadLowPriority() { - // no-op +#ifdef SCHED_IDLE + struct sched_param param; + param.sched_priority = 0; + int status = sched_setscheduler(0, SCHED_IDLE, ¶m); + if (status != 0) { + Log::Warning(Event::General, "Couldn't set thread scheduling policy"); + } +#endif } -} -} +} // namespace platform +} // namespace mbgl From 0a527f623ed88ef46c104e97a86239b012141074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 10 Aug 2015 22:39:08 +0300 Subject: [PATCH 461/760] don't constantly repaint by setting needsRepaint to false fix by @brunoabinader --- src/mbgl/map/map_context.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 33e4e6d18..c14a61b8b 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -345,12 +345,8 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) } view.swap(); - viewInvalidated = false; - - if (style->hasTransitions() || painter->needsAnimation()) { - data.setNeedsRepaint(true); - } + data.setNeedsRepaint(style->hasTransitions() || painter->needsAnimation()); return isLoaded(); } From 9cd4a7ccb73311a9dc542afb631db0cb373b55bb Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Mon, 10 Aug 2015 15:41:02 -0500 Subject: [PATCH 462/760] #1856 - Removing FAB from SDK and replacing with intial tracking mode API. Making Compass visible under Toolbar. --- .../com/mapbox/mapboxgl/views/MapView.java | 107 +++++------------- .../drawable-hdpi/ic_explore_black_24dp.png | Bin 465 -> 0 bytes .../ic_my_location_black_24dp.png | Bin 549 -> 0 bytes .../drawable-mdpi/ic_explore_black_24dp.png | Bin 322 -> 0 bytes .../ic_my_location_black_24dp.png | Bin 341 -> 0 bytes .../drawable-xhdpi/ic_explore_black_24dp.png | Bin 624 -> 0 bytes .../ic_my_location_black_24dp.png | Bin 660 -> 0 bytes .../drawable-xxhdpi/ic_explore_black_24dp.png | Bin 913 -> 0 bytes .../ic_my_location_black_24dp.png | Bin 976 -> 0 bytes .../ic_explore_black_24dp.png | Bin 1219 -> 0 bytes .../ic_my_location_black_24dp.png | Bin 1334 -> 0 bytes .../src/main/res/values/strings.xml | 1 - .../mapbox/mapboxgl/testapp/MainActivity.java | 14 +++ .../src/main/res/menu/menu_drawer.xml | 13 +++ .../src/main/res/values/strings.xml | 3 + 15 files changed, 57 insertions(+), 81 deletions(-) delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_explore_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_my_location_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_explore_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_explore_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_explore_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_my_location_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_explore_black_24dp.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_my_location_black_24dp.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 953bd6ae0..fe146cf0d 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -11,7 +11,6 @@ import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.PointF; import android.hardware.GeomagneticField; @@ -26,7 +25,6 @@ import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; -import android.support.v4.content.res.ResourcesCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.text.TextUtils; @@ -126,13 +124,18 @@ public class MapView extends FrameLayout implements LocationListener { // Holds the context private Context mContext; - // Used for GPS + // Used for GPS / Location private boolean mIsGpsOn = false; private LostApiClient mLocationClient; private LocationRequest mLocationRequest; private ImageView mGpsMarker; private Location mGpsLocation; + public enum UserLocationTrackingMode { + NONE, FOLLOW, FOLLOW_BEARING; + } + private UserLocationTrackingMode userLocationTrackingMode = UserLocationTrackingMode.FOLLOW; + // Used for compass private boolean mIsCompassEnabled = true; private ImageView mCompassView; @@ -149,13 +152,13 @@ public class MapView extends FrameLayout implements LocationListener { private boolean mCompassValid = false; // Used for map toggle mode - private FloatingActionButton trackingModeButton; - private int trackingMode = 0; +// private FloatingActionButton trackingModeButton; private long t0 = new Date().getTime(); // Used to manage Event Listeners private ArrayList mOnMapChangedListener; + // // Properties // @@ -299,26 +302,12 @@ public class MapView extends FrameLayout implements LocationListener { mCompassView.setContentDescription(getResources().getString(R.string.compassContentDescription)); LayoutParams lp = new FrameLayout.LayoutParams((int)(48 * mScreenDensity), (int)(48 * mScreenDensity)); lp.gravity = Gravity.TOP | Gravity.END; - int tenDp = (int)(10 * mScreenDensity); - lp.setMargins(tenDp, tenDp, tenDp, tenDp); + int twentyDp = (int)(20 * mScreenDensity); + lp.setMargins(twentyDp, twentyDp * 4, twentyDp, twentyDp); mCompassView.setLayoutParams(lp); addView(mCompassView); mCompassView.setOnClickListener(new CompassOnClickListener()); - // Setup tracking mode - trackingModeButton = new FloatingActionButton(mContext); - trackingModeButton.setContentDescription(getResources().getString(R.string.trackingModeButtonContentDescription)); - trackingModeButton.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.background_material_light))); - trackingModeButton.setElevation(4); - trackingModeButton.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.ic_my_location_black_24dp)); - LayoutParams lp2 = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - lp2.gravity = Gravity.BOTTOM | Gravity.END; - int twentyDp = (int)(20 * mScreenDensity); - lp2.setMargins(twentyDp, twentyDp, twentyDp, twentyDp); - trackingModeButton.setLayoutParams(lp2); - addView(trackingModeButton); - trackingModeButton.setOnClickListener(new ToggleModeClickListener()); - // Setup Support For Listener Tracking // MapView's internal listener is setup in onCreate() mOnMapChangedListener = new ArrayList(); @@ -512,6 +501,14 @@ public class MapView extends FrameLayout implements LocationListener { mNativeMapView.toggleCollisionDebug(); } + public UserLocationTrackingMode getUserLocationTrackingMode() { + return userLocationTrackingMode; + } + + public void setUserLocationTrackingMode(UserLocationTrackingMode userLocationTrackingMode) { + this.userLocationTrackingMode = userLocationTrackingMode; + } + public boolean isFullyLoaded() { return mNativeMapView.isFullyLoaded(); } @@ -916,10 +913,6 @@ public class MapView extends FrameLayout implements LocationListener { return false; } - if (trackingMode != 0) { - setTrackingMode(0); - } - // Cancel any animation mNativeMapView.cancelTransitions(); // TODO need to test canceling // transitions with touch @@ -1608,11 +1601,9 @@ public class MapView extends FrameLayout implements LocationListener { @Override public void onClick(View view) { - if(trackingMode == 2){ - setTrackingMode(0); - } resetNorth(); } + } /** @@ -1629,12 +1620,6 @@ public class MapView extends FrameLayout implements LocationListener { private void updateLocation(Location location) { if (location != null) { mGpsLocation = location; - - // Update map position if in follow mode - if (trackingMode != 0) { - setCenterCoordinate(new LatLng(mGpsLocation)); - } - updateMap(); } } @@ -1679,7 +1664,7 @@ public class MapView extends FrameLayout implements LocationListener { mGpsMarker.requestLayout(); // Update direction if tracking mode - if(trackingMode == 2 && mCompassValid){ + if(userLocationTrackingMode == UserLocationTrackingMode.FOLLOW_BEARING && mCompassValid){ // TODO need to do proper filtering (see branch filter-compass) or else map will lock up because of all the compass events long t = new Date().getTime(); if((t-t0)>1000){ @@ -1688,6 +1673,13 @@ public class MapView extends FrameLayout implements LocationListener { } } +/* + // Update map position if NOT in NONE mode + if (userLocationTrackingMode != UserLocationTrackingMode.NONE) { + setCenterCoordinate(new LatLng(mGpsLocation)); + } +*/ + /* // Used For User Location Bearing UI if (mGpsLocation.hasBearing() || mCompassValid) { @@ -1708,49 +1700,4 @@ public class MapView extends FrameLayout implements LocationListener { } } } - - private class ToggleModeClickListener implements OnClickListener { - @Override - public void onClick(View v) { - setTrackingMode((trackingMode + 1) % 3); - } - } - - public void setTrackingMode(int trackingMode) { - - this.trackingMode = trackingMode; - - switch (trackingMode) { - - case 0: { - trackingModeButton.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_my_location_black_24dp, mContext.getTheme())); - trackingModeButton.setColorFilter(Color.TRANSPARENT); - updateMap(); - } - break; - - case 1: { - trackingModeButton.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_my_location_black_24dp, mContext.getTheme())); - trackingModeButton.setColorFilter(Color.BLUE); - if(mGpsLocation != null){ - setCenterCoordinate(new LatLng(mGpsLocation)); - } - updateMap(); - } - break; - - case 2: { - trackingModeButton.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_explore_black_24dp, mContext.getTheme())); - trackingModeButton.setColorFilter(Color.BLUE); - if(mGpsLocation != null){ - setCenterCoordinate(new LatLng(mGpsLocation)); - } - updateMap(); - } - break; - - default: - break; - } - } } diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_explore_black_24dp.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_explore_black_24dp.png deleted file mode 100644 index f4bc5acd008d74f521e382cc37930af05e787cc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSWDP)zzL>$B+NW=(k(oRL_AU;FzAqu5*C^0&T zOH#PrlBm#U%VTt^ddBr-ipjD>8w;9Ll2VBV9+G%7o2U=DPIa0>Q>Y@@Q(4ggJOPQ63J zPf@m7_Of3r$l4J(^(8JdwXjczgZ=7Yq+FWCCT}XAvHG*iR93pm_1S5Hp zGX{g3?7{FW1~WTE1v&0B)$aWAV|TJUEMI4Vh=>I~Eg~f)`bUbsvBa4vI%APZJ|NE; zS0tp?c;%8+^1MTZHfhFaF~^AZ80bfR8dQmisnWF0?lOWOivMAs61I>7o-}(5etQc3 zW(t=Q*GlZ*QPJi9WQ;5UCnl3HONKmcUCkIi0XHVsB!|$`+Jp$PIdkEDiW5@$l+e}KHK>XL2GeFWdrUB7$wV6n8>Ei0iSS^K z=Z7{5y;D_$J7-ZHp=ORx?M)`Kf-yqJS=6;x2@N7&(Tl%`L%Ya#YYk`7z30|sQ4g80 z*Lne78Wk85Q(Xo|0QUIf@zkeKuOA|f-32DEnDS;fBGv_$1n#m_CUSyLx zUAo+}P64FMv&k;wl-GS<6W|kYt7j@lhB^8sazfy#`K4`S29I5>#|uP4vFFe9;gv>NR1ZS{7KkmM6)zWGe(^m-XX^d7xb_zdYrRNjt`h% no>RS36~_OkuMzu0>#O_)RZ4aRmaPDG00000NkvXXu0mjf7M<-G diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_explore_black_24dp.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_explore_black_24dp.png deleted file mode 100644 index 3c9da64daa13096c7d7309c61530260d8c58228b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 322 zcmV-I0lof-P)db|w-s4}^E&zOs{y}JZgEeY3xY9-cCUm`HF4TT% zRoL~uDLdGr)@qb4{ml;=QThNXB{q_XSK%?rI{=q9{`3a-!XkdtCvasaj)}~KN_(oF zaqYW*(jbizVR|Z8UL2)tZs^-fBe^wDj7)Y4SrhM>+i#30%{_Q?aPaEhOzF)20~&+} U`B9}ZjsO4v07*qoM6N<$f`Gb?E&u=k diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png deleted file mode 100644 index 5684aa7dc5d4dd20ffac6d3191a20b810fa3b56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 341 zcmV-b0jmCqP)mMbk{NI>0N1x?|uus^FEj=X)&pHzW-zSTS)^M<$cW&4ZYJcs=$YBhD;OSn~x>^datnyArVw)DU;Gg0)Zj8#)n zvEOVsVD8IDj$T&^mqn$1cGf=f59R|YEe}L#KYAzm#9c{d-LoWdQk3+qH>6fP6iLS= zHbg$(d&OcGMVj*x+ajNrE!Hs-X$BHI|GPPJM;)=(IkB*z#8_l|6Y!A`w#>EN6$=|l zEUvs>fMIi-OW zpO{oQRv$a4v99;s%YSoR@|?%ka@=_XLn9LtBSQo0UbEy0@YH_@FdzKA=A!Zd0000< KMNUMnLSTY+uq#CX diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png deleted file mode 100644 index 7faa3455f60127e423ab3cd481745bfa47eb121f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 660 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 diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml index 78e20893b..df022a467 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ Map compass. Click to reset the map rotation to North. - Press button to toggle tracking modes (None, Follow, FollowWithHeading) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 5e0e0002a..8fbd17d2d 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -287,6 +287,20 @@ public class MainActivity extends AppCompatActivity { mapView.setCompassEnabled(!mapView.isCompassEnabled()); break; +/* + case R.id.followNone: + mapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.NONE); + break; + + case R.id.followFollow: + mapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.FOLLOW); + break; + + case R.id.followBearing: + mapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.FOLLOW_BEARING); + break; +*/ + case R.id.actionStyleMapboxStreets: changeMapStyle(getString(R.string.styleURLMapboxStreets)); break; diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml index 1474161e0..38288bb57 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -20,6 +20,19 @@ android:title="@string/action_compass" /> + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 40bd286e5..a26fa55e7 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -4,6 +4,9 @@ Mapbox GL Toggle GPS location + Toggle Location Bearing Tracking + Follow + Bearing Toggle Compass Toggle debug mode Toggle point annotations From 26d300b4f435519b73743b36b48bf5cf71a678a4 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 6 Apr 2015 21:37:58 -0700 Subject: [PATCH 463/760] Refactor user location annotation Adds: - Accuracy ring - Heading indicator Fixes: - Pulse sync - Visual appearance (removes slight aliasing) Closes #1222 (squashed) --- LICENSE.md | 10 + platform/ios/MGLMapView.mm | 1 + platform/ios/MGLUserLocationAnnotationView.m | 334 +++++++++++++------ 3 files changed, 242 insertions(+), 103 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index e5ed29c9a..6de1da495 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -53,3 +53,13 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of SVPulsingAnnotationView. + +Copyright (c) 2013, Sam Vermette + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 81338f288..3a517ddef 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -737,6 +737,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) { [self updateHeadingForDeviceOrientation]; [self updateCompass]; + [self updateUserLocationAnnotationView]; } } diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 67f360f68..a6310a82c 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -5,7 +5,8 @@ #import "MGLAnnotation.h" #import "MGLMapView.h" -const CGFloat MGLTrackingDotRingWidth = 24.0; +const CGFloat MGLUserLocationAnnotationDotSize = 22.0; +const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; @interface MGLUserLocationAnnotationView () @@ -17,9 +18,15 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; @implementation MGLUserLocationAnnotationView { + CALayer *_headingIndicatorLayer; + CAShapeLayer *_headingIndicatorMaskLayer; CALayer *_accuracyRingLayer; CALayer *_dotBorderLayer; CALayer *_dotLayer; + + double _oldHeadingAccuracy; + CLLocationAccuracy _oldHorizontalAccuracy; + double _oldZoom; } - (instancetype)initWithFrame:(CGRect)frame @@ -30,7 +37,7 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (instancetype)initInMapView:(MGLMapView *)mapView { - if (self = [super initWithFrame:CGRectMake(0, 0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)]) + if (self = [super initWithFrame:CGRectMake(0, 0, MGLUserLocationAnnotationDotSize, MGLUserLocationAnnotationDotSize)]) { self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView]; _mapView = mapView; @@ -48,164 +55,285 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (void)setTintColor:(UIColor *)tintColor { - UIImage *trackingDotHaloImage = [self trackingDotHaloImage]; - _haloLayer.bounds = CGRectMake(0, 0, trackingDotHaloImage.size.width, trackingDotHaloImage.size.height); - _haloLayer.contents = (__bridge id)[trackingDotHaloImage CGImage]; + if (_accuracyRingLayer) + { + _accuracyRingLayer.backgroundColor = [tintColor CGColor]; + } - UIImage *dotImage = [self dotImage]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; + _haloLayer.backgroundColor = [tintColor CGColor]; + _dotLayer.backgroundColor = [tintColor CGColor]; + + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; } - (void)setupLayers { if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) { + // update heading indicator + // + if (_headingIndicatorLayer) + { + _headingIndicatorLayer.hidden = (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading) ? NO : YES; + + if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy) + { + // recalculate the clipping mask based on updated accuracy + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + } + + // heading indicator (tinted, semi-circle) + // + if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy) + { + CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize; + + _headingIndicatorLayer = [CALayer layer]; + _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize); + _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; + _headingIndicatorLayer.contentsGravity = kCAGravityBottom; + _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.opacity = 0.4; + _headingIndicatorLayer.shouldRasterize = YES; + _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.drawsAsynchronously = YES; + + [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; + } + + // heading indicator accuracy mask (fan-shaped) + // + if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy) + { + _headingIndicatorMaskLayer = [CAShapeLayer layer]; + _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds; + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + // apply the mask to the halo-radius-sized gradient layer + _headingIndicatorLayer.mask = _headingIndicatorMaskLayer; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + + // update accuracy ring + // + if (_accuracyRingLayer) + { + // FIX: This stops EVERYTHING... and that isn't necessarily necessary, now is it? + if (_oldZoom == self.mapView.zoomLevel && _oldHorizontalAccuracy == self.annotation.location.horizontalAccuracy) + { + return; + } + + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + + // only show the accuracy ring if it won't be obscured by the location dot + if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15) + { + _accuracyRingLayer.hidden = NO; + _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; + + // match the halo to the accuracy ring + _haloLayer.bounds = _accuracyRingLayer.bounds; + _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; + _haloLayer.shouldRasterize = NO; + } + else + { + _accuracyRingLayer.hidden = YES; + + _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0; + _haloLayer.shouldRasterize = YES; + _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // accuracy ring (circular, tinted, mostly-transparent) + // if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) { - UIImage *accuracyRingImage = [self accuracyRingImage]; - _accuracyRingLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, accuracyRingImage.size.width, accuracyRingImage.size.height); - _haloLayer.contents = (__bridge id)[accuracyRingImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; + _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor]; + _accuracyRingLayer.opacity = 0.1; + _accuracyRingLayer.shouldRasterize = NO; + _accuracyRingLayer.allowsGroupOpacity = NO; [self.layer addSublayer:_accuracyRingLayer]; } + // expanding sonar-like pulse (circular, tinted, fades out) + // if ( ! _haloLayer) { - UIImage *haloImage = [self trackingDotHaloImage]; - _haloLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, haloImage.size.width, haloImage.size.height); - _haloLayer.contents = (__bridge id)[haloImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; + _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; + _haloLayer.allowsGroupOpacity = NO; - [CATransaction begin]; + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; - [CATransaction setAnimationDuration:3.5]; - [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; + // scale out radially with initial acceleration + CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; + boundsAnimation.values = @[@0, @0.35, @1]; + boundsAnimation.keyTimes = @[@0, @0.2, @1]; - // scale out radially - // - CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; - boundsAnimation.repeatCount = MAXFLOAT; - boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; - boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]; - boundsAnimation.removedOnCompletion = NO; + // go transparent as scaled out, start semi-opaque + CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.values = @[@0.4, @0.4, @0]; + opacityAnimation.keyTimes = @[@0, @0.2, @1]; - [_haloLayer addAnimation:boundsAnimation forKey:@"animateScale"]; + animationGroup.animations = @[boundsAnimation, opacityAnimation]; - // go transparent as scaled out - // - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.repeatCount = MAXFLOAT; - opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0]; - opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0]; - opacityAnimation.removedOnCompletion = NO; - - [_haloLayer addAnimation:opacityAnimation forKey:@"animateOpacity"]; - - [CATransaction commit]; + [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; [self.layer addSublayer:_haloLayer]; } - // white dot background with shadow + // background dot (white with black shadow) // if ( ! _dotBorderLayer) { - CGRect rect = CGRectMake(0, 0, MGLTrackingDotRingWidth * 1.5, MGLTrackingDotRingWidth * 1.5); + _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; + _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + _dotBorderLayer.shadowOpacity = 0.25; - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetShadow(context, CGSizeMake(0, 0), MGLTrackingDotRingWidth / 4.0); - - CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - MGLTrackingDotRingWidth) / 2.0, (rect.size.height - MGLTrackingDotRingWidth) / 2.0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)); - - UIImage *whiteBackground = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); - - _dotBorderLayer = [CALayer layer]; - _dotBorderLayer.bounds = CGRectMake(0, 0, whiteBackground.size.width, whiteBackground.size.height); - _dotBorderLayer.contents = (__bridge id)[whiteBackground CGImage]; - _dotBorderLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); [self.layer addSublayer:_dotBorderLayer]; } - - // pulsing, tinted dot sublayer + + // inner dot (pulsing, tinted) // if ( ! _dotLayer) { - UIImage *dotImage = [self dotImage]; - _dotLayer = [CALayer layer]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; - _dotLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); + _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; + _dotLayer.backgroundColor = [_mapView.tintColor CGColor]; + _dotLayer.shouldRasterize = NO; - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; - animation.repeatCount = MAXFLOAT; - animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]; - animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]; - animation.removedOnCompletion = NO; - animation.autoreverses = YES; - animation.duration = 1.5; - animation.beginTime = CACurrentMediaTime() + 1.0; - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; + animationGroup.autoreverses = YES; + animationGroup.fillMode = kCAFillModeBoth; - [_dotLayer addAnimation:animation forKey:@"animateTransform"]; + // scale the dot up and down + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + + // fade opacity in and out, subtly + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; [self.layer addSublayer:_dotLayer]; } } } -- (UIImage *)accuracyRingImage +- (CALayer *)circleLayerWithSize:(CGFloat)layerSize +{ + CALayer *circleLayer = [CALayer layer]; + circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); + circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + circleLayer.cornerRadius = layerSize / 2.0; + circleLayer.shouldRasterize = YES; + circleLayer.rasterizationScale = [UIScreen mainScreen].scale; + circleLayer.drawsAsynchronously = YES; + + return circleLayer; +} + +- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration +{ + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = animationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + return animationGroup; +} + +- (CGFloat)calculateAccuracyRingSize { CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f; CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude]; - UIGraphicsBeginImageContextWithOptions(CGSizeMake(pixelRadius * 2, pixelRadius * 2), NO, [[UIScreen mainScreen] scale]); - CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7] CGColor]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15] CGColor]); - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 2.0); - CGContextStrokeEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, pixelRadius * 2, pixelRadius * 2)); - - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return finalImage; + return pixelRadius * 2; } -- (UIImage *)trackingDotHaloImage +- (UIImage *)headingIndicatorTintedGradientImage { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, [[UIScreen mainScreen] scale]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[_mapView.tintColor colorWithAlphaComponent:0.75] CGColor]); - CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 100, 100)); - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image; - return finalImage; -} - -- (UIImage *)dotImage -{ - CGFloat tintedWidth = MGLTrackingDotRingWidth * 0.7; + CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0; - CGRect rect = CGRectMake(0, 0, tintedWidth, tintedWidth); + UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0); - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetFillColorWithColor(context, [_mapView.tintColor CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - tintedWidth) / 2.0, (rect.size.height - tintedWidth) / 2.0, tintedWidth, tintedWidth)); + // gradient from the tint color to no-alpha tint color + CGFloat gradientLocations[] = {0.0, 1.0}; + CGGradientRef gradient = CGGradientCreateWithColors( + colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor], + (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations); - UIImage *tintedForeground = UIGraphicsGetImageFromCurrentImageContext(); + // draw the gradient from the center point to the edge (full halo radius) + CGPoint centerPoint = CGPointMake(haloRadius, haloRadius); + CGContextDrawRadialGradient(context, gradient, + centerPoint, 0.0, + centerPoint, haloRadius, + nil); + image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - return tintedForeground; + CGGradientRelease(gradient); + CGColorSpaceRelease(colorSpace); + + return image; +} + +- (UIBezierPath *)headingIndicatorClippingMask +{ + CGFloat accuracy = self.annotation.heading.headingAccuracy; + + // size the mask using exagerated accuracy, but keep within a good display range + CGFloat clippingDegrees = 90 - (accuracy * 1.5); + clippingDegrees = fmin(clippingDegrees, 55); + clippingDegrees = fmax(clippingDegrees, 10); + + CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + UIBezierPath *ovalPath = UIBezierPath.bezierPath; + + // clip the oval to ± incoming accuracy degrees (converted to radians), from the top + [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) + radius:CGRectGetWidth(ovalRect) / 2.0 + startAngle:(-180 + clippingDegrees) * M_PI / 180 + endAngle:-clippingDegrees * M_PI / 180 + clockwise:YES]; + + [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))]; + [ovalPath closePath]; + + return ovalPath; } @end From dc11b8132ba7ccc58c280e580526425e7389df86 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Mon, 10 Aug 2015 14:14:58 -0400 Subject: [PATCH 464/760] Improve accuracy ring update checks --- platform/ios/MGLUserLocationAnnotationView.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index a6310a82c..536326d1f 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -119,16 +119,10 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; } - // update accuracy ring + // update accuracy ring (if zoom or horizontal accuracy have changed) // - if (_accuracyRingLayer) + if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy)) { - // FIX: This stops EVERYTHING... and that isn't necessarily necessary, now is it? - if (_oldZoom == self.mapView.zoomLevel && _oldHorizontalAccuracy == self.annotation.location.horizontalAccuracy) - { - return; - } - CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; // only show the accuracy ring if it won't be obscured by the location dot From bfb44846c012018b27e81a09642eac9f9655a996 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Mon, 10 Aug 2015 17:32:26 -0500 Subject: [PATCH 465/760] #1856 - Adding Location FAB to TestApp with color toggle to show state --- .../mapbox/mapboxgl/testapp/MainActivity.java | 37 +++++++------------ .../src/main/res/layout/activity_main.xml | 17 ++++----- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 8fbd17d2d..5d36a98cd 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -4,6 +4,7 @@ import android.graphics.Color; import android.graphics.PointF; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -62,7 +63,7 @@ public class MainActivity extends AppCompatActivity { private ArrayAdapter mSatelliteClassAdapter; // Used for GPS - private MenuItem mGpsMenuItem; + private FloatingActionButton locationFAB; // Used for Annotations private boolean mIsAnnotationsOn = false; @@ -143,6 +144,16 @@ public class MainActivity extends AppCompatActivity { mMapFrameLayout = (FrameLayout) findViewById(R.id.layout_map); + locationFAB = (FloatingActionButton)findViewById(R.id.locationFAB); + locationFAB.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Toggle GPS position updates + toggleGps(!mapView.isMyLocationEnabled()); + updateMap(); + } + }); + /* // Add the spinner to select class styles mClassSpinner = (Spinner) findViewById(R.id.spinner_class); @@ -220,22 +231,6 @@ public class MainActivity extends AppCompatActivity { // Other events // -/* - // Adds items to the action bar menu - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_main, menu); - mGpsMenuItem = menu.findItem(R.id.action_gps); - if (mapView.isMyLocationEnabled()) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_found); - } else { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_searching); - } - return super.onCreateOptionsMenu(menu); - } -*/ - // Called when pressing action bar items @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -340,16 +335,12 @@ public class MainActivity extends AppCompatActivity { if (enableGps) { if (!mapView.isMyLocationEnabled()) { mapView.setMyLocationEnabled(enableGps); - if (mGpsMenuItem != null) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_found); - } + locationFAB.setColorFilter(getResources().getColor(R.color.primary)); } } else { if (mapView.isMyLocationEnabled()) { mapView.setMyLocationEnabled(enableGps); - if (mGpsMenuItem != null) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_searching); - } + locationFAB.setColorFilter(Color.TRANSPARENT); } } } diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 616e5fe1d..19647d20f 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -65,15 +65,14 @@ - + From b4eb1662c561b87913caebd81229b5f5f5993eda Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Mon, 10 Aug 2015 17:36:05 -0500 Subject: [PATCH 466/760] #1856 - Removing GPS Toggle from Navigation Drawer --- .../main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 6 ------ .../src/main/res/menu/menu_drawer.xml | 4 ---- 2 files changed, 10 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 5d36a98cd..e35a5503e 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -31,7 +31,6 @@ import com.mapbox.mapboxgl.annotations.PolylineOptions; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.views.MapView; import io.fabric.sdk.android.Fabric; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -253,11 +252,6 @@ public class MainActivity extends AppCompatActivity { // Respond To Selection switch (menuItem.getItemId()) { - case R.id.action_gps: - // Toggle GPS position updates - toggleGps(!mapView.isMyLocationEnabled()); - updateMap(); - break; case R.id.action_debug: // Toggle debug mode diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml index 38288bb57..7cfc47f90 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -11,10 +11,6 @@ android:icon="@android:drawable/ic_menu_myplaces" android:title="@string/action_point_annotations" /> - Date: Tue, 11 Aug 2015 09:06:19 +1000 Subject: [PATCH 467/760] Fix ThreadContext to check for valid current pointer --- src/mbgl/util/thread_context.hpp | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/mbgl/util/thread_context.hpp b/src/mbgl/util/thread_context.hpp index a2be0cd36..626925bdc 100644 --- a/src/mbgl/util/thread_context.hpp +++ b/src/mbgl/util/thread_context.hpp @@ -36,27 +36,51 @@ public: } static std::string getName() { - return current.get()->name; + if (current.get() != nullptr) { + return current.get()->name; + } else { + return "Unknown"; + } } static ThreadPriority getPriority() { - return current.get()->priority; + if (current.get() != nullptr) { + return current.get()->priority; + } else { + return ThreadPriority::Regular; + } } static FileSource* getFileSource() { - return current.get()->fileSource; + if (current.get() != nullptr) { + return current.get()->fileSource; + } else { + return nullptr; + } } static void setFileSource(FileSource* fileSource) { - current.get()->fileSource = fileSource; + if (current.get() != nullptr) { + current.get()->fileSource = fileSource; + } else { + throw new std::runtime_error("Current thread has no current ThreadContext."); + } } static GLObjectStore* getGLObjectStore() { - return current.get()->glObjectStore; + if (current.get() != nullptr) { + return current.get()->glObjectStore; + } else { + return nullptr; + } } static void setGLObjectStore(GLObjectStore* glObjectStore) { - current.get()->glObjectStore = glObjectStore; + if (current.get() != nullptr) { + current.get()->glObjectStore = glObjectStore; + } else { + throw new std::runtime_error("Current thread has no current ThreadContext."); + } } private: From 7325000edb13526e7ab694aef573a6717ef26884 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Tue, 11 Aug 2015 09:44:49 +1000 Subject: [PATCH 468/760] Check API level for totalMemory --- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index cd7e84cee..3b34fcca0 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -192,6 +192,7 @@ public class MapView extends FrameLayout implements LocationListener { // // Common initialization code goes here + @TargetApi(16) private void initialize(Context context, AttributeSet attrs) { // Save the context @@ -218,9 +219,12 @@ public class MapView extends FrameLayout implements LocationListener { ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(memoryInfo); - long totalMemory = memoryInfo.totalMem; - mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity,availableProcessors, totalMemory); - + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + long totalMemory = memoryInfo.totalMem; + mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, totalMemory); + } else { + throw new RuntimeException("Need to implement totalMemory on pre-Jelly Bean devices."); + } // Load the attributes TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); try { @@ -685,7 +689,7 @@ public class MapView extends FrameLayout implements LocationListener { } public void onSizeChanged(int width, int height, int oldw, int oldh) { - mNativeMapView.resizeView((int)(width / mScreenDensity), (int)(height / mScreenDensity)); + mNativeMapView.resizeView((int) (width / mScreenDensity), (int) (height / mScreenDensity)); } // This class handles SurfaceHolder callbacks From 0cbe7e07f5ada444de45b3936f7378dd4f3b9d6b Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 31 Jul 2015 13:00:52 +1000 Subject: [PATCH 469/760] Use OkHTTP to implement HTTPContext on Android. Closes #823 --- android/cpp/jni.cpp | 142 ++++++- android/cpp/native_map_view.cpp | 81 +--- .../com/mapbox/mapboxgl/http/HTTPContext.java | 93 ++++ .../mapbox/mapboxgl/util/JavaFileSource.java | 51 --- android/mapboxgl-app.gypi | 5 - gyp/http-android.gypi | 70 +++ include/mbgl/android/jni.hpp | 29 ++ mbgl.gyp | 1 + platform/android/http_request_android.cpp | 399 ++++++++++++++++++ platform/default/http_request_curl.cpp | 109 ----- scripts/android/configure.sh | 4 +- scripts/android/defaults.mk | 2 +- 12 files changed, 734 insertions(+), 252 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java create mode 100644 gyp/http-android.gypi create mode 100644 platform/android/http_request_android.cpp diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 6d7def861..f1e9072cb 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -28,6 +28,8 @@ namespace mbgl { namespace android { +JavaVM* theJVM; + std::string cachePath; std::string dataPath; std::string apkPath; @@ -90,7 +92,15 @@ jmethodID pointFConstructorId = nullptr; jfieldID pointFXId = nullptr; jfieldID pointFYId = nullptr; -bool throw_error(JNIEnv *env, const char *msg) { +jclass httpContextClass = nullptr; +jmethodID httpContextGetInstanceId = nullptr; +jmethodID httpContextCreateRequestId = nullptr; + +jclass httpRequestClass = nullptr; +jmethodID httpRequestStartId = nullptr; +jmethodID httpRequestCancelId = nullptr; + +bool throw_jni_error(JNIEnv *env, const char *msg) { if (env->ThrowNew(runtimeExceptionClass, msg) < 0) { env->ExceptionDescribe(); return false; @@ -99,6 +109,41 @@ bool throw_error(JNIEnv *env, const char *msg) { return true; } +bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName) { + JavaVMAttachArgs args = {JNI_VERSION_1_2, threadName.c_str(), NULL}; + + jint ret; + *env = nullptr; + bool detach = false; + ret = vm->GetEnv(reinterpret_cast(env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + if (ret != JNI_EDETACHED) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } else { + ret = vm->AttachCurrentThread(env, &args); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); + throw new std::runtime_error("AttachCurrentThread() failed"); + } + detach = true; + } + } + + return detach; +} + +void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach) { + if (detach) { + jint ret; + if ((ret = vm->DetachCurrentThread()) != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); + throw new std::runtime_error("DetachCurrentThread() failed"); + } + } + *env = nullptr; +} + std::string std_string_from_jstring(JNIEnv *env, jstring jstr) { std::string str; @@ -342,7 +387,7 @@ void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapVi { nativeMapView->initializeDisplay(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL display."); + throw_jni_error(env, "Unable to initialize GL display."); } } @@ -361,7 +406,7 @@ void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapVi try { nativeMapView->initializeContext(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL context."); + throw_jni_error(env, "Unable to initialize GL context."); } } @@ -380,7 +425,7 @@ void JNICALL nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPt try { nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface)); } catch(const std::exception& e) { - throw_error(env, "Unable to create GL surface."); + throw_jni_error(env, "Unable to create GL surface."); } } @@ -1131,6 +1176,8 @@ extern "C" { extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -1426,6 +1473,36 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext"); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + } + + httpContextGetInstanceId = env->GetStaticMethodID(httpContextClass, "getInstance", "()Lcom/mapbox/mapboxgl/http/HTTPContext;"); + if (httpContextGetInstanceId == nullptr) { + env->ExceptionDescribe(); + } + + httpContextCreateRequestId = env->GetMethodID(httpContextClass, "createRequest", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/mapbox/mapboxgl/http/HTTPContext$HTTPRequest;"); + if (httpContextCreateRequestId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext$HTTPRequest"); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestStartId = env->GetMethodID(httpRequestClass, "start", "()V"); + if (httpRequestStartId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestCancelId = env->GetMethodID(httpRequestClass, "cancel", "()V"); + if (httpRequestCancelId == nullptr) { + env->ExceptionDescribe(); + } + const std::vector methods = { {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;FIJ)J", reinterpret_cast(&nativeCreate)}, @@ -1524,10 +1601,16 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { //{"nativeGetWorldBoundsMeters", "(J)V", reinterpret_cast(&nativeGetWorldBoundsMeters)}, //{"nativeGetWorldBoundsLatLng", "(J)V", reinterpret_cast(&nativeGetWorldBoundsLatLng)}, {"nativeGetMetersPerPixelAtLatitude", "(JDD)D", reinterpret_cast(&nativeGetMetersPerPixelAtLatitude)}, - {"nativeProjectedMetersForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", reinterpret_cast(&nativeProjectedMetersForLatLng)}, - {"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForProjectedMeters)}, - {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast(&nativePixelForLatLng)}, - {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForPixel)}, + {"nativeProjectedMetersForLatLng", + "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", + reinterpret_cast(&nativeProjectedMetersForLatLng)}, + {"nativeLatLngForProjectedMeters", + "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForProjectedMeters)}, + {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", + reinterpret_cast(&nativePixelForLatLng)}, + {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForPixel)}, }; if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { @@ -1641,6 +1724,37 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = reinterpret_cast(env->NewGlobalRef(httpContextClass)); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + } + + httpRequestClass = reinterpret_cast(env->NewGlobalRef(httpRequestClass)); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(httpContextClass); + } + char release[PROP_VALUE_MAX] = ""; __system_property_get("ro.build.version.release", release); androidRelease = std::string(release); @@ -1651,6 +1765,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -1725,5 +1841,15 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { pointFConstructorId = nullptr; pointFXId = nullptr; pointFYId = nullptr; + + env->DeleteGlobalRef(httpContextClass); + httpContextGetInstanceId = nullptr; + httpContextCreateRequestId = nullptr; + + env->DeleteGlobalRef(httpRequestClass); + httpRequestStartId = nullptr; + httpRequestCancelId = nullptr; + + theJVM = nullptr; } } diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index d0581212d..d7c40ba5a 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -148,38 +148,15 @@ void NativeMapView::invalidate() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::invalidate()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::invalidate()"); env->CallVoidMethod(obj, onInvalidateId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::swap() { @@ -662,38 +639,15 @@ void NativeMapView::notifyMapChange(mbgl::MapChange) { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::notifyMapChange()"); env->CallVoidMethod(obj, onMapChangedId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::enableFps(bool enable) { @@ -727,38 +681,15 @@ void NativeMapView::updateFps() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::updateFps()"); env->CallVoidMethod(obj, onFpsChangedId, fps); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::onInvalidate() { diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java new file mode 100644 index 000000000..509c79a4a --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java @@ -0,0 +1,93 @@ +package com.mapbox.mapboxgl.http; + +import com.mapbox.mapboxgl.constants.MapboxConstants; +import com.squareup.okhttp.Call; +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLException; + +class HTTPContext { + + private static final int CONNECTION_ERROR = 0; + private static final int TEMPORARY_ERROR = 1; + private static final int PERMANENT_ERROR = 2; + + private static HTTPContext mInstance = null; + + private OkHttpClient mClient; + + private HTTPContext() { + super(); + mClient = new OkHttpClient(); + } + + public static HTTPContext getInstance() { + if (mInstance == null) { + mInstance = new HTTPContext(); + } + + return mInstance; + } + + public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified); + } + + public class HTTPRequest implements Callback { + private long mNativePtr = 0; + + private Call mCall; + private Request mRequest; + + private native void nativeOnFailure(long nativePtr, int type, String message); + private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body); + + private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + mNativePtr = nativePtr; + Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent); + if (etag.length() > 0) { + builder = builder.addHeader("If-None-Match", etag); + } else if (modified.length() > 0) { + builder = builder.addHeader("If-Modified-Since", modified); + } + mRequest = builder.build(); + } + + public void start() { + mCall = HTTPContext.getInstance().mClient.newCall(mRequest); + mCall.enqueue(this); + } + + public void cancel() { + mCall.cancel(); + } + + @Override + public void onFailure(Request request, IOException e) { + int type = PERMANENT_ERROR; + if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) { + type = CONNECTION_ERROR; + } else if ((e instanceof InterruptedIOException)) { + type = TEMPORARY_ERROR; + } + nativeOnFailure(mNativePtr, type, e.getMessage()); + } + + @Override + public void onResponse(Response response) throws IOException { + byte[] body = response.body().bytes(); + response.body().close(); + + nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body); + } + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java deleted file mode 100644 index bd8624188..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.mapbox.mapboxgl.util; - -import com.mapbox.mapboxgl.constants.MapboxConstants; -import com.squareup.okhttp.Callback; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; - -public class JavaFileSource { - - private static JavaFileSource instance = null; - - // Single reference to OkHttp for performance gains - private OkHttpClient client; - - /** - * Private Constructor to support Singleton pattern - */ - private JavaFileSource() { - super(); - client = new OkHttpClient(); - } - - /** - * Get the singleton instance of JavaFileSource - * @return Reference to the Singleton Instance of JavaFileSource - */ - public static JavaFileSource getInstance() { - if (instance == null) { - instance = new JavaFileSource(); - } - return instance; - } - - /** - * Make an HTTP Request - * @param resourceUrl URL to resource - * @param callback Callback class - */ - public void request(final String resourceUrl, final Callback callback) { - Request request = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).build(); - client.newCall(request).enqueue(callback); - } - - /** - * Attempt to cancel HTTP Request made - * @param resourceUrl URL of request to cancel - */ - public void cancel(final String resourceUrl) { - client.cancel(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)); - } -} diff --git a/android/mapboxgl-app.gypi b/android/mapboxgl-app.gypi index e32775ff7..e3db1269d 100644 --- a/android/mapboxgl-app.gypi +++ b/android/mapboxgl-app.gypi @@ -25,8 +25,6 @@ '<@(boost_cflags)', ], 'libraries': [ - '<@(openssl_static_libs)', - '<@(libcurl_static_libs)', '<@(libpng_static_libs)', '<@(jpeg_static_libs)', '<@(sqlite_static_libs)', @@ -45,8 +43,6 @@ '<@(libpng_ldflags)', '<@(jpeg_ldflags)', '<@(sqlite_ldflags)', - '<@(openssl_ldflags)', - '<@(libcurl_ldflags)', '<@(zlib_ldflags)', '<@(libzip_ldflags)', ], @@ -74,7 +70,6 @@ 'copies': [ { 'files': [ - '../common/ca-bundle.crt', '../styles/styles' ], 'destination': '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/assets' diff --git a/gyp/http-android.gypi b/gyp/http-android.gypi new file mode 100644 index 000000000..c210db0fb --- /dev/null +++ b/gyp/http-android.gypi @@ -0,0 +1,70 @@ +{ + 'targets': [ + { 'target_name': 'http-android', + 'product_name': 'mbgl-http-android', + 'type': 'static_library', + 'standalone_static_library': 1, + 'hard_dependency': 1, + + 'sources': [ + '../platform/android/http_request_android.cpp', + ], + + 'include_dirs': [ + '../include', + '../src', + ], + + 'variables': { + 'cflags_cc': [ + '<@(libuv_cflags)', + '<@(boost_cflags)', + ], + 'ldflags': [ + '<@(libuv_ldflags)', + ], + 'libraries': [ + '<@(libuv_static_libs)', + ], + 'defines': [ + '-DMBGL_HTTP_ANDROID' + ], + }, + + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], + }, + }, { + 'cflags_cc': [ '<@(cflags_cc)' ], + }], + ], + + 'direct_dependent_settings': { + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CFLAGS': [ '<@(defines)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ], + } + }, { + 'cflags': [ '<@(defines)' ], + 'cflags_cc': [ '<@(defines)' ], + }] + ], + }, + + 'link_settings': { + 'conditions': [ + ['OS == "mac"', { + 'libraries': [ '<@(libraries)' ], + 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } + }, { + 'libraries': [ '<@(libraries)', '<@(ldflags)' ], + }] + ], + }, + }, + ], +} diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp index f27c8c844..62cb399d3 100644 --- a/include/mbgl/android/jni.hpp +++ b/include/mbgl/android/jni.hpp @@ -2,15 +2,27 @@ #define MBGL_ANDROID_JNI #include +#include // Forward definition of JNI types typedef class _jclass* jclass; +typedef class _jstring* jstring; +typedef class _jobject* jobject; +typedef class _jlongArray* jlongArray; typedef struct _jmethodID* jmethodID; typedef struct _jfieldID* jfieldID; +struct _JavaVM; +typedef _JavaVM JavaVM; + +struct _JNIEnv; +typedef _JNIEnv JNIEnv; + namespace mbgl { namespace android { +extern JavaVM* theJVM; + extern std::string cachePath; extern std::string dataPath; extern std::string apkPath; @@ -73,6 +85,23 @@ extern jmethodID pointFConstructorId; extern jfieldID pointFXId; extern jfieldID pointFYId; +extern jclass httpContextClass; +extern jmethodID httpContextGetInstanceId; +extern jmethodID httpContextCreateRequestId; + +extern jclass httpRequestClass; +extern jmethodID httpRequestStartId; +extern jmethodID httpRequestCancelId; + +extern bool throw_jni_error(JNIEnv *env, const char *msg); +extern bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName); +extern void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach); +extern std::string std_string_from_jstring(JNIEnv *env, jstring jstr); +extern jstring std_string_to_jstring(JNIEnv *env, std::string str); +extern std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jlist); +extern jobject std_vector_string_to_jobject(JNIEnv *env, std::vector vector); +extern jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector vector); + } } diff --git a/mbgl.gyp b/mbgl.gyp index c5ede2823..959a9729d 100644 --- a/mbgl.gyp +++ b/mbgl.gyp @@ -18,6 +18,7 @@ ['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ], ['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ], ['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ], + ['http_lib == "android" and host == "android"', { 'includes': [ './gyp/http-android.gypi' ] } ], ['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ], ['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ], ['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ], diff --git a/platform/android/http_request_android.cpp b/platform/android/http_request_android.cpp new file mode 100644 index 000000000..044f77262 --- /dev/null +++ b/platform/android/http_request_android.cpp @@ -0,0 +1,399 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace mbgl { + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message); +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body); + +class HTTPAndroidRequest; + +class HTTPAndroidContext : public HTTPContextBase { +public: + explicit HTTPAndroidContext(uv_loop_t *loop); + ~HTTPAndroidContext(); + + HTTPRequestBase* createRequest(const Resource&, + RequestBase::Callback, + uv_loop_t*, + std::shared_ptr) final; + + uv_loop_t *loop = nullptr; + + JavaVM *vm = nullptr; + jobject obj = nullptr; +}; + +class HTTPAndroidRequest : public HTTPRequestBase { +public: + HTTPAndroidRequest(HTTPAndroidContext*, + const Resource&, + Callback, + uv_loop_t*, + std::shared_ptr); + ~HTTPAndroidRequest(); + + void cancel() final; + void retry() final; + + void onFailure(int type, std::string message); + void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body); + +private: + void retry(uint64_t timeout) final; +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + static void restart(uv_timer_t *timer, int); +#else + static void restart(uv_timer_t *timer); +#endif + void finish(ResponseStatus status); + void start(); + + HTTPAndroidContext *context = nullptr; + + bool cancelled = false; + + std::unique_ptr response; + const std::shared_ptr existingResponse; + + jobject obj = nullptr; + + uv_timer_t *timer = nullptr; + enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately; + int attempts = 0; + + static const int maxAttempts = 4; + + static const int connectionError = 0; + static const int temporaryError = 1; + static const int permanentError = 1; +}; + +// ------------------------------------------------------------------------------------------------- + +HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_) + : HTTPContextBase(loop_), + loop(loop_), + vm(mbgl::android::theJVM) { + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()"); + + const std::vector methods = { + {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast(&nativeOnFailure)}, + {"nativeOnResponse", + "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V", + reinterpret_cast(&nativeOnResponse)} + }; + + if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) { + env->ExceptionDescribe(); + } + + obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(vm, &env, detach); +} + +HTTPAndroidContext::~HTTPAndroidContext() { + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(vm, &env, detach); + + vm = nullptr; +} + +HTTPRequestBase* HTTPAndroidContext::createRequest(const Resource& resource, + RequestBase::Callback callback, + uv_loop_t* loop_, + std::shared_ptr response) { + return new HTTPAndroidRequest(this, resource, callback, loop_, response); +} + +HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) + : HTTPRequestBase(resource_, callback_), + context(context_), + existingResponse(response_) { + + std::string etagStr; + std::string modifiedStr; + if (existingResponse) { + if (!existingResponse->etag.empty()) { + etagStr = existingResponse->etag; + } else if (existingResponse->modified) { + modifiedStr = util::rfc1123(existingResponse->modified); + } + } + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()"); + + jstring resourceUrl = mbgl::android::std_string_to_jstring(env, resource.url); + jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0"); + jstring etag = mbgl::android::std_string_to_jstring(env, etagStr); + jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr); + obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast(this), resourceUrl, userAgent, etag, modified); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + context->addRequest(this); + start(); +} + +HTTPAndroidRequest::~HTTPAndroidRequest() { + context->removeRequest(this); + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + if (timer) { + uv_timer_stop(timer); + uv::close(timer); + timer = nullptr; + } +} + +void HTTPAndroidRequest::cancel() { + cancelled = true; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::start() { + attempts++; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestStartId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::retry(uint64_t timeout) { + response.reset(); + + assert(!timer); + timer = new uv_timer_t; + timer->data = this; + uv_timer_init(context->loop, timer); + uv_timer_start(timer, restart, timeout, 0); +} + +void HTTPAndroidRequest::retry() { + if (timer && strategy == PreemptImmediately) { + uv_timer_stop(timer); + uv_timer_start(timer, restart, 0, 0); + } +} + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 +void HTTPAndroidRequest::restart(uv_timer_t *timer, int) { +#else +void HTTPAndroidRequest::restart(uv_timer_t *timer) { +#endif + auto baton = reinterpret_cast(timer->data); + + baton->timer = nullptr; + uv::close(timer); + + baton->start(); +} + +void HTTPAndroidRequest::finish(ResponseStatus status) { + if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) { + strategy = ExponentialBackoff; + return retry((1 << (attempts - 1)) * 1000); + } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) { + strategy = PreemptImmediately; + return retry(30000); + } + + if (status == ResponseStatus::NotModified) { + notify(std::move(response), FileCache::Hint::Refresh); + } else { + notify(std::move(response), FileCache::Hint::Full); + } + + delete this; +} + +int64_t parseCacheControl(const char *value) { + if (value) { + unsigned long long seconds = 0; + // TODO: cache-control may contain other information as well: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + if (std::sscanf(value, "max-age=%llu", &seconds) == 1) { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count() + + seconds; + } + } + + return 0; +} + +void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) { + if (cancelled) { + delete this; + return; + } + + if (!response) { + response = std::make_unique(); + } + + response->message = message; + response->modified = parse_date(modified.c_str()); + response->etag = etag; + response->expires = parseCacheControl(cacheControl.c_str()); + if (!expires.empty()) { + response->expires = parse_date(expires.c_str()); + } + response->data = body; + + if (code == 304) { + if (existingResponse) { + response->status = existingResponse->status; + response->message = existingResponse->message; + response->modified = existingResponse->modified; + response->etag = existingResponse->etag; + response->data = existingResponse->data; + return finish(ResponseStatus::NotModified); + } else { + response->status = Response::Successful; + return finish(ResponseStatus::Successful); + } + } else if (code == 200) { + response->status = Response::Successful; + return finish(ResponseStatus::Successful); + } else if (code >= 500 && code < 600) { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + return finish(ResponseStatus::TemporaryError); + } else { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + return finish(ResponseStatus::PermanentError); + } + + throw std::runtime_error("Response hasn't been handled"); +} + +void HTTPAndroidRequest::onFailure(int type, std::string message) { + if (cancelled) { + delete this; + return; + } + + if (!response) { + response = std::make_unique(); + } + + response->status = Response::Error; + response->message = message; + + switch (type) { + case connectionError: + return finish(ResponseStatus::ConnectionError); + + case temporaryError: + return finish(ResponseStatus::TemporaryError); + + default: + return finish(ResponseStatus::PermanentError); + } + + throw std::runtime_error("Response hasn't been handled"); +} + +std::unique_ptr HTTPContextBase::createContext(uv_loop_t* loop) { + return std::make_unique(loop); +} + +#pragma clang diagnostic ignored "-Wunused-parameter" + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + return request->onFailure(type, messageStr); +} + +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + std::string etagStr; + if (etag != nullptr) { + etagStr = mbgl::android::std_string_from_jstring(env, etag); + } + std::string modifiedStr; + if (modified != nullptr) { + modifiedStr = mbgl::android::std_string_from_jstring(env, modified); + } + std::string cacheControlStr; + if (cacheControl != nullptr) { + cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl); + } + std::string expiresStr; + if (expires != nullptr) { + expiresStr = mbgl::android::std_string_from_jstring(env, expires); + } + jbyte* bodyData = env->GetByteArrayElements(body, nullptr); + std::string bodyStr(reinterpret_cast(bodyData), env->GetArrayLength(body)); + env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT); + return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr); +} + +} diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp index 0f7f8c0ac..e416034b4 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_request_curl.cpp @@ -11,12 +11,6 @@ #include -#ifdef __ANDROID__ -#include -#include -#include -#endif - #include #include #include @@ -331,104 +325,6 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us // ------------------------------------------------------------------------------------------------- -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) { - - int error = 0; - struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error); - if (apk == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE); - if (apkFile == nullptr) { - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_stat stat; - if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - if (stat.size > std::numeric_limits::max()) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - const auto pem = std::make_unique(stat.size); - - if (static_cast(zip_fread(apkFile, reinterpret_cast(pem.get()), stat.size)) != stat.size) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // get a pointer to the X509 certificate store (which may be empty!) - X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); - if (store == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - // get a BIO - BIO *bio = BIO_new_mem_buf(pem.get(), static_cast(stat.size)); - if (bio == nullptr) { - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // use it to read the PEM formatted certificate from memory into an X509 - // structure that SSL can use - X509 *cert = nullptr; - while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) { - if (cert == nullptr) { - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // add our certificate to this store - if (X509_STORE_add_cert(store, cert) == 0) { - X509_free(cert); - cert = nullptr; - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - X509_free(cert); - cert = nullptr; - } - - // decrease reference counts - BIO_free(bio); - bio = nullptr; - - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - - // all set to go - return CURLE_OK; -} -#endif - HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) : HTTPRequestBase(resource_, callback_), context(context_), @@ -458,12 +354,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); diff --git a/scripts/android/configure.sh b/scripts/android/configure.sh index 667ea6891..70c8973a8 100644 --- a/scripts/android/configure.sh +++ b/scripts/android/configure.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash BOOST_VERSION=1.57.0 -LIBCURL_VERSION=7.40.0 -OPENSSL_VERSION=1.0.1l LIBPNG_VERSION=1.6.16 JPEG_VERSION=v9a SQLITE_VERSION=3.8.8.1 @@ -11,4 +9,4 @@ ZLIB_VERSION=system NUNICODE_VERSION=1.5.1 LIBZIP_VERSION=0.11.2 -export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} \ No newline at end of file +export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} diff --git a/scripts/android/defaults.mk b/scripts/android/defaults.mk index 436f9db71..29f047c88 100644 --- a/scripts/android/defaults.mk +++ b/scripts/android/defaults.mk @@ -1,7 +1,7 @@ HEADLESS ?= none PLATFORM ?= android ASSET ?= zip -HTTP ?= curl +HTTP ?= android CACHE ?= sqlite GYP_FLAVOR_SUFFIX=-android From 21ec5056489f53601b3d45a0b1c79a85add42b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 10 Aug 2015 17:14:00 +0300 Subject: [PATCH 470/760] add benchmark mode that shows the real frame time rather than the vsync time --- include/mbgl/map/map.hpp | 1 + include/mbgl/platform/default/glfw_view.hpp | 10 +++- linux/main.cpp | 13 ++++- macosx/main.mm | 53 +++++++++++++++++++-- macosx/mapboxgl-app.gypi | 1 + platform/default/glfw_view.cpp | 34 ++++++++----- src/mbgl/map/map.cpp | 4 ++ 7 files changed, 96 insertions(+), 20 deletions(-) diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index ae43dc8a8..ebd79620d 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -164,6 +164,7 @@ public: void setDebug(bool value); void toggleDebug(); bool getDebug() const; + void setNeedsRepaint(); void setCollisionDebug(bool value); void toggleCollisionDebug(); bool getCollisionDebug() const; diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index 1bc210b29..ec9908073 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -12,7 +12,7 @@ class GLFWView : public mbgl::View { public: - GLFWView(bool fullscreen = false); + GLFWView(bool fullscreen = false, bool benchmark = false); ~GLFWView(); float getPixelRatio() const override; @@ -42,7 +42,7 @@ public: void setWindowTitle(const std::string&); void run(); - void fps(); + void report(float duration); private: mbgl::LatLng makeRandomPoint() const; @@ -61,9 +61,15 @@ private: private: bool fullscreen = false; + const bool benchmark = false; bool tracking = false; bool rotating = false; + // Frame timer + int frames = 0; + float frameTime = 0; + double lastReported = 0; + int width = 1024; int height = 768; int fbWidth; diff --git a/linux/main.cpp b/linux/main.cpp index c21d7446e..0892ce8f1 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -32,17 +32,19 @@ void quit_handler(int) { int main(int argc, char *argv[]) { bool fullscreen = false; + bool benchmark = false; std::string style; const struct option long_options[] = { {"fullscreen", no_argument, 0, 'f'}, + {"benchmark", no_argument, 0, 'b'}, {"style", required_argument, 0, 's'}, {0, 0, 0, 0} }; while (true) { int option_index = 0; - int opt = getopt_long(argc, argv, "fs:", long_options, &option_index); + int opt = getopt_long(argc, argv, "fbs:", long_options, &option_index); if (opt == -1) break; switch (opt) { @@ -52,6 +54,9 @@ int main(int argc, char *argv[]) { case 'f': fullscreen = true; break; + case 'b': + benchmark = true; + break; case 's': style = std::string("asset://") + std::string(optarg); default: @@ -67,7 +72,11 @@ int main(int argc, char *argv[]) { sigIntHandler.sa_flags = 0; sigaction(SIGINT, &sigIntHandler, NULL); - view = std::make_unique(fullscreen); + if (benchmark) { + mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); + } + + view = std::make_unique(fullscreen, benchmark); mbgl::SQLiteCache cache("/tmp/mbgl-cache.db"); mbgl::DefaultFileSource fileSource(&cache); diff --git a/macosx/main.mm b/macosx/main.mm index 4a786f577..24251e6d4 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -12,6 +12,19 @@ #import +#pragma GCC diagnostic push +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include +#pragma GCC diagnostic pop + +#include + +namespace po = boost::program_options; + + @interface URLHandler : NSObject @property (nonatomic) mbgl::Map *map; @@ -101,8 +114,33 @@ const std::string &defaultCacheDatabase() { return path; } -int main() { - GLFWView view; +int main(int argc, char* argv[]) { + bool fullscreen = false; + bool benchmark = false; + std::string style; + + po::options_description desc("Allowed options"); + desc.add_options() + ("fullscreen,f", po::bool_switch(&fullscreen)->default_value(fullscreen), "Fullscreen mode") + ("style,s", po::value(&style)->value_name("json"), "Map stylesheet") + ("benchmark,b", po::bool_switch(&benchmark)->default_value(benchmark), "Benchmark mode") + ; + + try { + auto parsed = po::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); + po::variables_map vm; + po::store(parsed, vm); + po::notify(vm); + } catch(std::exception& e) { + std::cout << "Error: " << e.what() << std::endl << desc; + exit(1); + } + + if (benchmark) { + mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); + } + + GLFWView view(fullscreen, benchmark); mbgl::SQLiteCache cache(defaultCacheDatabase()); mbgl::DefaultFileSource fileSource(&cache); @@ -146,10 +184,15 @@ int main() { mbgl::Log::Info(mbgl::Event::Setup, std::string("Changed style to: ") + newStyle.first); }); + // Load style - const auto& newStyle = mbgl::util::defaultStyles.front(); - map.setStyleURL(newStyle.first); - view.setWindowTitle(newStyle.second); + if (style.empty()) { + const auto& newStyle = mbgl::util::defaultStyles.front(); + style = newStyle.first; + view.setWindowTitle(newStyle.second); + } + + map.setStyleURL(style); view.run(); diff --git a/macosx/mapboxgl-app.gypi b/macosx/mapboxgl-app.gypi index 589ae29bd..e1d9476e1 100644 --- a/macosx/mapboxgl-app.gypi +++ b/macosx/mapboxgl-app.gypi @@ -43,6 +43,7 @@ ], 'libraries': [ '<@(glfw_static_libs)', + '<@(boost_libprogram_options_static_libs)' ], }, diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 078c26fee..3ac60621e 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -15,7 +15,8 @@ void glfwError(int error, const char *description) { assert(false); } -GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { +GLFWView::GLFWView(bool fullscreen_, bool benchmark_) + : fullscreen(fullscreen_), benchmark(benchmark_) { glfwSetErrorCallback(glfwError); std::srand(std::time(0)); @@ -56,7 +57,13 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { glfwSetWindowUserPointer(window, this); glfwMakeContextCurrent(window); - glfwSwapInterval(1); + if (benchmark) { + // Disables vsync on platforms that support it. + glfwSwapInterval(0); + } else { + glfwSwapInterval(1); + } + glfwSetCursorPosCallback(window, onMouseMove); glfwSetMouseButtonCallback(window, onMouseClick); @@ -336,7 +343,12 @@ void GLFWView::run() { glfwWaitEvents(); const bool dirty = !clean.test_and_set(); if (dirty) { + const double started = glfwGetTime(); map->renderSync(); + report(1000 * (glfwGetTime() - started)); + if (benchmark) { + map->setNeedsRepaint(); + } map->nudgeTransitions(); } } @@ -373,20 +385,20 @@ void GLFWView::invalidate() { void GLFWView::swap() { glfwSwapBuffers(window); - fps(); } -void GLFWView::fps() { - static int frames = 0; - static double timeElapsed = 0; - +void GLFWView::report(float duration) { frames++; - double currentTime = glfwGetTime(); + frameTime += duration; - if (currentTime - timeElapsed >= 1) { - mbgl::Log::Info(mbgl::Event::OpenGL, "FPS: %4.2f", frames / (currentTime - timeElapsed)); - timeElapsed = currentTime; + const double currentTime = glfwGetTime(); + if (currentTime - lastReported >= 1) { + frameTime /= frames; + mbgl::Log::Info(mbgl::Event::OpenGL, "Frame time: %6.2fms (%6.2f fps)", frameTime, + 1000 / frameTime); frames = 0; + frameTime = 0; + lastReported = currentTime; } } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index fadd4c48d..a7015695c 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -375,6 +375,10 @@ bool Map::getDebug() const { return data->getDebug(); } +void Map::setNeedsRepaint() { + data->setNeedsRepaint(true); +} + void Map::setCollisionDebug(bool value) { data->setCollisionDebug(value); update(Update::Repaint); From 5a79bd832da2c32f715870a653475dc96fea3f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 11 Aug 2015 16:17:15 +0300 Subject: [PATCH 471/760] allow setting a location and print the location on exit --- linux/main.cpp | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/linux/main.cpp b/linux/main.cpp index 0892ce8f1..6bd2f7c43 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -34,11 +34,18 @@ int main(int argc, char *argv[]) { bool fullscreen = false; bool benchmark = false; std::string style; + double latitude = 0, longitude = 0; + double bearing = 0, zoom = 1; + bool skipConfig = false; const struct option long_options[] = { {"fullscreen", no_argument, 0, 'f'}, {"benchmark", no_argument, 0, 'b'}, {"style", required_argument, 0, 's'}, + {"lon", required_argument, 0, 'x'}, + {"lat", required_argument, 0, 'y'}, + {"zoom", required_argument, 0, 'z'}, + {"bearing", required_argument, 0, 'r'}, {0, 0, 0, 0} }; @@ -59,6 +66,23 @@ int main(int argc, char *argv[]) { break; case 's': style = std::string("asset://") + std::string(optarg); + break; + case 'x': + longitude = atof(optarg); + skipConfig = true; + break; + case 'y': + latitude = atof(optarg); + skipConfig = true; + break; + case 'z': + zoom = atof(optarg); + skipConfig = true; + break; + case 'r': + bearing = atof(optarg); + skipConfig = true; + break; default: break; } @@ -93,9 +117,16 @@ int main(int argc, char *argv[]) { // Load settings mbgl::Settings_JSON settings; - map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); - map.setBearing(settings.bearing); - map.setDebug(settings.debug); + + if (skipConfig) { + map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom); + map.setBearing(bearing); + mbgl::Log::Info(mbgl::Event::General, "Location: %f/%f (z%.2f, %.2f deg)", latitude, longitude, zoom, bearing); + } else { + map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); + map.setBearing(settings.bearing); + map.setDebug(settings.debug); + } view->setChangeStyleCallback([&map] () { static uint8_t currentStyleIndex; @@ -129,7 +160,12 @@ int main(int argc, char *argv[]) { settings.zoom = map.getZoom(); settings.bearing = map.getBearing(); settings.debug = map.getDebug(); - settings.save(); + if (!skipConfig) { + settings.save(); + } + mbgl::Log::Info(mbgl::Event::General, + "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"", + settings.latitude, settings.longitude, settings.zoom, settings.bearing); return 0; } From aa06a9d58748b5ef9e9e979891d197f5a26c9fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Tue, 11 Aug 2015 16:39:52 +0300 Subject: [PATCH 472/760] query the monitor's resolution instead of forcing 1024x768 --- platform/default/glfw_view.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 3ac60621e..8453845d1 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -29,6 +29,9 @@ GLFWView::GLFWView(bool fullscreen_, bool benchmark_) GLFWmonitor *monitor = nullptr; if (fullscreen) { monitor = glfwGetPrimaryMonitor(); + auto videoMode = glfwGetVideoMode(monitor); + width = videoMode->width; + height = videoMode->height; } #ifdef DEBUG From 64b5e506c435d8f83b10875a380b380162ec9d1d Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Tue, 11 Aug 2015 11:40:45 -0500 Subject: [PATCH 473/760] #1856 - Adding TrackingMode TODO for update display --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index fe146cf0d..d6e878ad8 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1674,6 +1674,8 @@ public class MapView extends FrameLayout implements LocationListener { } /* + // TODO - Too much overhead on main thread. Needs to be refactored before it + // can be re-enabled // Update map position if NOT in NONE mode if (userLocationTrackingMode != UserLocationTrackingMode.NONE) { setCenterCoordinate(new LatLng(mGpsLocation)); From 5b787455ddc77324e9ea2501576a767af55d3b60 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Tue, 11 Aug 2015 15:26:24 -0500 Subject: [PATCH 474/760] #1856 - Using fixed Toolbar with no content scrolling underneath --- .../mapbox/mapboxgl/testapp/MainActivity.java | 2 +- .../src/main/res/layout/activity_main.xml | 65 ++++++------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index e35a5503e..3d058b2d3 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -141,7 +141,7 @@ public class MainActivity extends AppCompatActivity { mFpsTextView = (TextView) findViewById(R.id.view_fps); mFpsTextView.setText(""); - mMapFrameLayout = (FrameLayout) findViewById(R.id.layout_map); +// mMapFrameLayout = (FrameLayout) findViewById(R.id.layout_map); locationFAB = (FloatingActionButton)findViewById(R.id.locationFAB); locationFAB.setOnClickListener(new View.OnClickListener() { diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 19647d20f..efd063be2 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -3,50 +3,28 @@ - - - - - - - - + android:background="?attr/colorPrimary" + android:minHeight="?attr/actionBarSize" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> - - + android:layout_below="@+id/toolbar"> + + + - - - - - + + app:menu="@menu/menu_drawer"/> From 7563e11eee56cd68afcb0aef41c9be28068d805c Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Tue, 11 Aug 2015 15:51:01 -0500 Subject: [PATCH 475/760] #1856 - Adjusting compas layout back to 10dp --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index d6e878ad8..33a0682b6 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -302,8 +302,8 @@ public class MapView extends FrameLayout implements LocationListener { mCompassView.setContentDescription(getResources().getString(R.string.compassContentDescription)); LayoutParams lp = new FrameLayout.LayoutParams((int)(48 * mScreenDensity), (int)(48 * mScreenDensity)); lp.gravity = Gravity.TOP | Gravity.END; - int twentyDp = (int)(20 * mScreenDensity); - lp.setMargins(twentyDp, twentyDp * 4, twentyDp, twentyDp); + int tenDp = (int)(10 * mScreenDensity); + lp.setMargins(tenDp, tenDp, tenDp, tenDp); mCompassView.setLayoutParams(lp); addView(mCompassView); mCompassView.setOnClickListener(new CompassOnClickListener()); From c1d796bbc173ee64aecd4816a1099fe449ec8ad9 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Tue, 11 Aug 2015 16:16:13 -0500 Subject: [PATCH 476/760] #1856 - Added CoordinatorLayout back in to support Snackbar widget --- .../mapbox/mapboxgl/testapp/MainActivity.java | 2 + .../src/main/res/layout/activity_main.xml | 55 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 3d058b2d3..79b366d42 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; +import android.support.design.widget.Snackbar; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; @@ -150,6 +151,7 @@ public class MainActivity extends AppCompatActivity { // Toggle GPS position updates toggleGps(!mapView.isMyLocationEnabled()); updateMap(); +// Snackbar.make(v, "Snack time!", Snackbar.LENGTH_LONG).show(); } }); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index efd063be2..305f15afe 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -20,37 +20,42 @@ android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> - - - + android:layout_below="@+id/toolbar"> - + + + + + - + + - + From 01b5636df0f7fe885176ad4918d18c5aa12de11d Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 11 Aug 2015 15:10:55 -0700 Subject: [PATCH 477/760] fixes #1951: check for support of title before querying --- platform/ios/MGLMapView.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 3a517ddef..895066355 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -2047,7 +2047,9 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) self.selectedAnnotation = annotation; - if (annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && + if ([annotation respondsToSelector:@selector(title)] && + annotation.title && + [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && [self.delegate mapView:self annotationCanShowCallout:annotation]) { // build the callout From 91cf989d8c989fc09e5a8a948b66d8a4151996dd Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Wed, 12 Aug 2015 10:33:35 -0500 Subject: [PATCH 478/760] #1856 - Move Map display to user location when location is enabled --- .../main/java/com/mapbox/mapboxgl/testapp/MainActivity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 79b366d42..5ef019110 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -2,6 +2,7 @@ package com.mapbox.mapboxgl.testapp; import android.graphics.Color; import android.graphics.PointF; +import android.location.Location; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; @@ -331,6 +332,10 @@ public class MainActivity extends AppCompatActivity { if (enableGps) { if (!mapView.isMyLocationEnabled()) { mapView.setMyLocationEnabled(enableGps); + Location location = mapView.getMyLocation(); + if (location != null) { + mapView.setCenterCoordinate(new LatLng(location), true); + } locationFAB.setColorFilter(getResources().getColor(R.color.primary)); } } else { From f5a95e18d94e6a89ce05443729d5cf1ba3b41a5c Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Wed, 12 Aug 2015 11:13:33 -0500 Subject: [PATCH 479/760] #1856 - Enabling and setting clickable state for features in Navigation Drawer menu --- .../main/java/com/mapbox/mapboxgl/views/MapView.java | 4 ---- .../com/mapbox/mapboxgl/testapp/MainActivity.java | 11 +++++++++-- .../src/main/res/menu/menu_drawer.xml | 3 +++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 33a0682b6..60c57593c 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -7,10 +7,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.PointF; import android.hardware.GeomagneticField; @@ -24,7 +22,6 @@ import android.net.NetworkInfo; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.design.widget.FloatingActionButton; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; import android.text.TextUtils; @@ -40,7 +37,6 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewConfiguration; -import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ZoomButtonsController; diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 5ef019110..dbdf83ae9 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -7,7 +7,6 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; -import android.support.design.widget.Snackbar; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; @@ -163,6 +162,11 @@ public class MainActivity extends AppCompatActivity { R.array.outdoors_class_list, android.R.layout.simple_spinner_dropdown_item); */ + // Set default UI state + navigationView.getMenu().findItem(R.id.action_compass).setChecked(mapView.isCompassEnabled()); + navigationView.getMenu().findItem(R.id.action_debug).setChecked(mapView.isDebugActive()); + navigationView.getMenu().findItem(R.id.action_markers).setChecked(mIsAnnotationsOn); + if (savedInstanceState != null) { mapView.setMyLocationEnabled(savedInstanceState.getBoolean(STATE_IS_GPS_ON, false)); } @@ -250,7 +254,6 @@ public class MainActivity extends AppCompatActivity { new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { - menuItem.setChecked(true); mDrawerLayout.closeDrawers(); // Respond To Selection @@ -260,6 +263,8 @@ public class MainActivity extends AppCompatActivity { // Toggle debug mode mapView.toggleDebug(); + menuItem.setChecked(mapView.isDebugActive()); + // Show the FPS counter if (mapView.isDebugActive()) { mFpsTextView.setVisibility(View.VISIBLE); @@ -272,11 +277,13 @@ public class MainActivity extends AppCompatActivity { case R.id.action_markers: // Toggle markers toggleAnnotations(!mIsAnnotationsOn); + menuItem.setChecked(mIsAnnotationsOn); break; case R.id.action_compass: // Toggle compass mapView.setCompassEnabled(!mapView.isCompassEnabled()); + menuItem.setChecked(mapView.isCompassEnabled()); break; /* diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml index 7cfc47f90..7244fa09b 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -5,15 +5,18 @@ android:id="@+id/action_debug" android:icon="@drawable/ic_action_about" android:title="@string/action_debug" + android:checkable="true" /> point for perspective views --- platform/ios/MGLMapView.mm | 8 -- src/mbgl/map/map.cpp | 4 +- src/mbgl/map/source.cpp | 2 +- src/mbgl/map/transform_state.cpp | 152 +++++++++++++------------------ src/mbgl/map/transform_state.hpp | 26 ++++-- src/mbgl/renderer/painter.cpp | 2 +- 6 files changed, 86 insertions(+), 108 deletions(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index cdd3044dc..cdfa77935 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1621,10 +1621,6 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { CGPoint convertedPoint = [self convertPoint:point fromView:view]; - // flip y coordinate for iOS view origin top left - // - convertedPoint.y = self.bounds.size.height - convertedPoint.y; - return MGLLocationCoordinate2DFromLatLng(_mbglMap->latLngForPixel(mbgl::vec2(convertedPoint.x, convertedPoint.y))); } @@ -1632,10 +1628,6 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { mbgl::vec2 pixel = _mbglMap->pixelForLatLng(MGLLatLngFromLocationCoordinate2D(coordinate)); - // flip y coordinate for iOS view origin in top left - // - pixel.y = self.bounds.size.height - pixel.y; - return [self convertPoint:CGPointMake(pixel.x, pixel.y) toView:view]; } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index e70972a13..065c33ce5 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -304,11 +304,11 @@ const LatLng Map::latLngForProjectedMeters(const ProjectedMeters projectedMeters } const vec2 Map::pixelForLatLng(const LatLng latLng) const { - return transform->getState().pixelForLatLng(latLng); + return transform->getState().latLngToPoint(latLng); } const LatLng Map::latLngForPixel(const vec2 pixel) const { - return transform->getState().latLngForPixel(pixel); + return transform->getState().pointToLatLng(pixel); } #pragma mark - Annotations diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index f60ddce57..9aedf26a6 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -333,7 +333,7 @@ std::forward_list Source::coveringTiles(const TransformState& state) con // Map four viewport corners to pixel coordinates box points = state.cornersToBox(z); - const TileCoordinate center = state.pointCoordinate({ state.getWidth() / 2.0f, state.getHeight()/ 2.0f }).zoomTo(z); + const TileCoordinate center = state.pointToCoordinate({ state.getWidth() / 2.0f, state.getHeight()/ 2.0f }).zoomTo(z); std::forward_list covering_tiles = tileCover(z, points, reparseOverscaled ? actualZ : z); diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 2f6231828..9e4ea457e 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -46,10 +45,10 @@ box TransformState::cornersToBox(uint32_t z) const { double w = width; double h = height; box b( - pointCoordinate({ 0, 0 }).zoomTo(z), - pointCoordinate({ w, 0 }).zoomTo(z), - pointCoordinate({ w, h }).zoomTo(z), - pointCoordinate({ 0, h }).zoomTo(z)); + pointToCoordinate({ 0, 0 }).zoomTo(z), + pointToCoordinate({ w, 0 }).zoomTo(z), + pointToCoordinate({ w, h }).zoomTo(z), + pointToCoordinate({ 0, h }).zoomTo(z)); return b; } @@ -158,94 +157,73 @@ float TransformState::getPitch() const { #pragma mark - Projection -const vec2 TransformState::pixelForLatLng(const LatLng latLng) const { - LatLng ll = getLatLng(); - double zoom = getZoom(); - - const double centerX = static_cast(width) / 2.0; - const double centerY = static_cast(height) / 2.0; - - const double m = Projection::getMetersPerPixelAtLatitude(0, zoom); - - const double angle_sin = std::sin(-angle); - const double angle_cos = std::cos(-angle); - - const ProjectedMeters givenMeters = Projection::projectedMetersForLatLng(latLng); - - const double givenAbsoluteX = givenMeters.easting / m; - const double givenAbsoluteY = givenMeters.northing / m; - - const ProjectedMeters centerMeters = Projection::projectedMetersForLatLng(ll); - - const double centerAbsoluteX = centerMeters.easting / m; - const double centerAbsoluteY = centerMeters.northing / m; - - const double deltaX = givenAbsoluteX - centerAbsoluteX; - const double deltaY = givenAbsoluteY - centerAbsoluteY; - - const double translatedX = deltaX + centerX; - const double translatedY = deltaY + centerY; - - const double rotatedX = translatedX * angle_cos - translatedY * angle_sin; - const double rotatedY = translatedX * angle_sin + translatedY * angle_cos; - - const double rotatedCenterX = centerX * angle_cos - centerY * angle_sin; - const double rotatedCenterY = centerX * angle_sin + centerY * angle_cos; - - double x_ = rotatedX + (centerX - rotatedCenterX); - double y_ = rotatedY + (centerY - rotatedCenterY); - - return vec2(x_, y_); +float TransformState::lngX(float lng) const { + return (180.0f + lng) * worldSize() / 360.0f; } -const LatLng TransformState::latLngForPixel(const vec2 pixel) const { - LatLng ll = getLatLng(); - double zoom = getZoom(); - - const double centerX = static_cast(width) / 2.0; - const double centerY = static_cast(height) / 2.0; - - const double m = Projection::getMetersPerPixelAtLatitude(0, zoom); - - const double angle_sin = std::sin(angle); - const double angle_cos = std::cos(angle); - - const double unrotatedCenterX = centerX * angle_cos - centerY * angle_sin; - const double unrotatedCenterY = centerX * angle_sin + centerY * angle_cos; - - const double unrotatedX = pixel.x * angle_cos - pixel.y * angle_sin; - const double unrotatedY = pixel.x * angle_sin + pixel.y * angle_cos; - - const double givenX = unrotatedX + (centerX - unrotatedCenterX); - const double givenY = unrotatedY + (centerY - unrotatedCenterY); - - const ProjectedMeters centerMeters = Projection::projectedMetersForLatLng(ll); - - const double centerAbsoluteX = centerMeters.easting / m; - const double centerAbsoluteY = centerMeters.northing / m; - - const double givenAbsoluteX = givenX + centerAbsoluteX - centerX; - const double givenAbsoluteY = givenY + centerAbsoluteY - centerY; - - ProjectedMeters givenMeters = ProjectedMeters(givenAbsoluteY * m, givenAbsoluteX * m); - - // adjust for date line - ProjectedMeters sw, ne; - Projection::getWorldBoundsMeters(sw, ne); - double d = ne.easting - sw.easting; - if (ll.longitude > 0 && givenMeters.easting > centerMeters.easting) givenMeters.easting -= d; - - // adjust for world wrap - while (givenMeters.easting < sw.easting) givenMeters.easting += d; - while (givenMeters.easting > ne.easting) givenMeters.easting -= d; - - return Projection::latLngForProjectedMeters(givenMeters); +float TransformState::latY(float lat) const { + float y_ = 180.0f / M_PI * std::log(std::tan(M_PI / 4 + lat * M_PI / 360.0f)); + return (180.0f - y_) * worldSize() / 360.0f; } -TileCoordinate TransformState::pointCoordinate(const vec2 point) const { +float TransformState::xLng(float x_, float worldSize_) const { + return x_ * 360.0f / worldSize_ - 180.0f; +} + +float TransformState::yLat(float y_, float worldSize_) const { + float y2 = 180.0f - y_ * 360.0f / worldSize_; + return 360.0f / M_PI * std::atan(std::exp(y2 * M_PI / 180.0f)) - 90.0f; +} + +float TransformState::zoomScale(float zoom) const { + return std::pow(2.0f, zoom); +} + +float TransformState::worldSize() const { + return util::tileSize * scale; +} + +vec2 TransformState::latLngToPoint(const LatLng& latLng) const { + return coordinateToPoint(latLngToCoordinate(latLng)); +} + +LatLng TransformState::pointToLatLng(const vec2 point) const { + return coordinateToLatLng(pointToCoordinate(point)); +} + +TileCoordinate TransformState::latLngToCoordinate(const LatLng& latLng) const { + const float tileZoom = getIntegerZoom(); + const float k = zoomScale(tileZoom) / worldSize(); + return { + lngX(latLng.longitude) * k, + latY(latLng.latitude) * k, + tileZoom + }; +} + +LatLng TransformState::coordinateToLatLng(const TileCoordinate& coord) const { + const float worldSize_ = zoomScale(coord.zoom); + LatLng latLng = { + yLat(coord.row, worldSize_), + xLng(coord.column, worldSize_) + }; + while (latLng.longitude < 180.0f) latLng.longitude += 360.0f; + while (latLng.longitude > 180.0f) latLng.longitude -= 360.0f; + return latLng; +} + +vec2 TransformState::coordinateToPoint(const TileCoordinate& coord) const { + mat4 mat = coordinatePointMatrix(coord.zoom); + matrix::vec4 p; + matrix::vec4 c = {{ coord.column, coord.row, 0, 1 }}; + matrix::transformMat4(p, c, mat); + return { p[0] / p[3], p[1] / p[3] }; +} + +TileCoordinate TransformState::pointToCoordinate(const vec2 point) const { float targetZ = 0; - float tileZoom = std::floor(getZoom()); + const float tileZoom = getIntegerZoom(); mat4 mat = coordinatePointMatrix(tileZoom); diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 521131b33..d4b46f04b 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -31,9 +31,6 @@ public: uint16_t getWidth() const; uint16_t getHeight() const; - float worldSize() const; - float lngX(float lon) const; - float latY(float lat) const; std::array locationCoordinate(float lon, float lat) const; void getLonLat(double &lon, double &lat) const; @@ -55,18 +52,22 @@ public: float getAltitude() const; float getPitch() const; - // Projection - const vec2 pixelForLatLng(const LatLng latLng) const; - const LatLng latLngForPixel(const vec2 pixel) const; - // Changing bool isChanging() const; double pixel_x() const; double pixel_y() const; - // Conversions - TileCoordinate pointCoordinate(const vec2 point) const; + // Conversion and projection + + vec2 latLngToPoint(const LatLng& latLng) const; + LatLng pointToLatLng(const vec2 point) const; + + TileCoordinate latLngToCoordinate(const LatLng& latLng) const; + LatLng coordinateToLatLng(const TileCoordinate& coord) const; + + vec2 coordinateToPoint(const TileCoordinate& coord) const; + TileCoordinate pointToCoordinate(const vec2 point) const; private: void constrain(double& scale, double& y) const; @@ -78,6 +79,13 @@ private: // logical dimensions uint16_t width = 0, height = 0; + float xLng(float x, float worldSize) const; + float yLat(float y, float worldSize) const; + float lngX(float lon) const; + float latY(float lat) const; + float zoomScale(float zoom) const; + float worldSize() const; + mat4 coordinatePointMatrix(float z) const; mat4 getPixelMatrix() const; diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index 6b78ca40d..9ea75efb8 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -379,7 +379,7 @@ void Painter::renderBackground(const StyleLayer &layer_desc) { patternShader->u_opacity = properties.opacity; LatLng latLng = state.getLatLng(); - vec2 center = state.pixelForLatLng(latLng); + vec2 center = state.latLngToPoint(latLng); float scale = 1 / std::pow(2, zoomFraction); std::array sizeA = imagePosA.size; From 10f0d8d23ab0d40254751220df76e28bac4786b8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 24 Aug 2015 20:33:19 -0400 Subject: [PATCH 583/760] fix circle rendering --- src/mbgl/renderer/painter_circle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/renderer/painter_circle.cpp b/src/mbgl/renderer/painter_circle.cpp index 0c0ac1506..538ba9e6a 100644 --- a/src/mbgl/renderer/painter_circle.cpp +++ b/src/mbgl/renderer/painter_circle.cpp @@ -40,7 +40,7 @@ void Painter::renderCircle(CircleBucket& bucket, useProgram(circleShader->program); circleShader->u_matrix = vtxMatrix; - circleShader->u_exmatrix = projMatrix; + circleShader->u_exmatrix = extrudeMatrix; circleShader->u_color = color; circleShader->u_blur = std::max(properties.blur, antialiasing); circleShader->u_size = properties.radius; From 6966addb8d0b850f10195408c0eef38836c97a1d Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Tue, 25 Aug 2015 11:48:49 +1000 Subject: [PATCH 584/760] Use v8 styles on Android. Closes #2165 --- .../src/main/res/values/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 81ba6e1cc..403da277c 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -19,11 +19,11 @@ Dark Satellite - asset://styles/mapbox-streets-v7.json - asset://styles/emerald-v7.json - asset://styles/light-v7.json - asset://styles/dark-v7.json - asset://styles/satellite-v7.json + asset://styles/streets-v8.json + asset://styles/emerald-v8.json + asset://styles/light-v8.json + asset://styles/dark-v8.json + asset://styles/satellite-v8.json Day From 15a5532d76c387142db60913c4ce0876d4450eb8 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 24 Aug 2015 17:51:54 -0700 Subject: [PATCH 585/760] updates for "iOS SDK" series releases --- CHANGELOG.md | 8 ++++++++ ios/Mapbox-iOS-SDK.podspec | 2 +- scripts/ios/package.sh | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b1959a4..c1b3e3003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## iOS 2.0.0 + +Repackaging 2.0.0-pre.1 as it contained no issues. + +## iOS 2.0.0-pre.1 + +Repackaging 0.5.1 as the Mapbox iOS SDK 2.0.0 series. + ## 0.5.1 ### iOS diff --git a/ios/Mapbox-iOS-SDK.podspec b/ios/Mapbox-iOS-SDK.podspec index f09486c82..099a84c8a 100644 --- a/ios/Mapbox-iOS-SDK.podspec +++ b/ios/Mapbox-iOS-SDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |m| m.name = 'Mapbox-iOS-SDK' - m.version = '2.0.0-pre.1-symbols' + m.version = '2.0.0-symbols' m.summary = 'Open source vector map solution for iOS with full styling capabilities.' m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.' diff --git a/scripts/ios/package.sh b/scripts/ios/package.sh index 26e0cc37f..2b1e97c30 100755 --- a/scripts/ios/package.sh +++ b/scripts/ios/package.sh @@ -111,7 +111,7 @@ if [ -z `which appledoc` ]; then fi DOCS_OUTPUT="${OUTPUT}/static/Docs" git fetch --tags -DOCS_VERSION=$( git tag --sort -v:refname | grep -v '\-rc.' | sed -n '1p' | sed 's/^v//' ) +DOCS_VERSION=$( git tag | sed 's/^ios-//' | sort -r | grep -v '\-rc.' | grep -v '\-pre.' | sed -n '1p' | sed 's/^v//' ) rm -rf /tmp/mbgl mkdir -p /tmp/mbgl/ README=/tmp/mbgl/README.md From 36d71c95dc36a182e2b27cf874ab4cbd168a8cd0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Tue, 25 Aug 2015 11:59:02 -0400 Subject: [PATCH 586/760] move the point origin back to bottom-left --- platform/ios/MGLMapView.mm | 8 ++++++++ src/mbgl/map/transform_state.cpp | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index cdfa77935..cdd3044dc 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1621,6 +1621,10 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { CGPoint convertedPoint = [self convertPoint:point fromView:view]; + // flip y coordinate for iOS view origin top left + // + convertedPoint.y = self.bounds.size.height - convertedPoint.y; + return MGLLocationCoordinate2DFromLatLng(_mbglMap->latLngForPixel(mbgl::vec2(convertedPoint.x, convertedPoint.y))); } @@ -1628,6 +1632,10 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { mbgl::vec2 pixel = _mbglMap->pixelForLatLng(MGLLatLngFromLocationCoordinate2D(coordinate)); + // flip y coordinate for iOS view origin in top left + // + pixel.y = self.bounds.size.height - pixel.y; + return [self convertPoint:CGPointMake(pixel.x, pixel.y) toView:view]; } diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 9e4ea457e..c672ca5de 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -217,7 +217,7 @@ vec2 TransformState::coordinateToPoint(const TileCoordinate& coord) cons matrix::vec4 p; matrix::vec4 c = {{ coord.column, coord.row, 0, 1 }}; matrix::transformMat4(p, c, mat); - return { p[0] / p[3], p[1] / p[3] }; + return { p[0] / p[3], height - p[1] / p[3] }; } TileCoordinate TransformState::pointToCoordinate(const vec2 point) const { @@ -232,14 +232,16 @@ TileCoordinate TransformState::pointToCoordinate(const vec2 point) const if (err) throw std::runtime_error("failed to invert coordinatePointMatrix"); + double flippedY = height - point.y; + // since we don't know the correct projected z value for the point, // unproject two points to get a line and then find the point on that // line with z=0 matrix::vec4 coord0; matrix::vec4 coord1; - matrix::vec4 point0 = {{ point.x, point.y, 0, 1 }}; - matrix::vec4 point1 = {{ point.x, point.y, 1, 1 }}; + matrix::vec4 point0 = {{ point.x, flippedY, 0, 1 }}; + matrix::vec4 point1 = {{ point.x, flippedY, 1, 1 }}; matrix::transformMat4(coord0, point0, inverted); matrix::transformMat4(coord1, point1, inverted); From 3fea9d38782f9c42a606206b1318f1e5508cf62a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 14:30:13 -0400 Subject: [PATCH 587/760] 'make node' compiles! --- .gitignore | 2 ++ Makefile | 4 ++++ platform/node/binding.gyp | 9 ++++++--- scripts/main.mk | 18 +++++++++++++++--- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 3e46c0a0a..7613490aa 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ /src/shader/shaders_gl.cpp /src/shader/shaders_gles2.cpp /bin/style.bin.js +/platform/*.mk +/platform/*.Makefile *~ /ios/benchmark/assets/tiles/mapbox.mapbox-streets-v6/ diff --git a/Makefile b/Makefile index 358b3c05c..c901f645a 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,10 @@ android-all: android-lib-x86 android-lib-x86-64 android-all: android-lib-mips android-lib-mips-64 cd android/java && ./gradlew --parallel-threads=$(JOBS) assemble$(BUILDTYPE) +# Builds the Node.js library +.PHONY: node +node: ; $(RUN) HTTP=none ASSET=none CACHE=none Makefile/node + .PHONY: test test: ; $(RUN) Makefile/test test-%: ; $(RUN) test-$* diff --git a/platform/node/binding.gyp b/platform/node/binding.gyp index a5fb58339..560d82c9c 100644 --- a/platform/node/binding.gyp +++ b/platform/node/binding.gyp @@ -1,10 +1,13 @@ { + 'includes': [ + '../../gyp/common.gypi', + ], 'targets': [ { 'target_name': '<(module_name)', 'dependencies': [ - './<(mbgl)/mbgl.gyp:core', - './<(mbgl)/mbgl.gyp:platform-<(platform_lib)', - './<(mbgl)/mbgl.gyp:headless-<(headless_lib)', + '../../mbgl.gyp:core', + '../../mbgl.gyp:platform-<(platform_lib)', + '../../mbgl.gyp:headless-<(headless_lib)', ], 'include_dirs': [ " Date: Tue, 25 Aug 2015 11:18:27 -0700 Subject: [PATCH 588/760] Make Bucket::uploaded an atomic bool This class is called from multiple threads, so we better make this synchronization flag atomic. --- src/mbgl/renderer/bucket.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp index 4bdb766a7..6185a5c9b 100644 --- a/src/mbgl/renderer/bucket.hpp +++ b/src/mbgl/renderer/bucket.hpp @@ -5,6 +5,8 @@ #include #include +#include + #define BUFFER_OFFSET(i) ((char*)nullptr + (i)) namespace mbgl { @@ -15,6 +17,8 @@ class TileID; class Bucket : private util::noncopyable { public: + Bucket() : uploaded(false) {} + // As long as this bucket has a Prepare render pass, this function is getting called. Typically, // this only happens once when the bucket is being rendered for the first time. virtual void upload() = 0; @@ -33,7 +37,7 @@ public: virtual void swapRenderData() {} protected: - bool uploaded = false; + std::atomic uploaded; }; From 765c4695d85ca9114c419e2e49f121b41f80a85d Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Tue, 25 Aug 2015 11:18:31 -0700 Subject: [PATCH 589/760] Revert "only update the render order array when there are changes to tiles" This reverts commit fd98607c5fcb14aaa29c046a0b7115f47aaf2ddc. Fixes #2163. --- src/mbgl/map/map_context.cpp | 5 ----- src/mbgl/map/source.cpp | 9 ++------- src/mbgl/renderer/painter.cpp | 28 +++++++++++++++++----------- src/mbgl/renderer/painter.hpp | 6 ++---- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 19e8d02ec..a8dd86284 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -334,7 +334,6 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) if (!painter) { painter = std::make_unique(data); painter->setup(); - painter->updateRenderOrder(*style); } painter->setDebug(data.getDebug()); @@ -401,10 +400,6 @@ void MapContext::setSprite(const std::string& name, std::shared_ptrupdateRenderOrder(*style); - } - updateFlags |= Update::Repaint; asyncUpdate->send(); } diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 4b2ae2cfb..518468af6 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -529,14 +529,9 @@ void Source::invalidateTiles(const std::unordered_set& ids } void Source::updateTilePtrs() { - std::vector newPtrs; + tilePtrs.clear(); for (const auto& pair : tiles) { - newPtrs.push_back(pair.second.get()); - } - - if (tilePtrs != newPtrs) { - tilePtrs = newPtrs; - emitTileLoaded(true); + tilePtrs.push_back(pair.second.get()); } } diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index b55b71dcb..9e5feaec7 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -184,6 +184,9 @@ void Painter::render(const Style& style, TransformState state_, const FrameData& resize(); changeMatrix(); + // Figure out what buckets we have to draw and what order we have to draw them in. + const auto order = determineRenderOrder(style); + // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. { @@ -202,6 +205,7 @@ void Painter::render(const Style& style, TransformState state_, const FrameData& } } + // - CLIPPING MASKS ---------------------------------------------------------------------------- // Draws the clipping masks to the stencil buffer. { @@ -272,8 +276,6 @@ void Painter::renderPass(RenderPass pass_, const float strataThickness) { pass = pass_; - const double zoom = state.getZoom(); - MBGL_DEBUG_GROUP(pass == RenderPass::Opaque ? "opaque" : "translucent"); if (debug::renderTree) { @@ -286,13 +288,6 @@ void Painter::renderPass(RenderPass pass_, for (; it != end; ++it, i += increment) { const auto& item = *it; if (item.bucket && item.tile) { - // Skip this layer if it's outside the range of min/maxzoom. - // This may occur when there /is/ a bucket created for this layer, but the min/max-zoom - // is set to a fractional value, or value that is larger than the source maxzoom. - if (item.layer.bucket->min_zoom > zoom || - item.layer.bucket->max_zoom <= zoom) { - continue; - } if (item.layer.hasRenderPass(pass)) { MBGL_DEBUG_GROUP(item.layer.id + " - " + std::string(item.tile->id)); setStrata(i * strataThickness); @@ -311,8 +306,8 @@ void Painter::renderPass(RenderPass pass_, } } -void Painter::updateRenderOrder(const Style& style) { - order.clear(); +std::vector Painter::determineRenderOrder(const Style& style) { + std::vector order; for (const auto& layerPtr : style.layers) { const auto& layer = *layerPtr; @@ -349,6 +344,15 @@ void Painter::updateRenderOrder(const Style& style) { continue; } + // Skip this layer if it's outside the range of min/maxzoom. + // This may occur when there /is/ a bucket created for this layer, but the min/max-zoom + // is set to a fractional value, or value that is larger than the source maxzoom. + const double zoom = state.getZoom(); + if (layer.bucket->min_zoom > zoom || + layer.bucket->max_zoom <= zoom) { + continue; + } + const auto& tiles = source->getTiles(); for (auto tile : tiles) { assert(tile); @@ -362,6 +366,8 @@ void Painter::updateRenderOrder(const Style& style) { } } } + + return order; } void Painter::renderBackground(const StyleLayer &layer_desc) { diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index 78cf308b7..e4e1f8cff 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -137,12 +137,12 @@ public: bool needsAnimation() const; - void updateRenderOrder(const Style& style); - private: void setupShaders(); mat4 translatedMatrix(const mat4& matrix, const std::array &translation, const TileID &id, TranslateAnchorType anchor); + std::vector determineRenderOrder(const Style& style); + template void renderPass(RenderPass, Iterator it, Iterator end, @@ -204,8 +204,6 @@ private: const float strata_epsilon = 1.0f / (1 << 16); Color background = {{ 0, 0, 0, 0 }}; - std::vector order; - public: FrameHistory frameHistory; From 4540c3c0e8c80de4f0074c1c4328840ebf708470 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 20 Aug 2015 16:11:32 -0400 Subject: [PATCH 590/760] View::swap -> View::beforeRender View::afterRender To be able to resize the framebuffer on the map thread in HeadlessView. --- android/cpp/native_map_view.cpp | 6 +++++- include/mbgl/android/native_map_view.hpp | 3 ++- include/mbgl/map/view.hpp | 5 ++++- include/mbgl/platform/default/glfw_view.hpp | 3 ++- .../mbgl/platform/default/headless_view.hpp | 5 ++++- platform/default/glfw_view.cpp | 6 +++++- platform/default/headless_view.cpp | 19 ++++++++++++++----- platform/ios/MGLMapView.mm | 7 ++++++- src/mbgl/map/map_context.cpp | 4 +++- test/fixtures/mock_view.hpp | 3 ++- 10 files changed, 47 insertions(+), 14 deletions(-) diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index d7c40ba5a..0cb68408a 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -159,7 +159,11 @@ void NativeMapView::invalidate() { detach_jni_thread(vm, &env, detach); } -void NativeMapView::swap() { +void NativeMapView::beforeRender() { + // no-op +} + +void NativeMapView::afterRender() { mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::swap"); if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) { diff --git a/include/mbgl/android/native_map_view.hpp b/include/mbgl/android/native_map_view.hpp index 3ae3d6ed9..d5606ae37 100644 --- a/include/mbgl/android/native_map_view.hpp +++ b/include/mbgl/android/native_map_view.hpp @@ -27,7 +27,8 @@ public: void deactivate() override; void notify() override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; void notifyMapChange(mbgl::MapChange) override; diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index 3086d51aa..fadd035e4 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -62,8 +62,11 @@ public: // (map->renderSync() from the main thread must be called as a result of this) virtual void invalidate() = 0; + // Called from the render thread before the render begins. + virtual void beforeRender() = 0; + // Called from the render thread after the render is complete. - virtual void swap() = 0; + virtual void afterRender() = 0; // Reads the pixel data from the current framebuffer. If your View implementation // doesn't support reading from the framebuffer, return a null pointer. diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index ec9908073..17050d64d 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -24,7 +24,8 @@ public: void deactivate() override; void notify() override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods); static void onScroll(GLFWwindow *window, double xoffset, double yoffset); diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_view.hpp index ce0ff2a68..50edc4842 100644 --- a/include/mbgl/platform/default/headless_view.hpp +++ b/include/mbgl/platform/default/headless_view.hpp @@ -37,9 +37,11 @@ public: void deactivate() override; void notify() override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; std::unique_ptr readStillImage() override; + void resizeFramebuffer(); void resize(uint16_t width, uint16_t height); private: @@ -52,6 +54,7 @@ private: std::shared_ptr display; const float pixelRatio; std::array dimensions; + bool needsResize; #if MBGL_USE_CGL CGLContextObj glContext = nullptr; diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index 8453845d1..19f337994 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -386,7 +386,11 @@ void GLFWView::invalidate() { glfwPostEmptyEvent(); } -void GLFWView::swap() { +void GLFWView::beforeRender() { + // no-op +} + +void GLFWView::afterRender() { glfwSwapBuffers(window); } diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index 2d6527785..a582ed579 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -114,10 +114,10 @@ bool HeadlessView::isActive() { return std::this_thread::get_id() == thread; } -void HeadlessView::resize(const uint16_t width, const uint16_t height) { - activate(); +void HeadlessView::resizeFramebuffer() { + assert(isActive()); - dimensions = {{ width, height }}; + if (!needsResize) return; clearBuffers(); @@ -158,7 +158,12 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height) { throw std::runtime_error(error); } - deactivate(); + needsResize = false; +} + +void HeadlessView::resize(const uint16_t width, const uint16_t height) { + dimensions = {{ width, height }}; + needsResize = true; } std::unique_ptr HeadlessView::readStillImage() { @@ -293,7 +298,11 @@ void HeadlessView::invalidate() { // no-op } -void HeadlessView::swap() { +void HeadlessView::beforeRender() { + resizeFramebuffer(); +} + +void HeadlessView::afterRender() { // no-op } diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index cdd3044dc..803cef9f5 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -2943,7 +2943,12 @@ class MBGLView : public mbgl::View waitUntilDone:NO]; } - void swap() override + void beforeRender() override + { + // no-op + } + + void afterRender() override { // no-op } diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 19e8d02ec..175f98719 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -326,6 +326,8 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) return false; } + view.beforeRender(); + transformState = state; // Cleanup OpenGL objects that we abandoned since the last render call. @@ -345,7 +347,7 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) callback = nullptr; } - view.swap(); + view.afterRender(); viewInvalidated = false; data.setNeedsRepaint(style->hasTransitions() || painter->needsAnimation()); diff --git a/test/fixtures/mock_view.hpp b/test/fixtures/mock_view.hpp index 17ad6ccfa..e608545da 100644 --- a/test/fixtures/mock_view.hpp +++ b/test/fixtures/mock_view.hpp @@ -20,7 +20,8 @@ public: void deactivate() override {}; void notify() override {}; void invalidate() override {} - void swap() override {} + void beforeRender() override {} + void afterRender() override {} }; } From 6a31e6c801a536f6341aca0ef9f0ce072de9c338 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 14:46:07 -0400 Subject: [PATCH 591/760] update mbgl::Resource --- platform/node/src/node_mapbox_gl_native.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp index 9ba0abbd0..14a0c9ad2 100644 --- a/platform/node/src/node_mapbox_gl_native.cpp +++ b/platform/node/src/node_mapbox_gl_native.cpp @@ -19,10 +19,12 @@ void RegisterModule(v8::Handle exports) { auto ConstantProperty = static_cast(v8::ReadOnly | v8::DontDelete); auto resource = NanNew(); 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("Image"), NanNew(mbgl::Resource::Image), ConstantProperty); - resource->ForceSet(NanNew("JSON"), NanNew(mbgl::Resource::JSON), 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 From 94af30ff4a93226c8f268dbaa28387b7c487c3b5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 14:57:23 -0400 Subject: [PATCH 592/760] migrate style test fixture to v8, handle errors more gracefully in render test --- platform/node/test/fixtures/style.json | 49 ++++++++++++++------------ platform/node/test/render.test.js | 7 ++-- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/platform/node/test/fixtures/style.json b/platform/node/test/fixtures/style.json index 47a1990eb..55442c879 100644 --- a/platform/node/test/fixtures/style.json +++ b/platform/node/test/fixtures/style.json @@ -1,26 +1,29 @@ { - "version": 7, - "name": "Empty", - "sources": { - "mapbox": { - "type": "vector", - "url": "./fixtures/tiles.tilejson", - "maxzoom": 15 - } + "version": 8, + "name": "Empty", + "sources": { + "mapbox": { + "type": "vector", + "url": "./fixtures/tiles.tilejson", + "maxzoom": 15 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } }, - "layers": [{ - "id": "background", - "type": "background", - "paint": { - "background-color": "white" - } - }, { - "id": "water", - "type": "fill", - "source": "mapbox", - "source-layer": "water", - "paint": { - "fill-color": "blue" - } - }] + { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + } + ] } diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index b55841fef..2fab98bb9 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -53,10 +53,13 @@ function renderTest(style, info, base, key) { map.load(style); map.render(info[key], function(err, data) { - t.error(err); - map.release(); + if (err) { + t.error(err); + return t.end(); + } + var expected = path.join(dir, 'expected.png'); var actual = path.join(dir, 'actual.png'); var diff = path.join(dir, 'diff.png'); From 96d59900892cb7cc4ec23121b231514da910d8eb Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 15:03:05 -0400 Subject: [PATCH 593/760] update npm test --- platform/node/package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/platform/node/package.json b/platform/node/package.json index 7ff254dce..dce278d39 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -35,10 +35,9 @@ "tape": "^4.0.0" }, "scripts": { - "install": "node-pre-gyp install || make", - "test": "make test", - "rebuild": "make clean && make", - "build": "make" + "install": "node-pre-gyp install", + "test": "tape test/js/**/*.test.js | faucet", + "test-suite": "tape test/render.test.js | faucet" }, "gypfile": true, "binary": { From a622bb47564b7cb6b37a6a1e2387816da3137af1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 17:07:19 -0400 Subject: [PATCH 594/760] add FLAVOR=node to build matrix --- .travis.yml | 27 ++++++++++++++++ .../node/after_script.sh | 3 +- scripts/node/install.sh | 22 +++++++++++++ scripts/node/run.sh | 31 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) rename platform/node/scripts/travis_publish.sh => scripts/node/after_script.sh (95%) create mode 100644 scripts/node/install.sh create mode 100644 scripts/node/run.sh diff --git a/.travis.yml b/.travis.yml index 9c65614df..8127ae461 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,33 @@ matrix: addons: apt: packages: [ 'lib32stdc++6' ] + - os: linux + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=iojs NODE_VERSION=2.0.1 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=node NODE_VERSION=0.12.0 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=node NODE_VERSION=0.10.36 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: osx + compiler: clang + env: FLAVOR=node NODE_EXE=iojs NODE_VERSION=2.0.1 + - os: osx + compiler: clang + env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.12.0 + - os: osx + compiler: clang + env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.10.36 env: global: diff --git a/platform/node/scripts/travis_publish.sh b/scripts/node/after_script.sh similarity index 95% rename from platform/node/scripts/travis_publish.sh rename to scripts/node/after_script.sh index d06a2a8a3..c757a1b8c 100755 --- a/platform/node/scripts/travis_publish.sh +++ b/scripts/node/after_script.sh @@ -1,6 +1,7 @@ #!/bin/bash set -e +set -o pipefail # Inspect binary. if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then @@ -11,7 +12,7 @@ fi COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') -if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ $CXX =~ ^clang* ]]; then +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then npm install aws-sdk ./node_modules/.bin/node-pre-gyp package diff --git a/scripts/node/install.sh b/scripts/node/install.sh new file mode 100644 index 000000000..e804d47fa --- /dev/null +++ b/scripts/node/install.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +mapbox_time "checkout_mason" \ +git submodule update --init .mason + +PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" \ + +if [ ${TRAVIS_OS_NAME} == "linux" ]; then + mapbox_time "install_mesa" \ + mason install mesa 10.4.3 +fi + +mapbox_time $NODE_EXE \ +mason install $NODE_EXE $NODE_VERSION + +export PATH="`mason prefix $NODE_EXE $NODE_VERSION`/bin":"$PATH" + +$NODE_EXE --version +npm --version diff --git a/scripts/node/run.sh b/scripts/node/run.sh new file mode 100644 index 000000000..f36ea02a1 --- /dev/null +++ b/scripts/node/run.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +source ./scripts/${TRAVIS_OS_NAME}/setup.sh + +BUILDTYPE=${BUILDTYPE:-Release} + +################################################################################ +# Build +################################################################################ + +mapbox_time "checkout_styles" \ +git submodule update --init styles + +mapbox_time "compile_program" \ +cd platform/node && BUILDTYPE=${BUILDTYPE} npm install --build-from-source + +################################################################################ +# Test +################################################################################ + +mapbox_time "checkout_test_suite" \ +git submodule update --init test/suite + +mapbox_time "run_tests" \ +npm test + +mapbox_time "run_render_tests" \ +npm run test-suite From 2586899ee86e1374295b1af8e592c7f663a32075 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 17:51:41 -0400 Subject: [PATCH 595/760] chmod +x --- scripts/node/install.sh | 0 scripts/node/run.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/node/install.sh mode change 100644 => 100755 scripts/node/run.sh diff --git a/scripts/node/install.sh b/scripts/node/install.sh old mode 100644 new mode 100755 diff --git a/scripts/node/run.sh b/scripts/node/run.sh old mode 100644 new mode 100755 From 92d9308eb8a996ad2d85268e36fc04caeec8f5d6 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 21 Jul 2015 18:09:48 -0400 Subject: [PATCH 596/760] Ask for 'always' location permissions, too --- platform/ios/MGLMapView.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index cdd3044dc..5ed9d9200 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -2214,7 +2214,15 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) [NSException raise:@"Missing Location Services usage description" format: @"In iOS 8 and above, this app must have a value for NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in its Info.plist."]; } - [self.locationManager requestWhenInUseAuthorization]; + // request location permissions, if both keys exist ask for less permissive + if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) + { + [self.locationManager requestWhenInUseAuthorization]; + } + else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]) + { + [self.locationManager requestAlwaysAuthorization]; + } } #endif From 4977858f4d5cc181ab8204c472b3ee84c37245a4 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Wed, 22 Jul 2015 11:41:58 -0400 Subject: [PATCH 597/760] Fix testUserTrackingModeFollow test to accept location permissions ask I thought that the tests were bypassing the permissions dialog, but it turns out that was only half correct: we were not actually asking for `always` location permission in MBGL. --- test/ios/MapViewTests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ios/MapViewTests.m b/test/ios/MapViewTests.m index fadbbae60..f4ae501d7 100644 --- a/test/ios/MapViewTests.m +++ b/test/ios/MapViewTests.m @@ -544,7 +544,7 @@ - (void)testUserTrackingModeFollow { tester.mapView.userTrackingMode = MGLUserTrackingModeFollow; - [tester waitForTimeInterval:1]; + [tester acknowledgeSystemAlert]; XCTAssertEqual(tester.mapView.userLocationVisible, YES, @@ -570,7 +570,7 @@ - (void)testUserTrackingModeFollowWithHeading { tester.mapView.userTrackingMode = MGLUserTrackingModeFollowWithHeading; - [tester waitForTimeInterval:1]; + [tester acknowledgeSystemAlert]; XCTAssertEqual(tester.mapView.userLocationVisible, YES, From dca0b1e446dfff9a59140e4cd55e8448c7772cd7 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 25 Aug 2015 18:21:17 -0400 Subject: [PATCH 598/760] export PATH --- scripts/node/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index e804d47fa..b0989ca0b 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -6,7 +6,7 @@ set -o pipefail mapbox_time "checkout_mason" \ git submodule update --init .mason -PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" \ +export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" if [ ${TRAVIS_OS_NAME} == "linux" ]; then mapbox_time "install_mesa" \ From 3643053cb413c3f8289be9e5d183218fb314b310 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 25 Aug 2015 16:00:44 -0700 Subject: [PATCH 599/760] Bump metrics tests timeouts EVERYWHERE --- test/ios/MetricsTests.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/ios/MetricsTests.m b/test/ios/MetricsTests.m index cd02c2d52..d3b99dc63 100644 --- a/test/ios/MetricsTests.m +++ b/test/ios/MetricsTests.m @@ -119,7 +119,7 @@ const NSTimeInterval MGLFlushInterval = 60; [[MGLMapboxEvents sharedManager] flush]; - [self waitForExpectationsWithTimeout:1.0 handler:nil]; + [self waitForExpectationsWithTimeout:2.0 handler:nil]; XCTAssertNil([[MGLMapboxEvents sharedManager] timer]); } @@ -143,7 +143,7 @@ const NSTimeInterval MGLFlushInterval = 60; id eventsMock = [OCMockObject partialMockForObject:[MGLMapboxEvents sharedManager]]; [[[eventsMock expect] andForwardToRealObject] startTimer]; [self pushFakeEvent]; - [eventsMock verifyWithDelay:0.5]; + [eventsMock verifyWithDelay:2.0]; XCTAssertEqual([[[MGLMapboxEvents sharedManager] eventQueue] count], 1); XCTAssertNotNil([[MGLMapboxEvents sharedManager] timer]); @@ -164,7 +164,7 @@ const NSTimeInterval MGLFlushInterval = 60; [[MGLMapboxEvents sharedManager] flush]; - [self waitForExpectationsWithTimeout:1.0 handler:nil]; + [self waitForExpectationsWithTimeout:2.0 handler:nil]; [self pushFakeEvent]; id eventsMock = [OCMockObject partialMockForObject:[MGLMapboxEvents sharedManager]]; @@ -178,7 +178,7 @@ const NSTimeInterval MGLFlushInterval = 60; id eventsMock = [OCMockObject partialMockForObject:[MGLMapboxEvents sharedManager]]; [[eventsMock expect] postEvents:events]; [[MGLMapboxEvents sharedManager] flush]; - [eventsMock verifyWithDelay:1.0]; + [eventsMock verifyWithDelay:2.0]; XCTAssertTrue([[[MGLMapboxEvents sharedManager] eventQueue] count] == 0); } @@ -209,7 +209,7 @@ const NSTimeInterval MGLFlushInterval = 60; [self pushFakeEvent]; [MGLMapboxEvents flush]; - [self waitForExpectationsWithTimeout:1.0 handler:nil]; + [self waitForExpectationsWithTimeout:2.0 handler:nil]; } - (void)testPushEventAddsToEventQueue { From d42728fdabc49b24df78e6dfee3f7cc1717db126 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:04:11 -0400 Subject: [PATCH 600/760] travis make node --- scripts/node/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/node/run.sh b/scripts/node/run.sh index f36ea02a1..e173bb706 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -15,7 +15,7 @@ mapbox_time "checkout_styles" \ git submodule update --init styles mapbox_time "compile_program" \ -cd platform/node && BUILDTYPE=${BUILDTYPE} npm install --build-from-source +make node -j${JOBS} BUILDTYPE=${BUILDTYPE} ################################################################################ # Test From 031a76303590183bc1d42f609592f7fe81035fbb Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:04:51 -0400 Subject: [PATCH 601/760] cd platform/node && npm test --- scripts/node/run.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/node/run.sh b/scripts/node/run.sh index e173bb706..dea4ed8d8 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -25,7 +25,7 @@ mapbox_time "checkout_test_suite" \ git submodule update --init test/suite mapbox_time "run_tests" \ -npm test +cd platform/node && npm test mapbox_time "run_render_tests" \ -npm run test-suite +cd platform/node && npm run test-suite From 677954822c87350ff6d84cdb529685bd594e439a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:16:31 -0400 Subject: [PATCH 602/760] npm install --build-from-source --- platform/node/package.json | 2 +- scripts/node/run.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/node/package.json b/platform/node/package.json index dce278d39..9644083c0 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -35,7 +35,7 @@ "tape": "^4.0.0" }, "scripts": { - "install": "node-pre-gyp install", + "install": "node-pre-gyp install || make node -C ../../", "test": "tape test/js/**/*.test.js | faucet", "test-suite": "tape test/render.test.js | faucet" }, diff --git a/scripts/node/run.sh b/scripts/node/run.sh index dea4ed8d8..0316065ef 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -15,7 +15,7 @@ mapbox_time "checkout_styles" \ git submodule update --init styles mapbox_time "compile_program" \ -make node -j${JOBS} BUILDTYPE=${BUILDTYPE} +cd platform/node && npm install --build-from-source ################################################################################ # Test From e3ede563fbbb2712f78d032e41e8e5eef3b72f11 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:35:08 -0400 Subject: [PATCH 603/760] use mapbox-gl-test-suite submodule, bump node deps --- platform/node/package.json | 11 +++++------ platform/node/test/render.test.js | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/platform/node/package.json b/platform/node/package.json index 9644083c0..6181dfa2f 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -19,20 +19,19 @@ ], "dependencies": { "nan": "^1.9.0", - "node-pre-gyp": "^0.6.7" + "node-pre-gyp": "^0.6.9" }, "bundledDependencies": [ "node-pre-gyp" ], "devDependencies": { - "aws-sdk": "^2.1.27", + "aws-sdk": "^2.1.47", "faucet": "0.0.1", - "mapbox-gl-test-suite": "git://github.com/mapbox/mapbox-gl-test-suite#7251829de9d2804fe02cd8f7acc0e30ea278429d", "mkdirp": "^0.5.1", "pngjs": "^0.4.0", - "request": "^2.55.0", - "st": "^0.5.3", - "tape": "^4.0.0" + "request": "^2.61.0", + "st": "^0.5.5", + "tape": "^4.2.0" }, "scripts": { "install": "node-pre-gyp install || make node -C ../../", diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 2fab98bb9..2edb6fac1 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -9,10 +9,10 @@ var path = require('path'); var mkdirp = require('mkdirp'); var PNG = require('pngjs').PNG; var compare = require('./compare.js'); -var suitePath = path.dirname(require.resolve('mapbox-gl-test-suite/package.json')); +var suitePath = path.join(__dirname, '../../../test/suite'); function template(name) { - return fs.readFileSync(require.resolve('mapbox-gl-test-suite/templates/' + name + '.html.tmpl')).toString(); + return fs.readFileSync(path.join(suitePath, 'templates', name + '.html.tmpl')).toString(); } var results = ''; From fde64030b1ee882394e8042a37713764413aad5b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:37:20 -0400 Subject: [PATCH 604/760] glfw_ldflags --- platform/node/binding.gyp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/binding.gyp b/platform/node/binding.gyp index 560d82c9c..88ad5ed59 100644 --- a/platform/node/binding.gyp +++ b/platform/node/binding.gyp @@ -58,7 +58,7 @@ '-frtti', '-fexceptions', ], - 'libraries': [ '<@(glfw3_ldflags)' ], + 'libraries': [ '<@(glfw_ldflags)' ], }], ['OS=="linux"', { 'cflags_cc': [ From e27d9cfbf7ec5a3a9817629855ce3a3a0681547a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:40:49 -0400 Subject: [PATCH 605/760] xcode6.4 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8127ae461..db296ec15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,12 +77,15 @@ matrix: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: osx + osx_image: xcode6.4 compiler: clang env: FLAVOR=node NODE_EXE=iojs NODE_VERSION=2.0.1 - os: osx + osx_image: xcode6.4 compiler: clang env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.12.0 - os: osx + osx_image: xcode6.4 compiler: clang env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.10.36 From e64c26db2aa3225201e0d0c54eb839a1400b2f61 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 11:54:31 -0400 Subject: [PATCH 606/760] pushd/popd --- scripts/node/run.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 0316065ef..118e1ed25 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -15,7 +15,7 @@ mapbox_time "checkout_styles" \ git submodule update --init styles mapbox_time "compile_program" \ -cd platform/node && npm install --build-from-source +pushd platform/node && npm install --build-from-source && popd ################################################################################ # Test @@ -25,7 +25,7 @@ mapbox_time "checkout_test_suite" \ git submodule update --init test/suite mapbox_time "run_tests" \ -cd platform/node && npm test +pushd platform/node && npm test && popd mapbox_time "run_render_tests" \ -cd platform/node && npm run test-suite +pushd platform/node && npm run test-suite && popd From 233e78d9f38346943fc42491401fd617674e1c0d Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 26 Aug 2015 12:04:09 -0400 Subject: [PATCH 607/760] allow test suite failures --- platform/node/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/package.json b/platform/node/package.json index 6181dfa2f..0a7e021ba 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -36,7 +36,7 @@ "scripts": { "install": "node-pre-gyp install || make node -C ../../", "test": "tape test/js/**/*.test.js | faucet", - "test-suite": "tape test/render.test.js | faucet" + "test-suite": "(tape test/render.test.js | faucet) || true" }, "gypfile": true, "binary": { From 2f30dfe97d851f2a2bb4141dc234013b01f8afd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Tue, 25 Aug 2015 15:27:23 -0700 Subject: [PATCH 608/760] Disable heading updates for .FollowWithCourse Fixes #2180. --- platform/ios/MGLMapView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 5ed9d9200..f12836b05 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -2310,6 +2310,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) break; } case MGLUserTrackingModeFollow: + case MGLUserTrackingModeFollowWithCourse: { self.showsUserLocation = YES; @@ -2323,7 +2324,6 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) break; } case MGLUserTrackingModeFollowWithHeading: - case MGLUserTrackingModeFollowWithCourse: { self.showsUserLocation = YES; From 42dc5336db5179c50b8d1edeca0bef7a27c8cefd Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 18 Aug 2015 16:25:40 -0400 Subject: [PATCH 609/760] iOS perspective gesture support Drag two fingers upward to tilt the map. Implements #2116 --- include/mbgl/ios/MGLMapView.h | 21 ++++ .../darwin/settings_nsuserdefaults.hpp | 1 + ios/app/MBXViewController.mm | 2 + platform/darwin/settings_nsuserdefaults.mm | 3 + platform/ios/MGLMapView.mm | 111 +++++++++++++++++- platform/ios/MGLMapboxEvents.h | 1 + platform/ios/MGLMapboxEvents.m | 1 + 7 files changed, 136 insertions(+), 4 deletions(-) diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 2b9a3d291..fc70f3d90 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -74,6 +74,13 @@ IB_DESIGNABLE * The default value of this property is `YES`. */ @property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled; +/** A Boolean value that determines whether the user may change the pitch (tilt) of the map. + * + * This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still change the pitch of the map programmatically. + * + * The default value of this property is `YES`. */ +@property(nonatomic, getter=isPitchEnabled) BOOL pitchEnabled; + /** The compass image view shown in the upper-right when the map is rotated. */ @property (nonatomic, readonly) UIImageView *compassView; @@ -173,6 +180,20 @@ IB_DESIGNABLE /** Resets the map rotation to a northern heading. */ - (IBAction)resetNorth; +/** The pitch of the map (measured in degrees). + * + * The default value `0` shows a completely flat map. Maximum value is `60`. */ +@property (nonatomic) double pitch; + +/** Changes the pitch of the map. + * @param pitch The pitch of the map (measured in degrees) relative to top-down. + * + * Changing the pitch tilts the map without changing the current center coordinate or zoom level. */ +- (void)setPitch:(double)pitch; + +/** Resets the map pitch to head-on. */ +- (IBAction)resetPitch; + #pragma mark - Converting Map Coordinates /** @name Converting Map Coordinates */ diff --git a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp index 6c91fd302..615320ee5 100644 --- a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp +++ b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp @@ -17,6 +17,7 @@ public: double latitude = 0; double zoom = 0; double bearing = 0; + double pitch = 0; MGLUserTrackingMode userTrackingMode = MGLUserTrackingModeNone; bool showsUserLocation = false; diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index f7cbf0c04..99c207d24 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -86,6 +86,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; settings->latitude = self.mapView.centerCoordinate.latitude; settings->zoom = self.mapView.zoomLevel; settings->bearing = self.mapView.direction; + settings->pitch = self.mapView.pitch; settings->debug = self.mapView.isDebugActive; settings->userTrackingMode = self.mapView.userTrackingMode; settings->showsUserLocation = self.mapView.showsUserLocation; @@ -99,6 +100,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; settings->load(); [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO]; self.mapView.direction = settings->bearing; + self.mapView.pitch = settings->pitch; self.mapView.userTrackingMode = settings->userTrackingMode; self.mapView.showsUserLocation = settings->showsUserLocation; [self.mapView setDebugActive:settings->debug]; diff --git a/platform/darwin/settings_nsuserdefaults.mm b/platform/darwin/settings_nsuserdefaults.mm index f6f9bbeed..5c943baff 100644 --- a/platform/darwin/settings_nsuserdefaults.mm +++ b/platform/darwin/settings_nsuserdefaults.mm @@ -11,6 +11,7 @@ Settings_NSUserDefaults::Settings_NSUserDefaults() @"latitude" : @(latitude), @"zoom" : @(zoom), @"bearing" : @(bearing), + @"pitch" : @(pitch), @"userTrackingMode" : @(userTrackingMode), @"showsUserLocation" : @(showsUserLocation), @"debug" : @(debug), @@ -26,6 +27,7 @@ void Settings_NSUserDefaults::load() latitude = [settings[@"latitude"] doubleValue]; zoom = [settings[@"zoom"] doubleValue]; bearing = [settings[@"bearing"] doubleValue]; + pitch = [settings[@"pitch"] doubleValue]; debug = [settings[@"debug"] boolValue]; unsigned uncheckedTrackingMode = [settings[@"trackingMode"] unsignedIntValue]; @@ -44,6 +46,7 @@ void Settings_NSUserDefaults::save() @"latitude" : @(latitude), @"zoom" : @(zoom), @"bearing" : @(bearing), + @"pitch" : @(pitch), @"userTrackingMode" : @(userTrackingMode), @"showsUserLocation" : @(showsUserLocation), @"debug" : @(debug), diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index f12836b05..4f3e90307 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -85,6 +85,7 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) @property (nonatomic) UIPinchGestureRecognizer *pinch; @property (nonatomic) UIRotationGestureRecognizer *rotate; @property (nonatomic) UILongPressGestureRecognizer *quickZoom; +@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag; @property (nonatomic) NSMapTable *annotationIDsByAnnotation; @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImages; @property (nonatomic) std::vector annotationsNearbyLastTap; @@ -338,6 +339,15 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [twoFingerTap requireGestureRecognizerToFail:_pinch]; [twoFingerTap requireGestureRecognizerToFail:_rotate]; [self addGestureRecognizer:twoFingerTap]; + + _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)]; + _twoFingerDrag.minimumNumberOfTouches = 2; + _twoFingerDrag.maximumNumberOfTouches = 2; + _twoFingerDrag.delegate = self; + [_twoFingerDrag requireGestureRecognizerToFail:twoFingerTap]; + [_twoFingerDrag requireGestureRecognizerToFail:_pan]; + [self addGestureRecognizer:_twoFingerDrag]; + _pitchEnabled = YES; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { @@ -1326,6 +1336,59 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } } +- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag +{ + if ( ! self.isPitchEnabled) return; + + _mbglMap->cancelTransitions(); + + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) + { + [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag]; + } + else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged) + { + CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y; + double currentPitch = _mbglMap->getPitch(); + double minPitch = 0; + double maxPitch = 60.0; + double slowdown = 20.0; + + double pitchNew = fmax(fmin(currentPitch - (gestureDistance / slowdown), maxPitch), minPitch); + + _mbglMap->setPitch(pitchNew); + } + else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled) + { + [self unrotateIfNeededAnimated:YES]; + + //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)]; + } + +} + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) + { + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; + + if (panGesture.minimumNumberOfTouches == 2) + { + CGPoint velocity = [panGesture velocityInView:panGesture.view]; + double gestureAngle = MGLDegreesFromRadians(atan(velocity.y / velocity.x)); + double horizontalToleranceDegrees = 20.0; + + // cancel if gesture angle is not 90º±20º (more or less vertical) + if ( ! (fabs((fabs(gestureAngle) - 90.0)) < horizontalToleranceDegrees)) + { + return NO; + } + } + } + return YES; +} + - (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap { if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)]) @@ -1419,6 +1482,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) return [NSSet setWithObject:@"allowsRotating"]; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitchEnabled +{ + return [NSSet setWithObject:@"allowsPitching"]; +} + - (void)setDebugActive:(BOOL)debugActive { _mbglMap->setDebug(debugActive); @@ -1617,6 +1685,25 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord [self setDirection:direction animated:NO]; } +- (double)pitch +{ + return _mbglMap->getPitch(); +} + +- (void)setPitch:(double)pitch +{ + // constrain pitch to between 0º and 60º + // + _mbglMap->setPitch(fmax(fmin(pitch, 60), 0)); + + //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)]; +} + +- (void)resetPitch +{ + [self setPitch:0]; +} + - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view { CGPoint convertedPoint = [self convertPoint:point fromView:view]; @@ -2630,10 +2717,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) { [self updateCompass]; - if (self.pan.state == UIGestureRecognizerStateChanged || - self.pinch.state == UIGestureRecognizerStateChanged || - self.rotate.state == UIGestureRecognizerStateChanged || - self.quickZoom.state == UIGestureRecognizerStateChanged) return; + if (self.pan.state == UIGestureRecognizerStateChanged || + self.pinch.state == UIGestureRecognizerStateChanged || + self.rotate.state == UIGestureRecognizerStateChanged || + self.quickZoom.state == UIGestureRecognizerStateChanged || + self.twoFingerDrag.state == UIGestureRecognizerStateChanged) return; if (self.isAnimatingGesture) return; @@ -3062,6 +3150,21 @@ class MBGLView : public mbgl::View self.rotateEnabled = allowsRotating; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsPitching +{ + return [NSSet setWithObject:@"pitchEnabled"]; +} + +- (BOOL)allowsPitching +{ + return self.pitchEnabled; +} + +- (void)setAllowsPitching:(BOOL)allowsPitching +{ + self.pitchEnabled = allowsPitching; +} + - (void)didReceiveMemoryWarning { _mbglMap->onLowMemory(); diff --git a/platform/ios/MGLMapboxEvents.h b/platform/ios/MGLMapboxEvents.h index 9efb53c5e..45f809495 100644 --- a/platform/ios/MGLMapboxEvents.h +++ b/platform/ios/MGLMapboxEvents.h @@ -30,6 +30,7 @@ extern NSString *const MGLEventGestureQuickZoom; extern NSString *const MGLEventGesturePanStart; extern NSString *const MGLEventGesturePinchStart; extern NSString *const MGLEventGestureRotateStart; +extern NSString *const MGLEventGesturePitchStart; typedef NS_DICTIONARY_OF(NSString *, id) MGLMapboxEventAttributes; typedef NS_MUTABLE_DICTIONARY_OF(NSString *, id) MGLMutableMapboxEventAttributes; diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m index 314d50dd0..77e68ddde 100644 --- a/platform/ios/MGLMapboxEvents.m +++ b/platform/ios/MGLMapboxEvents.m @@ -45,6 +45,7 @@ NSString *const MGLEventGestureQuickZoom = @"QuickZoom"; NSString *const MGLEventGesturePanStart = @"Pan"; NSString *const MGLEventGesturePinchStart = @"Pinch"; NSString *const MGLEventGestureRotateStart = @"Rotation"; +NSString *const MGLEventGesturePitchStart = @"Pitch"; const NSUInteger MGLMaximumEventsPerFlush = 20; const NSTimeInterval MGLFlushInterval = 60; From 1424ce72a5d9fcb9f3861a0006989092a3b984ee Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Wed, 26 Aug 2015 17:25:30 -0700 Subject: [PATCH 610/760] #2182 - Replacing existing iOS style compass with more Android style compass artwork --- .../java/com/mapbox/mapboxgl/views/MapView.java | 2 +- .../res/drawable-xxhdpi/compass_custom_flat.png | Bin 0 -> 2519 bytes .../src/main/res/drawable/compass.png | Bin 2376 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass_custom_flat.png delete mode 100644 android/java/MapboxGLAndroidSDK/src/main/res/drawable/compass.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index f6723d786..219069fda 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -287,7 +287,7 @@ public class MapView extends FrameLayout implements LocationListener { mCompassListener = new CompassListener(); mCompassView = new ImageView(mContext); - mCompassView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass)); + mCompassView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass_custom_flat)); mCompassView.setContentDescription(getResources().getString(R.string.compassContentDescription)); LayoutParams lp = new FrameLayout.LayoutParams((int)(48 * mScreenDensity), (int)(48 * mScreenDensity)); lp.gravity = Gravity.TOP | Gravity.END; diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass_custom_flat.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass_custom_flat.png new file mode 100644 index 0000000000000000000000000000000000000000..d545b03f3ca054eb6923eac871d9570467e4ee97 GIT binary patch literal 2519 zcmV;|2`Ki7P)Px;kx4{BRCodHnd^^L)fK?6Ac{h%f`V2>WEgx+QAeZlQqb5M6dSO*$JEA3;1{Ix|^#39Fc&HM1T&# znXxbf?C)wg9_GS0(AguwdOEKd#IIln7`V^i1=s?QfWi3>d=x%CK|;O zcb*I2KG+3SMt;DIId5!ZU~A7H=W|~%i<17eMVl@JZLBPHGRThS!&0~j+_Edl5m<3I z8~r&P23UidQC`Kq^rh}r6KylA_3u-77b=~bs?JlV)%kE8To1uhiZ=V?Xxuo1lg|Xw zMw!L^WS)Io$)!OZ{5>2zTDV|)R(c8cK#&;WP_d1pV{ptI4XG>#WBwgSG#c0ZfK|1% z_e^2fcCd21NEX#xKpe=E0ZonfVLJrnWoyi6R$e^pEreU3R&eZcCNA`oF=Jc7@tOBN zvn+yWqqbfI+8!jK0dh*71^2@Epq@HZZl)rESlmIA&(5SQ$*K;`Lmt_~Z^3g?(6SN| zApPkBw{j*$eVIyzxV7)@kn}!VO4-yPcXDmgW^LDp(o0L(0677UhTFgw%P8VP+)I!S zO--*vvCn2uW}ObjNwv_cLDC1OS}C<<17yT|;ar#qwZ-lHPRyN75VFKF>x?PoN|u%( z&$V42PJvRnSvEkPD}Dj}5Jfl)HhR&Tx5ijDgF3UR8!tvpAM{0^3Z+bd%$`eNexbIK z6`VfvI5`=lBC(Ej&A5o->HkvDw_GU`Ah*d=TI4D#Tj=hzgngyVInX-RwN1nr^`O=z zqwttg6d-My2EI;o*lTziC=Qv^<|Nja!r`N?b*53*HlyQ3NMH168sx?xw~)pzWSnsu zY`FCYQ2%^uci}y(!M2dqBnKLMRzvq$s~F$({a+D zb!{_;wt0(sQorO?pRR=5nNeh?bJS0nQG}V&_$eA^mfAmtc4-^isw1N48!KG4AwX+r zS3gEg-<%~c+AVxX@&aW4n6vM{o!@C(_Q5zjbAkN^jpwBfUS*quuEtA0+`gHJ@WtcL=r%v?>pE(L< z!!fDh&>{o$;Lhx~`4yB|CvCkt7NaYrs<8i@3dY=3F;iV$fNbO&y#F1jELyOLed6@C zoy+W4h335Zt8p(-$GT}b4PENYGd^8E=iV=kM?t2l%mNy@fbE9N=u%cFo_@c=!S>l; z;9R&#kWA5l@f|EQUVHd6N+tEg@$o+(b`>m+`ENjb>hcjf=0m$nm-EaI>RI|NyA0Bz z4t1$Bt6j)*eQe8bp0uRH_#{QmL3iNUGir;=>Q_R{hOKbC=kNJQrppIK&|v`pm3 zQfMvR>7w9!eY3yz1mCA9Z~8b=Tj6tHS*#`Q6n)^L@1P_GZQ%Y%Fx77br?levYdKi9 z5fZVji>kWRSy%LJ3$$nUKD~JXa$ar&mcuA=E?RDb-?eG`2fyEezrlInh^~h9uo2Q? z*)PF*F~os43G_8s9IdnAP0+Bqm^CZmbf_w}RfoFj+GBftI}0*1h_YNUogac2#w0p! zL|+wyY-8I*PmHU+>0_ptC3#}G>FgWN(I zYs}-|S`Q^J#B7;SYjsJ4WR+Li#E26 zC1#Jl>0_o81;}~sImGQYmbjMmf;*A-MPeQ6&Mc}JSgud{rjMCY6d)UVV0#T-@kbpA$&S$vBzu*JNU7QoWWdr2sZ3Yikejf0544I6QxzOot+GkF| zDG+1r(PnM`3%modd_dJuSJ07A- z=sFQ%fIE{riPNqRehyPwBs<b|hs6MKLH&UwI5(hbJKEi`%%8Wmw!^2H&+# zMX85D17?QJ2LHF^jxzy#_eyp`-|)TP3BGddf>q$zXeI1`H^6`mtBh2D5+~d>9c?qh z-O9jB1s7G%R4(*RMNhmszYR8nfpV(a-mJ9F_ePSVhJ$60lIdpvk}~hR(v3hj0^JC7 hBhZaNHv&Ns_#dqPnoFPLWIzA_002ovPDHLkV1hZC*E|3K literal 0 HcmV?d00001 diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable/compass.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable/compass.png deleted file mode 100644 index 58e7e08d2455e10b98d529d45ae3a9646956027c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2376 zcmV-O3Agr%P)F;&-Y`@6;yRCQSV~1a)066EUBX5c6tjiQ!3N0~mlMr1N@~m8VJc4LQ|J z;UFL}MJq|&zxmv5K*xduxEJ?}@>Q+@o4F5hkHO&qV4%ld^By~oW zAlQOU*yioXQeY;Mx;}xQ&`tBL42=JKy_pOuZmB8P z2{{1ys8V73`}<5ctw^Wrp#sC>cdYdtHed@jTk}J=6oVDfg$G5Z^GBO4CUe;Jd=_$ zV;9-qvCGbLuP~CBBvB`X=FGt5vX1B685%w$hpXs8F`0_u5(J->*l{C)+4kO-(F*~BwtAo;ads ztX(2Vyf1QZAKKM})oFcW!&y3UD#sx)$vLPM>_burCqtAj6i=oySpze$!v|~w;8WPj z>)n=w3(2{f5%d^hFbG-TB#nb-obymCDf~B-0fmT&;rTyG7nvo%#BIq^UY zT*s<~vqSp;m3u3M8`2nVkB3i5$>AiStk|J;#f!?7r>{%HElyp!cO!y=dm@t7gvfZ> z`L)JYqM$R4-|sECgL6;Xw>^Ph~UtA~?^I%(q&*#j2=g6PnOyYjAs zD9{l7eeRO7dN@J9s7_WY0v7>-h@7=72)bey*dOoA#hq78uTtGCFoKH!LEe8-WCftF z+W{8=f@lTX&Zu|#njD7%;vz_pVeK3f#OGjZCxjsW(M|DB1pJ<=HBGuf>}4m|g<_~r zPoF&xsG1Y>4*&rF|8_b0YybcRdPzhZCE_T)_k z4^l<9-#`ViFBaS4(Ss-y>=p|4U{89eXQ5cwp7c{zo}XtQmzN{I+Q49_Ua-w+&({XeNBn`0FJ4YX zeBxaE)Ucf0A{~79{sTKcK4v>Rza!$fj)lcVw+>QXR!r*b`e1m*Fzuhkblw@M91tMM z0F(xZSa2?WYFhU9vH%?(9hSuF$5#>O{y8vWSXIyHd6f^+fH ze$!qk1JIMnNyZ^PefB&AsjLwM7CsoBF-@mY79dP7OtXW7gV0nf3lLavE`DlR)<{`^ z&@YGaX>H9PMZ64Yk=nq*2NTFtXeIlVCd9NIHad1SvtYi8VT$oSvE*Y_yL0!RTb3uF zI%$S>-j_5b2E?Qi;Bu0sSZGDoaA`AH0#Cq#rC4XyiP!|OM0vd`=5?C9u-wOTFqxY1xvZ6Kk8a6~TJY&11J!9m$a9+7Lq zs9sYurZ$yiwvqs$4Ie$d7(p^k@I3+~uE4PEFH$2UK9758fY1lxqQU@?&v7hb%ld8m zb{-JUk`4GA3qw*^p;7cz$HBN2L8pD5b+xsSuf!of_K^VUGflHjqT4FMA52WROQ*K5 zoTh6~BQ#(cho)e8LcR@OWgHUAY18&5!LTcU!_g3=D!l7@I-1~PB7~Hi6c^c>GDtc? zWHx$LuBdI3{7;U1QUGD9T_Gp-L`QCokLwQQu1AE}x9T;A1g$Y`_TXEZ|4R31`~OJn zz^>Jpq~ka-nfGqN{&n_&&cHDrGkv2MD1GZo@*oSH!K9JLTOS%T7bKO9~4jM-# zX-@F>aB1~mtF^tIYEQJ|!8xblZ&y_-I@1l)3UNvD8BAyUq6!GMU{hzxdef?BBPT-A zdvHfqH}SuQqY|9Gh~;tDs({cIHeox?j!;IcRn8CO2n|Vr7XnD*BnZWsi)w_e0|+*w z=Q)v$^k!~LxrBjAan*~sm9{0qe>XG&bO0sUp?hsFLe3Ths(7zjhyGJ_-3_kjBtDqH zPb}BEri~}GsOm1dUd25sDd0RQ0*(6gjx36M5NDy=Ihn-qZTO5k;#j5~J#14SswaV2 z9O_})YUgbUy;4$m@0JSCwc#|qw-2Z18Iu#h3OP1H%Wi`nqWBhI)%~i({q!dIeG=uV u%=_YG6?M@DZAr)3m_j)L`)}r~6Zi+-n6!b6vRQlp0000 Date: Thu, 27 Aug 2015 14:00:43 -0400 Subject: [PATCH 611/760] add Point <--> LatLng conversion tests --- test/miscellaneous/transform.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/miscellaneous/transform.cpp b/test/miscellaneous/transform.cpp index 5d00bfd20..426e9e534 100644 --- a/test/miscellaneous/transform.cpp +++ b/test/miscellaneous/transform.cpp @@ -105,3 +105,35 @@ TEST(Transform, InvalidBearing) { ASSERT_DOUBLE_EQ(2, transform.getScale()); ASSERT_DOUBLE_EQ(2, transform.getAngle()); } + +TEST(Transform, PerspectiveProjection) { + MockView view; + Transform transform(view); + + transform.resize({{ 1000, 1000 }}); + transform.setScale(1024); + transform.setPitch(0.9); + transform.setLatLng(LatLng(38, -77)); + + // expected values are from mapbox-gl-js + + LatLng loc = transform.getState().pointToLatLng({ 500, 500 }); + ASSERT_NEAR(-77, loc.longitude, 0.0001); + ASSERT_NEAR(38, loc.latitude, 0.0001); + + loc = transform.getState().pointToLatLng({ 0, 1000 }); + ASSERT_NEAR(-77.59198961199148, loc.longitude, 0.0002); + ASSERT_NEAR(38.74661326302018, loc.latitude, 0.0001); + + loc = transform.getState().pointToLatLng({ 1000, 0 }); + ASSERT_NEAR(-76.75823239205641, loc.longitude, 0.0001); + ASSERT_NEAR(37.692872969426375, loc.latitude, 0.0001); + + vec2 point = transform.getState().latLngToPoint({38.74661326302018, -77.59198961199148}); + ASSERT_NEAR(point.x, 0, 0.01); + ASSERT_NEAR(point.y, 1000, 0.01); + + point = transform.getState().latLngToPoint({37.692872969426375, -76.75823239205641}); + ASSERT_NEAR(point.x, 1000, 0.02); + ASSERT_NEAR(point.y, 0, 0.02); +} From 580719c9f9913cf5b412852c38c49e2024e478b4 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 15:11:42 -0400 Subject: [PATCH 612/760] add request/respond logging --- platform/node/src/node_request.cpp | 6 +++++- platform/node/test/render.test.js | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp index d2cf43790..521b10516 100644 --- a/platform/node/src/node_request.cpp +++ b/platform/node/src/node_request.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace node_mbgl { @@ -61,6 +62,7 @@ NAN_METHOD(NodeRequest::Respond) { // Request has already been responded to, or was canceled, fail silently. if (!nodeRequest->resource) NanReturnUndefined(); + std::cout << "RESPOND: " << nodeRequest->resource->url << std::endl; auto source = nodeRequest->source; auto resource = std::move(nodeRequest->resource); @@ -133,7 +135,9 @@ NodeRequest::NodeRequest(NodeFileSource* source_, const mbgl::Resource& resource : source(source_), resource(std::make_unique(resource_)) {} -NodeRequest::~NodeRequest() {} +NodeRequest::~NodeRequest() { + if (resource) std::cout << "DESTROY: " << resource->url << std::endl; +} void NodeRequest::cancel() { resource.reset(); diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 2edb6fac1..ff0f1684f 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -41,12 +41,13 @@ function renderTest(style, info, base, key) { var options = {}; options.request = function(req) { - fs.readFile(path.join(suitePath, decodeURIComponent(req.url)), function(err, data) { + var url = decodeURIComponent(req.url); + console.log('REQUEST: ', url); + fs.readFile(path.join(suitePath, url), function(err, data) { + // t.error(err); req.respond(err, { data: data }); - t.error(err); }); }; - options.cancel = function() {}; options.ratio = 1.0; var map = new mbgl.Map(options); @@ -56,6 +57,7 @@ function renderTest(style, info, base, key) { map.release(); if (err) { + console.log('THIS IS AN ERROR'); t.error(err); return t.end(); } From c2539d6bd4579e0599a5f86a66e7ffd591b049c2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 15:23:46 -0400 Subject: [PATCH 613/760] more logging, don't respond to cancelled requests --- platform/node/src/node_request.cpp | 5 +++-- platform/node/test/render.test.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp index 521b10516..a003e41ee 100644 --- a/platform/node/src/node_request.cpp +++ b/platform/node/src/node_request.cpp @@ -62,7 +62,7 @@ NAN_METHOD(NodeRequest::Respond) { // Request has already been responded to, or was canceled, fail silently. if (!nodeRequest->resource) NanReturnUndefined(); - std::cout << "RESPOND: " << nodeRequest->resource->url << std::endl; + std::cout << "C++ RESPOND: " << nodeRequest->resource->url << std::endl; auto source = nodeRequest->source; auto resource = std::move(nodeRequest->resource); @@ -136,10 +136,11 @@ NodeRequest::NodeRequest(NodeFileSource* source_, const mbgl::Resource& resource resource(std::make_unique(resource_)) {} NodeRequest::~NodeRequest() { - if (resource) std::cout << "DESTROY: " << resource->url << std::endl; + if (resource) std::cout << "C++ DESTROY: " << resource->url << std::endl; } void NodeRequest::cancel() { + std::cout << "C++ CANCEL: " << resource->url << std::endl; resource.reset(); } diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index ff0f1684f..a76e6baf0 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -42,12 +42,18 @@ function renderTest(style, info, base, key) { var options = {}; options.request = function(req) { var url = decodeURIComponent(req.url); - console.log('REQUEST: ', url); + console.log('JAVASCRIPT REQUEST:', url); fs.readFile(path.join(suitePath, url), function(err, data) { // t.error(err); - req.respond(err, { data: data }); + console.log('CANCELED?', req.canceled, 'ERROR?', err, url); + if (!req.canceled) req.respond(err, { data: data }); }); }; + options.cancel = function(req) { + var url = decodeURIComponent(req.url); + console.log('JAVASCRIPT CANCEL:', url); + req.canceled = true; + }; options.ratio = 1.0; var map = new mbgl.Map(options); From cb2b57a7c5497ac15e8b417950430a222d32e959 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 15:57:03 -0400 Subject: [PATCH 614/760] drop faucet from test-suite --- platform/node/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/package.json b/platform/node/package.json index 0a7e021ba..d0cb4eb76 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -36,7 +36,7 @@ "scripts": { "install": "node-pre-gyp install || make node -C ../../", "test": "tape test/js/**/*.test.js | faucet", - "test-suite": "(tape test/render.test.js | faucet) || true" + "test-suite": "tape test/render.test.js || true" }, "gypfile": true, "binary": { From 76e94fdb2db7d917b055e2c46dfa5c2a4050efd3 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Thu, 27 Aug 2015 13:31:07 -0700 Subject: [PATCH 615/760] #2194 - Removing elevation due to drop shadow issue --- .../src/main/res/layout/activity_main.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 292f77c3a..2d22dcce7 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -54,6 +54,7 @@ android:layout_margin="@dimen/fab_margin" app:backgroundTint="@color/white" app:borderWidth="0dp" + app:elevation="0dp" /> From 294b307cb3f470b1cf5fb94633c9082dc0bfd719 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 27 Aug 2015 16:49:35 -0400 Subject: [PATCH 616/760] fix line antialiasing adjustment bug fix #2189 This puts an upper limit on how much lines can be blurred because of their y position. It's a hacky fix but the entire way lines are antialiased in perspective view is hacky. --- src/mbgl/shader/line.vertex.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/shader/line.vertex.glsl b/src/mbgl/shader/line.vertex.glsl index f818565cc..3b3caaab1 100644 --- a/src/mbgl/shader/line.vertex.glsl +++ b/src/mbgl/shader/line.vertex.glsl @@ -49,7 +49,7 @@ void main() { float squish_scale = length(a_extrude) / length(u_antialiasingmatrix * a_extrude); // how much features are squished in all directions by the perspectiveness - float perspective_scale = 1.0 / (1.0 - y * u_extra); + float perspective_scale = 1.0 / (1.0 - min(y * u_extra, 0.90)); v_gamma_scale = perspective_scale * squish_scale; } From d50ade7aa47a4460b0d6bc4e89f83288070b8d9f Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 27 Aug 2015 17:33:56 -0400 Subject: [PATCH 617/760] improve dashed and patterned line antialiasing in perspective view. This is exactly the same approach used for regular lines. I just forgot to implement it for these types of lines. --- src/mbgl/renderer/painter_line.cpp | 4 ++++ src/mbgl/shader/line.vertex.glsl | 4 ++-- src/mbgl/shader/linepattern.fragment.glsl | 9 ++++---- src/mbgl/shader/linepattern.vertex.glsl | 28 ++++++++++++----------- src/mbgl/shader/linepattern_shader.hpp | 2 ++ src/mbgl/shader/linesdf.fragment.glsl | 4 +++- src/mbgl/shader/linesdf.vertex.glsl | 19 +++++++++++++-- src/mbgl/shader/linesdf_shader.hpp | 2 ++ 8 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp index 0b7e8a96f..258bab6ae 100644 --- a/src/mbgl/renderer/painter_line.cpp +++ b/src/mbgl/renderer/painter_line.cpp @@ -97,6 +97,8 @@ void Painter::renderLine(LineBucket& bucket, const StyleLayer &layer_desc, const linesdfShader->u_image = 0; linesdfShader->u_sdfgamma = lineAtlas->width / (properties.dash_line_width * std::min(posA.width, posB.width) * 256.0 * data.pixelRatio) / 2; linesdfShader->u_mix = properties.dash_array.t; + linesdfShader->u_extra = extra; + linesdfShader->u_antialiasingmatrix = antialiasingMatrix; bucket.drawLineSDF(*linesdfShader); @@ -122,6 +124,8 @@ void Painter::renderLine(LineBucket& bucket, const StyleLayer &layer_desc, const linepatternShader->u_pattern_br_b = imagePosB.br; linepatternShader->u_fade = properties.image.t; linepatternShader->u_opacity = properties.opacity; + linepatternShader->u_extra = extra; + linepatternShader->u_antialiasingmatrix = antialiasingMatrix; MBGL_CHECK_ERROR(glActiveTexture(GL_TEXTURE0)); spriteAtlas->bind(true); diff --git a/src/mbgl/shader/line.vertex.glsl b/src/mbgl/shader/line.vertex.glsl index 3b3caaab1..980212384 100644 --- a/src/mbgl/shader/line.vertex.glsl +++ b/src/mbgl/shader/line.vertex.glsl @@ -34,13 +34,13 @@ void main() { // Scale the extrusion vector down to a normal and then up by the line width // of this vertex. - vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0); + vec2 dist = u_linewidth.s * a_extrude * scale; // Remove the texture normal bit of the position before scaling it with the // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. - gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist.xy / u_ratio, 0.0, 1.0); + gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist / u_ratio, 0.0, 1.0); // position of y on the screen float y = gl_Position.y / gl_Position.w; diff --git a/src/mbgl/shader/linepattern.fragment.glsl b/src/mbgl/shader/linepattern.fragment.glsl index 08feff439..4110a99ea 100644 --- a/src/mbgl/shader/linepattern.fragment.glsl +++ b/src/mbgl/shader/linepattern.fragment.glsl @@ -1,5 +1,4 @@ uniform vec2 u_linewidth; -uniform float u_point; uniform float u_blur; uniform vec2 u_pattern_size_a; @@ -15,17 +14,17 @@ uniform sampler2D u_image; varying vec2 v_normal; varying float v_linesofar; +varying float v_gamma_scale; void main() { // Calculate the distance of the pixel from the line in pixels. - float dist = length(v_normal) * (1.0 - u_point) + u_point * length(gl_PointCoord * 2.0 - 1.0); - - dist *= u_linewidth.s; + float dist = length(v_normal) * u_linewidth.s; // Calculate the antialiasing fade factor. This is either when fading in // the line in case of an offset line (v_linewidth.t) or when fading out // (v_linewidth.s) - float alpha = clamp(min(dist - (u_linewidth.t - u_blur), u_linewidth.s - dist) / u_blur, 0.0, 1.0); + float blur = u_blur * v_gamma_scale; + float alpha = clamp(min(dist - (u_linewidth.t - blur), u_linewidth.s - dist) / blur, 0.0, 1.0); float x_a = mod(v_linesofar / u_pattern_size_a.x, 1.0); float y_a = 0.5 + (v_normal.y * u_linewidth.s / u_pattern_size_a.y); diff --git a/src/mbgl/shader/linepattern.vertex.glsl b/src/mbgl/shader/linepattern.vertex.glsl index 47d551e2e..541c9fc3a 100644 --- a/src/mbgl/shader/linepattern.vertex.glsl +++ b/src/mbgl/shader/linepattern.vertex.glsl @@ -18,10 +18,13 @@ uniform mat4 u_exmatrix; uniform float u_ratio; uniform vec2 u_linewidth; uniform vec4 u_color; -uniform float u_point; + +uniform float u_extra; +uniform mat2 u_antialiasingmatrix; varying vec2 v_normal; varying float v_linesofar; +varying float v_gamma_scale; void main() { vec2 a_extrude = a_data.xy; @@ -37,24 +40,23 @@ void main() { // Scale the extrusion vector down to a normal and then up by the line width // of this vertex. - vec2 extrude = a_extrude * scale; - vec2 dist = u_linewidth.s * extrude * (1.0 - u_point); - - // If the x coordinate is the maximum integer, we move the z coordinates out - // of the view plane so that the triangle gets clipped. This makes it easier - // for us to create degenerate triangle strips. - float z = step(32767.0, a_pos.x); - - // When drawing points, skip every other vertex - z += u_point * step(1.0, v_normal.y); + vec2 dist = u_linewidth.s * a_extrude * scale; // Remove the texture normal bit of the position before scaling it with the // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. gl_Position = u_matrix * vec4(floor(a_pos / 2.0) + dist.xy / u_ratio, 0.0, 1.0); - v_linesofar = a_linesofar;// * u_ratio; + v_linesofar = a_linesofar; + // position of y on the screen + float y = gl_Position.y / gl_Position.w; - gl_PointSize = 2.0 * u_linewidth.s - 1.0; + // how much features are squished in the y direction by the tilt + float squish_scale = length(a_extrude) / length(u_antialiasingmatrix * a_extrude); + + // how much features are squished in all directions by the perspectiveness + float perspective_scale = 1.0 / (1.0 - y * u_extra); + + v_gamma_scale = perspective_scale * squish_scale; } diff --git a/src/mbgl/shader/linepattern_shader.hpp b/src/mbgl/shader/linepattern_shader.hpp index 16d9ff8be..dce065a0d 100644 --- a/src/mbgl/shader/linepattern_shader.hpp +++ b/src/mbgl/shader/linepattern_shader.hpp @@ -26,6 +26,8 @@ public: Uniform u_blur = {"u_blur", *this}; Uniform u_fade = {"u_fade", *this}; Uniform u_opacity = {"u_opacity", *this}; + Uniform u_extra = {"u_extra", *this}; + UniformMatrix<2> u_antialiasingmatrix = {"u_antialiasingmatrix", *this}; private: int32_t a_pos = -1; diff --git a/src/mbgl/shader/linesdf.fragment.glsl b/src/mbgl/shader/linesdf.fragment.glsl index 5d53dbe88..f8962fa57 100644 --- a/src/mbgl/shader/linesdf.fragment.glsl +++ b/src/mbgl/shader/linesdf.fragment.glsl @@ -8,6 +8,7 @@ uniform float u_mix; varying vec2 v_normal; varying vec2 v_tex_a; varying vec2 v_tex_b; +varying float v_gamma_scale; void main() { // Calculate the distance of the pixel from the line in pixels. @@ -16,7 +17,8 @@ void main() { // Calculate the antialiasing fade factor. This is either when fading in // the line in case of an offset line (v_linewidth.t) or when fading out // (v_linewidth.s) - float alpha = clamp(min(dist - (u_linewidth.t - u_blur), u_linewidth.s - dist) / u_blur, 0.0, 1.0); + float blur = u_blur * v_gamma_scale; + float alpha = clamp(min(dist - (u_linewidth.t - blur), u_linewidth.s - dist) / blur, 0.0, 1.0); float sdfdist_a = texture2D(u_image, v_tex_a).a; float sdfdist_b = texture2D(u_image, v_tex_b).a; diff --git a/src/mbgl/shader/linesdf.vertex.glsl b/src/mbgl/shader/linesdf.vertex.glsl index 6529fb45c..03851ed53 100644 --- a/src/mbgl/shader/linesdf.vertex.glsl +++ b/src/mbgl/shader/linesdf.vertex.glsl @@ -22,9 +22,13 @@ uniform float u_tex_y_a; uniform vec2 u_patternscale_b; uniform float u_tex_y_b; +uniform float u_extra; +uniform mat2 u_antialiasingmatrix; + varying vec2 v_normal; varying vec2 v_tex_a; varying vec2 v_tex_b; +varying float v_gamma_scale; void main() { vec2 a_extrude = a_data.xy; @@ -40,14 +44,25 @@ void main() { // Scale the extrusion vector down to a normal and then up by the line width // of this vertex. - vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0); + vec2 dist = u_linewidth.s * a_extrude * scale; // Remove the texture normal bit of the position before scaling it with the // model/view matrix. Add the extrusion vector *after* the model/view matrix // because we're extruding the line in pixel space, regardless of the current // tile's zoom level. - gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist.xy / u_ratio, 0.0, 1.0); + gl_Position = u_matrix * vec4(floor(a_pos * 0.5) + dist / u_ratio, 0.0, 1.0); v_tex_a = vec2(a_linesofar * u_patternscale_a.x, normal.y * u_patternscale_a.y + u_tex_y_a); v_tex_b = vec2(a_linesofar * u_patternscale_b.x, normal.y * u_patternscale_b.y + u_tex_y_b); + + // position of y on the screen + float y = gl_Position.y / gl_Position.w; + + // how much features are squished in the y direction by the tilt + float squish_scale = length(a_extrude) / length(u_antialiasingmatrix * a_extrude); + + // how much features are squished in all directions by the perspectiveness + float perspective_scale = 1.0 / (1.0 - min(y * u_extra, 0.9)); + + v_gamma_scale = perspective_scale * squish_scale; } diff --git a/src/mbgl/shader/linesdf_shader.hpp b/src/mbgl/shader/linesdf_shader.hpp index 16b7f3cb1..9928d5b30 100644 --- a/src/mbgl/shader/linesdf_shader.hpp +++ b/src/mbgl/shader/linesdf_shader.hpp @@ -25,6 +25,8 @@ public: Uniform u_image = {"u_image", *this}; Uniform u_sdfgamma = {"u_sdfgamma", *this}; Uniform u_mix = {"u_mix", *this}; + Uniform u_extra = {"u_extra", *this}; + UniformMatrix<2> u_antialiasingmatrix = {"u_antialiasingmatrix", *this}; private: int32_t a_pos = -1; From 53f2b125c2ff583fee62c95d56d988bc330d60d5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 18:09:08 -0400 Subject: [PATCH 618/760] merge platform/node/scripts/* into scripts/node/run.sh --- platform/node/scripts/deploy_results.sh | 15 --------------- platform/node/scripts/install_node.sh | 13 ------------- platform/node/scripts/travis_test.sh | 10 ---------- scripts/node/run.sh | 25 +++++++++++++++++++------ 4 files changed, 19 insertions(+), 44 deletions(-) delete mode 100755 platform/node/scripts/deploy_results.sh delete mode 100755 platform/node/scripts/install_node.sh delete mode 100755 platform/node/scripts/travis_test.sh diff --git a/platform/node/scripts/deploy_results.sh b/platform/node/scripts/deploy_results.sh deleted file mode 100755 index adbed4c2d..000000000 --- a/platform/node/scripts/deploy_results.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then - # Install and add awscli to PATH for uploading the results - mapbox_time "install_awscli" \ - pip install --user awscli - export PATH="`python -m site --user-base`/bin:${PATH}" - - mapbox_time_start "deploy_results" - (cd ./node_modules/mapbox-gl-test-suite/ && ./bin/deploy_results.sh) - mapbox_time_finish -fi diff --git a/platform/node/scripts/install_node.sh b/platform/node/scripts/install_node.sh deleted file mode 100755 index 311b00424..000000000 --- a/platform/node/scripts/install_node.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -# Mason exists on PATH from sourcing mbgl install script -mapbox_time $NODE_EXE \ -mason install $NODE_EXE $NODE_VERSION - -export PATH="`mason prefix $NODE_EXE $NODE_VERSION`/bin":"$PATH" - -$NODE_EXE --version -npm --version diff --git a/platform/node/scripts/travis_test.sh b/platform/node/scripts/travis_test.sh deleted file mode 100755 index 70fff88aa..000000000 --- a/platform/node/scripts/travis_test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -# Travis OS X has no GPU -if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then - npm test - ./scripts/deploy_results.sh -fi diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 118e1ed25..232823f03 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -21,11 +21,24 @@ pushd platform/node && npm install --build-from-source && popd # Test ################################################################################ -mapbox_time "checkout_test_suite" \ -git submodule update --init test/suite +# Travis OS X has no GPU +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + mapbox_time "checkout_test_suite" \ + git submodule update --init test/suite -mapbox_time "run_tests" \ -pushd platform/node && npm test && popd + mapbox_time "run_tests" \ + pushd platform/node && npm test && popd -mapbox_time "run_render_tests" \ -pushd platform/node && npm run test-suite && popd + mapbox_time "run_render_tests" \ + pushd platform/node && npm run test-suite && popd + + if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then + # Install and add awscli to PATH for uploading the results + mapbox_time "install_awscli" \ + pip install --user awscli + export PATH="`python -m site --user-base`/bin:${PATH}" + + mapbox_time "deploy_results" \ + pushd test/suite && ./bin/deploy_results.sh && popd + fi +fi From 7ba673dafd6d6a437110a50e31744bfe480da7a3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 18:10:21 -0400 Subject: [PATCH 619/760] move travis-resources.template to cloudformation directory --- {travis => cloudformation}/travis-resources.template | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {travis => cloudformation}/travis-resources.template (100%) diff --git a/travis/travis-resources.template b/cloudformation/travis-resources.template similarity index 100% rename from travis/travis-resources.template rename to cloudformation/travis-resources.template From 337208772c1d4b09ec3ad59adc533800fd10fb35 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 27 Aug 2015 18:11:24 -0400 Subject: [PATCH 620/760] naming conventions --- cloudformation/{travis-resources.template => travis.template} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cloudformation/{travis-resources.template => travis.template} (100%) diff --git a/cloudformation/travis-resources.template b/cloudformation/travis.template similarity index 100% rename from cloudformation/travis-resources.template rename to cloudformation/travis.template From f3cd47ededd6c2994749df12d543de744773b671 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 28 Aug 2015 10:26:59 -0400 Subject: [PATCH 621/760] update test suite to disable failing test --- test/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/suite b/test/suite index b3a0cddc8..80e8ff67d 160000 --- a/test/suite +++ b/test/suite @@ -1 +1 @@ -Subproject commit b3a0cddc8ae488df6ed2fc966a51c86f0fe78cd7 +Subproject commit 80e8ff67dcf9ae5627d5fcd22a831fe318df7cdf From b213766236e7ac8aefed6a41b247bca7c8ff2f9f Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 12:27:47 -0400 Subject: [PATCH 622/760] drop platform/node/.travis.yml --- platform/node/.travis.yml | 115 -------------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 platform/node/.travis.yml diff --git a/platform/node/.travis.yml b/platform/node/.travis.yml deleted file mode 100644 index 891496e03..000000000 --- a/platform/node/.travis.yml +++ /dev/null @@ -1,115 +0,0 @@ -# to prevent Travis from exporting CXX after matrix env -language: c - -sudo: false - -matrix: - include: - - os: linux - compiler: clang - env: - - CXX: clang++-3.5 - - NODE_EXE: "iojs" - - NODE_VERSION: 2.0.1 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] - packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: linux - compiler: clang - env: - - CXX: clang++-3.5 - - NODE_EXE: "node" - - NODE_VERSION: 0.12.0 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] - packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: linux - compiler: clang - env: - - CXX: clang++-3.5 - - NODE_EXE: "node" - - NODE_VERSION: 0.10.36 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] - packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: linux - compiler: gcc - env: - - CXX: g++-4.9 - - NODE_EXE: "iojs" - - NODE_VERSION: 2.0.1 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: linux - compiler: gcc - env: - - CXX: g++-4.9 - - NODE_EXE: "node" - - NODE_VERSION: 0.12.0 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: linux - compiler: gcc - env: - - CXX: g++-4.9 - - NODE_EXE: "node" - - NODE_VERSION: 0.10.36 - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'gdb', 'g++-4.9', 'gcc-4.9', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - - os: osx - compiler: clang - env: - - CXX: clang++ - - NODE_EXE: "iojs" - - NODE_VERSION: 2.0.1 - - os: osx - compiler: clang - env: - - CXX: clang++ - - NODE_EXE: "node" - - NODE_VERSION: 0.12.0 - - os: osx - compiler: clang - env: - - CXX: clang++ - - NODE_EXE: "node" - - NODE_VERSION: 0.10.36 - -env: - global: - - LD_LIBRARY_PATH: "/usr/local/lib" - - secure: pz/HAMQpnde//JJi3f+RcW32APN6g3QyFAH41JlZwgsM5Daj9RRoXqUcNg4hEyTvlThtc5t+wQQ2ejYAjMwDu00GAzRFJ03Sm45w2fPvith9fu4crXsyPUvWUfWPC0ajTXzorN4cwFwOoMoeo9DihLwb0EC0n4T6jWdBCae3k+s= - - secure: jYFAOQoMZkZVyc5AFPBKhR9oDqp5CciwCxFhVEHVjdImM+8V60loKazyw+bVLIjzKLnQbKKdiDudWL9TE1ylK/XSlF3K7o4gU2vIh5WzosKnU70Sanxd6tHt/Ui5eK+bAymKHbLdGiXIZtBZE0tML6+wgJ9vhV4ZKna5dM9tps4= - -before_install: -- source ./vendor/mbgl/scripts/travis_helper.sh -- pushd ./vendor/mbgl -- source ./scripts/${TRAVIS_OS_NAME}/install.sh -- source ./scripts/${TRAVIS_OS_NAME}/setup.sh -- popd -- source ./scripts/install_node.sh - -install: -- npm install --build-from-source - -before_script: -- ulimit -c unlimited -S -- ulimit -a - -script: -- "./scripts/travis_test.sh" - -after_success: -- "./scripts/travis_publish.sh" - -git: - submodules: true From 5262750a582d954157de5d1fdbc11901aa906c25 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 12:39:50 -0400 Subject: [PATCH 623/760] merge node travis build user into cloudformation/travis.template --- cloudformation/travis.template | 36 ++++++ platform/node/cloudformation/travis.template | 114 ------------------- 2 files changed, 36 insertions(+), 114 deletions(-) delete mode 100644 platform/node/cloudformation/travis.template diff --git a/cloudformation/travis.template b/cloudformation/travis.template index 935150105..4e7a0c751 100644 --- a/cloudformation/travis.template +++ b/cloudformation/travis.template @@ -84,6 +84,42 @@ ] } }, + { + "PolicyName": "list-node", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::mapbox-node-binary" + ], + "Effect": "Allow" + } + ] + } + }, + { + "PolicyName": "build-node", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": [ + "arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*" + ], + "Effect": "Allow" + } + ] + } + }, { "PolicyName": "android", "PolicyDocument": { diff --git a/platform/node/cloudformation/travis.template b/platform/node/cloudformation/travis.template deleted file mode 100644 index 3ba9b9625..000000000 --- a/platform/node/cloudformation/travis.template +++ /dev/null @@ -1,114 +0,0 @@ - -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "node-mapbox-gl-native travis resources", - "Resources": { - "BuildUser": { - "Type": "AWS::IAM::User", - "Properties": { - "Policies": [ - { - "PolicyName": "build", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:DeleteObject", - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl" - ], - "Resource": [ - "arn:aws:s3:::mapbox-node-binary/mapbox-gl-native/*" - ], - "Effect": "Allow" - } - ] - } - }, - { - "PolicyName": "list", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::mapbox-node-binary" - ], - "Effect": "Allow" - } - ] - } - }, - { - "PolicyName": "build-testing", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:DeleteObject", - "s3:GetObject", - "s3:GetObjectAcl", - "s3:PutObject", - "s3:PutObjectAcl" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::mapbox/node-mapbox-gl-native/*" - ] - } - ] - } - }, - { - "PolicyName": "list-testing", - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:ListBucket" - ], - "Effect": "Allow", - "Resource": [ - "arn:aws:s3:::mapbox" - ], - "Condition": { - "StringLike": { - "s3:prefix": "node-mapbox-gl-native/*" - } - } - } - ] - } - } - ] - } - }, - "BuildUserKey": { - "Type": "AWS::IAM::AccessKey", - "Properties": { - "UserName": { - "Ref": "BuildUser" - } - } - } - }, - "Outputs": { - "AccessKeyId": { - "Value": { - "Ref": "BuildUserKey" - } - }, - "SecretAccessKey": { - "Value": { - "Fn::GetAtt": [ - "BuildUserKey", - "SecretAccessKey" - ] - } - } - } -} From b05fa9732af1e9dd624ece7f6698411937f4a0f5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:12:54 -0400 Subject: [PATCH 624/760] back to nvm node installs on travis --- .travis.yml | 12 ++++++------ scripts/node/install.sh | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index db296ec15..299786029 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,19 +59,19 @@ matrix: apt: packages: [ 'lib32stdc++6' ] - os: linux - env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=iojs NODE_VERSION=2.0.1 + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=iojs-v2 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux - env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=node NODE_VERSION=0.12.0 + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.12 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: linux - env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_EXE=node NODE_VERSION=0.10.36 + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.10 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] @@ -79,15 +79,15 @@ matrix: - os: osx osx_image: xcode6.4 compiler: clang - env: FLAVOR=node NODE_EXE=iojs NODE_VERSION=2.0.1 + env: FLAVOR=node NODE_VERSION=iojs-v2 - os: osx osx_image: xcode6.4 compiler: clang - env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.12.0 + env: FLAVOR=node NODE_VERSION=0.12 - os: osx osx_image: xcode6.4 compiler: clang - env: FLAVOR=node NODE_EXE=node NODE_VERSION=0.10.36 + env: FLAVOR=node NODE_VERSION=0.10 env: global: diff --git a/scripts/node/install.sh b/scripts/node/install.sh index b0989ca0b..8852f351e 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,10 +13,11 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -mapbox_time $NODE_EXE \ -mason install $NODE_EXE $NODE_VERSION +mapbox_time "install_nvm" \ +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.26.1/install.sh | bash -export PATH="`mason prefix $NODE_EXE $NODE_VERSION`/bin":"$PATH" +mapbox_time $NODE_VERSION \ +nvm install $NODE_VERSION -$NODE_EXE --version +node --version npm --version From 8d9b6335080d610d85bc3d805ab5e1fd1d2e288b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:20:25 -0400 Subject: [PATCH 625/760] nvm already exists --- scripts/node/install.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 8852f351e..6c1120a08 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,10 +13,7 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -mapbox_time "install_nvm" \ -curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.26.1/install.sh | bash - -mapbox_time $NODE_VERSION \ +# nvm already exists on the Travis image nvm install $NODE_VERSION node --version From f9739604b4a64a20a7d6e84a136806a564f344ff Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:23:50 -0400 Subject: [PATCH 626/760] source nvm --- scripts/node/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 6c1120a08..ec263f86b 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -14,6 +14,7 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then fi # nvm already exists on the Travis image +. ~/.nvm/nvm.sh nvm install $NODE_VERSION node --version From c1d485eb35561bcfe18fb1a27962d04cbc629be8 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:34:42 -0400 Subject: [PATCH 627/760] actually use NODE_VERSION --- scripts/node/install.sh | 3 ++- scripts/node/run.sh | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index ec263f86b..99b480399 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,8 +13,9 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -# nvm already exists on the Travis image . ~/.nvm/nvm.sh + +mapbox_time "install_$NODE_VERSION" \ nvm install $NODE_VERSION node --version diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 232823f03..8e543fecf 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,6 +11,9 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ +. ~/.nvm/nvm.sh +nvm use $NODE_VERSION + mapbox_time "checkout_styles" \ git submodule update --init styles From 5ac2d7416761aca525341e9d45d61b298c884289 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:45:36 -0400 Subject: [PATCH 628/760] nvm alias default --- scripts/node/install.sh | 1 + scripts/node/run.sh | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 99b480399..e50e9574f 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -17,6 +17,7 @@ fi mapbox_time "install_$NODE_VERSION" \ nvm install $NODE_VERSION +nvm alias default $NODE_VERSION node --version npm --version diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 8e543fecf..232823f03 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,9 +11,6 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ -. ~/.nvm/nvm.sh -nvm use $NODE_VERSION - mapbox_time "checkout_styles" \ git submodule update --init styles From f676c716d0a53b9cfcf54b8deb6f50ddd0f49610 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:46:45 -0400 Subject: [PATCH 629/760] mapbox_time --- scripts/node/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index e50e9574f..e90f9adb2 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -15,7 +15,7 @@ fi . ~/.nvm/nvm.sh -mapbox_time "install_$NODE_VERSION" \ +mapbox_time $NODE_VERSION \ nvm install $NODE_VERSION nvm alias default $NODE_VERSION From 44bf39b5b04c4670ddba9f52f5e3b8afb1b9e8b2 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 14:51:14 -0400 Subject: [PATCH 630/760] use NODE_VERSION --- scripts/node/run.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 232823f03..8e543fecf 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,6 +11,9 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ +. ~/.nvm/nvm.sh +nvm use $NODE_VERSION + mapbox_time "checkout_styles" \ git submodule update --init styles From a42a9e4a7ca10edea17a14d6d31770707be74360 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 15:24:30 -0400 Subject: [PATCH 631/760] . -> source --- scripts/node/install.sh | 2 +- scripts/node/run.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index e90f9adb2..9711ad359 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,7 +13,7 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -. ~/.nvm/nvm.sh +source ~/.nvm/nvm.sh mapbox_time $NODE_VERSION \ nvm install $NODE_VERSION diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 8e543fecf..5e11ab751 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,7 +11,7 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ -. ~/.nvm/nvm.sh +source ~/.nvm/nvm.sh nvm use $NODE_VERSION mapbox_time "checkout_styles" \ From 6bfda4fcef60978ad39e0c2c3859d9902fcf1076 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 15:49:25 -0400 Subject: [PATCH 632/760] local nvm? --- platform/node/test/render.test.js | 9 +-------- scripts/node/install.sh | 2 +- scripts/node/run.sh | 2 +- src/mbgl/map/map.cpp | 2 ++ 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index a76e6baf0..87536891c 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -44,16 +44,9 @@ function renderTest(style, info, base, key) { var url = decodeURIComponent(req.url); console.log('JAVASCRIPT REQUEST:', url); fs.readFile(path.join(suitePath, url), function(err, data) { - // t.error(err); - console.log('CANCELED?', req.canceled, 'ERROR?', err, url); - if (!req.canceled) req.respond(err, { data: data }); + req.respond(err, { data: data }); }); }; - options.cancel = function(req) { - var url = decodeURIComponent(req.url); - console.log('JAVASCRIPT CANCEL:', url); - req.canceled = true; - }; options.ratio = 1.0; var map = new mbgl.Map(options); diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 9711ad359..7ad60d1d9 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,7 +13,7 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -source ~/.nvm/nvm.sh +source .nvm/nvm.sh mapbox_time $NODE_VERSION \ nvm install $NODE_VERSION diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 5e11ab751..b8b31dc19 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,7 +11,7 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ -source ~/.nvm/nvm.sh +source .nvm/nvm.sh nvm use $NODE_VERSION mapbox_time "checkout_styles" \ diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 9bfb04841..b260b228f 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace mbgl { @@ -63,6 +64,7 @@ void Map::renderSync() { MapChangeDidFinishRenderingFrame); if (!fullyLoaded) { + std::cout << "RenderState::partial" << std::endl; renderState = RenderState::partial; } else if (renderState != RenderState::fully) { renderState = RenderState::fully; From 5d6db325dea3721dacc5c444d40eb52c71eed025 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 15:56:36 -0400 Subject: [PATCH 633/760] nvm manual install --- scripts/node/install.sh | 4 +++- scripts/node/run.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 7ad60d1d9..8a9bd70f2 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,7 +13,9 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -source .nvm/nvm.sh +git clone git@github.com:creationix/nvm.git ~/.nvm +pushd ~/.nvm && git checkout `git describe --abbrev=0 --tags` && popd +source ~/.nvm/nvm.sh mapbox_time $NODE_VERSION \ nvm install $NODE_VERSION diff --git a/scripts/node/run.sh b/scripts/node/run.sh index b8b31dc19..5e11ab751 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -11,7 +11,7 @@ BUILDTYPE=${BUILDTYPE:-Release} # Build ################################################################################ -source .nvm/nvm.sh +source ~/.nvm/nvm.sh nvm use $NODE_VERSION mapbox_time "checkout_styles" \ From ef6392234ec4fa30d0cc89041ebc8d9470b1a35b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 16:10:07 -0400 Subject: [PATCH 634/760] conditional nvm install --- scripts/node/install.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 8a9bd70f2..b6196dda9 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -13,8 +13,11 @@ if [ ${TRAVIS_OS_NAME} == "linux" ]; then mason install mesa 10.4.3 fi -git clone git@github.com:creationix/nvm.git ~/.nvm -pushd ~/.nvm && git checkout `git describe --abbrev=0 --tags` && popd +if [ ! -d ~/.nvm ]; then + git clone https://github.com/creationix/nvm.git ~/.nvm +fi + +pushd ~/.nvm && git fetch && git checkout `git describe --abbrev=0 --tags` && popd source ~/.nvm/nvm.sh mapbox_time $NODE_VERSION \ From ca794eb56d90b3b737f25c99d2378e91eb0cc7e8 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 16:27:38 -0400 Subject: [PATCH 635/760] fix && to exit subshell with -e on failure --- scripts/node/install.sh | 7 ++++++- scripts/node/run.sh | 14 ++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index b6196dda9..6d6273599 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -17,11 +17,16 @@ if [ ! -d ~/.nvm ]; then git clone https://github.com/creationix/nvm.git ~/.nvm fi -pushd ~/.nvm && git fetch && git checkout `git describe --abbrev=0 --tags` && popd +pushd ~/.nvm +git fetch +git checkout `git describe --abbrev=0 --tags` +popd + source ~/.nvm/nvm.sh mapbox_time $NODE_VERSION \ nvm install $NODE_VERSION + nvm alias default $NODE_VERSION node --version diff --git a/scripts/node/run.sh b/scripts/node/run.sh index 5e11ab751..b42452976 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -17,8 +17,10 @@ nvm use $NODE_VERSION mapbox_time "checkout_styles" \ git submodule update --init styles +pushd platform/node mapbox_time "compile_program" \ -pushd platform/node && npm install --build-from-source && popd +npm install --build-from-source +popd ################################################################################ # Test @@ -29,11 +31,13 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then mapbox_time "checkout_test_suite" \ git submodule update --init test/suite + pushd platform/node mapbox_time "run_tests" \ - pushd platform/node && npm test && popd + npm test mapbox_time "run_render_tests" \ - pushd platform/node && npm run test-suite && popd + npm run test-suite + popd if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then # Install and add awscli to PATH for uploading the results @@ -41,7 +45,9 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then pip install --user awscli export PATH="`python -m site --user-base`/bin:${PATH}" + pushd test/suite mapbox_time "deploy_results" \ - pushd test/suite && ./bin/deploy_results.sh && popd + ./bin/deploy_results.sh + popd fi fi From 80532f1378ea23efbfbf43c97cc704d86728f9f4 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 16:30:01 -0400 Subject: [PATCH 636/760] update suitePath in consecutive.test.js --- platform/node/test/js/consecutive.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platform/node/test/js/consecutive.test.js b/platform/node/test/js/consecutive.test.js index f493e2a31..5f4c97e2f 100644 --- a/platform/node/test/js/consecutive.test.js +++ b/platform/node/test/js/consecutive.test.js @@ -7,8 +7,7 @@ var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); -var suitePath = path.dirname(require.resolve('mapbox-gl-test-suite/package.json')); - +var suitePath = path.join(__dirname, '../../../test/suite'); function renderTest(style, info, dir, key) { return function (t) { From 82bc4ef8eb985a27d5b0928ccacbbbd321b25974 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 16:40:31 -0400 Subject: [PATCH 637/760] only assume ~/.nvm is a git repo if we clone it --- scripts/node/install.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/node/install.sh b/scripts/node/install.sh index 6d6273599..26bf98002 100755 --- a/scripts/node/install.sh +++ b/scripts/node/install.sh @@ -15,12 +15,12 @@ fi if [ ! -d ~/.nvm ]; then git clone https://github.com/creationix/nvm.git ~/.nvm -fi -pushd ~/.nvm -git fetch -git checkout `git describe --abbrev=0 --tags` -popd + pushd ~/.nvm + git fetch + git checkout `git describe --abbrev=0 --tags` + popd +fi source ~/.nvm/nvm.sh From 5a8be45497fc9d0903d13b4614ed7c8019b81770 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 28 Aug 2015 16:56:22 -0400 Subject: [PATCH 638/760] fix consecutive.test.js suitePath --- platform/node/test/js/consecutive.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/test/js/consecutive.test.js b/platform/node/test/js/consecutive.test.js index 5f4c97e2f..ec4b2968b 100644 --- a/platform/node/test/js/consecutive.test.js +++ b/platform/node/test/js/consecutive.test.js @@ -7,7 +7,7 @@ var mbgl = require('../..'); var fs = require('fs'); var path = require('path'); -var suitePath = path.join(__dirname, '../../../test/suite'); +var suitePath = path.join(__dirname, '../../../../test/suite'); function renderTest(style, info, dir, key) { return function (t) { From bcfce808ca178c01eb417a6575035903d57265c1 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Fri, 28 Aug 2015 15:29:04 -0700 Subject: [PATCH 639/760] implement annotation marker tap recognition in Android --- android/cpp/jni.cpp | 104 ++++++++++++++++++ .../com/mapbox/mapboxgl/views/MapView.java | 36 ++++++ .../mapbox/mapboxgl/views/NativeMapView.java | 9 ++ include/mbgl/android/jni.hpp | 7 ++ 4 files changed, 156 insertions(+) diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index d1040d68e..e077e318a 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -50,6 +50,13 @@ jfieldID latLngZoomLatitudeId = nullptr; jfieldID latLngZoomLongitudeId = nullptr; jfieldID latLngZoomZoomId = nullptr; +jclass bboxClass = nullptr; +jmethodID bboxConstructorId = nullptr; +jfieldID bboxLatNorthId = nullptr; +jfieldID bboxLatSouthId = nullptr; +jfieldID bboxLonEastId = nullptr; +jfieldID bboxLonWestId = nullptr; + jclass markerClass = nullptr; jmethodID markerConstructorId = nullptr; jfieldID markerPositionId = nullptr; @@ -970,6 +977,50 @@ void JNICALL nativeRemoveAnnotations(JNIEnv *env, jobject obj, jlong nativeMapVi nativeMapView->getMap().removeAnnotations(ids); } +jlongArray JNICALL nativeGetAnnotationsInBounds(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject bbox) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAnnotationsInBounds"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + if (env->ExceptionCheck() || (bbox == nullptr)) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble swLat = env->GetDoubleField(bbox, bboxLatSouthId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble swLon = env->GetDoubleField(bbox, bboxLonWestId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble neLat = env->GetDoubleField(bbox, bboxLatNorthId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble neLon = env->GetDoubleField(bbox, bboxLonEastId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + mbgl::LatLngBounds bounds; + bounds.sw = { swLat, swLon }; + bounds.ne = { neLat, neLon }; + + // assume only points for now + std::vector annotations = nativeMapView->getMap().getAnnotationsInBounds(bounds, mbgl::AnnotationType::Point); + + return std_vector_uint_to_jobject(env, annotations); +} + void JNICALL nativeSetSprite(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring symbol, jint width, jint height, jfloat scale, jbyteArray jpixels) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetSprite"); @@ -1246,6 +1297,42 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + bboxClass = env->FindClass("com/mapbox/mapboxgl/geometry/BoundingBox"); + if (bboxClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxConstructorId = env->GetMethodID(bboxClass, "", "(DDDD)V"); + if (bboxConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLatNorthId = env->GetFieldID(bboxClass, "mLatNorth", "D"); + if (bboxLatNorthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLatSouthId = env->GetFieldID(bboxClass, "mLatSouth", "D"); + if (bboxLatSouthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLonEastId = env->GetFieldID(bboxClass, "mLonEast", "D"); + if (bboxLonEastId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLonWestId = env->GetFieldID(bboxClass, "mLonWest", "D"); + if (bboxLonWestId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + markerClass = env->FindClass("com/mapbox/mapboxgl/annotations/Marker"); if (markerClass == nullptr) { env->ExceptionDescribe(); @@ -1596,6 +1683,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { reinterpret_cast(&nativeAddPolygons)}, {"nativeRemoveAnnotation", "(JJ)V", reinterpret_cast(&nativeRemoveAnnotation)}, {"nativeRemoveAnnotations", "(J[J)V", reinterpret_cast(&nativeRemoveAnnotations)}, + {"nativeGetAnnotationsInBounds", "(JLcom/mapbox/mapboxgl/geometry/BoundingBox;)[J", + reinterpret_cast(&nativeGetAnnotationsInBounds)}, {"nativeSetSprite", "(JLjava/lang/String;IIF[B)V", reinterpret_cast(&nativeSetSprite)}, {"nativeOnLowMemory", "(J)V", reinterpret_cast(&nativeOnLowMemory)}, {"nativeSetDebug", "(JZ)V", reinterpret_cast(&nativeSetDebug)}, @@ -1639,6 +1728,13 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + bboxClass = reinterpret_cast(env->NewGlobalRef(bboxClass)); + if (bboxClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(bboxClass); + return JNI_ERR; + } + markerClass = reinterpret_cast(env->NewGlobalRef(markerClass)); if (markerClass == nullptr) { env->ExceptionDescribe(); @@ -1795,6 +1891,14 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { latLngZoomLatitudeId = nullptr; latLngZoomZoomId = nullptr; + env->DeleteGlobalRef(bboxClass); + bboxClass = nullptr; + bboxConstructorId = nullptr; + bboxLatNorthId = nullptr; + bboxLatSouthId = nullptr; + bboxLonEastId = nullptr; + bboxLonWestId = nullptr; + env->DeleteGlobalRef(markerClass); markerClass = nullptr; markerConstructorId = nullptr; diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 219069fda..8dde403c7 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -50,6 +50,7 @@ import com.mapbox.mapboxgl.annotations.Polygon; import com.mapbox.mapboxgl.annotations.PolygonOptions; import com.mapbox.mapboxgl.annotations.Polyline; import com.mapbox.mapboxgl.annotations.PolylineOptions; +import com.mapbox.mapboxgl.geometry.BoundingBox; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; import com.mapzen.android.lost.api.LocationListener; @@ -428,6 +429,25 @@ public class MapView extends FrameLayout implements LocationListener { return Collections.unmodifiableList(mAnnotations); } + public List getAnnotationsInBounds(BoundingBox bbox) { + List annotations = new ArrayList<>(); + long[] ids = mNativeMapView.getAnnotationsInBounds(bbox); + List idsList = new ArrayList<>(); + for(int i = 0; i < ids.length; i++) { + idsList.add(new Long(ids[i])); + } + for(int i = 0; i < mAnnotations.size(); i++) { + Annotation annotation = mAnnotations.get(i); + if (annotation instanceof Marker && idsList.contains(annotation.getId())) { + annotations.add(annotation); + } + } + for(int i = 0; i < annotations.size(); i++) { + Log.d(TAG, "tapped: " + Long.toString(annotations.get(i).getId())); + } + return annotations; + } + // // Property methods // @@ -919,6 +939,22 @@ public class MapView extends FrameLayout implements LocationListener { // Cancel any animation mNativeMapView.cancelTransitions(); + // Select or deselect point annotations + PointF tapPoint = new PointF(e.getX(), e.getY()); + + float toleranceWidth = 60 * mScreenDensity; + float toleranceHeight = 80 * mScreenDensity; + + PointF tr = new PointF(tapPoint.x + toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3); + PointF bl = new PointF(tapPoint.x - toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); + + LatLng sw = fromScreenLocation(bl); + LatLng ne = fromScreenLocation(tr); + + BoundingBox bbox = new BoundingBox(ne, sw); + + List annotations = getAnnotationsInBounds(bbox); + return true; } diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java index 6257a2362..56589f479 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java @@ -3,13 +3,16 @@ package com.mapbox.mapboxgl.views; import android.graphics.PointF; import android.view.Surface; +import com.mapbox.mapboxgl.annotations.Annotation; import com.mapbox.mapboxgl.annotations.Marker; import com.mapbox.mapboxgl.annotations.Polygon; import com.mapbox.mapboxgl.annotations.Polyline; +import com.mapbox.mapboxgl.geometry.BoundingBox; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; import com.mapbox.mapboxgl.geometry.ProjectedMeters; +import java.util.ArrayList; import java.util.List; // Class that wraps the native methods for convenience @@ -353,6 +356,10 @@ class NativeMapView { nativeRemoveAnnotations(mNativeMapViewPtr, ids); } + public long[] getAnnotationsInBounds(BoundingBox bbox) { + return nativeGetAnnotationsInBounds(mNativeMapViewPtr, bbox); + } + public void setSprite(String symbol, int width, int height, float scale, byte[] pixels) { nativeSetSprite(mNativeMapViewPtr, symbol, width, height, scale, pixels); } @@ -563,6 +570,8 @@ class NativeMapView { private native void nativeRemoveAnnotations(long nativeMapViewPtr, long[] id); + private native long[] nativeGetAnnotationsInBounds(long mNativeMapViewPtr, BoundingBox bbox); + private native void nativeSetSprite(long nativeMapViewPtr, String symbol, int width, int height, float scale, byte[] pixels); diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp index 62cb399d3..07c44fc7a 100644 --- a/include/mbgl/android/jni.hpp +++ b/include/mbgl/android/jni.hpp @@ -43,6 +43,13 @@ extern jfieldID latLngZoomLatitudeId; extern jfieldID latLngZoomLongitudeId; extern jfieldID latLngZoomZoomId; +extern jclass bboxClass; +extern jmethodID bboxConstructorId; +extern jfieldID bboxLatNorthId; +extern jfieldID bboxLatSouthId; +extern jfieldID bboxLonEastId; +extern jfieldID bboxLonWestId; + extern jclass markerClass; extern jmethodID markerConstructorId; extern jfieldID markerPositionId; From c6e3ed8c6629d0a42d984a4ced57eff558eac0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 28 Aug 2015 13:03:05 -0700 Subject: [PATCH 640/760] Updated title of attribution action sheet --- platform/ios/MGLMapView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 4f3e90307..a037ff05c 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -280,7 +280,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [self addSubview:_attributionButton]; _attributionButtonConstraints = [NSMutableArray array]; - _attributionSheet = [[UIActionSheet alloc] initWithTitle:@"Mapbox for iOS" + _attributionSheet = [[UIActionSheet alloc] initWithTitle:@"Mapbox iOS SDK" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil From dabfec6ddc2701e0e225ad8d90febbad299e6c1e Mon Sep 17 00:00:00 2001 From: tobrunvannuland Date: Sun, 30 Aug 2015 20:46:13 +0200 Subject: [PATCH 641/760] Added proguard config for SDK Updated build.gradle from the SDK to correct use proguard file from a AAR library Adjusted build.gradle sample app to use the thumb proguard file Added proguard config for SDK, added thumb config for sample project Updated build.gradle from the SDK to correct use proguard file from a AAR library Adjusted build.gradle sample app to use the thumb proguard file Added proguard config for SDK, added thumb config for sample project Updated build.gradle from the SDK to correct use proguard file from a AAR library Adjusted build.gradle sample app to use the thumb proguard file --- android/java/MapboxGLAndroidSDK/build.gradle | 3 +-- .../MapboxGLAndroidSDK/proguard-rules.pro | 24 +++++++++++++++++++ .../MapboxGLAndroidSDKTestApp/build.gradle | 2 +- .../proguard-rules.pro | 1 + 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 android/java/MapboxGLAndroidSDK/proguard-rules.pro create mode 100644 android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro diff --git a/android/java/MapboxGLAndroidSDK/build.gradle b/android/java/MapboxGLAndroidSDK/build.gradle index b749765eb..ea0be4fa1 100644 --- a/android/java/MapboxGLAndroidSDK/build.gradle +++ b/android/java/MapboxGLAndroidSDK/build.gradle @@ -65,9 +65,8 @@ android { } release { - minifyEnabled false jniDebuggable false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + consumerProguardFiles 'proguard-rules.pro' } } } diff --git a/android/java/MapboxGLAndroidSDK/proguard-rules.pro b/android/java/MapboxGLAndroidSDK/proguard-rules.pro new file mode 100644 index 000000000..6d3ca44d4 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/proguard-rules.pro @@ -0,0 +1,24 @@ +# By default, the flags in this file are appended to flags specified +# in ../sdk/tools/proguard/proguard-android.txt, +# contents of this file will be appended into proguard-android.txt + +# Square okio, ignoring warnings, +# see https://github.com/square/okio/issues/60 +-dontwarn okio.** + +# Package: annotations +-keep class com.mapbox.mapboxgl.annotations.** { *; } + +# Package: geometry +-keep class com.mapbox.mapboxgl.geometry.** { *; } + +# Package: http +-keep class com.mapbox.mapboxgl.http.** { *; } + +# Package views +-keep class com.mapbox.mapboxgl.views.MapView** +-keep class com.mapbox.mapboxgl.views.NativeMapView** +-keepclassmembers class com.mapbox.mapboxgl.views.NativeMapView** { + *; +} + diff --git a/android/java/MapboxGLAndroidSDKTestApp/build.gradle b/android/java/MapboxGLAndroidSDKTestApp/build.gradle index b3e4164f2..b0c0f1a3e 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/build.gradle +++ b/android/java/MapboxGLAndroidSDKTestApp/build.gradle @@ -73,7 +73,7 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } diff --git a/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro b/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro new file mode 100644 index 000000000..578d94692 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro @@ -0,0 +1 @@ +# Proguard configuration is handeld in the SDK \ No newline at end of file From 1189dea852cc207253452b511a2f62564535f4b2 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Mon, 31 Aug 2015 16:55:25 -0500 Subject: [PATCH 642/760] #2063 - Fixing test/suite stomping from merge --- test/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/suite b/test/suite index b6828212d..80e8ff67d 160000 --- a/test/suite +++ b/test/suite @@ -1 +1 @@ -Subproject commit b6828212d3fe5a06e1e91df1dc9a212cb5656093 +Subproject commit 80e8ff67dcf9ae5627d5fcd22a831fe318df7cdf From fc55a34831021efe3475346cf2f4c45f9bb1d151 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 15:17:45 -0700 Subject: [PATCH 643/760] Update test-suite --- test/suite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/suite b/test/suite index 80e8ff67d..083dbe5f3 160000 --- a/test/suite +++ b/test/suite @@ -1 +1 @@ -Subproject commit 80e8ff67dcf9ae5627d5fcd22a831fe318df7cdf +Subproject commit 083dbe5f3da13d006f3d2eb6669780b1d003a7e3 From cb905bac28f845477816bd04128bb2d04f7667ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Fri, 28 Aug 2015 12:35:50 -0700 Subject: [PATCH 644/760] Fixed persistence of user tracking mode in iOS This typo prevented the tracking mode from being restored when relaunching iosapp. --- platform/darwin/settings_nsuserdefaults.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/darwin/settings_nsuserdefaults.mm b/platform/darwin/settings_nsuserdefaults.mm index 5c943baff..548ee9b22 100644 --- a/platform/darwin/settings_nsuserdefaults.mm +++ b/platform/darwin/settings_nsuserdefaults.mm @@ -30,7 +30,7 @@ void Settings_NSUserDefaults::load() pitch = [settings[@"pitch"] doubleValue]; debug = [settings[@"debug"] boolValue]; - unsigned uncheckedTrackingMode = [settings[@"trackingMode"] unsignedIntValue]; + unsigned uncheckedTrackingMode = [settings[@"userTrackingMode"] unsignedIntValue]; if (uncheckedTrackingMode > MGLUserTrackingModeNone && uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse) { @@ -57,4 +57,4 @@ void Settings_NSUserDefaults::save() void Settings_NSUserDefaults::clear() { [NSUserDefaults resetStandardUserDefaults]; -} \ No newline at end of file +} From 42efc9a21aa09d22ebe30345cd385706428eaf13 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 15:42:04 -0700 Subject: [PATCH 645/760] Fix NODE_PRE_GYP variable `cd` outputs the resulting directory, which is not what is desired here. --- scripts/main.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/main.mk b/scripts/main.mk index 451f6a0d0..120b13c3c 100644 --- a/scripts/main.mk +++ b/scripts/main.mk @@ -95,7 +95,7 @@ Xcode/__project__: print-env $(SUBMODULES) config/$(HOST_SLUG).gypi #### Build individual targets ################################################## -NODE_PRE_GYP = $(shell cd platform/node && npm bin)/node-pre-gyp +NODE_PRE_GYP = $(shell cd platform/node > /dev/null && npm bin)/node-pre-gyp node/configure: cd platform/node && $(NODE_PRE_GYP) configure --clang -- \ $(GYP_FLAGS) -I../../config/$(HOST_SLUG).gypi \ From 831df11ec47cb44f6664490a361ff42ef288ce9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Mon, 31 Aug 2015 15:27:26 -0700 Subject: [PATCH 646/760] Disabled iOS metrics tests ref #1887 --- .../xcschemes/Mapbox GL Tests.xcscheme | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme index f7173ee64..2737f5185 100644 --- a/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme +++ b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme @@ -37,10 +37,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -58,6 +58,9 @@ + + @@ -79,15 +82,18 @@ ReferencedContainer = "container:ios-tests.xcodeproj"> + + @@ -103,10 +109,10 @@ From 41edb666ce6483635e9a8e943af00de423ec3bd4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 16:26:17 -0700 Subject: [PATCH 647/760] Fix render test script --- platform/node/test/render.test.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 87536891c..6045aae38 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -47,7 +47,7 @@ function renderTest(style, info, base, key) { req.respond(err, { data: data }); }); }; - options.ratio = 1.0; + options.ratio = info[key].pixelRatio || 1; var map = new mbgl.Map(options); map.load(style); @@ -83,13 +83,20 @@ function renderTest(style, info, base, key) { compare(actual, expected, diff, t, function(err, diff) { t.error(err); - var allowed = ('diff' in info) ? info.diff : 0.001; - var color = diff <= allowed ? 'green' : 'red'; + var allowed = 0.001; + + if ('diff' in info[key]) { + if (typeof info[key].diff === 'number') { + allowed = info[key].diff; + } else if ('native' in info[key].diff) { + allowed = info[key].diff.native; + } + } results += format(resultTemplate, { name: base, key: key, - color: color, + color: diff <= allowed ? 'green' : 'red', error: err ? '

' + err + '

' : '', difference: diff, zoom: info.zoom || 0, @@ -99,7 +106,10 @@ function renderTest(style, info, base, key) { height: info.height || 512 }); - t.ok(diff <= allowed, 'actual matches expected'); + if (!info[key].ignored || !('native' in info[key].ignored)) { + t.ok(diff <= allowed, 'expected ' + diff + ' to be less than ' + allowed); + } + t.end(); }); }); From 8977441a84d101fd2aa209106d676526b97282ea Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 16:44:08 -0700 Subject: [PATCH 648/760] Drop faucet, it hides key error output when tests fail --- platform/node/Makefile | 4 ++-- platform/node/package.json | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/platform/node/Makefile b/platform/node/Makefile index 74b911a14..b17f66e26 100644 --- a/platform/node/Makefile +++ b/platform/node/Makefile @@ -63,11 +63,11 @@ $(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure .PHONY: test-suite test-suite: - -@(`npm bin`/tape test/render.test.js | `npm bin`/faucet) + -@(`npm bin`/tape test/render.test.js) .PHONY: test-js test-js: - @(`npm bin`/tape test/js/**/*.test.js | `npm bin`/faucet) + @(`npm bin`/tape test/js/**/*.test.js) .PHONY: test test: test-js test-suite diff --git a/platform/node/package.json b/platform/node/package.json index d0cb4eb76..d04c08697 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -26,7 +26,6 @@ ], "devDependencies": { "aws-sdk": "^2.1.47", - "faucet": "0.0.1", "mkdirp": "^0.5.1", "pngjs": "^0.4.0", "request": "^2.61.0", @@ -35,7 +34,7 @@ }, "scripts": { "install": "node-pre-gyp install || make node -C ../../", - "test": "tape test/js/**/*.test.js | faucet", + "test": "tape test/js/**/*.test.js", "test-suite": "tape test/render.test.js || true" }, "gypfile": true, From 119a9fbf776fc488b19668f54cc68998ebdfb12a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 16:44:45 -0700 Subject: [PATCH 649/760] Don't let test-suite fail; we have exceptions for known failures now --- platform/node/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/package.json b/platform/node/package.json index d04c08697..fb8663d9a 100644 --- a/platform/node/package.json +++ b/platform/node/package.json @@ -35,7 +35,7 @@ "scripts": { "install": "node-pre-gyp install || make node -C ../../", "test": "tape test/js/**/*.test.js", - "test-suite": "tape test/render.test.js || true" + "test-suite": "tape test/render.test.js" }, "gypfile": true, "binary": { From 92f8372d0c2901b0fff70bc4eb0b5a17337ae5b9 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 17:20:18 -0700 Subject: [PATCH 650/760] Always release map --- platform/node/test/render.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 6045aae38..b97b04747 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -37,6 +37,11 @@ function renderTest(style, info, base, key) { t.once('end', function() { clearTimeout(watchdog); + + if (map) { + map.release(); + map = null; + } }); var options = {}; @@ -53,8 +58,6 @@ function renderTest(style, info, base, key) { map.load(style); map.render(info[key], function(err, data) { - map.release(); - if (err) { console.log('THIS IS AN ERROR'); t.error(err); From e33abc975bff22f0bebd32e72e87ff6a70c1f509 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 17:20:58 -0700 Subject: [PATCH 651/760] More logging --- platform/node/test/render.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index b97b04747..76acff5c0 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -31,11 +31,16 @@ function renderTest(style, info, base, key) { mkdirp.sync(dir); return function(t) { + console.log('STARTING'); + var watchdog = setTimeout(function() { + console.log('THIS IS A TIMEOUT'); t.fail('timed out after 20 seconds'); }, 20000); t.once('end', function() { + console.log('THIS IS THE END'); + clearTimeout(watchdog); if (map) { From 0918ad5a29ea535ac59c39097cd7a670f0a76320 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 17:53:37 -0700 Subject: [PATCH 652/760] Clean up tests --- platform/node/test/js/map.test.js | 355 ++++++++++++++---------------- 1 file changed, 163 insertions(+), 192 deletions(-) diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index 4586ceeed..ecd89c148 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -20,200 +20,178 @@ function filePath(name) { }, {}); } -function setup(options, callback) { - callback(new mbgl.Map(options)); -} - test('Map', function(t) { - t.test('constructor', function(t) { - t.test('must be called with new', function(t) { - t.throws(function() { - mbgl.Map(); - }, /Use the new operator to create new Map objects/); + t.test('must be constructed with new', function(t) { + t.throws(function() { + mbgl.Map(); + }, /Use the new operator to create new Map objects/); - t.end(); - }); + t.end(); + }); - t.test('should require an options object as first parameter', function(t) { - t.throws(function() { - new mbgl.Map(); - }, /Requires an options object as first argument/); + t.test('must be constructed with options object', function(t) { + t.throws(function() { + new mbgl.Map(); + }, /Requires an options object as first argument/); - t.throws(function() { - new mbgl.Map('options'); - }, /Requires an options object as first argument/); + t.throws(function() { + new mbgl.Map('options'); + }, /Requires an options object as first argument/); - t.end(); - }); + t.end(); + }); - t.test('should require then options object to have request and cancel methods', function(t) { - var options = {}; + t.test('requires request and ratio options', function(t) { + var options = {}; - t.throws(function() { - new mbgl.Map(options); - }, /Options object must have a 'request' method/); + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); - options.request = 'test'; - t.throws(function() { - new mbgl.Map(options); - }, /Options object must have a 'request' method/); + options.request = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); - options.request = function() {}; - options.cancel = 'test'; - t.throws(function() { - new mbgl.Map(options); - }, /Options object 'cancel' property must be a function/); + options.request = function() {}; + options.cancel = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object 'cancel' property must be a function/); - options.cancel = function() {}; - t.throws(function() { - new mbgl.Map(options); - }, /Options object must have a numerical 'ratio' property/); + options.cancel = function() {}; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); - options.ratio = 'test'; - t.throws(function() { - new mbgl.Map(options); - }, /Options object must have a numerical 'ratio' property/); + options.ratio = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); - options.ratio = 1.0; - t.doesNotThrow(function() { - new mbgl.Map(options); - }); - - t.end(); + options.ratio = 1.0; + t.doesNotThrow(function() { + new mbgl.Map(options); }); t.end(); }); - t.test('load styles', function(t) { - var options = {}; - options.request = function() {}; - options.cancel = function() {}; - options.ratio = 1.0; + t.test('.load', function(t) { + var options = { + request: function() {}, + ratio: 1 + }; - t.test('requires a string or object as the first parameter', function(t) { - t.test('requires a map style as first argument', function(t) { - setup(options, function(map) { - t.throws(function() { - map.load(); - }, /Requires a map style as first argument/); + t.test('requires a map style as first argument', function(t) { + var map = new mbgl.Map(options); - map.release(); - - t.end(); - }); - }); - - t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { - setup(options, function(map) { - mbgl.once('message', function(msg) { - t.equal(msg.severity, 'ERROR'); - t.equal(msg.class, 'ParseStyle'); - t.ok(msg.text.match(/Expect either an object or array at root/)); - - map.release(); - - t.end(); - }); - - map.load('invalid'); - }); - }); + t.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + map.release(); t.end(); }); + t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + mbgl.once('message', function(msg) { + t.equal(msg.severity, 'ERROR'); + t.equal(msg.class, 'ParseStyle'); + t.ok(msg.text.match(/Expect either an object or array at root/)); + + map.release(); + t.end(); + }); + + map.load('invalid'); + }); + t.test('accepts an empty stylesheet string', function(t) { - setup(options, function(map) { - t.doesNotThrow(function() { - map.load('{}'); - }); + var map = new mbgl.Map(options); - map.release(); - - t.end(); + t.doesNotThrow(function() { + map.load('{}'); }); + + map.release(); + t.end(); }); t.test('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { - setup(options, function(map) { - t.doesNotThrow(function() { - map.load(style); - }); + var map = new mbgl.Map(options); - map.release(); - - t.end(); + t.doesNotThrow(function() { + map.load(style); }); + + map.release(); + t.end(); }); t.test('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { - setup(options, function(map) { - t.doesNotThrow(function() { - map.load(JSON.stringify(style)); - }); + var map = new mbgl.Map(options); - map.release(); - - t.end(); + t.doesNotThrow(function() { + map.load(JSON.stringify(style)); }); - }); - t.end(); + map.release(); + t.end(); + }); }); - t.test('render argument requirements', function(t) { - var options = {}; - options.request = function(req) { - fs.readFile(path.join('test', req.url), function(err, data) { - req.respond(err, { data: data }); - }); + t.test('.render', function(t) { + var options = { + request: function(req) { + fs.readFile(path.join('test', req.url), function(err, data) { + req.respond(err, { data: data }); + }); + }, + ratio: 1 }; - options.cancel = function() {}; - options.ratio = 1.0; t.test('requires an object as the first parameter', function(t) { - setup(options, function(map) { - t.throws(function() { - map.render(); - }, /First argument must be an options object/); + var map = new mbgl.Map(options); - t.throws(function() { - map.render('invalid'); - }, /First argument must be an options object/); + t.throws(function() { + map.render(); + }, /First argument must be an options object/); - map.release(); + t.throws(function() { + map.render('invalid'); + }, /First argument must be an options object/); - t.end(); - }); + map.release(); + t.end(); }); t.test('requires a callback as the second parameter', function(t) { - setup(options, function(map) { - t.throws(function() { - map.render({}); - }, /Second argument must be a callback function/); + var map = new mbgl.Map(options); - t.throws(function() { - map.render({}, 'invalid'); - }, /Second argument must be a callback function/); + t.throws(function() { + map.render({}); + }, /Second argument must be a callback function/); - map.release(); + t.throws(function() { + map.render({}, 'invalid'); + }, /Second argument must be a callback function/); - t.end(); - }); + map.release(); + t.end(); }); t.test('requires a style to be set', function(t) { - setup(options, function(map) { - t.throws(function() { - map.render({}, function() {}); - }, /Style is not loaded/); + var map = new mbgl.Map(options); - map.release(); + t.throws(function() { + map.render({}, function() {}); + }, /Style is not loaded/); - t.end(); - }); + map.release(); + t.end(); }); t.test('returns an error', function(t) { @@ -224,70 +202,63 @@ test('Map', function(t) { t.ok(msg.text.match(/Failed to load/), 'error text matches'); }); - setup(options, function(map) { - map.load(style); - map.render({ zoom: 1 }, function(err, data) { - mbgl.removeAllListeners('message'); - map.release(); - - t.ok(err, 'returns error'); - t.ok(err.message.match(/Failed to load/), 'error text matches'); - - t.end(); - }); - }); - }); - - t.test('double release', function(t) { - setup(options, function(map) { + var map = new mbgl.Map(options); + map.load(style); + map.render({ zoom: 1 }, function(err, data) { + mbgl.removeAllListeners('message'); map.release(); - t.throws(function() { - map.release(); - }, /Map resources have already been released/); + t.ok(err, 'returns error'); + t.ok(err.message.match(/Failed to load/), 'error text matches'); t.end(); }); }); - t.test('returns an image', function(t) { - setup(options, function(map) { - map.load(style); - map.render({}, function(err, data) { - t.error(err); + t.test('double release', function(t) { + var map = new mbgl.Map(options); + map.release(); - map.release(); + t.throws(function() { + map.release(); + }, /Map resources have already been released/); - var filename = filePath('image.png'); - - var png = new PNG({ - width: data.width, - height: data.height - }); - - png.data = data.pixels; - - if (process.env.UPDATE) { - png.pack() - .pipe(fs.createWriteStream(filename.expected)) - .on('finish', t.end); - } else { - png.pack() - .pipe(fs.createWriteStream(filename.actual)) - .on('finish', function() { - compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) { - t.error(err); - t.ok(diff <= 0.01, 'actual matches expected'); - t.end(); - }); - }); - } - }); - }); + t.end(); }); - t.end(); - }); + t.test('returns an image', function(t) { + var map = new mbgl.Map(options); + map.load(style); + map.render({}, function(err, data) { + t.error(err); - t.end(); + map.release(); + + var filename = filePath('image.png'); + + var png = new PNG({ + width: data.width, + height: data.height + }); + + png.data = data.pixels; + + if (process.env.UPDATE) { + png.pack() + .pipe(fs.createWriteStream(filename.expected)) + .on('finish', t.end); + } else { + png.pack() + .pipe(fs.createWriteStream(filename.actual)) + .on('finish', function() { + compare(filename.actual, filename.expected, filename.diff, t, function(err, diff) { + t.error(err); + t.ok(diff <= 0.01, 'actual matches expected'); + t.end(); + }); + }); + } + }); + }); + }); }); From 55d25a80a77c06ef5e66acc0d8518867b03fe8a4 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 17:56:27 -0700 Subject: [PATCH 653/760] In still mode, don't load resources until a render request is made --- platform/node/test/js/map.test.js | 22 ++++++++++++++++++++++ src/mbgl/map/map_context.cpp | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index ecd89c148..d4a8a7796 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -141,6 +141,28 @@ test('Map', function(t) { map.release(); t.end(); }); + + t.test('does not immediately trigger any tile loads', function(t) { + var map = new mbgl.Map({ + request: function(req) { + if (req.url === './fixtures/tiles.tilejson') { + fs.readFile(path.join('test', req.url), function (err, data) { + req.respond(err, {data: data}); + }); + } else { + t.fail('unexpected request ' + req.url); + } + }, + ratio: 1 + }); + + map.load(style); + + setTimeout(function() { + map.release(); + t.end(); + }, 100); + }); }); t.test('.render', function(t) { diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index f6cc4ec2b..17d3a87cc 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -259,7 +259,7 @@ void MapContext::update() { updateFlags = Update::Nothing; } - if (updateFlags == Update::Nothing) { + if (updateFlags == Update::Nothing || (data.mode == MapMode::Still && !callback)) { return; } From c4c3b5bc71c38fe6d6c0df0ddb38c57dfd2767ab Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 31 Aug 2015 18:41:57 -0700 Subject: [PATCH 654/760] Remove debug logging --- platform/node/src/node_request.cpp | 3 --- platform/node/test/render.test.js | 7 ------- 2 files changed, 10 deletions(-) diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp index a003e41ee..ea9fc4d73 100644 --- a/platform/node/src/node_request.cpp +++ b/platform/node/src/node_request.cpp @@ -62,7 +62,6 @@ NAN_METHOD(NodeRequest::Respond) { // Request has already been responded to, or was canceled, fail silently. if (!nodeRequest->resource) NanReturnUndefined(); - std::cout << "C++ RESPOND: " << nodeRequest->resource->url << std::endl; auto source = nodeRequest->source; auto resource = std::move(nodeRequest->resource); @@ -136,11 +135,9 @@ NodeRequest::NodeRequest(NodeFileSource* source_, const mbgl::Resource& resource resource(std::make_unique(resource_)) {} NodeRequest::~NodeRequest() { - if (resource) std::cout << "C++ DESTROY: " << resource->url << std::endl; } void NodeRequest::cancel() { - std::cout << "C++ CANCEL: " << resource->url << std::endl; resource.reset(); } diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 76acff5c0..e14f294eb 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -31,16 +31,11 @@ function renderTest(style, info, base, key) { mkdirp.sync(dir); return function(t) { - console.log('STARTING'); - var watchdog = setTimeout(function() { - console.log('THIS IS A TIMEOUT'); t.fail('timed out after 20 seconds'); }, 20000); t.once('end', function() { - console.log('THIS IS THE END'); - clearTimeout(watchdog); if (map) { @@ -52,7 +47,6 @@ function renderTest(style, info, base, key) { var options = {}; options.request = function(req) { var url = decodeURIComponent(req.url); - console.log('JAVASCRIPT REQUEST:', url); fs.readFile(path.join(suitePath, url), function(err, data) { req.respond(err, { data: data }); }); @@ -64,7 +58,6 @@ function renderTest(style, info, base, key) { map.render(info[key], function(err, data) { if (err) { - console.log('THIS IS AN ERROR'); t.error(err); return t.end(); } From 244fe93f0e6610cd7d66e1f9fd08ca84cb0dd7b2 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Tue, 1 Sep 2015 16:48:46 +0300 Subject: [PATCH 655/760] Bump geojsonvt version It was downgraded (probably accidentally) by 60e9712916. --- src/mbgl/util/geojsonvt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mbgl/util/geojsonvt b/src/mbgl/util/geojsonvt index ed99a6290..995ffc72c 160000 --- a/src/mbgl/util/geojsonvt +++ b/src/mbgl/util/geojsonvt @@ -1 +1 @@ -Subproject commit ed99a6290fa42107a982e7f3675aae49d29026b1 +Subproject commit 995ffc72c556da4b4880a6036fbcf7159fc5ecce From 4d5a4d27f1a2e9d2a06ffde44dc2194ff22bec10 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Sep 2015 13:38:52 -0400 Subject: [PATCH 656/760] Optimize bundled images' file sizes Saves 8KB, because we care. --- platform/ios/resources/Compass@2x.png | Bin 2376 -> 1300 bytes platform/ios/resources/Compass@3x.png | Bin 2540 -> 2514 bytes platform/ios/resources/mapbox.png | Bin 1958 -> 1085 bytes platform/ios/resources/mapbox@2x.png | Bin 4492 -> 2180 bytes platform/ios/resources/mapbox@3x.png | Bin 7059 -> 3293 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/platform/ios/resources/Compass@2x.png b/platform/ios/resources/Compass@2x.png index 58e7e08d2455e10b98d529d45ae3a9646956027c..2bd8a286dad78d48e294d5067ce90a24baa99f94 100644 GIT binary patch literal 1300 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 delta 1559 zcmV+y2I%?H6YLX^BnaYAOjJbx006Nm-vSN~jeTzwhNv8m>-eE;-r#f9i)Ozut3(bD5b3x_K$+=EIP==5SGJZ=uwp!!>3f zhUlT)^SPuLkQI}}&EM@&$bWizK{4o5yx_FeGti1D+slDi`SvL*t{W;m9}X0SHx*tN z6d4o+ltl$a8iulhA~b;!BQ&Xqpe#dBMw1D&>)su-Gc|>=Lmh1ZED)}(oe_#30;CcSxZD z1EfwaIs)pJS;>(36ll~By$QM{W>!YKs zn$V2^1c}kX-J}I|08mPszN2t(?P@5j4QQnY?x%HNY?unV2S6b?EZWY`{I9=hLly^i zB<)i|Std|Q{Xn}vc>Yh&k(3UYrYfNS0d1Rjo*(rt(AHNDJ%1QM4L!^Y-9ZgSOprl+ zshzv|tBrb={*k26m?&!K30~|jYA9-q1Ztvop5}3*o}rewYfxx7HS`?s?0IS^Jk$kE z5rO*0hq-lL;=B6$Wp_Yj#ie>tigiKh&UbR-^1(pv4c^s#WTrXrI4W5sLg`(QtsKNo zX4axv{woR2hkyLFuG~prENktK{p*9VX|g$?Sm>&H!%>u-cT5+$mzVUmF7sF!8s}FE zu3|AHe}P*P*CpsVFY?l^+^z@BEt^&)g->9pAP!_r_tk@5=Vkq_2PwZGu0VwHfwH%F zc~|}d%CbagJT3HpUeSBB(0CEzD4mCRWu1>uLYxRCQGY^@@~U2;gpx!khR%7lQw?># zMhC@Qh0H+Lle|Vx0bLmaR72-%>~V`mJvP!obpq5Myik7uD)I(YBtSQK1G-s&dV2%v zBS3vbVCX?-!#x8GKj>`ezX5rnK5UDjCqgq_0REX_F%&UDArlQjlLe?h6NFj~LHz}& zh6M^iXn&Fss7`>)EYLeWQHv2M1HUHdp+WEC?`MQ!@QaTHf)DU|V1w|Bk7I&v4MDBU z5GO+8nIIU1*8>yukqAv?fkdu_75Z3&R>!eHpJIXl`A(NRajQkh=Y$lfnpQTK3_+`~ z9*9uFxPAXXd#Y#ph1RUCN&gQC_AEIh2RSeF&@$QOo#y1*bkj( z9)HPrK{N-0%=IH44C3}FofHSNWO1Y@rSs~;+p&amSgP=eFX`|W*FkU$OYNiscH<6~ z2nXyq>u8B%{K`NgeQ9-b2@-%Eqf!9j5(GEfGuJx)fSb*JQFSaqUrx|_A?FmZU%lno zztV-i{)X-94QEc)a#m(1f^O5$w^~lJnSXpeYTE+p{86s{_2>@GzhaBc-G#4J04K`1X zCKlwM;Vvsj6EAaT@(buk6B){=TRQnrZ=pkzm)2!44qJMue*k+DU2i3oYN7xD002ov JPDHLkV1n5-&b9ym diff --git a/platform/ios/resources/mapbox.png b/platform/ios/resources/mapbox.png index 5a9da3fe390e746402533e524b7d3f486541eb55..533ad9f723c4c8857dfd60c603b2bd99e971be71 100644 GIT binary patch delta 1075 zcmV-31kC%U54{MG8Gi%-004Nsn2G=Z1O!P$K~#7F&6NXqBuf;9Pt0X(J8Qe1ZQHhO z+qP}n=ESy>*>88Em+9#~ud37A!P?^_J94b!SaVFf0yiQz5NUc<9~;|+})fGL+fjUkQ=N` z=@4Y+KM)ArXausSE^J3kgK)e|DH9tvhAcSK_(&w$F?OyJ{E%cERvgg z0how+_NsYUY=4a=BNU6Qapo!z?mFA;(z0 z2jxH?pp>EY)O~w@;C2#2(3GdoS@hQRrt@$tx%T9?-*vaHb^LYnX%Ig@N;dx}=N6uH zgr{HYIpPpZT?N9~$9#1!xvZRQ`yFBsA#l6aUrEj7EPvE`$@(uL6oS^%@#oSK{Xeqi zQ)N*rI{sW%VQAz(FR19;ca8)#T(!%qY3S}pE(qMt=^#J3BXoOb5&6L_5SXw^gwXBf zUubA&9lZkK#Vbf>;p{yjcG!1$C3)XD3Snun+IX)6l;z>`hZ1rAvHcy?FFihV``#8q zf9XnPdw=p`S=g=Pb~xrRPp_x1;ytqQdudMtR*3*{@7XkpUWZtLQ1u}x`(&2P?VM*W zAq!u%5t1?Qf%C}ncWs1dEc)PP^3P{)J%g&>>1I_Y4UfVdXUZsyL*B0YNrbP5MPcsa7m{T|QHaFC zcWzKoxaIVbQBb80QS>^gD&w*fumAz8L&$}%3}=XRW+A+AIq58#yO&DMC%J>+r>e!ty1zxK|7?h3RyIt!qq}JW} zPsg8~4imxZ2xp;3b;M_ON2nfgla80|j-ac~Wp%{V-TT@M&yA--RJ>0XzNR9yjtJ^p z_~(WGPFfm7psC)*A08ThE>L&Xd~_QGvVRbUC0Cx*_NzXiyF30=8`pe7{`riYo1V36 zZxi+V&&5Ivlb3xh#aosj>pWY6vB-fK@z{wl=hKm3jYpo26EN4dEl`-|lO_{A+Zs>B z2>BsGKI4a3f=x@X1r&!A>t>i@Bjg~)EGjR=T64*W8gWNVM1plkgbwrXi*Pv(hb#{^ tQ1Qc>aw{p!PC*d1nj*}LdOS9X{s)Xkx-h5Vk9Yt8002ovPDHLkV1jZs7Zd;h delta 1955 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 diff --git a/platform/ios/resources/mapbox@2x.png b/platform/ios/resources/mapbox@2x.png index 194aa64da214388eec2e2449cf35d57a4d0fbed9..51ad74adcb6cd049234ac754d7852b0a510be6ca 100644 GIT binary patch delta 2178 zcmV-|2z~dABZLu<8Gi%-005Yc>iz%#2un#sK~#7F?U@5$Bufy6{}^YRv2{o;nmpUK zZQHhO+qQYi+1lv5n-?du`>yBj+04$&ZYFs~RnT4CJNUZ0s=62d%m4Df1eZvpp%jXy z(~yHv2uGmAApw*9eWqXtejf?LI82jQEk!os@s~*!6q8OxAAdvpSd>|M48ZRp6D;?Q zz+WQKP;9gqF`$gJZBNHL z^w?ObwdUcsODtcUcb@))RvZ48L!h`9_uk4sDrus0*|BOn+x`;}D4*UT9bkX)KeA$D zCG8;~gl$nfiGMVz0|+A>E~M~397212#qPq&c<|);q!TT0dLZk3Cpz6SbzA@!JPL zC?fyJiht)Xb$!{oc}s)Zynf|}_nigB*xugYlfu~JSz7t*$%pDA)(L^q(b!hsu)6m9 zk8dy7S@y}(FbvRx>0M{6c)xj_SM_;YH?02j@$;cjA3oRTf%NKt~ZtGdjD}Vp6 zX3cO20ZQFZEWdIkl$vil?M4XH1NYlB&u5aFZ+{DSWvdke#oMvyjWOG?-3TEN*T=WX z_6)?+3v9hd?RVyW>u2t>FZ(TegO<9VBP;YPS3W2G*gQE;$2x{^>R}9sO>3XOl-6}K zpjaUseNgjypN-V7D%mwS1j?p)Svv)XKw0t@Ux#z*&;uH)?Rqt@KkabQp16I3iIn}Fb^Q-CTgXAKOa0~&;+!t8$wsYjpIwBAPrGT3qzml}bOS=}PJgW%I$1Ax6^a$YN4K)IsrKBXM4cJ&bkgou~U$ZDsxt1OlgN2gg2mbbpxn zq`o8=raKx%t#uRKp00(vHq~TtBj;Hl0Fjt1NXNs7-dj#(*$m;MTiAcsy|uPj0N_sJhLhRy@VT}S;(tWx;QiU> zof~W*0K-K|&sVNH5z6XMG=ESd^FCdsh&ug3^*2z=bGXe-hso@q`w$Am-;!PDzappK zWJeIJSF%Eg7o{T)WS_UMw}mjw2;rI&LPKEGsQy}%xlr31^@KzwQ$>jC34~$HyQN>9 zh>T#%;m*^!92`R!FG|l`9DiatgdtayetfvA<-kDQ(QI4}u@&oQ8*+#ZGGoc?34|dS zh;qY`mj!}8p%->K#<5830zw|rc}>?>wvLg&Cli1D3cf~UMJMu)ZZ$i`SFV(f@mgN_ zoBLSTbBHyc(~glnfdC9fE~X$&o9Y?CLZsTle2p3!g04?q<8Glp?+-5Da34K(D~$`l4gH1L2Kp)Rxf6 zQtz$4yw|I80my_`nf^F?HC8j~2tDx-J&Tj<3;CF>U-EY5*b9(?mUG`q$_Mx}Z9WCEKB!6R57UJjxD|WB`HrQ4= zWe0u!ym~cXm~X|?5Ve(CKukdp>16|9aF-B@P(m;M>?N+xTQ~Z=P9Ui5jjn~S(TCU_ zs_^yQi>E)a$KL+)rwHv_sg(6QPFwa~(^_$;(!62yCo&IUVyDUKPxLWokpJ#M+C58# z@hmFb@mC<0BY(;9mL5syg(BneTH&sNI%pe0U=*j#bi5YF@iKnOf6743Km;g#P}Gg% zwQ)?VE%qr0Dq~DtlVO%96xw`06^TUND7e~$NX~&V+L&a9_-rAD!6=d3qFD}Fvn|h% zBX^8rEqwM3RC?aKM#HIzx=)ASN0#0#8#Vb?*IS*07*qoM6N<$ Eg8Je^Z2$lO literal 4492 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 b3>wW(bTes_m8aA^00000NkvXXu0mjfNQZ02 literal 7059 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 From 0e79d56b0939de9f4722350a34ab76554a23431c Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 15:51:29 -0400 Subject: [PATCH 657/760] move package.json and binding.gyp to root, drop tiles.tilejson --- .gitignore | 3 +-- platform/node/binding.gyp => binding.gyp | 28 +++++++++++----------- platform/node/package.json => package.json | 6 ++--- platform/node/test/fixtures/style.json | 6 +++-- platform/node/test/fixtures/tiles.tilejson | 13 ---------- platform/node/test/js/consecutive.test.js | 2 +- platform/node/test/js/gzip.test.js | 9 ++++--- platform/node/test/js/map.test.js | 14 ++++------- platform/node/test/render.test.js | 2 +- scripts/main.mk | 16 ++++++------- 10 files changed, 40 insertions(+), 59 deletions(-) rename platform/node/binding.gyp => binding.gyp (74%) rename platform/node/package.json => package.json (84%) delete mode 100644 platform/node/test/fixtures/tiles.tilejson diff --git a/.gitignore b/.gitignore index 7613490aa..4ee833bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /mason_packages /config/*.gypi /build +/lib /macosx/build /linux/build /ios/build @@ -28,8 +29,6 @@ /src/shader/shaders_gl.cpp /src/shader/shaders_gles2.cpp /bin/style.bin.js -/platform/*.mk -/platform/*.Makefile *~ /ios/benchmark/assets/tiles/mapbox.mapbox-streets-v6/ diff --git a/platform/node/binding.gyp b/binding.gyp similarity index 74% rename from platform/node/binding.gyp rename to binding.gyp index 88ad5ed59..374ae1afd 100644 --- a/platform/node/binding.gyp +++ b/binding.gyp @@ -1,28 +1,28 @@ { 'includes': [ - '../../gyp/common.gypi', + 'gyp/common.gypi', ], 'targets': [ { 'target_name': '<(module_name)', 'dependencies': [ - '../../mbgl.gyp:core', - '../../mbgl.gyp:platform-<(platform_lib)', - '../../mbgl.gyp:headless-<(headless_lib)', + 'mbgl.gyp:core', + 'mbgl.gyp:platform-<(platform_lib)', + 'mbgl.gyp:headless-<(headless_lib)', ], 'include_dirs': [ " /dev/null && npm bin)/node-pre-gyp +NODE_PRE_GYP = $(shell npm bin)/node-pre-gyp node/configure: - cd platform/node && $(NODE_PRE_GYP) configure --clang -- \ - $(GYP_FLAGS) -I../../config/$(HOST_SLUG).gypi \ - -Dlibuv_ldflags= -Dlibuv_static_libs= + $(QUIET)$(ENV) $(NODE_PRE_GYP) configure --clang -- \ + $(GYP_FLAGS) -Dlibuv_ldflags= -Dlibuv_static_libs= Makefile/node: Makefile/__project__ node/configure @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target node...$(FORMAT_END)\n" - cd platform/node && $(NODE_PRE_GYP) build --clang -- \ - -j$(JOBS) + $(QUIET)$(ENV) $(NODE_PRE_GYP) build --clang -- \ + -j$(JOBS) Makefile/%: Makefile/__project__ @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target $*...$(FORMAT_END)\n" From 1c5fddab6c07f27ef5a56013061320d3212a4a3f Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 15:55:03 -0400 Subject: [PATCH 658/760] drop conditions from binding.gyp --- binding.gyp | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/binding.gyp b/binding.gyp index 374ae1afd..e9f0d6edc 100644 --- a/binding.gyp +++ b/binding.gyp @@ -24,48 +24,6 @@ 'platform/node/src/node_request.cpp', 'platform/node/src/util/async_queue.hpp', ], - - 'conditions': [ - ['OS=="mac"', { - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', - 'GCC_ENABLE_CPP_RTTI': 'YES', - 'OTHER_CPLUSPLUSFLAGS': [ - '-std=c++1y', - '-Werror', - '-Wall', - '-Wextra', - '-Wshadow', - '-Wno-variadic-macros', - '-Wno-error=unused-parameter', - '-frtti', - '-fexceptions', - ], - 'GCC_WARN_PEDANTIC': 'YES', - 'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE', - 'MACOSX_DEPLOYMENT_TARGET': '10.9', - }, - }, { - 'cflags_cc': [ - '-std=c++14', - '-Werror', - '-Wall', - '-Wextra', - '-Wno-variadic-macros', - '-Wno-error=unused-parameter', - '-frtti', - '-fexceptions', - ], - 'libraries': [ '<@(glfw_ldflags)' ], - }], - ['OS=="linux"', { - 'cflags_cc': [ - '-Wno-unknown-pragmas', # We are using '#pragma mark', but it is only available on Darwin. - ], - }], - ], }, { 'target_name': 'action_after_build', From 4694c7b51a4e4e18dcbf2d45cc087f125c581a91 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 16:08:46 -0400 Subject: [PATCH 659/760] fix example, clean up duplicate files --- .gitignore | 3 + platform/node/.clang-format | 18 ------ platform/node/.gitignore | 5 -- platform/node/CONTRIBUTING.md | 1 - platform/node/LICENSE.txt | 27 --------- platform/node/Makefile | 78 -------------------------- platform/node/examples/load-from-fs.js | 7 ++- 7 files changed, 7 insertions(+), 132 deletions(-) delete mode 100644 platform/node/.clang-format delete mode 100644 platform/node/.gitignore delete mode 100644 platform/node/CONTRIBUTING.md delete mode 100644 platform/node/LICENSE.txt delete mode 100644 platform/node/Makefile diff --git a/.gitignore b/.gitignore index 4ee833bcf..123bf8cba 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ /ios/benchmark/assets/tiles/mapbox.mapbox-streets-v6/ /ios/benchmark/assets/tiles/mapbox.mapbox-terrain-v2/ /ios/benchmark/assets/glyphs/DIN Offc Pro*/ + +/platform/node/test/actual +/platform/node/test/diff diff --git a/platform/node/.clang-format b/platform/node/.clang-format deleted file mode 100644 index 109b562b5..000000000 --- a/platform/node/.clang-format +++ /dev/null @@ -1,18 +0,0 @@ -Standard: Cpp11 -IndentWidth: 4 -AccessModifierOffset: -4 -UseTab: Never -BinPackParameters: true -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/platform/node/.gitignore b/platform/node/.gitignore deleted file mode 100644 index 2f53e5d17..000000000 --- a/platform/node/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build -lib -node_modules -test/actual -test/diff diff --git a/platform/node/CONTRIBUTING.md b/platform/node/CONTRIBUTING.md deleted file mode 100644 index 166929e33..000000000 --- a/platform/node/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -Please run [`clang-format`](http://clang.llvm.org/docs/ClangFormat.html) and verify that code formatting will remain consistent before submitting a pull request. diff --git a/platform/node/LICENSE.txt b/platform/node/LICENSE.txt deleted file mode 100644 index c9018a139..000000000 --- a/platform/node/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2014, Mapbox -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of [project] nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/platform/node/Makefile b/platform/node/Makefile deleted file mode 100644 index b17f66e26..000000000 --- a/platform/node/Makefile +++ /dev/null @@ -1,78 +0,0 @@ -BUILDTYPE ?= Release -MBGL ?= vendor/mbgl - -DEBUG_FLAG = -ifeq ($(BUILDTYPE), Debug) -DEBUG_FLAG = -d -endif - -ifeq ($(shell uname -s), Darwin) -HOST ?= osx -ifeq ($(JOBS),) -JOBS = $(shell sysctl -n hw.ncpu) -endif -endif -ifeq ($(shell uname -s), Linux) -HOST ?= linux -ifeq ($(JOBS),) -JOBS = $(shell nproc) -endif -endif - -NODE_PRE_GYP = $(shell which node-pre-gyp) -ifeq ($(NODE_PRE_GYP),) -NODE_PRE_GYP = $(shell npm bin)/node-pre-gyp -endif - -# Explicitly disable the default FileSource implementation -ASSET = none -HTTP = none -CACHE = none - -include $(MBGL)/config/defaults.mk - -global: build - -.PHONY: build -build: build/Makefile - @$(NODE_PRE_GYP) build $(DEBUG_FLAG) --clang -- -j$(JOBS) - -vendor/mbgl: - git submodule update --init - -GYP_SETTINGS = -Dmbgl=$(MBGL) \ - -Dhost=$(HOST) \ - -I$(MBGL)/config/$(HOST).gypi \ - $(LIBS_$(HOST)) \ - -Duv_static_libs= -Duv_ldflags= - -.PHONY: build/Makefile -build/Makefile: $(MBGL)/config/$(HOST).gypi - @$(NODE_PRE_GYP) configure --clang -- $(GYP_SETTINGS) -f make - -.PHONY: build/binding.xcodeproj -build/binding.xcodeproj: $(MBGL)/config/$(HOST).gypi - @$(NODE_PRE_GYP) configure --clang -- $(GYP_SETTINGS) -f xcode - -.PHONY: xproj -xproj: build/binding.xcodeproj - open build/binding.xcodeproj - -$(MBGL)/config/%.gypi: $(MBGL) $(MBGL)/configure - make -C $(MBGL) config/$*.gypi geojsonvt - -.PHONY: test-suite -test-suite: - -@(`npm bin`/tape test/render.test.js) - -.PHONY: test-js -test-js: - @(`npm bin`/tape test/js/**/*.test.js) - -.PHONY: test -test: test-js test-suite - -.PHONY: clean -clean: - rm -rf build lib - rm -f $(MBGL)/config/$(HOST).gypi diff --git a/platform/node/examples/load-from-fs.js b/platform/node/examples/load-from-fs.js index 352b01557..f8c072d4b 100755 --- a/platform/node/examples/load-from-fs.js +++ b/platform/node/examples/load-from-fs.js @@ -2,19 +2,20 @@ /* jshint node: true, unused: false */ 'use strict'; -var mbgl = require('..'); +var mbgl = require('../../..'); var fs = require('fs'); var path = require('path'); var PNG = require('pngjs').PNG; -var base = path.join(path.dirname(process.mainModule.filename), '../test'); +var base = path.join(__dirname, '../test'); var map = new mbgl.Map({ request: function(req) { fs.readFile(path.join(base, req.url), function(err, data) { req.respond(err, { data: data }); }); - } + }, + ratio: 1.0 }); map.load(require('../test/fixtures/style.json')); From 3d04da40a679dde25025e1d6da1f5cfa427b9ed1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 16:14:38 -0400 Subject: [PATCH 660/760] remove pushd/popd from scripts/node/run.sh, update license in package.json --- package.json | 4 ++-- scripts/node/run.sh | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0063cc989..145e80485 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "main": "lib/mapbox-gl-native.node", "repository": { "type": "git", - "url": "git://github.com/mapbox/node-mapbox-gl-native.git" + "url": "git://github.com/mapbox/mapbox-gl-native.git" }, "licenses": [ { "type": "BSD", - "url": "https://github.com/mapbox/node-mapbox-gl-native/blob/master/LICENSE.txt" + "url": "https://github.com/mapbox/mapbox-gl-native/blob/master/LICENSE.md" } ], "dependencies": { diff --git a/scripts/node/run.sh b/scripts/node/run.sh index b42452976..8566fc3b3 100755 --- a/scripts/node/run.sh +++ b/scripts/node/run.sh @@ -17,10 +17,8 @@ nvm use $NODE_VERSION mapbox_time "checkout_styles" \ git submodule update --init styles -pushd platform/node mapbox_time "compile_program" \ npm install --build-from-source -popd ################################################################################ # Test @@ -31,13 +29,11 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then mapbox_time "checkout_test_suite" \ git submodule update --init test/suite - pushd platform/node mapbox_time "run_tests" \ npm test mapbox_time "run_render_tests" \ npm run test-suite - popd if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then # Install and add awscli to PATH for uploading the results From 45eb197bd31bacacf9d1963512dd9a2bcc8ee56d Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 16:46:57 -0400 Subject: [PATCH 661/760] remove debug logging and whitespace [skip ci] --- scripts/main.mk | 2 +- src/mbgl/map/map.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/main.mk b/scripts/main.mk index c5319cf3d..65f283b21 100644 --- a/scripts/main.mk +++ b/scripts/main.mk @@ -70,7 +70,7 @@ config/%.gypi: $(SUBMODULES) configure $(CONFIGURE_FILES) #### Build files ############################################################### GYP_FLAGS += -Dhost=$(HOST) -GYP_FLAGS += -Iconfig/$(HOST_SLUG).gypi +GYP_FLAGS += -Iconfig/$(HOST_SLUG).gypi GYP_FLAGS += -Dplatform_lib=$(PLATFORM) GYP_FLAGS += -Dhttp_lib=$(HTTP) GYP_FLAGS += -Dasset_lib=$(ASSET) diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index bab1a4822..065c33ce5 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -9,7 +9,6 @@ #include #include -#include namespace mbgl { @@ -64,7 +63,6 @@ void Map::renderSync() { MapChangeDidFinishRenderingFrame); if (!fullyLoaded) { - std::cout << "RenderState::partial" << std::endl; renderState = RenderState::partial; } else if (renderState != RenderState::fully) { renderState = RenderState::fully; From b81d98b7a8c27626a557cbe1ce5b3347b7274a1a Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 17:14:15 -0400 Subject: [PATCH 662/760] Update README.md --- platform/node/README.md | 100 +++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 53 deletions(-) diff --git a/platform/node/README.md b/platform/node/README.md index 1b7d4b2d6..870151fd2 100644 --- a/platform/node/README.md +++ b/platform/node/README.md @@ -1,12 +1,7 @@ # node-mapbox-gl-native -Renders map tiles with [Mapbox GL](https://github.com/mapbox/mapbox-gl-native). - - [![NPM](https://nodei.co/npm/mapbox-gl-native.png)](https://npmjs.org/package/mapbox-gl-native) -[![Build Status](https://travis-ci.org/mapbox/node-mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/node-mapbox-gl-native) - ## Installing Requires a modern C++ runtime that supports C++14. @@ -22,7 +17,7 @@ Just run: npm install mapbox-gl-native ``` -Other platforms will fall back to a source compile with `npm run build`. To compile this module, make sure all submodules are initialized with `git submodule update --init` and install the [external dependencies required to build from source](https://github.com/mapbox/mapbox-gl-native#depends). +Other platforms will fall back to a source compile with `make node`. To compile this module, make sure all submodules are initialized with `git submodule update --init` and install the [external dependencies required to build from source](https://github.com/mapbox/mapbox-gl-native/blob/master/INSTALL.md#depends). ## Rendering a map tile @@ -44,13 +39,10 @@ The first argument passed to `map.render` is an options object, all keys are opt height: {height}, // number (px), defaults to 512 center: [{latitude}, {longitude}], // array of numbers (coordinates), defaults to [0,0] bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0 - ratio: {ratio} // number (scale factor), defaults to 1.0, - classes: {classes} // array of strings, optional + classes: {classes} // array of strings } ``` -_More about classes: https://github.com/mapbox/mapbox-gl-js/blob/master/API.md#working-with-style-classes_ - When you are finished using a map object, you can call `map.release()` to dispose the internal map resources manually. This is not necessary, but can be helpful to optimize resource usage (memory, file sockets) on a more granualar level than v8's garbage collector. ## Testing @@ -61,7 +53,7 @@ npm test ## Implementing a file source -When creating a `Map`, you must pass an options object (with a required `request` and optional `cancel` method) as the first parameter. +When creating a `Map`, you must pass an options object (with a required `ratio`, required `request` and optional `cancel` method) as the first parameter. ```js var map = new mbgl.Map({ @@ -70,11 +62,12 @@ var map = new mbgl.Map({ }, cancel: function(req) { // TODO - } + }, + ratio: 1.0 }); ``` -The `request()` method starts a new request to a file, while `cancel()` tells the FileSource to cancel the request (if possible). The `req` parameter has two properties: +The `request()` method starts a new request to a file, while `cancel()` tells the FileSource to cancel the request (if possible). The `ratio` sets the scale at which the map will render tiles, such as `2.0` for rendering images for high pixel density displays. The `req` parameter has two properties: ```json { @@ -83,7 +76,7 @@ The `request()` method starts a new request to a file, while `cancel()` tells th } ``` -The `kind` is an enum and defined in `mbgl.Resource`: +The `kind` is an enum and defined in [`mbgl.Resource`](https://github.com/mapbox/mapbox-gl-native/blob/node/include/mbgl/storage/resource.hpp): ```json { @@ -92,8 +85,8 @@ The `kind` is an enum and defined in `mbgl.Resource`: "Source": 2, "Tile": 3, "Glyphs": 4, - "JSON": 5, - "Image": 6 + "SpriteImage": 5, + "SpriteJSON": 6 } ``` @@ -107,7 +100,8 @@ var map = new mbgl.Map({ fs.readFile(path.join('base/path', req.url), function(err, data) { req.respond(err, { data: data }); }); - } + }, + ratio: 1.0 }); ``` @@ -135,10 +129,6 @@ var map = new mbgl.Map({ encoding: null, gzip: true }, function (err, res, body) { - if (req.canceled) { - return; - } - if (err) { req.respond(err); } else if (res.statusCode == 200) { @@ -147,20 +137,20 @@ var map = new mbgl.Map({ if (res.headers.modified) { response.modified = new Date(res.headers.modified); } if (res.headers.expires) { response.expires = new Date(res.headers.expires); } if (res.headers.etag) { response.etag = res.headers.etag; } + response.data = body; + req.respond(null, response); } else { req.respond(new Error(JSON.parse(body).message)); } }); }, - cancel: function(req) { - req.canceled = true; - } + ratio: 1.0 }); ``` -Note that in reality, Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of `request` supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3. +Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of `request` supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3. ## Mapbox API Access tokens @@ -171,37 +161,41 @@ var mbgl = require('mapbox-gl-native'); var request = require('request'); var url = require('url'); -var options = {}; -options.request = function(req) { - var opts = { - url: req.url, - encoding: null, - gzip: true - }; +var map = new mbgl.Map({ + request: function(req) { + var opts = { + url: req.url, + encoding: null, + gzip: true + }; - if (url.parse(req.url).protocol === 'mapbox:') { - opts.qs = { access_token: process.env.MAPBOX_ACCESS_TOKEN}; - } - - request(opts, function (err, res, body) { - if (err) { - req.respond(err); - } else if (res.statusCode == 200) { - var response = {}; - - if (res.headers.modified) { response.modified = new Date(res.headers.modified); } - if (res.headers.expires) { response.expires = new Date(res.headers.expires); } - if (res.headers.etag) { response.etag = res.headers.etag; } - response.data = body; - req.respond(null, response); - } else { - req.respond(new Error(JSON.parse(body).message)); + if (url.parse(req.url).protocol === 'mapbox:') { + opts.qs = { access_token: process.env.MAPBOX_ACCESS_TOKEN}; } - }); -} -var map = new mbgl.Map(options); -var style = mapboxStyle; // includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` + request(opts, function (err, res, body) { + if (err) { + req.respond(err); + } else if (res.statusCode == 200) { + var response = {}; + + if (res.headers.modified) { response.modified = new Date(res.headers.modified); } + if (res.headers.expires) { response.expires = new Date(res.headers.expires); } + if (res.headers.etag) { response.etag = res.headers.etag; } + + response.data = body; + + req.respond(null, response); + } else { + req.respond(new Error(JSON.parse(body).message)); + } + }); + }, + ratio: 1.0 +}); + +// includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` +var style = mapboxStyle; map.load(style); map.render({}, function(err, image) { From 0145f60dba93086e93d84b61ccc908b780063df3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 17:16:39 -0400 Subject: [PATCH 663/760] drop platform/node/examples, this is covered in README --- platform/node/examples/load-from-fs.js | 38 -------------------------- 1 file changed, 38 deletions(-) delete mode 100755 platform/node/examples/load-from-fs.js diff --git a/platform/node/examples/load-from-fs.js b/platform/node/examples/load-from-fs.js deleted file mode 100755 index f8c072d4b..000000000 --- a/platform/node/examples/load-from-fs.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node -/* jshint node: true, unused: false */ -'use strict'; - -var mbgl = require('../../..'); -var fs = require('fs'); -var path = require('path'); -var PNG = require('pngjs').PNG; - -var base = path.join(__dirname, '../test'); - -var map = new mbgl.Map({ - request: function(req) { - fs.readFile(path.join(base, req.url), function(err, data) { - req.respond(err, { data: data }); - }); - }, - ratio: 1.0 -}); - -map.load(require('../test/fixtures/style.json')); - -map.render({}, function(err, data) { - if (err) throw err; - - var png = new PNG({ - width: data.width, - height: data.height - }); - - png.data = data.pixels; - - png.pack() - .pipe(fs.createWriteStream('image.png')) - .on('finish', function() { - console.warn('Written image.png'); - }); -}); From 853da02101cbddef58c287d63637b98ccfd1bd05 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 1 Sep 2015 17:18:12 -0400 Subject: [PATCH 664/760] drop ratio from RenderOptions --- platform/node/src/node_map.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index a24f0c46b..6fd044dae 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -15,7 +15,6 @@ struct NodeMap::RenderOptions { double longitude = 0; unsigned int width = 512; unsigned int height = 512; - float ratio = 1.0f; std::vector classes; }; @@ -147,7 +146,6 @@ std::unique_ptr NodeMap::ParseOptions(v8::LocalHas(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(); From 224bca71b00f6a406a2f95f239a5af6c9df12ff3 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Tue, 1 Sep 2015 14:47:54 -0700 Subject: [PATCH 665/760] Bump to node-2.0.0-pre.1 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 145e80485..c8dd4c1f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "1.1.3", + "version": "2.0.0-pre.1", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 991b1a28fb777ec6d9c27f166bddd75f6dbafe43 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Wed, 2 Sep 2015 09:58:47 +1000 Subject: [PATCH 666/760] Fix crash when using MapView access token constructor Fixes #2197 --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index a36719592..301463c9b 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -200,15 +200,15 @@ public class MapView extends FrameLayout implements LocationListener { public MapView(Context context, @NonNull String accessToken) { super(context); - setAccessToken(accessToken); initialize(context, null); + setAccessToken(accessToken); } public MapView(Context context, @NonNull String accessToken, String styleUrl) { super(context); + initialize(context, null); setAccessToken(accessToken); setStyleUrl(styleUrl); - initialize(context, null); } // Called when properties are being set from XML From f772973847ae339936e22fe722aaafcc0b49d91b Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Mon, 31 Aug 2015 15:35:39 +1000 Subject: [PATCH 667/760] Tidy up missing JNI deletes --- android/cpp/jni.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index e077e318a..b3e9edd04 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -979,7 +979,7 @@ void JNICALL nativeRemoveAnnotations(JNIEnv *env, jobject obj, jlong nativeMapVi jlongArray JNICALL nativeGetAnnotationsInBounds(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject bbox) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAnnotationsInBounds"); - assert(nativeMapViewPtr != 0); + assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); if (env->ExceptionCheck() || (bbox == nullptr)) { @@ -1731,6 +1731,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { bboxClass = reinterpret_cast(env->NewGlobalRef(bboxClass)); if (bboxClass == nullptr) { env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngZoomClass); env->DeleteGlobalRef(bboxClass); return JNI_ERR; } @@ -1740,6 +1742,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); return JNI_ERR; } @@ -1748,6 +1751,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); return JNI_ERR; } @@ -1757,6 +1761,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(polylineClass); return JNI_ERR; @@ -1767,6 +1772,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); @@ -1779,6 +1785,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); @@ -1791,6 +1798,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); @@ -1804,6 +1812,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); @@ -1819,6 +1828,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); @@ -1834,6 +1844,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); @@ -1849,6 +1860,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); env->DeleteGlobalRef(polylineClass); env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); From f2eecb4d5e3f7e72ca1643569b2bc731855810c3 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 2 Sep 2015 12:33:45 -0400 Subject: [PATCH 668/760] set NODE_VERSION is after_script.sh --- scripts/node/after_script.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/node/after_script.sh b/scripts/node/after_script.sh index c757a1b8c..4a2717a3a 100755 --- a/scripts/node/after_script.sh +++ b/scripts/node/after_script.sh @@ -13,6 +13,9 @@ fi COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then + source ~/.nvm/nvm.sh + nvm use $NODE_VERSION + npm install aws-sdk ./node_modules/.bin/node-pre-gyp package From f2eb5bdaa19960c6be79716ce58e2428831059a5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 2 Sep 2015 12:34:49 -0400 Subject: [PATCH 669/760] node-v2.0.0-pre.2 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c8dd4c1f7..1957f7e6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "2.0.0-pre.1", + "version": "2.0.0-pre.2", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 9c1c40b3e57c2bdf0d8de8966c87c7fd431db3ff Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Sep 2015 17:00:02 -0400 Subject: [PATCH 670/760] Add iOS resources README, reminder to minimize image sizes Ref #2227 --- platform/ios/resources/README | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 platform/ios/resources/README diff --git a/platform/ios/resources/README b/platform/ios/resources/README new file mode 100644 index 000000000..34dedaeb0 --- /dev/null +++ b/platform/ios/resources/README @@ -0,0 +1,5 @@ +When adding new image resources, make sure that you've reduced the file size as much as possible using a tool such as: + +ImageOptim — https://imageoptim.com + +See also: https://github.com/mapbox/mapbox-gl-native/pull/2227 \ No newline at end of file From 6929dcec966c5c9eb2f2409fb4568f55253116f9 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Sep 2015 17:01:07 -0400 Subject: [PATCH 671/760] Add resources folder to iOS Xcode project, exclude README from .bundle --- gyp/platform-ios.gypi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 0f709d694..516d5e832 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -61,6 +61,7 @@ '../platform/ios/NSString+MGLAdditions.m', '../platform/ios/vendor/SMCalloutView/SMCalloutView.h', '../platform/ios/vendor/SMCalloutView/SMCalloutView.m', + '../platform/ios/resources/', ], 'variables': { @@ -105,7 +106,7 @@ '../include', ], 'mac_bundle_resources': [ - ' Date: Wed, 2 Sep 2015 13:21:56 -0400 Subject: [PATCH 672/760] source scripts/linux/setup.sh in scripts/node/after_script.sh to configure Mesa --- scripts/node/after_script.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/node/after_script.sh b/scripts/node/after_script.sh index 4a2717a3a..d78e820e9 100755 --- a/scripts/node/after_script.sh +++ b/scripts/node/after_script.sh @@ -27,6 +27,8 @@ if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then ./node_modules/.bin/node-pre-gyp publish info if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + source ./scripts/${TRAVIS_OS_NAME}/setup.sh + rm -rf build rm -rf lib npm install --fallback-to-build=false From 6634adee398c7e84c0892042751fa299546a9965 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 2 Sep 2015 13:22:33 -0400 Subject: [PATCH 673/760] node-v2.0.0-pre.3 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1957f7e6b..3795ca8f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "2.0.0-pre.2", + "version": "2.0.0-pre.3", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 12b566a9c1d2f4d573bcb9ae0827ec2067a8fe5b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 2 Sep 2015 16:00:13 -0400 Subject: [PATCH 674/760] remove node-mapbox-gl-native from 'products based on Mapbox GL' --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 57928618a..97f749121 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ An OpenGL ES-based renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blo If you want to use products _based on_ Mapbox GL, check out: - [Mapbox Mobile](http://mapbox.com/mobile) — iOS and Android native development libraries for vector maps in apps -- [`node-mapbox-gl-native`](https://github.com/mapbox/node-mapbox-gl-native) — Node.js library for vector map rendering - [`react-native-mapbox-gl`](https://github.com/mapbox/react-native-mapbox-gl`) — React Native library for vector maps in apps - [`qmapboxgl`](https://github.com/tmpsantos/qmapboxgl) — Qt library for vector map rendering From 13bcd109f0fb4ed3f2b180737f8b30ae2db64061 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Wed, 2 Sep 2015 16:12:27 -0400 Subject: [PATCH 675/760] update node changelog --- platform/node/CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index a86be910e..6d6486bd2 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.0.0 + +- Integrates Node.js bindings into core mapbox-gl-native project. +- Requires an options object argument to `new mbgl.Map()` + (with required `request` and optional `cancel` methods), + drops `mbgl.FileSource`. +- Requires numerical `ratio` in `mbgl.Map` options argument. + Map pixel ratio is now immutable and can no longer be set with + render options. +- Adds support for rendering v8 styles. +- No longer load resources before a render request is made. + # 1.1.3 - Removes deprecated mbgl::Environment from NodeLogObserver. From 227776dee04d440c0b6082def2bf5e40da65ecbc Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Sun, 30 Aug 2015 18:45:24 +0300 Subject: [PATCH 676/760] Style no longer requires mainloop access --- src/mbgl/map/map_context.cpp | 4 ++-- src/mbgl/style/style.cpp | 3 +-- src/mbgl/style/style.hpp | 4 +--- test/style/resource_loading.cpp | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 17d3a87cc..c36b4fc9d 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -104,7 +104,7 @@ void MapContext::setStyleURL(const std::string& url) { styleURL = url; styleJSON.clear(); - style = std::make_unique + + \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml index ac3b4fc30..af798515f 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - #1E8CAB + @color/mapbox_blue #166B83 #E55E5E #FFFFFF From e7398c16c133e74274ffeda4b5b2994816fcf121 Mon Sep 17 00:00:00 2001 From: tobrun Date: Sun, 6 Sep 2015 16:37:34 +0200 Subject: [PATCH 731/760] Added "Improve this map" functionality, current zoom level, center latitude and longitude will be parsed in to the url of improve this map. --- .../com/mapbox/mapboxgl/views/MapView.java | 22 ++++++++++++++----- .../src/main/res/values/arrays.xml | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 5a07365c4..71121b7aa 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -120,6 +120,8 @@ public class MapView extends FrameLayout implements LocationListener { private static final float DIMENSION_SIXTEEN_DP = 16f; private static final float DIMENSION_TEN_DP = 10f; + private static final int IMPROVE_THIS_MAP_INDEX = 2; + /** * Every annotation that has been added to the map. */ @@ -425,7 +427,7 @@ public class MapView extends FrameLayout implements LocationListener { LayoutParams attrParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mAttributionsView.setLayoutParams(attrParams); addView(mAttributionsView); - mAttributionsView.setOnClickListener(new AttributionOnClickListener()); + mAttributionsView.setOnClickListener(new AttributionOnClickListener(this)); // Setup Support For Listener Tracking // MapView's internal listener is setup in onCreate() @@ -2040,6 +2042,12 @@ public class MapView extends FrameLayout implements LocationListener { private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener { + private MapView mMapView; + + public AttributionOnClickListener(MapView mapView) { + mMapView = mapView; + } + // Called when someone presses the attribution icon @Override public void onClick(View v) { @@ -2051,13 +2059,17 @@ public class MapView extends FrameLayout implements LocationListener { builder.show(); } - // Called when someone selects an attribution + // Called when someone selects an attribution, 'Improve this map' adds location data to the url @Override public void onClick(DialogInterface dialog, int which) { - Context context = ((Dialog)dialog).getContext(); - String[]links = context.getResources().getStringArray(R.array.attribution_links); + Context context = ((Dialog) dialog).getContext(); + String url = context.getResources().getStringArray(R.array.attribution_links)[which]; + if (which == IMPROVE_THIS_MAP_INDEX) { + LatLng latLng = mMapView.getCenterCoordinate(); + url = String.format(url, latLng.getLongitude(), latLng.getLatitude(), (int) mMapView.getZoomLevel()); + } Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(links[which])); + intent.setData(Uri.parse(url)); context.startActivity(intent); } } diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml index 3e937722c..8a8e204b7 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml @@ -8,6 +8,6 @@ https://www.mapbox.com/about/maps/ http://www.openstreetmap.org/about/ - https://www.mapbox.com/map-feedback + https://www.mapbox.com/map-feedback/#/%1$f/%2$f/%3$d \ No newline at end of file From 36a754a1fe20afeedd931d2b8dcd68d4ff16191f Mon Sep 17 00:00:00 2001 From: tobrun Date: Sun, 6 Sep 2015 19:20:05 +0200 Subject: [PATCH 732/760] Using AlertDialog from AppCompat instead of SDKs version --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 71121b7aa..b78db1736 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1,7 +1,6 @@ package com.mapbox.mapboxgl.views; import android.app.ActivityManager; -import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -30,6 +29,7 @@ import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; +import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.view.GestureDetector; import android.view.Gravity; From 290a857b511f6500c8d376af78f5492c274c9bef Mon Sep 17 00:00:00 2001 From: tobrun Date: Wed, 9 Sep 2015 18:40:27 +0200 Subject: [PATCH 733/760] Moved icon to the right of Mapbox logo --- .../com/mapbox/mapboxgl/views/MapView.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index b78db1736..d50fac6ee 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -117,10 +117,12 @@ public class MapView extends FrameLayout implements LocationListener { private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom"; // Used for positioning views - private static final float DIMENSION_SIXTEEN_DP = 16f; + private static final float DIMENSION_SEVEN_DP = 7f; private static final float DIMENSION_TEN_DP = 10f; + private static final float DIMENSION_SIXTEEN_DP = 16f; + private static final float DIMENSION_SEVENTYSIX_DP = 76f; - private static final int IMPROVE_THIS_MAP_INDEX = 2; + private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; /** * Every annotation that has been added to the map. @@ -422,7 +424,11 @@ public class MapView extends FrameLayout implements LocationListener { // Setup Attributions control mAttributionsView = new ImageView(mContext); - mAttributionsView.setBackgroundResource(R.drawable.ic_info_selector); + mAttributionsView.setClickable(true); + mAttributionsView.setImageResource(R.drawable.ic_info_selector); + int attrPadding = (int) (DIMENSION_SEVEN_DP * mScreenDensity); + mAttributionsView.setPadding(attrPadding, attrPadding, attrPadding, attrPadding); + mAttributionsView.setAdjustViewBounds(true); mAttributionsView.setContentDescription(getResources().getString(R.string.attributionsIconContentDescription)); LayoutParams attrParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mAttributionsView.setLayoutParams(attrParams); @@ -478,11 +484,11 @@ public class MapView extends FrameLayout implements LocationListener { , typedArray.getDimension(R.styleable.MapView_logoMarginBottom, DIMENSION_SIXTEEN_DP)); // Attribution - setAttributionGravity(typedArray.getInt(R.styleable.MapView_attributionGravity, Gravity.BOTTOM | Gravity.END)); - setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attributionMarginLeft, DIMENSION_SIXTEEN_DP) - , typedArray.getDimension(R.styleable.MapView_attributionMarginTop, DIMENSION_SIXTEEN_DP) - , typedArray.getDimension(R.styleable.MapView_attributionMarginRight, DIMENSION_SIXTEEN_DP) - , typedArray.getDimension(R.styleable.MapView_attributionMarginBottom, DIMENSION_SIXTEEN_DP)); + setAttributionGravity(typedArray.getInt(R.styleable.MapView_attributionGravity, Gravity.BOTTOM)); + setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attributionMarginLeft, DIMENSION_SEVENTYSIX_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginTop, DIMENSION_SEVEN_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginRight, DIMENSION_SEVEN_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginBottom, DIMENSION_SEVEN_DP)); setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_myLocationEnabled, false)); } finally { @@ -2064,7 +2070,7 @@ public class MapView extends FrameLayout implements LocationListener { public void onClick(DialogInterface dialog, int which) { Context context = ((Dialog) dialog).getContext(); String url = context.getResources().getStringArray(R.array.attribution_links)[which]; - if (which == IMPROVE_THIS_MAP_INDEX) { + if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) { LatLng latLng = mMapView.getCenterCoordinate(); url = String.format(url, latLng.getLongitude(), latLng.getLatitude(), (int) mMapView.getZoomLevel()); } From d371dd07cbe7093996ba2bf16dd5c33d4037e9f7 Mon Sep 17 00:00:00 2001 From: tobrun Date: Wed, 9 Sep 2015 18:55:17 +0200 Subject: [PATCH 734/760] Changed title dialog --- .../java/MapboxGLAndroidSDK/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml index 455fdb026..be9a1d38c 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -1,10 +1,10 @@ Map compass. Click to reset the map rotation to North. + Attribution icon. Click to show attribution dialog. + Mapbox Android SDK + Mapbox logo. Title Description Address - Attribution icon. Click to show attribution dialog. - Mapbox for Android - Mapbox logo. From aa001f07c315d89a43ef1c894ca11af236d607a5 Mon Sep 17 00:00:00 2001 From: Brad Leege Date: Wed, 9 Sep 2015 15:15:23 -0500 Subject: [PATCH 735/760] #2293 - Removing Drawable --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 823424be7..664268141 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -14,7 +14,6 @@ import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.SurfaceTexture; -import android.graphics.drawable.Drawable; import android.hardware.GeomagneticField; import android.hardware.Sensor; import android.hardware.SensorEvent; From 0531b38367eb23b787276b9b6ef1f6992b4b3bc2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 3 Sep 2015 15:54:18 -0700 Subject: [PATCH 736/760] Nan 2.0 --- package.json | 2 +- platform/node/src/node_file_source.cpp | 49 ++---- platform/node/src/node_file_source.hpp | 12 +- platform/node/src/node_log.cpp | 39 +++-- platform/node/src/node_log.hpp | 5 +- platform/node/src/node_map.cpp | 180 ++++++++++---------- platform/node/src/node_map.hpp | 22 +-- platform/node/src/node_mapbox_gl_native.cpp | 67 +++++--- platform/node/src/node_request.cpp | 91 +++++----- platform/node/src/node_request.hpp | 14 +- 10 files changed, 239 insertions(+), 242 deletions(-) diff --git a/package.json b/package.json index 0fd6887ba..b8305af3e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ } ], "dependencies": { - "nan": "^1.9.0", + "nan": "^2.0.8", "node-pre-gyp": "^0.6.9" }, "bundledDependencies": [ diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp index 204e85a12..29a4748c5 100644 --- a/platform/node/src/node_file_source.cpp +++ b/platform/node/src/node_file_source.cpp @@ -11,7 +11,7 @@ struct NodeFileSource::Action { mbgl::Resource const resource; }; -NodeFileSource::NodeFileSource(v8::Handle options_) : +NodeFileSource::NodeFileSource(v8::Local options_) : queue(new Queue(uv_default_loop(), [this](Action &action) { if (action.type == Action::Add) { processAdd(action.resource); @@ -20,7 +20,7 @@ NodeFileSource::NodeFileSource(v8::Handle options_) : } })) { - NanAssignPersistent(options, options_->ToObject()); + options.Reset(options_); // Make sure that the queue doesn't block the loop from exiting. queue->unref(); @@ -30,7 +30,7 @@ NodeFileSource::~NodeFileSource() { queue->stop(); queue = nullptr; - NanDisposePersistent(options); + options.Reset(); } mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) { @@ -68,44 +68,30 @@ void NodeFileSource::cancel(mbgl::Request* req) { } void NodeFileSource::processAdd(const mbgl::Resource& resource) { - NanScope(); + Nan::HandleScope scope; // Make sure the loop stays alive as long as request is pending. if (pending.empty()) { queue->ref(); } - auto requestHandle = NanNew(NodeRequest::Create(this, resource)); - - v8::Persistent requestPersistent; - NanAssignPersistent(requestPersistent, requestHandle); - pending.emplace(resource, std::move(requestPersistent)); - -#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - auto requestFunction = v8::Local::New(v8::Isolate::GetCurrent(), options)->Get(NanNew("request")).As(); -#else - auto requestFunction = options->Get(NanNew("request")).As(); -#endif + auto requestHandle = Nan::New(NodeRequest::Create(this, resource)); + pending.emplace(resource, requestHandle); v8::Local argv[] = { requestHandle }; - NanMakeCallback(NanGetCurrentContext()->Global(), requestFunction, 1, argv); + Nan::MakeCallback(Nan::New(options), "request", 1, argv); } void NodeFileSource::processCancel(const mbgl::Resource& resource) { - NanScope(); + Nan::HandleScope scope; 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::New(v8::Isolate::GetCurrent(), it->second); + v8::Local requestHandle = Nan::New(it->second); it->second.Reset(); -#else - auto requestHandle = NanNew(it->second); - NanDisposePersistent(it->second); -#endif pending.erase(it); // Make sure the the loop can exit when there are no pending requests. @@ -113,20 +99,13 @@ void NodeFileSource::processCancel(const mbgl::Resource& resource) { queue->unref(); } -#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - auto optionsObject = v8::Local::New(v8::Isolate::GetCurrent(), options); - if (optionsObject->Has(NanNew("cancel"))) { - auto cancelFunction = optionsObject->Get(NanNew("cancel")).As(); -#else - if (options->Has(NanNew("cancel"))) { - auto cancelFunction = options->Get(NanNew("cancel")).As(); -#endif + if (Nan::Has(Nan::New(options), Nan::New("cancel").ToLocalChecked()).FromJust()) { v8::Local argv[] = { requestHandle }; - NanMakeCallback(NanGetCurrentContext()->Global(), cancelFunction, 1, argv); + Nan::MakeCallback(Nan::New(options), "cancel", 1, argv); } // Set the request handle in the request wrapper handle to null - node::ObjectWrap::Unwrap(requestHandle)->cancel(); + Nan::ObjectWrap::Unwrap(requestHandle)->cancel(); } } @@ -134,11 +113,7 @@ void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_pt // 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. diff --git a/platform/node/src/node_file_source.hpp b/platform/node/src/node_file_source.hpp index f412bdee1..038765cb2 100644 --- a/platform/node/src/node_file_source.hpp +++ b/platform/node/src/node_file_source.hpp @@ -5,8 +5,6 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" -#include -#include #include #pragma GCC diagnostic pop @@ -20,7 +18,7 @@ namespace util { template class AsyncQueue; } class NodeFileSource : public mbgl::FileSource { public: - NodeFileSource(v8::Handle); + NodeFileSource(v8::Local); ~NodeFileSource(); mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); @@ -36,14 +34,10 @@ private: void processAdd(const mbgl::Resource&); void processCancel(const mbgl::Resource&); - v8::Persistent options; + Nan::Persistent options; private: -#if (NODE_MODULE_VERSION > NODE_0_10_MODULE_VERSION) - std::unordered_map>, mbgl::Resource::Hash> pending; -#else - std::unordered_map, mbgl::Resource::Hash> pending; -#endif + std::unordered_map, mbgl::Resource::Hash> pending; // The observers list will hold pointers to all the requests waiting // for a particular resource. The access must be guarded by a mutex diff --git a/platform/node/src/node_log.cpp b/platform/node/src/node_log.cpp index 637534807..8230bb36c 100644 --- a/platform/node/src/node_log.cpp +++ b/platform/node/src/node_log.cpp @@ -16,27 +16,35 @@ struct NodeLogObserver::LogMessage { text(text_) {} }; -NodeLogObserver::NodeLogObserver(v8::Handle target) +NodeLogObserver::NodeLogObserver(v8::Local target) : queue(new Queue(uv_default_loop(), [this](LogMessage &message) { - NanScope(); + Nan::HandleScope scope; + + auto msg = Nan::New(); + + Nan::Set(msg, Nan::New("class").ToLocalChecked(), + Nan::New(mbgl::EventClass(message.event).c_str()).ToLocalChecked()); + + Nan::Set(msg, Nan::New("severity").ToLocalChecked(), + Nan::New(mbgl::EventSeverityClass(message.severity).c_str()).ToLocalChecked()); - auto msg = NanNew(); - 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(message.code)); - } - if (!message.text.empty()) { - msg->Set(NanNew("text"), NanNew(message.text)); + Nan::Set(msg, Nan::New("code").ToLocalChecked(), + Nan::New(message.code)); } - v8::Local argv[] = { NanNew("message"), msg }; - auto handle = NanNew(module); - auto emit = handle->Get(NanNew("emit"))->ToObject(); - emit->CallAsFunction(handle, 2, argv); + if (!message.text.empty()) { + Nan::Set(msg, Nan::New("text").ToLocalChecked(), + Nan::New(message.text).ToLocalChecked()); + } + + v8::Local argv[] = { Nan::New("message").ToLocalChecked(), msg }; + auto handle = Nan::New(module); + auto emit = Nan::Get(handle, Nan::New("emit").ToLocalChecked()).ToLocalChecked()->ToObject(); + Nan::CallAsFunction(emit, handle, 2, argv); })) { - NanScope(); - NanAssignPersistent(module, target); + Nan::HandleScope scope; + module.Reset(target); // Don't keep the event loop alive. queue->unref(); @@ -44,6 +52,7 @@ NodeLogObserver::NodeLogObserver(v8::Handle target) NodeLogObserver::~NodeLogObserver() { queue->stop(); + module.Reset(); } bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) { diff --git a/platform/node/src/node_log.hpp b/platform/node/src/node_log.hpp index 5c0048d26..d29e4e28e 100644 --- a/platform/node/src/node_log.hpp +++ b/platform/node/src/node_log.hpp @@ -5,7 +5,6 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" -#include #include #pragma GCC diagnostic pop @@ -15,14 +14,14 @@ namespace util { template class AsyncQueue; } class NodeLogObserver : public mbgl::Log::Observer { public: - NodeLogObserver(v8::Handle target); + NodeLogObserver(v8::Local 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 module; + Nan::Persistent module; struct LogMessage; using Queue = util::AsyncQueue; diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index b624e32e6..0133babc0 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -21,7 +21,7 @@ struct NodeMap::RenderOptions { //////////////////////////////////////////////////////////////////////////////////////////////// // Static Node Methods -v8::Persistent NodeMap::constructorTemplate; +Nan::Persistent NodeMap::constructor; static std::shared_ptr sharedDisplay() { static auto display = std::make_shared(); @@ -32,127 +32,137 @@ const static char* releasedMessage() { return "Map resources have already been released"; } -void NodeMap::Init(v8::Handle target) { - NanScope(); +NAN_MODULE_INIT(NodeMap::Init) { + v8::Local tpl = Nan::New(New); - v8::Local t = NanNew(New); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->SetClassName(Nan::New("Map").ToLocalChecked()); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(NanNew("Map")); + Nan::SetPrototypeMethod(tpl, "load", Load); + Nan::SetPrototypeMethod(tpl, "render", Render); + Nan::SetPrototypeMethod(tpl, "release", Release); - 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()); + constructor.Reset(tpl->GetFunction()); + Nan::Set(target, Nan::New("Map").ToLocalChecked(), tpl->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 (!info.IsConstructCall()) { + return Nan::ThrowTypeError("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"); + if (info.Length() < 1 || !info[0]->IsObject()) { + return Nan::ThrowTypeError("Requires an options object as first argument"); } - auto options = args[0]->ToObject(); + auto options = info[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 (!Nan::Has(options, Nan::New("request").ToLocalChecked()).FromJust() + || !Nan::Get(options, Nan::New("request").ToLocalChecked()).ToLocalChecked()->IsFunction()) { + return Nan::ThrowError("Options object must have a 'request' method"); } - if (!options->Has(NanNew("ratio")) || !options->Get(NanNew("ratio"))->IsNumber()) { - return NanThrowError("Options object must have a numerical 'ratio' property"); + if ( Nan::Has(options, Nan::New("cancel").ToLocalChecked()).FromJust() + && !Nan::Get(options, Nan::New("cancel").ToLocalChecked()).ToLocalChecked()->IsFunction()) { + return Nan::ThrowError("Options object 'cancel' property must be a function"); + } + + if (!Nan::Has(options, Nan::New("ratio").ToLocalChecked()).FromJust() + || !Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->IsNumber()) { + return Nan::ThrowError("Options object must have a numerical 'ratio' property"); } try { auto nodeMap = new NodeMap(options); - nodeMap->Wrap(args.This()); + nodeMap->Wrap(info.This()); } catch(std::exception &ex) { - return NanThrowError(ex.what()); + return Nan::ThrowError(ex.what()); } - NanReturnValue(args.This()); + info.GetReturnValue().Set(info.This()); } -const std::string StringifyStyle(v8::Handle styleHandle) { - NanScope(); +const std::string StringifyStyle(v8::Local styleHandle) { + Nan::HandleScope scope; - auto JSON = NanGetCurrentContext()->Global()->Get(NanNew("JSON"))->ToObject(); - auto stringify = v8::Handle::Cast(JSON->Get(NanNew("stringify"))); + v8::Local JSON = Nan::Get( + Nan::GetCurrentContext()->Global(), + Nan::New("JSON").ToLocalChecked()).ToLocalChecked()->ToObject(); - return *NanUtf8String(stringify->Call(JSON, 1, &styleHandle)); + return *Nan::Utf8String(Nan::MakeCallback(JSON, "stringify", 1, &styleHandle)); } NAN_METHOD(NodeMap::Load) { - NanScope(); + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - - if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); + if (!nodeMap->isValid()) return Nan::ThrowError(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"); + if (info.Length() < 1) { + return Nan::ThrowError("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]); + if (info[0]->IsObject()) { + style = StringifyStyle(info[0]); + } else if (info[0]->IsString()) { + style = *Nan::Utf8String(info[0]); } else { - return NanThrowTypeError("First argument must be a string or object"); + return Nan::ThrowTypeError("First argument must be a string or object"); } try { nodeMap->map->setStyleJSON(style, "."); } catch (const std::exception &ex) { - return NanThrowError(ex.what()); + return Nan::ThrowError(ex.what()); } nodeMap->loaded = true; - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); } std::unique_ptr NodeMap::ParseOptions(v8::Local obj) { - NanScope(); + Nan::HandleScope scope; auto options = std::make_unique(); - 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(); - if (center->Length() > 0) { options->latitude = center->Get(0)->NumberValue(); } - if (center->Length() > 1) { options->longitude = center->Get(1)->NumberValue(); } + if (Nan::Has(obj, Nan::New("zoom").ToLocalChecked()).FromJust()) { + options->zoom = Nan::Get(obj, Nan::New("zoom").ToLocalChecked()).ToLocalChecked()->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(); + if (Nan::Has(obj, Nan::New("bearing").ToLocalChecked()).FromJust()) { + options->bearing = Nan::Get(obj, Nan::New("bearing").ToLocalChecked()).ToLocalChecked()->NumberValue(); + } + + if (Nan::Has(obj, Nan::New("center").ToLocalChecked()).FromJust()) { + auto center = Nan::Get(obj, Nan::New("center").ToLocalChecked()).ToLocalChecked().As(); + if (center->Length() > 0) { options->latitude = Nan::Get(center, 0).ToLocalChecked()->NumberValue(); } + if (center->Length() > 1) { options->longitude = Nan::Get(center, 1).ToLocalChecked()->NumberValue(); } + } + + if (Nan::Has(obj, Nan::New("width").ToLocalChecked()).FromJust()) { + options->width = Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()->IntegerValue(); + } + + if (Nan::Has(obj, Nan::New("height").ToLocalChecked()).FromJust()) { + options->height = Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()->IntegerValue(); + } + + if (Nan::Has(obj, Nan::New("classes").ToLocalChecked()).FromJust()) { + auto classes = Nan::Get(obj, Nan::New("classes").ToLocalChecked()).ToLocalChecked()->ToObject().As(); 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()) }); + options->classes.push_back(std::string { *Nan::Utf8String(Nan::Get(classes, i).ToLocalChecked()->ToString()) }); } } @@ -160,37 +170,35 @@ std::unique_ptr NodeMap::ParseOptions(v8::Local(info.Holder()); - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); + if (!nodeMap->isValid()) return Nan::ThrowError(releasedMessage()); - if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); - - if (args.Length() <= 0 || !args[0]->IsObject()) { - return NanThrowTypeError("First argument must be an options object"); + if (info.Length() <= 0 || !info[0]->IsObject()) { + return Nan::ThrowTypeError("First argument must be an options object"); } - if (args.Length() <= 1 || !args[1]->IsFunction()) { - return NanThrowTypeError("Second argument must be a callback function"); + if (info.Length() <= 1 || !info[1]->IsFunction()) { + return Nan::ThrowTypeError("Second argument must be a callback function"); } if (!nodeMap->isLoaded()) { - return NanThrowTypeError("Style is not loaded"); + return Nan::ThrowTypeError("Style is not loaded"); } - auto options = ParseOptions(args[0]->ToObject()); + auto options = ParseOptions(info[0]->ToObject()); assert(!nodeMap->callback); assert(!nodeMap->image); - nodeMap->callback = std::unique_ptr(new NanCallback(args[1].As())); + nodeMap->callback = std::make_unique(info[1].As()); try { nodeMap->startRender(std::move(options)); } catch (mbgl::util::Exception &ex) { - return NanThrowError(ex.what()); + return Nan::ThrowError(ex.what()); } - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); } void NodeMap::startRender(std::unique_ptr options) { @@ -221,7 +229,7 @@ void NodeMap::startRender(std::unique_ptr options) { } void NodeMap::renderFinished() { - NanScope(); + Nan::HandleScope scope; // We're done with this render call, so we're unrefing so that the loop could close. uv_unref(reinterpret_cast(async)); @@ -249,7 +257,7 @@ void NodeMap::renderFinished() { } v8::Local argv[] = { - NanError(errorMessage.c_str()) + Nan::Error(errorMessage.c_str()) }; // This must be empty to be prepared for the next render call. @@ -258,7 +266,7 @@ void NodeMap::renderFinished() { cb->Call(1, argv); } else if (img) { - v8::Local pixels = NanNewBufferHandle( + v8::Local pixels = Nan::NewBuffer( reinterpret_cast(img->pixels.get()), size_t(img->width) * size_t(img->height) * sizeof(mbgl::StillImage::Pixel), @@ -267,36 +275,34 @@ void NodeMap::renderFinished() { delete reinterpret_cast(hint); }, const_cast(img.get()) - ); + ).ToLocalChecked(); img.release(); v8::Local argv[] = { - NanNull(), + Nan::Null(), pixels, }; cb->Call(2, argv); } else { v8::Local argv[] = { - NanError("Didn't get an image") + Nan::Error("Didn't get an image") }; cb->Call(1, argv); } } NAN_METHOD(NodeMap::Release) { - NanScope(); + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); - auto nodeMap = node::ObjectWrap::Unwrap(args.Holder()); - - if (!nodeMap->isValid()) return NanThrowError(releasedMessage()); + if (!nodeMap->isValid()) return Nan::ThrowError(releasedMessage()); try { nodeMap->release(); } catch (const std::exception &ex) { - return NanThrowError(ex.what()); + return Nan::ThrowError(ex.what()); } - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); } void NodeMap::release() { @@ -315,8 +321,8 @@ void NodeMap::release() { //////////////////////////////////////////////////////////////////////////////////////////////// // Instance -NodeMap::NodeMap(v8::Handle options) : - view(sharedDisplay(), options->Get(NanNew("ratio"))->NumberValue()), +NodeMap::NodeMap(v8::Local options) : + view(sharedDisplay(), Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->NumberValue()), fs(options), map(std::make_unique(view, fs, mbgl::MapMode::Still)), async(new uv_async_t) { diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp index 2c87900d4..90edf44f7 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -8,7 +8,6 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" -#include #include #pragma GCC diagnostic pop @@ -16,14 +15,13 @@ namespace node_mbgl { -class NodeMap : public node::ObjectWrap { +class NodeMap : public Nan::ObjectWrap { +public: struct RenderOptions; class RenderWorker; - //////////////////////////////////////////////////////////////////////////////////////////////// - // Static Node Methods -public: - static void Init(v8::Handle target); + static NAN_MODULE_INIT(Init); + static NAN_METHOD(New); static NAN_METHOD(Load); static NAN_METHOD(Render); @@ -37,24 +35,20 @@ public: inline bool isLoaded() { return loaded; } inline bool isValid() { return valid; } - static std::unique_ptr ParseOptions(v8::Local obj); + static std::unique_ptr ParseOptions(v8::Local); + static Nan::Persistent constructor; - static v8::Persistent constructorTemplate; - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Instance private: - NodeMap(v8::Handle); + NodeMap(v8::Local); ~NodeMap(); -private: mbgl::HeadlessView view; NodeFileSource fs; std::unique_ptr map; std::exception_ptr error; std::unique_ptr image; - std::unique_ptr callback; + std::unique_ptr callback; // Async for delivering the notifications of render completion. uv_async_t *async; diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp index 14a0c9ad2..b631b32c5 100644 --- a/platform/node/src/node_mapbox_gl_native.cpp +++ b/platform/node/src/node_mapbox_gl_native.cpp @@ -9,30 +9,57 @@ #include "node_log.hpp" #include "node_request.hpp" -void RegisterModule(v8::Handle exports) { - NanScope(); - - node_mbgl::NodeMap::Init(exports); - node_mbgl::NodeRequest::Init(exports); +NAN_MODULE_INIT(RegisterModule) { + node_mbgl::NodeMap::Init(target); + node_mbgl::NodeRequest::Init(target); // Exports Resource constants. - auto ConstantProperty = static_cast(v8::ReadOnly | v8::DontDelete); - auto resource = NanNew(); - 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); + v8::Local resource = Nan::New(); - // 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"))); + Nan::Set(resource, + Nan::New("Unknown").ToLocalChecked(), + Nan::New(mbgl::Resource::Unknown)); - mbgl::Log::setObserver(std::make_unique(exports)); + Nan::Set(resource, + Nan::New("Style").ToLocalChecked(), + Nan::New(mbgl::Resource::Style)); + + Nan::Set(resource, + Nan::New("Source").ToLocalChecked(), + Nan::New(mbgl::Resource::Source)); + + Nan::Set(resource, + Nan::New("Tile").ToLocalChecked(), + Nan::New(mbgl::Resource::Tile)); + + Nan::Set(resource, + Nan::New("Glyphs").ToLocalChecked(), + Nan::New(mbgl::Resource::Glyphs)); + + Nan::Set(resource, + Nan::New("SpriteImage").ToLocalChecked(), + Nan::New(mbgl::Resource::SpriteImage)); + + Nan::Set(resource, + Nan::New("SpriteJSON").ToLocalChecked(), + Nan::New(mbgl::Resource::SpriteJSON)); + + Nan::Set(target, + Nan::New("Resource").ToLocalChecked(), + resource); + + // Make the exported object inherit from process.EventEmitter + v8::Local process = Nan::Get( + Nan::GetCurrentContext()->Global(), + Nan::New("process").ToLocalChecked()).ToLocalChecked()->ToObject(); + + v8::Local EventEmitter = Nan::Get(process, + Nan::New("EventEmitter").ToLocalChecked()).ToLocalChecked()->ToObject(); + + Nan::SetPrototype(target, + Nan::Get(EventEmitter, Nan::New("prototype").ToLocalChecked()).ToLocalChecked()); + + mbgl::Log::setObserver(std::make_unique(Nan::New(target))); } NODE_MODULE(mapbox_gl_native, RegisterModule) diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp index ea9fc4d73..09d7bf798 100644 --- a/platform/node/src/node_request.cpp +++ b/platform/node/src/node_request.cpp @@ -11,112 +11,111 @@ namespace node_mbgl { //////////////////////////////////////////////////////////////////////////////////////////////// // Static Node Methods -v8::Persistent NodeRequest::constructorTemplate; +Nan::Persistent NodeRequest::constructor; -void NodeRequest::Init(v8::Handle target) { - NanScope(); +NAN_MODULE_INIT(NodeRequest::Init) { + v8::Local tpl = Nan::New(New); - v8::Local t = NanNew(New); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->SetClassName(Nan::New("Request").ToLocalChecked()); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(NanNew("Request")); + Nan::SetPrototypeMethod(tpl, "respond", Respond); - NODE_SET_PROTOTYPE_METHOD(t, "respond", Respond); - - NanAssignPersistent(constructorTemplate, t); - - target->Set(NanNew("Request"), t->GetFunction()); + constructor.Reset(tpl->GetFunction()); + Nan::Set(target, Nan::New("Request").ToLocalChecked(), tpl->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"); + if (info.Length() < 2 || !info[0]->IsExternal() || !info[1]->IsExternal()) { + return Nan::ThrowTypeError("Cannot create Request objects explicitly"); } - auto source = reinterpret_cast(args[0].As()->Value()); - auto resource = reinterpret_cast(args[1].As()->Value()); + auto source = reinterpret_cast(info[0].As()->Value()); + auto resource = reinterpret_cast(info[1].As()->Value()); auto req = new NodeRequest(source, *resource); - req->Wrap(args.This()); + req->Wrap(info.This()); - NanReturnValue(args.This()); + info.GetReturnValue().Set(info.This()); } v8::Handle NodeRequest::Create(NodeFileSource* source, const mbgl::Resource& resource) { - NanEscapableScope(); + Nan::EscapableHandleScope scope; - v8::Local argv[] = { NanNew(const_cast(source)), - NanNew(const_cast(&resource)) }; - auto instance = NanNew(constructorTemplate)->GetFunction()->NewInstance(2, argv); + v8::Local argv[] = { + Nan::New(const_cast(source)), + Nan::New(const_cast(&resource)) + }; + auto instance = Nan::New(constructor)->NewInstance(2, argv); - instance->ForceSet(NanNew("url"), NanNew(resource.url), v8::ReadOnly); - instance->ForceSet(NanNew("kind"), NanNew(int(resource.kind)), v8::ReadOnly); + Nan::Set(instance, Nan::New("url").ToLocalChecked(), Nan::New(resource.url).ToLocalChecked()); + Nan::Set(instance, Nan::New("kind").ToLocalChecked(), Nan::New(int(resource.kind))); - return NanEscapeScope(instance); + return scope.Escape(instance); } NAN_METHOD(NodeRequest::Respond) { - auto nodeRequest = ObjectWrap::Unwrap(args.Holder()); + auto nodeRequest = Nan::ObjectWrap::Unwrap(info.Holder()); // Request has already been responded to, or was canceled, fail silently. - if (!nodeRequest->resource) NanReturnUndefined(); + if (!nodeRequest->resource) { + return info.GetReturnValue().SetUndefined(); + } 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()) { + if (info.Length() < 1) { + return Nan::ThrowTypeError("First argument must be an error object"); + } else if (info[0]->BooleanValue()) { auto response = std::make_shared(); response->status = mbgl::Response::Error; // Store the error string. - const NanUtf8String message { args[0]->ToString() }; + const Nan::Utf8String message { info[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 if (info.Length() < 2 || !info[1]->IsObject()) { + return Nan::ThrowTypeError("Second argument must be a response object"); } else { auto response = std::make_shared(); - auto res = args[1]->ToObject(); + auto res = info[1]->ToObject(); response->status = mbgl::Response::Successful; - if (res->Has(NanNew("modified"))) { - const double modified = res->Get(NanNew("modified"))->ToNumber()->Value(); + if (Nan::Has(res, Nan::New("modified").ToLocalChecked()).FromJust()) { + const double modified = Nan::Get(res, Nan::New("modified").ToLocalChecked()).ToLocalChecked()->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 (Nan::Has(res, Nan::New("expires").ToLocalChecked()).FromJust()) { + const double expires = Nan::Get(res, Nan::New("expires").ToLocalChecked()).ToLocalChecked()->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 (Nan::Has(res, Nan::New("etag").ToLocalChecked()).FromJust()) { + auto etagHandle = Nan::Get(res, Nan::New("etag").ToLocalChecked()).ToLocalChecked(); if (etagHandle->BooleanValue()) { - const NanUtf8String etag { etagHandle->ToString() }; + const Nan::Utf8String etag { etagHandle->ToString() }; response->etag = std::string { *etag, size_t(etag.length()) }; } } - if (res->Has(NanNew("data"))) { - auto dataHandle = res->Get(NanNew("data")); + if (Nan::Has(res, Nan::New("data").ToLocalChecked()).FromJust()) { + auto dataHandle = Nan::Get(res, Nan::New("data").ToLocalChecked()).ToLocalChecked(); 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"); + return Nan::ThrowTypeError("Response data must be a Buffer"); } } @@ -124,7 +123,7 @@ NAN_METHOD(NodeRequest::Respond) { source->notify(*resource, response); } - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/platform/node/src/node_request.hpp b/platform/node/src/node_request.hpp index a690904ef..35bb8e520 100644 --- a/platform/node/src/node_request.hpp +++ b/platform/node/src/node_request.hpp @@ -3,7 +3,6 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" -#include #include #pragma GCC diagnostic pop @@ -15,21 +14,16 @@ namespace node_mbgl { class NodeFileSource; -class NodeRequest : public node::ObjectWrap { - //////////////////////////////////////////////////////////////////////////////////////////////// - // Static Node Methods +class NodeRequest : public Nan::ObjectWrap { public: - static void Init(v8::Handle target); + static NAN_MODULE_INIT(Init); + static NAN_METHOD(New); static NAN_METHOD(Respond); static v8::Handle Create(NodeFileSource*, const mbgl::Resource&); + static Nan::Persistent constructor; - static v8::Persistent constructorTemplate; - - //////////////////////////////////////////////////////////////////////////////////////////////// - // Instance -public: NodeRequest(NodeFileSource* source, const mbgl::Resource& resource); ~NodeRequest(); From 627623edbcc0fa8812bfc345b3e338eff229626b Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 8 Sep 2015 17:14:39 -0400 Subject: [PATCH 737/760] Use nan with https://github.com/nodejs/nan/pull/443 fixed --- package.json | 2 +- platform/node/src/node_map.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b8305af3e..4284fccfd 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ } ], "dependencies": { - "nan": "^2.0.8", + "nan": "nodejs/nan#a71301e0f62877ca017c4ebec7fbac9f4359038e", "node-pre-gyp": "^0.6.9" }, "bundledDependencies": [ diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 0133babc0..121265f4f 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -280,7 +280,7 @@ void NodeMap::renderFinished() { v8::Local argv[] = { Nan::Null(), - pixels, + pixels }; cb->Call(2, argv); } else { From 3d48147f2f8d2ef3ed3a9b8848ba0304d27c3403 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Tue, 8 Sep 2015 15:17:49 -0400 Subject: [PATCH 738/760] add io.js v3.x support --- .travis.yml | 4 ++-- platform/node/CHANGELOG.md | 1 + platform/node/src/node_file_source.cpp | 2 +- platform/node/src/node_mapbox_gl_native.cpp | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2e300c568..d16643364 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ matrix: apt: packages: [ 'lib32stdc++6' ] - os: linux - env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=iojs-v2 + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=iojs-v3 addons: apt: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] @@ -79,7 +79,7 @@ matrix: - os: osx osx_image: xcode6.4 compiler: clang - env: FLAVOR=node NODE_VERSION=iojs-v2 + env: FLAVOR=node NODE_VERSION=iojs-v3 - os: osx osx_image: xcode6.4 compiler: clang diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 6d6486bd2..927cc5ece 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -9,6 +9,7 @@ render options. - Adds support for rendering v8 styles. - No longer load resources before a render request is made. +- Adds io.js v3.x support. # 1.1.3 diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp index 29a4748c5..622584846 100644 --- a/platform/node/src/node_file_source.cpp +++ b/platform/node/src/node_file_source.cpp @@ -75,7 +75,7 @@ void NodeFileSource::processAdd(const mbgl::Resource& resource) { queue->ref(); } - auto requestHandle = Nan::New(NodeRequest::Create(this, resource)); + v8::Local requestHandle = NodeRequest::Create(this, resource)->ToObject(); pending.emplace(resource, requestHandle); v8::Local argv[] = { requestHandle }; diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp index b631b32c5..c0fb47d14 100644 --- a/platform/node/src/node_mapbox_gl_native.cpp +++ b/platform/node/src/node_mapbox_gl_native.cpp @@ -59,7 +59,7 @@ NAN_MODULE_INIT(RegisterModule) { Nan::SetPrototype(target, Nan::Get(EventEmitter, Nan::New("prototype").ToLocalChecked()).ToLocalChecked()); - mbgl::Log::setObserver(std::make_unique(Nan::New(target))); + mbgl::Log::setObserver(std::make_unique(target->ToObject())); } NODE_MODULE(mapbox_gl_native, RegisterModule) From 532bbd54845d78749983b9c5410f506d7dc64a66 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 9 Sep 2015 15:06:54 -0700 Subject: [PATCH 739/760] Add anonymous lambda for HandleScope --- platform/node/src/node_map.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 121265f4f..5b2a83916 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -322,7 +322,10 @@ void NodeMap::release() { // Instance NodeMap::NodeMap(v8::Local options) : - view(sharedDisplay(), Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->NumberValue()), + view(sharedDisplay(), [&] { + Nan::HandleScope scope; + return Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->NumberValue(); + }()), fs(options), map(std::make_unique(view, fs, mbgl::MapMode::Still)), async(new uv_async_t) { From 1296f91c828b9a9a7b0d261637c15e3ba7db3868 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 10 Sep 2015 15:54:06 -0400 Subject: [PATCH 740/760] [node] change request semantics Passes a second, callback argument to the request implementation instead of needing to call req.respond. --- package.json | 2 +- platform/node/CHANGELOG.md | 4 +++- platform/node/README.md | 24 ++++++++++++------------ platform/node/src/node_file_source.cpp | 9 ++++++--- platform/node/src/node_request.cpp | 4 +--- platform/node/test/js/map.test.js | 4 ++-- platform/node/test/render.test.js | 4 ++-- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 4284fccfd..8491a4851 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ } ], "dependencies": { - "nan": "nodejs/nan#a71301e0f62877ca017c4ebec7fbac9f4359038e", + "nan": "^2.0.9", "node-pre-gyp": "^0.6.9" }, "bundledDependencies": [ diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 927cc5ece..dfa5ad33e 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -4,11 +4,13 @@ - Requires an options object argument to `new mbgl.Map()` (with required `request` and optional `cancel` methods), drops `mbgl.FileSource`. +- Changes `request` semantics to pass a second, callback argument instead + of needing to call `req.respond`. - Requires numerical `ratio` in `mbgl.Map` options argument. Map pixel ratio is now immutable and can no longer be set with render options. - Adds support for rendering v8 styles. -- No longer load resources before a render request is made. +- No longer loads resources before a render request is made. - Adds io.js v3.x support. # 1.1.3 diff --git a/platform/node/README.md b/platform/node/README.md index 72a08e853..dfe5d909f 100644 --- a/platform/node/README.md +++ b/platform/node/README.md @@ -92,22 +92,22 @@ The `kind` is an enum and defined in [`mbgl.Resource`](https://github.com/mapbox It has no significance for anything but serves as a hint to your implemention as to what sort of resource to expect. E.g., your implementation could choose caching strategies based on the expected file type. -THe `request` implementation should pass uncompressed data to `req.respond`. If you are downloading assets from a source that applies gzip transport encoding, the implementation must decompress the results before passing them on. +The `request` implementation should pass uncompressed data to `callback`. If you are downloading assets from a source that applies gzip transport encoding, the implementation must decompress the results before passing them on. A sample implementation that reads files from disk would look like the following: ```js var map = new mbgl.Map({ - request: function(req) { + request: function(req, callback) { fs.readFile(path.join('base/path', req.url), function(err, data) { - req.respond(err, { data: data }); + callback(err, { data: data }); }); }, ratio: 1.0 }); ``` -This is a very barebones implementation and you'll probably want a better implementation. E.g. it passes the url verbatim to the file system, but you'd want add some logic that normalizes `http` URLs. You'll notice that once your implementation has obtained the requested file, you have to deliver it to the requestee by calling `req.respond()`, which takes either an error object or `null` and an object with several settings: +This is a very barebones implementation and you'll probably want a better implementation. E.g. it passes the url verbatim to the file system, but you'd want add some logic that normalizes `http` URLs. You'll notice that once your implementation has obtained the requested file, you have to deliver it to the requestee by calling `callback()`, which takes either an error object or `null` and an object with several settings: ```js { @@ -125,14 +125,14 @@ var mbgl = require('mapbox-gl-native'); var request = require('request'); var map = new mbgl.Map({ - request: function(req) { + request: function(req, callback) { request({ url: req.url, encoding: null, gzip: true }, function (err, res, body) { if (err) { - req.respond(err); + callback(err); } else if (res.statusCode == 200) { var response = {}; @@ -142,9 +142,9 @@ var map = new mbgl.Map({ response.data = body; - req.respond(null, response); + callback(null, response); } else { - req.respond(new Error(JSON.parse(body).message)); + callback(new Error(JSON.parse(body).message)); } }); }, @@ -164,7 +164,7 @@ var request = require('request'); var url = require('url'); var map = new mbgl.Map({ - request: function(req) { + request: function(req, callback) { var opts = { url: req.url, encoding: null, @@ -177,7 +177,7 @@ var map = new mbgl.Map({ request(opts, function (err, res, body) { if (err) { - req.respond(err); + callback(err); } else if (res.statusCode == 200) { var response = {}; @@ -187,9 +187,9 @@ var map = new mbgl.Map({ response.data = body; - req.respond(null, response); + callback(null, response); } else { - req.respond(new Error(JSON.parse(body).message)); + callback(new Error(JSON.parse(body).message)); } }); }, diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp index 622584846..b4828e768 100644 --- a/platform/node/src/node_file_source.cpp +++ b/platform/node/src/node_file_source.cpp @@ -75,11 +75,14 @@ void NodeFileSource::processAdd(const mbgl::Resource& resource) { queue->ref(); } - v8::Local requestHandle = NodeRequest::Create(this, resource)->ToObject(); + auto requestHandle = NodeRequest::Create(this, resource)->ToObject(); pending.emplace(resource, requestHandle); - v8::Local argv[] = { requestHandle }; - Nan::MakeCallback(Nan::New(options), "request", 1, argv); + auto callback = Nan::GetFunction(Nan::New(NodeRequest::Respond, requestHandle)).ToLocalChecked(); + callback->SetName(Nan::New("respond").ToLocalChecked()); + + v8::Local argv[] = { requestHandle, callback }; + Nan::MakeCallback(Nan::New(options), "request", 2, argv); } void NodeFileSource::processCancel(const mbgl::Resource& resource) { diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp index 09d7bf798..76cafacbd 100644 --- a/platform/node/src/node_request.cpp +++ b/platform/node/src/node_request.cpp @@ -19,8 +19,6 @@ NAN_MODULE_INIT(NodeRequest::Init) { tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("Request").ToLocalChecked()); - Nan::SetPrototypeMethod(tpl, "respond", Respond); - constructor.Reset(tpl->GetFunction()); Nan::Set(target, Nan::New("Request").ToLocalChecked(), tpl->GetFunction()); } @@ -55,7 +53,7 @@ v8::Handle NodeRequest::Create(NodeFileSource* source, const mbgl::R } NAN_METHOD(NodeRequest::Respond) { - auto nodeRequest = Nan::ObjectWrap::Unwrap(info.Holder()); + auto nodeRequest = Nan::ObjectWrap::Unwrap(info.Data().As()); // Request has already been responded to, or was canceled, fail silently. if (!nodeRequest->resource) { diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index b3934e4fd..295fec693 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -147,9 +147,9 @@ test('Map', function(t) { t.test('.render', function(t) { var options = { - request: function(req) { + request: function(req, callback) { fs.readFile(path.join(__dirname, '..', req.url), function(err, data) { - req.respond(err, { data: data }); + callback(err, { data: data }); }); }, ratio: 1 diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index 2050677e9..c36600157 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -13,9 +13,9 @@ if (process.argv[1] === __filename && process.argv.length > 2) { suite.run('native', {tests: tests}, function (style, options, callback) { var map = new mbgl.Map({ ratio: options.pixelRatio, - request: function (req) { + request: function(req, callback) { request(req.url, {encoding: null}, function (err, response, body) { - req.respond(err, {data: body}); + callback(err, {data: body}); }); } }); From f6648f585e40bda82613c65dcfc03d4618f15007 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 10 Sep 2015 16:49:26 -0400 Subject: [PATCH 741/760] node-v2.0.0-pre.4 [publish binary] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8491a4851..33ffb3288 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapbox-gl-native", - "version": "2.0.0-pre.3", + "version": "2.0.0-pre.4", "description": "Renders map tiles with Mapbox GL", "keywords": [ "mapbox", From 5a5a5ef4f05169ac3147f15f3063649f4e587a20 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 10 Sep 2015 19:28:47 -0400 Subject: [PATCH 742/760] [node] update CHANGELOG.md [skip ci] --- platform/node/CHANGELOG.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index dfa5ad33e..6a5f5f2dd 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,14 +1,16 @@ # 2.0.0 -- Integrates Node.js bindings into core mapbox-gl-native project. +- Integrates Node.js bindings into core mapbox-gl-native project. (#2179) - Requires an options object argument to `new mbgl.Map()` (with required `request` and optional `cancel` methods), - drops `mbgl.FileSource`. + drops `mbgl.FileSource`. (https://github.com/mapbox/node-mapbox-gl-native/pull/143) - Changes `request` semantics to pass a second, callback argument instead - of needing to call `req.respond`. + of needing to call `req.respond`. (#2299) - Requires numerical `ratio` in `mbgl.Map` options argument. Map pixel ratio is now immutable and can no longer be set with - render options. + render options. (https://github.com/mapbox/mapbox-gl-native/commit/a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6, #1799) +- `map.render` now returns a raw image buffer instead of an object with + `width`, `height` and `pixels` properties. (#2262) - Adds support for rendering v8 styles. - No longer loads resources before a render request is made. - Adds io.js v3.x support. From 7cc0ef019a2b9681b110c4968fcba8b16159d6e8 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 10 Sep 2015 19:33:07 -0400 Subject: [PATCH 743/760] [node] update platform/node/CHANGELOG.md [skip ci] --- platform/node/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 6a5f5f2dd..0fb8f3d38 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -3,12 +3,12 @@ - Integrates Node.js bindings into core mapbox-gl-native project. (#2179) - Requires an options object argument to `new mbgl.Map()` (with required `request` and optional `cancel` methods), - drops `mbgl.FileSource`. (https://github.com/mapbox/node-mapbox-gl-native/pull/143) + drops `mbgl.FileSource`. (mapbox/node-mapbox-gl-native#143) - Changes `request` semantics to pass a second, callback argument instead of needing to call `req.respond`. (#2299) - Requires numerical `ratio` in `mbgl.Map` options argument. Map pixel ratio is now immutable and can no longer be set with - render options. (https://github.com/mapbox/mapbox-gl-native/commit/a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6, #1799) + render options. (a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6, #1799) - `map.render` now returns a raw image buffer instead of an object with `width`, `height` and `pixels` properties. (#2262) - Adds support for rendering v8 styles. From f9f43fd1506b9bc7d45ffd55fb788ff7672a45b5 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Thu, 10 Sep 2015 19:40:16 -0400 Subject: [PATCH 744/760] [node] fix links in CHANGELOG.md [skip ci] --- platform/node/CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 0fb8f3d38..158d4958e 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,19 +1,19 @@ # 2.0.0 -- Integrates Node.js bindings into core mapbox-gl-native project. (#2179) +- Integrates Node.js bindings into core mapbox-gl-native project. ([#2179](https://github.com/mapbox/mapbox-gl-native/pull/2179)) - Requires an options object argument to `new mbgl.Map()` (with required `request` and optional `cancel` methods), - drops `mbgl.FileSource`. (mapbox/node-mapbox-gl-native#143) + drops `mbgl.FileSource`. ([mapbox/node-mapbox-gl-native#143](https://github.com/mapbox/node-mapbox-gl-native/pull/143)) - Changes `request` semantics to pass a second, callback argument instead - of needing to call `req.respond`. (#2299) + of needing to call `req.respond`. ([#2299](https://github.com/mapbox/mapbox-gl-native/pull/2299)) - Requires numerical `ratio` in `mbgl.Map` options argument. Map pixel ratio is now immutable and can no longer be set with - render options. (a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6, #1799) + render options. ([`a8d9b92`](https://github.com/mapbox/mapbox-gl-native/commit/a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6), [#1799](https://github.com/mapbox/mapbox-gl-native/pull/1799)) - `map.render` now returns a raw image buffer instead of an object with - `width`, `height` and `pixels` properties. (#2262) + `width`, `height` and `pixels` properties. ([#2262](https://github.com/mapbox/mapbox-gl-native/pull/2262)) - Adds support for rendering v8 styles. - No longer loads resources before a render request is made. -- Adds io.js v3.x support. +- Adds io.js v3.x support. ([#2261](https://github.com/mapbox/mapbox-gl-native/pull/2261)) # 1.1.3 From a9b163fadcf24d7e9754044e10789f42969c7638 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 11 Sep 2015 11:34:04 +1000 Subject: [PATCH 745/760] Call cancelTransitions when setting map position, zoom level, or direction. Fixes # 2296 --- .../src/main/java/com/mapbox/mapboxgl/views/MapView.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index 664268141..614ccdb11 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -628,6 +628,7 @@ public class MapView extends FrameLayout implements LocationListener { public void setCenterCoordinate(LatLng centerCoordinate, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setLatLng(centerCoordinate, duration); } @@ -638,6 +639,7 @@ public class MapView extends FrameLayout implements LocationListener { public void setCenterCoordinate(LatLngZoom centerCoordinate, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setLatLngZoom(centerCoordinate, duration); } @@ -660,14 +662,17 @@ public class MapView extends FrameLayout implements LocationListener { public void setDirection(double direction, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setBearing(-direction, duration); } public void resetPosition() { + mNativeMapView.cancelTransitions(); mNativeMapView.resetPosition(); } public void resetNorth() { + mNativeMapView.cancelTransitions(); mNativeMapView.resetNorth(); } @@ -681,6 +686,7 @@ public class MapView extends FrameLayout implements LocationListener { public void setZoomLevel(double zoomLevel, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setZoom(zoomLevel, duration); } From 344a7b2ffdd449642ce2a78b0d438611dd0773dd Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 11 Sep 2015 12:12:03 +1000 Subject: [PATCH 746/760] Update to Android build tools 23.0.1 Closes #2303 --- .travis.yml | 8 ++++---- android/java/MapboxGLAndroidSDK/gradle.properties | 2 +- android/java/MapboxGLAndroidSDKTestApp/build.gradle | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index d16643364..f3a830849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,28 +33,28 @@ matrix: - os: linux env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Release android: - components: [ 'build-tools-23', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Debug android: - components: [ 'build-tools-23', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=arm-v8 BUILDTYPE=Release android: - components: [ 'build-tools-23', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=x86 BUILDTYPE=Release android: - components: [ 'build-tools-23', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] diff --git a/android/java/MapboxGLAndroidSDK/gradle.properties b/android/java/MapboxGLAndroidSDK/gradle.properties index 5d862987b..680aad376 100644 --- a/android/java/MapboxGLAndroidSDK/gradle.properties +++ b/android/java/MapboxGLAndroidSDK/gradle.properties @@ -14,7 +14,7 @@ POM_DEVELOPER_NAME=Mapbox ANDROID_MIN_SDK=15 ANDROID_BUILD_TARGET_SDK_VERSION=23 -ANDROID_BUILD_TOOLS_VERSION=23 +ANDROID_BUILD_TOOLS_VERSION=23.0.1 ANDROID_BUILD_SDK_VERSION=23 POM_NAME=Mapbox GL Android SDK diff --git a/android/java/MapboxGLAndroidSDKTestApp/build.gradle b/android/java/MapboxGLAndroidSDKTestApp/build.gradle index 84f627d9e..172f31d0f 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/build.gradle +++ b/android/java/MapboxGLAndroidSDKTestApp/build.gradle @@ -39,7 +39,7 @@ gradle.projectsEvaluated { android { compileSdkVersion 23 - buildToolsVersion "23" + buildToolsVersion "23.0.1" repositories { mavenCentral() From cdafaf49cf7a87cea9b4a72f6c8c4326ca7b03da Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Fri, 11 Sep 2015 15:47:28 +1000 Subject: [PATCH 747/760] Add missing support libs --- .../src/main/res/layout/activity_main.xml | 2 -- .../MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 2d22dcce7..0e569b770 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -53,8 +53,6 @@ android:layout_gravity="end|bottom" android:layout_margin="@dimen/fab_margin" app:backgroundTint="@color/white" - app:borderWidth="0dp" - app:elevation="0dp" /> diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml index af798515f..ac3b4fc30 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - @color/mapbox_blue + #1E8CAB #166B83 #E55E5E #FFFFFF From 169f580519367105ca626f158964910295ee73c1 Mon Sep 17 00:00:00 2001 From: Mike Morris Date: Fri, 11 Sep 2015 14:16:42 -0400 Subject: [PATCH 748/760] [node] update platform/node/CHANGELOG.md [skip ci] --- platform/node/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 158d4958e..d4b0abd49 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -12,7 +12,7 @@ - `map.render` now returns a raw image buffer instead of an object with `width`, `height` and `pixels` properties. ([#2262](https://github.com/mapbox/mapbox-gl-native/pull/2262)) - Adds support for rendering v8 styles. -- No longer loads resources before a render request is made. +- No longer loads resources before a render request is made. ([`55d25a8`](https://github.com/mapbox/mapbox-gl-native/commit/55d25a80a77c06ef5e66acc0d8518867b03fe8a4)) - Adds io.js v3.x support. ([#2261](https://github.com/mapbox/mapbox-gl-native/pull/2261)) # 1.1.3 From c8e80595d854c79f4c16ef4237f7a4ca9d18ddd9 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Tue, 1 Sep 2015 23:30:54 -0400 Subject: [PATCH 749/760] [iOS] Initial user location puck --- platform/ios/MGLUserLocationAnnotationView.m | 480 ++++++++++++------- 1 file changed, 296 insertions(+), 184 deletions(-) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 7733674e6..301e3c4b2 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -8,6 +8,9 @@ const CGFloat MGLUserLocationAnnotationDotSize = 22.0; const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; +const CGFloat MGLUserLocationAnnotationPuckSize = 35.0; +const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6; + @interface MGLUserLocationAnnotationView () @property (nonatomic, readwrite) CALayer *haloLayer; @@ -18,12 +21,17 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; @implementation MGLUserLocationAnnotationView { + BOOL _puckModeActivated; + + CALayer *_puckDot; + CAShapeLayer *_puckArrow; + CALayer *_headingIndicatorLayer; CAShapeLayer *_headingIndicatorMaskLayer; CALayer *_accuracyRingLayer; CALayer *_dotBorderLayer; CALayer *_dotLayer; - + double _oldHeadingAccuracy; CLLocationAccuracy _oldHorizontalAccuracy; double _oldZoom; @@ -37,7 +45,9 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; - (instancetype)initInMapView:(MGLMapView *)mapView { - if (self = [super initWithFrame:CGRectMake(0, 0, MGLUserLocationAnnotationDotSize, MGLUserLocationAnnotationDotSize)]) + CGFloat frameSize = (mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? MGLUserLocationAnnotationPuckSize : MGLUserLocationAnnotationDotSize; + + if (self = [super initWithFrame:CGRectMake(0, 0, frameSize, frameSize)]) { self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView]; _mapView = mapView; @@ -55,189 +65,291 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; - (void)setTintColor:(UIColor *)tintColor { - if (_accuracyRingLayer) + if (_puckModeActivated) { - _accuracyRingLayer.backgroundColor = [tintColor CGColor]; + _puckArrow.fillColor = [tintColor CGColor]; + } + else + { + if (_accuracyRingLayer) + { + _accuracyRingLayer.backgroundColor = [tintColor CGColor]; + } + + _haloLayer.backgroundColor = [tintColor CGColor]; + _dotLayer.backgroundColor = [tintColor CGColor]; + + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; } - - _haloLayer.backgroundColor = [tintColor CGColor]; - _dotLayer.backgroundColor = [tintColor CGColor]; - - _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; } - (void)setupLayers { if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) { - // update heading indicator - // - if (_headingIndicatorLayer) - { - _headingIndicatorLayer.hidden = !(_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading || - _mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse); - - if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy) - { - // recalculate the clipping mask based on updated accuracy - _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; + } +} - _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; - } - } - - // heading indicator (tinted, semi-circle) - // - if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy) +- (void)drawPuck +{ + if ( ! _puckModeActivated) + { + self.layer.sublayers = nil; + + _headingIndicatorLayer = nil; + _headingIndicatorMaskLayer = nil; + _accuracyRingLayer = nil; + _haloLayer = nil; + _dotBorderLayer = nil; + _dotLayer = nil; + + _puckModeActivated = YES; + } + + // background dot (white with black shadow) + // + if ( ! _puckDot) + { + _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize]; + _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; + _puckDot.shadowColor = [[UIColor blackColor] CGColor]; + _puckDot.shadowOffset = CGSizeMake(0, 1); + _puckDot.shadowRadius = 1; + _puckDot.shadowOpacity = 0.1; + + [self.layer addSublayer:_puckDot]; + } + + // arrow + // + if ( ! _puckArrow) + { + _puckArrow = [CAShapeLayer layer]; + _puckArrow.path = [[self puckArrow] CGPath]; + _puckArrow.fillColor = [_mapView.tintColor CGColor]; + _puckArrow.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationArrowSize, MGLUserLocationAnnotationArrowSize); + _puckArrow.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _puckArrow.shouldRasterize = YES; + _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; + _puckArrow.drawsAsynchronously = YES; + + [self.layer addSublayer:_puckArrow]; + } + + if (self.mapView.pitch) + { + CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.pitch), 1.0, 0, 0); + self.layer.sublayerTransform = t; + } +} + +CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) +{ + return degrees * M_PI / 180; +} + +CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) +{ + return radians * 180 / M_PI; +} + +- (UIBezierPath *)puckArrow +{ + CGFloat max = MGLUserLocationAnnotationArrowSize; + + UIBezierPath *bezierPath = UIBezierPath.bezierPath; + [bezierPath moveToPoint: CGPointMake(max * 0.5, 0)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.1, max)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.5, max * 0.65)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.9, max)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.5, 0)]; + [bezierPath closePath]; + + return bezierPath; +} + +- (void)drawDot +{ + if (_puckModeActivated) + { + self.layer.sublayers = nil; + + _puckDot = nil; + _puckArrow = nil; + + _puckModeActivated = NO; + + self.layer.sublayerTransform = CATransform3DIdentity; + } + + // update heading indicator + // + if (_headingIndicatorLayer) + { + _headingIndicatorLayer.hidden = !(_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading || + _mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse); + + if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy) { - CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize; - - _headingIndicatorLayer = [CALayer layer]; - _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize); - _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); - _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; - _headingIndicatorLayer.contentsGravity = kCAGravityBottom; - _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale; - _headingIndicatorLayer.opacity = 0.4; - _headingIndicatorLayer.shouldRasterize = YES; - _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale; - _headingIndicatorLayer.drawsAsynchronously = YES; - - [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; - } - - // heading indicator accuracy mask (fan-shaped) - // - if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy) - { - _headingIndicatorMaskLayer = [CAShapeLayer layer]; - _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds; + // recalculate the clipping mask based on updated accuracy _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; - // apply the mask to the halo-radius-sized gradient layer - _headingIndicatorLayer.mask = _headingIndicatorMaskLayer; - _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; } + } + + // heading indicator (tinted, semi-circle) + // + if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy) + { + CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize; + + _headingIndicatorLayer = [CALayer layer]; + _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize); + _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; + _headingIndicatorLayer.contentsGravity = kCAGravityBottom; + _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.opacity = 0.4; + _headingIndicatorLayer.shouldRasterize = YES; + _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.drawsAsynchronously = YES; + + [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; + } + + // heading indicator accuracy mask (fan-shaped) + // + if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy) + { + _headingIndicatorMaskLayer = [CAShapeLayer layer]; + _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds; + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + // apply the mask to the halo-radius-sized gradient layer + _headingIndicatorLayer.mask = _headingIndicatorMaskLayer; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + + // update accuracy ring (if zoom or horizontal accuracy have changed) + // + if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy)) + { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; - // update accuracy ring (if zoom or horizontal accuracy have changed) - // - if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy)) + // only show the accuracy ring if it won't be obscured by the location dot + if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15) { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; - - // only show the accuracy ring if it won't be obscured by the location dot - if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15) - { - _accuracyRingLayer.hidden = NO; - _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); - _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; - - // match the halo to the accuracy ring - _haloLayer.bounds = _accuracyRingLayer.bounds; - _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; - _haloLayer.shouldRasterize = NO; - } - else - { - _accuracyRingLayer.hidden = YES; - - _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); - _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0; - _haloLayer.shouldRasterize = YES; - _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; - } - - // store accuracy and zoom so we're not redrawing unchanged location updates - _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy; - _oldZoom = self.mapView.zoomLevel; + _accuracyRingLayer.hidden = NO; + _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; + + // match the halo to the accuracy ring + _haloLayer.bounds = _accuracyRingLayer.bounds; + _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; + _haloLayer.shouldRasterize = NO; } + else + { + _accuracyRingLayer.hidden = YES; + + _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0; + _haloLayer.shouldRasterize = YES; + _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // accuracy ring (circular, tinted, mostly-transparent) + // + if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) + { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; + _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor]; + _accuracyRingLayer.opacity = 0.1; + _accuracyRingLayer.shouldRasterize = NO; + _accuracyRingLayer.allowsGroupOpacity = NO; - // accuracy ring (circular, tinted, mostly-transparent) - // - if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) - { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; - _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; - _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor]; - _accuracyRingLayer.opacity = 0.1; - _accuracyRingLayer.shouldRasterize = NO; - _accuracyRingLayer.allowsGroupOpacity = NO; - - [self.layer addSublayer:_accuracyRingLayer]; - } + [self.layer addSublayer:_accuracyRingLayer]; + } + + // expanding sonar-like pulse (circular, tinted, fades out) + // + if ( ! _haloLayer) + { + _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; + _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; + _haloLayer.allowsGroupOpacity = NO; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; + + // scale out radially with initial acceleration + CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; + boundsAnimation.values = @[@0, @0.35, @1]; + boundsAnimation.keyTimes = @[@0, @0.2, @1]; + + // go transparent as scaled out, start semi-opaque + CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.values = @[@0.4, @0.4, @0]; + opacityAnimation.keyTimes = @[@0, @0.2, @1]; + + animationGroup.animations = @[boundsAnimation, opacityAnimation]; + + [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_haloLayer]; + } + + // background dot (white with black shadow) + // + if ( ! _dotBorderLayer) + { + _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; + _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + _dotBorderLayer.shadowOpacity = 0.25; - // expanding sonar-like pulse (circular, tinted, fades out) - // - if ( ! _haloLayer) - { - _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; - _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; - _haloLayer.allowsGroupOpacity = NO; - - // set defaults for the animations - CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; - - // scale out radially with initial acceleration - CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; - boundsAnimation.values = @[@0, @0.35, @1]; - boundsAnimation.keyTimes = @[@0, @0.2, @1]; - - // go transparent as scaled out, start semi-opaque - CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.values = @[@0.4, @0.4, @0]; - opacityAnimation.keyTimes = @[@0, @0.2, @1]; - - animationGroup.animations = @[boundsAnimation, opacityAnimation]; - - [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; - - [self.layer addSublayer:_haloLayer]; - } - - // background dot (white with black shadow) - // - if ( ! _dotBorderLayer) - { - _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; - _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; - _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; - _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); - _dotBorderLayer.shadowRadius = 3; - _dotBorderLayer.shadowOpacity = 0.25; - - [self.layer addSublayer:_dotBorderLayer]; - } - - // inner dot (pulsing, tinted) - // - if ( ! _dotLayer) - { - _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; - _dotLayer.backgroundColor = [_mapView.tintColor CGColor]; - _dotLayer.shouldRasterize = NO; - - // set defaults for the animations - CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; - animationGroup.autoreverses = YES; - animationGroup.fillMode = kCAFillModeBoth; - - // scale the dot up and down - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.8; - pulseAnimation.toValue = @1; - - // fade opacity in and out, subtly - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.fromValue = @0.8; - opacityAnimation.toValue = @1; - - animationGroup.animations = @[pulseAnimation, opacityAnimation]; - - [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; - - [self.layer addSublayer:_dotLayer]; - } + [self.layer addSublayer:_dotBorderLayer]; + } + + // inner dot (pulsing, tinted) + // + if ( ! _dotLayer) + { + _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; + _dotLayer.backgroundColor = [_mapView.tintColor CGColor]; + _dotLayer.shouldRasterize = NO; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; + animationGroup.autoreverses = YES; + animationGroup.fillMode = kCAFillModeBoth; + + // scale the dot up and down + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + + // fade opacity in and out, subtly + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_dotLayer]; } } @@ -250,7 +362,7 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; circleLayer.shouldRasterize = YES; circleLayer.rasterizationScale = [UIScreen mainScreen].scale; circleLayer.drawsAsynchronously = YES; - + return circleLayer; } @@ -261,7 +373,7 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; animationGroup.repeatCount = INFINITY; animationGroup.removedOnCompletion = NO; animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - + return animationGroup; } @@ -269,65 +381,65 @@ const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; { CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f; CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude]; - + return pixelRadius * 2; } - (UIImage *)headingIndicatorTintedGradientImage { UIImage *image; - + CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0; - + UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0); - + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = UIGraphicsGetCurrentContext(); - + // gradient from the tint color to no-alpha tint color CGFloat gradientLocations[] = {0.0, 1.0}; CGGradientRef gradient = CGGradientCreateWithColors( - colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor], - (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations); - + colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor], + (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations); + // draw the gradient from the center point to the edge (full halo radius) CGPoint centerPoint = CGPointMake(haloRadius, haloRadius); CGContextDrawRadialGradient(context, gradient, centerPoint, 0.0, centerPoint, haloRadius, nil); - + image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - + CGGradientRelease(gradient); CGColorSpaceRelease(colorSpace); - + return image; } - (UIBezierPath *)headingIndicatorClippingMask { CGFloat accuracy = self.annotation.heading.headingAccuracy; - + // size the mask using exagerated accuracy, but keep within a good display range CGFloat clippingDegrees = 90 - (accuracy * 1.5); clippingDegrees = fmin(clippingDegrees, 55); clippingDegrees = fmax(clippingDegrees, 10); - + CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); UIBezierPath *ovalPath = UIBezierPath.bezierPath; - + // clip the oval to ± incoming accuracy degrees (converted to radians), from the top [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) radius:CGRectGetWidth(ovalRect) / 2.0 startAngle:(-180 + clippingDegrees) * M_PI / 180 endAngle:-clippingDegrees * M_PI / 180 clockwise:YES]; - + [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))]; [ovalPath closePath]; - + return ovalPath; } From c34d62aca29f207bbd68b48ee13063c9e4fed82a Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 4 Sep 2015 00:00:28 -0400 Subject: [PATCH 750/760] [iOS] Move radians/degrees conversion functions to MGLGeometry.h --- include/mbgl/ios/MGLGeometry.h | 11 +++++++++++ platform/ios/MGLMapView.mm | 10 ---------- platform/ios/MGLUserLocationAnnotationView.m | 10 ---------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/include/mbgl/ios/MGLGeometry.h b/include/mbgl/ios/MGLGeometry.h index 6226af1d0..2dee7c76c 100644 --- a/include/mbgl/ios/MGLGeometry.h +++ b/include/mbgl/ios/MGLGeometry.h @@ -3,6 +3,7 @@ #import "MGLTypes.h" #import +#import NS_ASSUME_NONNULL_BEGIN @@ -82,4 +83,14 @@ NS_INLINE NSString *MGLStringFromCoordinateBounds(MGLCoordinateBounds bounds) { bounds.ne.latitude, bounds.ne.longitude]; } +NS_INLINE CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) +{ + return degrees * M_PI / 180; +} + +NS_INLINE CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) +{ + return radians * 180 / M_PI; +} + NS_ASSUME_NONNULL_END diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index e509b5c8d..705627663 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -60,16 +60,6 @@ static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) return [NSURL URLWithString:[NSString stringWithFormat:@"asset://styles/%@.json", styleName]]; } -CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) -{ - return degrees * M_PI / 180; -} - -CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) -{ - return radians * 180 / M_PI; -} - mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { if ( ! function) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 301e3c4b2..06da8a998 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -144,16 +144,6 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck } } -CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) -{ - return degrees * M_PI / 180; -} - -CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) -{ - return radians * 180 / M_PI; -} - - (UIBezierPath *)puckArrow { CGFloat max = MGLUserLocationAnnotationArrowSize; From 2a300bd425cfed1a8494a0efd4790976ae2f5aa5 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 4 Sep 2015 00:41:43 -0400 Subject: [PATCH 751/760] =?UTF-8?q?[iOS]=20Pitch=20the=20dot,=20add=20faux?= =?UTF-8?q?=203D=20effect=20to=20tilted=20dot=20and=20puck=20This=20expose?= =?UTF-8?q?s=20the=20na=C3=AFvet=C3=A9=20of=20the=20current=20pitch=20tran?= =?UTF-8?q?sform:=20accuracy=20ring=20sizing=20stays=20constant,=20despite?= =?UTF-8?q?=20vertical=20screen=20movement.=20Still,=20it=20looks=20better?= =?UTF-8?q?=20pitched=20than=20not.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platform/ios/MGLUserLocationAnnotationView.m | 88 ++++++++++++++++---- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 06da8a998..22c1d00f1 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -8,7 +8,7 @@ const CGFloat MGLUserLocationAnnotationDotSize = 22.0; const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; -const CGFloat MGLUserLocationAnnotationPuckSize = 35.0; +const CGFloat MGLUserLocationAnnotationPuckSize = 45.0; const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6; @interface MGLUserLocationAnnotationView () @@ -35,6 +35,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck double _oldHeadingAccuracy; CLLocationAccuracy _oldHorizontalAccuracy; double _oldZoom; + double _oldPitch; } - (instancetype)initWithFrame:(CGRect)frame @@ -88,6 +89,42 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) { (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; + [self updatePitch]; + } +} + +- (void)updatePitch +{ + if (self.mapView.camera.pitch && (self.mapView.camera.pitch != _oldPitch)) + { + CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0); + self.layer.sublayerTransform = t; + + [self updateFaux3DEffect]; + + _oldPitch = self.mapView.camera.pitch; + } +} + +- (void)updateFaux3DEffect +{ + CGFloat pitch = MGLRadiansFromDegrees(self.mapView.camera.pitch); + + if (_puckDot) + { + _puckDot.shadowOffset = CGSizeMake(0, fmaxf(pitch * 10.f, 1.f)); + _puckDot.shadowRadius = fmaxf(pitch * 5.f, 0.75f); + } + + if (_dotBorderLayer) + { + _dotBorderLayer.shadowOffset = CGSizeMake(0.f, pitch * 10.f); + _dotBorderLayer.shadowRadius = fmaxf(pitch * 5.f, 3.f); + } + + if (_dotLayer) + { + _dotLayer.zPosition = pitch * 2.f; } } @@ -103,8 +140,6 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck _haloLayer = nil; _dotBorderLayer = nil; _dotLayer = nil; - - _puckModeActivated = YES; } // background dot (white with black shadow) @@ -114,9 +149,17 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize]; _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; _puckDot.shadowColor = [[UIColor blackColor] CGColor]; - _puckDot.shadowOffset = CGSizeMake(0, 1); - _puckDot.shadowRadius = 1; - _puckDot.shadowOpacity = 0.1; + _puckDot.shadowOpacity = 0.25; + + if (self.mapView.camera.pitch) + { + [self updateFaux3DEffect]; + } + else + { + _puckDot.shadowOffset = CGSizeMake(0, 1); + _puckDot.shadowRadius = 0.75; + } [self.layer addSublayer:_puckDot]; } @@ -137,10 +180,11 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck [self.layer addSublayer:_puckArrow]; } - if (self.mapView.pitch) + if ( ! _puckModeActivated) { - CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.pitch), 1.0, 0, 0); - self.layer.sublayerTransform = t; + _puckModeActivated = YES; + + [self updateFaux3DEffect]; } } @@ -167,10 +211,6 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck _puckDot = nil; _puckArrow = nil; - - _puckModeActivated = NO; - - self.layer.sublayerTransform = CATransform3DIdentity; } // update heading indicator @@ -277,6 +317,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; _haloLayer.allowsGroupOpacity = NO; + _haloLayer.zPosition = -0.1f; // set defaults for the animations CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; @@ -305,10 +346,18 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; - _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); - _dotBorderLayer.shadowRadius = 3; _dotBorderLayer.shadowOpacity = 0.25; - + + if (self.mapView.camera.pitch) + { + [self updateFaux3DEffect]; + } + else + { + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + } + [self.layer addSublayer:_dotBorderLayer]; } @@ -341,6 +390,13 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck [self.layer addSublayer:_dotLayer]; } + + if (_puckModeActivated) + { + _puckModeActivated = NO; + + [self updateFaux3DEffect]; + } } - (CALayer *)circleLayerWithSize:(CGFloat)layerSize From 7782802f3741ddff15ae9753ca484d13543a65cd Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Thu, 10 Sep 2015 19:29:13 -0400 Subject: [PATCH 752/760] [iOS] Remove unneccessary pitch check Fix case where moving the tilted dot off-screen, changing the pitch to 0, and then moving the dot back on screen would not reset the pitch of the dot. --- platform/ios/MGLUserLocationAnnotationView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 22c1d00f1..b477b1088 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -95,7 +95,7 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck - (void)updatePitch { - if (self.mapView.camera.pitch && (self.mapView.camera.pitch != _oldPitch)) + if (self.mapView.camera.pitch != _oldPitch) { CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0); self.layer.sublayerTransform = t; From b4b56c951e55bc47d7877e90b219ece4b656300a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 26 Aug 2015 11:14:08 -0400 Subject: [PATCH 753/760] increase precision for coordinate conversions This fixes the earthquakes while panning at high zoom levels. Cherry-picked from 9607171612c4a40e41eddaff5230ad571571a5b9 on the perspective-improved-gestures branch. --- src/mbgl/map/transform_state.cpp | 31 +++++++++++++++---------------- src/mbgl/map/transform_state.hpp | 8 ++++---- src/mbgl/util/mat4.cpp | 2 +- src/mbgl/util/tile_coordinate.hpp | 12 ++++++------ src/mbgl/util/vec4.cpp | 2 +- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index c672ca5de..e37f10dc6 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -157,21 +157,21 @@ float TransformState::getPitch() const { #pragma mark - Projection -float TransformState::lngX(float lng) const { +double TransformState::lngX(double lng) const { return (180.0f + lng) * worldSize() / 360.0f; } -float TransformState::latY(float lat) const { - float y_ = 180.0f / M_PI * std::log(std::tan(M_PI / 4 + lat * M_PI / 360.0f)); +double TransformState::latY(double lat) const { + double y_ = 180.0f / M_PI * std::log(std::tan(M_PI / 4 + lat * M_PI / 360.0f)); return (180.0f - y_) * worldSize() / 360.0f; } -float TransformState::xLng(float x_, float worldSize_) const { +double TransformState::xLng(double x_, double worldSize_) const { return x_ * 360.0f / worldSize_ - 180.0f; } -float TransformState::yLat(float y_, float worldSize_) const { - float y2 = 180.0f - y_ * 360.0f / worldSize_; +double TransformState::yLat(double y_, double worldSize_) const { + double y2 = 180.0f - y_ * 360.0f / worldSize_; return 360.0f / M_PI * std::atan(std::exp(y2 * M_PI / 180.0f)) - 90.0f; } @@ -245,17 +245,16 @@ TileCoordinate TransformState::pointToCoordinate(const vec2 point) const matrix::transformMat4(coord0, point0, inverted); matrix::transformMat4(coord1, point1, inverted); - float w0 = coord0[3]; - float w1 = coord1[3]; - float x0 = coord0[0] / w0; - float x1 = coord1[0] / w1; - float y0 = coord0[1] / w0; - float y1 = coord1[1] / w1; - float z0 = coord0[2] / w0; - float z1 = coord1[2] / w1; + double w0 = coord0[3]; + double w1 = coord1[3]; + double x0 = coord0[0] / w0; + double x1 = coord1[0] / w1; + double y0 = coord0[1] / w0; + double y1 = coord1[1] / w1; + double z0 = coord0[2] / w0; + double z1 = coord1[2] / w1; - - float t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0); + double t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0); return { util::interpolate(x0, x1, t), util::interpolate(y0, y1, t), tileZoom }; } diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index d4b46f04b..c3e1072c9 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -79,10 +79,10 @@ private: // logical dimensions uint16_t width = 0, height = 0; - float xLng(float x, float worldSize) const; - float yLat(float y, float worldSize) const; - float lngX(float lon) const; - float latY(float lat) const; + double xLng(double x, double worldSize) const; + double yLat(double y, double worldSize) const; + double lngX(double lon) const; + double latY(double lat) const; float zoomScale(float zoom) const; float worldSize() const; diff --git a/src/mbgl/util/mat4.cpp b/src/mbgl/util/mat4.cpp index 123c8464f..0934cd081 100644 --- a/src/mbgl/util/mat4.cpp +++ b/src/mbgl/util/mat4.cpp @@ -46,7 +46,7 @@ void matrix::identity(mat4& out) { } bool matrix::invert(mat4& out, mat4& a) { - float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + double a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], diff --git a/src/mbgl/util/tile_coordinate.hpp b/src/mbgl/util/tile_coordinate.hpp index 0f5f0dcb8..836539b5b 100644 --- a/src/mbgl/util/tile_coordinate.hpp +++ b/src/mbgl/util/tile_coordinate.hpp @@ -6,15 +6,15 @@ namespace mbgl { struct TileCoordinate { - float column; - float row; - float zoom; + double column; + double row; + double zoom; - TileCoordinate(float column_, float row_, float zoom_) : + TileCoordinate(double column_, double row_, double zoom_) : column(column_), row(row_), zoom(zoom_) {} - TileCoordinate zoomTo(float targetZoom) { - float scale = std::pow(2, targetZoom - zoom); + TileCoordinate zoomTo(double targetZoom) { + double scale = std::pow(2, targetZoom - zoom); return { column * scale, row * scale, targetZoom }; } diff --git a/src/mbgl/util/vec4.cpp b/src/mbgl/util/vec4.cpp index 415b15d80..bee4eaf97 100644 --- a/src/mbgl/util/vec4.cpp +++ b/src/mbgl/util/vec4.cpp @@ -25,7 +25,7 @@ using namespace mbgl; void matrix::transformMat4(vec4& out, vec4& a, mat4& m) { - float x = a[0], y = a[1], z = a[2], w = a[3]; + double x = a[0], y = a[1], z = a[2], w = a[3]; out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; From 60d0db0d2d0eb046df26c5a76c3144ff01e2e0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 10 Sep 2015 13:39:47 -0700 Subject: [PATCH 754/760] Increased precision of coordinate conversion methods Fixes #2230. --- src/mbgl/map/transform_state.cpp | 16 ++++++++-------- src/mbgl/map/transform_state.hpp | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index e37f10dc6..ac3988dc5 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -175,7 +175,7 @@ double TransformState::yLat(double y_, double worldSize_) const { return 360.0f / M_PI * std::atan(std::exp(y2 * M_PI / 180.0f)) - 90.0f; } -float TransformState::zoomScale(float zoom) const { +double TransformState::zoomScale(double zoom) const { return std::pow(2.0f, zoom); } @@ -192,8 +192,8 @@ LatLng TransformState::pointToLatLng(const vec2 point) const { } TileCoordinate TransformState::latLngToCoordinate(const LatLng& latLng) const { - const float tileZoom = getIntegerZoom(); - const float k = zoomScale(tileZoom) / worldSize(); + const double tileZoom = getZoom(); + const double k = zoomScale(tileZoom) / worldSize(); return { lngX(latLng.longitude) * k, latY(latLng.latitude) * k, @@ -202,12 +202,12 @@ TileCoordinate TransformState::latLngToCoordinate(const LatLng& latLng) const { } LatLng TransformState::coordinateToLatLng(const TileCoordinate& coord) const { - const float worldSize_ = zoomScale(coord.zoom); + const double worldSize_ = zoomScale(coord.zoom); LatLng latLng = { yLat(coord.row, worldSize_), xLng(coord.column, worldSize_) }; - while (latLng.longitude < 180.0f) latLng.longitude += 360.0f; + while (latLng.longitude < -180.0f) latLng.longitude += 360.0f; while (latLng.longitude > 180.0f) latLng.longitude -= 360.0f; return latLng; } @@ -223,7 +223,7 @@ vec2 TransformState::coordinateToPoint(const TileCoordinate& coord) cons TileCoordinate TransformState::pointToCoordinate(const vec2 point) const { float targetZ = 0; - const float tileZoom = getIntegerZoom(); + const double tileZoom = getZoom(); mat4 mat = coordinatePointMatrix(tileZoom); @@ -259,11 +259,11 @@ TileCoordinate TransformState::pointToCoordinate(const vec2 point) const return { util::interpolate(x0, x1, t), util::interpolate(y0, y1, t), tileZoom }; } -mat4 TransformState::coordinatePointMatrix(float z) const { +mat4 TransformState::coordinatePointMatrix(double z) const { mat4 proj; getProjMatrix(proj); float s = util::tileSize * scale / std::pow(2, z); - matrix::scale(proj, proj, s , s, 1); + matrix::scale(proj, proj, s, s, 1); matrix::multiply(proj, getPixelMatrix(), proj); return proj; } diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index c3e1072c9..6fcd35ffa 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -83,10 +83,10 @@ private: double yLat(double y, double worldSize) const; double lngX(double lon) const; double latY(double lat) const; - float zoomScale(float zoom) const; + double zoomScale(double zoom) const; float worldSize() const; - mat4 coordinatePointMatrix(float z) const; + mat4 coordinatePointMatrix(double z) const; mat4 getPixelMatrix() const; private: From d9c7fdc5655d48a6e7c54016c0efd94710431430 Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 11 Sep 2015 16:34:10 -0400 Subject: [PATCH 755/760] [iOS] Fix MGLPolyline documentation typo --- include/mbgl/ios/MGLPolyline.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mbgl/ios/MGLPolyline.h b/include/mbgl/ios/MGLPolyline.h index 7cbbadeca..07c5286c8 100644 --- a/include/mbgl/ios/MGLPolyline.h +++ b/include/mbgl/ios/MGLPolyline.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN /** The `MGLPolyline` class represents a shape consisting of one or more points that define connecting line segments. The points are connected end-to-end in the order they are provided. The first and last points are not connected to each other. */ @interface MGLPolyline : MGLMultiPoint -/** Creates and returns an `MGLPolygon` object from the specified set of coordinates. +/** Creates and returns an `MGLPolyline` object from the specified set of coordinates. * @param coords The array of coordinates defining the shape. The data in this array is copied to the new object. * @param count The number of items in the _`coords`_ array. * @return A new polyline object. */ From 8d6b9696bf3cea0cf8c3beaf9dfb48c7d376d925 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 10 Sep 2015 14:05:16 +0300 Subject: [PATCH 756/760] [shader] Moved 'a_pos' GL attribute to Shader Avoids duplicated code in all derived classes. --- src/mbgl/shader/box_shader.cpp | 1 - src/mbgl/shader/box_shader.hpp | 1 - src/mbgl/shader/circle_shader.cpp | 1 - src/mbgl/shader/circle_shader.hpp | 3 --- src/mbgl/shader/dot_shader.cpp | 1 - src/mbgl/shader/dot_shader.hpp | 3 --- src/mbgl/shader/icon_shader.cpp | 1 - src/mbgl/shader/icon_shader.hpp | 1 - src/mbgl/shader/line_shader.cpp | 1 - src/mbgl/shader/line_shader.hpp | 1 - src/mbgl/shader/linepattern_shader.cpp | 1 - src/mbgl/shader/linepattern_shader.hpp | 1 - src/mbgl/shader/linesdf_shader.cpp | 1 - src/mbgl/shader/linesdf_shader.hpp | 1 - src/mbgl/shader/outline_shader.cpp | 1 - src/mbgl/shader/outline_shader.hpp | 3 --- src/mbgl/shader/pattern_shader.cpp | 1 - src/mbgl/shader/pattern_shader.hpp | 3 --- src/mbgl/shader/plain_shader.cpp | 1 - src/mbgl/shader/plain_shader.hpp | 3 --- src/mbgl/shader/raster_shader.cpp | 1 - src/mbgl/shader/raster_shader.hpp | 3 --- src/mbgl/shader/sdf_shader.cpp | 1 - src/mbgl/shader/sdf_shader.hpp | 1 - src/mbgl/shader/shader.cpp | 2 ++ src/mbgl/shader/shader.hpp | 7 ++++++- 26 files changed, 8 insertions(+), 37 deletions(-) diff --git a/src/mbgl/shader/box_shader.cpp b/src/mbgl/shader/box_shader.cpp index 2676223a9..5c290718e 100644 --- a/src/mbgl/shader/box_shader.cpp +++ b/src/mbgl/shader/box_shader.cpp @@ -12,7 +12,6 @@ CollisionBoxShader::CollisionBoxShader() shaders[BOX_SHADER].vertex, shaders[BOX_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_extrude = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_extrude")); a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } diff --git a/src/mbgl/shader/box_shader.hpp b/src/mbgl/shader/box_shader.hpp index 5c84d8906..832d3e1c0 100644 --- a/src/mbgl/shader/box_shader.hpp +++ b/src/mbgl/shader/box_shader.hpp @@ -18,7 +18,6 @@ public: Uniform u_maxzoom = {"u_maxzoom", *this}; protected: - int32_t a_pos = -1; int32_t a_extrude = -1; int32_t a_data = -1; }; diff --git a/src/mbgl/shader/circle_shader.cpp b/src/mbgl/shader/circle_shader.cpp index 074f92d68..dc8558d10 100644 --- a/src/mbgl/shader/circle_shader.cpp +++ b/src/mbgl/shader/circle_shader.cpp @@ -12,7 +12,6 @@ CircleShader::CircleShader() shaders[CIRCLE_SHADER].vertex, shaders[CIRCLE_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void CircleShader::bind(char *offset) { diff --git a/src/mbgl/shader/circle_shader.hpp b/src/mbgl/shader/circle_shader.hpp index 545a0baad..ce7bb2d20 100644 --- a/src/mbgl/shader/circle_shader.hpp +++ b/src/mbgl/shader/circle_shader.hpp @@ -17,9 +17,6 @@ public: Uniform> u_color = {"u_color", *this}; Uniform u_size = {"u_size", *this}; Uniform u_blur = {"u_blur", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/dot_shader.cpp b/src/mbgl/shader/dot_shader.cpp index 57a60cc51..67c463944 100644 --- a/src/mbgl/shader/dot_shader.cpp +++ b/src/mbgl/shader/dot_shader.cpp @@ -12,7 +12,6 @@ DotShader::DotShader() shaders[DOT_SHADER].vertex, shaders[DOT_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void DotShader::bind(char *offset) { diff --git a/src/mbgl/shader/dot_shader.hpp b/src/mbgl/shader/dot_shader.hpp index 2c4176f36..51263a731 100644 --- a/src/mbgl/shader/dot_shader.hpp +++ b/src/mbgl/shader/dot_shader.hpp @@ -16,9 +16,6 @@ public: Uniform> u_color = {"u_color", *this}; Uniform u_size = {"u_size", *this}; Uniform u_blur = {"u_blur", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/icon_shader.cpp b/src/mbgl/shader/icon_shader.cpp index 0f626ec9e..1084ab308 100644 --- a/src/mbgl/shader/icon_shader.cpp +++ b/src/mbgl/shader/icon_shader.cpp @@ -12,7 +12,6 @@ IconShader::IconShader() shaders[ICON_SHADER].vertex, shaders[ICON_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_offset = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_offset")); a_data1 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data1")); a_data2 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data2")); diff --git a/src/mbgl/shader/icon_shader.hpp b/src/mbgl/shader/icon_shader.hpp index 18b0fce37..20d3ebf5c 100644 --- a/src/mbgl/shader/icon_shader.hpp +++ b/src/mbgl/shader/icon_shader.hpp @@ -25,7 +25,6 @@ public: Uniform u_extra = {"u_extra", *this}; private: - int32_t a_pos = -1; int32_t a_offset = -1; int32_t a_data1 = -1; int32_t a_data2 = -1; diff --git a/src/mbgl/shader/line_shader.cpp b/src/mbgl/shader/line_shader.cpp index c2364c9be..66dff81c9 100644 --- a/src/mbgl/shader/line_shader.cpp +++ b/src/mbgl/shader/line_shader.cpp @@ -12,7 +12,6 @@ LineShader::LineShader() shaders[LINE_SHADER].vertex, shaders[LINE_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } diff --git a/src/mbgl/shader/line_shader.hpp b/src/mbgl/shader/line_shader.hpp index 0572cac88..1a4289ded 100644 --- a/src/mbgl/shader/line_shader.hpp +++ b/src/mbgl/shader/line_shader.hpp @@ -22,7 +22,6 @@ public: UniformMatrix<2> u_antialiasingmatrix = {"u_antialiasingmatrix", *this}; private: - int32_t a_pos = -1; int32_t a_data = -1; }; diff --git a/src/mbgl/shader/linepattern_shader.cpp b/src/mbgl/shader/linepattern_shader.cpp index 7bfd1412d..65404cb11 100644 --- a/src/mbgl/shader/linepattern_shader.cpp +++ b/src/mbgl/shader/linepattern_shader.cpp @@ -12,7 +12,6 @@ LinepatternShader::LinepatternShader() shaders[LINEPATTERN_SHADER].vertex, shaders[LINEPATTERN_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } diff --git a/src/mbgl/shader/linepattern_shader.hpp b/src/mbgl/shader/linepattern_shader.hpp index dce065a0d..a06135180 100644 --- a/src/mbgl/shader/linepattern_shader.hpp +++ b/src/mbgl/shader/linepattern_shader.hpp @@ -30,7 +30,6 @@ public: UniformMatrix<2> u_antialiasingmatrix = {"u_antialiasingmatrix", *this}; private: - int32_t a_pos = -1; int32_t a_data = -1; }; } diff --git a/src/mbgl/shader/linesdf_shader.cpp b/src/mbgl/shader/linesdf_shader.cpp index bd2137de3..a08c99c74 100644 --- a/src/mbgl/shader/linesdf_shader.cpp +++ b/src/mbgl/shader/linesdf_shader.cpp @@ -12,7 +12,6 @@ LineSDFShader::LineSDFShader() shaders[LINESDF_SHADER].vertex, shaders[LINESDF_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } diff --git a/src/mbgl/shader/linesdf_shader.hpp b/src/mbgl/shader/linesdf_shader.hpp index 9928d5b30..4d08916cd 100644 --- a/src/mbgl/shader/linesdf_shader.hpp +++ b/src/mbgl/shader/linesdf_shader.hpp @@ -29,7 +29,6 @@ public: UniformMatrix<2> u_antialiasingmatrix = {"u_antialiasingmatrix", *this}; private: - int32_t a_pos = -1; int32_t a_data = -1; }; diff --git a/src/mbgl/shader/outline_shader.cpp b/src/mbgl/shader/outline_shader.cpp index ee2a6bcd4..1d2c3667f 100644 --- a/src/mbgl/shader/outline_shader.cpp +++ b/src/mbgl/shader/outline_shader.cpp @@ -12,7 +12,6 @@ OutlineShader::OutlineShader() shaders[OUTLINE_SHADER].vertex, shaders[OUTLINE_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void OutlineShader::bind(char *offset) { diff --git a/src/mbgl/shader/outline_shader.hpp b/src/mbgl/shader/outline_shader.hpp index f3e8175fd..9b086594b 100644 --- a/src/mbgl/shader/outline_shader.hpp +++ b/src/mbgl/shader/outline_shader.hpp @@ -15,9 +15,6 @@ public: UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_color = {"u_color", *this}; Uniform> u_world = {"u_world", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/pattern_shader.cpp b/src/mbgl/shader/pattern_shader.cpp index 66369d528..b5883b956 100644 --- a/src/mbgl/shader/pattern_shader.cpp +++ b/src/mbgl/shader/pattern_shader.cpp @@ -12,7 +12,6 @@ PatternShader::PatternShader() shaders[PATTERN_SHADER].vertex, shaders[PATTERN_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void PatternShader::bind(char *offset) { diff --git a/src/mbgl/shader/pattern_shader.hpp b/src/mbgl/shader/pattern_shader.hpp index 9fc8dcfa0..5090a453e 100644 --- a/src/mbgl/shader/pattern_shader.hpp +++ b/src/mbgl/shader/pattern_shader.hpp @@ -22,9 +22,6 @@ public: Uniform u_image = {"u_image", *this}; UniformMatrix<3> u_patternmatrix_a = {"u_patternmatrix_a", *this}; UniformMatrix<3> u_patternmatrix_b = {"u_patternmatrix_b", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/plain_shader.cpp b/src/mbgl/shader/plain_shader.cpp index 93dbb42a5..bb719f47d 100644 --- a/src/mbgl/shader/plain_shader.cpp +++ b/src/mbgl/shader/plain_shader.cpp @@ -12,7 +12,6 @@ PlainShader::PlainShader() shaders[PLAIN_SHADER].vertex, shaders[PLAIN_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void PlainShader::bind(char *offset) { diff --git a/src/mbgl/shader/plain_shader.hpp b/src/mbgl/shader/plain_shader.hpp index 051501c3c..266600024 100644 --- a/src/mbgl/shader/plain_shader.hpp +++ b/src/mbgl/shader/plain_shader.hpp @@ -14,9 +14,6 @@ public: UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_color = {"u_color", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/raster_shader.cpp b/src/mbgl/shader/raster_shader.cpp index 2d6954920..ce03488ac 100644 --- a/src/mbgl/shader/raster_shader.cpp +++ b/src/mbgl/shader/raster_shader.cpp @@ -12,7 +12,6 @@ RasterShader::RasterShader() shaders[RASTER_SHADER].vertex, shaders[RASTER_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } void RasterShader::bind(char *offset) { diff --git a/src/mbgl/shader/raster_shader.hpp b/src/mbgl/shader/raster_shader.hpp index 8cf97055a..e869fb741 100644 --- a/src/mbgl/shader/raster_shader.hpp +++ b/src/mbgl/shader/raster_shader.hpp @@ -21,9 +21,6 @@ public: Uniform u_saturation_factor = {"u_saturation_factor", *this}; Uniform u_contrast_factor = {"u_contrast_factor", *this}; Uniform> u_spin_weights = {"u_spin_weights", *this}; - -private: - int32_t a_pos = -1; }; } diff --git a/src/mbgl/shader/sdf_shader.cpp b/src/mbgl/shader/sdf_shader.cpp index ca1dab58d..7b729a458 100644 --- a/src/mbgl/shader/sdf_shader.cpp +++ b/src/mbgl/shader/sdf_shader.cpp @@ -12,7 +12,6 @@ SDFShader::SDFShader() shaders[SDF_SHADER].vertex, shaders[SDF_SHADER].fragment ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); a_offset = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_offset")); a_data1 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data1")); a_data2 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data2")); diff --git a/src/mbgl/shader/sdf_shader.hpp b/src/mbgl/shader/sdf_shader.hpp index 181788ab1..f8e0aef2b 100644 --- a/src/mbgl/shader/sdf_shader.hpp +++ b/src/mbgl/shader/sdf_shader.hpp @@ -27,7 +27,6 @@ public: Uniform u_extra = {"u_extra", *this}; protected: - int32_t a_pos = -1; int32_t a_offset = -1; int32_t a_data1 = -1; int32_t a_data2 = -1; diff --git a/src/mbgl/shader/shader.cpp b/src/mbgl/shader/shader.cpp index 6210c1c32..1e3df1bbc 100644 --- a/src/mbgl/shader/shader.cpp +++ b/src/mbgl/shader/shader.cpp @@ -97,6 +97,8 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo MBGL_CHECK_ERROR(glDeleteShader(vertShader)); MBGL_CHECK_ERROR(glDetachShader(program, fragShader)); MBGL_CHECK_ERROR(glDeleteShader(fragShader)); + + a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } diff --git a/src/mbgl/shader/shader.hpp b/src/mbgl/shader/shader.hpp index b3d9b4e46..2a1cf39fe 100644 --- a/src/mbgl/shader/shader.hpp +++ b/src/mbgl/shader/shader.hpp @@ -1,10 +1,12 @@ #ifndef MBGL_RENDERER_SHADER #define MBGL_RENDERER_SHADER +#include +#include + #include #include #include -#include namespace mbgl { @@ -19,6 +21,9 @@ public: return program; } +protected: + GLint a_pos = -1; + private: bool compileShader(uint32_t *shader, uint32_t type, const char *source); }; From 2156aa35ca96f9dd05166fd6b012f09eaaa5b934 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Thu, 10 Sep 2015 14:46:38 +0300 Subject: [PATCH 757/760] [shader] bind() is now a pure virtual - Shader::bind() now receives a GLbyte* - BUFFER_OFFSET now uses GLbyte --- src/mbgl/geometry/vao.cpp | 4 ++-- src/mbgl/geometry/vao.hpp | 10 +++++----- src/mbgl/renderer/bucket.hpp | 4 +++- src/mbgl/renderer/circle_bucket.cpp | 4 ++-- src/mbgl/renderer/debug_bucket.cpp | 4 ++-- src/mbgl/renderer/fill_bucket.cpp | 12 ++++++------ src/mbgl/renderer/line_bucket.cpp | 12 ++++++------ src/mbgl/renderer/painter_clipping.cpp | 2 +- src/mbgl/renderer/painter_debug.cpp | 2 +- src/mbgl/renderer/raster_bucket.cpp | 2 +- src/mbgl/renderer/symbol_bucket.cpp | 14 +++++++------- src/mbgl/shader/box_shader.cpp | 2 +- src/mbgl/shader/box_shader.hpp | 3 ++- src/mbgl/shader/circle_shader.cpp | 2 +- src/mbgl/shader/circle_shader.hpp | 2 +- src/mbgl/shader/dot_shader.cpp | 2 +- src/mbgl/shader/dot_shader.hpp | 2 +- src/mbgl/shader/icon_shader.cpp | 2 +- src/mbgl/shader/icon_shader.hpp | 2 +- src/mbgl/shader/line_shader.cpp | 2 +- src/mbgl/shader/line_shader.hpp | 2 +- src/mbgl/shader/linepattern_shader.cpp | 2 +- src/mbgl/shader/linepattern_shader.hpp | 2 +- src/mbgl/shader/linesdf_shader.cpp | 2 +- src/mbgl/shader/linesdf_shader.hpp | 2 +- src/mbgl/shader/outline_shader.cpp | 2 +- src/mbgl/shader/outline_shader.hpp | 2 +- src/mbgl/shader/pattern_shader.cpp | 2 +- src/mbgl/shader/pattern_shader.hpp | 2 +- src/mbgl/shader/plain_shader.cpp | 2 +- src/mbgl/shader/plain_shader.hpp | 2 +- src/mbgl/shader/raster_shader.cpp | 2 +- src/mbgl/shader/raster_shader.hpp | 2 +- src/mbgl/shader/sdf_shader.cpp | 4 ++-- src/mbgl/shader/sdf_shader.hpp | 6 ++---- src/mbgl/shader/shader.hpp | 2 ++ 36 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/mbgl/geometry/vao.cpp b/src/mbgl/geometry/vao.cpp index ef4cb26db..1330a057f 100644 --- a/src/mbgl/geometry/vao.cpp +++ b/src/mbgl/geometry/vao.cpp @@ -69,7 +69,7 @@ void VertexArrayObject::bindVertexArrayObject() { } void VertexArrayObject::verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, - char *offset) { + GLbyte *offset) { if (bound_shader != shader.getID()) { throw std::runtime_error(std::string("trying to rebind VAO to another shader from " + util::toString(bound_shader) + "(" + bound_shader_name + ") to " + @@ -84,7 +84,7 @@ void VertexArrayObject::verifyBinding(Shader &shader, GLuint vertexBuffer, GLuin } void VertexArrayObject::storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, - char *offset) { + GLbyte *offset) { bound_shader = shader.getID(); bound_shader_name = shader.name; bound_offset = offset; diff --git a/src/mbgl/geometry/vao.hpp b/src/mbgl/geometry/vao.hpp index e512a3e48..bd845b1e3 100644 --- a/src/mbgl/geometry/vao.hpp +++ b/src/mbgl/geometry/vao.hpp @@ -18,7 +18,7 @@ public: ~VertexArrayObject(); template - inline void bind(Shader& shader, VertexBuffer &vertexBuffer, char *offset) { + inline void bind(Shader& shader, VertexBuffer &vertexBuffer, GLbyte *offset) { bindVertexArrayObject(); if (bound_shader == 0) { vertexBuffer.bind(); @@ -32,7 +32,7 @@ public: } template - inline void bind(Shader& shader, VertexBuffer &vertexBuffer, ElementsBuffer &elementsBuffer, char *offset) { + inline void bind(Shader& shader, VertexBuffer &vertexBuffer, ElementsBuffer &elementsBuffer, GLbyte *offset) { bindVertexArrayObject(); if (bound_shader == 0) { vertexBuffer.bind(); @@ -52,8 +52,8 @@ public: private: void bindVertexArrayObject(); - void storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset); - void verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset); + void storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, GLbyte *offset); + void verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, GLbyte *offset); GLuint vao = 0; @@ -63,7 +63,7 @@ private: const char *bound_shader_name = ""; GLuint bound_vertex_buffer = 0; GLuint bound_elements_buffer = 0; - char *bound_offset = 0; + GLbyte *bound_offset = 0; }; } diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp index eb02f32eb..a1dbdeeed 100644 --- a/src/mbgl/renderer/bucket.hpp +++ b/src/mbgl/renderer/bucket.hpp @@ -1,13 +1,15 @@ #ifndef MBGL_RENDERER_BUCKET #define MBGL_RENDERER_BUCKET +#include #include #include #include #include -#define BUFFER_OFFSET(i) ((char*)nullptr + (i)) +#define BUFFER_OFFSET_0 ((GLbyte*)nullptr) +#define BUFFER_OFFSET(i) ((BUFFER_OFFSET_0) + (i)) namespace mbgl { diff --git a/src/mbgl/renderer/circle_bucket.cpp b/src/mbgl/renderer/circle_bucket.cpp index fde04df29..a6a439d86 100644 --- a/src/mbgl/renderer/circle_bucket.cpp +++ b/src/mbgl/renderer/circle_bucket.cpp @@ -74,8 +74,8 @@ void CircleBucket::addGeometry(const GeometryCollection& geometryCollection) { } void CircleBucket::drawCircles(CircleShader& shader) { - char* vertexIndex = BUFFER_OFFSET(vertexStart_ * vertexBuffer_.itemSize); - char* elementsIndex = BUFFER_OFFSET(elementsStart_ * elementsBuffer_.itemSize); + GLbyte *vertexIndex = BUFFER_OFFSET(vertexStart_ * vertexBuffer_.itemSize); + GLbyte *elementsIndex = BUFFER_OFFSET(elementsStart_ * elementsBuffer_.itemSize); for (auto& group : triangleGroups_) { assert(group); diff --git a/src/mbgl/renderer/debug_bucket.cpp b/src/mbgl/renderer/debug_bucket.cpp index 1d9b9fb6d..161412f0d 100644 --- a/src/mbgl/renderer/debug_bucket.cpp +++ b/src/mbgl/renderer/debug_bucket.cpp @@ -23,11 +23,11 @@ void DebugBucket::render(Painter& painter, const StyleLayer&, const TileID&, con } void DebugBucket::drawLines(PlainShader& shader) { - array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + array.bind(shader, fontBuffer, BUFFER_OFFSET_0); MBGL_CHECK_ERROR(glDrawArrays(GL_LINES, 0, (GLsizei)(fontBuffer.index()))); } void DebugBucket::drawPoints(PlainShader& shader) { - array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + array.bind(shader, fontBuffer, BUFFER_OFFSET_0); MBGL_CHECK_ERROR(glDrawArrays(GL_POINTS, 0, (GLsizei)(fontBuffer.index()))); } diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp index 151e4c4a4..1828ae0a2 100644 --- a/src/mbgl/renderer/fill_bucket.cpp +++ b/src/mbgl/renderer/fill_bucket.cpp @@ -216,8 +216,8 @@ bool FillBucket::hasData() const { } void FillBucket::drawElements(PlainShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + GLbyte *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); group->array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); @@ -228,8 +228,8 @@ void FillBucket::drawElements(PlainShader& shader) { } void FillBucket::drawElements(PatternShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + GLbyte *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); group->array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); @@ -240,8 +240,8 @@ void FillBucket::drawElements(PatternShader& shader) { } void FillBucket::drawVertices(OutlineShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize); + GLbyte *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize); for (auto& group : lineGroups) { assert(group); group->array[0].bind(shader, vertexBuffer, lineElementsBuffer, vertex_index); diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index 24bda6d0a..f0d52fa51 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -405,8 +405,8 @@ bool LineBucket::hasData() const { } void LineBucket::drawLines(LineShader& shader) { - char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + GLbyte* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { @@ -421,8 +421,8 @@ void LineBucket::drawLines(LineShader& shader) { } void LineBucket::drawLineSDF(LineSDFShader& shader) { - char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + GLbyte* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { @@ -437,8 +437,8 @@ void LineBucket::drawLineSDF(LineSDFShader& shader) { } void LineBucket::drawLinePatterns(LinepatternShader& shader) { - char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + GLbyte* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + GLbyte* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { diff --git a/src/mbgl/renderer/painter_clipping.cpp b/src/mbgl/renderer/painter_clipping.cpp index 332d2876f..59e40fefe 100644 --- a/src/mbgl/renderer/painter_clipping.cpp +++ b/src/mbgl/renderer/painter_clipping.cpp @@ -16,7 +16,7 @@ void Painter::drawClippingMasks(const std::set& sources) { config.colorMask = { false, false, false, false }; config.depthRange = { 1.0f, 1.0f }; - coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); + coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET_0); for (const auto& source : sources) { source->drawClippingMasks(*this); diff --git a/src/mbgl/renderer/painter_debug.cpp b/src/mbgl/renderer/painter_debug.cpp index f4f3a19cb..bc4055324 100644 --- a/src/mbgl/renderer/painter_debug.cpp +++ b/src/mbgl/renderer/painter_debug.cpp @@ -59,7 +59,7 @@ void Painter::renderDebugFrame(const mat4 &matrix) { plainShader->u_matrix = matrix; // draw tile outline - tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); + tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET_0); plainShader->u_color = {{ 1.0f, 0.0f, 0.0f, 1.0f }}; lineWidth(4.0f * data.pixelRatio); MBGL_CHECK_ERROR(glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index())); diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp index 70cc1065d..cf72f860a 100644 --- a/src/mbgl/renderer/raster_bucket.cpp +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -30,7 +30,7 @@ bool RasterBucket::setImage(std::unique_ptr image) { void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array) { raster.bind(true); shader.u_image = 0; - array.bind(shader, vertices, BUFFER_OFFSET(0)); + array.bind(shader, vertices, BUFFER_OFFSET_0); MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index())); } diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index 7665fd9a6..11b3bf3d0 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -565,8 +565,8 @@ void SymbolBucket::swapRenderData() { } void SymbolBucket::drawGlyphs(SDFShader &shader) { - char *vertex_index = BUFFER_OFFSET(0); - char *elements_index = BUFFER_OFFSET(0); + GLbyte *vertex_index = BUFFER_OFFSET_0; + GLbyte *elements_index = BUFFER_OFFSET_0; auto& text = renderData->text; for (auto &group : text.groups) { assert(group); @@ -578,8 +578,8 @@ void SymbolBucket::drawGlyphs(SDFShader &shader) { } void SymbolBucket::drawIcons(SDFShader &shader) { - char *vertex_index = BUFFER_OFFSET(0); - char *elements_index = BUFFER_OFFSET(0); + GLbyte *vertex_index = BUFFER_OFFSET_0; + GLbyte *elements_index = BUFFER_OFFSET_0; auto& icon = renderData->icon; for (auto &group : icon.groups) { assert(group); @@ -591,8 +591,8 @@ void SymbolBucket::drawIcons(SDFShader &shader) { } void SymbolBucket::drawIcons(IconShader &shader) { - char *vertex_index = BUFFER_OFFSET(0); - char *elements_index = BUFFER_OFFSET(0); + GLbyte *vertex_index = BUFFER_OFFSET_0; + GLbyte *elements_index = BUFFER_OFFSET_0; auto& icon = renderData->icon; for (auto &group : icon.groups) { assert(group); @@ -604,7 +604,7 @@ void SymbolBucket::drawIcons(IconShader &shader) { } void SymbolBucket::drawCollisionBoxes(CollisionBoxShader &shader) { - char *vertex_index = BUFFER_OFFSET(0); + GLbyte *vertex_index = BUFFER_OFFSET_0; auto& collisionBox = renderData->collisionBox; for (auto &group : collisionBox.groups) { group->array[0].bind(shader, collisionBox.vertices, vertex_index); diff --git a/src/mbgl/shader/box_shader.cpp b/src/mbgl/shader/box_shader.cpp index 5c290718e..ffcce815f 100644 --- a/src/mbgl/shader/box_shader.cpp +++ b/src/mbgl/shader/box_shader.cpp @@ -16,7 +16,7 @@ CollisionBoxShader::CollisionBoxShader() a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } -void CollisionBoxShader::bind(char *offset) { +void CollisionBoxShader::bind(GLbyte *offset) { const int stride = 12; MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); diff --git a/src/mbgl/shader/box_shader.hpp b/src/mbgl/shader/box_shader.hpp index 832d3e1c0..20d1deec6 100644 --- a/src/mbgl/shader/box_shader.hpp +++ b/src/mbgl/shader/box_shader.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace mbgl { @@ -10,7 +11,7 @@ class CollisionBoxShader : public Shader { public: CollisionBoxShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform u_scale = {"u_scale", *this}; diff --git a/src/mbgl/shader/circle_shader.cpp b/src/mbgl/shader/circle_shader.cpp index dc8558d10..8fa10191b 100644 --- a/src/mbgl/shader/circle_shader.cpp +++ b/src/mbgl/shader/circle_shader.cpp @@ -14,7 +14,7 @@ CircleShader::CircleShader() ) { } -void CircleShader::bind(char *offset) { +void CircleShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 4, offset)); } diff --git a/src/mbgl/shader/circle_shader.hpp b/src/mbgl/shader/circle_shader.hpp index ce7bb2d20..58314e837 100644 --- a/src/mbgl/shader/circle_shader.hpp +++ b/src/mbgl/shader/circle_shader.hpp @@ -10,7 +10,7 @@ class CircleShader : public Shader { public: CircleShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; diff --git a/src/mbgl/shader/dot_shader.cpp b/src/mbgl/shader/dot_shader.cpp index 67c463944..baec3678b 100644 --- a/src/mbgl/shader/dot_shader.cpp +++ b/src/mbgl/shader/dot_shader.cpp @@ -14,7 +14,7 @@ DotShader::DotShader() ) { } -void DotShader::bind(char *offset) { +void DotShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset)); } diff --git a/src/mbgl/shader/dot_shader.hpp b/src/mbgl/shader/dot_shader.hpp index 51263a731..45ac1c26c 100644 --- a/src/mbgl/shader/dot_shader.hpp +++ b/src/mbgl/shader/dot_shader.hpp @@ -10,7 +10,7 @@ class DotShader : public Shader { public: DotShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_color = {"u_color", *this}; diff --git a/src/mbgl/shader/icon_shader.cpp b/src/mbgl/shader/icon_shader.cpp index 1084ab308..736bb47e7 100644 --- a/src/mbgl/shader/icon_shader.cpp +++ b/src/mbgl/shader/icon_shader.cpp @@ -17,7 +17,7 @@ IconShader::IconShader() a_data2 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data2")); } -void IconShader::bind(char *offset) { +void IconShader::bind(GLbyte *offset) { const int stride = 16; MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); diff --git a/src/mbgl/shader/icon_shader.hpp b/src/mbgl/shader/icon_shader.hpp index 20d3ebf5c..a0e9397f0 100644 --- a/src/mbgl/shader/icon_shader.hpp +++ b/src/mbgl/shader/icon_shader.hpp @@ -10,7 +10,7 @@ class IconShader : public Shader { public: IconShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; diff --git a/src/mbgl/shader/line_shader.cpp b/src/mbgl/shader/line_shader.cpp index 66dff81c9..1e2306939 100644 --- a/src/mbgl/shader/line_shader.cpp +++ b/src/mbgl/shader/line_shader.cpp @@ -15,7 +15,7 @@ LineShader::LineShader() a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } -void LineShader::bind(char *offset) { +void LineShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset + 0)); diff --git a/src/mbgl/shader/line_shader.hpp b/src/mbgl/shader/line_shader.hpp index 1a4289ded..77866e89d 100644 --- a/src/mbgl/shader/line_shader.hpp +++ b/src/mbgl/shader/line_shader.hpp @@ -10,7 +10,7 @@ class LineShader : public Shader { public: LineShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; diff --git a/src/mbgl/shader/linepattern_shader.cpp b/src/mbgl/shader/linepattern_shader.cpp index 65404cb11..4d416d506 100644 --- a/src/mbgl/shader/linepattern_shader.cpp +++ b/src/mbgl/shader/linepattern_shader.cpp @@ -15,7 +15,7 @@ LinepatternShader::LinepatternShader() a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } -void LinepatternShader::bind(char *offset) { +void LinepatternShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset + 0)); diff --git a/src/mbgl/shader/linepattern_shader.hpp b/src/mbgl/shader/linepattern_shader.hpp index a06135180..335281c19 100644 --- a/src/mbgl/shader/linepattern_shader.hpp +++ b/src/mbgl/shader/linepattern_shader.hpp @@ -10,7 +10,7 @@ class LinepatternShader : public Shader { public: LinepatternShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; diff --git a/src/mbgl/shader/linesdf_shader.cpp b/src/mbgl/shader/linesdf_shader.cpp index a08c99c74..1a56bb106 100644 --- a/src/mbgl/shader/linesdf_shader.cpp +++ b/src/mbgl/shader/linesdf_shader.cpp @@ -15,7 +15,7 @@ LineSDFShader::LineSDFShader() a_data = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data")); } -void LineSDFShader::bind(char *offset) { +void LineSDFShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset + 0)); diff --git a/src/mbgl/shader/linesdf_shader.hpp b/src/mbgl/shader/linesdf_shader.hpp index 4d08916cd..1996d16f1 100644 --- a/src/mbgl/shader/linesdf_shader.hpp +++ b/src/mbgl/shader/linesdf_shader.hpp @@ -10,7 +10,7 @@ class LineSDFShader : public Shader { public: LineSDFShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; diff --git a/src/mbgl/shader/outline_shader.cpp b/src/mbgl/shader/outline_shader.cpp index 1d2c3667f..74453468b 100644 --- a/src/mbgl/shader/outline_shader.cpp +++ b/src/mbgl/shader/outline_shader.cpp @@ -14,7 +14,7 @@ OutlineShader::OutlineShader() ) { } -void OutlineShader::bind(char *offset) { +void OutlineShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset)); } diff --git a/src/mbgl/shader/outline_shader.hpp b/src/mbgl/shader/outline_shader.hpp index 9b086594b..9de01e214 100644 --- a/src/mbgl/shader/outline_shader.hpp +++ b/src/mbgl/shader/outline_shader.hpp @@ -10,7 +10,7 @@ class OutlineShader : public Shader { public: OutlineShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_color = {"u_color", *this}; diff --git a/src/mbgl/shader/pattern_shader.cpp b/src/mbgl/shader/pattern_shader.cpp index b5883b956..d6ddab8e2 100644 --- a/src/mbgl/shader/pattern_shader.cpp +++ b/src/mbgl/shader/pattern_shader.cpp @@ -14,7 +14,7 @@ PatternShader::PatternShader() ) { } -void PatternShader::bind(char *offset) { +void PatternShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset)); } diff --git a/src/mbgl/shader/pattern_shader.hpp b/src/mbgl/shader/pattern_shader.hpp index 5090a453e..28cd341d8 100644 --- a/src/mbgl/shader/pattern_shader.hpp +++ b/src/mbgl/shader/pattern_shader.hpp @@ -10,7 +10,7 @@ class PatternShader : public Shader { public: PatternShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_pattern_tl_a = {"u_pattern_tl_a", *this}; diff --git a/src/mbgl/shader/plain_shader.cpp b/src/mbgl/shader/plain_shader.cpp index bb719f47d..52fef07c0 100644 --- a/src/mbgl/shader/plain_shader.cpp +++ b/src/mbgl/shader/plain_shader.cpp @@ -14,7 +14,7 @@ PlainShader::PlainShader() ) { } -void PlainShader::bind(char *offset) { +void PlainShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset)); } diff --git a/src/mbgl/shader/plain_shader.hpp b/src/mbgl/shader/plain_shader.hpp index 266600024..cf2bcc38c 100644 --- a/src/mbgl/shader/plain_shader.hpp +++ b/src/mbgl/shader/plain_shader.hpp @@ -10,7 +10,7 @@ class PlainShader : public Shader { public: PlainShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform> u_color = {"u_color", *this}; diff --git a/src/mbgl/shader/raster_shader.cpp b/src/mbgl/shader/raster_shader.cpp index ce03488ac..b8496230b 100644 --- a/src/mbgl/shader/raster_shader.cpp +++ b/src/mbgl/shader/raster_shader.cpp @@ -14,7 +14,7 @@ RasterShader::RasterShader() ) { } -void RasterShader::bind(char *offset) { +void RasterShader::bind(GLbyte *offset) { MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset)); } diff --git a/src/mbgl/shader/raster_shader.hpp b/src/mbgl/shader/raster_shader.hpp index e869fb741..705efec99 100644 --- a/src/mbgl/shader/raster_shader.hpp +++ b/src/mbgl/shader/raster_shader.hpp @@ -10,7 +10,7 @@ class RasterShader : public Shader { public: RasterShader(); - void bind(char *offset); + void bind(GLbyte *offset) final; UniformMatrix<4> u_matrix = {"u_matrix", *this}; Uniform u_image = {"u_image", *this}; diff --git a/src/mbgl/shader/sdf_shader.cpp b/src/mbgl/shader/sdf_shader.cpp index 7b729a458..144ba6c47 100644 --- a/src/mbgl/shader/sdf_shader.cpp +++ b/src/mbgl/shader/sdf_shader.cpp @@ -17,7 +17,7 @@ SDFShader::SDFShader() a_data2 = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_data2")); } -void SDFGlyphShader::bind(char *offset) { +void SDFGlyphShader::bind(GLbyte *offset) { const int stride = 16; MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); @@ -33,7 +33,7 @@ void SDFGlyphShader::bind(char *offset) { MBGL_CHECK_ERROR(glVertexAttribPointer(a_data2, 4, GL_UNSIGNED_BYTE, false, stride, offset + 12)); } -void SDFIconShader::bind(char *offset) { +void SDFIconShader::bind(GLbyte *offset) { const int stride = 16; MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); diff --git a/src/mbgl/shader/sdf_shader.hpp b/src/mbgl/shader/sdf_shader.hpp index f8e0aef2b..a56f3b2eb 100644 --- a/src/mbgl/shader/sdf_shader.hpp +++ b/src/mbgl/shader/sdf_shader.hpp @@ -10,8 +10,6 @@ class SDFShader : public Shader { public: SDFShader(); - virtual void bind(char *offset) = 0; - UniformMatrix<4> u_matrix = {"u_matrix", *this}; UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this}; Uniform> u_color = {"u_color", *this}; @@ -34,12 +32,12 @@ protected: class SDFGlyphShader : public SDFShader { public: - void bind(char *offset); + void bind(GLbyte *offset) final; }; class SDFIconShader : public SDFShader { public: - void bind(char *offset); + void bind(GLbyte *offset) final; }; } diff --git a/src/mbgl/shader/shader.hpp b/src/mbgl/shader/shader.hpp index 2a1cf39fe..9b150c37e 100644 --- a/src/mbgl/shader/shader.hpp +++ b/src/mbgl/shader/shader.hpp @@ -21,6 +21,8 @@ public: return program; } + virtual void bind(GLbyte *offset) = 0; + protected: GLint a_pos = -1; From 9ff66acf61c33c4b073ea44d0932807e9c7632be Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Mon, 14 Sep 2015 14:54:54 +0300 Subject: [PATCH 758/760] Typo fix in README.md Fixed URL typo for react-native-mapbox-gl. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c54afa28f..00432f17c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ An OpenGL ES-based renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blo If you want to use products _based on_ Mapbox GL, check out: - [Mapbox Mobile](http://mapbox.com/mobile) — iOS and Android native development libraries for vector maps in apps -- [`react-native-mapbox-gl`](https://github.com/mapbox/react-native-mapbox-gl`) — React Native library for vector maps in apps +- [`react-native-mapbox-gl`](https://github.com/mapbox/react-native-mapbox-gl) — React Native library for vector maps in apps - [`qmapboxgl`](https://github.com/tmpsantos/qmapboxgl) — Qt library for vector map rendering -- From 6db8e65394838e3269351cf393a8820cea1326a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Fri, 21 Aug 2015 14:33:17 -0400 Subject: [PATCH 759/760] don't validate the program it's not in a state where validation always succeeds with all drivers, e.g. because textures might not be attached yet --- src/mbgl/shader/shader.cpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/mbgl/shader/shader.cpp b/src/mbgl/shader/shader.cpp index 1e3df1bbc..ed4cb729b 100644 --- a/src/mbgl/shader/shader.cpp +++ b/src/mbgl/shader/shader.cpp @@ -67,31 +67,6 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo } } - { - // Validate program - GLint status; - MBGL_CHECK_ERROR(glValidateProgram(program)); - - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_VALIDATE_STATUS, &status)); - if (status == 0) { - GLint logLength; - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength)); - const auto log = std::make_unique(logLength); - if (logLength > 0) { - MBGL_CHECK_ERROR(glGetProgramInfoLog(program, logLength, &logLength, log.get())); - Log::Error(Event::Shader, "Program failed to validate: %s", log.get()); - } - - MBGL_CHECK_ERROR(glDeleteShader(vertShader)); - vertShader = 0; - MBGL_CHECK_ERROR(glDeleteShader(fragShader)); - fragShader = 0; - MBGL_CHECK_ERROR(glDeleteProgram(program)); - program = 0; - throw util::ShaderException(std::string { "Program " } + name + " failed to link: " + log.get()); - } - } - // Remove the compiled shaders; they are now part of the program. MBGL_CHECK_ERROR(glDetachShader(program, vertShader)); MBGL_CHECK_ERROR(glDeleteShader(vertShader)); From 5ef7011089e761dda0a26782c04dad9f61e65d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Fri, 21 Aug 2015 14:34:06 -0400 Subject: [PATCH 760/760] don't delete the shaders before the program some GPU drivers have issues with deleting shader objects that are attached to programs. while this is allowed by the spec, it seems that some drivers are crashing nonetheless. --- src/mbgl/shader/shader.cpp | 14 ++++++-------- src/mbgl/shader/shader.hpp | 3 +++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/mbgl/shader/shader.cpp b/src/mbgl/shader/shader.cpp index ed4cb729b..c69518c8c 100644 --- a/src/mbgl/shader/shader.cpp +++ b/src/mbgl/shader/shader.cpp @@ -20,8 +20,6 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo program = MBGL_CHECK_ERROR(glCreateProgram()); - GLuint vertShader = 0; - GLuint fragShader = 0; if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) { Log::Error(Event::Shader, "Vertex shader %s failed to compile: %s", name, vertSource); MBGL_CHECK_ERROR(glDeleteProgram(program)); @@ -67,12 +65,6 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo } } - // Remove the compiled shaders; they are now part of the program. - MBGL_CHECK_ERROR(glDetachShader(program, vertShader)); - MBGL_CHECK_ERROR(glDeleteShader(vertShader)); - MBGL_CHECK_ERROR(glDetachShader(program, fragShader)); - MBGL_CHECK_ERROR(glDeleteShader(fragShader)); - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); } @@ -115,7 +107,13 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) { Shader::~Shader() { if (program) { + MBGL_CHECK_ERROR(glDetachShader(program, vertShader)); + MBGL_CHECK_ERROR(glDetachShader(program, fragShader)); MBGL_CHECK_ERROR(glDeleteProgram(program)); program = 0; + MBGL_CHECK_ERROR(glDeleteShader(vertShader)); + vertShader = 0; + MBGL_CHECK_ERROR(glDeleteShader(fragShader)); + fragShader = 0; } } diff --git a/src/mbgl/shader/shader.hpp b/src/mbgl/shader/shader.hpp index 9b150c37e..329022479 100644 --- a/src/mbgl/shader/shader.hpp +++ b/src/mbgl/shader/shader.hpp @@ -28,6 +28,9 @@ protected: private: bool compileShader(uint32_t *shader, uint32_t type, const char *source); + + uint32_t vertShader = 0; + uint32_t fragShader = 0; }; }