Merge branch 'master' into 1ec5-analyzer
This commit is contained in:
10
.travis.yml
10
.travis.yml
@@ -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' ]
|
||||
|
||||
17
README.md
17
README.md
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
6
include/mbgl/ios/MGLAccountManager.h
Normal file
6
include/mbgl/ios/MGLAccountManager.h
Normal file
@@ -0,0 +1,6 @@
|
||||
@interface MGLAccountManager : NSObject
|
||||
|
||||
+ (void) setAccessToken:(NSString *) accessToken;
|
||||
+ (NSString *) accessToken;
|
||||
|
||||
@end
|
||||
@@ -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 */
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
#import "MGLMapView.h"
|
||||
#import "MGLTypes.h"
|
||||
#import "MGLUserLocation.h"
|
||||
#import "MGLAccountManager.h"
|
||||
@@ -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);
|
||||
|
||||
@@ -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++' }
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -17,4 +17,5 @@ appledoc \
|
||||
--project-company Mapbox \
|
||||
--create-docset \
|
||||
--company-id com.mapbox \
|
||||
--ignore ../../include/mbgl/ios/private \
|
||||
../../include/mbgl/ios
|
||||
|
||||
53
platform/ios/MGLAccountManager.m
Normal file
53
platform/ios/MGLAccountManager.m
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
101
src/mbgl/map/resource_loader.cpp
Normal file
101
src/mbgl/map/resource_loader.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
80
src/mbgl/map/resource_loader.hpp
Normal file
80
src/mbgl/map/resource_loader.hpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
2
styles
Submodule styles updated: 161c289517...81be4f715e
@@ -13,6 +13,8 @@
|
||||
@implementation MapViewTests
|
||||
|
||||
- (void)beforeEach {
|
||||
[tester acknowledgeSystemAlert];
|
||||
|
||||
[system simulateDeviceRotationToOrientation:UIDeviceOrientationPortrait];
|
||||
[tester.viewController resetMapView];
|
||||
|
||||
|
||||
22
test/miscellaneous/map.cpp
Normal file
22
test/miscellaneous/map.cpp
Normal 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("", "");
|
||||
}
|
||||
Submodule test/suite updated: 6ac1515382...fe4b1ff371
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user