Merge pull request #1034 from mapbox/1034-separate-data-object

Isolate Map data into separate object
This commit is contained in:
Konstantin Käfer
2015-03-23 11:43:50 +01:00
4 changed files with 295 additions and 104 deletions

View File

@@ -39,6 +39,7 @@ class LineAtlas;
class Environment;
class EnvironmentScope;
class AnnotationManager;
class MapData;
class Map : private util::noncopyable {
friend class View;
@@ -75,7 +76,15 @@ public:
void render();
// Notifies the Map thread that the state has changed and an update might be necessary.
void triggerUpdate();
using UpdateType = uint32_t;
enum class Update : UpdateType {
Nothing = 0,
StyleInfo = 1 << 0,
Debug = 1 << 1,
DefaultTransitionDuration = 1 << 2,
Classes = 1 << 3,
};
void triggerUpdate(Update = Update::Nothing);
// Triggers a render. Can be called from any thread.
void triggerRender();
@@ -92,8 +101,8 @@ public:
void setDefaultTransitionDuration(std::chrono::steady_clock::duration duration = std::chrono::steady_clock::duration::zero());
std::chrono::steady_clock::duration getDefaultTransitionDuration();
void setStyleURL(const std::string &url);
void setStyleJSON(std::string newStyleJSON, const std::string &base = "");
void setStyleURL(const std::string& url);
void setStyleJSON(const std::string& json, const std::string& base = "");
std::string getStyleJSON() const;
// Transition
@@ -131,7 +140,7 @@ public:
// API
void setAccessToken(const std::string &token);
const std::string &getAccessToken() const;
std::string getAccessToken() const;
// Projection
inline void getWorldBoundsMeters(ProjectedMeters &sw, ProjectedMeters &ne) const { Projection::getWorldBoundsMeters(sw, ne); }
@@ -157,7 +166,7 @@ public:
bool getDebug() const;
inline const TransformState &getState() const { return state; }
inline std::chrono::steady_clock::time_point getTime() const { return animationTime; }
std::chrono::steady_clock::time_point getTime() const;
inline AnnotationManager& getAnnotationManager() const { return *annotationManager; }
private:
@@ -178,6 +187,13 @@ private:
void updateSources();
void updateSources(const util::ptr<StyleLayerGroup> &group);
// 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();
@@ -234,17 +250,11 @@ private:
std::unique_ptr<Painter> painter;
util::ptr<AnnotationManager> annotationManager;
std::string styleURL;
std::string styleJSON = "";
std::vector<std::string> classes;
std::string accessToken;
std::chrono::steady_clock::duration defaultTransitionDuration;
bool debug = false;
std::chrono::steady_clock::time_point animationTime = std::chrono::steady_clock::time_point::min();
const std::unique_ptr<MapData> data;
std::set<util::ptr<StyleSource>> activeSources;
std::atomic<UpdateType> updated;
};
}

View File

@@ -1,6 +1,7 @@
#include <mbgl/map/map.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/view.hpp>
#include <mbgl/map/map_data.hpp>
#include <mbgl/platform/platform.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/renderer/painter.hpp>
@@ -71,7 +72,8 @@ Map::Map(View& view_, FileSource& fileSource_)
lineAtlas(util::make_unique<LineAtlas>(512, 512)),
texturePool(std::make_shared<TexturePool>()),
painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas, *lineAtlas)),
annotationManager(util::make_unique<AnnotationManager>())
annotationManager(util::make_unique<AnnotationManager>()),
data(util::make_unique<MapData>())
{
view.initialize(this);
}
@@ -138,11 +140,7 @@ void Map::start(bool startPaused) {
});
asyncUpdate = util::make_unique<uv::async>(env->loop, [this] {
assert(Environment::currentlyOn(ThreadType::Map));
if (state.hasSize()) {
prepare();
}
update();
});
asyncRender = util::make_unique<uv::async>(env->loop, [this] {
@@ -175,6 +173,8 @@ void Map::start(bool startPaused) {
isStopped = true;
view.notify();
});
triggerUpdate();
}
void Map::stop(std::function<void ()> callback) {
@@ -252,7 +252,9 @@ void Map::run() {
checkForPause();
}
if (mode == Mode::Static && !style && styleURL.empty()) {
auto styleInfo = data->getStyleInfo();
if (mode == Mode::Static && !style && (styleInfo.url.empty() && styleInfo.json.empty())) {
throw util::Exception("Style is not set");
}
@@ -297,7 +299,9 @@ void Map::renderSync() {
rendered = false;
}
void Map::triggerUpdate() {
void Map::triggerUpdate(const Update u) {
updated |= static_cast<UpdateType>(u);
if (mode == Mode::Static) {
prepare();
} else if (asyncUpdate) {
@@ -345,36 +349,27 @@ void Map::setup() {
}
void Map::setStyleURL(const std::string &url) {
// TODO: Make threadsafe.
assert(Environment::currentlyOn(ThreadType::Main));
styleURL = url;
if (mode == Mode::Continuous) {
stop();
start();
const size_t pos = url.rfind('/');
std::string base = "";
if (pos != std::string::npos) {
base = url.substr(0, pos + 1);
}
data->setStyleInfo({ url, base, "" });
triggerUpdate(Update::StyleInfo);
}
void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
// TODO: Make threadsafe.
styleJSON.swap(newStyleJSON);
sprite.reset();
if (!style) {
style = std::make_shared<Style>();
}
void Map::setStyleJSON(const std::string& json, const std::string& base) {
assert(Environment::currentlyOn(ThreadType::Main));
style->base = base;
style->loadJSON((const uint8_t *)styleJSON.c_str());
style->cascadeClasses(classes);
style->setDefaultTransitionDuration(defaultTransitionDuration);
const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken());
glyphStore->setURL(glyphURL);
triggerUpdate();
data->setStyleInfo({ "", base, json });
triggerUpdate(Update::StyleInfo);
}
std::string Map::getStyleJSON() const {
return styleJSON;
return data->getStyleInfo().json;
}
util::ptr<Sprite> Map::getSprite() {
@@ -535,11 +530,11 @@ void Map::stopRotating() {
#pragma mark - Access Token
void Map::setAccessToken(const std::string &token) {
accessToken = token;
data->setAccessToken(token);
}
const std::string &Map::getAccessToken() const {
return accessToken;
std::string Map::getAccessToken() const {
return data->getAccessToken();
}
#pragma mark - Annotations
@@ -596,69 +591,58 @@ void Map::updateAnnotationTiles(std::vector<Tile::ID>& ids) {
#pragma mark - Toggles
void Map::setDebug(bool value) {
debug = value;
assert(painter);
painter->setDebug(debug);
triggerUpdate();
data->setDebug(value);
triggerUpdate(Update::Debug);
}
void Map::toggleDebug() {
setDebug(!debug);
data->toggleDebug();
triggerUpdate(Update::Debug);
}
bool Map::getDebug() const {
return debug;
return data->getDebug();
}
std::chrono::steady_clock::time_point Map::getTime() const {
return data->getAnimationTime();
}
void Map::addClass(const std::string& klass) {
if (hasClass(klass)) return;
classes.push_back(klass);
if (style) {
style->cascadeClasses(classes);
if (style->hasTransitions()) {
triggerUpdate();
}
if (data->addClass(klass)) {
triggerUpdate(Update::Classes);
}
}
void Map::removeClass(const std::string& klass) {
if (!hasClass(klass)) return;
classes.erase(std::remove(classes.begin(), classes.end(), klass), classes.end());
if (style) {
style->cascadeClasses(classes);
if (style->hasTransitions()) {
triggerUpdate();
}
if (data->removeClass(klass)) {
triggerUpdate(Update::Classes);
}
}
void Map::setClasses(const std::vector<std::string>& classes_) {
classes = classes_;
if (style) {
style->cascadeClasses(classes);
if (style->hasTransitions()) {
triggerUpdate();
}
}
void Map::setClasses(const std::vector<std::string>& classes) {
data->setClasses(classes);
triggerUpdate(Update::Classes);
}
bool Map::hasClass(const std::string& klass) const {
return std::find(classes.begin(), classes.end(), klass) != classes.end();
return data->hasClass(klass);
}
std::vector<std::string> Map::getClasses() const {
return classes;
return data->getClasses();
}
void Map::setDefaultTransitionDuration(std::chrono::steady_clock::duration duration) {
defaultTransitionDuration = duration;
if (style) {
style->setDefaultTransitionDuration(duration);
}
assert(Environment::currentlyOn(ThreadType::Main));
data->setDefaultTransitionDuration(duration);
triggerUpdate(Update::DefaultTransitionDuration);
}
std::chrono::steady_clock::duration Map::getDefaultTransitionDuration() {
return defaultTransitionDuration;
assert(Environment::currentlyOn(ThreadType::Main));
return data->getDefaultTransitionDuration();
}
void Map::updateSources() {
@@ -670,7 +654,9 @@ void Map::updateSources() {
}
// Then, reenable all of those that we actually use when drawing this layer.
updateSources(style->layers);
if (style) {
updateSources(style->layers);
}
// Then, construct or destroy the actual source object, depending on enabled state.
for (const auto& source : activeSources) {
@@ -715,45 +701,94 @@ void Map::updateTiles() {
}
}
void Map::prepare() {
void Map::update() {
assert(Environment::currentlyOn(ThreadType::Map));
if (!style) {
style = std::make_shared<Style>();
if (state.hasSize()) {
prepare();
}
}
env->request({ Resource::Kind::JSON, styleURL}, [&](const Response &res) {
void Map::reloadStyle() {
assert(Environment::currentlyOn(ThreadType::Map));
style = std::make_shared<Style>();
const auto styleInfo = data->getStyleInfo();
if (!styleInfo.url.empty()) {
// We have a style URL
env->request({ Resource::Kind::JSON, styleInfo.url }, [&](const Response &res) {
if (res.status == Response::Successful) {
// Calculate the base
const size_t pos = styleURL.rfind('/');
std::string base = "";
if (pos != std::string::npos) {
base = styleURL.substr(0, pos + 1);
}
setStyleJSON(res.data, base);
loadStyleJSON(res.data, styleInfo.base);
} else {
Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str());
}
});
} else {
// 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));
sprite.reset();
style = std::make_shared<Style>();
style->base = base;
style->loadJSON((const uint8_t *)json.c_str());
style->cascadeClasses(data->getClasses());
style->setDefaultTransitionDuration(data->getDefaultTransitionDuration());
const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken());
glyphStore->setURL(glyphURL);
triggerUpdate();
}
void Map::prepare() {
assert(Environment::currentlyOn(ThreadType::Map));
const auto u = updated.exchange(static_cast<UpdateType>(Update::Nothing));
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::DefaultTransitionDuration)) {
if (style) {
style->setDefaultTransitionDuration(data->getDefaultTransitionDuration());
}
}
if (u & static_cast<UpdateType>(Update::Classes)) {
if (style) {
style->cascadeClasses(data->getClasses());
}
}
// Update transform transitions.
animationTime = std::chrono::steady_clock::now();
const auto animationTime = std::chrono::steady_clock::now();
data->setAnimationTime(animationTime);
if (transform.needsTransition()) {
transform.updateTransitions(animationTime);
}
state = transform.currentState();
animationTime = std::chrono::steady_clock::now();
updateSources();
style->updateProperties(state.getNormalizedZoom(), animationTime);
if (style) {
updateSources();
style->updateProperties(state.getNormalizedZoom(), animationTime);
// Allow the sprite atlas to potentially pull new sprite images if needed.
spriteAtlas->resize(state.getPixelRatio());
spriteAtlas->setSprite(getSprite());
// Allow the sprite atlas to potentially pull new sprite images if needed.
spriteAtlas->resize(state.getPixelRatio());
spriteAtlas->setSprite(getSprite());
updateTiles();
updateTiles();
}
if (mode == Mode::Continuous) {
view.invalidate();
@@ -768,7 +803,7 @@ void Map::render() {
assert(painter);
painter->render(*style, activeSources,
state, animationTime);
state, data->getAnimationTime());
// Schedule another rerender when we definitely need a next frame.
if (transform.needsTransition() || style->hasTransitions()) {
triggerUpdate();

44
src/mbgl/map/map_data.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include "map_data.hpp"
#include <algorithm>
namespace mbgl {
// Adds the class if it's not yet set. Returns true when it added the class, and false when it
// was already present.
bool MapData::addClass(const std::string& klass) {
Lock lock(mtx);
if (std::find(classes.begin(), classes.end(), klass) != classes.end()) return false;
classes.push_back(klass);
return true;
}
// Removes the class if it's present. Returns true when it remvoed the class, and false when it
// was not present.
bool MapData::removeClass(const std::string& klass) {
Lock lock(mtx);
const auto it = std::find(classes.begin(), classes.end(), klass);
if (it != classes.end()) {
classes.erase(it);
return true;
}
return false;
}
// Returns true when class is present in the list of currently set classes.
bool MapData::hasClass(const std::string& klass) const {
Lock lock(mtx);
return std::find(classes.begin(), classes.end(), klass) != classes.end();
}
void MapData::setClasses(const std::vector<std::string>& klasses) {
Lock lock(mtx);
classes = klasses;
}
std::vector<std::string> MapData::getClasses() const {
Lock lock(mtx);
return classes;
}
}

102
src/mbgl/map/map_data.hpp Normal file
View File

@@ -0,0 +1,102 @@
#ifndef MBGL_MAP_MAP_DATA
#define MBGL_MAP_MAP_DATA
#include <string>
#include <mutex>
#include <atomic>
#include <chrono>
#include <vector>
namespace mbgl {
struct StyleInfo {
std::string url;
std::string base;
std::string json;
};
class MapData {
using Lock = std::lock_guard<std::mutex>;
public:
inline MapData() {
setAnimationTime(std::chrono::steady_clock::time_point::min());
setDefaultTransitionDuration(std::chrono::steady_clock::duration::zero());
}
inline StyleInfo getStyleInfo() const {
Lock lock(mtx);
return styleInfo;
}
inline void setStyleInfo(StyleInfo&& info) {
Lock lock(mtx);
styleInfo = info;
}
inline std::string getAccessToken() const {
Lock lock(mtx);
return accessToken;
}
inline void setAccessToken(const std::string &token) {
Lock lock(mtx);
accessToken = token;
}
// Adds the class if it's not yet set. Returns true when it added the class, and false when it
// was already present.
bool addClass(const std::string& klass);
// Removes the class if it's present. Returns true when it remvoed the class, and false when it
// was not present.
bool removeClass(const std::string& klass);
// Returns true when class is present in the list of currently set classes.
bool hasClass(const std::string& klass) const;
// Changes the list of currently set classes to the new list.
void setClasses(const std::vector<std::string>& klasses);
// Returns a list of all currently set classes.
std::vector<std::string> getClasses() const;
inline bool getDebug() const {
return debug;
}
inline bool toggleDebug() {
return debug ^= 1u;
}
inline void setDebug(bool value) {
debug = value;
}
inline std::chrono::steady_clock::time_point getAnimationTime() const {
// We're casting the time_point to and from a duration because libstdc++
// has a bug that doesn't allow time_points to be atomic.
return std::chrono::steady_clock::time_point(animationTime);
}
inline void setAnimationTime(std::chrono::steady_clock::time_point timePoint) {
animationTime = timePoint.time_since_epoch();
};
inline std::chrono::steady_clock::duration getDefaultTransitionDuration() const {
return defaultTransitionDuration;
}
inline void setDefaultTransitionDuration(std::chrono::steady_clock::duration duration) {
defaultTransitionDuration = duration;
};
private:
mutable std::mutex mtx;
StyleInfo styleInfo;
std::string accessToken;
std::vector<std::string> classes;
std::atomic<uint8_t> debug { false };
std::atomic<std::chrono::steady_clock::time_point::duration> animationTime;
std::atomic<std::chrono::steady_clock::duration> defaultTransitionDuration;
};
}
#endif