move most private Map code to MapContext

This commit is contained in:
Konstantin Käfer
2015-03-24 15:43:08 +01:00
committed by John Firebaugh
parent 9dc51e7f80
commit b0a30943b4
19 changed files with 482 additions and 432 deletions

View File

@@ -287,14 +287,7 @@ void JNICALL nativeUpdate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeUpdate");
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
nativeMapView->getMap().triggerUpdate();
}
void JNICALL nativeTerminate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminate");
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
nativeMapView->getMap().terminate();
nativeMapView->getMap().update();
}
void JNICALL nativeResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height,
@@ -943,7 +936,6 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
{"nativePause", "(J)V", reinterpret_cast<void *>(&nativePause)},
{"nativeResume", "(J)V", reinterpret_cast<void *>(&nativeResume)},
{"nativeUpdate", "(J)V", reinterpret_cast<void *>(&nativeUpdate)},
{"nativeTerminate", "(J)V", reinterpret_cast<void *>(&nativeTerminate)},
{"nativeResize", "(JIIFII)V",
reinterpret_cast<void *>(static_cast<void JNICALL (
*)(JNIEnv *, jobject, jlong, jint, jint, jfloat, jint, jint)>(&nativeResize))},

View File

@@ -121,11 +121,11 @@ void NativeMapView::deactivate() {
}
}
void NativeMapView::invalidate() {
void NativeMapView::invalidate(std::function<void()> render) {
mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::invalidate");
if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) {
map.render();
render();
if (!eglSwapBuffers(display, surface)) {
mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapBuffers() returned error %d",

View File

@@ -396,8 +396,6 @@ class NativeMapView {
private native void nativeUpdate(long nativeMapViewPtr);
private native void nativeTerminate(long nativeMapViewPtr);
private native void nativeResize(long nativeMapViewPtr, int width,
int height, float ratio, int fbWidth, int fbHeight);

View File

@@ -23,7 +23,7 @@ public:
void activate() override;
void deactivate() override;
void notify() override;
void invalidate() override;
void invalidate(std::function<void()> render) override;
void notifyMapChange(mbgl::MapChange change, Duration delay = Duration::zero()) override;

View File

@@ -25,7 +25,6 @@ namespace uv { class async; }
namespace mbgl {
class Sprite;
class FileSource;
class View;
class Environment;
@@ -65,15 +64,8 @@ public:
// Triggers a synchronous or asynchronous render.
void renderSync();
// Unconditionally performs a render with the current map state. May only be called from the Map
// thread.
void render();
// Notifies the Map thread that the state has changed and an update might be necessary.
void triggerUpdate(Update = Update::Nothing);
// Releases resources immediately
void terminate();
void update();
// Styling
void addClass(const std::string&);
@@ -171,29 +163,7 @@ private:
// Setup
void setup();
void updateTiles();
// Triggered by triggerUpdate();
void update();
// Loads the style set in the data object. Called by Update::StyleInfo
void reloadStyle();
void loadStyleJSON(const std::string& json, const std::string& base);
// Prepares a map render by updating the tiles we need for the current view, as well as updating
// the stylesheet.
void prepare();
// Runs the function in the map thread.
void invokeTask(std::function<void()>&&);
template <typename Fn> auto invokeSyncTask(const Fn& fn) -> decltype(fn());
void processTasks();
void updateAnnotationTiles(const std::vector<TileID>&);
size_t sourceCacheSize;
const std::unique_ptr<Environment> env;
std::unique_ptr<EnvironmentScope> scope;
View &view;
@@ -202,11 +172,7 @@ private:
private:
std::thread thread;
std::unique_ptr<uv::async> asyncTerminate;
std::unique_ptr<uv::async> asyncUpdate;
std::unique_ptr<uv::async> asyncInvoke;
bool terminating = false;
bool pausing = false;
bool isPaused = false;
std::mutex mutexRun;
@@ -214,18 +180,9 @@ private:
std::mutex mutexPause;
std::condition_variable condPause;
// Used to signal that rendering completed.
bool rendered = false;
std::condition_variable condRendered;
std::mutex mutexRendered;
// Stores whether the map thread has been stopped already.
std::atomic_bool isStopped;
std::atomic<UpdateType> updated;
std::mutex mutexTask;
std::queue<std::function<void()>> tasks;
StillImageCallback callback;
};

View File

@@ -2,6 +2,7 @@
#define MBGL_MAP_VIEW
#include <mbgl/util/chrono.hpp>
#include <functional>
#include <memory>
@@ -40,8 +41,9 @@ public:
virtual void notify() = 0;
// Called from the render thread. The implementation must trigger a rerender.
// (i.e. map->renderSync() or map->renderAsync() must be called as a result of this)
virtual void invalidate() = 0;
// (i.e. either the passed render() function for rendering immediately on the map thread,
// or map->renderSync() from the main thread must be called as a result of this)
virtual void invalidate(std::function<void()> render) = 0;
// Called from the render (=GL) thread. Signals that the contents of the contents
// may be discarded. The default is a no-op.

View File

@@ -17,7 +17,7 @@ public:
void activate() override;
void deactivate() override;
void notify() override;
void invalidate() override;
void invalidate(std::function<void()> render) override;
static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods);
static void onScroll(GLFWwindow *window, double xoffset, double yoffset);

View File

@@ -33,7 +33,7 @@ public:
void activate() override;
void deactivate() override;
void notify() override;
void invalidate() override;
void invalidate(std::function<void()> render) override;
void discard() override;
std::unique_ptr<StillImage> readStillImage() override;

View File

@@ -275,9 +275,6 @@ int GLFWView::run() {
glfwWaitEvents();
});
// Terminate here to save binary shaders
map->terminate();
return 0;
}
@@ -293,9 +290,8 @@ void GLFWView::notify() {
glfwPostEmptyEvent();
}
void GLFWView::invalidate() {
assert(map);
map->render();
void GLFWView::invalidate(std::function<void()> render) {
render();
glfwSwapBuffers(window);
fps();
}

View File

@@ -328,9 +328,8 @@ void HeadlessView::deactivate() {
#endif
}
void HeadlessView::invalidate() {
assert(map);
map->render();
void HeadlessView::invalidate(std::function<void()> render) {
render();
}
}

View File

@@ -645,7 +645,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
if ( ! _isTargetingInterfaceBuilder)
{
mbglMap->triggerUpdate();
mbglMap->update();
}
}
@@ -2532,7 +2532,7 @@ class MBGLView : public mbgl::View
View::resize(width, height, ratio, fbWidth, fbHeight);
}
void invalidate() override
void invalidate(std::function<void()>) override
{
[nativeView performSelectorOnMainThread:@selector(invalidate)
withObject:nil

View File

@@ -1,4 +1,5 @@
#include <mbgl/map/map.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/map_context.hpp>
#include <mbgl/map/view.hpp>
@@ -66,8 +67,7 @@ Map::Map(View& view_, FileSource& fileSource_)
scope(util::make_unique<EnvironmentScope>(*env, ThreadType::Main, "Main")),
view(view_),
data(util::make_unique<MapData>(view_)),
context(util::make_unique<MapContext>(*env, *data)),
updated(static_cast<UpdateType>(Update::Nothing))
context(util::make_unique<MapContext>(*env, view, *data))
{
view.initialize(this);
}
@@ -104,51 +104,7 @@ void Map::start(bool startPaused, MapMode renderMode) {
// Reset the flag.
isStopped = false;
// Setup async notifications
asyncTerminate = util::make_unique<uv::async>(env->loop, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
// Remove all of these to make sure they are destructed in the correct thread.
context->style.reset();
// It's now safe to destroy/join the workers since there won't be any more callbacks that
// could dispatch to the worker pool.
context->workers.reset();
terminating = true;
// Closes all open handles on the loop. This means that the loop will automatically terminate.
context->asyncRender.reset();
asyncUpdate.reset();
asyncInvoke.reset();
asyncTerminate.reset();
});
asyncUpdate = util::make_unique<uv::async>(env->loop, [this] {
// Whenever we call triggerUpdate(), we ref() the asyncUpdate handle to make sure that all
// of the calls actually get triggered.
asyncUpdate->unref();
update();
});
asyncInvoke = util::make_unique<uv::async>(env->loop, [this] {
processTasks();
});
context->asyncRender = util::make_unique<uv::async>(env->loop, [this] {
// Must be called in Map thread.
assert(Environment::currentlyOn(ThreadType::Map));
render();
// Finally, notify all listeners that we have finished rendering this frame.
{
std::lock_guard<std::mutex> lk(mutexRendered);
rendered = true;
}
condRendered.notify_all();
});
context->start();
// Do we need to pause first?
if (startPaused) {
@@ -167,14 +123,14 @@ void Map::start(bool startPaused, MapMode renderMode) {
view.notify();
});
triggerUpdate();
context->triggerUpdate();
}
void Map::stop(std::function<void ()> cb) {
assert(Environment::currentlyOn(ThreadType::Main));
assert(data->mode != MapMode::None);
asyncTerminate->send();
context->terminate();
resume();
@@ -205,7 +161,7 @@ void Map::pause(bool waitForPause) {
mutexRun.unlock();
uv_stop(env->loop);
triggerUpdate(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause
context->triggerUpdate(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause
if (waitForPause) {
std::unique_lock<std::mutex> lockPause (mutexPause);
@@ -240,7 +196,7 @@ void Map::renderStill(StillImageCallback fn) {
callback = std::move(fn);
triggerUpdate(Update::RenderStill);
context->triggerUpdate(Update::RenderStill);
}
void Map::run() {
@@ -258,25 +214,25 @@ void Map::run() {
context->workers = util::make_unique<Worker>(env->loop, 4);
setup();
prepare();
context->prepare();
if (data->mode == MapMode::Continuous) {
terminating = false;
while (!terminating) {
context->terminating = false;
while (!context->terminating) {
uv_run(env->loop, UV_RUN_DEFAULT);
checkForPause();
}
} else if (data->mode == MapMode::Still) {
terminating = false;
while (!terminating) {
context->terminating = false;
while (!context->terminating) {
uv_run(env->loop, UV_RUN_DEFAULT);
// After the loop terminated, these async handles may have been deleted if the terminate()
// callback was fired. In this case, we are exiting the loop.
if (asyncTerminate && asyncUpdate) {
if (context->asyncTerminate && context->asyncUpdate) {
// Otherwise, loop termination means that we have acquired and parsed all resources
// required for this map image and we can now proceed to rendering.
render();
context->render();
auto image = view.readStillImage();
// We are moving the callback out of the way and empty it in case the callback function
@@ -290,9 +246,9 @@ void Map::run() {
// To prepare for the next event loop run, we have to make sure the async handles keep
// the loop alive.
asyncTerminate->ref();
asyncUpdate->ref();
asyncInvoke->ref();
context->asyncTerminate->ref();
context->asyncUpdate->ref();
context->asyncInvoke->ref();
context->asyncRender->ref();
}
}
@@ -312,41 +268,7 @@ void Map::renderSync() {
context->triggerRender();
std::unique_lock<std::mutex> lock(mutexRendered);
condRendered.wait(lock, [this] { return rendered; });
rendered = false;
}
// Runs the function in the map thread.
void Map::invokeTask(std::function<void()>&& fn) {
{
std::lock_guard<std::mutex> lock(mutexTask);
tasks.emplace(::std::forward<std::function<void()>>(fn));
}
if (asyncInvoke) {
asyncInvoke->send();
}
}
template <typename Fn> auto Map::invokeSyncTask(const Fn& fn) -> decltype(fn()) {
std::promise<decltype(fn())> promise;
invokeTask([&fn, &promise] { promise.set_value(fn()); });
return promise.get_future().get();
}
// Processes the functions that should be run in the map thread.
void Map::processTasks() {
std::queue<std::function<void()>> queue;
{
std::lock_guard<std::mutex> lock(mutexTask);
queue.swap(tasks);
}
while (!queue.empty()) {
queue.front()();
queue.pop();
}
context->rendered.wait();
}
void Map::checkForPause() {
@@ -369,10 +291,8 @@ void Map::checkForPause() {
mutexPause.unlock();
}
void Map::terminate() {
assert(context->painter);
context->painter->terminate();
view.deactivate();
void Map::update() {
context->triggerUpdate();
}
#pragma mark - Setup
@@ -399,14 +319,14 @@ void Map::setStyleURL(const std::string &url) {
}
data->setStyleInfo({ styleURL, base, "" });
triggerUpdate(Update::StyleInfo);
context->triggerUpdate(Update::StyleInfo);
}
void Map::setStyleJSON(const std::string& json, const std::string& base) {
assert(Environment::currentlyOn(ThreadType::Main));
data->setStyleInfo({ "", base, json });
triggerUpdate(Update::StyleInfo);
context->triggerUpdate(Update::StyleInfo);
}
std::string Map::getStyleJSON() const {
@@ -421,7 +341,7 @@ void Map::resize(uint16_t width, uint16_t height, float ratio) {
void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) {
if (data->transform.resize(width, height, ratio, fbWidth, fbHeight)) {
triggerUpdate();
context->triggerUpdate();
}
}
@@ -429,24 +349,24 @@ void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth,
void Map::cancelTransitions() {
data->transform.cancelTransitions();
triggerUpdate();
context->triggerUpdate();
}
void Map::setGestureInProgress(bool inProgress) {
data->transform.setGestureInProgress(inProgress);
triggerUpdate();
context->triggerUpdate();
}
#pragma mark - Position
void Map::moveBy(double dx, double dy, Duration duration) {
data->transform.moveBy(dx, dy, duration);
triggerUpdate();
context->triggerUpdate();
}
void Map::setLatLng(LatLng latLng, Duration duration) {
data->transform.setLatLng(latLng, duration);
triggerUpdate();
context->triggerUpdate();
}
LatLng Map::getLatLng() const {
@@ -457,7 +377,7 @@ void Map::resetPosition() {
data->transform.setAngle(0);
data->transform.setLatLng(LatLng(0, 0));
data->transform.setZoom(0);
triggerUpdate(Update::Zoom);
context->triggerUpdate(Update::Zoom);
}
@@ -465,12 +385,12 @@ void Map::resetPosition() {
void Map::scaleBy(double ds, double cx, double cy, Duration duration) {
data->transform.scaleBy(ds, cx, cy, duration);
triggerUpdate(Update::Zoom);
context->triggerUpdate(Update::Zoom);
}
void Map::setScale(double scale, double cx, double cy, Duration duration) {
data->transform.setScale(scale, cx, cy, duration);
triggerUpdate(Update::Zoom);
context->triggerUpdate(Update::Zoom);
}
double Map::getScale() const {
@@ -479,7 +399,7 @@ double Map::getScale() const {
void Map::setZoom(double zoom, Duration duration) {
data->transform.setZoom(zoom, duration);
triggerUpdate(Update::Zoom);
context->triggerUpdate(Update::Zoom);
}
double Map::getZoom() const {
@@ -488,13 +408,24 @@ double Map::getZoom() const {
void Map::setLatLngZoom(LatLng latLng, double zoom, Duration duration) {
data->transform.setLatLngZoom(latLng, zoom, duration);
triggerUpdate(Update::Zoom);
context->triggerUpdate(Update::Zoom);
}
void Map::resetZoom() {
setZoom(0);
}
double Map::getMinZoom() const {
return data->transform.getMinZoom();
}
double Map::getMaxZoom() const {
return data->transform.getMaxZoom();
}
#pragma mark - Size
uint16_t Map::getWidth() const {
return data->getTransformState().getWidth();
}
@@ -508,17 +439,17 @@ uint16_t Map::getHeight() const {
void Map::rotateBy(double sx, double sy, double ex, double ey, Duration duration) {
data->transform.rotateBy(sx, sy, ex, ey, duration);
triggerUpdate();
context->triggerUpdate();
}
void Map::setBearing(double degrees, Duration duration) {
data->transform.setAngle(-degrees * M_PI / 180, duration);
triggerUpdate();
context->triggerUpdate();
}
void Map::setBearing(double degrees, double cx, double cy) {
data->transform.setAngle(-degrees * M_PI / 180, cx, cy);
triggerUpdate();
context->triggerUpdate();
}
double Map::getBearing() const {
@@ -527,22 +458,10 @@ double Map::getBearing() const {
void Map::resetNorth() {
data->transform.setAngle(0, std::chrono::milliseconds(500));
triggerUpdate();
context->triggerUpdate();
}
#pragma mark - Rotation
double Map::getMinZoom() const {
return data->transform.getMinZoom();
}
double Map::getMaxZoom() const {
return data->transform.getMaxZoom();
}
#pragma mark - Access Token
void Map::setAccessToken(const std::string &token) {
@@ -588,14 +507,14 @@ const LatLng Map::latLngForPixel(const vec2<double> pixel) const {
void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) {
assert(Environment::currentlyOn(ThreadType::Main));
invokeTask([=] {
context->invokeTask([=] {
data->annotationManager.setDefaultPointAnnotationSymbol(symbol);
});
}
double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) {
assert(Environment::currentlyOn(ThreadType::Main));
return invokeSyncTask([&] {
return context->invokeSyncTask([&] {
assert(context->sprite);
const SpritePosition pos = context->sprite->getSpritePosition(symbol);
return -pos.height / pos.pixelRatio / 2;
@@ -608,9 +527,9 @@ uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol)
std::vector<uint32_t> Map::addPointAnnotations(const std::vector<LatLng>& points, const std::vector<std::string>& symbols) {
assert(Environment::currentlyOn(ThreadType::Main));
return invokeSyncTask([&] {
return context->invokeSyncTask([&] {
auto result = data->annotationManager.addPointAnnotations(points, symbols, *data);
updateAnnotationTiles(result.first);
context->updateAnnotationTiles(result.first);
return result.second;
});
}
@@ -622,22 +541,22 @@ void Map::removeAnnotation(uint32_t annotation) {
void Map::removeAnnotations(const std::vector<uint32_t>& annotations) {
assert(Environment::currentlyOn(ThreadType::Main));
invokeTask([=] {
context->invokeTask([=] {
auto result = data->annotationManager.removeAnnotations(annotations, *data);
updateAnnotationTiles(result);
context->updateAnnotationTiles(result);
});
}
std::vector<uint32_t> Map::getAnnotationsInBounds(const LatLngBounds& bounds) {
assert(Environment::currentlyOn(ThreadType::Main));
return invokeSyncTask([&] {
return context->invokeSyncTask([&] {
return data->annotationManager.getAnnotationsInBounds(bounds, *data);
});
}
LatLngBounds Map::getBoundsForAnnotations(const std::vector<uint32_t>& annotations) {
assert(Environment::currentlyOn(ThreadType::Main));
return invokeSyncTask([&] {
return context->invokeSyncTask([&] {
return data->annotationManager.getBoundsForAnnotations(annotations);
});
}
@@ -647,12 +566,12 @@ LatLngBounds Map::getBoundsForAnnotations(const std::vector<uint32_t>& annotatio
void Map::setDebug(bool value) {
data->setDebug(value);
triggerUpdate(Update::Debug);
context->triggerUpdate(Update::Debug);
}
void Map::toggleDebug() {
data->toggleDebug();
triggerUpdate(Update::Debug);
context->triggerUpdate(Update::Debug);
}
bool Map::getDebug() const {
@@ -661,19 +580,19 @@ bool Map::getDebug() const {
void Map::addClass(const std::string& klass) {
if (data->addClass(klass)) {
triggerUpdate(Update::Classes);
context->triggerUpdate(Update::Classes);
}
}
void Map::removeClass(const std::string& klass) {
if (data->removeClass(klass)) {
triggerUpdate(Update::Classes);
context->triggerUpdate(Update::Classes);
}
}
void Map::setClasses(const std::vector<std::string>& classes) {
data->setClasses(classes);
triggerUpdate(Update::Classes);
context->triggerUpdate(Update::Classes);
}
bool Map::hasClass(const std::string& klass) const {
@@ -688,7 +607,7 @@ void Map::setDefaultTransitionDuration(Duration duration) {
assert(Environment::currentlyOn(ThreadType::Main));
data->setDefaultTransitionDuration(duration);
triggerUpdate(Update::DefaultTransitionDuration);
context->triggerUpdate(Update::DefaultTransitionDuration);
}
Duration Map::getDefaultTransitionDuration() {
@@ -698,178 +617,10 @@ Duration Map::getDefaultTransitionDuration() {
#pragma mark - Private
void Map::triggerUpdate(const Update u) {
updated |= static_cast<UpdateType>(u);
if (asyncUpdate) {
asyncUpdate->ref();
asyncUpdate->send();
}
}
void Map::updateAnnotationTiles(const std::vector<TileID>& ids) {
assert(Environment::currentlyOn(ThreadType::Main));
if (!context->style) return;
for (const auto &source : context->style->sources) {
if (source->info.type == SourceType::Annotations) {
source->invalidateTiles(ids);
}
}
triggerUpdate();
}
void Map::updateTiles() {
assert(Environment::currentlyOn(ThreadType::Map));
if (!context->style) return;
for (const auto& source : context->style->sources) {
source->update(*data, context->getWorker(), context->style, *context->glyphAtlas, *context->glyphStore,
*context->spriteAtlas, context->getSprite(), *context->texturePool, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
}
void Map::update() {
assert(Environment::currentlyOn(ThreadType::Map));
if (data->getTransformState().hasSize()) {
prepare();
}
}
void Map::reloadStyle() {
assert(Environment::currentlyOn(ThreadType::Map));
context->style = std::make_shared<Style>();
const auto styleInfo = data->getStyleInfo();
if (!styleInfo.url.empty()) {
const auto base = styleInfo.base;
// We have a style URL
env->request({ Resource::Kind::JSON, styleInfo.url }, [this, base](const Response &res) {
if (res.status == Response::Successful) {
loadStyleJSON(res.data, base);
} else {
Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str());
}
});
} else if (!styleInfo.json.empty()) {
// We got JSON data directly.
loadStyleJSON(styleInfo.json, styleInfo.base);
}
}
void Map::loadStyleJSON(const std::string& json, const std::string& base) {
assert(Environment::currentlyOn(ThreadType::Map));
context->sprite.reset();
context->style = std::make_shared<Style>();
context->style->base = base;
context->style->loadJSON((const uint8_t *)json.c_str());
context->style->cascade(data->getClasses());
context->style->setDefaultTransitionDuration(data->getDefaultTransitionDuration());
const std::string glyphURL = util::mapbox::normalizeGlyphsURL(context->style->glyph_url, getAccessToken());
context->glyphStore->setURL(glyphURL);
for (const auto& source : context->style->sources) {
source->load(getAccessToken(), *env, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
triggerUpdate(Update::Zoom);
}
void Map::prepare() {
assert(Environment::currentlyOn(ThreadType::Map));
const auto now = Clock::now();
data->setAnimationTime(now);
auto u = updated.exchange(static_cast<UpdateType>(Update::Nothing)) |
data->transform.updateTransitions(now);
if (!context->style) {
u |= static_cast<UpdateType>(Update::StyleInfo);
}
data->setTransformState(data->transform.currentState());
if (u & static_cast<UpdateType>(Update::StyleInfo)) {
reloadStyle();
}
if (u & static_cast<UpdateType>(Update::Debug)) {
assert(context->painter);
context->painter->setDebug(data->getDebug());
}
if (u & static_cast<UpdateType>(Update::RenderStill)) {
// Triggers a view resize.
view.discard();
// Whenever we trigger an image render, we are unrefing all async handles so the loop will
// eventually terminate. However, it'll stay alive as long as there are pending requests
// (like work requests or HTTP requests).
asyncTerminate->unref();
asyncUpdate->unref();
asyncInvoke->unref();
context->asyncRender->unref();
}
if (context->style) {
if (u & static_cast<UpdateType>(Update::DefaultTransitionDuration)) {
context->style->setDefaultTransitionDuration(data->getDefaultTransitionDuration());
}
if (u & static_cast<UpdateType>(Update::Classes)) {
context->style->cascade(data->getClasses());
}
if (u & static_cast<UpdateType>(Update::StyleInfo) ||
u & static_cast<UpdateType>(Update::Classes) ||
u & static_cast<UpdateType>(Update::Zoom)) {
context->style->recalculate(data->getTransformState().getNormalizedZoom(), now);
}
// Allow the sprite atlas to potentially pull new sprite images if needed.
context->spriteAtlas->resize(data->getTransformState().getPixelRatio());
context->spriteAtlas->setSprite(context->getSprite());
updateTiles();
}
if (data->mode == MapMode::Continuous) {
view.invalidate();
}
}
void Map::render() {
assert(Environment::currentlyOn(ThreadType::Map));
view.discard();
// Cleanup OpenGL objects that we abandoned since the last render call.
env->performCleanup();
assert(context->style);
assert(context->painter);
context->painter->render(*context->style, data->getTransformState(), data->getAnimationTime());
// Schedule another rerender when we definitely need a next frame.
if (data->transform.needsTransition() || context->style->hasTransitions()) {
triggerUpdate();
}
}
void Map::setSourceTileCacheSize(size_t size) {
if (size != getSourceTileCacheSize()) {
invokeTask([=] {
context->invokeTask([=] {
sourceCacheSize = size;
if (!context->style) return;
for (const auto &source : context->style->sources) {
@@ -881,7 +632,7 @@ void Map::setSourceTileCacheSize(size_t size) {
}
void Map::onLowMemory() {
invokeTask([=] {
context->invokeTask([=] {
if (!context->style) return;
for (const auto &source : context->style->sources) {
source->onLowMemory();

View File

@@ -1,8 +1,12 @@
#include <mbgl/map/map_context.hpp>
#include <mbgl/map/map_data.hpp>
#include <mbgl/map/view.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/renderer/painter.hpp>
#include <mbgl/text/glyph_store.hpp>
@@ -11,18 +15,24 @@
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/geometry/line_atlas.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <mbgl/util/worker.hpp>
#include <mbgl/util/texture_pool.hpp>
#include <mbgl/util/mapbox.hpp>
namespace mbgl {
MapContext::MapContext(Environment& env_, MapData& data_)
MapContext::MapContext(Environment& env_, View& view_, MapData& data_)
: env(env_),
view(view_),
data(data_),
updated(static_cast<UpdateType>(Update::Nothing)),
glyphStore(util::make_unique<GlyphStore>(env)),
glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)),
spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)),
@@ -31,6 +41,90 @@ MapContext::MapContext(Environment& env_, MapData& data_)
painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas, *lineAtlas)) {
}
void MapContext::start() {
// Setup async notifications
assert(!asyncTerminate);
asyncTerminate = util::make_unique<uv::async>(env.loop, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
// Remove all of these to make sure they are destructed in the correct thread.
style.reset();
// It's now safe to destroy/join the workers since there won't be any more callbacks that
// could dispatch to the worker pool.
workers.reset();
terminating = true;
// Closes all open handles on the loop. This means that the loop will automatically terminate.
asyncRender.reset();
asyncUpdate.reset();
asyncInvoke.reset();
asyncTerminate.reset();
});
assert(!asyncUpdate);
asyncUpdate = util::make_unique<uv::async>(env.loop, [this] {
// of the calls actually get triggered.
asyncUpdate->unref();
update();
});
assert(!asyncInvoke);
asyncInvoke = util::make_unique<uv::async>(env.loop, [this] {
processTasks();
});
assert(!asyncRender);
asyncRender = util::make_unique<uv::async>(env.loop, [this] {
// Must be called in Map thread.
assert(Environment::currentlyOn(ThreadType::Map));
render();
// Finally, notify all listeners that we have finished rendering this frame.
rendered.notify();
});
}
void MapContext::terminate() {
assert(asyncTerminate);
asyncTerminate->send();
}
void MapContext::triggerRender() {
assert(data.mode == MapMode::Continuous);
assert(asyncRender);
asyncRender->send();
}
void MapContext::triggerUpdate(const Update u) {
updated |= static_cast<UpdateType>(u);
if (asyncUpdate) {
asyncUpdate->ref();
asyncUpdate->send();
}
}
// Runs the function in the map thread.
void MapContext::invokeTask(std::function<void()>&& fn) {
{
std::lock_guard<std::mutex> lock(mutexTask);
tasks.emplace(::std::forward<std::function<void()>>(fn));
}
// TODO: Once we have aligned static and continuous rendering, this should always dispatch
// to the async queue.
if (asyncInvoke) {
asyncInvoke->send();
} else {
processTasks();
}
}
Worker& MapContext::getWorker() {
assert(workers);
return *workers;
@@ -47,10 +141,180 @@ util::ptr<Sprite> MapContext::getSprite() {
return sprite;
}
void MapContext::triggerRender() {
assert(data.mode == MapMode::Continuous);
assert(asyncRender);
asyncRender->send();
void MapContext::reloadStyle() {
assert(Environment::currentlyOn(ThreadType::Map));
style = std::make_shared<Style>();
const auto styleInfo = data.getStyleInfo();
if (!styleInfo.url.empty()) {
const auto base = styleInfo.base;
// We have a style URL
env.request({ Resource::Kind::JSON, styleInfo.url }, [this, base](const Response &res) {
if (res.status == Response::Successful) {
loadStyleJSON(res.data, base);
} else {
Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str());
}
});
} else if (!styleInfo.json.empty()) {
// We got JSON data directly.
loadStyleJSON(styleInfo.json, styleInfo.base);
}
}
void MapContext::loadStyleJSON(const std::string& json, const std::string& base) {
assert(Environment::currentlyOn(ThreadType::Map));
sprite.reset();
style = std::make_shared<Style>();
style->base = base;
style->loadJSON((const uint8_t *)json.c_str());
style->cascade(data.getClasses());
style->setDefaultTransitionDuration(data.getDefaultTransitionDuration());
const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, data.getAccessToken());
glyphStore->setURL(glyphURL);
for (const auto& source : style->sources) {
source->load(data.getAccessToken(), env, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
triggerUpdate(Update::Zoom);
}
void MapContext::updateTiles() {
assert(Environment::currentlyOn(ThreadType::Map));
if (!style) return;
for (const auto& source : style->sources) {
source->update(data, getWorker(), style, *glyphAtlas, *glyphStore, *spriteAtlas,
getSprite(), *texturePool, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
}
void MapContext::updateAnnotationTiles(const std::vector<TileID>& ids) {
assert(Environment::currentlyOn(ThreadType::Main));
if (!style) return;
for (const auto &source : style->sources) {
if (source->info.type == SourceType::Annotations) {
source->invalidateTiles(ids);
}
}
triggerUpdate();
}
void MapContext::update() {
assert(Environment::currentlyOn(ThreadType::Map));
if (data.getTransformState().hasSize()) {
prepare();
}
}
void MapContext::prepare() {
assert(Environment::currentlyOn(ThreadType::Map));
const auto now = Clock::now();
data.setAnimationTime(now);
auto u = updated.exchange(static_cast<UpdateType>(Update::Nothing)) |
data.transform.updateTransitions(now);
if (!style) {
u |= static_cast<UpdateType>(Update::StyleInfo);
}
data.setTransformState(data.transform.currentState());
if (u & static_cast<UpdateType>(Update::StyleInfo)) {
reloadStyle();
}
if (u & static_cast<UpdateType>(Update::Debug)) {
assert(painter);
painter->setDebug(data.getDebug());
}
if (u & static_cast<UpdateType>(Update::RenderStill)) {
// Triggers a view resize.
view.discard();
// Whenever we trigger an image render, we are unrefing all async handles so the loop will
// eventually terminate. However, it'll stay alive as long as there are pending requests
// (like work requests or HTTP requests).
asyncTerminate->unref();
asyncUpdate->unref();
asyncInvoke->unref();
asyncRender->unref();
}
if (style) {
if (u & static_cast<UpdateType>(Update::DefaultTransitionDuration)) {
style->setDefaultTransitionDuration(data.getDefaultTransitionDuration());
}
if (u & static_cast<UpdateType>(Update::Classes)) {
style->cascade(data.getClasses());
}
if (u & static_cast<UpdateType>(Update::StyleInfo) ||
u & static_cast<UpdateType>(Update::Classes) ||
u & static_cast<UpdateType>(Update::Zoom)) {
style->recalculate(data.getTransformState().getNormalizedZoom(), now);
}
// Allow the sprite atlas to potentially pull new sprite images if needed.
spriteAtlas->resize(data.getTransformState().getPixelRatio());
spriteAtlas->setSprite(getSprite());
updateTiles();
}
if (data.mode == MapMode::Continuous) {
view.invalidate([this] { render(); });
}
}
void MapContext::render() {
assert(Environment::currentlyOn(ThreadType::Map));
view.discard();
// Cleanup OpenGL objects that we abandoned since the last render call.
env.performCleanup();
assert(style);
assert(painter);
painter->render(*style, data.getTransformState(), data.getAnimationTime());
// Schedule another rerender when we definitely need a next frame.
if (data.transform.needsTransition() || style->hasTransitions()) {
triggerUpdate();
}
}
// Processes the functions that should be run in the map thread.
void MapContext::processTasks() {
std::queue<std::function<void()>> queue;
{
std::lock_guard<std::mutex> lock(mutexTask);
queue.swap(tasks);
}
while (!queue.empty()) {
queue.front()();
queue.pop();
}
}
}

View File

@@ -1,9 +1,15 @@
#ifndef MBGL_MAP_MAP_CONTEXT
#define MBGL_MAP_MAP_CONTEXT
#include <mbgl/map/tile.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/util/ptr.hpp>
#include <mbgl/util/signal.hpp>
#include <set>
#include <vector>
#include <queue>
#include <future>
namespace uv {
class async;
@@ -12,6 +18,7 @@ class async;
namespace mbgl {
class Environment;
class View;
class MapData;
class GlyphStore;
class GlyphAtlas;
@@ -25,30 +32,94 @@ class Worker;
class MapContext {
public:
MapContext(Environment&, MapData&);
MapContext(Environment&, View&, MapData&);
// These can only be called from the Map thread.
Worker& getWorker();
util::ptr<Sprite> getSprite();
// Starts the async handles.
void start();
// Terminates the map thread
void terminate();
// Triggers a render. Can be called from any thread.
void triggerRender();
public:
Environment& env;
MapData& data;
// Notifies the Map thread that the state has changed and an update might be necessary.
void triggerUpdate(Update = Update::Nothing);
std::unique_ptr<uv::async> asyncRender;
std::unique_ptr<Worker> workers;
const std::unique_ptr<GlyphStore> glyphStore;
const std::unique_ptr<GlyphAtlas> glyphAtlas;
const std::unique_ptr<SpriteAtlas> spriteAtlas;
const std::unique_ptr<LineAtlas> lineAtlas;
const std::unique_ptr<TexturePool> texturePool;
const std::unique_ptr<Painter> painter;
util::ptr<Sprite> sprite;
util::ptr<Style> style;
// Executes the function on the thread of the MapContext.
void invokeTask(std::function<void()>&& fn);
// Executes the function on the thread of the MapContext and blocks until it
// has been run.
template <typename Fn> auto invokeSyncTask(const Fn& fn) -> decltype(fn()) {
std::promise<decltype(fn())> promise;
invokeTask([&fn, &promise] { promise.set_value(fn()); });
return promise.get_future().get();
}
// These can only be called from the Map thread.
private:
Worker& getWorker();
util::ptr<Sprite> getSprite();
void updateTiles();
// TODO: Make these private
public: void updateAnnotationTiles(const std::vector<TileID>& ids);
// Triggered by triggerUpdate();
private: void update();
// Prepares a map render by updating the tiles we need for the current view, as well as updating
// the stylesheet.
public: void prepare();
// Unconditionally performs a render with the current map state.
public: void render();
// Runs the enqueued tasks.
private: void processTasks();
private:
// Loads the style set in the data object. Called by Update::StyleInfo
private: void reloadStyle();
// Loads the actual JSON object an creates a new Style object.
private: void loadStyleJSON(const std::string& json, const std::string& base);
// TODO: Make all of these private
public:
private: Environment& env;
private: View& view;
private: MapData& data;
private: std::atomic<UpdateType> updated { static_cast<UpdateType>(Update::Nothing) };
private: std::mutex mutexTask;
private: std::queue<std::function<void()>> tasks;
public: std::unique_ptr<uv::async> asyncUpdate;
public: std::unique_ptr<uv::async> asyncRender;
public: std::unique_ptr<uv::async> asyncInvoke;
public: std::unique_ptr<uv::async> asyncTerminate;
public: std::unique_ptr<Worker> workers;
private: const std::unique_ptr<GlyphStore> glyphStore;
private: const std::unique_ptr<GlyphAtlas> glyphAtlas;
private: const std::unique_ptr<SpriteAtlas> spriteAtlas;
private: const std::unique_ptr<LineAtlas> lineAtlas;
private: const std::unique_ptr<TexturePool> texturePool;
public: const std::unique_ptr<Painter> painter;
public: util::ptr<Sprite> sprite;
public: util::ptr<Style> style;
// Used to signal that rendering completed.
public: util::Signal rendered;
// TODO: document this
public: bool terminating = false;
};
}
#endif

View File

@@ -1,4 +1,3 @@
#include <mbgl/map/map.hpp>
#include <mbgl/map/raster_tile_data.hpp>
#include <mbgl/style/style.hpp>

View File

@@ -106,24 +106,6 @@ void Painter::setupShaders() {
if (!gaussianShader) gaussianShader = util::make_unique<GaussianShader>();
}
void Painter::deleteShaders() {
plainShader = nullptr;
outlineShader = nullptr;
lineShader = nullptr;
linepatternShader = nullptr;
patternShader = nullptr;
iconShader = nullptr;
rasterShader = nullptr;
sdfGlyphShader = nullptr;
sdfIconShader = nullptr;
dotShader = nullptr;
gaussianShader = nullptr;
}
void Painter::terminate() {
deleteShaders();
}
void Painter::resize() {
if (gl_viewport != state.getFramebufferDimensions()) {
gl_viewport = state.getFramebufferDimensions();

View File

@@ -62,11 +62,6 @@ public:
void setup();
// Perform cleanup tasks that prepare shutting down the app. This doesn't mean that the
// app will be shut down. That means all operations must be automatically be reversed (e.g. through
// lazy initialization) in case rendering continues.
void terminate();
// Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any
// tiles whatsoever.
void clear();
@@ -132,7 +127,6 @@ public:
private:
void setupShaders();
void deleteShaders();
mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const TileID &id, TranslateAnchorType anchor);
void prepareTile(const Tile& tile);

21
src/mbgl/util/signal.cpp Normal file
View File

@@ -0,0 +1,21 @@
#include "signal.hpp"
namespace mbgl {
namespace util {
void Signal::wait() {
std::unique_lock<std::mutex> lock(mutex);
condition.wait(lock, [this] { return status; });
status = false;
}
void Signal::notify() {
{
std::lock_guard<std::mutex> lock(mutex);
status = true;
}
condition.notify_all();
}
}
}

24
src/mbgl/util/signal.hpp Normal file
View File

@@ -0,0 +1,24 @@
#ifndef MBGL_UTIL_SIGNAL
#define MBGL_UTIL_SIGNAL
#include <mutex>
#include <condition_variable>
namespace mbgl {
namespace util {
class Signal {
public:
void wait();
void notify();
private:
bool status = false;
std::condition_variable condition;
std::mutex mutex;
};
}
}
#endif