summaryrefslogtreecommitdiff
path: root/platform/default/src/mbgl/util
diff options
context:
space:
mode:
Diffstat (limited to 'platform/default/src/mbgl/util')
-rw-r--r--platform/default/src/mbgl/util/async_task.cpp66
-rw-r--r--platform/default/src/mbgl/util/default_thread_pool.cpp57
-rw-r--r--platform/default/src/mbgl/util/image.cpp31
-rw-r--r--platform/default/src/mbgl/util/jpeg_reader.cpp151
-rw-r--r--platform/default/src/mbgl/util/logging_stderr.cpp12
-rw-r--r--platform/default/src/mbgl/util/png_reader.cpp142
-rw-r--r--platform/default/src/mbgl/util/png_writer.cpp77
-rw-r--r--platform/default/src/mbgl/util/run_loop.cpp219
-rw-r--r--platform/default/src/mbgl/util/shared_thread_pool.cpp14
-rw-r--r--platform/default/src/mbgl/util/string_stdlib.cpp74
-rw-r--r--platform/default/src/mbgl/util/thread.cpp37
-rw-r--r--platform/default/src/mbgl/util/thread_local.cpp66
-rw-r--r--platform/default/src/mbgl/util/timer.cpp73
-rw-r--r--platform/default/src/mbgl/util/utf.cpp17
14 files changed, 1036 insertions, 0 deletions
diff --git a/platform/default/src/mbgl/util/async_task.cpp b/platform/default/src/mbgl/util/async_task.cpp
new file mode 100644
index 0000000000..50891056d8
--- /dev/null
+++ b/platform/default/src/mbgl/util/async_task.cpp
@@ -0,0 +1,66 @@
+#include <mbgl/util/async_task.hpp>
+
+#include <mbgl/util/run_loop.hpp>
+
+#include <atomic>
+#include <functional>
+
+#include <uv.h>
+
+namespace mbgl {
+namespace util {
+
+class AsyncTask::Impl {
+public:
+ Impl(std::function<void()>&& fn)
+ : async(new uv_async_t),
+ task(std::move(fn)) {
+
+ auto* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle());
+ if (uv_async_init(loop, async, asyncCallback) != 0) {
+ throw std::runtime_error("Failed to initialize async.");
+ }
+
+ handle()->data = this;
+ uv_unref(handle());
+ }
+
+ ~Impl() {
+ uv_close(handle(), [](uv_handle_t* h) {
+ delete reinterpret_cast<uv_async_t*>(h);
+ });
+ }
+
+ void maySend() {
+ // uv_async_send will do the call coalescing for us.
+ if (uv_async_send(async) != 0) {
+ throw std::runtime_error("Failed to async send.");
+ }
+ }
+
+private:
+ static void asyncCallback(uv_async_t* handle) {
+ reinterpret_cast<Impl*>(handle->data)->task();
+ }
+
+ uv_handle_t* handle() {
+ return reinterpret_cast<uv_handle_t*>(async);
+ }
+
+ uv_async_t* async;
+
+ std::function<void()> task;
+};
+
+AsyncTask::AsyncTask(std::function<void()>&& fn)
+ : impl(std::make_unique<Impl>(std::move(fn))) {
+}
+
+AsyncTask::~AsyncTask() = default;
+
+void AsyncTask::send() {
+ impl->maySend();
+}
+
+} // namespace util
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/default_thread_pool.cpp b/platform/default/src/mbgl/util/default_thread_pool.cpp
new file mode 100644
index 0000000000..d3950bb8aa
--- /dev/null
+++ b/platform/default/src/mbgl/util/default_thread_pool.cpp
@@ -0,0 +1,57 @@
+#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/actor/mailbox.hpp>
+#include <mbgl/util/platform.hpp>
+#include <mbgl/util/string.hpp>
+
+namespace mbgl {
+
+ThreadPool::ThreadPool(std::size_t count) {
+ threads.reserve(count);
+ for (std::size_t i = 0; i < count; ++i) {
+ threads.emplace_back([this, i]() {
+ platform::setCurrentThreadName(std::string{ "Worker " } + util::toString(i + 1));
+
+ while (true) {
+ std::unique_lock<std::mutex> lock(mutex);
+
+ cv.wait(lock, [this] {
+ return !queue.empty() || terminate;
+ });
+
+ if (terminate) {
+ return;
+ }
+
+ auto mailbox = queue.front();
+ queue.pop();
+ lock.unlock();
+
+ Mailbox::maybeReceive(mailbox);
+ }
+ });
+ }
+}
+
+ThreadPool::~ThreadPool() {
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ terminate = true;
+ }
+
+ cv.notify_all();
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+}
+
+void ThreadPool::schedule(std::weak_ptr<Mailbox> mailbox) {
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ queue.push(mailbox);
+ }
+
+ cv.notify_one();
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/image.cpp b/platform/default/src/mbgl/util/image.cpp
new file mode 100644
index 0000000000..25063892b7
--- /dev/null
+++ b/platform/default/src/mbgl/util/image.cpp
@@ -0,0 +1,31 @@
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/premultiply.hpp>
+
+namespace mbgl {
+
+PremultipliedImage decodePNG(const uint8_t*, size_t);
+PremultipliedImage decodeJPEG(const uint8_t*, size_t);
+
+PremultipliedImage decodeImage(const std::string& string) {
+ const auto* data = reinterpret_cast<const uint8_t*>(string.data());
+ const size_t size = string.size();
+
+ if (size >= 4) {
+ uint32_t magic = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+ if (magic == 0x89504E47U) {
+ return decodePNG(data, size);
+ }
+ }
+
+ if (size >= 2) {
+ uint16_t magic = ((data[0] << 8) | data[1]) & 0xffff;
+ if (magic == 0xFFD8) {
+ return decodeJPEG(data, size);
+ }
+ }
+
+ throw std::runtime_error("unsupported image type");
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/jpeg_reader.cpp b/platform/default/src/mbgl/util/jpeg_reader.cpp
new file mode 100644
index 0000000000..5f613f9423
--- /dev/null
+++ b/platform/default/src/mbgl/util/jpeg_reader.cpp
@@ -0,0 +1,151 @@
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/char_array_buffer.hpp>
+
+#include <istream>
+#include <sstream>
+#include <array>
+
+extern "C"
+{
+#include <jpeglib.h>
+}
+
+namespace mbgl {
+
+const static unsigned BUF_SIZE = 4096;
+
+struct jpeg_stream_wrapper {
+ jpeg_source_mgr manager;
+ std::istream* stream;
+ std::array<JOCTET, BUF_SIZE> buffer;
+};
+
+static void init_source(j_decompress_ptr cinfo) {
+ auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src);
+ wrap->stream->seekg(0, std::ios_base::beg);
+}
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo) {
+ auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src);
+ wrap->stream->read(reinterpret_cast<char*>(&wrap->buffer[0]), BUF_SIZE);
+ std::streamsize size = wrap->stream->gcount();
+ wrap->manager.next_input_byte = wrap->buffer.data();
+ wrap->manager.bytes_in_buffer = BUF_SIZE;
+ return (size > 0) ? TRUE : FALSE;
+}
+
+static void skip(j_decompress_ptr cinfo, long count) {
+ if (count <= 0) return; // A zero or negative skip count should be treated as a no-op.
+ auto* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src);
+
+ if (wrap->manager.bytes_in_buffer > 0 && count < static_cast<long>(wrap->manager.bytes_in_buffer))
+ {
+ wrap->manager.bytes_in_buffer -= count;
+ wrap->manager.next_input_byte = &wrap->buffer[BUF_SIZE - wrap->manager.bytes_in_buffer];
+ }
+ else
+ {
+ wrap->stream->seekg(count - wrap->manager.bytes_in_buffer, std::ios_base::cur);
+ // trigger buffer fill
+ wrap->manager.next_input_byte = nullptr;
+ wrap->manager.bytes_in_buffer = 0; // bytes_in_buffer may be zero on return.
+ }
+}
+
+static void term(j_decompress_ptr) {}
+
+static void attach_stream(j_decompress_ptr cinfo, std::istream* in) {
+ if (cinfo->src == nullptr) {
+ cinfo->src = (struct jpeg_source_mgr *)
+ (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(jpeg_stream_wrapper));
+ }
+ auto * src = reinterpret_cast<jpeg_stream_wrapper*> (cinfo->src);
+ src->manager.init_source = init_source;
+ src->manager.fill_input_buffer = fill_input_buffer;
+ src->manager.skip_input_data = skip;
+ src->manager.resync_to_restart = jpeg_resync_to_restart;
+ src->manager.term_source = term;
+ src->manager.bytes_in_buffer = 0;
+ src->manager.next_input_byte = nullptr;
+ src->stream = in;
+}
+
+static void on_error(j_common_ptr) {}
+
+static void on_error_message(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+ throw std::runtime_error(std::string("JPEG Reader: libjpeg could not read image: ") + buffer);
+}
+
+struct jpeg_info_guard {
+ jpeg_info_guard(jpeg_decompress_struct* cinfo)
+ : i_(cinfo) {}
+
+ ~jpeg_info_guard() {
+ jpeg_destroy_decompress(i_);
+ }
+
+ jpeg_decompress_struct* i_;
+};
+
+PremultipliedImage decodeJPEG(const uint8_t* data, size_t size) {
+ util::CharArrayBuffer dataBuffer { reinterpret_cast<const char*>(data), size };
+ std::istream stream(&dataBuffer);
+
+ jpeg_decompress_struct cinfo;
+ jpeg_info_guard iguard(&cinfo);
+ jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jerr.error_exit = on_error;
+ jerr.output_message = on_error_message;
+ jpeg_create_decompress(&cinfo);
+ attach_stream(&cinfo, &stream);
+
+ int ret = jpeg_read_header(&cinfo, TRUE);
+ if (ret != JPEG_HEADER_OK)
+ throw std::runtime_error("JPEG Reader: failed to read header");
+
+ jpeg_start_decompress(&cinfo);
+
+ if (cinfo.out_color_space == JCS_UNKNOWN)
+ throw std::runtime_error("JPEG Reader: failed to read unknown color space");
+
+ if (cinfo.output_width == 0 || cinfo.output_height == 0)
+ throw std::runtime_error("JPEG Reader: failed to read image size");
+
+ size_t width = cinfo.output_width;
+ size_t height = cinfo.output_height;
+ size_t components = cinfo.output_components;
+ size_t rowStride = components * width;
+
+ PremultipliedImage image({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
+ uint8_t* dst = image.data.get();
+
+ JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, rowStride, 1);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ jpeg_read_scanlines(&cinfo, buffer, 1);
+
+ for (size_t i = 0; i < width; ++i) {
+ dst[0] = buffer[0][components * i];
+ dst[3] = 0xFF;
+
+ if (components > 2) {
+ dst[1] = buffer[0][components * i + 1];
+ dst[2] = buffer[0][components * i + 2];
+ } else {
+ dst[1] = dst[0];
+ dst[2] = dst[0];
+ }
+
+ dst += 4;
+ }
+ }
+
+ jpeg_finish_decompress(&cinfo);
+
+ return image;
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/logging_stderr.cpp b/platform/default/src/mbgl/util/logging_stderr.cpp
new file mode 100644
index 0000000000..41585fb7bb
--- /dev/null
+++ b/platform/default/src/mbgl/util/logging_stderr.cpp
@@ -0,0 +1,12 @@
+#include <mbgl/util/logging.hpp>
+#include <mbgl/util/enum.hpp>
+
+#include <iostream>
+
+namespace mbgl {
+
+void Log::platformRecord(EventSeverity severity, const std::string &msg) {
+ std::cerr << "[" << Enum<EventSeverity>::toString(severity) << "] " << msg << std::endl;
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/png_reader.cpp b/platform/default/src/mbgl/util/png_reader.cpp
new file mode 100644
index 0000000000..4d4ee29d1f
--- /dev/null
+++ b/platform/default/src/mbgl/util/png_reader.cpp
@@ -0,0 +1,142 @@
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/premultiply.hpp>
+#include <mbgl/util/char_array_buffer.hpp>
+#include <mbgl/util/logging.hpp>
+
+#include <istream>
+#include <sstream>
+
+extern "C"
+{
+#include <png.h>
+}
+
+template<size_t max, typename... Args>
+static std::string sprintf(const char *msg, Args... args) {
+ char res[max];
+ int len = snprintf(res, sizeof(res), msg, args...);
+ return std::string(res, len);
+}
+
+const static bool png_version_check __attribute__((unused)) = []() {
+ const png_uint_32 version = png_access_version_number();
+ if (version != PNG_LIBPNG_VER) {
+ throw std::runtime_error(sprintf<96>(
+ "libpng version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d",
+ PNG_LIBPNG_VER / 10000, (PNG_LIBPNG_VER / 100) % 100, PNG_LIBPNG_VER % 100,
+ version / 10000, (version / 100) % 100, version % 100));
+ }
+ return true;
+}();
+
+namespace mbgl {
+
+static void user_error_fn(png_structp, png_const_charp error_msg) {
+ throw std::runtime_error(std::string("failed to read invalid png: '") + error_msg + "'");
+}
+
+static void user_warning_fn(png_structp, png_const_charp warning_msg) {
+ Log::Warning(Event::Image, "ImageReader (PNG): %s", warning_msg);
+}
+
+static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
+ auto* fin = reinterpret_cast<std::istream*>(png_get_io_ptr(png_ptr));
+ fin->read(reinterpret_cast<char*>(data), length);
+ std::streamsize read_count = fin->gcount();
+ if (read_count < 0 || static_cast<png_size_t>(read_count) != length)
+ {
+ png_error(png_ptr, "Read Error");
+ }
+}
+
+struct png_struct_guard {
+ png_struct_guard(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr)
+ : p_(png_ptr_ptr),
+ i_(info_ptr_ptr) {}
+
+ ~png_struct_guard() {
+ png_destroy_read_struct(p_,i_,nullptr);
+ }
+
+ png_structpp p_;
+ png_infopp i_;
+};
+
+PremultipliedImage decodePNG(const uint8_t* data, size_t size) {
+ util::CharArrayBuffer dataBuffer { reinterpret_cast<const char*>(data), size };
+ std::istream stream(&dataBuffer);
+
+ png_byte header[8] = { 0 };
+ stream.read(reinterpret_cast<char*>(header), 8);
+ if (stream.gcount() != 8)
+ throw std::runtime_error("PNG reader: Could not read image");
+
+ int is_png = !png_sig_cmp(header, 0, 8);
+ if (!is_png)
+ throw std::runtime_error("File or stream is not a png");
+
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!png_ptr)
+ throw std::runtime_error("failed to allocate png_ptr");
+
+ // catch errors in a custom way to avoid the need for setjmp
+ png_set_error_fn(png_ptr, png_get_error_ptr(png_ptr), user_error_fn, user_warning_fn);
+
+ png_infop info_ptr;
+ png_struct_guard sguard(&png_ptr, &info_ptr);
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ throw std::runtime_error("failed to create info_ptr");
+
+ png_set_read_fn(png_ptr, &stream, png_read_data);
+ png_set_sig_bytes(png_ptr, 8);
+ png_read_info(png_ptr, info_ptr);
+
+ png_uint_32 width = 0;
+ png_uint_32 height = 0;
+ int bit_depth = 0;
+ int color_type = 0;
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
+
+ UnassociatedImage image({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_expand(png_ptr);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+ png_set_expand(png_ptr);
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_expand(png_ptr);
+
+ if (bit_depth == 16)
+ png_set_strip_16(png_ptr);
+
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(png_ptr);
+
+ png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
+
+ if (png_get_interlace_type(png_ptr,info_ptr) == PNG_INTERLACE_ADAM7) {
+ png_set_interlace_handling(png_ptr); // FIXME: libpng bug?
+ // according to docs png_read_image
+ // "..automatically handles interlacing,
+ // so you don't need to call png_set_interlace_handling()"
+ }
+
+ png_read_update_info(png_ptr, info_ptr);
+
+ // we can read whole image at once
+ // alloc row pointers
+ const std::unique_ptr<png_bytep[]> rows(new png_bytep[height]);
+ for (unsigned row = 0; row < height; ++row)
+ rows[row] = image.data.get() + row * width * 4;
+ png_read_image(png_ptr, rows.get());
+
+ png_read_end(png_ptr, nullptr);
+
+ return util::premultiply(std::move(image));
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/png_writer.cpp b/platform/default/src/mbgl/util/png_writer.cpp
new file mode 100644
index 0000000000..b89e253f85
--- /dev/null
+++ b/platform/default/src/mbgl/util/png_writer.cpp
@@ -0,0 +1,77 @@
+#include <mbgl/util/compression.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/premultiply.hpp>
+
+#include <boost/crc.hpp>
+
+#include <cassert>
+#include <cstring>
+
+#define NETWORK_BYTE_UINT32(value) \
+ char(value >> 24), char(value >> 16), char(value >> 8), char(value >> 0)
+
+namespace {
+
+void addChunk(std::string& png, const char* type, const char* data = "", const uint32_t size = 0) {
+ assert(strlen(type) == 4);
+
+ // Checksum encompasses type + data
+ boost::crc_32_type checksum;
+ checksum.process_bytes(type, 4);
+ checksum.process_bytes(data, size);
+
+ const char length[4] = { NETWORK_BYTE_UINT32(size) };
+ const char crc[4] = { NETWORK_BYTE_UINT32(checksum.checksum()) };
+
+ png.reserve(png.size() + 4 /* length */ + 4 /* type */ + size + 4 /* CRC */);
+ png.append(length, 4);
+ png.append(type, 4);
+ png.append(data, size);
+ png.append(crc, 4);
+}
+
+} // namespace
+
+namespace mbgl {
+
+// Encode PNGs without libpng.
+std::string encodePNG(const PremultipliedImage& pre) {
+ // Make copy of the image so that we can unpremultiply it.
+ const auto src = util::unpremultiply(pre.clone());
+
+ // PNG magic bytes
+ const char preamble[8] = { char(0x89), 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' };
+
+ // IHDR chunk for our RGBA image.
+ const char ihdr[13] = {
+ NETWORK_BYTE_UINT32(src.size.width), // width
+ NETWORK_BYTE_UINT32(src.size.height), // height
+ 8, // bit depth == 8 bits
+ 6, // color type == RGBA
+ 0, // compression method == deflate
+ 0, // filter method == default
+ 0, // interlace method == none
+ };
+
+ // Prepare the (compressed) data chunk.
+ const auto stride = src.stride();
+ std::string idat;
+ for (uint32_t y = 0; y < src.size.height; y++) {
+ // Every scanline needs to be prefixed with one byte that indicates the filter type.
+ idat.append(1, 0); // filter type 0
+ idat.append((const char*)(src.data.get() + y * stride), stride);
+ }
+ idat = util::compress(idat);
+
+ // Assemble the PNG.
+ std::string png;
+ png.reserve((8 /* preamble */) + (12 + 13 /* IHDR */) +
+ (12 + idat.size() /* IDAT */) + (12 /* IEND */));
+ png.append(preamble, 8);
+ addChunk(png, "IHDR", ihdr, 13);
+ addChunk(png, "IDAT", idat.data(), static_cast<uint32_t>(idat.size()));
+ addChunk(png, "IEND");
+ return png;
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/run_loop.cpp b/platform/default/src/mbgl/util/run_loop.cpp
new file mode 100644
index 0000000000..868ee72114
--- /dev/null
+++ b/platform/default/src/mbgl/util/run_loop.cpp
@@ -0,0 +1,219 @@
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/async_task.hpp>
+#include <mbgl/util/thread_local.hpp>
+#include <mbgl/actor/scheduler.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+#include <functional>
+#include <unordered_map>
+
+namespace {
+
+void dummyCallback(uv_async_t*) {}
+
+} // namespace
+
+namespace mbgl {
+namespace util {
+
+struct Watch {
+ static void onEvent(uv_poll_t* poll, int, int event) {
+ auto watch = reinterpret_cast<Watch*>(poll->data);
+
+ RunLoop::Event watchEvent = RunLoop::Event::None;
+ switch (event) {
+ case UV_READABLE:
+ watchEvent = RunLoop::Event::Read;
+ break;
+ case UV_WRITABLE:
+ watchEvent = RunLoop::Event::Write;
+ break;
+ case UV_READABLE | UV_WRITABLE:
+ watchEvent = RunLoop::Event::ReadWrite;
+ break;
+ }
+
+ watch->eventCallback(watch->fd, watchEvent);
+ };
+
+ static void onClose(uv_handle_t *poll) {
+ auto watch = reinterpret_cast<Watch*>(poll->data);
+ watch->closeCallback();
+ };
+
+ uv_poll_t poll;
+ int fd;
+
+ std::function<void(int, RunLoop::Event)> eventCallback;
+ std::function<void()> closeCallback;
+};
+
+RunLoop* RunLoop::Get() {
+ assert(static_cast<RunLoop*>(Scheduler::GetCurrent()));
+ return static_cast<RunLoop*>(Scheduler::GetCurrent());
+}
+
+class RunLoop::Impl {
+public:
+ void closeHolder() {
+ uv_close(holderHandle(), [](uv_handle_t* h) {
+ delete reinterpret_cast<uv_async_t*>(h);
+ });
+ }
+
+ uv_handle_t* holderHandle() {
+ return reinterpret_cast<uv_handle_t*>(holder);
+ }
+
+ uv_loop_t *loop = nullptr;
+ uv_async_t* holder = new uv_async_t;
+
+ RunLoop::Type type;
+ std::unique_ptr<AsyncTask> async;
+
+ std::unordered_map<int, std::unique_ptr<Watch>> watchPoll;
+};
+
+RunLoop::RunLoop(Type type) : impl(std::make_unique<Impl>()) {
+ switch (type) {
+ case Type::New:
+ impl->loop = new uv_loop_t;
+ if (uv_loop_init(impl->loop) != 0) {
+ throw std::runtime_error("Failed to initialize loop.");
+ }
+ break;
+ case Type::Default:
+ impl->loop = uv_default_loop();
+ break;
+ }
+
+ // Just for holding a ref to the main loop and keep
+ // it alive as required by libuv.
+ if (uv_async_init(impl->loop, impl->holder, dummyCallback) != 0) {
+ throw std::runtime_error("Failed to initialize async.");
+ }
+
+ impl->type = type;
+
+ Scheduler::SetCurrent(this);
+ impl->async = std::make_unique<AsyncTask>(std::bind(&RunLoop::process, this));
+}
+
+RunLoop::~RunLoop() {
+ Scheduler::SetCurrent(nullptr);
+
+ // Close the dummy handle that we have
+ // just to keep the main loop alive.
+ impl->closeHolder();
+
+ if (impl->type == Type::Default) {
+ return;
+ }
+
+ // Run the loop again to ensure that async
+ // close callbacks have been called. Not needed
+ // for the default main loop because it is only
+ // closed when the application exits.
+ impl->async.reset();
+ runOnce();
+
+ if (uv_loop_close(impl->loop) == UV_EBUSY) {
+ assert(false && "Failed to close loop.");
+ }
+ delete impl->loop;
+}
+
+LOOP_HANDLE RunLoop::getLoopHandle() {
+ return Get()->impl->loop;
+}
+
+void RunLoop::wake() {
+ impl->async->send();
+}
+
+void RunLoop::run() {
+ MBGL_VERIFY_THREAD(tid);
+
+ uv_ref(impl->holderHandle());
+ uv_run(impl->loop, UV_RUN_DEFAULT);
+}
+
+void RunLoop::runOnce() {
+ MBGL_VERIFY_THREAD(tid);
+
+ uv_run(impl->loop, UV_RUN_NOWAIT);
+}
+
+void RunLoop::stop() {
+ invoke([&] { uv_unref(impl->holderHandle()); });
+}
+
+void RunLoop::addWatch(int fd, Event event, std::function<void(int, Event)>&& callback) {
+ MBGL_VERIFY_THREAD(tid);
+
+ Watch *watch = nullptr;
+ auto watchPollIter = impl->watchPoll.find(fd);
+
+ if (watchPollIter == impl->watchPoll.end()) {
+ std::unique_ptr<Watch> watchPtr = std::make_unique<Watch>();
+
+ watch = watchPtr.get();
+ impl->watchPoll[fd] = std::move(watchPtr);
+
+ if (uv_poll_init(impl->loop, &watch->poll, fd)) {
+ throw std::runtime_error("Failed to init poll on file descriptor.");
+ }
+ } else {
+ watch = watchPollIter->second.get();
+ }
+
+ watch->poll.data = watch;
+ watch->fd = fd;
+ watch->eventCallback = std::move(callback);
+
+ int pollEvent = 0;
+ switch (event) {
+ case Event::Read:
+ pollEvent = UV_READABLE;
+ break;
+ case Event::Write:
+ pollEvent = UV_WRITABLE;
+ break;
+ case Event::ReadWrite:
+ pollEvent = UV_READABLE | UV_WRITABLE;
+ break;
+ default:
+ throw std::runtime_error("Unhandled event.");
+ }
+
+ if (uv_poll_start(&watch->poll, pollEvent, &Watch::onEvent)) {
+ throw std::runtime_error("Failed to start poll on file descriptor.");
+ }
+}
+
+void RunLoop::removeWatch(int fd) {
+ MBGL_VERIFY_THREAD(tid);
+
+ auto watchPollIter = impl->watchPoll.find(fd);
+ if (watchPollIter == impl->watchPoll.end()) {
+ return;
+ }
+
+ Watch* watch = watchPollIter->second.release();
+ impl->watchPoll.erase(watchPollIter);
+
+ watch->closeCallback = [watch] {
+ delete watch;
+ };
+
+ if (uv_poll_stop(&watch->poll)) {
+ throw std::runtime_error("Failed to stop poll on file descriptor.");
+ }
+
+ uv_close(reinterpret_cast<uv_handle_t*>(&watch->poll), &Watch::onClose);
+}
+
+} // namespace util
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/shared_thread_pool.cpp b/platform/default/src/mbgl/util/shared_thread_pool.cpp
new file mode 100644
index 0000000000..d7facbab94
--- /dev/null
+++ b/platform/default/src/mbgl/util/shared_thread_pool.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/util/shared_thread_pool.hpp>
+
+namespace mbgl {
+
+std::shared_ptr<ThreadPool> sharedThreadPool() {
+ static std::weak_ptr<ThreadPool> weak;
+ auto pool = weak.lock();
+ if (!pool) {
+ weak = pool = std::make_shared<ThreadPool>(4);
+ }
+ return pool;
+}
+
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/string_stdlib.cpp b/platform/default/src/mbgl/util/string_stdlib.cpp
new file mode 100644
index 0000000000..103444df1c
--- /dev/null
+++ b/platform/default/src/mbgl/util/string_stdlib.cpp
@@ -0,0 +1,74 @@
+#include <mbgl/util/platform.hpp>
+#include <libnu/casemap.h>
+#include <cstring>
+#include <sstream>
+
+namespace mbgl { namespace platform {
+
+std::string uppercase(const std::string& str)
+{
+ std::stringstream output;
+ char const *itr = str.c_str(), *nitr;
+ char const *end = itr + str.length();
+ char lo[5] = { 0 };
+
+ for (; itr < end; itr = nitr)
+ {
+ uint32_t code_point = 0;
+ char const* buf = nullptr;
+
+ nitr = _nu_toupper(itr, end, nu_utf8_read, &code_point, &buf, nullptr);
+ if (buf != nullptr)
+ {
+ do
+ {
+ buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point);
+ if (code_point == 0) break;
+ output.write(lo, nu_utf8_write(code_point, lo) - lo);
+ }
+ while (code_point != 0);
+ }
+ else
+ {
+ output.write(itr, nitr - itr);
+ }
+ }
+
+ return output.str();
+
+}
+
+std::string lowercase(const std::string& str)
+{
+ std::stringstream output;
+ char const *itr = str.c_str(), *nitr;
+ char const *end = itr + str.length();
+ char lo[5] = { 0 };
+
+ for (; itr < end; itr = nitr)
+ {
+ uint32_t code_point = 0;
+ char const* buf = nullptr;
+
+ nitr = _nu_tolower(itr, end, nu_utf8_read, &code_point, &buf, nullptr);
+ if (buf != nullptr)
+ {
+ do
+ {
+ buf = NU_CASEMAP_DECODING_FUNCTION(buf, &code_point);
+ if (code_point == 0) break;
+ output.write(lo, nu_utf8_write(code_point, lo) - lo);
+ }
+ while (code_point != 0);
+ }
+ else
+ {
+ output.write(itr, nitr - itr);
+ }
+ }
+
+ return output.str();
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/thread.cpp b/platform/default/src/mbgl/util/thread.cpp
new file mode 100644
index 0000000000..c7c79b4fb0
--- /dev/null
+++ b/platform/default/src/mbgl/util/thread.cpp
@@ -0,0 +1,37 @@
+#include <mbgl/util/platform.hpp>
+#include <mbgl/util/logging.hpp>
+
+#include <string>
+
+#include <pthread.h>
+#include <sched.h>
+
+namespace mbgl {
+namespace platform {
+
+std::string getCurrentThreadName() {
+ char name[32] = "unknown";
+ pthread_getname_np(pthread_self(), name, sizeof(name));
+
+ return name;
+}
+
+void setCurrentThreadName(const std::string& name) {
+ if (name.size() > 15) { // Linux hard limit (see manpages).
+ pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
+ } else {
+ pthread_setname_np(pthread_self(), name.c_str());
+ }
+}
+
+void makeThreadLowPriority() {
+ struct sched_param param;
+ param.sched_priority = 0;
+
+ if (sched_setscheduler(0, SCHED_IDLE, &param) != 0) {
+ Log::Warning(Event::General, "Couldn't set thread scheduling policy");
+ }
+}
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/thread_local.cpp b/platform/default/src/mbgl/util/thread_local.cpp
new file mode 100644
index 0000000000..db70773c12
--- /dev/null
+++ b/platform/default/src/mbgl/util/thread_local.cpp
@@ -0,0 +1,66 @@
+#include <mbgl/util/thread_local.hpp>
+
+#include <mbgl/renderer/backend_scope.hpp>
+#include <mbgl/util/logging.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#include <stdexcept>
+#include <cassert>
+
+#include <pthread.h>
+
+namespace mbgl {
+namespace util {
+
+template <class T>
+class ThreadLocal<T>::Impl {
+public:
+ pthread_key_t key;
+};
+
+template <class T>
+ThreadLocal<T>::ThreadLocal() : impl(std::make_unique<Impl>()) {
+ int ret = pthread_key_create(&impl->key, [](void *) {});
+
+ if (ret) {
+ throw std::runtime_error("Failed to init local storage key.");
+ }
+}
+
+template <class T>
+ThreadLocal<T>::~ThreadLocal() {
+ // ThreadLocal will not take ownership
+ // of the pointer it is managing. The pointer
+ // needs to be explicitly cleared before we
+ // destroy this object.
+ assert(!get());
+
+ if (pthread_key_delete(impl->key)) {
+ Log::Error(Event::General, "Failed to delete local storage key.");
+ assert(false);
+ }
+}
+
+template <class T>
+T* ThreadLocal<T>::get() {
+ auto* ret = reinterpret_cast<T*>(pthread_getspecific(impl->key));
+ if (!ret) {
+ return nullptr;
+ }
+
+ return ret;
+}
+
+template <class T>
+void ThreadLocal<T>::set(T* ptr) {
+ if (pthread_setspecific(impl->key, ptr)) {
+ throw std::runtime_error("Failed to set local storage.");
+ }
+}
+
+template class ThreadLocal<BackendScope>;
+template class ThreadLocal<Scheduler>;
+template class ThreadLocal<int>; // For unit tests
+
+} // namespace util
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/timer.cpp b/platform/default/src/mbgl/util/timer.cpp
new file mode 100644
index 0000000000..90a85bfc1f
--- /dev/null
+++ b/platform/default/src/mbgl/util/timer.cpp
@@ -0,0 +1,73 @@
+#include <mbgl/util/timer.hpp>
+
+#include <mbgl/util/run_loop.hpp>
+
+#include <uv.h>
+
+namespace mbgl {
+namespace util {
+
+class Timer::Impl {
+public:
+ Impl() : timer(new uv_timer_t) {
+ auto* loop = reinterpret_cast<uv_loop_t*>(RunLoop::getLoopHandle());
+ if (uv_timer_init(loop, timer) != 0) {
+ throw std::runtime_error("Failed to initialize timer.");
+ }
+
+ handle()->data = this;
+ uv_unref(handle());
+ }
+
+ ~Impl() {
+ uv_close(handle(), [](uv_handle_t* h) {
+ delete reinterpret_cast<uv_timer_t*>(h);
+ });
+ }
+
+ void start(uint64_t timeout, uint64_t repeat, std::function<void ()>&& cb_) {
+ cb = std::move(cb_);
+ if (uv_timer_start(timer, timerCallback, timeout, repeat) != 0) {
+ throw std::runtime_error("Failed to start timer.");
+ }
+ }
+
+ void stop() {
+ cb = nullptr;
+ if (uv_timer_stop(timer) != 0) {
+ throw std::runtime_error("Failed to stop timer.");
+ }
+ }
+
+private:
+ static void timerCallback(uv_timer_t* handle) {
+ reinterpret_cast<Impl*>(handle->data)->cb();
+ }
+
+ uv_handle_t* handle() {
+ return reinterpret_cast<uv_handle_t*>(timer);
+ }
+
+ uv_timer_t* timer;
+
+ std::function<void()> cb;
+};
+
+Timer::Timer()
+ : impl(std::make_unique<Impl>()) {
+}
+
+Timer::~Timer() = default;
+
+void Timer::start(Duration timeout, Duration repeat, std::function<void()>&& cb) {
+ impl->start(std::chrono::duration_cast<Milliseconds>(timeout).count(),
+ std::chrono::duration_cast<Milliseconds>(repeat).count(),
+ std::move(cb));
+}
+
+void Timer::stop() {
+ impl->stop();
+}
+
+} // namespace util
+} // namespace mbgl
diff --git a/platform/default/src/mbgl/util/utf.cpp b/platform/default/src/mbgl/util/utf.cpp
new file mode 100644
index 0000000000..f0f9d3e67a
--- /dev/null
+++ b/platform/default/src/mbgl/util/utf.cpp
@@ -0,0 +1,17 @@
+#include <mbgl/util/utf.hpp>
+
+#include <boost/locale/encoding_utf.hpp>
+
+namespace mbgl {
+namespace util {
+
+std::u16string convertUTF8ToUTF16(const std::string& str) {
+ return boost::locale::conv::utf_to_utf<char16_t>(str);
+}
+
+std::string convertUTF16ToUTF8(const std::u16string& str) {
+ return boost::locale::conv::utf_to_utf<char>(str);
+}
+
+} // namespace util
+} // namespace mbgl