Merge branch 'master' into 1ec5-analyzer

This commit is contained in:
Minh Nguyễn
2015-05-06 12:04:19 -07:00
45 changed files with 732 additions and 250 deletions

View File

@@ -31,35 +31,35 @@ matrix:
- os: linux
env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Release
android:
components: [ 'build-tools-22.0.0', 'android-22', 'extra' ]
components: [ 'build-tools-22.0.1', 'android-22', 'extra' ]
addons:
apt:
packages: [ 'lib32stdc++6' ]
- os: linux
env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Debug
android:
components: [ 'build-tools-22.0.0', 'android-22', 'extra' ]
components: [ 'build-tools-22.0.1', 'android-22', 'extra' ]
addons:
apt:
packages: [ 'lib32stdc++6' ]
- os: linux
env: FLAVOR=android ANDROID_ABI=arm-v8 BUILDTYPE=Release
android:
components: [ 'build-tools-22.0.0', 'android-22', 'extra' ]
components: [ 'build-tools-22.0.1', 'android-22', 'extra' ]
addons:
apt:
packages: [ 'lib32stdc++6' ]
- os: linux
env: FLAVOR=android ANDROID_ABI=x86 BUILDTYPE=Release
android:
components: [ 'build-tools-22.0.0', 'android-22', 'extra' ]
components: [ 'build-tools-22.0.1', 'android-22', 'extra' ]
addons:
apt:
packages: [ 'lib32stdc++6' ]
- os: linux
env: FLAVOR=android ANDROID_ABI=mips BUILDTYPE=Release
android:
components: [ 'build-tools-22.0.0', 'android-22', 'extra' ]
components: [ 'build-tools-22.0.1', 'android-22', 'extra' ]
addons:
apt:
packages: [ 'lib32stdc++6' ]

View File

@@ -45,7 +45,20 @@ Target OS X: 10.9+
## iOS
If you merely want to install the library for iOS and try it out as an Objective-C consumer:
### Use
#### CocoaPods
CocoaPods is preferred. As of yet, Mapbox GL isn't in the global specs repo, but you can use it like so in your `Podfile`:
```ruby
use_frameworks!
pod 'MapboxGL', :podspec => 'https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/ios/MapboxGL.podspec'
```
Once the library stabilizes, a mere `pod 'MapboxGL'` will work.
#### Manually
0. Use [Homebrew](http://brew.sh/) to install Boost headers and [appledoc](http://gentlebytes.com/appledoc/): `brew install boost appledoc`.
1. Run `make ipackage`. The packaging script will produce the statically-linked `libMapboxGL.a`, `MapboxGL.bundle` for resources, a `Headers` folder, and a `Docs` folder with HTML API documentation.
@@ -67,6 +80,8 @@ If you merely want to install the library for iOS and try it out as an Objective
5. [Set the Mapbox API access token](#mapbox-api-access-tokens).
6. `#import "MapboxGL.h"`
### Development
If you want to build from source and/or contribute to development of the project, run `make iproj`, which will create and open an Xcode project which can build the entire library from source as well as an Objective-C test app. If you don't have an Apple Developer account, change the destination from "My Mac" to a simulator such as "iPhone 6" before you run and build the app.
You can run `make itest` to run the included integration tests. Requires `gem install xcpretty`. If you want to run the tests in Xcode instead, first `make ipackage` to create a local static library version, then open `test/ios/ios-tests.xcodeproj`, and lastly `Command + U` on the `Mapbox GL Tests` application target.

View File

@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.3'
classpath 'com.android.tools.build:gradle:1.2.2'
classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
}
}
@@ -29,9 +29,9 @@ repositories {
dependencies {
compile 'commons-validator:commons-validator:1.4.1'
compile 'com.android.support:support-annotations:22.0.0'
compile 'com.android.support:support-v4:22.0.0'
compile 'com.squareup.okhttp:okhttp:2.2.0'
compile 'com.android.support:support-annotations:22.1.1'
compile 'com.android.support:support-v4:22.1.1'
compile 'com.squareup.okhttp:okhttp:2.3.0'
}
android {

View File

@@ -14,7 +14,7 @@ POM_DEVELOPER_NAME=Mapbox
ANDROID_MIN_SDK=8
ANDROID_BUILD_TARGET_SDK_VERSION=22
ANDROID_BUILD_TOOLS_VERSION=22.0.0
ANDROID_BUILD_TOOLS_VERSION=22.0.1
ANDROID_BUILD_SDK_VERSION=22
POM_NAME=Mapbox GL Android SDK

View File

@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.3'
classpath 'com.android.tools.build:gradle:1.2.2'
classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0'
}
}
@@ -31,7 +31,7 @@ gradle.projectsEvaluated {
android {
compileSdkVersion 22
buildToolsVersion "22.0.0"
buildToolsVersion "22.0.1"
repositories {
mavenCentral()
@@ -74,9 +74,9 @@ dependencies {
compile(project(':MapboxGLAndroidSDK')) {
transitive = true
}
compile 'com.android.support:support-annotations:22.0.0'
compile 'com.android.support:support-v4:22.0.0'
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:support-annotations:22.1.1'
compile 'com.android.support:support-v4:22.1.1'
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.mapzen.android:lost:1.0.0'
}

View File

@@ -25,6 +25,8 @@
'../platform/ios/MGLMapView.mm',
'../platform/ios/MGLFileCache.h',
'../platform/ios/MGLFileCache.mm',
'../include/mbgl/ios/MGLAccountManager.h',
'../platform/ios/MGLAccountManager.m',
'../include/mbgl/ios/MGLAnnotation.h',
'../include/mbgl/ios/MGLUserLocation.h',
'../platform/ios/MGLUserLocation_Private.h',

View File

@@ -0,0 +1,6 @@
@interface MGLAccountManager : NSObject
+ (void) setAccessToken:(NSString *) accessToken;
+ (NSString *) accessToken;
@end

View File

@@ -20,6 +20,11 @@ IB_DESIGNABLE
/** @name Initializing a Map View */
/** Initialize a map view with the default style, given frame, and access token set in MapboxGL singleton.
* @param frame The frame with which to initialize the map view.
* @return An initialized map view, or `nil` if the map view was unable to be initialized. */
- (instancetype)initWithFrame:(CGRect)frame;
/** Initialize a map view with the default style and a given frame and access token.
* @param frame The frame with which to initialize the map view.
* @param accessToken A Mapbox API access token.
@@ -33,8 +38,6 @@ IB_DESIGNABLE
* @return An initialized map view, or `nil` if the map view was unable to be initialized. */
- (instancetype)initWithFrame:(CGRect)frame accessToken:(NSString *)accessToken styleURL:(NSURL *)styleURL;
- (instancetype)initWithFrame:(CGRect)frame __attribute__((unavailable("Instantiating an MGLMapView requires setting a style and/or an access token.")));
#pragma mark - Authorizing Access
/** @name Authorizing Access */

View File

@@ -2,3 +2,4 @@
#import "MGLMapView.h"
#import "MGLTypes.h"
#import "MGLUserLocation.h"
#import "MGLAccountManager.h"

View File

@@ -19,8 +19,6 @@ namespace mbgl {
class Response;
class Request : private util::noncopyable {
MBGL_STORE_THREAD(tid)
public:
using Callback = std::function<void(const Response &)>;
Request(const Resource &resource, uv_loop_t *loop, Callback callback);

View File

@@ -1,7 +1,7 @@
Pod::Spec.new do |m|
m.name = 'MapboxGL'
m.version = '0.2.19'
m.version = '0.2.21'
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.'
@@ -26,7 +26,6 @@ Pod::Spec.new do |m|
m.resource_bundle = { 'MapboxGL' => 'MapboxGL.bundle/*' }
m.vendored_library = 'libMapboxGL.a'
m.frameworks = 'CoreLocation', 'CoreTelephony', 'GLKit', 'ImageIO', 'MobileCoreServices', 'QuartzCore', 'SystemConfiguration'
m.libraries = 'c++', 'sqlite3', 'z'
m.xcconfig = { 'OTHER_CPLUSPLUSFLAGS' => '-std=gnu++11 -stdlib=libc++' }

View File

@@ -1,11 +1,29 @@
#import "MBXAppDelegate.h"
#import "MBXViewController.h"
#import <mbgl/ios/MapboxGL.h>
#import <mbgl/ios/MGLAccountManager.h>
#import <mbgl/ios/MGLMapboxEvents.h>
@implementation MBXAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Set Access Token
NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
if (accessToken) {
// Store to preferences so that we can launch the app later on without having to specify
// token.
[[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:@"access_token"];
} else {
// Try to retrieve from preferences, maybe we've stored them there previously and can reuse
// the token.
accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"access_token"];
}
if ( ! accessToken) NSLog(@"No access token set. Mapbox vector tiles won't work.");
// Start Mapbox GL SDK
[MGLAccountManager setAccessToken:accessToken];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[MBXViewController new]];
[self.window makeKeyAndVisible];

View File

@@ -52,20 +52,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
{
[super viewDidLoad];
NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
if (accessToken) {
// Store to preferences so that we can launch the app later on without having to specify
// token.
[[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:@"access_token"];
} else {
// Try to retrieve from preferences, maybe we've stored them there previously and can reuse
// the token.
accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:@"access_token"];
}
if ( ! accessToken) NSLog(@"No access token set. Mapbox vector tiles won't work.");
self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds accessToken:accessToken];
self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.mapView.showsUserLocation = YES;
self.mapView.delegate = self;

View File

@@ -1,7 +1,7 @@
#import "MBXBenchViewController.h"
#import <mbgl/ios/MGLMapView.h>
#import <mbgl/ios/MGLMapView_Private.h>
#import <mbgl/ios/private/MGLMapView_Private.h>
#include "locations.hpp"

View File

@@ -17,4 +17,5 @@ appledoc \
--project-company Mapbox \
--create-docset \
--company-id com.mapbox \
--ignore ../../include/mbgl/ios/private \
../../include/mbgl/ios

View File

@@ -0,0 +1,53 @@
#import <Foundation/Foundation.h>
#import "MGLAccountManager.h"
#import "NSProcessInfo+MGLAdditions.h"
#import "MGLMapboxEvents.h"
@interface MGLAccountManager()
@property (atomic) NSString *accessToken;
@end
@implementation MGLAccountManager
static MGLAccountManager *_sharedManager;
// Can be called from any thread.
//
+ (instancetype) sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ( ! NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
void (^setupBlock)() = ^{
_sharedManager = [[self alloc] init];
};
if ( ! [[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
setupBlock();
});
}
else {
setupBlock();
}
}
});
return _sharedManager;
}
+ (void) setAccessToken:(NSString *) accessToken {
[[MGLAccountManager sharedInstance] setAccessToken:accessToken];
// Update MGLMapboxEvents
// NOTE: This is (likely) the initial setup of MGLMapboxEvents
[MGLMapboxEvents setToken:accessToken];
}
+ (NSString *) accessToken {
return [MGLAccountManager sharedInstance].accessToken;
}
@end

View File

@@ -28,6 +28,7 @@
#import "SMCalloutView.h"
#import "MGLMapboxEvents.h"
#import "MapboxGL.h"
#import <algorithm>
@@ -109,6 +110,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
if (self && [self commonInit])
{
self.styleURL = nil;
self.accessToken = [MGLAccountManager accessToken];
return self;
}

View File

@@ -15,4 +15,7 @@ gem install xcpretty --no-rdoc --no-ri --no-document --quiet
mapbox_time "install_recent_git" \
brew install git
mapbox_time "install_awscli" \
brew install awscli
mkdir -p ${KIF_SCREENSHOTS}

View File

@@ -81,7 +81,9 @@ echo "Created ${OUTPUT}/static/lib${NAME}.a"
step "Copying Headers..."
mkdir -p "${OUTPUT}/static/Headers"
cp -pv include/mbgl/ios/* "${OUTPUT}/static/Headers"
for i in `ls -R include/mbgl/ios | grep -vi private`; do
cp -pv include/mbgl/ios/$i "${OUTPUT}/static/Headers"
done
# Manually create resource bundle. We don't use a GYP target here because of
@@ -109,6 +111,3 @@ appledoc \
--no-install-docset \
--company-id com.mapbox \
include/mbgl/ios
cd ${DOCS_OUTPUT}
ln -s html/index.html
cd ${OLDPWD}

View File

@@ -128,7 +128,6 @@ void Environment::requestAsync(const Resource& resource,
Request* Environment::request(const Resource& resource,
std::function<void(const Response&)> callback) {
assert(currentlyOn(ThreadType::Map));
return fileSource.request(resource, util::RunLoop::current.get()->get(), std::move(callback));
}

View File

@@ -3,7 +3,6 @@
#include <mbgl/map/view.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/map/still_image.hpp>
#include <mbgl/platform/log.hpp>
@@ -62,8 +61,8 @@ MapContext::~MapContext() {
// Explicit resets currently necessary because these abandon resources that need to be
// cleaned up by env.performCleanup();
resourceLoader.reset();
style.reset();
sprite.reset();
painter.reset();
texturePool.reset();
lineAtlas.reset();
@@ -124,24 +123,10 @@ void MapContext::setStyleJSON(const std::string& json, const std::string& base)
loadStyleJSON(json, base);
}
util::ptr<Sprite> MapContext::getSprite() {
assert(Environment::currentlyOn(ThreadType::Map));
const float pixelRatio = transformState.getPixelRatio();
const std::string &sprite_url = style->getSpriteURL();
if (!sprite || !sprite->hasPixelRatio(pixelRatio)) {
sprite = std::make_shared<Sprite>(sprite_url, pixelRatio, env, [this] {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
return sprite;
}
void MapContext::loadStyleJSON(const std::string& json, const std::string& base) {
assert(Environment::currentlyOn(ThreadType::Map));
sprite.reset();
resourceLoader.reset();
style.reset();
style = util::make_unique<Style>();
@@ -153,26 +138,19 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base)
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();
});
}
resourceLoader = util::make_unique<ResourceLoader>();
resourceLoader->setAccessToken(data.getAccessToken());
resourceLoader->setObserver(this);
resourceLoader->setStyle(style.get());
triggerUpdate(Update::Zoom);
}
void MapContext::updateTiles() {
assert(Environment::currentlyOn(ThreadType::Map));
if (!style) return;
for (const auto& source : style->sources) {
source->update(data, transformState, *style, *glyphAtlas, *glyphStore, *spriteAtlas,
getSprite(), *texturePool, [this]() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
});
}
resourceLoader->update(data, transformState, *glyphAtlas, *glyphStore,
*spriteAtlas, *texturePool);
}
void MapContext::updateAnnotationTiles(const std::vector<TileID>& ids) {
@@ -214,13 +192,9 @@ void MapContext::update() {
style->recalculate(transformState.getNormalizedZoom(), now);
}
// Allow the sprite atlas to potentially pull new sprite images if needed.
spriteAtlas->resize(transformState.getPixelRatio());
spriteAtlas->setSprite(getSprite());
updateTiles();
if (sprite->isLoaded() && style->isLoaded()) {
if (style->isLoaded()) {
if (!data.getFullyLoaded()) {
data.setFullyLoaded(true);
}
@@ -260,7 +234,7 @@ void MapContext::render() {
painter->render(*style, transformState, data.getAnimationTime());
if (data.mode == MapMode::Still && callback && style->isLoaded() && getSprite()->isLoaded()) {
if (data.mode == MapMode::Still && callback && style->isLoaded() && resourceLoader->getSprite()->isLoaded()) {
callback(view.readStillImage());
callback = nullptr;
}
@@ -273,8 +247,7 @@ void MapContext::render() {
double MapContext::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) {
assert(Environment::currentlyOn(ThreadType::Map));
assert(sprite);
const SpritePosition pos = sprite->getSpritePosition(symbol);
const SpritePosition pos = resourceLoader->getSprite()->getSpritePosition(symbol);
return -pos.height / pos.pixelRatio / 2;
}
@@ -299,4 +272,9 @@ void MapContext::onLowMemory() {
view.invalidate([this] { render(); });
}
void MapContext::onTileDataChanged() {
assert(Environment::currentlyOn(ThreadType::Map));
triggerUpdate();
}
}

View File

@@ -4,6 +4,7 @@
#include <mbgl/map/tile_id.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/resource_loader.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/util/ptr.hpp>
@@ -32,7 +33,7 @@ class StillImage;
struct LatLng;
struct LatLngBounds;
class MapContext {
class MapContext : public ResourceLoader::Observer {
public:
MapContext(uv_loop_t*, View&, FileSource&, MapData&, bool startPaused);
~MapContext();
@@ -58,6 +59,9 @@ public:
void setSourceTileCacheSize(size_t size);
void onLowMemory();
// ResourceLoader::Observer implementation.
void onTileDataChanged() override;
private:
util::ptr<Sprite> getSprite();
void updateTiles();
@@ -84,8 +88,7 @@ private:
std::unique_ptr<TexturePool> texturePool;
std::unique_ptr<Painter> painter;
std::unique_ptr<Style> style;
util::ptr<Sprite> sprite;
std::unique_ptr<ResourceLoader> resourceLoader;
std::string styleURL;
std::string styleJSON;

View File

@@ -0,0 +1,101 @@
#include <mbgl/map/resource_loader.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/style/style.hpp>
#include <cassert>
namespace mbgl {
ResourceLoader::ResourceLoader() {
assert(Environment::currentlyOn(ThreadType::Map));
}
ResourceLoader::~ResourceLoader() {
assert(Environment::currentlyOn(ThreadType::Map));
for (const auto& source : style_->sources) {
source->setObserver(nullptr);
}
if (sprite_) {
sprite_->setObserver(nullptr);
}
}
void ResourceLoader::setObserver(Observer* observer) {
assert(Environment::currentlyOn(ThreadType::Map));
assert(!observer_);
observer_ = observer;
}
void ResourceLoader::setStyle(Style* style) {
assert(style);
style_ = style;
for (const auto& source : style->sources) {
source->setObserver(this);
source->load(accessToken_);
}
}
void ResourceLoader::setAccessToken(const std::string& accessToken) {
accessToken_ = accessToken;
}
void ResourceLoader::update(MapData& data,
const TransformState& transform,
GlyphAtlas& glyphAtlas,
GlyphStore& glyphStore,
SpriteAtlas& spriteAtlas,
TexturePool& texturePool) {
if (!style_) {
return;
}
const float pixelRatio = transform.getPixelRatio();
if (!sprite_ || !sprite_->hasPixelRatio(pixelRatio)) {
sprite_ = util::make_unique<Sprite>(style_->getSpriteURL(), pixelRatio);
sprite_->setObserver(this);
spriteAtlas.resize(pixelRatio);
spriteAtlas.setSprite(sprite_);
}
for (const auto& source : style_->sources) {
source->update(
data, transform, *style_, glyphAtlas, glyphStore, spriteAtlas, sprite_, texturePool);
}
}
void ResourceLoader::onGlyphRangeLoaded() {
emitTileDataChanged();
}
void ResourceLoader::onSourceLoaded() {
emitTileDataChanged();
}
void ResourceLoader::onTileLoaded() {
emitTileDataChanged();
}
void ResourceLoader::onSpriteLoaded() {
emitTileDataChanged();
}
void ResourceLoader::emitTileDataChanged() {
assert(Environment::currentlyOn(ThreadType::Map));
if (observer_) {
observer_->onTileDataChanged();
}
}
}

View File

@@ -0,0 +1,80 @@
#ifndef MBGL_MAP_RESOURCE_LOADER
#define MBGL_MAP_RESOURCE_LOADER
#include <mbgl/map/source.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/text/glyph_store.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <string>
namespace mbgl {
class GlyphAtlas;
class GlyphStore;
class MapData;
class SpriteAtlas;
class Style;
class TexturePool;
class TransformState;
// ResourceLoader is responsible for loading and updating the Source(s) owned
// by the Style. The Source object currently owns all the tiles, thus this
// class will notify its observers of any change on these tiles which will
// ultimately cause a new rendering to be triggered.
class ResourceLoader : public GlyphStore::Observer,
public Source::Observer,
public Sprite::Observer,
private util::noncopyable {
public:
class Observer {
public:
virtual ~Observer() = default;
virtual void onTileDataChanged() = 0;
};
ResourceLoader();
~ResourceLoader();
void setObserver(Observer* observer);
// The style object currently owns all the sources. When setting
// a new style we will go through all of them and try to load.
void setStyle(Style* style);
// Set the access token to be used for loading the tile data.
void setAccessToken(const std::string& accessToken);
// Fetch the tiles needed by the current viewport and emit a signal when
// a tile is ready so observers can render the tile.
void update(MapData&, const TransformState&, GlyphAtlas&, GlyphStore&,
SpriteAtlas&, TexturePool&);
// FIXME: There is probably a better place for this.
inline util::ptr<Sprite> getSprite() const {
return sprite_;
}
// GlyphStore::Observer implementation.
void onGlyphRangeLoaded() override;
// Source::Observer implementation.
void onSourceLoaded() override;
void onTileLoaded() override;
// Sprite::Observer implementation.
void onSpriteLoaded() override;
private:
void emitTileDataChanged();
std::string accessToken_;
util::ptr<Sprite> sprite_;
Style* style_ = nullptr;
Observer* observer_ = nullptr;
};
}
#endif

View File

@@ -113,11 +113,13 @@ std::string SourceInfo::tileURL(const TileID& id, float pixelRatio) const {
return result;
}
Source::Source()
{
}
Source::Source() {}
Source::~Source() {}
Source::~Source() {
if (req) {
Environment::Get().cancelRequest(req);
}
}
bool Source::isLoaded() const {
if (!loaded) {
@@ -136,9 +138,7 @@ bool Source::isLoaded() const {
// Note: This is a separate function that must be called exactly once after creation
// The reason this isn't part of the constructor is that calling shared_from_this() in
// the constructor fails.
void Source::load(const std::string& accessToken,
Environment& env,
std::function<void()> callback) {
void Source::load(const std::string& accessToken) {
if (info.url.empty()) {
loaded = true;
return;
@@ -147,7 +147,9 @@ void Source::load(const std::string& accessToken,
util::ptr<Source> source = shared_from_this();
const std::string url = util::mapbox::normalizeSourceURL(info.url, accessToken);
env.request({ Resource::Kind::JSON, url }, [source, callback](const Response &res) {
req = Environment::Get().request({ Resource::Kind::JSON, url }, [source](const Response &res) {
source->clearRequest();
if (res.status != Response::Successful) {
Log::Warning(Event::General, "Failed to load source TileJSON: %s", res.message.c_str());
return;
@@ -164,7 +166,7 @@ void Source::load(const std::string& accessToken,
source->info.parseTileJSONProperties(d);
source->loaded = true;
callback();
source->emitSourceLoaded();
});
}
@@ -218,6 +220,23 @@ TileData::State Source::hasTile(const TileID& id) {
return TileData::State::invalid;
}
void Source::handlePartialTile(const TileID &id, Worker &worker) {
const TileID normalized_id = id.normalized();
auto it = tile_data.find(normalized_id);
if (it == tile_data.end()) {
return;
}
util::ptr<TileData> data = it->second.lock();
if (!data) {
return;
}
auto callback = std::bind(&Source::emitTileLoaded, this);
data->reparse(worker, callback);
}
TileData::State Source::addTile(MapData& data,
const TransformState& transformState,
Style& style,
@@ -226,8 +245,7 @@ TileData::State Source::addTile(MapData& data,
SpriteAtlas& spriteAtlas,
util::ptr<Sprite> sprite,
TexturePool& texturePool,
const TileID& id,
std::function<void()> callback) {
const TileID& id) {
const TileData::State state = hasTile(id);
if (state != TileData::State::invalid) {
@@ -256,6 +274,7 @@ TileData::State Source::addTile(MapData& data,
new_tile.data = cache.get(normalized_id.to_uint64());
}
auto callback = std::bind(&Source::emitTileLoaded, this);
if (!new_tile.data) {
// If we don't find working tile data, we're just going to load it.
if (info.type == SourceType::Vector) {
@@ -325,7 +344,7 @@ bool Source::findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::
auto ids = id.children(z + 1);
for (const auto& child_id : ids) {
const TileData::State state = hasTile(child_id);
if (state == TileData::State::parsed) {
if (TileData::isReadyState(state)) {
retain.emplace_front(child_id);
} else {
complete = false;
@@ -351,7 +370,7 @@ bool Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::fo
for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) {
const TileID parent_id = id.parent(z);
const TileData::State state = hasTile(parent_id);
if (state == TileData::State::parsed) {
if (TileData::isReadyState(state)) {
retain.emplace_front(parent_id);
return true;
}
@@ -366,8 +385,7 @@ void Source::update(MapData& data,
GlyphStore& glyphStore,
SpriteAtlas& spriteAtlas,
util::ptr<Sprite> sprite,
TexturePool& texturePool,
std::function<void()> callback) {
TexturePool& texturePool) {
if (!loaded || data.getAnimationTime() <= updated) {
return;
}
@@ -386,10 +404,21 @@ void Source::update(MapData& data,
// Add existing child/parent tiles if the actual tile is not yet loaded
for (const auto& id : required) {
const TileData::State state = addTile(data, transformState, style, glyphAtlas, glyphStore,
spriteAtlas, sprite, texturePool, id, callback);
TileData::State state = hasTile(id);
if (state != TileData::State::parsed) {
switch (state) {
case TileData::State::partial:
handlePartialTile(id, style.workers);
break;
case TileData::State::invalid:
state = addTile(data, transformState, style, glyphAtlas, glyphStore,
spriteAtlas, sprite, texturePool, id);
break;
default:
break;
}
if (!TileData::isReadyState(state)) {
// The tile we require is not yet loaded. Try to find a parent or
// child tile that we already have.
@@ -424,7 +453,10 @@ void Source::update(MapData& data,
bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
if (!obsolete) {
retain_data.insert(tile.data->id);
} else if (type != SourceType::Raster && tile.data->ready()) {
} else if (type != SourceType::Raster && tile.data->state == TileData::State::parsed) {
// Partially parsed tiles are never added to the cache because otherwise
// they never get updated if the go out from the viewport and the pending
// resources arrive.
tileCache.add(tile.id.normalized().to_uint64(), tile.data);
}
return obsolete;
@@ -477,4 +509,24 @@ void Source::onLowMemory() {
cache.clear();
}
void Source::clearRequest() {
req = nullptr;
}
void Source::setObserver(Observer* observer) {
observer_ = observer;
}
void Source::emitSourceLoaded() {
if (observer_) {
observer_->onSourceLoaded();
}
}
void Source::emitTileLoaded() {
if (observer_) {
observer_->onTileLoaded();
}
}
}

View File

@@ -29,6 +29,7 @@ class Sprite;
class TexturePool;
class Style;
class Painter;
class Request;
class TransformState;
class Tile;
struct ClipID;
@@ -52,12 +53,18 @@ public:
class Source : public std::enable_shared_from_this<Source>, private util::noncopyable {
public:
class Observer {
public:
virtual ~Observer() = default;
virtual void onSourceLoaded() = 0;
virtual void onTileLoaded() = 0;
};
Source();
~Source();
void load(const std::string& accessToken,
Environment&,
std::function<void()> callback);
void load(const std::string& accessToken);
bool isLoaded() const;
void load(MapData&, Environment&, std::function<void()> callback);
@@ -68,8 +75,7 @@ public:
GlyphStore&,
SpriteAtlas&,
util::ptr<Sprite>,
TexturePool&,
std::function<void()> callback);
TexturePool&);
void invalidateTiles(const std::vector<TileID>&);
@@ -82,11 +88,18 @@ public:
void setCacheSize(size_t);
void onLowMemory();
void clearRequest();
void setObserver(Observer* observer);
SourceInfo info;
bool enabled;
private:
void emitSourceLoaded();
void emitTileLoaded();
void handlePartialTile(const TileID &id, Worker &worker);
bool findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::forward_list<TileID>& retain);
bool findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain);
int32_t coveringZoomLevel(const TransformState&) const;
@@ -100,8 +113,7 @@ private:
SpriteAtlas&,
util::ptr<Sprite>,
TexturePool&,
const TileID&,
std::function<void()> callback);
const TileID&);
TileData::State hasTile(const TileID& id);
void updateTilePtrs();
@@ -117,6 +129,9 @@ private:
std::vector<Tile*> tilePtrs;
std::map<TileID, std::weak_ptr<TileData>> tile_data;
TileCache cache;
Request* req = nullptr;
Observer* observer_ = nullptr;
};
}

View File

@@ -23,20 +23,16 @@ SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16
sdf(sdf_) {
}
Sprite::Sprite(const std::string& baseUrl, float pixelRatio_, Environment& env_, std::function<void ()> callback_)
Sprite::Sprite(const std::string& baseUrl, float pixelRatio_)
: pixelRatio(pixelRatio_ > 1 ? 2 : 1),
raster(),
loadedImage(false),
loadedJSON(false),
future(promise.get_future()),
callback(callback_),
env(env_) {
env(Environment::Get()) {
if (baseUrl.empty()) {
// Treat a non-existent sprite as a successfully loaded empty sprite.
loadedImage = true;
loadedJSON = true;
promise.set_value();
return;
}
@@ -52,7 +48,7 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_, Environment& env_,
Log::Warning(Event::Sprite, "Failed to load sprite info: %s", res.message.c_str());
}
loadedJSON = true;
complete();
emitSpriteLoadedIfComplete();
});
spriteRequest = env.request({ Resource::Kind::Image, spriteURL }, [this](const Response &res) {
@@ -64,7 +60,7 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_, Environment& env_,
Log::Warning(Event::Sprite, "Failed to load sprite image: %s", res.message.c_str());
}
loadedImage = true;
complete();
emitSpriteLoadedIfComplete();
});
}
@@ -78,10 +74,9 @@ Sprite::~Sprite() {
}
}
void Sprite::complete() {
if (loadedImage && loadedJSON) {
promise.set_value();
callback();
void Sprite::emitSpriteLoadedIfComplete() {
if (isLoaded() && observer) {
observer->onSpriteLoaded();
}
}
@@ -89,10 +84,6 @@ bool Sprite::isLoaded() const {
return loadedImage && loadedJSON;
}
void Sprite::waitUntilLoaded() const {
future.wait();
}
bool Sprite::hasPixelRatio(float ratio) const {
return pixelRatio == (ratio > 1 ? 2 : 1);
}
@@ -144,3 +135,7 @@ const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {
auto it = pos.find(name);
return it == pos.end() ? empty : it->second;
}
void Sprite::setObserver(Observer* observer_) {
observer = observer_;
}

View File

@@ -11,7 +11,6 @@
#include <iosfwd>
#include <string>
#include <unordered_map>
#include <future>
namespace mbgl {
@@ -35,23 +34,31 @@ public:
class Sprite : private util::noncopyable {
public:
Sprite(const std::string& baseUrl, float pixelRatio, Environment&, std::function<void()> callback);
class Observer {
public:
virtual ~Observer() = default;
virtual void onSpriteLoaded() = 0;
};
Sprite(const std::string& baseUrl, float pixelRatio);
~Sprite();
const SpritePosition &getSpritePosition(const std::string& name) const;
bool hasPixelRatio(float ratio) const;
void waitUntilLoaded() const;
bool isLoaded() const;
const float pixelRatio;
std::unique_ptr<util::Image> raster;
void setObserver(Observer* observer);
private:
void emitSpriteLoadedIfComplete();
void parseJSON();
void parseImage();
void complete();
std::string body;
std::string image;
@@ -60,13 +67,10 @@ private:
std::unordered_map<std::string, SpritePosition> pos;
const SpritePosition empty;
std::promise<void> promise;
std::future<void> future;
std::function<void ()> callback;
Environment& env;
Request* jsonRequest = nullptr;
Request* spriteRequest = nullptr;
Observer* observer = nullptr;
};
}

View File

@@ -12,6 +12,7 @@ TileData::TileData(const TileID& id_, const SourceInfo& source_)
: id(id_),
name(id),
state(State::initial),
parsing(ATOMIC_FLAG_INIT),
source(source_),
env(Environment::Get()),
debugBucket(debugFontBuffer) {
@@ -60,6 +61,18 @@ void TileData::cancel() {
}
}
void TileData::reparse(Worker& worker, std::function<void()> callback) {
workRequest = worker.send([this] { parse(); }, callback);
bool TileData::mayStartParsing() {
return !parsing.test_and_set(std::memory_order_acquire);
}
void TileData::endParsing() {
parsing.clear(std::memory_order_release);
}
void TileData::reparse(Worker& worker, std::function<void()> callback) {
if (!mayStartParsing()) {
return;
}
workRequest = worker.send([this] { parse(); endParsing(); }, callback);
}

View File

@@ -29,10 +29,18 @@ public:
initial,
loading,
loaded,
partial,
parsed,
obsolete
};
// Tile data considered "Ready" can be used for rendering. Data in
// partial state is still waiting for network resources but can also
// be rendered, although layers will be missing.
inline static bool isReadyState(const State& state) {
return state == State::partial || state == State::parsed;
}
TileData(const TileID&, const SourceInfo&);
~TileData();
@@ -42,9 +50,11 @@ public:
const std::string toString() const;
inline bool ready() const {
return state == State::parsed;
return isReadyState(state);
}
void endParsing();
// Override this in the child class.
virtual void parse() = 0;
virtual Bucket* getBucket(StyleLayer const &layer_desc) = 0;
@@ -52,8 +62,17 @@ public:
const TileID id;
const std::string name;
std::atomic<State> state;
std::atomic_flag parsing;
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;
Environment& env;

View File

@@ -3,6 +3,7 @@
#include <mbgl/platform/log.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/renderer/fill_bucket.hpp>
#include <mbgl/renderer/line_bucket.hpp>
#include <mbgl/renderer/symbol_bucket.hpp>
@@ -34,7 +35,8 @@ TileParser::TileParser(const GeometryTile& geometryTile_,
glyphStore(glyphStore_),
spriteAtlas(spriteAtlas_),
sprite(sprite_),
collision(util::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) {
collision(util::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)),
partialParse(false) {
assert(sprite);
assert(collision);
}
@@ -56,15 +58,15 @@ void TileParser::parse() {
if (layer_desc->bucket) {
// This is a singular layer. Check if this bucket already exists. If not,
// parse this bucket.
auto bucket_it = tile.buckets.find(layer_desc->bucket->name);
if (bucket_it == tile.buckets.end()) {
// We need to create this bucket since it doesn't exist yet.
std::unique_ptr<Bucket> 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.buckets[layer_desc->bucket->name] = std::move(bucket);
}
if (tile.getBucket(*layer_desc)) {
continue;
}
std::unique_ptr<Bucket> 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());
@@ -117,7 +119,14 @@ std::unique_ptr<Bucket> TileParser::createBucket(const StyleBucket &bucketDesc)
} else if (bucketDesc.type == StyleLayerType::Line) {
return createLineBucket(*layer, bucketDesc);
} else if (bucketDesc.type == StyleLayerType::Symbol) {
return createSymbolBucket(*layer, bucketDesc);
bool needsResources = false;
auto symbolBucket = createSymbolBucket(*layer, bucketDesc, needsResources);
if (needsResources) {
partialParse = true;
return nullptr;
} else {
return std::move(symbolBucket);
}
} else if (bucketDesc.type == StyleLayerType::Raster) {
return nullptr;
} else {
@@ -178,7 +187,13 @@ std::unique_ptr<Bucket> TileParser::createLineBucket(const GeometryTileLayer& la
}
std::unique_ptr<Bucket> TileParser::createSymbolBucket(const GeometryTileLayer& layer,
const StyleBucket& bucket_desc) {
const StyleBucket& bucket_desc,
bool& needsResources) {
if (!sprite->isLoaded()) {
needsResources = true;
return nullptr;
}
auto bucket = util::make_unique<SymbolBucket>(*collision);
const float z = tile.id.z;
@@ -224,6 +239,13 @@ std::unique_ptr<Bucket> TileParser::createSymbolBucket(const GeometryTileLayer&
bucket->addFeatures(
layer, bucket_desc.filter, reinterpret_cast<uintptr_t>(&tile), spriteAtlas, *sprite, glyphAtlas, glyphStore);
if (bucket->needsGlyphs()) {
needsResources = true;
return nullptr;
}
return bucket->hasData() ? std::move(bucket) : nullptr;
}
}

View File

@@ -44,6 +44,9 @@ public:
public:
void parse();
inline bool isPartialParse() const {
return partialParse;
}
private:
bool obsolete() const;
@@ -51,7 +54,7 @@ private:
std::unique_ptr<Bucket> createBucket(const StyleBucket&);
std::unique_ptr<Bucket> createFillBucket(const GeometryTileLayer&, const StyleBucket&);
std::unique_ptr<Bucket> createLineBucket(const GeometryTileLayer&, const StyleBucket&);
std::unique_ptr<Bucket> createSymbolBucket(const GeometryTileLayer&, const StyleBucket&);
std::unique_ptr<Bucket> createSymbolBucket(const GeometryTileLayer&, const StyleBucket&, bool& needsResources);
template <class Bucket>
void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&);
@@ -67,6 +70,7 @@ private:
util::ptr<Sprite> sprite;
std::unique_ptr<Collision> collision;
bool partialParse;
};
}

View File

@@ -35,7 +35,7 @@ VectorTileData::~VectorTileData() {
}
void VectorTileData::parse() {
if (state != State::loaded) {
if (state != State::loaded && state != State::partial) {
return;
}
@@ -47,24 +47,47 @@ void VectorTileData::parse() {
const VectorTile* vt = &vectorTile;
TileParser parser(*vt, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite);
parser.parse();
if (state == State::obsolete) {
return;
}
if (parser.isPartialParse()) {
state = State::partial;
} else {
state = State::parsed;
}
} catch (const std::exception& ex) {
Log::Error(Event::ParseTile, "Parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what());
state = State::obsolete;
return;
}
if (state != State::obsolete) {
state = State::parsed;
}
}
Bucket* VectorTileData::getBucket(StyleLayer const& layer) {
if (state == State::parsed && layer.bucket) {
const auto it = buckets.find(layer.bucket->name);
if (it != buckets.end()) {
assert(it->second);
return it->second.get();
}
if (!ready() || !layer.bucket) {
return nullptr;
}
return nullptr;
std::lock_guard<std::mutex> lock(bucketsMutex);
const auto it = buckets.find(layer.bucket->name);
if (it == buckets.end()) {
return nullptr;
}
assert(it->second);
return it->second.get();
}
void VectorTileData::setBucket(StyleLayer const& layer, std::unique_ptr<Bucket> bucket) {
assert(layer.bucket);
std::lock_guard<std::mutex> lock(bucketsMutex);
if (buckets.find(layer.bucket->name) != buckets.end()) {
return;
}
buckets[layer.bucket->name] = std::move(bucket);
}

View File

@@ -10,6 +10,7 @@
#include <iosfwd>
#include <memory>
#include <mutex>
#include <unordered_map>
namespace mbgl {
@@ -42,6 +43,8 @@ public:
void parse() override;
virtual Bucket* getBucket(StyleLayer const &layer_desc) override;
void setBucket(StyleLayer const &layer_desc, std::unique_ptr<Bucket> bucket);
protected:
// Holds the actual geometries in this tile.
FillVertexBuffer fillVertexBuffer;
@@ -50,16 +53,22 @@ protected:
TriangleElementsBuffer triangleElementsBuffer;
LineElementsBuffer lineElementsBuffer;
// Holds the buckets of this tile.
// They contain the location offsets in the buffers stored above
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
GlyphAtlas& glyphAtlas;
GlyphStore& glyphStore;
SpriteAtlas& spriteAtlas;
util::ptr<Sprite> sprite;
Style& style;
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<std::string, std::unique_ptr<Bucket>> buckets;
mutable std::mutex bucketsMutex;
public:
const float depth;
};

View File

@@ -29,7 +29,7 @@
namespace mbgl {
SymbolBucket::SymbolBucket(Collision &collision_)
: collision(collision_) {
: collision(collision_), needsGlyphs_(false) {
}
SymbolBucket::~SymbolBucket() {
@@ -64,8 +64,7 @@ bool SymbolBucket::hasIconData() const { return !icon.groups.empty(); }
std::vector<SymbolFeature> SymbolBucket::processFeatures(const GeometryTileLayer& layer,
const FilterExpression& filter,
GlyphStore &glyphStore,
const Sprite &sprite) {
GlyphStore &glyphStore) {
const bool has_text = !layout.text.field.empty() && !layout.text.font.empty();
const bool has_icon = !layout.icon.image.empty();
@@ -135,8 +134,10 @@ std::vector<SymbolFeature> SymbolBucket::processFeatures(const GeometryTileLayer
util::mergeLines(features);
}
glyphStore.waitForGlyphRanges(layout.text.font, ranges);
sprite.waitUntilLoaded();
if (glyphStore.requestGlyphRangesIfNeeded(layout.text.font, ranges)) {
needsGlyphs_ = true;
return {};
}
return features;
}
@@ -148,7 +149,13 @@ void SymbolBucket::addFeatures(const GeometryTileLayer& layer,
Sprite& sprite,
GlyphAtlas& glyphAtlas,
GlyphStore& glyphStore) {
const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore, sprite);
const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore);
// Stop if we still need glyphs because the
// bucket will be discarded.
if (needsGlyphs()) {
return;
}
float horizontalAlign = 0.5;
float verticalAlign = 0.5;
@@ -191,7 +198,7 @@ void SymbolBucket::addFeatures(const GeometryTileLayer& layer,
if (layout.text.justify == TextJustifyType::Right) justify = 1;
else if (layout.text.justify == TextJustifyType::Left) justify = 0;
const auto &fontStack = glyphStore.getFontStack(layout.text.font);
auto* fontStack = glyphStore.getFontStack(layout.text.font);
for (const auto& feature : features) {
if (!feature.geometry.size()) continue;
@@ -215,13 +222,12 @@ void SymbolBucket::addFeatures(const GeometryTileLayer& layer,
// Add the glyphs we need for this label to the glyph atlas.
if (shaping.size()) {
glyphAtlas.addGlyphs(tileUID, feature.label, layout.text.font, fontStack, face);
glyphAtlas.addGlyphs(tileUID, feature.label, layout.text.font, *fontStack, face);
}
}
// if feature has icon, get sprite atlas position
if (feature.sprite.length()) {
sprite.waitUntilLoaded();
image = spriteAtlas.getImage(feature.sprite, false);
if (sprite.getSpritePosition(feature.sprite).sdf) {

View File

@@ -69,6 +69,8 @@ public:
GlyphAtlas&,
GlyphStore&);
inline bool needsGlyphs() const { return needsGlyphs_; }
void drawGlyphs(SDFShader& shader);
void drawIcons(SDFShader& shader);
void drawIcons(IconShader& shader);
@@ -76,8 +78,7 @@ public:
private:
std::vector<SymbolFeature> processFeatures(const GeometryTileLayer&,
const FilterExpression&,
GlyphStore&,
const Sprite&);
GlyphStore&);
void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image);
@@ -104,6 +105,7 @@ private:
std::vector<std::unique_ptr<IconElementGroup>> groups;
} icon;
bool needsGlyphs_;
};
}

View File

@@ -1,5 +1,6 @@
#include <mbgl/storage/request.hpp>
#include <mbgl/map/environment.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/util.hpp>
@@ -33,7 +34,6 @@ Request::Request(const Resource &resource_, uv_loop_t *loop, Callback callback_)
// Called in the originating thread.
void Request::notifyCallback() {
MBGL_VERIFY_THREAD(tid)
if (!canceled) {
invoke();
} else {
@@ -84,7 +84,6 @@ void Request::notify(const std::shared_ptr<const Response> &response_) {
// Called in the originating thread.
void Request::cancel() {
MBGL_VERIFY_THREAD(tid)
assert(async);
assert(!canceled);
canceled = util::make_unique<Canceled>();

View File

@@ -10,6 +10,7 @@
#include <mbgl/util/token.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/platform/platform.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <algorithm>
@@ -138,11 +139,12 @@ void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float m
align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line);
}
GlyphPBF::GlyphPBF(const std::string &glyphURL,
const std::string &fontStack,
GlyphPBF::GlyphPBF(const std::string& glyphURL,
const std::string& fontStack,
GlyphRange glyphRange,
Environment &env)
: future(promise.get_future().share()) {
Environment& env_,
const GlyphLoadedCallback& callback)
: parsed(false), env(env_) {
// Load the glyph set URL
std::string url = util::replaceTokens(glyphURL, [&](const std::string &name) -> std::string {
if (name == "fontstack") return util::percentEncode(fontStack);
@@ -151,25 +153,29 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL,
});
// The prepare call jumps back to the main thread.
env.requestAsync({ Resource::Kind::Glyphs, url }, [&, url](const Response &res) {
req = env.request({ Resource::Kind::Glyphs, url }, [&, url, callback](const Response &res) {
req = nullptr;
if (res.status != Response::Successful) {
// Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
// Something went wrong with loading the glyph pbf.
const std::string msg = std::string { "[ERROR] failed to load glyphs: " } + url + " message: " + res.message;
promise.set_exception(std::make_exception_ptr(std::runtime_error(msg)));
Log::Error(Event::HttpRequest, msg);
} else {
// Transfer the data to the GlyphSet and signal its availability.
// Once it is available, the caller will need to call parse() to actually
// parse the data we received. We are not doing this here since this callback is being
// called from another (unknown) thread.
data = res.data;
promise.set_value(*this);
parsed = true;
callback(this);
}
});
}
std::shared_future<GlyphPBF &> GlyphPBF::getFuture() {
return future;
GlyphPBF::~GlyphPBF() {
if (req) {
env.cancelRequest(req);
}
}
void GlyphPBF::parse(FontStack &stack) {
@@ -226,68 +232,85 @@ void GlyphPBF::parse(FontStack &stack) {
data.clear();
}
GlyphStore::GlyphStore(Environment& env_) : env(env_), mtx(util::make_unique<uv::mutex>()) {}
bool GlyphPBF::isParsed() const {
return parsed;
}
GlyphStore::GlyphStore(Environment& env_)
: env(env_), observer(nullptr) {
}
GlyphStore::~GlyphStore() {
observer = nullptr;
}
void GlyphStore::setURL(const std::string &url) {
glyphURL = url;
}
bool GlyphStore::requestGlyphRangesIfNeeded(const std::string& fontStack,
const std::set<GlyphRange>& glyphRanges) {
bool requestIsNeeded = false;
void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges) {
// We are implementing a blocking wait with futures: Every GlyphSet has a future that we are
// waiting for until it is loaded.
if (glyphRanges.empty()) {
return;
return requestIsNeeded;
}
uv::exclusive<FontStack> stack(mtx);
auto callback = [this, fontStack](GlyphPBF* glyph) {
glyph->parse(*createFontStack(fontStack));
emitGlyphRangeLoaded();
};
std::vector<std::shared_future<GlyphPBF &>> futures;
futures.reserve(glyphRanges.size());
{
auto &rangeSets = ranges[fontStack];
std::lock_guard<std::mutex> lock(rangesMutex);
auto& rangeSets = ranges[fontStack];
stack << createFontStack(fontStack);
for (const auto& range : glyphRanges) {
const auto& rangeSets_it = rangeSets.find(range);
if (rangeSets_it == rangeSets.end()) {
auto glyph = util::make_unique<GlyphPBF>(glyphURL, fontStack, range, env, callback);
rangeSets.emplace(range, std::move(glyph));
requestIsNeeded = true;
continue;
}
// Attempt to load the glyph range. If the GlyphSet already exists, we are getting back
// the same shared_future.
for (const auto range : glyphRanges) {
futures.emplace_back(loadGlyphRange(fontStack, rangeSets, range));
if (!rangeSets_it->second->isParsed()) {
requestIsNeeded = true;
}
}
// Now that we potentially created all GlyphSets, we are waiting for the results, one by one.
// When we get a result (or the GlyphSet is aready loaded), we are attempting to parse the
// GlyphSet.
for (const auto& future : futures) {
future.get().parse(stack);
}
return requestIsNeeded;
}
std::shared_future<GlyphPBF &> GlyphStore::loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, const GlyphRange range) {
auto range_it = rangeSets.find(range);
if (range_it == rangeSets.end()) {
// We don't have this glyph set yet for this font stack.
range_it = rangeSets.emplace(range, util::make_unique<GlyphPBF>(glyphURL, fontStack, range, env)).first;
}
FontStack* GlyphStore::createFontStack(const std::string &fontStack) {
std::lock_guard<std::mutex> lock(stacksMutex);
return range_it->second->getFuture();
}
FontStack &GlyphStore::createFontStack(const std::string &fontStack) {
auto stack_it = stacks.find(fontStack);
if (stack_it == stacks.end()) {
stack_it = stacks.emplace(fontStack, util::make_unique<FontStack>()).first;
}
return *stack_it->second.get();
return stack_it->second.get();
}
uv::exclusive<FontStack> GlyphStore::getFontStack(const std::string &fontStack) {
uv::exclusive<FontStack> stack(mtx);
stack << createFontStack(fontStack);
return stack;
FontStack* GlyphStore::getFontStack(const std::string &fontStack) {
std::lock_guard<std::mutex> lock(stacksMutex);
const auto& stack_it = stacks.find(fontStack);
if (stack_it == stacks.end()) {
return nullptr;
}
return stack_it->second.get();
}
void GlyphStore::setObserver(Observer* observer_) {
observer = observer_;
}
void GlyphStore::emitGlyphRangeLoaded() {
if (observer) {
observer->onGlyphRangeLoaded();
}
}
}

View File

@@ -4,19 +4,20 @@
#include <mbgl/text/glyph.hpp>
#include <mbgl/util/vec.hpp>
#include <mbgl/util/ptr.hpp>
#include <mbgl/util/uv.hpp>
#include <cstdint>
#include <vector>
#include <future>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
namespace mbgl {
class FileSource;
class Environment;
class Request;
class SDFGlyph {
public:
@@ -49,10 +50,17 @@ private:
class GlyphPBF {
public:
using GlyphLoadedCallback = std::function<void(GlyphPBF*)>;
GlyphPBF(const std::string &glyphURL,
const std::string &fontStack,
GlyphRange glyphRange,
Environment &env);
Environment &env,
const GlyphLoadedCallback& callback);
~GlyphPBF();
void parse(FontStack &stack);
bool isParsed() const;
private:
GlyphPBF(const GlyphPBF &) = delete;
@@ -60,43 +68,56 @@ private:
GlyphPBF &operator=(const GlyphPBF &) = delete;
GlyphPBF &operator=(GlyphPBF &&) = delete;
public:
void parse(FontStack &stack);
std::shared_future<GlyphPBF &> getFuture();
private:
std::string data;
std::promise<GlyphPBF &> promise;
std::shared_future<GlyphPBF &> future;
std::mutex mtx;
std::atomic_bool parsed;
Environment& env;
Request* req = nullptr;
mutable std::mutex mtx;
};
// Manages Glyphrange PBF loading.
class GlyphStore {
public:
class Observer {
public:
virtual ~Observer() = default;
virtual void onGlyphRangeLoaded() = 0;
};
GlyphStore(Environment &);
~GlyphStore();
// Block until all specified GlyphRanges of the specified font stack are loaded.
void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges);
// Asynchronously request for GlyphRanges and when it gets loaded, notifies the
// observer subscribed to this object. Successive requests for the same range are
// going to be discarded. Returns true if a request was made or false if all the
// GlyphRanges are already available, and thus, no request is performed.
bool requestGlyphRangesIfNeeded(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges);
uv::exclusive<FontStack> getFontStack(const std::string &fontStack);
FontStack* getFontStack(const std::string &fontStack);
void setURL(const std::string &url);
private:
// Loads an individual glyph range from the font stack and adds it to rangeSets
std::shared_future<GlyphPBF &> loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, GlyphRange range);
void setObserver(Observer* observer);
FontStack &createFontStack(const std::string &fontStack);
private:
void emitGlyphRangeLoaded();
FontStack* createFontStack(const std::string &fontStack);
std::string glyphURL;
Environment &env;
std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges;
std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks;
std::unique_ptr<uv::mutex> mtx;
};
std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges;
std::mutex rangesMutex;
std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks;
std::mutex stacksMutex;
Observer* observer;
};
}

2
styles

Submodule styles updated: 161c289517...81be4f715e

View File

@@ -13,6 +13,8 @@
@implementation MapViewTests
- (void)beforeEach {
[tester acknowledgeSystemAlert];
[system simulateDeviceRotationToOrientation:UIDeviceOrientationPortrait];
[tester.viewController resetMapView];

View File

@@ -0,0 +1,22 @@
#include "../fixtures/util.hpp"
#include <mbgl/util/run_loop.hpp>
#include <mbgl/map/map_data.hpp>
#include <mbgl/map/map_context.hpp>
#include <mbgl/platform/default/headless_view.hpp>
#include <mbgl/platform/default/headless_display.hpp>
#include <mbgl/storage/default_file_source.hpp>
using namespace mbgl;
TEST(MapContext, DoubleStyleLoad) {
util::RunLoop runLoop(uv_default_loop());
std::shared_ptr<HeadlessDisplay> display = std::make_shared<HeadlessDisplay>();
HeadlessView view(display, 512, 512, 1);
DefaultFileSource fileSource(nullptr);
MapData data(view, MapMode::Continuous);
MapContext context(uv_default_loop(), view, fileSource, data, false);
context.setStyleJSON("", "");
context.setStyleJSON("", "");
}

View File

@@ -38,11 +38,14 @@
'api/set_style.cpp',
'api/repeated_render.cpp',
'headless/headless.cpp',
'miscellaneous/clip_ids.cpp',
'miscellaneous/bilinear.cpp',
'miscellaneous/comparisons.cpp',
'miscellaneous/enums.cpp',
'miscellaneous/functions.cpp',
'miscellaneous/map.cpp',
'miscellaneous/mapbox.cpp',
'miscellaneous/merge_lines.cpp',
'miscellaneous/rotation_range.cpp',