diff options
author | Mike Morris <michael.patrick.morris@gmail.com> | 2014-10-10 12:24:45 -0400 |
---|---|---|
committer | Mike Morris <michael.patrick.morris@gmail.com> | 2014-10-10 12:24:45 -0400 |
commit | 2d1219fa5154c489cd856bedd04b84573d45ac04 (patch) | |
tree | a8e42e6acd79f73aac228e0fe6876917067db8c4 | |
parent | 8f6e8eead12c6b2c2de0ce76fa7df39ca2445006 (diff) | |
parent | f390dab0ea7d449bdd89855c84e47f4a07606fe4 (diff) | |
download | qtlocation-mapboxgl-2d1219fa5154c489cd856bedd04b84573d45ac04.tar.gz |
Merge branch 'master' into libuv-0.10-headless-display
Conflicts:
common/curl_request.cpp
common/glfw_view.cpp
common/glfw_view.hpp
include/mbgl/platform/request.hpp
ios/mapbox-gl-cocoa
setup-libraries.sh
src/map/map.cpp
src/platform/request.cpp
test/fixtures/fixture_request.cpp
115 files changed, 5031 insertions, 862 deletions
@@ -108,4 +108,4 @@ distclean: clean -rm -rf ./mapnik-packaging/osx/out/universal -find ./mapnik-packaging/osx/out/packages -type d ! -name 'packages' -maxdepth 1 -exec rm -rf {} \; -.PHONY: mbgl test linux +.PHONY: mbgl test linux build/test/Makefile diff --git a/common/Reachability.h b/common/Reachability.h new file mode 100644 index 0000000000..1cf7d2ecea --- /dev/null +++ b/common/Reachability.h @@ -0,0 +1,112 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import <Foundation/Foundation.h> +#import <SystemConfiguration/SystemConfiguration.h> + +#import <sys/socket.h> +#import <netinet/in.h> +#import <netinet6/in6.h> +#import <arpa/inet.h> +#import <ifaddrs.h> +#import <netdb.h> + +/** + * Does ARC support GCD objects? + * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ + * + * @see http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h + **/ +#if OS_OBJECT_USE_OBJC +#define NEEDS_DISPATCH_RETAIN_RELEASE 0 +#else +#define NEEDS_DISPATCH_RETAIN_RELEASE 1 +#endif + +/** + * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. + * + * @see http://nshipster.com/ns_enum-ns_options/ + **/ +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +extern NSString *const kReachabilityChangedNotification; + +typedef NS_ENUM(NSInteger, NetworkStatus) { + // Apple NetworkStatus Compatible Names. + NotReachable = 0, + ReachableViaWiFi = 2, + ReachableViaWWAN = 1 +}; + +@class Reachability; + +typedef void (^NetworkReachable)(Reachability * reachability); +typedef void (^NetworkUnreachable)(Reachability * reachability); + +@interface Reachability : NSObject + +@property (nonatomic, copy) NetworkReachable reachableBlock; +@property (nonatomic, copy) NetworkUnreachable unreachableBlock; + + +@property (nonatomic, assign) BOOL reachableOnWWAN; + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname; +// This is identical to the function above, but is here to maintain +//compatibility with Apples original code. (see .m) ++(Reachability*)reachabilityWithHostName:(NSString*)hostname; ++(Reachability*)reachabilityForInternetConnection; ++(Reachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; ++(Reachability*)reachabilityForLocalWiFi; + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; + +-(BOOL)startNotifier; +-(void)stopNotifier; + +-(BOOL)isReachable; +-(BOOL)isReachableViaWWAN; +-(BOOL)isReachableViaWiFi; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired; // Identical DDG variant. +-(BOOL)connectionRequired; // Apple's routine. +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand; +// Is user intervention required? +-(BOOL)isInterventionRequired; + +-(NetworkStatus)currentReachabilityStatus; +-(SCNetworkReachabilityFlags)reachabilityFlags; +-(NSString*)currentReachabilityString; +-(NSString*)currentReachabilityFlags; + +@end diff --git a/common/Reachability.m b/common/Reachability.m new file mode 100644 index 0000000000..c6f6388f52 --- /dev/null +++ b/common/Reachability.m @@ -0,0 +1,527 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Reachability.h" + + +NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; + +@interface Reachability () + +@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; + + +#if NEEDS_DISPATCH_RETAIN_RELEASE +@property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue; +#else +@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; +#endif + + +@property (nonatomic, strong) id reachabilityObject; + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; + +@end + +static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) +{ + return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", +#if TARGET_OS_IPHONE + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', +#else + 'X', +#endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +// Start listening for reachability notifications on the current run loop +static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target) +#if __has_feature(objc_arc) + Reachability *reachability = ((__bridge Reachability*)info); +#else + Reachability *reachability = ((Reachability*)info); +#endif + + // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, + // but what the heck eh? + @autoreleasepool + { + [reachability reachabilityChanged:flags]; + } +} + + +@implementation Reachability + +@synthesize reachabilityRef; +@synthesize reachabilitySerialQueue; + +@synthesize reachableOnWWAN; + +@synthesize reachableBlock; +@synthesize unreachableBlock; + +@synthesize reachabilityObject; + +#pragma mark - Class Constructor Methods + ++(Reachability*)reachabilityWithHostName:(NSString*)hostname +{ + return [Reachability reachabilityWithHostname:hostname]; +} + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + + } + + return nil; +} + ++(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + } + + return nil; +} + ++(Reachability *)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + ++(Reachability*)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + return [self reachabilityWithAddress:&localWifiAddress]; +} + + +// Initialization methods + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [super init]; + if (self != nil) + { + self.reachableOnWWAN = YES; + self.reachabilityRef = ref; + } + + return self; +} + +-(void)dealloc +{ + [self stopNotifier]; + + if(self.reachabilityRef) + { + CFRelease(self.reachabilityRef); + self.reachabilityRef = nil; + } + + self.reachableBlock = nil; + self.unreachableBlock = nil; + +#if !(__has_feature(objc_arc)) + [super dealloc]; +#endif + + +} + +#pragma mark - Notifier Methods + +// Notifier +// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD +// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. +// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) + +-(BOOL)startNotifier +{ + SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; + + // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves + // woah + self.reachabilityObject = self; + + + + // First, we need to create a serial queue. + // We allocate this once for the lifetime of the notifier. + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + if(self.reachabilitySerialQueue == nil) + { + return NO; + } + +#if __has_feature(objc_arc) + context.info = (__bridge void *)self; +#else + context.info = (void *)self; +#endif + + if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); +#endif + + // Clear out the dispatch queue + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + // Set it as our reachability queue, which will retain the queue + if(!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); +#endif + + // UH OH - FAILURE! + + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Then clear out the dispatch queue. + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + return YES; +} + +-(void)stopNotifier +{ + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Unregister target from the GCD serial dispatch queue. + SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); + + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; +} + +#pragma mark - reachability tests + +// This is for the case where you flick the airplane mode; +// you end up getting something like this: +//Reachability: WR ct----- +//Reachability: -- ------- +//Reachability: WR ct----- +//Reachability: -- ------- +// We treat this as 4 UNREACHABLE triggers - really apple should do better than this + +#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) + +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +{ + BOOL connectionUP = YES; + + if(!(flags & kSCNetworkReachabilityFlagsReachable)) + connectionUP = NO; + + if( (flags & testcase) == testcase ) + connectionUP = NO; + +#if TARGET_OS_IPHONE + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // We're on 3G. + if(!self.reachableOnWWAN) + { + // We don't want to connect when on 3G. + connectionUP = NO; + } + } +#endif + + return connectionUP; +} + +-(BOOL)isReachable +{ + SCNetworkReachabilityFlags flags; + + if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + return NO; + + return [self isReachableWithFlags:flags]; +} + +-(BOOL)isReachableViaWWAN +{ +#if TARGET_OS_IPHONE + + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're REACHABLE + if(flags & kSCNetworkReachabilityFlagsReachable) + { + // Now, check we're on WWAN + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return YES; + } + } + } +#endif + + return NO; +} + +-(BOOL)isReachableViaWiFi +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're reachable + if((flags & kSCNetworkReachabilityFlagsReachable)) + { +#if TARGET_OS_IPHONE + // Check we're NOT on WWAN + if((flags & kSCNetworkReachabilityFlagsIsWWAN)) + { + return NO; + } +#endif + return YES; + } + } + + return NO; +} + + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired +{ + return [self connectionRequired]; +} + +-(BOOL)connectionRequired +{ + SCNetworkReachabilityFlags flags; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); + } + + return NO; +} + +// Is user intervention required? +-(BOOL)isInterventionRequired +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + } + + return NO; +} + + +#pragma mark - reachability status stuff + +-(NetworkStatus)currentReachabilityStatus +{ + if([self isReachable]) + { + if([self isReachableViaWiFi]) + return ReachableViaWiFi; + +#if TARGET_OS_IPHONE + return ReachableViaWWAN; +#endif + } + + return NotReachable; +} + +-(SCNetworkReachabilityFlags)reachabilityFlags +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return flags; + } + + return 0; +} + +-(NSString*)currentReachabilityString +{ + NetworkStatus temp = [self currentReachabilityStatus]; + + if(temp == reachableOnWWAN) + { + // Updated for the fact that we have CDMA phones now! + return NSLocalizedString(@"Cellular", @""); + } + if (temp == ReachableViaWiFi) + { + return NSLocalizedString(@"WiFi", @""); + } + + return NSLocalizedString(@"No Connection", @""); +} + +-(NSString*)currentReachabilityFlags +{ + return reachabilityFlags([self reachabilityFlags]); +} + +#pragma mark - Callback function calls this method + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +{ + if([self isReachableWithFlags:flags]) + { + if(self.reachableBlock) + { + self.reachableBlock(self); + } + } + else + { + if(self.unreachableBlock) + { + self.unreachableBlock(self); + } + } + + // this makes sure the change notification happens on the MAIN THREAD + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification + object:self]; + }); +} + +#pragma mark - Debug Description + +- (NSString *) description +{ + NSString *description = [NSString stringWithFormat:@"<%@: %#x>", + NSStringFromClass([self class]), (unsigned int) self]; + return description; +} + +@end diff --git a/common/foundation_request.h b/common/foundation_request.h deleted file mode 100644 index 0b8c4b8fb0..0000000000 --- a/common/foundation_request.h +++ /dev/null @@ -1 +0,0 @@ -#import <Foundation/Foundation.h> diff --git a/common/foundation_request.mm b/common/foundation_request.mm deleted file mode 100644 index b7eafdb96c..0000000000 --- a/common/foundation_request.mm +++ /dev/null @@ -1,115 +0,0 @@ -#import "foundation_request.h" - -#include "TargetConditionals.h" -#if TARGET_OS_IPHONE -#import <UIKit/UIKit.h> -#include <atomic> -#endif - -#include <memory> -#include <string> -#include <functional> -#include <mbgl/platform/request.hpp> -#include <mbgl/platform/platform.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/uv.hpp> - -dispatch_once_t request_initialize = 0; -NSURLSession *session = nullptr; - -#if TARGET_OS_IPHONE -std::atomic<int> active_tasks; -#endif - - -// We're using a child class to make sure ARC is working correctly, as well as to add activity -// indicators on iOS. -class FoundationRequest : public mbgl::platform::Request { -public: - FoundationRequest(const std::string &url, - std::function<void(mbgl::platform::Response *)> callback, - std::shared_ptr<uv::loop> loop) - : Request(url, callback, loop) { -#if TARGET_OS_IPHONE - active_tasks++; - dispatch_async(dispatch_get_main_queue(), ^(void) { - [[UIApplication sharedApplication] - setNetworkActivityIndicatorVisible:(active_tasks > 0)]; - }); -#endif - } - - ~FoundationRequest() { -#if TARGET_OS_IPHONE - active_tasks--; - dispatch_async(dispatch_get_main_queue(), ^(void) { - [[UIApplication sharedApplication] - setNetworkActivityIndicatorVisible:(active_tasks > 0)]; - }); -#endif - } - - NSURLSessionDataTask *task = nullptr; -}; - -std::shared_ptr<mbgl::platform::Request> -mbgl::platform::request_http(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop) { - dispatch_once(&request_initialize, ^{ - NSURLSessionConfiguration *sessionConfig = - [NSURLSessionConfiguration defaultSessionConfiguration]; - sessionConfig.timeoutIntervalForResource = 30; - sessionConfig.HTTPMaximumConnectionsPerHost = 8; - sessionConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy; - - session = [NSURLSession sessionWithConfiguration:sessionConfig]; - -#if TARGET_OS_IPHONE - active_tasks = 0; -#endif - }); - - std::shared_ptr<FoundationRequest> req = - std::make_shared<FoundationRequest>(url, callback, loop); - - // Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one - // shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the - // callback that this pointer gets destroyed again. - std::shared_ptr<Request> *req_ptr = new std::shared_ptr<Request>(req); - - NSURLSessionDataTask *task = [session - dataTaskWithURL:[NSURL URLWithString:@(url.c_str())] - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if ([error code] == NSURLErrorCancelled) { - // We intentionally cancelled this request. Make sure we clear the shared_ptr to resolve - // the circular reference. We're referencing this shared_ptr by value so that the object - // stays around until this completion handler is invoked. - delete req_ptr; - - return; - } - - if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { - (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode]; - (*req_ptr)->res->body = {(const char *)[data bytes], [data length]}; - } else { - (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String]; - } - - (*req_ptr)->complete(); - - delete req_ptr; - }]; - - req->task = task; - - [task resume]; - return req; -} - -void mbgl::platform::cancel_request_http(const std::shared_ptr<Request> &req) { - if (req) { - [((FoundationRequest *)(req.get()))->task cancel]; - } -} diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index 0908786bb6..120faf4df1 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -186,7 +186,9 @@ int GLFWView::run() { glfwWaitEvents(); } - map->stop(); + map->stop([](void *) { + glfwWaitEvents(); + }); return 0; } @@ -199,6 +201,10 @@ void GLFWView::make_inactive() { glfwMakeContextCurrent(nullptr); } +void GLFWView::notify() { + glfwPostEmptyEvent(); +} + void GLFWView::swap() { glfwPostEmptyEvent(); diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp index 04085f7750..6e91c1125e 100644 --- a/common/glfw_view.hpp +++ b/common/glfw_view.hpp @@ -18,6 +18,7 @@ public: void swap(); void make_active(); void make_inactive(); + void notify(); void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay = 0); static void key(GLFWwindow *window, int key, int scancode, int action, int mods); diff --git a/common/headless_view.cpp b/common/headless_view.cpp index 3f945ee6aa..c2084ac90d 100644 --- a/common/headless_view.cpp +++ b/common/headless_view.cpp @@ -161,6 +161,10 @@ HeadlessView::~HeadlessView() { #endif } +void HeadlessView::notify() { + // no-op +} + void HeadlessView::notify_map_change(mbgl::MapChange /*change*/, mbgl::timestamp /*delay*/) { // no-op } diff --git a/common/headless_view.hpp b/common/headless_view.hpp index 600e9d51fa..d2fe75382a 100644 --- a/common/headless_view.hpp +++ b/common/headless_view.hpp @@ -28,6 +28,7 @@ public: void resize(uint16_t width, uint16_t height, float pixelRatio); const std::unique_ptr<uint32_t[]> readPixels(); + void notify(); void notify_map_change(MapChange change, timestamp delay = 0); void make_active(); void make_inactive(); diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm new file mode 100644 index 0000000000..a9992fad8c --- /dev/null +++ b/common/http_request_baton_cocoa.mm @@ -0,0 +1,149 @@ +#include <mbgl/storage/http_request_baton.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/util/parsedate.h> + +#include <uv.h> + +#include <mbgl/util/uv.hpp> + +#import <Foundation/Foundation.h> +#include <ctime> +#include <xlocale.h> + +namespace mbgl { + +dispatch_once_t request_initialize = 0; +NSURLSession *session = nullptr; + +void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + + // Starts the request. + util::ptr<HTTPRequestBaton> baton = ptr; + + // Create a C locale + static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr); + + dispatch_once(&request_initialize, ^{ + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + sessionConfig.timeoutIntervalForResource = 30; + sessionConfig.HTTPMaximumConnectionsPerHost = 8; + sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + sessionConfig.URLCache = nil; + + session = [NSURLSession sessionWithConfiguration:sessionConfig]; + }); + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]]; + if (baton->response) { + if (!baton->response->etag.empty()) { + [request addValue:@(baton->response->etag.c_str()) forHTTPHeaderField:@"If-None-Match"]; + } else if (baton->response->modified) { + const time_t modified = baton->response->modified; + struct tm *timeinfo = std::gmtime(&modified); + char buffer[32]; + strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale); + [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"]; + } + } + + NSURLSessionDataTask *task = [session dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) { + if (error) { + if ([error code] == NSURLErrorCancelled) { + // The response code remains at 0 to indicate cancelation. + // In addition, we don't need any response object. + baton->response.reset(); + baton->type = HTTPResponseType::Canceled; + } else { + // TODO: Use different codes for host not found, timeout, invalid URL etc. + // These can be categorized in temporary and permanent errors. + baton->response = std::make_unique<Response>(); + baton->response->code = [(NSHTTPURLResponse *)res statusCode]; + baton->response->message = [[error localizedDescription] UTF8String]; + + switch ([error code]) { + case NSURLErrorBadServerResponse: // 5xx errors + baton->type = HTTPResponseType::TemporaryError; + break; + + case NSURLErrorTimedOut: + case NSURLErrorUserCancelledAuthentication: + baton->type = HTTPResponseType::SingularError; // retry immediately + break; + + case NSURLErrorNetworkConnectionLost: + case NSURLErrorCannotFindHost: + case NSURLErrorCannotConnectToHost: + case NSURLErrorDNSLookupFailed: + case NSURLErrorNotConnectedToInternet: + case NSURLErrorInternationalRoamingOff: + case NSURLErrorCallIsActive: + case NSURLErrorDataNotAllowed: + baton->type = HTTPResponseType::ConnectionError; + break; + + default: + baton->type = HTTPResponseType::PermanentError; + } + } + } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { + const long code = [(NSHTTPURLResponse *)res statusCode]; + if (code == 304) { + // Assume a Response object already exists. + assert(baton->response); + } else { + baton->response = std::make_unique<Response>(); + baton->response->code = code; + baton->response->data = {(const char *)[data bytes], [data length]}; + } + + if (code == 304) { + baton->type = HTTPResponseType::NotModified; + } else if (code == 200) { + baton->type = HTTPResponseType::Successful; + } else { + baton->type = HTTPResponseType::PermanentError; + } + + NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; + NSString *cache_control = [headers objectForKey:@"Cache-Control"]; + if (cache_control) { + baton->response->expires = Response::parseCacheControl([cache_control UTF8String]); + } + + NSString *last_modified = [headers objectForKey:@"Last-Modified"]; + if (last_modified) { + baton->response->modified = parse_date([last_modified UTF8String]); + } + + NSString *etag = [headers objectForKey:@"ETag"]; + if (etag) { + baton->response->etag = [etag UTF8String]; + } + } else { + // This should never happen. + baton->type = HTTPResponseType::PermanentError; + baton->response = std::make_unique<Response>(); + baton->response->code = -1; + baton->response->message = "response class is not NSHTTPURLResponse"; + } + + uv_async_send(baton->async); + }]; + + [task resume]; + + baton->ptr = const_cast<void *>(CFBridgingRetain(task)); +} + +void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + assert(ptr->ptr); + + NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr); + ptr->ptr = nullptr; + [task cancel]; +} + +} diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp new file mode 100644 index 0000000000..772aeb86e1 --- /dev/null +++ b/common/http_request_baton_curl.cpp @@ -0,0 +1,465 @@ +#include <mbgl/storage/http_request_baton.hpp> +#include <mbgl/util/uv-messenger.h> +#include <mbgl/util/std.hpp> +#include <mbgl/util/ptr.hpp> + +#include <uv.h> +#include <curl/curl.h> + +#include <queue> +#include <cassert> +#include <cstring> +#include <ctime> +#include <xlocale.h> + +// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* Example application code using the multi socket interface to download + multiple files at once, but instead of using curl_multi_perform and + curl_multi_wait, which uses select(), we use libuv. + It supports epoll, kqueue, etc. on unixes and fast IO completion ports on + Windows, which means, it should be very fast on all platforms.. + + Written by Clemens Gruber, based on an outdated example from uvbook and + some tests from libuv. + + Requires libuv and (of course) libcurl. + + See http://nikhilm.github.com/uvbook/ for more information on libuv. +*/ + +// Handles the request thread + messaging to the thread. +static uv_once_t once; +static uv_loop_t loop; +static uv_messenger_t start_messenger; +static uv_messenger_t stop_messenger; +static uv_thread_t thread; +static unsigned long thread_id; + +// Used as the CURL timer function to periodically check for socket updates. +static uv_timer_t timeout; + +// CURL multi handle that we use to request multiple URLs at the same time, without having to block +// and spawn threads. +static CURLM *multi = nullptr; + +// CURL share handles are used for sharing session state (e.g.) +static uv_mutex_t share_mutex; +static CURLSH *share = nullptr; + +// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them +// all the time. +static std::queue<CURL *> handles; + +namespace mbgl { + +struct CURLContext { + util::ptr<HTTPRequestBaton> baton; + uv_poll_t *poll_handle = nullptr; + curl_socket_t sockfd = 0; + curl_slist *headers = nullptr; + + CURLContext(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) { + } + + ~CURLContext() { + // We are destructing the poll handle in a CURL callback already. + assert(!poll_handle); + + // Once the CURLContext gets destroyed, CURL doesn't need any headers anymore. + if (headers) { + curl_slist_free_all(headers); + headers = nullptr; + } + } +}; + +// Locks the CURL share handle +void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { + uv_mutex_lock(&share_mutex); +} + +// Unlocks the CURL share handle +void curl_share_unlock(CURL *, curl_lock_data, void *) { + uv_mutex_unlock(&share_mutex); +} + +// This function must run in the CURL thread. +// It is either called when the request is completed, or when we try to cancel the request. +void finish_request(const util::ptr<HTTPRequestBaton> &baton) { + assert(uv_thread_self() == thread_id); + if (baton->ptr) { + CURL *handle = (CURL *)baton->ptr; + CURLMcode error = curl_multi_remove_handle(multi, handle); + if (error != CURLM_OK) { + baton->response = std::make_unique<Response>(); + baton->response->code = -1; + baton->response->message = curl_multi_strerror(error); + } + + // Destroy the shared pointer. We still have one pointing to it + CURLContext *context = nullptr; + curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context); + curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr); + delete context; + + // TODO: delete the headers object again. + + curl_easy_reset(handle); + handles.push(handle); + baton->ptr = nullptr; + } +} + +void curl_perform(uv_poll_t *req, int, int events) { + int running_handles; + int flags = 0; + CURLContext *context = (CURLContext *)req->data; + CURLMsg *message; + int pending; + + uv_timer_stop(&timeout); + + if (events & UV_READABLE) { + flags |= CURL_CSELECT_IN; + } + if (events & UV_WRITABLE) { + flags |= CURL_CSELECT_OUT; + } + + curl_multi_socket_action(multi, context->sockfd, flags, &running_handles); + + while ((message = curl_multi_info_read(multi, &pending))) { + switch (message->msg) { + case CURLMSG_DONE: { + CURLContext *context = nullptr; + curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context); + + // Make a copy so that the Baton stays around even after we are calling finish_request + util::ptr<HTTPRequestBaton> baton = context->baton; + + // Add human-readable error code + if (message->data.result != CURLE_OK) { + baton->response->message = curl_easy_strerror(message->data.result); + baton->response->code = -1; + + switch (message->data.result) { + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + baton->type = HTTPResponseType::ConnectionError; + break; + + case CURLE_OPERATION_TIMEDOUT: + baton->type = HTTPResponseType::TemporaryError; + break; + + default: + baton->type = HTTPResponseType::PermanentError; + } + } else { + long code = 0; + curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &code); + + if (code != 304) { + baton->response->code = code; + } + + if (code == 304) { + baton->type = HTTPResponseType::NotModified; + } else if (code == 200) { + baton->type = HTTPResponseType::Successful; + } else if (code >= 500 && code < 600) { + baton->type = HTTPResponseType::TemporaryError; + } else if (code >= 400 && code < 500) { + baton->type = HTTPResponseType::PermanentError; + } else { + assert(!"code must be either 200 or 304"); + } + } + + // We're currently in the CURL request thread. + finish_request(baton); + + if (baton->async) { + uv_async_send(baton->async); + baton->async = nullptr; + } + + break; + } + + default: + // This should never happen, because there are no other message types. + throw std::runtime_error("CURLMSG returned unknown message type"); + } + } +} + +int handle_socket(CURL *handle, curl_socket_t sockfd, int action, void *, void *socketp) { + CURLContext *context = nullptr; + curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context); + + if (!socketp && action != CURL_POLL_REMOVE) { + // We haven't initialized the socket yet, so we need to do this now. + assert(context->sockfd == 0); + context->sockfd = sockfd; + assert(!context->poll_handle); + context->poll_handle = new uv_poll_t; + uv_poll_init_socket(&loop, context->poll_handle, sockfd); + context->poll_handle->data = context; + curl_multi_assign(multi, sockfd, context); + } + + if (context) { + if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { + uv_poll_start(context->poll_handle, UV_READABLE, curl_perform); + } + if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { + uv_poll_start(context->poll_handle, UV_WRITABLE, curl_perform); + } + if (action == CURL_POLL_REMOVE && socketp) { + uv_poll_stop(context->poll_handle); + uv_close((uv_handle_t *)context->poll_handle, [](uv_handle_t *handle) { + delete (uv_poll_t *)handle; + }); + context->poll_handle = nullptr; + curl_multi_assign(multi, sockfd, NULL); + } + } + + return 0; +} + +void on_timeout(uv_timer_t *) { + int running_handles; + CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); + if (error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + } +} + +void start_timeout(CURLM *, long timeout_ms, void *) { + if (timeout_ms <= 0) { + on_timeout(&timeout); + } else { + uv_timer_start(&timeout, on_timeout, timeout_ms, 0); + } +} + +void thread_init(void *) { +#ifdef __APPLE__ + pthread_setname_np("CURL"); +#endif + thread_id = uv_thread_self(); + + uv_timer_init(&loop, &timeout); + + CURLSHcode share_error; + share = curl_share_init(); + + share_error = curl_share_setopt(share, CURLSHOPT_LOCKFUNC, curl_share_lock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + share_error = curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + + CURLMcode multi_error; + multi = curl_multi_init(); + + multi_error = curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handle_socket); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + } + multi_error = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, start_timeout); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + + } + + // Main event loop. This will not return until the request loop is terminated. + uv_run(&loop, UV_RUN_DEFAULT); + + curl_multi_cleanup(multi); + multi = nullptr; + + curl_share_cleanup(share); + share = nullptr; + + thread_id = -1; +} + +// This function is called when we have new data for a request. We just append it to the string +// containing the previous data. +size_t curl_write_cb(void *const contents, const size_t size, const size_t nmemb, void *const userp) { + ((std::string *)userp)->append((char *)contents, size * nmemb); + return size * nmemb; +} + +// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!) +// header string. If the data buffer contains the header string at the beginning, it returns +// the length of the header string == begin of the value, otherwise it returns npos. +// The comparison of the header is ASCII-case-insensitive. +size_t header_matches(const char *const header, const char *const buffer, const size_t length) { + const size_t header_length = strlen(header); + if (length < header_length) return std::string::npos; + size_t i = 0; + while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) { + i++; + } + return i == header_length ? i : std::string::npos; +} + +size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) { + const size_t length = size * nmemb; + + Response *response = static_cast<Response *>(userp); + + size_t begin = std::string::npos; + if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) { + // Always overwrite the modification date; We might already have a value here from the + // Date header, but this one is more accurate. + const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n + response->modified = curl_getdate(value.c_str(), nullptr); + } else if ((begin = header_matches("etag: ", buffer, length)) != std::string::npos) { + response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n + } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) { + const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n + response->expires = Response::parseCacheControl(value.c_str()); + } + + return length; +} + +// This function must run in the CURL thread. +void start_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr }; + util::ptr<HTTPRequestBaton> &baton = *baton_guard.get(); + assert(baton); + + // Create a C locale + static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr); + + CURL *handle = nullptr; + if (!handles.empty()) { + handle = handles.front(); + handles.pop(); + } else { + handle = curl_easy_init(); + } + + baton->ptr = handle; + + // Wrap this in a unique_ptr for now so that it destructs until we assign it the the CURL handle. + std::unique_ptr<CURLContext> context = std::make_unique<CURLContext>(baton); + + if (baton->response) { + if (!baton->response->etag.empty()) { + const std::string header = std::string("If-None-Match: ") + baton->response->etag; + context->headers = curl_slist_append(context->headers, header.c_str()); + } else if (baton->response->modified) { + const time_t modified = baton->response->modified; + struct tm *timeinfo = std::gmtime(&modified); + char buffer[64]; + strftime_l(buffer, 64, "If-Modified-Since: %a, %d %b %Y %H:%M:%S GMT", timeinfo, locale); + context->headers = curl_slist_append(context->headers, buffer); + } + } + + if (context->headers) { + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, context->headers); + } + + if (!baton->response) { + baton->response = std::make_unique<Response>(); + } + + // Carry on the shared pointer in the private information of the CURL handle. + curl_easy_setopt(handle, CURLOPT_PRIVATE, context.release()); + curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"); + curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str()); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &baton->response->data); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, baton->response.get()); + curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_SHARE, share); + + // Start requesting the information. + curl_multi_add_handle(multi, handle); +} + +// This function must run in the CURL thread. +void stop_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr }; + util::ptr<HTTPRequestBaton> &baton = *baton_guard.get(); + assert(baton); + + if (baton->async) { + baton->type = HTTPResponseType::Canceled; + + // We can still stop the request because it is still in progress. + finish_request(baton); + + uv_async_send(baton->async); + baton->async = nullptr; + } else { + // If the async handle is gone, it means that the actual request has been completed before + // we got a chance to cancel it. In this case, this is a no-op. It is likely that + // the pointer below is the last lifeline of the HTTPRequestBaton. This means we're going + // to delete the HTTPRequestBaton in the current (CURL) thread. + } +} + +void create_thread() { + uv_mutex_init(&share_mutex); + uv_loop_init(&loop); + uv_messenger_init(&loop, &start_messenger, start_request); + uv_messenger_init(&loop, &stop_messenger, stop_request); + uv_thread_create(&thread, thread_init, nullptr); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr)); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr)); +} + +} diff --git a/common/ios.mm b/common/ios.mm new file mode 100644 index 0000000000..7989e73a4e --- /dev/null +++ b/common/ios.mm @@ -0,0 +1,21 @@ +#import <Foundation/Foundation.h> + +#include <mbgl/platform/platform.hpp> + +namespace mbgl { +namespace platform { + +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + if ([paths count] == 0) { + // Disable the cache if we don't have a location to write. + return ""; + } + + NSString *libraryDirectory = [paths objectAtIndex:0]; + return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String]; +} + +} +} diff --git a/common/linux.cpp b/common/linux.cpp new file mode 100644 index 0000000000..6132ace692 --- /dev/null +++ b/common/linux.cpp @@ -0,0 +1,12 @@ +#include <mbgl/platform/platform.hpp> + +namespace mbgl { +namespace platform { + +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + return "/tmp/mbgl-cache.db"; +} + +} +} diff --git a/common/osx.mm b/common/osx.mm new file mode 100644 index 0000000000..974ea537cb --- /dev/null +++ b/common/osx.mm @@ -0,0 +1,31 @@ +#import <Foundation/Foundation.h> + +#include <mbgl/platform/platform.hpp> + +namespace mbgl { +namespace platform { + +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + if ([paths count] == 0) { + // Disable the cache if we don't have a location to write. + return ""; + } + + NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"]; + + if (![[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:nil]) { + // Disable the cache if we couldn't create the directory. + return ""; + } + + return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String]; +} + +} +} @@ -107,6 +107,14 @@ def configure_curl(o): o['variables']['curl_libraries'] = ret[0].split() o['variables']['curl_cflags'] = ret[1].split() +def configure_sqlite3(o): + ret = pkg_config('sqlite3', options.pkgconfig_root) + if not ret: + sys.stderr.write('could not find sqlite3 with pkg-config') + sys.exit(-1) + o['variables']['sqlite3_libraries'] = ret[0].split() + o['variables']['sqlite3_cflags'] = ret[1].split() + def write(filename, data): filename = os.path.join(root_dir, filename) print "creating ", filename @@ -129,6 +137,7 @@ if __name__ == '__main__': configure_uv(output) configure_png(output) configure_curl(output) + configure_sqlite3(output) pprint.pprint(output, indent=2) write('config.gypi', "# Do not edit. Generated by the configure script.\n" + diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index ac33dba0ef..7e4687ea6f 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -7,11 +7,11 @@ #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/time.hpp> #include <mbgl/util/uv.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <atomic> #include <iosfwd> -#include <memory> #include <set> #include <vector> @@ -31,21 +31,31 @@ class FileSource; class View; class Map : private util::noncopyable { + typedef void (*stop_callback)(void *); + public: explicit Map(View &view); ~Map(); - // Start/stop the map render thread + // Start the map render thread. It is asynchronous. void start(); - void stop(); - // Runs the map event loop. + // Stop the map render thread. This call will block until the map rendering thread stopped. + // The optional callback function will be invoked repeatedly until the map thread is stopped. + // The callback function should wait until it is woken up again by view.notify(), otherwise + // this will be a busy waiting loop. The optional data parameter will be passed to the callback + // function. + void stop(stop_callback cb = nullptr, void *data = nullptr); + + // Runs the map event loop. ONLY run this function when you want to get render a single frame + // with this map object. It will *not* spawn a separate thread and instead block until the + // frame is completely rendered. void run(); // Triggers a lazy rerender: only performs a render when the map is not clean. void rerender(); - void renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id = nullptr, const mat4* matrix = nullptr); + void renderLayer(util::ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id = nullptr, const mat4* matrix = nullptr); // Forces a map update: always triggers a rerender. void update(); @@ -65,7 +75,7 @@ public: void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height); // Styling - const std::set<std::shared_ptr<StyleSource>> getActiveSources() const; + const std::set<util::ptr<StyleSource>> getActiveSources() const; void setAppliedClasses(const std::vector<std::string> &classes); void toggleClass(const std::string &name); const std::vector<std::string> &getAppliedClasses() const; @@ -116,16 +126,20 @@ public: void toggleDebug(); bool getDebug() const; + // Call this when the network reachability changed. + void setReachability(bool status); + public: inline const TransformState &getState() const { return state; } - inline std::shared_ptr<FileSource> getFileSource() const { return fileSource; } - inline std::shared_ptr<Style> getStyle() const { return style; } - inline std::shared_ptr<GlyphAtlas> getGlyphAtlas() { return glyphAtlas; } - inline std::shared_ptr<GlyphStore> getGlyphStore() { return glyphStore; } - inline std::shared_ptr<SpriteAtlas> getSpriteAtlas() { return spriteAtlas; } - std::shared_ptr<Sprite> getSprite(); - inline std::shared_ptr<Texturepool> getTexturepool() { return texturepool; } - inline std::shared_ptr<uv::loop> getLoop() { return loop; } + inline util::ptr<FileSource> getFileSource() const { return fileSource; } + inline util::ptr<Style> getStyle() const { return style; } + inline util::ptr<GlyphAtlas> getGlyphAtlas() { return glyphAtlas; } + inline util::ptr<GlyphStore> getGlyphStore() { return glyphStore; } + inline util::ptr<SpriteAtlas> getSpriteAtlas() { return spriteAtlas; } + util::ptr<Sprite> getSprite(); + inline util::ptr<Texturepool> getTexturepool() { return texturepool; } + inline util::ptr<uv::loop> getLoop() { return loop; } + uv::worker &getWorker(); inline timestamp getAnimationTime() const { return animationTime; } inline timestamp getTime() const { return animationTime; } void updateTiles(); @@ -141,7 +155,7 @@ private: void setup(); void updateSources(); - void updateSources(const std::shared_ptr<StyleLayerGroup> &group); + void updateSources(const util::ptr<StyleLayerGroup> &group); void updateRenderState(); @@ -154,15 +168,16 @@ private: // Unconditionally performs a render with the current map state. void render(); - void renderLayers(std::shared_ptr<StyleLayerGroup> group); + void renderLayers(util::ptr<StyleLayerGroup> group); private: bool async = false; - std::shared_ptr<uv::loop> loop; + util::ptr<uv::loop> loop; + std::unique_ptr<uv::worker> workers; std::unique_ptr<uv::thread> thread; - uv_async_t *async_terminate = nullptr; - uv_async_t *async_render = nullptr; - uv_async_t *async_cleanup = nullptr; + std::unique_ptr<uv_async_t> async_terminate; + std::unique_ptr<uv_async_t> async_render; + std::unique_ptr<uv_async_t> async_cleanup; private: // If cleared, the next time the render thread attempts to render the map, it will *actually* @@ -177,24 +192,33 @@ private: // ready for rendering. std::atomic_flag is_rendered = ATOMIC_FLAG_INIT; + // Stores whether the map thread has been stopped already. + std::atomic_bool is_stopped; + public: View &view; private: +#ifndef NDEBUG + const unsigned long main_thread; + unsigned long map_thread = -1; +#endif + Transform transform; TransformState state; - std::shared_ptr<FileSource> fileSource; + util::ptr<FileSource> fileSource; - std::shared_ptr<Style> style; - std::shared_ptr<GlyphAtlas> glyphAtlas; - std::shared_ptr<GlyphStore> glyphStore; - std::shared_ptr<SpriteAtlas> spriteAtlas; - std::shared_ptr<Sprite> sprite; - std::shared_ptr<Texturepool> texturepool; + util::ptr<Style> style; + util::ptr<GlyphAtlas> glyphAtlas; + util::ptr<GlyphStore> glyphStore; + util::ptr<SpriteAtlas> spriteAtlas; + util::ptr<Sprite> sprite; + util::ptr<Texturepool> texturepool; Painter painter; + std::string styleURL; std::string styleJSON = ""; std::string accessToken = ""; @@ -203,7 +227,7 @@ private: int indent = 0; - std::set<std::shared_ptr<StyleSource>> activeSources; + std::set<util::ptr<StyleSource>> activeSources; }; diff --git a/include/mbgl/map/raster_tile_data.hpp b/include/mbgl/map/raster_tile_data.hpp index 98aa3baaf5..14833c0d84 100644 --- a/include/mbgl/map/raster_tile_data.hpp +++ b/include/mbgl/map/raster_tile_data.hpp @@ -5,8 +5,6 @@ #include <mbgl/map/tile_data.hpp> #include <mbgl/renderer/raster_bucket.hpp> -#include <memory> - namespace mbgl { class Map; @@ -18,12 +16,12 @@ class RasterTileData : public TileData { friend class TileParser; public: - RasterTileData(Tile::ID id, Map &map, const SourceInfo &source); + RasterTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source); ~RasterTileData(); virtual void parse(); - virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix); - virtual bool hasData(std::shared_ptr<StyleLayer> layer_desc) const; + virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix); + virtual bool hasData(util::ptr<StyleLayer> layer_desc) const; protected: StyleBucketRaster properties; diff --git a/include/mbgl/map/source.hpp b/include/mbgl/map/source.hpp index cb069db272..e74ddb9902 100644 --- a/include/mbgl/map/source.hpp +++ b/include/mbgl/map/source.hpp @@ -8,12 +8,12 @@ #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/time.hpp> #include <mbgl/util/mat4.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <forward_list> #include <iosfwd> #include <map> -#include <memory> namespace mbgl { @@ -25,7 +25,7 @@ struct box; class Source : public std::enable_shared_from_this<Source>, private util::noncopyable { public: - Source(SourceInfo& info); + Source(const util::ptr<SourceInfo>& info); void load(Map &map); @@ -33,8 +33,8 @@ public: void updateMatrices(const mat4 &projMatrix, const TransformState &transform); void drawClippingMasks(Painter &painter); size_t getTileCount() const; - void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc); - void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix); + void render(Painter &painter, util::ptr<StyleLayer> layer_desc); + void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix); void finishRender(Painter &painter); std::forward_list<Tile::ID> getIDs() const; @@ -54,7 +54,7 @@ private: double getZoom(const TransformState &state) const; - SourceInfo& info; + util::ptr<SourceInfo> info; bool loaded = false; // Stores the time when this source was most recently updated. diff --git a/include/mbgl/map/sprite.hpp b/include/mbgl/map/sprite.hpp index 6461a5e33d..454ebd8886 100644 --- a/include/mbgl/map/sprite.hpp +++ b/include/mbgl/map/sprite.hpp @@ -3,11 +3,11 @@ #include <mbgl/util/image.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <atomic> #include <iosfwd> -#include <memory> #include <string> #include <unordered_map> #include <future> @@ -34,11 +34,11 @@ public: class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable { private: struct Key {}; - void load(const std::shared_ptr<FileSource> &fileSource); + void load(const util::ptr<FileSource> &fileSource); public: Sprite(const Key &, const std::string& base_url, float pixelRatio); - static std::shared_ptr<Sprite> Create(const std::string& base_url, float pixelRatio, const std::shared_ptr<FileSource> &fileSource); + static util::ptr<Sprite> Create(const std::string& base_url, float pixelRatio, const util::ptr<FileSource> &fileSource); const SpritePosition &getSpritePosition(const std::string& name) const; diff --git a/include/mbgl/map/tile.hpp b/include/mbgl/map/tile.hpp index 75ced7e384..b9f0556add 100644 --- a/include/mbgl/map/tile.hpp +++ b/include/mbgl/map/tile.hpp @@ -3,6 +3,7 @@ #include <mbgl/util/mat4.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <bitset> @@ -10,7 +11,6 @@ #include <cstdint> #include <forward_list> #include <iosfwd> -#include <memory> #include <string> namespace mbgl { @@ -78,7 +78,7 @@ public: const Tile::ID id; ClipID clip; mat4 matrix; - std::shared_ptr<TileData> data; + util::ptr<TileData> data; }; } diff --git a/include/mbgl/map/tile_data.hpp b/include/mbgl/map/tile_data.hpp index 9aaef84e04..07cf19c5c8 100644 --- a/include/mbgl/map/tile_data.hpp +++ b/include/mbgl/map/tile_data.hpp @@ -6,11 +6,11 @@ #include <mbgl/geometry/debug_font_buffer.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/ptr.hpp> #include <atomic> #include <exception> #include <iosfwd> -#include <memory> #include <string> namespace mbgl { @@ -19,8 +19,7 @@ class Map; class Painter; class SourceInfo; class StyleLayer; - -namespace platform { class Request; } +class Request; class TileData : public std::enable_shared_from_this<TileData>, private util::noncopyable { @@ -29,7 +28,7 @@ public: struct geometry_too_long_exception : exception {}; public: - typedef std::shared_ptr<TileData> Ptr; + typedef util::ptr<TileData> Ptr; enum class State { invalid, @@ -41,7 +40,7 @@ public: }; public: - TileData(Tile::ID id, Map &map, const SourceInfo &source); + TileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source); ~TileData(); void request(); @@ -57,8 +56,8 @@ public: virtual void beforeParse(); virtual void parse() = 0; virtual void afterParse(); - virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) = 0; - virtual bool hasData(std::shared_ptr<StyleLayer> layer_desc) const = 0; + virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) = 0; + virtual bool hasData(util::ptr<StyleLayer> layer_desc) const = 0; public: @@ -69,10 +68,10 @@ protected: Map ↦ public: - const SourceInfo &source; + util::ptr<SourceInfo> source; protected: - std::weak_ptr<platform::Request> req; + std::unique_ptr<Request> req; std::string data; // Contains the tile ID string for painting debug information. diff --git a/include/mbgl/map/tile_parser.hpp b/include/mbgl/map/tile_parser.hpp index fa64dad6d5..ddb4b98820 100644 --- a/include/mbgl/map/tile_parser.hpp +++ b/include/mbgl/map/tile_parser.hpp @@ -4,10 +4,10 @@ #include <mbgl/map/vector_tile.hpp> #include <mbgl/style/filter_expression.hpp> #include <mbgl/text/glyph.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <iosfwd> -#include <memory> #include <string> namespace mbgl { @@ -32,11 +32,11 @@ class Collision; class TileParser { public: TileParser(const std::string &data, VectorTileData &tile, - const std::shared_ptr<const Style> &style, - const std::shared_ptr<GlyphAtlas> &glyphAtlas, - const std::shared_ptr<GlyphStore> &glyphStore, - const std::shared_ptr<SpriteAtlas> &spriteAtlas, - const std::shared_ptr<Sprite> &sprite); + const util::ptr<const Style> &style, + const util::ptr<GlyphAtlas> &glyphAtlas, + const util::ptr<GlyphStore> &glyphStore, + const util::ptr<SpriteAtlas> &spriteAtlas, + const util::ptr<Sprite> &sprite); ~TileParser(); public: @@ -44,11 +44,11 @@ public: private: bool obsolete() const; - void parseStyleLayers(std::shared_ptr<StyleLayerGroup> group); - std::unique_ptr<Bucket> createBucket(std::shared_ptr<StyleBucket> bucket_desc); + void parseStyleLayers(util::ptr<StyleLayerGroup> group); + std::unique_ptr<Bucket> createBucket(util::ptr<StyleBucket> bucket_desc); std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill); - std::unique_ptr<Bucket> createRasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster &raster); + std::unique_ptr<Bucket> createRasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster &raster); std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line); std::unique_ptr<Bucket> createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol); @@ -59,12 +59,12 @@ private: VectorTileData& tile; // Cross-thread shared data. - std::shared_ptr<const Style> style; - std::shared_ptr<GlyphAtlas> glyphAtlas; - std::shared_ptr<GlyphStore> glyphStore; - std::shared_ptr<SpriteAtlas> spriteAtlas; - std::shared_ptr<Sprite> sprite; - std::shared_ptr<Texturepool> texturePool; + util::ptr<const Style> style; + util::ptr<GlyphAtlas> glyphAtlas; + util::ptr<GlyphStore> glyphStore; + util::ptr<SpriteAtlas> spriteAtlas; + util::ptr<Sprite> sprite; + util::ptr<Texturepool> texturePool; std::unique_ptr<Collision> collision; }; diff --git a/include/mbgl/map/transform.hpp b/include/mbgl/map/transform.hpp index d0f56b7fba..5404f333c1 100644 --- a/include/mbgl/map/transform.hpp +++ b/include/mbgl/map/transform.hpp @@ -98,10 +98,10 @@ private: // cache values for spherical mercator math double Bc, Cc; - std::forward_list<std::shared_ptr<util::transition>> transitions; - std::shared_ptr<util::transition> scale_timeout; - std::shared_ptr<util::transition> rotate_timeout; - std::shared_ptr<util::transition> pan_timeout; + std::forward_list<util::ptr<util::transition>> transitions; + util::ptr<util::transition> scale_timeout; + util::ptr<util::transition> rotate_timeout; + util::ptr<util::transition> pan_timeout; }; } diff --git a/include/mbgl/map/vector_tile_data.hpp b/include/mbgl/map/vector_tile_data.hpp index deb628e485..9de666c84f 100644 --- a/include/mbgl/map/vector_tile_data.hpp +++ b/include/mbgl/map/vector_tile_data.hpp @@ -26,14 +26,14 @@ class VectorTileData : public TileData { friend class TileParser; public: - VectorTileData(Tile::ID id, Map &map, const SourceInfo &source); + VectorTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source); ~VectorTileData(); virtual void beforeParse(); virtual void parse(); virtual void afterParse(); - virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix); - virtual bool hasData(std::shared_ptr<StyleLayer> layer_desc) const; + virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix); + virtual bool hasData(util::ptr<StyleLayer> layer_desc) const; protected: // Holds the actual geometries in this tile. diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index 3e2f1a4b5a..395a05d435 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -45,6 +45,8 @@ public: return 0; } + virtual void notify() = 0; + // Notifies a watcher of map x/y/scale/rotation changes. // Must only be called from the same thread that caused the change. // Must not be called from the render thread. diff --git a/include/mbgl/platform/event.hpp b/include/mbgl/platform/event.hpp index 1676f40d2c..d2a06618a3 100644 --- a/include/mbgl/platform/event.hpp +++ b/include/mbgl/platform/event.hpp @@ -29,6 +29,7 @@ enum class Event : uint8_t { ParseStyle, ParseTile, Render, + Database, HttpRequest, Sprite, }; @@ -40,6 +41,7 @@ MBGL_DEFINE_ENUM_CLASS(EventClass, Event, { { Event::ParseStyle, "ParseStyle" }, { Event::ParseTile, "ParseTile" }, { Event::Render, "Render" }, + { Event::Database, "Database" }, { Event::HttpRequest, "HttpRequest" }, { Event::Sprite, "Sprite" }, { Event(-1), "Unknown" }, diff --git a/include/mbgl/platform/platform.hpp b/include/mbgl/platform/platform.hpp index 22405a4cfd..b7107bb9cd 100644 --- a/include/mbgl/platform/platform.hpp +++ b/include/mbgl/platform/platform.hpp @@ -4,7 +4,6 @@ #include <mbgl/util/uv.hpp> #include <memory> -#include <functional> #include <string> namespace mbgl { @@ -12,30 +11,14 @@ namespace platform { class Request; -struct Response { - Response(std::function<void(Response *)> callback) : callback(callback) {} - int16_t code = -1; - std::string body; - std::string error_message; - std::function<void(Response *)> callback; -}; - -// Makes an HTTP request of a URL, preferrably on a background thread, and calls a function with the -// results in the original thread (which runs the libuv loop). -// If the loop pointer is NULL, the callback function will be called on an arbitrary thread. -// Returns a cancellable request. -std::shared_ptr<Request> request_http(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop = nullptr); - // Uppercase a string, potentially using platform-specific routines. std::string uppercase(const std::string &string); // Lowercase a string, potentially using platform-specific routines. std::string lowercase(const std::string &string); -// Cancels an HTTP request. -void cancel_request_http(const std::shared_ptr<Request> &req); +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase(); // Shows an alpha image with the specified dimensions in a named window. void show_debug_image(std::string name, const char *data, size_t width, size_t height); diff --git a/include/mbgl/platform/request.hpp b/include/mbgl/platform/request.hpp deleted file mode 100644 index 2a231769c1..0000000000 --- a/include/mbgl/platform/request.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef MBGL_PLATFORM_REQUEST -#define MBGL_PLATFORM_REQUEST - -#include <string> -#include <functional> -#include <memory> -#include <atomic> - -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/uv.hpp> - -namespace mbgl { -namespace platform { - -struct Response; - -class Request : public std::enable_shared_from_this<Request>, private util::noncopyable { -public: - Request(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop); - ~Request(); - - void complete(); - -private: - static void complete(uv_async_t *async, int status); - -public: - const std::string url; - std::unique_ptr<Response> res; - std::atomic<bool> cancelled; - -public: - uv_async_t *async = nullptr; - std::shared_ptr<uv::loop> loop; -}; -} -} - -#endif diff --git a/include/mbgl/renderer/bucket.hpp b/include/mbgl/renderer/bucket.hpp index 1391f6e3e3..696bfb1110 100644 --- a/include/mbgl/renderer/bucket.hpp +++ b/include/mbgl/renderer/bucket.hpp @@ -1,11 +1,11 @@ #ifndef MBGL_RENDERER_BUCKET #define MBGL_RENDERER_BUCKET -#include <string> -#include <memory> #include <mbgl/map/tile.hpp> #include <mbgl/util/noncopyable.hpp> +#include <string> + namespace mbgl { class Painter; @@ -13,7 +13,7 @@ class StyleLayer; class Bucket : private util::noncopyable { public: - virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) = 0; + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) = 0; virtual bool hasData() const = 0; virtual ~Bucket() {} diff --git a/include/mbgl/renderer/debug_bucket.hpp b/include/mbgl/renderer/debug_bucket.hpp index 660b7fcba8..fb6cfb4cae 100644 --- a/include/mbgl/renderer/debug_bucket.hpp +++ b/include/mbgl/renderer/debug_bucket.hpp @@ -6,7 +6,6 @@ #include <mbgl/geometry/vao.hpp> #include <vector> -#include <memory> #ifndef BUFFER_OFFSET #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) @@ -20,7 +19,7 @@ class DebugBucket : public Bucket { public: DebugBucket(DebugFontBuffer& fontBuffer); - virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); virtual bool hasData() const; void drawLines(PlainShader& shader); diff --git a/include/mbgl/renderer/fill_bucket.hpp b/include/mbgl/renderer/fill_bucket.hpp index 15868e4092..ae766ec28d 100644 --- a/include/mbgl/renderer/fill_bucket.hpp +++ b/include/mbgl/renderer/fill_bucket.hpp @@ -44,7 +44,7 @@ public: const StyleBucketFill& properties); ~FillBucket(); - virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); virtual bool hasData() const; void addGeometry(pbf& data); diff --git a/include/mbgl/renderer/line_bucket.hpp b/include/mbgl/renderer/line_bucket.hpp index 8babb734ed..7337ca80ad 100644 --- a/include/mbgl/renderer/line_bucket.hpp +++ b/include/mbgl/renderer/line_bucket.hpp @@ -1,13 +1,12 @@ #ifndef MBGL_RENDERER_LINEBUCKET #define MBGL_RENDERER_LINEBUCKET -#include "bucket.hpp" +#include <mbgl/renderer/bucket.hpp> #include <mbgl/geometry/vao.hpp> #include <mbgl/geometry/elements_buffer.hpp> #include <mbgl/geometry/line_buffer.hpp> #include <mbgl/style/style_bucket.hpp> -#include <memory> #include <vector> namespace mbgl { @@ -30,7 +29,7 @@ public: PointElementsBuffer& pointElementsBuffer, const StyleBucketLine& properties); - virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); virtual bool hasData() const; void addGeometry(pbf& data); diff --git a/include/mbgl/renderer/painter.hpp b/include/mbgl/renderer/painter.hpp index 15331b9b67..0f9bd79173 100644 --- a/include/mbgl/renderer/painter.hpp +++ b/include/mbgl/renderer/painter.hpp @@ -22,6 +22,7 @@ #include <mbgl/shader/gaussian_shader.hpp> #include <mbgl/map/transform_state.hpp> +#include <mbgl/util/ptr.hpp> #include <map> #include <unordered_map> @@ -73,7 +74,7 @@ public: void changeMatrix(); // Renders a particular layer from a tile. - void renderTileLayer(const Tile& tile, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix); + void renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix); // Renders debug information for a tile. void renderTileDebug(const Tile& tile); @@ -83,11 +84,11 @@ public: void renderDebugText(DebugBucket& bucket, const mat4 &matrix); void renderDebugText(const std::vector<std::string> &strings); - void renderFill(FillBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); - void renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); - void renderSymbol(SymbolBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); - void renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); - void renderBackground(std::shared_ptr<StyleLayer> layer_desc); + void renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderSymbol(SymbolBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderBackground(util::ptr<StyleLayer> layer_desc); float saturationFactor(float saturation); float contrastFactor(float contrast); @@ -97,7 +98,7 @@ public: void renderPrerenderedTexture(RasterBucket &bucket, const mat4 &matrix, const RasterProperties& properties); - void createPrerendered(RasterBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id); + void createPrerendered(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id); void resize(); @@ -111,7 +112,7 @@ public: // Configures the painter strata that is used for early z-culling of fragments. void setStrata(float strata); - void drawClippingMasks(const std::set<std::shared_ptr<StyleSource>> &sources); + void drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources); void drawClippingMask(const mat4& matrix, const ClipID& clip); void resetFramebuffer(); diff --git a/include/mbgl/renderer/raster_bucket.hpp b/include/mbgl/renderer/raster_bucket.hpp index 66cceac8e7..794da3b143 100644 --- a/include/mbgl/renderer/raster_bucket.hpp +++ b/include/mbgl/renderer/raster_bucket.hpp @@ -16,9 +16,9 @@ class VertexArrayObject; class RasterBucket : public Bucket { public: - RasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster& properties); + RasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster& properties); - virtual void render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); virtual bool hasData() const; bool setImage(const std::string &data); diff --git a/include/mbgl/renderer/symbol_bucket.hpp b/include/mbgl/renderer/symbol_bucket.hpp index c71d276456..42682401ef 100644 --- a/include/mbgl/renderer/symbol_bucket.hpp +++ b/include/mbgl/renderer/symbol_bucket.hpp @@ -56,7 +56,7 @@ class SymbolBucket : public Bucket { public: SymbolBucket(const StyleBucketSymbol &properties, Collision &collision); - virtual void render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix); + virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix); virtual bool hasData() const; virtual bool hasTextData() const; virtual bool hasIconData() const; diff --git a/include/mbgl/storage/base_request.hpp b/include/mbgl/storage/base_request.hpp new file mode 100644 index 0000000000..16ff24faa3 --- /dev/null +++ b/include/mbgl/storage/base_request.hpp @@ -0,0 +1,62 @@ +#ifndef MBGL_STORAGE_BASE_REQUEST +#define MBGL_STORAGE_BASE_REQUEST + +#include <mbgl/storage/request_callback.hpp> +#include <mbgl/util/ptr.hpp> + +#include <string> +#include <forward_list> +#include <functional> + + +typedef struct uv_loop_s uv_loop_t; +typedef struct uv_async_s uv_async_t; + +namespace mbgl { + +class Response; +class Request; + +class BaseRequest { +private: + // Make noncopyable and immovable + BaseRequest(const BaseRequest &) = delete; + BaseRequest(BaseRequest &&) = delete; + BaseRequest& operator=(const BaseRequest &) = delete; + BaseRequest& operator=(BaseRequest &&) = delete; + +public: + BaseRequest(const std::string &path); + virtual ~BaseRequest(); + + Callback *add(Callback &&callback, const util::ptr<BaseRequest> &request); + void remove(Callback *callback); + + // Must be called by subclasses when a valid Response object is available. It will notify + // all listeners. + void notify(); + + // This function is called when the request ought to be stopped. Any subclass must make sure this + // is also called in its destructor. Calling this function repeatedly must be safe. + // This function must call notify(). + virtual void cancel() = 0; + + // This function is called when the request should be reattempted immediately. This is typically + // reaction to a network status change. + virtual void retryImmediately(); + +public: + const unsigned long thread_id; + const std::string path; + std::unique_ptr<Response> response; + +protected: + // This object may hold a shared_ptr to itself. It does this to prevent destruction of this object + // while a request is in progress. + util::ptr<BaseRequest> self; + std::forward_list<std::unique_ptr<Callback>> callbacks; +}; + +} + +#endif diff --git a/include/mbgl/storage/file_request.hpp b/include/mbgl/storage/file_request.hpp new file mode 100644 index 0000000000..2f883728ff --- /dev/null +++ b/include/mbgl/storage/file_request.hpp @@ -0,0 +1,27 @@ +#ifndef MBGL_STORAGE_FILE_REQUEST +#define MBGL_STORAGE_FILE_REQUEST + +#include <mbgl/storage/base_request.hpp> + +namespace mbgl { + +typedef struct uv_loop_s uv_loop_t; + +struct FileRequestBaton; + +class FileRequest : public BaseRequest { +public: + FileRequest(const std::string &path, uv_loop_t *loop); + ~FileRequest(); + + void cancel(); + +private: + FileRequestBaton *ptr = nullptr; + + friend struct FileRequestBaton; +}; + +} + +#endif
\ No newline at end of file diff --git a/include/mbgl/storage/file_request_baton.hpp b/include/mbgl/storage/file_request_baton.hpp new file mode 100644 index 0000000000..0f1968ca13 --- /dev/null +++ b/include/mbgl/storage/file_request_baton.hpp @@ -0,0 +1,35 @@ +#ifndef MBGL_STORAGE_FILE_REQUEST_BATON +#define MBGL_STORAGE_FILE_REQUEST_BATON + +#include <mbgl/storage/file_request.hpp> + +#include <uv.h> + +namespace mbgl { + +struct FileRequestBaton { + FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop); + ~FileRequestBaton(); + + void cancel(); + static void file_opened(uv_fs_t *req); + static void file_stated(uv_fs_t *req); + static void file_read(uv_fs_t *req); + static void file_closed(uv_fs_t *req); + static void notify_error(uv_fs_t *req); + static void cleanup(uv_fs_t *req); + + const unsigned long thread_id; + FileRequest *request = nullptr; + uv_fs_t req; + uv_file fd = -1; + bool canceled = false; + std::string body; + uv_buf_t buffer; +}; + + +} + + +#endif diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp new file mode 100644 index 0000000000..4b17cd29a7 --- /dev/null +++ b/include/mbgl/storage/file_source.hpp @@ -0,0 +1,55 @@ +#ifndef MBGL_STORAGE_FILE_SOURCE +#define MBGL_STORAGE_FILE_SOURCE + +#include <mbgl/storage/resource_type.hpp> +#include <mbgl/storage/request.hpp> + +#include <string> +#include <unordered_map> +#include <functional> + +typedef struct uv_loop_s uv_loop_t; +typedef struct uv_messenger_s uv_messenger_t; + +namespace mbgl { + +class BaseRequest; +class SQLiteStore; + +class FileSource { +private: + FileSource(const FileSource &) = delete; + FileSource(FileSource &&) = delete; + FileSource& operator=(const FileSource &) = delete; + FileSource& operator=(FileSource &&) = delete; + +public: + FileSource(uv_loop_t *loop, const std::string &path); + ~FileSource(); + +public: + // Stores and retrieves the base path/URL for relative requests + void setBase(const std::string &value); + const std::string &getBase() const; + + std::unique_ptr<Request> request(ResourceType type, const std::string &url); + + void prepare(std::function<void()> fn); + + void retryAllPending(); + +private: + const unsigned long thread_id; + + // Stores a URL that is used as a base for loading resources with relative path. + std::string base; + + std::unordered_map<std::string, std::weak_ptr<BaseRequest>> pending; + util::ptr<SQLiteStore> store; + uv_loop_t *loop = nullptr; + uv_messenger_t *queue = nullptr; +}; + +} + +#endif diff --git a/include/mbgl/storage/http_request.hpp b/include/mbgl/storage/http_request.hpp new file mode 100644 index 0000000000..ec76f1147c --- /dev/null +++ b/include/mbgl/storage/http_request.hpp @@ -0,0 +1,57 @@ +#ifndef MBGL_STORAGE_HTTP_REQUEST +#define MBGL_STORAGE_HTTP_REQUEST + +#include <mbgl/storage/resource_type.hpp> +#include <mbgl/storage/base_request.hpp> +#include <mbgl/storage/http_request_baton.hpp> + +#include <string> +#include <memory> +#include <cassert> + +typedef struct uv_loop_s uv_loop_t; +typedef struct uv_timer_s uv_timer_t; + +namespace mbgl { + +struct CacheRequestBaton; +struct HTTPRequestBaton; +struct CacheEntry; +class SQLiteStore; + +class HTTPRequest : public BaseRequest { +public: + HTTPRequest(ResourceType type, const std::string &path, uv_loop_t *loop, util::ptr<SQLiteStore> store); + ~HTTPRequest(); + + void cancel(); + void retryImmediately(); + +private: + void startCacheRequest(); + void handleCacheResponse(std::unique_ptr<Response> &&response); + void startHTTPRequest(std::unique_ptr<Response> &&res); + void handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&response); + + void retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout); + + void removeCacheBaton(); + void removeHTTPBaton(); + void removeBackoffTimer(); + +private: + const unsigned long thread_id; + uv_loop_t *const loop; + CacheRequestBaton *cache_baton = nullptr; + util::ptr<HTTPRequestBaton> http_baton; + uv_timer_t *backoff_timer = nullptr; + util::ptr<SQLiteStore> store; + const ResourceType type; + uint8_t attempts = 0; + + friend struct HTTPRequestBaton; +}; + +} + +#endif
\ No newline at end of file diff --git a/include/mbgl/storage/http_request_baton.hpp b/include/mbgl/storage/http_request_baton.hpp new file mode 100644 index 0000000000..279784ec25 --- /dev/null +++ b/include/mbgl/storage/http_request_baton.hpp @@ -0,0 +1,73 @@ +#ifndef MBGL_STORAGE_HTTP_REQUEST_BATON +#define MBGL_STORAGE_HTTP_REQUEST_BATON + +#include <mbgl/storage/response.hpp> +#include <mbgl/util/ptr.hpp> + +#include <string> + +typedef struct uv_async_s uv_async_t; + +namespace mbgl { + +class HTTPRequest; + +enum class HTTPResponseType : int8_t { + // This error probably won't be resolved by retrying anytime soon. We are giving up. + PermanentError = -5, + + // This error might be resolved by waiting some time (e.g. server issues). + // We are going to do an exponential back-off and will try again in a few seconds. + TemporaryError = -4, + + // This error was caused by a temporary error and it is likely that it will be resolved + // immediately. We are going to try again right away. This is like the TemporaryError, except + // that we will not perform exponential back-off. + SingularError = -3, + + // This error might be resolved once the network reachability status changes. + // We are going to watch the network status for changes and will retry as soon as the operating + // system notifies us of a network status change. + ConnectionError = -2, + + // The request was canceled programatically. + Canceled = -1, + + // The request is still in progress. + Unknown = 0, + + // The request returned data successfully. We retrieved and decoded the data successfully. + Successful = 1, + + // The request confirmed that the data wasn't changed. We already have the data. + NotModified = 2, +}; + +struct HTTPRequestBaton { + HTTPRequestBaton(const std::string &path); + ~HTTPRequestBaton(); + + const unsigned long thread_id; + const std::string path; + + HTTPRequest *request = nullptr; + uv_async_t *async = nullptr; + + HTTPResponseType type = HTTPResponseType::Unknown; + std::unique_ptr<Response> response; + + // Implementation specific use. + void *ptr = nullptr; + + // IMPLEMENT THESE 3 PLATFORM SPECIFIC FUNCTIONS: + + // Begin the HTTP request. Platform-specific implementation. + static void start(const util::ptr<HTTPRequestBaton> &ptr); + + // This will be called to stop/cancel the HTTP request (if possible). Platform-specific implementation. + static void stop(const util::ptr<HTTPRequestBaton> &ptr); +}; + +} + +#endif diff --git a/include/mbgl/storage/request.hpp b/include/mbgl/storage/request.hpp new file mode 100644 index 0000000000..e603ee527a --- /dev/null +++ b/include/mbgl/storage/request.hpp @@ -0,0 +1,39 @@ +#ifndef MBGL_STORAGE_REQUEST +#define MBGL_STORAGE_REQUEST + +#include <mbgl/storage/request_callback.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/util/ptr.hpp> + +#include <forward_list> + +typedef struct uv_loop_s uv_loop_t; + +namespace mbgl { + +class BaseRequest; + +class Request { +private: + Request(const Request &) = delete; + Request(Request &&) = delete; + Request& operator=(const Request &) = delete; + Request& operator=(Request &&) = delete; + +public: + Request(const util::ptr<BaseRequest> &base); + ~Request(); + + void onload(CompletedCallback cb); + void oncancel(AbortedCallback cb); + void cancel(); + +private: + const unsigned long thread_id; + util::ptr<BaseRequest> base; + std::forward_list<Callback *> callbacks; +}; + +} + +#endif
\ No newline at end of file diff --git a/include/mbgl/storage/request_callback.hpp b/include/mbgl/storage/request_callback.hpp new file mode 100644 index 0000000000..01427bd96d --- /dev/null +++ b/include/mbgl/storage/request_callback.hpp @@ -0,0 +1,22 @@ +#ifndef MBGL_STORAGE_REQUEST_CALLBACK +#define MBGL_STORAGE_REQUEST_CALLBACK + +#include <mbgl/util/variant.hpp> + +#include <functional> + +namespace mbgl { + +class Response; + +using CompletedCallback = std::function<void(const Response &)>; +using AbortedCallback = std::function<void()>; + +using Callback = mapbox::util::variant< + CompletedCallback, + AbortedCallback +>; + +} + +#endif diff --git a/include/mbgl/storage/resource_type.hpp b/include/mbgl/storage/resource_type.hpp new file mode 100644 index 0000000000..b7204a9fa1 --- /dev/null +++ b/include/mbgl/storage/resource_type.hpp @@ -0,0 +1,18 @@ +#ifndef MBGL_STORAGE_RESOURCE_TYPE +#define MBGL_STORAGE_RESOURCE_TYPE + +#include <cstdint> + +namespace mbgl { + +enum class ResourceType : uint8_t { + Unknown = 0, + Tile = 1, + Glyphs = 2, + Image = 3, + JSON = 4 +}; + +} + +#endif diff --git a/include/mbgl/storage/response.hpp b/include/mbgl/storage/response.hpp new file mode 100644 index 0000000000..9357ad3c63 --- /dev/null +++ b/include/mbgl/storage/response.hpp @@ -0,0 +1,26 @@ +#ifndef MBGL_STORAGE_RESPONSE +#define MBGL_STORAGE_RESPONSE + +#include <string> +#include <ctime> + +namespace mbgl { + + + +class Response { +public: + long code = 0; + int64_t modified = 0; + int64_t expires = 0; + std::string etag; + std::string data; + + std::string message; + + static int64_t parseCacheControl(const char *value); +}; + +} + +#endif
\ No newline at end of file diff --git a/include/mbgl/storage/sqlite_store.hpp b/include/mbgl/storage/sqlite_store.hpp new file mode 100644 index 0000000000..cb7730d0bf --- /dev/null +++ b/include/mbgl/storage/sqlite_store.hpp @@ -0,0 +1,47 @@ +#ifndef MBGL_STORAGE_SQLITE_STORE +#define MBGL_STORAGE_SQLITE_STORE + +#include <mbgl/storage/file_source.hpp> +#include <mbgl/storage/response.hpp> + +#include <uv.h> + +#include <string> + +typedef struct uv_worker_s uv_worker_t; + +namespace mapbox { +namespace sqlite { +class Database; +} +} + +namespace mbgl { + +class SQLiteStore { +public: + SQLiteStore(uv_loop_t *loop, const std::string &path); + ~SQLiteStore(); + + typedef void (*GetCallback)(std::unique_ptr<Response> &&entry, void *ptr); + + void get(const std::string &path, GetCallback cb, void *ptr); + void put(const std::string &path, ResourceType type, const Response &entry); + void updateExpiration(const std::string &path, int64_t expires); + +private: + void createSchema(); + void closeDatabase(); + static void runGet(uv_work_t *req); + static void runPut(uv_work_t *req); + static void deliverResult(uv_work_t *req, int status); + +private: + const unsigned long thread_id; + util::ptr<mapbox::sqlite::Database> db; + uv_worker_t *worker = nullptr; +}; + +} + +#endif diff --git a/include/mbgl/style/style.hpp b/include/mbgl/style/style.hpp index c09de6ebba..56f318ecbb 100644 --- a/include/mbgl/style/style.hpp +++ b/include/mbgl/style/style.hpp @@ -6,6 +6,7 @@ #include <mbgl/util/time.hpp> #include <mbgl/util/uv.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <map> @@ -13,7 +14,6 @@ #include <unordered_map> #include <vector> #include <set> -#include <memory> namespace mbgl { @@ -49,7 +49,7 @@ public: const std::string &getSpriteURL() const; public: - std::shared_ptr<StyleLayerGroup> layers; + util::ptr<StyleLayerGroup> layers; std::vector<std::string> appliedClasses; std::string glyph_url; diff --git a/include/mbgl/style/style_bucket.hpp b/include/mbgl/style/style_bucket.hpp index c2cde52aa5..c4a8f6037e 100644 --- a/include/mbgl/style/style_bucket.hpp +++ b/include/mbgl/style/style_bucket.hpp @@ -8,8 +8,8 @@ #include <mbgl/util/vec.hpp> #include <mbgl/util/variant.hpp> #include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/ptr.hpp> -#include <memory> #include <forward_list> namespace mbgl { @@ -93,12 +93,12 @@ typedef mapbox::util::variant<StyleBucketFill, StyleBucketLine, StyleBucketSymbo class StyleBucket { public: - typedef std::shared_ptr<StyleBucket> Ptr; + typedef util::ptr<StyleBucket> Ptr; StyleBucket(StyleLayerType type); std::string name; - std::shared_ptr<StyleSource> style_source; + util::ptr<StyleSource> style_source; std::string source_layer; FilterExpression filter; StyleBucketRender render = std::false_type(); diff --git a/include/mbgl/style/style_layer.hpp b/include/mbgl/style/style_layer.hpp index 84981e3fb4..641dc1e71c 100644 --- a/include/mbgl/style/style_layer.hpp +++ b/include/mbgl/style/style_layer.hpp @@ -6,8 +6,9 @@ #include <mbgl/style/style_properties.hpp> #include <mbgl/style/applied_class_properties.hpp> +#include <mbgl/util/ptr.hpp> + #include <vector> -#include <memory> #include <string> #include <map> #include <set> @@ -64,7 +65,7 @@ public: // Bucket information, telling the renderer how to generate the geometries // for this layer (feature property filters, tessellation instructions, ...). - std::shared_ptr<StyleBucket> bucket; + util::ptr<StyleBucket> bucket; // Contains all style classes that can be applied to this layer. const std::map<ClassID, ClassProperties> styles; @@ -80,7 +81,7 @@ public: StyleProperties properties; // Child layer array (if this layer has child layers). - std::shared_ptr<StyleLayerGroup> layers; + util::ptr<StyleLayerGroup> layers; }; } diff --git a/include/mbgl/style/style_layer_group.hpp b/include/mbgl/style/style_layer_group.hpp index 983dd136f0..1af6e23bd7 100644 --- a/include/mbgl/style/style_layer_group.hpp +++ b/include/mbgl/style/style_layer_group.hpp @@ -15,7 +15,7 @@ public: bool hasTransitions() const; public: - std::vector<std::shared_ptr<StyleLayer>> layers; + std::vector<util::ptr<StyleLayer>> layers; }; } diff --git a/include/mbgl/style/style_parser.hpp b/include/mbgl/style/style_parser.hpp index e4e1b7f632..fc253bb1dd 100644 --- a/include/mbgl/style/style_parser.hpp +++ b/include/mbgl/style/style_parser.hpp @@ -27,7 +27,7 @@ public: void parse(JSVal document); - std::shared_ptr<StyleLayerGroup> getLayers() { + util::ptr<StyleLayerGroup> getLayers() { return root; } @@ -46,14 +46,14 @@ private: void parseSources(JSVal value); std::unique_ptr<StyleLayerGroup> createLayers(JSVal value); - std::shared_ptr<StyleLayer> createLayer(JSVal value); + util::ptr<StyleLayer> createLayer(JSVal value); void parseLayers(); - void parseLayer(std::pair<JSVal, std::shared_ptr<StyleLayer>> &pair); + void parseLayer(std::pair<JSVal, util::ptr<StyleLayer>> &pair); void parseStyles(JSVal value, std::map<ClassID, ClassProperties> &styles); void parseStyle(JSVal, ClassProperties &properties); - void parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer); - void parseBucket(JSVal value, std::shared_ptr<StyleLayer> &layer); - void parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer); + void parseReference(JSVal value, util::ptr<StyleLayer> &layer); + void parseBucket(JSVal value, util::ptr<StyleLayer> &layer); + void parseRender(JSVal value, util::ptr<StyleLayer> &layer); void parseSprite(JSVal value); void parseGlyphURL(JSVal value); @@ -94,13 +94,13 @@ private: private: std::unordered_map<std::string, const rapidjson::Value *> constants; - std::unordered_map<std::string, const std::shared_ptr<StyleSource>> sources; + std::unordered_map<std::string, const util::ptr<StyleSource>> sources; // This stores the root layer. - std::shared_ptr<StyleLayerGroup> root; + util::ptr<StyleLayerGroup> root; // This maps ids to Layer objects, with all items being at the root level. - std::unordered_map<std::string, std::pair<JSVal, std::shared_ptr<StyleLayer>>> layers; + std::unordered_map<std::string, std::pair<JSVal, util::ptr<StyleLayer>>> layers; // Store a stack of layers we're parsing right now. This is to prevent reference cycles. std::forward_list<StyleLayer *> stack; diff --git a/include/mbgl/style/style_source.hpp b/include/mbgl/style/style_source.hpp index b598550c65..00c48431a1 100644 --- a/include/mbgl/style/style_source.hpp +++ b/include/mbgl/style/style_source.hpp @@ -2,18 +2,18 @@ #define MBGL_STYLE_STYLE_SOURCE #include <mbgl/style/types.hpp> +#include <mbgl/util/ptr.hpp> +#include <mbgl/util/noncopyable.hpp> +#include <rapidjson/document.h> -#include <memory> #include <vector> #include <string> -#include <rapidjson/document.h> - namespace mbgl { class Source; -class SourceInfo { +class SourceInfo : private util::noncopyable { public: SourceType type = SourceType::Vector; std::string url; @@ -31,12 +31,12 @@ public: class StyleSource : public std::enable_shared_from_this<StyleSource> { public: - SourceInfo info; + util::ptr<SourceInfo> info; bool enabled = false; - std::shared_ptr<Source> source; + util::ptr<Source> source; - StyleSource(const SourceInfo &info) + StyleSource(const util::ptr<SourceInfo> &info) : info(info) {} }; diff --git a/include/mbgl/text/glyph_store.hpp b/include/mbgl/text/glyph_store.hpp index e0c0391c73..f8a7cb4d0c 100644 --- a/include/mbgl/text/glyph_store.hpp +++ b/include/mbgl/text/glyph_store.hpp @@ -4,6 +4,7 @@ #include <mbgl/text/glyph.hpp> #include <mbgl/util/pbf.hpp> #include <mbgl/util/vec.hpp> +#include <mbgl/util/ptr.hpp> #include <cstdint> #include <vector> @@ -47,8 +48,15 @@ private: class GlyphPBF { public: - GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const std::shared_ptr<FileSource> &fileSource); + GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const util::ptr<FileSource> &fileSource); +private: + GlyphPBF(const GlyphPBF &) = delete; + GlyphPBF(GlyphPBF &&) = delete; + GlyphPBF &operator=(const GlyphPBF &) = delete; + GlyphPBF &operator=(GlyphPBF &&) = delete; + +public: void parse(FontStack &stack); std::shared_future<GlyphPBF &> getFuture(); @@ -63,7 +71,7 @@ private: // Manages Glyphrange PBF loading. class GlyphStore { public: - GlyphStore(const std::shared_ptr<FileSource> &fileSource); + GlyphStore(const util::ptr<FileSource> &fileSource); // Block until all specified GlyphRanges of the specified font stack are loaded. void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges); @@ -82,7 +90,7 @@ public: std::string glyphURL; private: - const std::shared_ptr<FileSource> fileSource; + const util::ptr<FileSource> fileSource; std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges; std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks; std::mutex mtx; diff --git a/include/mbgl/util/compression.hpp b/include/mbgl/util/compression.hpp new file mode 100644 index 0000000000..a33b2476a7 --- /dev/null +++ b/include/mbgl/util/compression.hpp @@ -0,0 +1,15 @@ +#ifndef MBGL_UTIL_COMPRESSION +#define MBGL_UTIL_COMPRESSION + +#include <string> + +namespace mbgl { +namespace util { + +std::string compress(const std::string &raw); +std::string decompress(const std::string &raw); + +} +} + +#endif diff --git a/include/mbgl/util/filesource.hpp b/include/mbgl/util/filesource.hpp deleted file mode 100644 index 18c63ddfeb..0000000000 --- a/include/mbgl/util/filesource.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef MBGL_UTIL_FILESOURCE -#define MBGL_UTIL_FILESOURCE - -#include <mbgl/util/uv.hpp> - -#include <string> -#include <memory> -#include <functional> - -namespace mbgl { - -namespace platform { -struct Response; -} - -enum class ResourceType : uint8_t { - Unknown, - Tile, - Glyphs, - Image, - JSON -}; - -class FileSource { -public: - FileSource(); - - void setBase(const std::string &value); - const std::string &getBase() const; - - void load(ResourceType type, const std::string &url, std::function<void(platform::Response *)> callback, const std::shared_ptr<uv::loop> loop = nullptr); - -private: - // Stores a URL that is used as a base for loading resources with relative path. - std::string base; - - // Stores the absolute path to the cache directory. - const std::string cache; -}; - -} - -#endif diff --git a/include/mbgl/util/interpolate.hpp b/include/mbgl/util/interpolate.hpp index e8c3389350..c9232db4eb 100644 --- a/include/mbgl/util/interpolate.hpp +++ b/include/mbgl/util/interpolate.hpp @@ -1,6 +1,8 @@ #ifndef MBGL_UTIL_INTERPOLATE #define MBGL_UTIL_INTERPOLATE +#include <array> + namespace mbgl { namespace util { diff --git a/include/mbgl/util/parsedate.h b/include/mbgl/util/parsedate.h new file mode 100644 index 0000000000..6905e361d4 --- /dev/null +++ b/include/mbgl/util/parsedate.h @@ -0,0 +1,38 @@ +#ifndef HEADER_PARSEDATE_H +#define HEADER_PARSEDATE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <time.h> + +time_t parse_date(const char *p); + +#ifdef __cplusplus +} +#endif + +#endif /* HEADER_PARSEDATE_H */ diff --git a/include/mbgl/util/ptr.hpp b/include/mbgl/util/ptr.hpp new file mode 100644 index 0000000000..6e02f956f3 --- /dev/null +++ b/include/mbgl/util/ptr.hpp @@ -0,0 +1,29 @@ +#ifndef MBGL_UTIL_PTR +#define MBGL_UTIL_PTR + +#include <memory> +#include <cassert> + +namespace mbgl { +namespace util { + +template <typename T> +class ptr : public ::std::shared_ptr<T> { +public: + template <typename... Args> + inline ptr(Args &&... args) + : ::std::shared_ptr<T>(::std::forward<Args>(args)...) {} + + inline auto operator->() const -> decltype(this->::std::shared_ptr<T>::operator->()) { + assert(*this); + return ::std::shared_ptr<T>::operator->(); + } + inline auto operator*() const -> decltype(this->::std::shared_ptr<T>::operator*()) { + assert(*this); + return ::std::shared_ptr<T>::operator*(); + } +}; +} +} + +#endif
\ No newline at end of file diff --git a/include/mbgl/util/queue.h b/include/mbgl/util/queue.h new file mode 100644 index 0000000000..fe02b454ea --- /dev/null +++ b/include/mbgl/util/queue.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUEUE_H_ +#define QUEUE_H_ + +typedef void *QUEUE[2]; + +/* Private macros. */ +#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0])) +#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1])) +#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q))) +#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q))) + +/* Public macros. */ +#define QUEUE_DATA(ptr, type, field) \ + ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field))) + +#define QUEUE_FOREACH(q, h) \ + for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q)) + +#define QUEUE_EMPTY(q) \ + ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q)) + +#define QUEUE_HEAD(q) \ + (QUEUE_NEXT(q)) + +#define QUEUE_INIT(q) \ + do { \ + QUEUE_NEXT(q) = (q); \ + QUEUE_PREV(q) = (q); \ + } \ + while (0) + +#define QUEUE_ADD(h, n) \ + do { \ + QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \ + QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV(h) = QUEUE_PREV(n); \ + QUEUE_PREV_NEXT(h) = (h); \ + } \ + while (0) + +#define QUEUE_SPLIT(h, q, n) \ + do { \ + QUEUE_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(n) = (n); \ + QUEUE_NEXT(n) = (q); \ + QUEUE_PREV(h) = QUEUE_PREV(q); \ + QUEUE_PREV_NEXT(h) = (h); \ + QUEUE_PREV(q) = (n); \ + } \ + while (0) + +#define QUEUE_INSERT_HEAD(h, q) \ + do { \ + QUEUE_NEXT(q) = QUEUE_NEXT(h); \ + QUEUE_PREV(q) = (h); \ + QUEUE_NEXT_PREV(q) = (q); \ + QUEUE_NEXT(h) = (q); \ + } \ + while (0) + +#define QUEUE_INSERT_TAIL(h, q) \ + do { \ + QUEUE_NEXT(q) = (h); \ + QUEUE_PREV(q) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(q) = (q); \ + QUEUE_PREV(h) = (q); \ + } \ + while (0) + +#define QUEUE_REMOVE(q) \ + do { \ + QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \ + QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \ + } \ + while (0) + +#endif /* QUEUE_H_ */ diff --git a/include/mbgl/util/raster.hpp b/include/mbgl/util/raster.hpp index e526ffa5ac..7051c30091 100644 --- a/include/mbgl/util/raster.hpp +++ b/include/mbgl/util/raster.hpp @@ -4,11 +4,11 @@ #include <mbgl/util/transition.hpp> #include <mbgl/util/texturepool.hpp> #include <mbgl/util/image.hpp> +#include <mbgl/util/ptr.hpp> #include <mbgl/renderer/prerendered_texture.hpp> #include <string> #include <mutex> -#include <memory> typedef struct uv_loop_s uv_loop_t; @@ -17,7 +17,7 @@ namespace mbgl { class Raster : public std::enable_shared_from_this<Raster> { public: - Raster(const std::shared_ptr<Texturepool> &texturepool); + Raster(const util::ptr<Texturepool> &texturepool); ~Raster(); // load image data @@ -57,7 +57,7 @@ private: bool loaded = false; // shared texture pool - std::shared_ptr<Texturepool> texturepool; + util::ptr<Texturepool> texturepool; // min/mag filter uint32_t filter = 0; @@ -66,7 +66,7 @@ private: std::unique_ptr<util::Image> img; // fade in transition - std::shared_ptr<util::transition> fade_transition = nullptr; + util::ptr<util::transition> fade_transition = nullptr; }; } diff --git a/include/mbgl/util/sqlite3.hpp b/include/mbgl/util/sqlite3.hpp new file mode 100644 index 0000000000..3e324f7ce1 --- /dev/null +++ b/include/mbgl/util/sqlite3.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include <string> +#include <stdexcept> + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_stmt sqlite3_stmt; + +namespace mapbox { +namespace sqlite { + +enum OpenFlag : int { + ReadOnly = 0x00000001, + ReadWrite = 0x00000002, + Create = 0x00000004, + NoMutex = 0x00008000, + FullMutex = 0x00010000, + SharedCache = 0x00020000, + PrivateCache = 0x00040000, +}; + +struct Exception : std::runtime_error { + inline Exception(int err, const char *msg) : std::runtime_error(msg), code(err) {} + const int code = 0; +}; + +class Statement; + +class Database { +private: + Database(const Database &) = delete; + Database &operator=(const Database &) = delete; + +public: + Database(const std::string &filename, int flags = 0); + Database(Database &&); + ~Database(); + Database &operator=(Database &&); + + operator bool() const; + + void exec(const std::string &sql); + Statement prepare(const char *query); + +private: + sqlite3 *db = nullptr; +}; + +class Statement { +private: + Statement(const Statement &) = delete; + Statement &operator=(const Statement &) = delete; + +public: + Statement(sqlite3 *db, const char *sql); + Statement(Statement &&); + ~Statement(); + Statement &operator=(Statement &&); + + operator bool() const; + + template <typename T> void bind(int offset, T value); + void bind(int offset, const std::string &value, bool retain = true); + template <typename T> T get(int offset); + + bool run(); + void reset(); + +private: + sqlite3_stmt *stmt = nullptr; +}; + +} +} diff --git a/include/mbgl/util/uv-channel.h b/include/mbgl/util/uv-channel.h new file mode 100644 index 0000000000..ea5c279f65 --- /dev/null +++ b/include/mbgl/util/uv-channel.h @@ -0,0 +1,29 @@ +#ifndef MBGL_UTIL_UV_CHANNEL +#define MBGL_UTIL_UV_CHANNEL + +#ifdef __cplusplus +extern "C" { +#endif + +#include <uv.h> + +// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/ + +typedef struct uv_chan_s uv_chan_t; + +struct uv_chan_s { + uv_mutex_t mutex; + uv_cond_t cond; + void *q[2]; +}; + +int uv_chan_init(uv_chan_t *chan); +void uv_chan_send(uv_chan_t *chan, void *data); +void *uv_chan_receive(uv_chan_t *chan); +void uv_chan_destroy(uv_chan_t *chan); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/mbgl/util/uv-messenger.h b/include/mbgl/util/uv-messenger.h new file mode 100644 index 0000000000..82b8ef2d9c --- /dev/null +++ b/include/mbgl/util/uv-messenger.h @@ -0,0 +1,32 @@ +#ifndef MBGL_UTIL_UV_MESSENGER +#define MBGL_UTIL_UV_MESSENGER + +#ifdef __cplusplus +extern "C" { +#endif + +#include <uv.h> + +typedef struct uv_messenger_s uv_messenger_t; +typedef void (*uv_messenger_cb)(void *arg); +typedef void (*uv_messenger_stop_cb)(uv_messenger_t *msgr); + +struct uv_messenger_s { + uv_mutex_t mutex; + uv_async_t async; + uv_messenger_cb callback; + void *data; + void *queue[2]; +}; + +int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback); +void uv_messenger_send(uv_messenger_t *msgr, void *arg); +void uv_messenger_stop(uv_messenger_t *msgr); +void uv_messenger_ref(uv_messenger_t *msgr); +void uv_messenger_unref(uv_messenger_t *msgr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/mbgl/util/uv-worker.h b/include/mbgl/util/uv-worker.h new file mode 100644 index 0000000000..451b6fb04d --- /dev/null +++ b/include/mbgl/util/uv-worker.h @@ -0,0 +1,42 @@ +#ifndef MBGL_UTIL_UV_WORKER +#define MBGL_UTIL_UV_WORKER + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct uv_messenger_s uv_messenger_t; + +#include <mbgl/util/uv-channel.h> + +#include <stdlib.h> + +typedef struct uv_worker_s uv_worker_t; + +typedef void (*uv_worker_cb)(void *data); +typedef void (*uv_worker_after_cb)(void *data); +typedef void (*uv_worker_close_cb)(uv_worker_t *worker); + +struct uv_worker_s { +#ifndef NDEBUG + unsigned long thread_id; +#endif + uv_loop_t *loop; + uv_messenger_t *msgr; + uv_chan_t chan; + const char *name; + int count; + uv_worker_close_cb close_cb; + unsigned int active_items; +}; + +int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name); +void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb, + uv_worker_after_cb after_work_cb); +void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/mbgl/util/uv.hpp b/include/mbgl/util/uv.hpp index 4ceb8b873f..3c533cfbf8 100644 --- a/include/mbgl/util/uv.hpp +++ b/include/mbgl/util/uv.hpp @@ -4,6 +4,7 @@ #include <string> typedef struct uv_async_s uv_async_t; +typedef struct uv_timer_s uv_timer_t; typedef struct uv_handle_s uv_handle_t; typedef struct uv_loop_s uv_loop_t; @@ -11,9 +12,15 @@ namespace uv { std::string cwd(); +struct deleter { + void operator()(uv_async_t *async); + void operator()(uv_timer_t *timer); +}; + class thread; class rwlock; class loop; +class worker; } diff --git a/include/mbgl/util/uv_detail.hpp b/include/mbgl/util/uv_detail.hpp index b3fdbb3719..a80423f822 100644 --- a/include/mbgl/util/uv_detail.hpp +++ b/include/mbgl/util/uv_detail.hpp @@ -1,21 +1,13 @@ #ifndef MBGL_UTIL_UV_DETAIL #define MBGL_UTIL_UV_DETAIL +#include <mbgl/util/ptr.hpp> +#include <mbgl/util/uv-worker.h> + #include <uv.h> + #include <functional> #include <cassert> - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-parameter" -#endif - -#include <boost/lockfree/queue.hpp> - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - #include <string> @@ -112,6 +104,24 @@ private: uv_once_t o = UV_ONCE_INIT; }; +class worker { +public: + inline worker(uv_loop_t *loop, unsigned int count, const char *name = nullptr) : w(new uv_worker_t) { + uv_worker_init(w, loop, count, name); + } + inline ~worker() { + uv_worker_close(w, [](uv_worker_t *worker) { + delete worker; + }); + } + inline void add(void *data, uv_worker_cb work_cb, uv_worker_after_cb after_work_cb) { + uv_worker_send(w, data, work_cb, after_work_cb); + } + +private: + uv_worker_t *w; +}; + template <typename T> class work { public: @@ -119,30 +129,26 @@ public: typedef void (*after_work_callback)(T &object); template<typename... Args> - work(const std::shared_ptr<loop> &loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args) - : loop(loop), - data(std::forward<Args>(args)...), + work(worker &worker, work_callback work_cb, after_work_callback after_work_cb, Args&&... args) + : data(std::forward<Args>(args)...), work_cb(work_cb), after_work_cb(after_work_cb) { - req.data = this; - uv_queue_work(**loop, &req, do_work, after_work); + worker.add(this, do_work, after_work); } private: - static void do_work(uv_work_t *req) { - work<T> *w = static_cast<work<T> *>(req->data); + static void do_work(void *data) { + work<T> *w = reinterpret_cast<work<T> *>(data); w->work_cb(w->data); } - static void after_work(uv_work_t *req, int) { - work<T> *w = static_cast<work<T> *>(req->data); + static void after_work(void *data) { + work<T> *w = reinterpret_cast<work<T> *>(data); w->after_work_cb(w->data); delete w; } private: - std::shared_ptr<uv::loop> loop; - uv_work_t req; T data; work_callback work_cb; after_work_callback after_work_cb; diff --git a/linux/mapboxgl-app.gyp b/linux/mapboxgl-app.gyp index e19768caf9..dac1934d31 100644 --- a/linux/mapboxgl-app.gyp +++ b/linux/mapboxgl-app.gyp @@ -15,7 +15,8 @@ '../common/platform_default.cpp', '../common/glfw_view.hpp', '../common/glfw_view.cpp', - '../common/curl_request.cpp', + '../common/http_request_baton_curl.cpp', + '../common/linux.cpp', '../common/stderr_log.hpp', '../common/stderr_log.cpp', ], @@ -32,7 +33,6 @@ ], 'OTHER_LDFLAGS': [ '<@(glfw3_libraries)', - '<@(curl_libraries)', ], } }, @@ -46,7 +46,6 @@ 'link_settings': { 'libraries': [ '<@(glfw3_libraries)', - '<@(curl_libraries)', '-lboost_regex' ], }, diff --git a/macosx/main.mm b/macosx/main.mm index 9ecc307c05..aeb49225cb 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -1,9 +1,12 @@ #include "../common/settings_nsuserdefaults.hpp" #include "../common/glfw_view.hpp" #include "../common/nslog_log.hpp" +#include "../common/Reachability.h" #import <Foundation/Foundation.h> +#include <uv.h> + @interface URLHandler : NSObject @property (nonatomic) mbgl::Map *map; @@ -68,16 +71,25 @@ @end int main() { + fprintf(stderr, "main thread: 0x%lx\n", uv_thread_self()); mbgl::Log::Set<mbgl::NSLogBackend>(); GLFWView view; mbgl::Map map(view); + mbgl::Map *map_ptr = ↦ URLHandler *handler = [[URLHandler alloc] init]; [handler setMap:&map]; NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:handler andSelector:@selector(handleGetURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; + // Notify map object when network reachability status changes. + Reachability* reachability = [Reachability reachabilityForInternetConnection]; + reachability.reachableBlock = ^(Reachability *reachability) { + map_ptr->setReachability(true); + }; + [reachability startNotifier]; + // Load settings mbgl::Settings_NSUserDefaults settings; map.setLonLatZoom(settings.longitude, settings.latitude, settings.zoom); @@ -97,6 +109,8 @@ int main() { int ret = view.run(); + [reachability stopNotifier]; + // Save settings map.getLonLatZoom(settings.longitude, settings.latitude, settings.zoom); settings.bearing = map.getBearing(); diff --git a/macosx/mapboxgl-app.gyp b/macosx/mapboxgl-app.gyp index da5c68ea35..51f591fdd0 100644 --- a/macosx/mapboxgl-app.gyp +++ b/macosx/mapboxgl-app.gyp @@ -15,8 +15,10 @@ '../common/platform_nsstring.mm', '../common/glfw_view.hpp', '../common/glfw_view.cpp', - '../common/foundation_request.h', - '../common/foundation_request.mm', + '../common/reachability.h', + '../common/reachability.m', + '../common/http_request_baton_cocoa.mm', + '../common/osx.mm', '../common/nslog_log.hpp', '../common/nslog_log.mm', ], @@ -33,6 +35,7 @@ ], 'OTHER_LDFLAGS': [ '<@(glfw3_libraries)', + '-framework SystemConfiguration', ], 'SDKROOT': 'macosx', 'INFOPLIST_FILE': 'Info.plist', diff --git a/mapboxgl.gyp b/mapboxgl.gyp index dcd89f3a76..68434bf2ba 100644 --- a/mapboxgl.gyp +++ b/mapboxgl.gyp @@ -31,7 +31,7 @@ '<(SHARED_INTERMEDIATE_DIR)/src/shader/shaders_gl.cpp', '<(SHARED_INTERMEDIATE_DIR)/src/shader/shaders_gles2.cpp' ], - 'include_dirs':[ + 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)/include/', ] } @@ -126,53 +126,70 @@ '<!@(find src -name "*.glsl")', 'bin/style.json' ], - 'xcode_settings': { - 'SDKROOT': 'macosx', - 'SUPPORTED_PLATFORMS':['macosx'], - 'MACOSX_DEPLOYMENT_TARGET':'10.9', - 'PUBLIC_HEADERS_FOLDER_PATH': 'include', - 'OTHER_CPLUSPLUSFLAGS':[ - '<@(png_cflags)', - '<@(uv_cflags)', - '-I<(boost_root)/include', - ] - }, - 'include_dirs':[ - './include' - ], - 'cflags': [ - '<@(png_cflags)', - '-I<(boost_root)/include', + 'include_dirs': [ + './include' ], - 'direct_dependent_settings': { - 'include_dirs':[ - './include', - ], - 'cflags': [ + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'SDKROOT': 'macosx', + 'SUPPORTED_PLATFORMS': ['macosx'], + 'MACOSX_DEPLOYMENT_TARGET': '10.9', + 'PUBLIC_HEADERS_FOLDER_PATH': 'include', + 'OTHER_CPLUSPLUSFLAGS': [ '<@(png_cflags)', '<@(uv_cflags)', + '<@(curl_cflags)', + '<@(sqlite3_cflags)', + '-I<(boost_root)/include', + ], + 'OTHER_CFLAGS': [ + '<@(uv_cflags)', + ], + }, + }, { + 'cflags': [ + '<@(png_cflags)', + '<@(uv_cflags)', + '<@(curl_cflags)', + '<@(sqlite3_cflags)', + '-I<(boost_root)/include', ], - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS':[ - '<@(png_cflags)', + }] + ], + 'direct_dependent_settings': { + 'include_dirs': [ + './include', + ], + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ '<@(uv_cflags)', - ] - }, - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'OTHER_LDFLAGS': [ - '<@(png_libraries)', - '<@(uv_libraries)', - ] - } - }, { - 'libraries': [ + '<@(curl_cflags)', + '-I<(boost_root)/include', + ], + 'OTHER_LDFLAGS': [ '<@(png_libraries)', '<@(uv_libraries)', + '<@(curl_libraries)', + '<@(sqlite3_libraries)', ] - }] - ] + } + }, { + 'cflags': [ + '<@(uv_cflags)', + '<@(curl_cflags)', + '-I<(boost_root)/include', + ], + 'libraries': [ + '<@(png_libraries)', + '<@(uv_libraries)', + '<@(curl_libraries)', + '<@(sqlite3_libraries)', + ] + }] + ] } }, { @@ -181,7 +198,7 @@ 'type': 'static_library', 'hard_dependency': 1, 'dependencies': [ - 'shaders', + 'shaders', ], 'sources': [ '<!@(find src/ \( -name "*.cpp" ! -name shaders.hpp ! -name shaders_gles2.cpp ! -name shaders_gl.cpp \))', @@ -195,56 +212,42 @@ ], 'xcode_settings': { 'SDKROOT': 'iphoneos', - 'SUPPORTED_PLATFORMS':['iphonesimulator','iphoneos'], + 'SUPPORTED_PLATFORMS': ['iphonesimulator','iphoneos'], 'ARCHS': [ "armv7", "armv7s", "arm64", "i386", "x86_64" ], 'TARGETED_DEVICE_FAMILY': '1,2', 'CODE_SIGN_IDENTITY': 'iPhone Developer', 'IPHONEOS_DEPLOYMENT_TARGET': '7.0', 'PUBLIC_HEADERS_FOLDER_PATH': 'include', - 'GCC_INPUT_FILETYPE':'sourcecode.cpp.cpp', - 'OTHER_CPLUSPLUSFLAGS':[ + 'GCC_INPUT_FILETYPE': 'sourcecode.cpp.cpp', + 'OTHER_CPLUSPLUSFLAGS': [ '<@(png_cflags)', '<@(uv_cflags)', + '<@(curl_cflags)', + '<@(sqlite3_cflags)', '-I<(boost_root)/include', - ] + ], }, - 'include_dirs':[ - './include' - ], - 'cflags': [ - '<@(png_cflags)', - '<@(uv_cflags)', - '-I<(boost_root)/include', + 'include_dirs': [ + './include' ], 'direct_dependent_settings': { - 'include_dirs':[ - './include' + 'include_dirs': [ + './include', + ], + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '<@(png_libraries)', + '<@(uv_libraries)', + '<@(curl_libraries)', + '<@(sqlite3_libraries)', ], - 'cflags': [ - '<@(png_cflags)', - '<@(uv_cflags)', + 'OTHER_CPLUSPLUSFLAGS': [ + '<@(uv_cflags)', ], - 'xcode_settings': { - 'OTHER_CPLUSPLUSFLAGS':[ - '<@(png_cflags)', - '<@(uv_cflags)', - ] - }, - 'conditions': [ - ['OS == "mac"', { - 'xcode_settings': { - 'OTHER_LDFLAGS': [ - '<@(png_libraries)', - '<@(uv_libraries)', - ] - } - }, { - 'libraries': [ - '<@(png_libraries)', - '<@(uv_libraries)' - ] - }] - ] + 'OTHER_CFLAGS': [ + '<@(uv_cflags)', + ], + } } } ] diff --git a/scripts/flags.sh b/scripts/flags.sh index 0c0981cdc1..58aee18a04 100755 --- a/scripts/flags.sh +++ b/scripts/flags.sh @@ -16,12 +16,12 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then # some limited coverage if [[ ${BUILDTYPE} == "Debug" ]]; then if [[ ${CXX} == "g++" ]]; then - export CXXFLAGS="-fsanitize=address ${CXXFLAGS}" - export CFLAGS="-fsanitize=address ${CFLAGS}" + export CXXFLAGS="-fsanitize=address -g ${CXXFLAGS}" + export CFLAGS="-fsanitize=address -g ${CFLAGS}" export LDFLAGS="-fsanitize=address ${LDFLAGS}" elif [[ ${CXX} == "clang++" ]]; then - export CXXFLAGS="-fsanitize=thread -fPIC ${CXXFLAGS}" - export CFLAGS="-fsanitize=thread ${CFLAGS}" + export CXXFLAGS="-fsanitize=thread -g -fPIC ${CXXFLAGS}" + export CFLAGS="-fsanitize=thread -g ${CFLAGS}" export LDFLAGS="-fsanitize=thread -pie ${LDFLAGS}" fi fi diff --git a/setup-libraries.sh b/setup-libraries.sh index ce0a230bcd..dc1b1643fa 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -53,6 +53,7 @@ NPM=$(which npm) MP_HASH="e82e6c00ea5a0cda147e4c121a3c7751cae69ff8" DIR_HASH=$(echo `pwd` | git hash-object --stdin) + if [ ! -d 'mapnik-packaging/' ]; then git clone https://github.com/mapnik/mapnik-packaging.git fi @@ -66,7 +67,7 @@ export CXX11=true if [ ${UNAME} = 'Darwin' ]; then -if [ ! -z "${TRAVIS:-}" ]; then +if [[ $TRAVIS ]]; then if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then rm -rf out/build-cpp11-libcpp-x86_64-macosx @@ -83,30 +84,40 @@ source iPhoneOS.sh export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi echo ' ...done' source iPhoneOSs.sh export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi echo ' ...done' source iPhoneOS64.sh export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi echo ' ...done' source iPhoneSimulator.sh export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi echo ' ...done' source iPhoneSimulator64.sh export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi echo ' ...done' source MacOSX.sh @@ -114,6 +125,7 @@ export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libglfw3.a ] ; then ./scripts/build_glfw.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi if [ ! -d out/build-cpp11-libcpp-x86_64-macosx/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../include/ `pwd`/../../linux/ `pwd`/../../common/ ; fi @@ -121,8 +133,10 @@ export LIBUV_VERSION=0.10.28 # setup iOS universal libs ./scripts/make_universal.sh +patch -p0 --forward < patches/curl-ios.diff || true +echo "NOTE: One patch FAILURE is expected. The other should have been applied or ignored." -if [ ! -z "${TRAVIS:-}" ] && [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then +if [[ $TRAVIS && $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY ]] ; then tar -zcf out/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libcpp-universal aws s3 cp --acl public-read out/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz s3://${AWS_S3_BUCKET}/dependencies/ tar -zcf out/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libcpp-x86_64-macosx @@ -138,7 +152,7 @@ cd ../../ elif [ ${UNAME} = 'Linux' ]; then -if [ ! -z "${TRAVIS:-}" ]; then +if [[ $TRAVIS ]]; then if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then rm -rf out/build-cpp11-libstdcpp-gcc-x86_64-linux tar -xzf out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz @@ -150,11 +164,12 @@ export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libglfw3.a ] ; then ./scripts/build_glfw.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libsqlite3.a ] ; then ./scripts/build_sqlite.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libboost_regex.a ] ; then ./scripts/build_boost.sh --with-regex ; fi -if [ ! -z "${TRAVIS:-}" ] && [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then +if [[ $TRAVIS && $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY ]] ; then if ! tar --compare -zf out/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz ; then tar -zcf out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libstdcpp-gcc-x86_64-linux aws s3 cp --acl public-read out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz s3://${AWS_S3_BUCKET}/dependencies/ diff --git a/src/map/map.cpp b/src/map/map.cpp index ad8749ff08..9034c6d5ac 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -19,11 +19,10 @@ #include <mbgl/style/style_bucket.hpp> #include <mbgl/util/texturepool.hpp> #include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/platform/log.hpp> #include <algorithm> -#include <memory> #include <iostream> #define _USE_MATH_DEFINES @@ -34,12 +33,15 @@ using namespace mbgl; Map::Map(View& view) : loop(std::make_shared<uv::loop>()), thread(std::make_unique<uv::thread>()), + async_terminate(new uv_async_t()), + async_render(new uv_async_t()), + async_cleanup(new uv_async_t()), view(view), +#ifndef NDEBUG + main_thread(uv_thread_self()), +#endif transform(view), - fileSource(std::make_shared<FileSource>()), - style(std::make_shared<Style>()), glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)), - glyphStore(std::make_shared<GlyphStore>(fileSource)), spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)), texturepool(std::make_shared<Texturepool>()), painter(*this) { @@ -53,49 +55,93 @@ Map::Map(View& view) } Map::~Map() { - // Clear the style first before the rest of the constructor deletes members of this object. - // This is required because members of the style reference the Map object in their destructors. - style.reset(); - if (async) { stop(); } + + // Explicitly reset all pointers. + texturepool.reset(); + sprite.reset(); + spriteAtlas.reset(); + glyphStore.reset(); + glyphAtlas.reset(); + style.reset(); + fileSource.reset(); + workers.reset(); + + uv_run(**loop, UV_RUN_DEFAULT); +} + +uv::worker &Map::getWorker() { + if (!workers) { + workers = std::make_unique<uv::worker>(**loop, 4, "Tile Worker"); + } + return *workers; } void Map::start() { + assert(uv_thread_self() == main_thread); + assert(!async); + // When starting map rendering in another thread, we perform async/continuously // updated rendering. Only in these cases, we attach the async handlers. async = true; + // Reset the flag. + is_stopped = false; + // Setup async notifications - async_terminate = new uv_async_t(); - uv_async_init(**loop, async_terminate, terminate); - async_terminate->data = **loop; + uv_async_init(**loop, async_terminate.get(), terminate); + async_terminate->data = this; - async_render = new uv_async_t(); - uv_async_init(**loop, async_render, render); + uv_async_init(**loop, async_render.get(), render); async_render->data = this; - async_cleanup = new uv_async_t(); - uv_async_init(**loop, async_cleanup, cleanup); + uv_async_init(**loop, async_cleanup.get(), cleanup); async_cleanup->data = this; uv_thread_create(*thread, [](void *arg) { Map *map = static_cast<Map *>(arg); +#ifndef NDEBUG + map->map_thread = uv_thread_self(); +#endif +#ifdef __APPLE__ + pthread_setname_np("Map"); +#endif map->run(); +#ifndef NDEBUG + map->map_thread = -1; +#endif + + // Make sure that the stop() function knows when to stop invoking the callback function. + map->is_stopped = true; + map->view.notify(); }, this); } -void Map::stop() { - if (async_terminate != nullptr) { - uv_async_send(async_terminate); +void Map::stop(stop_callback cb, void *data) { + assert(uv_thread_self() == main_thread); + assert(main_thread != map_thread); + assert(async); + + uv_async_send(async_terminate.get()); + + if (cb) { + // Wait until the render thread stopped. We are using this construct instead of plainly + // relying on the thread_join because the system might need to run things in the current + // thread that is required for the render thread to terminate correctly. This is for example + // the case with Cocoa's NSURLRequest. Otherwise, we will eventually deadlock because this + // thread (== main thread) is blocked. The callback function should use an efficient waiting + // function to avoid a busy waiting loop. + while (!is_stopped) { + cb(data); + } } + // If a callback function was provided, this should return immediately because the thread has + // already finished executing. uv_thread_join(*thread); - // Run the event loop once to make sure our async delete handlers are called. - uv_run(**loop, UV_RUN_ONCE); - async = false; } @@ -104,22 +150,35 @@ void Map::delete_async(uv_handle_t *handle, int status) { } void Map::run() { +#ifndef NDEBUG + if (!async) { + map_thread = main_thread; + } +#endif + assert(uv_thread_self() == map_thread); + setup(); prepare(); uv_run(**loop, UV_RUN_DEFAULT); + // Run the event loop once more to make sure our async delete handlers are called. + uv_run(**loop, UV_RUN_ONCE); + // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. if (!async) { render(); +#ifndef NDEBUG + map_thread = -1; +#endif } } void Map::rerender() { // We only send render events if we want to continuously update the map // (== async rendering). - if (async && async_render != nullptr) { - uv_async_send(async_render); + if (async) { + uv_async_send(async_render.get()); } } @@ -139,7 +198,7 @@ void Map::swapped() { void Map::cleanup() { if (async_cleanup != nullptr) { - uv_async_send(async_cleanup); + uv_async_send(async_cleanup.get()); } } @@ -153,9 +212,20 @@ void Map::terminate() { painter.terminate(); } +void Map::setReachability(bool reachable) { + // Note: This function may be called from *any* thread. + if (reachable) { + if (fileSource) { + fileSource->prepare([&]() { + fileSource->retryAllPending(); + }); + } + } +} + void Map::render(uv_async_t *async, int status) { Map *map = static_cast<Map *>(async->data); - + assert(uv_thread_self() == map->map_thread); if (map->state.hasSize()) { if (map->is_rendered.test_and_set() == false) { @@ -175,44 +245,48 @@ void Map::render(uv_async_t *async, int status) { void Map::terminate(uv_async_t *async, int status) { // Closes all open handles on the loop. This means that the loop will automatically terminate. - uv_loop_t *loop = static_cast<uv_loop_t *>(async->data); - uv_walk(loop, [](uv_handle_t *handle, void */*arg*/) { - if (!uv_is_closing(handle)) { - uv_close(handle, NULL); - } - }, NULL); + Map *map = static_cast<Map *>(async->data); + assert(uv_thread_self() == map->map_thread); + + // Remove all of these to make sure they are destructed in the correct thread. + map->glyphStore.reset(); + map->fileSource.reset(); + map->style.reset(); + map->workers.reset(); + map->activeSources.clear(); + + uv_close((uv_handle_t *)map->async_cleanup.get(), nullptr); + uv_close((uv_handle_t *)map->async_render.get(), nullptr); + uv_close((uv_handle_t *)map->async_terminate.get(), nullptr); } #pragma mark - Setup void Map::setup() { + assert(uv_thread_self() == map_thread); view.make_active(); painter.setup(); view.make_inactive(); } void Map::setStyleURL(const std::string &url) { - fileSource->load(ResourceType::JSON, url, [&](platform::Response *res) { - if (res->code == 200) { - // Calculate the base - const size_t pos = url.rfind('/'); - std::string base = ""; - if (pos != std::string::npos) { - base = url.substr(0, pos + 1); - } - - this->setStyleJSON(res->body, base); - } else { - Log::Error(Event::Setup, "loading style failed: %d (%s)", res->code, res->error_message.c_str()); - } - }, loop); + // TODO: Make threadsafe. + styleURL = url; } void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) { + // TODO: Make threadsafe. styleJSON.swap(newStyleJSON); sprite.reset(); + if (!style) { + style = std::make_shared<Style>(); + } style->loadJSON((const uint8_t *)styleJSON.c_str()); + if (!fileSource) { + fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase()); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } fileSource->setBase(base); glyphStore->setURL(util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken())); update(); @@ -223,6 +297,7 @@ std::string Map::getStyleJSON() const { } void Map::setAccessToken(std::string access_token) { + // TODO: Make threadsafe. accessToken.swap(access_token); } @@ -230,7 +305,7 @@ std::string Map::getAccessToken() const { return accessToken; } -std::shared_ptr<Sprite> Map::getSprite() { +util::ptr<Sprite> Map::getSprite() { const float pixelRatio = state.getPixelRatio(); const std::string &sprite_url = style->getSpriteURL(); if (!sprite || sprite->pixelRatio != pixelRatio) { @@ -460,8 +535,10 @@ void Map::setDefaultTransitionDuration(uint64_t duration_milliseconds) { } void Map::updateSources() { + assert(uv_thread_self() == map_thread); + // First, disable all existing sources. - for (const std::shared_ptr<StyleSource> &source : activeSources) { + for (const util::ptr<StyleSource> &source : activeSources) { source->enabled = false; } @@ -469,7 +546,7 @@ void Map::updateSources() { updateSources(style->layers); // Then, construct or destroy the actual source object, depending on enabled state. - for (const std::shared_ptr<StyleSource> &style_source : activeSources) { + for (const util::ptr<StyleSource> &style_source : activeSources) { if (style_source->enabled) { if (!style_source->source) { style_source->source = std::make_shared<Source>(style_source->info); @@ -481,20 +558,20 @@ void Map::updateSources() { } // Finally, remove all sources that are disabled. - util::erase_if(activeSources, [](std::shared_ptr<StyleSource> source){ + util::erase_if(activeSources, [](util::ptr<StyleSource> source){ return !source->enabled; }); } -const std::set<std::shared_ptr<StyleSource>> Map::getActiveSources() const { +const std::set<util::ptr<StyleSource>> Map::getActiveSources() const { return activeSources; } -void Map::updateSources(const std::shared_ptr<StyleLayerGroup> &group) { +void Map::updateSources(const util::ptr<StyleLayerGroup> &group) { if (!group) { return; } - for (const std::shared_ptr<StyleLayer> &layer : group->layers) { + for (const util::ptr<StyleLayer> &layer : group->layers) { if (!layer) continue; if (layer->bucket) { if (layer->bucket->style_source) { @@ -507,7 +584,7 @@ void Map::updateSources(const std::shared_ptr<StyleLayerGroup> &group) { } void Map::updateTiles() { - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { source->source->update(*this); } } @@ -515,13 +592,37 @@ void Map::updateTiles() { void Map::updateRenderState() { // Update all clipping IDs. ClipIDGenerator generator; - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { generator.update(source->source->getLoadedTiles()); source->source->updateMatrices(painter.projMatrix, state); } } void Map::prepare() { + if (!fileSource) { + fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase()); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } + + if (!style) { + style = std::make_shared<Style>(); + + fileSource->request(ResourceType::JSON, styleURL)->onload([&](const Response &res) { + if (res.code == 200) { + // Calculate the base + const size_t pos = styleURL.rfind('/'); + std::string base = ""; + if (pos != std::string::npos) { + base = styleURL.substr(0, pos + 1); + } + + setStyleJSON(res.data, base); + } else { + Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str()); + } + }); + } + // Update transform transitions. animationTime = util::now(); if (transform.needsTransition()) { @@ -568,7 +669,7 @@ void Map::render() { // This guarantees that we have at least one function per tile called. // When only rendering layers via the stylesheet, it's possible that we don't // ever visit a tile during rendering. - for (const std::shared_ptr<StyleSource> &source : getActiveSources()) { + for (const util::ptr<StyleSource> &source : getActiveSources()) { source->source->finishRender(painter); } @@ -582,7 +683,7 @@ void Map::render() { view.make_inactive(); } -void Map::renderLayers(std::shared_ptr<StyleLayerGroup> group) { +void Map::renderLayers(util::ptr<StyleLayerGroup> group) { if (!group) { // Make sure that we actually do have a layer group. return; @@ -625,7 +726,7 @@ void Map::renderLayers(std::shared_ptr<StyleLayerGroup> group) { } } -void Map::renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id, const mat4* matrix) { +void Map::renderLayer(util::ptr<StyleLayer> layer_desc, RenderPass pass, const Tile::ID* id, const mat4* matrix) { if (layer_desc->type == StyleLayerType::Background) { // This layer defines a background color/image. diff --git a/src/map/raster_tile_data.cpp b/src/map/raster_tile_data.cpp index 182a614393..e7725b7abd 100644 --- a/src/map/raster_tile_data.cpp +++ b/src/map/raster_tile_data.cpp @@ -5,7 +5,7 @@ using namespace mbgl; -RasterTileData::RasterTileData(Tile::ID id, Map &map, const SourceInfo &source) +RasterTileData::RasterTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : TileData(id, map, source), bucket(map.getTexturepool(), properties) { } @@ -25,10 +25,10 @@ void RasterTileData::parse() { } } -void RasterTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void RasterTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { bucket.render(painter, layer_desc, id, matrix); } -bool RasterTileData::hasData(std::shared_ptr<StyleLayer> /*layer_desc*/) const { +bool RasterTileData::hasData(util::ptr<StyleLayer> /*layer_desc*/) const { return bucket.hasData(); } diff --git a/src/map/source.cpp b/src/map/source.cpp index 083e931b7a..36f1a71c84 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -6,7 +6,7 @@ #include <mbgl/util/raster.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/texturepool.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/vec.hpp> #include <mbgl/util/math.hpp> #include <mbgl/util/std.hpp> @@ -20,7 +20,7 @@ namespace mbgl { -Source::Source(SourceInfo& info) +Source::Source(const util::ptr<SourceInfo>& info) : info(info) { } @@ -29,32 +29,33 @@ Source::Source(SourceInfo& info) // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. void Source::load(Map& map) { - if (info.url.empty()) { + if (info->url.empty()) { loaded = true; return; } - std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken()); - std::shared_ptr<Source> source = shared_from_this(); + std::string url = util::mapbox::normalizeSourceURL(info->url, map.getAccessToken()); + util::ptr<Source> source = shared_from_this(); - map.getFileSource()->load(ResourceType::JSON, url, [source, &map](platform::Response *res) { - if (res->code != 200) { + map.getFileSource()->request(ResourceType::JSON, url)->onload([source, &map](const Response &res) { + if (res.code != 200) { Log::Warning(Event::General, "failed to load source TileJSON"); return; } rapidjson::Document d; - d.Parse<0>(res->body.c_str()); + d.Parse<0>(res.data.c_str()); if (d.HasParseError()) { Log::Warning(Event::General, "invalid source TileJSON"); return; } - source->info.parseTileJSONProperties(d); + source->info->parseTileJSONProperties(d); source->loaded = true; map.update(); + }); } @@ -98,7 +99,7 @@ void Source::drawClippingMasks(Painter &painter) { } } -void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc) { +void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc) { gl::group group(std::string("layer: ") + layer_desc->id); for (const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) { Tile &tile = *pair.second; @@ -108,7 +109,7 @@ void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc) { } } -void Source::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { auto it = tiles.find(id); if (it != tiles.end() && it->second->data && it->second->data->state == TileData::State::parsed) { painter.renderTileLayer(*it->second, layer_desc, matrix); @@ -183,9 +184,9 @@ TileData::State Source::addTile(Map &map, const Tile::ID& id) { if (!new_tile.data) { // If we don't find working tile data, we're just going to load it. - if (info.type == SourceType::Vector) { + if (info->type == SourceType::Vector) { new_tile.data = std::make_shared<VectorTileData>(normalized_id, map, info); - } else if (info.type == SourceType::Raster) { + } else if (info->type == SourceType::Raster) { new_tile.data = std::make_shared<RasterTileData>(normalized_id, map, info); } else { throw std::runtime_error("source type not implemented"); @@ -199,7 +200,7 @@ TileData::State Source::addTile(Map &map, const Tile::ID& id) { } double Source::getZoom(const TransformState& state) const { - double offset = std::log(util::tileSize / info.tile_size) / std::log(2); + double offset = std::log(util::tileSize / info->tile_size) / std::log(2); offset += (state.getPixelRatio() > 1.0 ? 1 :0); return state.getZoom() + offset; } @@ -211,8 +212,8 @@ int32_t Source::coveringZoomLevel(const TransformState& state) const { std::forward_list<Tile::ID> Source::coveringTiles(const TransformState& state) const { int32_t z = coveringZoomLevel(state); - if (z < info.min_zoom) return {{}}; - if (z > info.max_zoom) z = info.max_zoom; + if (z < info->min_zoom) return {{}}; + if (z > info->max_zoom) z = info->max_zoom; // Map four viewport corners to pixel coordinates box points = state.cornersToBox(z); @@ -285,8 +286,8 @@ bool Source::updateTiles(Map &map) { std::forward_list<Tile::ID> required = coveringTiles(map.getState()); // Determine the overzooming/underzooming amounts. - int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info.min_zoom, info.max_zoom); - int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info.min_zoom, info.max_zoom); + int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info->min_zoom, info->max_zoom); + int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info->min_zoom, info->max_zoom); // Retain is a list of tiles that we shouldn't delete, even if they are not // the most ideal tile for the current viewport. This may include tiles like @@ -333,7 +334,7 @@ bool Source::updateTiles(Map &map) { // Remove all the expired pointers from the set. util::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) { - const std::shared_ptr<TileData> tile = pair.second.lock(); + const util::ptr<TileData> tile = pair.second.lock(); if (!tile) { return true; } diff --git a/src/map/sprite.cpp b/src/map/sprite.cpp index af9413a0e3..c069ece45a 100644 --- a/src/map/sprite.cpp +++ b/src/map/sprite.cpp @@ -5,7 +5,7 @@ #include <string> #include <mbgl/platform/platform.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/std.hpp> @@ -22,8 +22,8 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t sdf(sdf) { } -std::shared_ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, const std::shared_ptr<FileSource> &fileSource) { - std::shared_ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); +util::ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, const util::ptr<FileSource> &fileSource) { + util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); sprite->load(fileSource); return sprite; } @@ -51,7 +51,7 @@ Sprite::operator bool() 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 Sprite::load(const std::shared_ptr<FileSource> &fileSource) { +void Sprite::load(const util::ptr<FileSource> &fileSource) { if (!valid) { // Treat a non-existent sprite as a successfully loaded empty sprite. loadedImage = true; @@ -60,30 +60,30 @@ void Sprite::load(const std::shared_ptr<FileSource> &fileSource) { return; } - std::shared_ptr<Sprite> sprite = shared_from_this(); + util::ptr<Sprite> sprite = shared_from_this(); - fileSource->load(ResourceType::JSON, jsonURL, [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->body.swap(res->body); + fileSource->request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) { + if (res.code == 200) { + sprite->body = res.data; sprite->parseJSON(); sprite->complete(); } else { - Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res->code, res->error_message.c_str()); + Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str()); if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message))); + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } } }); - fileSource->load(ResourceType::Image, spriteURL, [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->image.swap(res->body); + fileSource->request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) { + if (res.code == 200) { + sprite->image = res.data; sprite->parseImage(); sprite->complete(); } else { - Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res->code, res->error_message.c_str()); + Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str()); if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res->error_message))); + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } } }); diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 57afb8dadb..44e3826bf5 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -4,12 +4,12 @@ #include <mbgl/util/token.hpp> #include <mbgl/util/string.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/util/uv_detail.hpp> using namespace mbgl; -TileData::TileData(Tile::ID id, Map &map, const SourceInfo &source) +TileData::TileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : id(id), state(State::initial), map(map), @@ -29,10 +29,10 @@ const std::string TileData::toString() const { } void TileData::request() { - if (source.tiles.empty()) + if (source->tiles.empty()) return; - std::string url = source.tiles[(id.x + id.y) % source.tiles.size()]; + std::string url = source->tiles[(id.x + id.y) % source->tiles.size()]; url = util::replaceTokens(url, [&](const std::string &token) -> std::string { if (token == "z") return std::to_string(id.z); if (token == "x") return std::to_string(id.x); @@ -45,21 +45,28 @@ void TileData::request() { // Note: Somehow this feels slower than the change to request_http() std::weak_ptr<TileData> weak_tile = shared_from_this(); - map.getFileSource()->load(ResourceType::Tile, url, [weak_tile, url](platform::Response *res) { - std::shared_ptr<TileData> tile = weak_tile.lock(); + req = map.getFileSource()->request(ResourceType::Tile, url); + req->onload([weak_tile, url](const Response &res) { + util::ptr<TileData> tile = weak_tile.lock(); if (!tile || tile->state == State::obsolete) { // noop. Tile is obsolete and we're now just waiting for the refcount // to drop to zero for destruction. - } else if (res->code == 200) { + return; + } + + // Clear the request object. + tile->req.reset(); + + if (res.code == 200) { tile->state = State::loaded; - tile->data.swap(res->body); + tile->data = res.data; // Schedule tile parsing in another thread tile->reparse(); } else { #if defined(DEBUG) - fprintf(stderr, "[%s] tile loading failed: %d, %s\n", url.c_str(), res->code, res->error_message.c_str()); + fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str()); #endif } }); @@ -68,7 +75,7 @@ void TileData::request() { void TileData::cancel() { if (state != State::obsolete) { state = State::obsolete; - platform::cancel_request_http(req.lock()); + req.reset(); } } @@ -79,12 +86,12 @@ void TileData::reparse() { // We're creating a new work request. The work request deletes itself after it executed // the after work handler - new uv::work<std::shared_ptr<TileData>>( - map.getLoop(), - [](std::shared_ptr<TileData> &tile) { + new uv::work<util::ptr<TileData>>( + map.getWorker(), + [](util::ptr<TileData> &tile) { tile->parse(); }, - [](std::shared_ptr<TileData> &tile) { + [](util::ptr<TileData> &tile) { tile->afterParse(); tile->map.update(); }, diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 6923c1a422..3dc5cb9cef 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -38,19 +38,26 @@ namespace mbgl { TileParser::~TileParser() = default; TileParser::TileParser(const std::string &data, VectorTileData &tile, - const std::shared_ptr<const Style> &style, - const std::shared_ptr<GlyphAtlas> &glyphAtlas, - const std::shared_ptr<GlyphStore> &glyphStore, - const std::shared_ptr<SpriteAtlas> &spriteAtlas, - const std::shared_ptr<Sprite> &sprite) + const util::ptr<const Style> &style_, + const util::ptr<GlyphAtlas> &glyphAtlas_, + const util::ptr<GlyphStore> &glyphStore_, + const util::ptr<SpriteAtlas> &spriteAtlas_, + const util::ptr<Sprite> &sprite_) : vector_data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), - style(style), - glyphAtlas(glyphAtlas), - glyphStore(glyphStore), - spriteAtlas(spriteAtlas), - sprite(sprite), - collision(std::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) { + style(style_), + glyphAtlas(glyphAtlas_), + glyphStore(glyphStore_), + spriteAtlas(spriteAtlas_), + sprite(sprite_), + collision(std::make_unique<Collision>(tile.id.z, 4096, tile.source->tile_size, tile.depth)) { + assert(&tile != nullptr); + assert(style); + assert(glyphAtlas); + assert(glyphStore); + assert(spriteAtlas); + assert(sprite); + assert(collision); } void TileParser::parse() { @@ -59,12 +66,12 @@ void TileParser::parse() { bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; } -void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) { +void TileParser::parseStyleLayers(util::ptr<StyleLayerGroup> group) { if (!group) { return; } - for (const std::shared_ptr<StyleLayer> &layer_desc : group->layers) { + for (const util::ptr<StyleLayer> &layer_desc : group->layers) { // Cancel early when parsing. if (obsolete()) { return; @@ -96,14 +103,14 @@ void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) { } } -std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bucket_desc) { +std::unique_ptr<Bucket> TileParser::createBucket(util::ptr<StyleBucket> bucket_desc) { if (!bucket_desc) { fprintf(stderr, "missing bucket desc\n"); return nullptr; } // Skip this bucket if we are to not render this - if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source.max_zoom) return nullptr; + if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source->max_zoom) return nullptr; if (tile.id.z >= std::ceil(bucket_desc->max_zoom)) return nullptr; auto layer_it = vector_data.layers.find(bucket_desc->source_layer); @@ -157,7 +164,7 @@ std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& laye return obsolete() ? nullptr : std::move(bucket); } -std::unique_ptr<Bucket> TileParser::createRasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster &raster) { +std::unique_ptr<Bucket> TileParser::createRasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster &raster) { std::unique_ptr<RasterBucket> bucket = std::make_unique<RasterBucket>(texturepool, raster); return obsolete() ? nullptr : std::move(bucket); } diff --git a/src/map/transform.cpp b/src/map/transform.cpp index 6c5e70cc2f..baa615b94a 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -466,7 +466,7 @@ bool Transform::needsTransition() const { void Transform::updateTransitions(const timestamp now) { uv::writelock lock(mtx); - transitions.remove_if([now](const std::shared_ptr<util::transition> &transition) { + transitions.remove_if([now](const util::ptr<util::transition> &transition) { return transition->update(now) == util::transition::complete; }); } diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 1f74cab1d7..48b46059a5 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -8,13 +8,13 @@ using namespace mbgl; -VectorTileData::VectorTileData(Tile::ID id, Map &map, const SourceInfo &source) +VectorTileData::VectorTileData(Tile::ID id, Map &map, const util::ptr<SourceInfo> &source) : TileData(id, map, source), - depth(id.z >= source.max_zoom ? map.getMaxZoom() - id.z : 1) { + depth(id.z >= source->max_zoom ? map.getMaxZoom() - id.z : 1) { } VectorTileData::~VectorTileData() { - std::shared_ptr<GlyphAtlas> glyphAtlas = map.getGlyphAtlas(); + util::ptr<GlyphAtlas> glyphAtlas = map.getGlyphAtlas(); if (glyphAtlas) { glyphAtlas->removeGlyphs(id.to_uint64()); } @@ -52,7 +52,7 @@ void VectorTileData::afterParse() { parser.reset(); } -void VectorTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void VectorTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { if (state == State::parsed && layer_desc->bucket) { auto databucket_it = buckets.find(layer_desc->bucket->name); if (databucket_it != buckets.end()) { @@ -62,7 +62,7 @@ void VectorTileData::render(Painter &painter, std::shared_ptr<StyleLayer> layer_ } } -bool VectorTileData::hasData(std::shared_ptr<StyleLayer> layer_desc) const { +bool VectorTileData::hasData(util::ptr<StyleLayer> layer_desc) const { if (state == State::parsed && layer_desc->bucket) { auto databucket_it = buckets.find(layer_desc->bucket->name); if (databucket_it != buckets.end()) { diff --git a/src/platform/request.cpp b/src/platform/request.cpp deleted file mode 100644 index 82e80b9a7c..0000000000 --- a/src/platform/request.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include <mbgl/platform/request.hpp> -#include <mbgl/platform/platform.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/uv_detail.hpp> - -using namespace mbgl::platform; - -Request::Request(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop) - : url(url), - res(std::make_unique<Response>(callback)), - cancelled(false), - loop(loop) { - if (loop) { - // Add a check handle without a callback to keep the default loop running. - // We don't have a real handler attached to the default loop right from the - // beginning, because we're using asynchronous messaging to perform the actual - // request in the request thread. Only after the request is complete, we - // create an actual work request that is attached to the default loop. - async = new uv_async_t(); - async->data = new std::unique_ptr<Response>(); - uv_async_init(**loop, async, &complete); - } -} - -Request::~Request() { -} - -void Request::complete() { - if (loop) { - // We're scheduling the response callback to be invoked in the event loop. - // Since the Request object will be deleted before the callback is invoked, - // we move over the Response object to be owned by the async handle. - ((std::unique_ptr<Response> *)async->data)->swap(res); - uv_async_send(async); - } else { - // We're calling the response callback immediately. We're currently on an - // arbitrary thread, but that's okay. - res->callback(res.get()); - } -} - -void Request::complete(uv_async_t *async, int status) { - Response *res = static_cast<std::unique_ptr<Response> *>(async->data)->get(); - - res->callback(res); - - // We need to remove our async handle again to allow the main event loop to exit. - uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { - delete static_cast<std::unique_ptr<Response> *>(handle->data); - delete (uv_async_t *)handle; - }); -} diff --git a/src/renderer/debug_bucket.cpp b/src/renderer/debug_bucket.cpp index a3ac329f49..699c1c1db9 100644 --- a/src/renderer/debug_bucket.cpp +++ b/src/renderer/debug_bucket.cpp @@ -14,7 +14,7 @@ DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer) : fontBuffer(fontBuffer) { } -void DebugBucket::render(Painter& painter, std::shared_ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) { +void DebugBucket::render(Painter& painter, util::ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) { painter.renderDebugText(*this, matrix); } diff --git a/src/renderer/fill_bucket.cpp b/src/renderer/fill_bucket.cpp index 875cc279b9..5358cae0b2 100644 --- a/src/renderer/fill_bucket.cpp +++ b/src/renderer/fill_bucket.cpp @@ -204,7 +204,7 @@ void FillBucket::tessellate() { lineGroup.vertex_length += total_vertex_count; } -void FillBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void FillBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { painter.renderFill(*this, layer_desc, id, matrix); } diff --git a/src/renderer/line_bucket.cpp b/src/renderer/line_bucket.cpp index 2e89a7c35d..3ef7411be6 100644 --- a/src/renderer/line_bucket.cpp +++ b/src/renderer/line_bucket.cpp @@ -341,7 +341,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { } } -void LineBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void LineBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { painter.renderLine(*this, layer_desc, id, matrix); } diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 0d01fd1662..8988112585 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -197,7 +197,7 @@ void Painter::prepareTile(const Tile& tile) { glStencilFunc(GL_EQUAL, ref, mask); } -void Painter::renderTileLayer(const Tile& tile, std::shared_ptr<StyleLayer> layer_desc, const mat4 &matrix) { +void Painter::renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { assert(tile.data); if (tile.data->hasData(layer_desc) || layer_desc->type == StyleLayerType::Raster) { gl::group group(util::sprintf<32>("render %d/%d/%d\n", tile.id.z, tile.id.y, tile.id.z)); @@ -206,7 +206,7 @@ void Painter::renderTileLayer(const Tile& tile, std::shared_ptr<StyleLayer> laye } } -void Painter::renderBackground(std::shared_ptr<StyleLayer> layer_desc) { +void Painter::renderBackground(util::ptr<StyleLayer> layer_desc) { const BackgroundProperties& properties = layer_desc->getProperties<BackgroundProperties>(); Color color = properties.color; diff --git a/src/renderer/painter_clipping.cpp b/src/renderer/painter_clipping.cpp index 45b14a2f78..dc625ded4e 100644 --- a/src/renderer/painter_clipping.cpp +++ b/src/renderer/painter_clipping.cpp @@ -6,7 +6,7 @@ using namespace mbgl; -void Painter::drawClippingMasks(const std::set<std::shared_ptr<StyleSource>> &sources) { +void Painter::drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources) { gl::group group("clipping masks"); useProgram(plainShader->program); @@ -17,7 +17,7 @@ void Painter::drawClippingMasks(const std::set<std::shared_ptr<StyleSource>> &so coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); - for (const std::shared_ptr<StyleSource> &source : sources) { + for (const util::ptr<StyleSource> &source : sources) { source->source->drawClippingMasks(*this); } diff --git a/src/renderer/painter_fill.cpp b/src/renderer/painter_fill.cpp index 6d56b9077e..6c17ab037a 100644 --- a/src/renderer/painter_fill.cpp +++ b/src/renderer/painter_fill.cpp @@ -10,7 +10,7 @@ using namespace mbgl; -void Painter::renderFill(FillBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { // Abort early. if (!bucket.hasData()) return; diff --git a/src/renderer/painter_line.cpp b/src/renderer/painter_line.cpp index 161abcd6ff..cd973b46d6 100644 --- a/src/renderer/painter_line.cpp +++ b/src/renderer/painter_line.cpp @@ -8,7 +8,7 @@ using namespace mbgl; -void Painter::renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) return; if (!bucket.hasData()) return; @@ -62,7 +62,7 @@ void Painter::renderLine(LineBucket& bucket, std::shared_ptr<StyleLayer> layer_d bucket.drawPoints(*linejoinShader); } - const std::shared_ptr<Sprite> &sprite = map.getSprite(); + const util::ptr<Sprite> &sprite = map.getSprite(); if (properties.image.size() && sprite) { SpriteAtlasPosition imagePos = map.getSpriteAtlas()->getPosition(properties.image, *sprite); diff --git a/src/renderer/painter_raster.cpp b/src/renderer/painter_raster.cpp index 7f7afc3e84..c362042e55 100644 --- a/src/renderer/painter_raster.cpp +++ b/src/renderer/painter_raster.cpp @@ -9,7 +9,7 @@ using namespace mbgl; -void Painter::renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { +void Painter::renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { if (pass != RenderPass::Translucent) return; const RasterProperties &properties = layer_desc->getProperties<RasterProperties>(); @@ -34,7 +34,7 @@ void Painter::renderRaster(RasterBucket& bucket, std::shared_ptr<StyleLayer> lay // call updateTiles to get parsed data for sublayers map.updateTiles(); - for (const std::shared_ptr<StyleLayer> &layer : layer_desc->layers->layers) { + for (const util::ptr<StyleLayer> &layer : layer_desc->layers->layers) { setOpaque(); map.renderLayer(layer, RenderPass::Opaque, &id, &preMatrix); setTranslucent(); diff --git a/src/renderer/painter_symbol.cpp b/src/renderer/painter_symbol.cpp index 4fce66217f..415dead70e 100644 --- a/src/renderer/painter_symbol.cpp +++ b/src/renderer/painter_symbol.cpp @@ -112,7 +112,7 @@ void Painter::renderSDF(SymbolBucket &bucket, } } -void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void Painter::renderSymbol(SymbolBucket &bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) { return; diff --git a/src/renderer/raster_bucket.cpp b/src/renderer/raster_bucket.cpp index 492eea980d..fc5e3dd3c8 100644 --- a/src/renderer/raster_bucket.cpp +++ b/src/renderer/raster_bucket.cpp @@ -3,13 +3,13 @@ using namespace mbgl; -RasterBucket::RasterBucket(const std::shared_ptr<Texturepool> &texturepool, const StyleBucketRaster& properties) +RasterBucket::RasterBucket(const util::ptr<Texturepool> &texturepool, const StyleBucketRaster& properties) : properties(properties), texture(properties), raster(texturepool) { } -void RasterBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { +void RasterBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { painter.renderRaster(*this, layer_desc, id, matrix); } diff --git a/src/renderer/symbol_bucket.cpp b/src/renderer/symbol_bucket.cpp index 374ea2dc26..3a4b017ef3 100644 --- a/src/renderer/symbol_bucket.cpp +++ b/src/renderer/symbol_bucket.cpp @@ -22,7 +22,7 @@ namespace mbgl { SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties, Collision &collision) : properties(properties), collision(collision) {} -void SymbolBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, +void SymbolBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { painter.renderSymbol(*this, layer_desc, id, matrix); } diff --git a/src/storage/base_request.cpp b/src/storage/base_request.cpp new file mode 100644 index 0000000000..e87679ae1b --- /dev/null +++ b/src/storage/base_request.cpp @@ -0,0 +1,86 @@ +#include <mbgl/storage/base_request.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/storage/request.hpp> + +#include <uv.h> + +#include <cassert> + +namespace mbgl { + +template <typename T, typename ...Args> +void invoke(const std::forward_list<std::unique_ptr<Callback>> &list, Args&& ...args) { + for (const std::unique_ptr<Callback> &callback : list) { + assert(callback); + if (callback->is<T>()) { + callback->get<T>()(::std::forward<Args>(args)...); + } + } +} + +BaseRequest::BaseRequest(const std::string &path_) : thread_id(uv_thread_self()), path(path_) { +} + +// A base request can only be "canceled" by destroying the object. In that case, we'll have to +// notify all cancel callbacks. +BaseRequest::~BaseRequest() { + assert(thread_id == uv_thread_self()); + notify(); +} + +void BaseRequest::retryImmediately() { + // no-op. override in child class. +} + +void BaseRequest::notify() { + assert(thread_id == uv_thread_self()); + + // The parameter exists solely so that any calls to ->remove() + // are not going to cause deallocation of this object while this call is in progress. + util::ptr<BaseRequest> retain = self; + + // Swap the lists so that it's safe for callbacks to call ->cancel() + // on the request object, which would modify the list. + const std::forward_list<std::unique_ptr<Callback>> list = std::move(callbacks); + callbacks.clear(); + + if (response) { + invoke<CompletedCallback>(list, *response); + } else { + invoke<AbortedCallback>(list); + } + + self.reset(); +} + +Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &request) { + assert(thread_id == uv_thread_self()); + assert(this == request.get()); + + if (response) { + // We already have a response. Notify right away. + if (callback.is<CompletedCallback>()) { + callback.get<CompletedCallback>()(*response); + } else { + // We already know that this request was successful. The AbortedCallback will be discarded + // here since it would never be called. + } + return nullptr; + } else { + self = request; + callbacks.push_front(std::unique_ptr<Callback>(new Callback(std::move(callback)))); + return callbacks.front().get(); + } +} + +void BaseRequest::remove(Callback *callback) { + assert(thread_id == uv_thread_self()); + callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) { + return cb.get() == callback; + }); + if (callbacks.empty()) { + self.reset(); + } +} + +} diff --git a/src/storage/file_request.cpp b/src/storage/file_request.cpp new file mode 100644 index 0000000000..33398d5ef1 --- /dev/null +++ b/src/storage/file_request.cpp @@ -0,0 +1,37 @@ +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/file_request_baton.hpp> +#include <mbgl/storage/response.hpp> + +#include <uv.h> + +#include <cassert> + +#include <unistd.h> + +namespace mbgl { + +FileRequest::FileRequest(const std::string &path_, uv_loop_t *loop) + : BaseRequest(path_), ptr(new FileRequestBaton(this, path, loop)) { +} + +void FileRequest::cancel() { + assert(thread_id == uv_thread_self()); + + if (ptr) { + ptr->cancel(); + + // When deleting a FileRequest object with a uv_fs_* call is in progress, we are making sure + // that the callback doesn't accidentally reference this object again. + ptr->request = nullptr; + ptr = nullptr; + } + + notify(); +} + +FileRequest::~FileRequest() { + assert(thread_id == uv_thread_self()); + cancel(); +} + +} diff --git a/src/storage/file_request_baton.cpp b/src/storage/file_request_baton.cpp new file mode 100644 index 0000000000..127c78f3e5 --- /dev/null +++ b/src/storage/file_request_baton.cpp @@ -0,0 +1,142 @@ +#include <mbgl/storage/file_request_baton.hpp> +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/response.hpp> + +namespace mbgl { + +FileRequestBaton::FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop) + : thread_id(uv_thread_self()), request(request_) { + req.data = this; + uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened); +} + +FileRequestBaton::~FileRequestBaton() { +} + +void FileRequestBaton::cancel() { + canceled = true; + + // uv_cancel fails frequently when the request has already been started. + // In that case, we have to let it complete and check the canceled bool + // instead. + uv_cancel((uv_req_t *)&req); +} + +void FileRequestBaton::notify_error(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500; + ptr->request->response->message = uv_strerror(int(req->result)); + ptr->request->notify(); + } +} + +void FileRequestBaton::file_opened(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0) { + // Opening failed or was canceled. There isn't much left we can do. + notify_error(req); + cleanup(req); + } else { + const uv_file fd = uv_file(req->result); + + // We're going to reuse this handle, so we need to cleanup first. + uv_fs_req_cleanup(req); + + if (ptr->canceled || !ptr->request) { + // Either the FileRequest object has been destructed, or the + // request was canceled. + uv_fs_close(req->loop, req, fd, file_closed); + } else { + ptr->fd = fd; + uv_fs_fstat(req->loop, req, fd, file_stated); + } + } +} + +void FileRequestBaton::file_stated(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0 || ptr->canceled || !ptr->request) { + // Stating failed or was canceled. We already have an open file handle + // though, which we'll have to close. + notify_error(req); + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); + } else { + if (static_cast<const uv_stat_t *>(req->ptr)->st_size > std::numeric_limits<int>::max()) { + // File is too large for us to open this way because uv_buf's only support unsigned + // ints as maximum size. + if (ptr->request) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = UV_EFBIG; + ptr->request->response->message = uv_strerror(UV_EFBIG); + ptr->request->notify(); + } + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); + } else { + const unsigned int size = + (unsigned int)(static_cast<const uv_stat_t *>(req->ptr)->st_size); + ptr->body.resize(size); + ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size); + uv_fs_req_cleanup(req); + uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read); + } + } +} + +void FileRequestBaton::file_read(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0 || ptr->canceled || !ptr->request) { + // Stating failed or was canceled. We already have an open file handle + // though, which we'll have to close. + notify_error(req); + } else { + // File was successfully read. + if (ptr->request) { + ptr->request->response = std::unique_ptr<Response>(new Response); + ptr->request->response->code = 200; + ptr->request->response->data = std::move(ptr->body); + ptr->request->notify(); + } + } + + uv_fs_req_cleanup(req); + uv_fs_close(req->loop, req, ptr->fd, file_closed); +} + +void FileRequestBaton::file_closed(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (req->result < 0) { + // Closing the file failed. But there isn't anything we can do. + } + + cleanup(req); +} + +void FileRequestBaton::cleanup(uv_fs_t *req) { + FileRequestBaton *ptr = (FileRequestBaton *)req->data; + assert(ptr->thread_id == uv_thread_self()); + + if (ptr->request) { + ptr->request->ptr = nullptr; + } + + uv_fs_req_cleanup(req); + delete ptr; +} + +} diff --git a/src/storage/file_source.cpp b/src/storage/file_source.cpp new file mode 100644 index 0000000000..12d924416c --- /dev/null +++ b/src/storage/file_source.cpp @@ -0,0 +1,105 @@ +#include <mbgl/storage/file_source.hpp> +#include <mbgl/storage/file_request.hpp> +#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/util/uv-messenger.h> + +#include <uv.h> + +namespace mbgl { + +FileSource::FileSource(uv_loop_t *loop_, const std::string &path) + : thread_id(uv_thread_self()), + store(!path.empty() ? util::ptr<SQLiteStore>(new SQLiteStore(loop_, path)) : nullptr), + loop(loop_), + queue(new uv_messenger_t) { + + uv_messenger_init(loop, queue, [](void *ptr) { + std::unique_ptr<std::function<void()>> fn { reinterpret_cast<std::function<void()> *>(ptr) }; + (*fn)(); + }); + uv_unref((uv_handle_t *)&queue->async); +} + +FileSource::~FileSource() { + assert(thread_id == uv_thread_self()); + uv_messenger_stop(queue); + // NOTE: We don't need to delete the messenger since it will be deleted by the + // uv_messenger_stop() function. + + util::ptr<BaseRequest> request; + + // Send a cancel() message to all requests that we are still holding. + for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) { + if ((request = pair.second.lock())) { + request->cancel(); + } + } +} + +void FileSource::setBase(const std::string &value) { + assert(thread_id == uv_thread_self()); + base = value; +} + +const std::string &FileSource::getBase() const { + assert(thread_id == uv_thread_self()); + return base; +} + +std::unique_ptr<Request> FileSource::request(ResourceType type, const std::string &url) { + assert(thread_id == uv_thread_self()); + + // Make URL absolute. + const std::string absoluteURL = [&]() -> std::string { + const size_t separator = url.find("://"); + if (separator == std::string::npos) { + // Relative URL. + return base + url; + } else { + return url; + } + }(); + + util::ptr<BaseRequest> request; + + // First, try to find an existing Request object. + auto it = pending.find(absoluteURL); + if (it != pending.end()) { + request = it->second.lock(); + } + + if (!request) { + if (absoluteURL.substr(0, 7) == "file://") { + request = std::make_shared<FileRequest>(absoluteURL.substr(7), loop); + } else { + request = std::make_shared<HTTPRequest>(type, absoluteURL, loop, store); + } + + pending.emplace(absoluteURL, request); + } + + return std::unique_ptr<Request>(new Request(request)); +} + +void FileSource::prepare(std::function<void()> fn) { + if (thread_id == uv_thread_self()) { + fn(); + } else { + uv_messenger_send(queue, new std::function<void()>(std::move(fn))); + } +} + +void FileSource::retryAllPending() { + assert(thread_id == uv_thread_self()); + + util::ptr<BaseRequest> request; + for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) { + if ((request = pair.second.lock())) { + request->retryImmediately(); + } + } + +} + +}
\ No newline at end of file diff --git a/src/storage/http_request.cpp b/src/storage/http_request.cpp new file mode 100644 index 0000000000..1b799d4895 --- /dev/null +++ b/src/storage/http_request.cpp @@ -0,0 +1,270 @@ +#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/storage/http_request_baton.hpp> + +#include <uv.h> + +#include <cassert> +#include <chrono> + +namespace mbgl { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" + +struct CacheRequestBaton { + HTTPRequest *request = nullptr; + std::string path; + util::ptr<SQLiteStore> store; +}; + +HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path, uv_loop_t *loop_, util::ptr<SQLiteStore> store_) + : BaseRequest(path), thread_id(uv_thread_self()), loop(loop_), store(store_), type(type_) { + if (store) { + startCacheRequest(); + } else { + startHTTPRequest(nullptr); + } +} + +void HTTPRequest::startCacheRequest() { + assert(uv_thread_self() == thread_id); + + cache_baton = new CacheRequestBaton; + cache_baton->request = this; + cache_baton->path = path; + cache_baton->store = store; + store->get(path, [](std::unique_ptr<Response> &&response, void *ptr) { + // Wrap in a unique_ptr, so it'll always get auto-destructed. + std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr); + if (baton->request) { + baton->request->cache_baton = nullptr; + baton->request->handleCacheResponse(std::move(response)); + } + }, cache_baton); +} + +void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + + if (res) { + // This entry was stored in the cache. Now determine if we need to revalidate. + const int64_t now = std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()).count(); + if (res->expires > now) { + response = std::move(res); + notify(); + // Note: after calling notify(), the request object may cease to exist. + // This HTTPRequest is completed. + return; + } else { + // TODO: notify with preliminary results. + } + } + + startHTTPRequest(std::move(res)); +} + +void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + assert(!http_baton); + + http_baton = std::make_shared<HTTPRequestBaton>(path); + http_baton->request = this; + http_baton->async = new uv_async_t; + http_baton->response = std::move(res); + http_baton->async->data = new util::ptr<HTTPRequestBaton>(http_baton); + + uv_async_init(loop, http_baton->async, [](uv_async_t *async) { + util::ptr<HTTPRequestBaton> &http_baton = *(util::ptr<HTTPRequestBaton> *)async->data; + + if (http_baton->request) { + HTTPRequest *request = http_baton->request; + request->http_baton.reset(); + http_baton->request = nullptr; + request->handleHTTPResponse(http_baton->type, std::move(http_baton->response)); + } + + delete (util::ptr<HTTPRequestBaton> *)async->data; + uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { + uv_async_t *async = (uv_async_t *)handle; + delete async; + }); + }); + attempts++; + HTTPRequestBaton::start(http_baton); +} + + + +void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) { + assert(uv_thread_self() == thread_id); + assert(!http_baton); + assert(!response); + + switch (responseType) { + // This error was caused by a temporary error and it is likely that it will be resolved + // immediately. We are going to try again right away. This is like the TemporaryError, + // except that we will not perform exponential back-off. + case HTTPResponseType::SingularError: + if (attempts >= 4) { + // Report as error after 4 attempts. + response = std::move(res); + notify(); + } else if (attempts >= 2) { + // Switch to the back-off algorithm after the second failure. + retryHTTPRequest(std::move(res), (1 << attempts) * 1000); + return; + } else { + startHTTPRequest(std::move(res)); + } + break; + + // This error might be resolved by waiting some time (e.g. server issues). + // We are going to do an exponential back-off and will try again in a few seconds. + case HTTPResponseType::TemporaryError: + if (attempts >= 4) { + // Report error back after it failed completely. + response = std::move(res); + notify(); + } else { + retryHTTPRequest(std::move(res), (1 << attempts) * 1000); + } + break; + + // This error might be resolved once the network reachability status changes. + // We are going to watch the network status for changes and will retry as soon as the + // operating system notifies us of a network status change. + case HTTPResponseType::ConnectionError: + + if (attempts >= 4) { + // Report error back after it failed completely. + response = std::move(res); + notify(); + } else { + // By default, we will retry every 60 seconds. + retryHTTPRequest(std::move(res), 60000); + } + break; + + // The request was canceled programatically. + case HTTPResponseType::Canceled: + response.reset(); + notify(); + break; + + // This error probably won't be resolved by retrying anytime soon. We are giving up. + case HTTPResponseType::PermanentError: + response = std::move(res); + notify(); + break; + + // The request returned data successfully. We retrieved and decoded the data successfully. + case HTTPResponseType::Successful: + if (store) { + store->put(path, type, *res); + } + response = std::move(res); + notify(); + break; + + // The request confirmed that the data wasn't changed. We already have the data. + case HTTPResponseType::NotModified: + if (store) { + store->updateExpiration(path, res->expires); + } + response = std::move(res); + notify(); + break; + + default: + assert(!"Response wasn't set"); + break; + } +} + +using RetryBaton = std::pair<HTTPRequest *, std::unique_ptr<Response>>; + +void HTTPRequest::retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout) { + assert(uv_thread_self() == thread_id); + assert(!backoff_timer); + backoff_timer = new uv_timer_t(); + uv_timer_init(loop, backoff_timer); + backoff_timer->data = new RetryBaton(this, std::move(res)); + uv_timer_start(backoff_timer, [](uv_timer_t *timer) { + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) }; + pair->first->startHTTPRequest(std::move(pair->second)); + pair->first->backoff_timer = nullptr; + uv_timer_stop(timer); + uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + }, timeout, 0); +} + +void HTTPRequest::removeHTTPBaton() { + assert(uv_thread_self() == thread_id); + if (http_baton) { + http_baton->request = nullptr; + HTTPRequestBaton::stop(http_baton); + http_baton.reset(); + } +} + +void HTTPRequest::removeCacheBaton() { + assert(uv_thread_self() == thread_id); + if (cache_baton) { + // Make sre that this object doesn't accidentally get accessed when it is destructed before + // the callback returned. They are being run in the same thread, so just setting it to + // null is sufficient. + // Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the + // callback. + cache_baton->request = nullptr; + cache_baton = nullptr; + } +} + +void HTTPRequest::removeBackoffTimer() { + assert(uv_thread_self() == thread_id); + if (backoff_timer) { + delete static_cast<RetryBaton *>(backoff_timer->data); + uv_timer_stop(backoff_timer); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } +} + +void HTTPRequest::retryImmediately() { + assert(uv_thread_self() == thread_id); + if (!cache_baton && !http_baton) { + if (backoff_timer) { + // Retry immediately. + uv_timer_stop(backoff_timer); + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoff_timer->data) }; + assert(pair->first == this); + startHTTPRequest(std::move(pair->second)); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } else { + assert(!"We should always have a backoff_timer when there are no batons"); + } + } +} + +void HTTPRequest::cancel() { + assert(uv_thread_self() == thread_id); + removeCacheBaton(); + removeHTTPBaton(); + removeBackoffTimer(); + notify(); +} + + +HTTPRequest::~HTTPRequest() { + assert(uv_thread_self() == thread_id); + cancel(); +} + +#pragma clang diagnostic pop + +} diff --git a/src/storage/http_request_baton.cpp b/src/storage/http_request_baton.cpp new file mode 100644 index 0000000000..02edae748f --- /dev/null +++ b/src/storage/http_request_baton.cpp @@ -0,0 +1,12 @@ +#include <mbgl/storage/http_request_baton.hpp> +#include <uv.h> + +namespace mbgl { + +HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : thread_id(uv_thread_self()), path(path_) { +} + +HTTPRequestBaton::~HTTPRequestBaton() { +} + +} diff --git a/src/storage/request.cpp b/src/storage/request.cpp new file mode 100644 index 0000000000..42bf87a849 --- /dev/null +++ b/src/storage/request.cpp @@ -0,0 +1,49 @@ +#include <mbgl/storage/request.hpp> +#include <mbgl/storage/base_request.hpp> + +#include <uv.h> + +#include <cassert> + +namespace mbgl { + +Request::Request(const util::ptr<BaseRequest> &base_) + : thread_id(uv_thread_self()), base(base_) { +} + +Request::~Request() { + assert(thread_id == uv_thread_self()); +} + +void Request::onload(CompletedCallback cb) { + assert(thread_id == uv_thread_self()); + if (base) { + Callback *callback = base->add(std::move(cb), base); + if (callback) { + callbacks.push_front(callback); + } + } +} + +void Request::oncancel(AbortedCallback cb) { + assert(thread_id == uv_thread_self()); + if (base) { + Callback *callback = base->add(std::move(cb), base); + if (callback) { + callbacks.push_front(callback); + } + } +} + +void Request::cancel() { + assert(thread_id == uv_thread_self()); + if (base) { + for (Callback *callback : callbacks) { + base->remove(callback); + } + base.reset(); + } + callbacks.clear(); +} + +} diff --git a/src/storage/response.cpp b/src/storage/response.cpp new file mode 100644 index 0000000000..cdaf33e4e4 --- /dev/null +++ b/src/storage/response.cpp @@ -0,0 +1,22 @@ +#include <mbgl/storage/response.hpp> + +#include <chrono> + +namespace mbgl { + +int64_t Response::parseCacheControl(const char *value) { + if (value) { + uint64_t seconds = 0; + // TODO: cache-control may contain other information as well: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + if (std::sscanf(value, "max-age=%llu", &seconds) == 1) { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()).count() + + seconds; + } + } + + return -1; +} + +} diff --git a/src/storage/sqlite_store.cpp b/src/storage/sqlite_store.cpp new file mode 100644 index 0000000000..763100f411 --- /dev/null +++ b/src/storage/sqlite_store.cpp @@ -0,0 +1,227 @@ +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/util/compression.hpp> +#include <mbgl/util/sqlite3.hpp> + +#include <mbgl/util/uv-worker.h> + +#include <cassert> + +using namespace mapbox::sqlite; + +std::string removeAccessTokenFromURL(const std::string &url) { + const size_t token_start = url.find("access_token="); + // Ensure that token exists, isn't at the front and is preceded by either & or ?. + if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) { + return url; + } + + const size_t token_end = url.find_first_of('&', token_start); + if (token_end == std::string::npos) { + // The token is the last query argument. We slice away the "&access_token=..." part + return url.substr(0, token_start - 1); + } else { + // We slice away the "access_token=...&" part. + return url.substr(0, token_start) + url.substr(token_end + 1); + } +} + +std::string convertMapboxDomainsToProtocol(const std::string &url) { + const size_t protocol_separator = url.find("://"); + if (protocol_separator == std::string::npos) { + return url; + } + + const std::string protocol = url.substr(0, protocol_separator); + if (!(protocol == "http" || protocol == "https")) { + return url; + } + + const size_t domain_begin = protocol_separator + 3; + const size_t path_separator = url.find("/", domain_begin); + if (path_separator == std::string::npos) { + return url; + } + + const std::string domain = url.substr(domain_begin, path_separator - domain_begin); + if (domain.find(".tiles.mapbox.com") != std::string::npos) { + return "mapbox://" + url.substr(path_separator + 1); + } else { + return url; + } +} + +std::string unifyMapboxURLs(const std::string &url) { + return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url)); +} + +namespace mbgl { + +SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path) + : thread_id(uv_thread_self()), + db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) { + createSchema(); + worker = new uv_worker_t; + uv_worker_init(worker, loop, 1, "SQLite"); +} + +SQLiteStore::~SQLiteStore() { + // Nothing to do. This function needs to be here because we're forward-declaring + // Database, so we need the actual definition here to be able to properly destruct it. + if (worker) { + uv_worker_close(worker, [](uv_worker_t *worker) { + delete worker; + }); + } +} + +void SQLiteStore::createSchema() { + if (!db || !*db) { + return; + } + + db->exec("CREATE TABLE IF NOT EXISTS `http_cache` (" + " `url` TEXT PRIMARY KEY NOT NULL," + " `code` INTEGER NOT NULL," + " `type` INTEGER NOT NULL," + " `modified` INTEGER," + " `etag` TEXT," + " `expires` INTEGER," + " `data` BLOB," + " `compressed` INTEGER NOT NULL DEFAULT 0" + ");" + "CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);"); +} + +struct GetBaton { + util::ptr<Database> db; + std::string path; + ResourceType type; + void *ptr = nullptr; + SQLiteStore::GetCallback callback = nullptr; + std::unique_ptr<Response> response; +}; + +void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr) { + assert(uv_thread_self() == thread_id); + if (!db || !*db) { + if (callback) { + callback(nullptr, ptr); + } + return; + } + + GetBaton *get_baton = new GetBaton; + get_baton->db = db; + get_baton->path = path; + get_baton->ptr = ptr; + get_baton->callback = callback; + + uv_worker_send(worker, get_baton, [](void *data) { + GetBaton *baton = (GetBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + // 0 1 2 + Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, " + // 3 4 5 6 + "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?"); + + stmt.bind(1, url.c_str()); + if (stmt.run()) { + // There is data. + baton->response = std::unique_ptr<Response>(new Response); + + baton->response->code = stmt.get<int>(0); + baton->type = ResourceType(stmt.get<int>(1)); + baton->response->modified = stmt.get<int64_t>(2); + baton->response->etag = stmt.get<std::string>(3); + baton->response->expires = stmt.get<int64_t>(4); + baton->response->data = stmt.get<std::string>(5); + if (stmt.get<int>(6)) { // == compressed + baton->response->data = util::decompress(baton->response->data); + } + } else { + // There is no data. + // This is a noop. + } + }, [](void *data) { + std::unique_ptr<GetBaton> baton { (GetBaton *)data }; + if (baton->callback) { + baton->callback(std::move(baton->response), baton->ptr); + } + }); +} + + +struct PutBaton { + util::ptr<Database> db; + std::string path; + ResourceType type; + Response response; +}; + +void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) { + assert(uv_thread_self() == thread_id); + if (!db) return; + + PutBaton *put_baton = new PutBaton; + put_baton->db = db; + put_baton->path = path; + put_baton->type = type; + put_baton->response = response; + + uv_worker_send(worker, put_baton, [](void *data) { + PutBaton *baton = (PutBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` (" + // 1 2 3 4 5 6 7 8 + "`url`, `code`, `type`, `modified`, `etag`, `expires`, `data`, `compressed`" + ") VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.bind(1, url.c_str()); + stmt.bind(2, int(baton->response.code)); + stmt.bind(3, int(baton->type)); + stmt.bind(4, baton->response.modified); + stmt.bind(5, baton->response.etag.c_str()); + stmt.bind(6, baton->response.expires); + + if (baton->type == ResourceType::Image) { + stmt.bind(7, baton->response.data, false); // do not retain the string internally. + stmt.bind(8, false); + } else { + stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally. + stmt.bind(8, true); + } + + stmt.run(); + }, [](void *data) { + delete (PutBaton *)data; + }); +} + +struct ExpirationBaton { + util::ptr<Database> db; + std::string path; + int64_t expires; +}; + +void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) { + assert(uv_thread_self() == thread_id); + if (!db || !*db) return; + + ExpirationBaton *expiration_baton = new ExpirationBaton; + expiration_baton->db = db; + expiration_baton->path = path; + expiration_baton->expires = expires; + + uv_worker_send(worker, expiration_baton, [](void *data) { + ExpirationBaton *baton = (ExpirationBaton *)data; + const std::string url = unifyMapboxURLs(baton->path); + Statement stmt = // 1 2 + baton->db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?"); + stmt.bind<int64_t>(1, baton->expires); + stmt.bind(2, url.c_str()); + stmt.run(); + }, [](void *data) { + delete (ExpirationBaton *)data; + }); +} + +} diff --git a/src/style/style_layer_group.cpp b/src/style/style_layer_group.cpp index c7e4360d21..a731aebdcb 100644 --- a/src/style/style_layer_group.cpp +++ b/src/style/style_layer_group.cpp @@ -5,7 +5,7 @@ namespace mbgl { void StyleLayerGroup::setClasses(const std::vector<std::string> &class_names, timestamp now, const PropertyTransition &defaultTransition) { - for (const std::shared_ptr<StyleLayer> &layer : layers) { + for (const util::ptr<StyleLayer> &layer : layers) { if (layer) { layer->setClasses(class_names, now, defaultTransition); } @@ -13,7 +13,7 @@ void StyleLayerGroup::setClasses(const std::vector<std::string> &class_names, ti } void StyleLayerGroup::updateProperties(float z, timestamp t) { - for (const std::shared_ptr<StyleLayer> &layer: layers) { + for (const util::ptr<StyleLayer> &layer: layers) { if (layer) { layer->updateProperties(z, t); } @@ -21,7 +21,7 @@ void StyleLayerGroup::updateProperties(float z, timestamp t) { } bool StyleLayerGroup::hasTransitions() const { - for (const std::shared_ptr<const StyleLayer> &layer: layers) { + for (const util::ptr<const StyleLayer> &layer: layers) { if (layer) { if (layer->hasTransitions()) { return true; diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index ad7694d9a9..2a64ab38f8 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -176,12 +176,12 @@ void StyleParser::parseSources(JSVal value) { rapidjson::Value::ConstMemberIterator itr = value.MemberBegin(); for (; itr != value.MemberEnd(); ++itr) { std::string name { itr->name.GetString(), itr->name.GetStringLength() }; - SourceInfo info; + util::ptr<SourceInfo> info = std::make_shared<SourceInfo>(); - parseRenderProperty<SourceTypeClass>(itr->value, info.type, "type"); - parseRenderProperty(itr->value, info.url, "url"); - parseRenderProperty(itr->value, info.tile_size, "tileSize"); - info.parseTileJSONProperties(itr->value); + parseRenderProperty<SourceTypeClass>(itr->value, info->type, "type"); + parseRenderProperty(itr->value, info->url, "url"); + parseRenderProperty(itr->value, info->tile_size, "tileSize"); + info->parseTileJSONProperties(itr->value); sources.emplace(std::move(name), std::make_shared<StyleSource>(info)); } @@ -451,7 +451,7 @@ std::unique_ptr<StyleLayerGroup> StyleParser::createLayers(JSVal value) { if (value.IsArray()) { std::unique_ptr<StyleLayerGroup> group = std::make_unique<StyleLayerGroup>(); for (rapidjson::SizeType i = 0; i < value.Size(); ++i) { - std::shared_ptr<StyleLayer> layer = createLayer(value[i]); + util::ptr<StyleLayer> layer = createLayer(value[i]); if (layer) { group->layers.emplace_back(layer); } @@ -463,7 +463,7 @@ std::unique_ptr<StyleLayerGroup> StyleParser::createLayers(JSVal value) { } } -std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { +util::ptr<StyleLayer> StyleParser::createLayer(JSVal value) { if (value.IsObject()) { if (!value.HasMember("id")) { Log::Warning(Event::ParseStyle, "layer must have an id"); @@ -487,7 +487,7 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { std::map<ClassID, ClassProperties> styles; parseStyles(value, styles); - std::shared_ptr<StyleLayer> layer = std::make_shared<StyleLayer>( + util::ptr<StyleLayer> layer = std::make_shared<StyleLayer>( layer_id, std::move(styles)); if (value.HasMember("layers")) { @@ -495,7 +495,7 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { } // Store the layer ID so we can reference it later. - layers.emplace(layer_id, std::pair<JSVal, std::shared_ptr<StyleLayer>> { value, layer }); + layers.emplace(layer_id, std::pair<JSVal, util::ptr<StyleLayer>> { value, layer }); return layer; } else { @@ -505,14 +505,14 @@ std::shared_ptr<StyleLayer> StyleParser::createLayer(JSVal value) { } void StyleParser::parseLayers() { - for (std::pair<const std::string, std::pair<JSVal, std::shared_ptr<StyleLayer>>> &pair : layers) { + for (std::pair<const std::string, std::pair<JSVal, util::ptr<StyleLayer>>> &pair : layers) { parseLayer(pair.second); } } -void StyleParser::parseLayer(std::pair<JSVal, std::shared_ptr<StyleLayer>> &pair) { +void StyleParser::parseLayer(std::pair<JSVal, util::ptr<StyleLayer>> &pair) { JSVal value = pair.first; - std::shared_ptr<StyleLayer> &layer = pair.second; + util::ptr<StyleLayer> &layer = pair.second; if (value.HasMember("type")) { JSVal type = value["type"]; @@ -642,7 +642,7 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) { parseOptionalProperty<Function<Color>>("background-color", Key::BackgroundColor, klass, value); } -void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseReference(JSVal value, util::ptr<StyleLayer> &layer) { if (!value.IsString()) { Log::Warning(Event::ParseStyle, "layer ref of '%s' must be a string", layer->id.c_str()); return; @@ -661,7 +661,7 @@ void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer stack.pop_front(); - std::shared_ptr<StyleLayer> reference = it->second.second; + util::ptr<StyleLayer> reference = it->second.second; layer->type = reference->type; @@ -676,7 +676,7 @@ void StyleParser::parseReference(JSVal value, std::shared_ptr<StyleLayer> &layer #pragma mark - Parse Bucket -void StyleParser::parseBucket(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseBucket(JSVal value, util::ptr<StyleLayer> &layer) { layer->bucket = std::make_shared<StyleBucket>(layer->type); // We name the buckets according to the layer that defined it. @@ -822,7 +822,7 @@ std::vector<Value> StyleParser::parseValues(JSVal value) { return values; } -void StyleParser::parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer) { +void StyleParser::parseRender(JSVal value, util::ptr<StyleLayer> &layer) { if (!value.IsObject()) { Log::Warning(Event::ParseStyle, "render property of layer '%s' must be an object", layer->id.c_str()); return; diff --git a/src/text/glyph.cpp b/src/text/glyph.cpp index 7dea7246d7..f02c710db2 100644 --- a/src/text/glyph.cpp +++ b/src/text/glyph.cpp @@ -3,9 +3,6 @@ namespace mbgl { // Note: this only works for the BMP -// Note: we could use a binary lookup table to get averaged constant time lookups, however, -// most of our lookups are going to be within the first 3 ranges listed here, so this is -// likely faster. GlyphRange getGlyphRange(char32_t glyph) { unsigned start = (glyph/256) * 256; unsigned end = (start + 255); diff --git a/src/text/glyph_store.cpp b/src/text/glyph_store.cpp index 783710d929..1723bd3d94 100644 --- a/src/text/glyph_store.cpp +++ b/src/text/glyph_store.cpp @@ -8,7 +8,7 @@ #include <mbgl/util/constants.hpp> #include <mbgl/util/token.hpp> #include <mbgl/util/math.hpp> -#include <mbgl/util/filesource.hpp> +#include <mbgl/storage/file_source.hpp> #include <mbgl/platform/platform.hpp> #include <mbgl/util/uv_detail.hpp> #include <algorithm> @@ -137,7 +137,7 @@ 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, GlyphRange glyphRange, const std::shared_ptr<FileSource> &fileSource) +GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, const util::ptr<FileSource> &fileSource) : future(promise.get_future().share()) { // Load the glyph set URL @@ -147,23 +147,26 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, Gl return ""; }); -#if defined(DEBUG) - fprintf(stderr, "%s\n", url.c_str()); -#endif - - fileSource->load(ResourceType::Glyphs, url, [&](platform::Response *res) { - if (res->code != 200) { - // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. - const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res->code, res->error_message.c_str()); - promise.set_exception(std::make_exception_ptr(std::runtime_error(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.swap(res->body); - promise.set_value(*this); - } + // The prepare call jumps back to the main thread. + fileSource->prepare([&, url, fileSource] { + auto request = fileSource->request(ResourceType::Glyphs, url); + request->onload([&, url](const Response &res) { + if (res.code != 200) { + // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. + const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res.code, res.message.c_str()); + promise.set_exception(std::make_exception_ptr(std::runtime_error(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); + } + }); + request->oncancel([&]() { + promise.set_exception(std::make_exception_ptr(std::runtime_error("Loading glyphs was canceled"))); + }); }); } @@ -225,7 +228,7 @@ void GlyphPBF::parse(FontStack &stack) { data.clear(); } -GlyphStore::GlyphStore(const std::shared_ptr<FileSource> &fileSource) : fileSource(fileSource) {} +GlyphStore::GlyphStore(const util::ptr<FileSource> &fileSource) : fileSource(fileSource) {} void GlyphStore::setURL(const std::string &url) { glyphURL = url; diff --git a/src/util/compression.cpp b/src/util/compression.cpp new file mode 100644 index 0000000000..de4e09764c --- /dev/null +++ b/src/util/compression.cpp @@ -0,0 +1,81 @@ +#include <mbgl/util/compression.hpp> + +#include <zlib.h> + +#include <cstring> +#include <stdexcept> + +namespace mbgl { +namespace util { + +std::string compress(const std::string &raw) { + z_stream deflate_stream; + memset(&deflate_stream, 0, sizeof(deflate_stream)); + + // TODO: reuse z_streams + if (deflateInit(&deflate_stream, Z_DEFAULT_COMPRESSION) != Z_OK) { + throw std::runtime_error("failed to initialize deflate"); + } + + deflate_stream.next_in = (Bytef *)raw.data(); + deflate_stream.avail_in = raw.size(); + + std::string result; + char out[16384]; + + int code; + do { + deflate_stream.next_out = reinterpret_cast<Bytef *>(out); + deflate_stream.avail_out = sizeof(out); + code = deflate(&deflate_stream, Z_FINISH); + if (result.size() < deflate_stream.total_out) { + // append the block to the output string + result.append(out, deflate_stream.total_out - result.size()); + } + } while (code == Z_OK); + + deflateEnd(&deflate_stream); + + if (code != Z_STREAM_END) { + throw std::runtime_error(deflate_stream.msg); + } + + return result; +} + +std::string decompress(const std::string &raw) { + z_stream inflate_stream; + memset(&inflate_stream, 0, sizeof(inflate_stream)); + + // TODO: reuse z_streams + if (inflateInit(&inflate_stream) != Z_OK) { + throw std::runtime_error("failed to initialize inflate"); + } + + inflate_stream.next_in = (Bytef *)raw.data(); + inflate_stream.avail_in = raw.size(); + + std::string result; + char out[15384]; + + int code; + do { + inflate_stream.next_out = reinterpret_cast<Bytef *>(out); + inflate_stream.avail_out = sizeof(out); + code = inflate(&inflate_stream, 0); + // result.append(out, sizeof(out) - inflate_stream.avail_out); + if (result.size() < inflate_stream.total_out) { + result.append(out, inflate_stream.total_out - result.size()); + } + } while (code == Z_OK); + + inflateEnd(&inflate_stream); + + if (code != Z_STREAM_END) { + throw std::runtime_error(inflate_stream.msg); + } + + return result; +} +} +} diff --git a/src/util/filesource.cpp b/src/util/filesource.cpp deleted file mode 100644 index 503d4dfb1d..0000000000 --- a/src/util/filesource.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include <mbgl/util/filesource.hpp> -#include <mbgl/platform/platform.hpp> - -#include <fstream> -#include <sstream> - -namespace mbgl { - -FileSource::FileSource() {} - - -void FileSource::setBase(const std::string &value) { - base = value; -} - -const std::string &FileSource::getBase() const { - return base; -} - -void FileSource::load(ResourceType /*type*/, const std::string &url, std::function<void(platform::Response *)> callback, const std::shared_ptr<uv::loop> loop) { - // convert relative URLs to absolute URLs - - const std::string absoluteURL = [&]() -> std::string { - const size_t separator = url.find("://"); - if (separator == std::string::npos) { - // Relative URL. - return base + url; - } else { - return url; - } - }(); - - const size_t separator = absoluteURL.find("://"); - const std::string protocol = separator != std::string::npos ? absoluteURL.substr(0, separator) : ""; - - if (protocol == "file") { - // load from disk - const std::string path = absoluteURL.substr(separator + 3); - std::ifstream file(path); - - platform::Response response(callback); - if (!file.good()) { - response.error_message = "file not found (" + path + ")"; - } else { - std::stringstream data; - data << file.rdbuf(); - response.code = 200; - response.body = data.str(); - } - - callback(&response); - } else { - // load from the internet - platform::request_http(absoluteURL, callback, loop); - } -} - -}
\ No newline at end of file diff --git a/src/util/parsedate.c b/src/util/parsedate.c new file mode 100644 index 0000000000..123c5c4e5f --- /dev/null +++ b/src/util/parsedate.c @@ -0,0 +1,689 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +/* + A brief summary of the date string formats this parser groks: + + RFC 2616 3.3.1 + + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + we support dates without week day name: + + 06 Nov 1994 08:49:37 GMT + 06-Nov-94 08:49:37 GMT + Nov 6 08:49:37 1994 + + without the time zone: + + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 + + weird order: + + 1994 Nov 6 08:49:37 (GNU date fails) + GMT 08:49:37 06-Nov-94 Sunday + 94 6 Nov 08:49:37 (GNU date fails) + + time left out: + + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 + + unusual separators: + + 1994.Nov.6 + Sun/Nov/6/94/GMT + + commonly used time zone names: + + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST + + time zones specified using RFC822 style: + + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 + + compact numerical date strings: + + 20040912 15:05:58 -0700 + 20040911 +0200 + +*/ + +#include <mbgl/util/parsedate.h> + + + +#ifdef __cplusplus +extern "C" { +#endif + +#include <limits.h> +#include <stdbool.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> + + +#define ERRNO (errno) +#define SET_ERRNO(x) (errno = (x)) + + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +char raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +int raw_equal(const char *first, const char *second) +{ + while(*first && *second) { + if(raw_toupper(*first) != raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (raw_toupper(*first) == raw_toupper(*second)); +} + +#define ISSPACE(x) (isspace((int) ((unsigned char)x))) +#define ISDIGIT(x) (isdigit((int) ((unsigned char)x))) +#define ISALNUM(x) (isalnum((int) ((unsigned char)x))) +#define ISALPHA(x) (isalpha((int) ((unsigned char)x))) + + +/* + * Redefine TRUE and FALSE too, to catch current use. With this + * change, 'bool found = 1' will give a warning on MIPSPro, but + * 'bool found = TRUE' will not. Change tested on IRIX/MIPSPro, + * AIX 5.1/Xlc, Tru64 5.1/cc, w/make test too. + */ + +#ifndef TRUE +#define TRUE true +#endif +#ifndef FALSE +#define FALSE false +#endif + + + +/* +** signed long to signed int +*/ + +int clamp_to_int(long slnum) +{ + return slnum > INT_MAX ? INT_MAX : slnum < INT_MIN ? INT_MIN : slnum; +} + + +const char * const wkday[] = +{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; +static const char * const weekday[] = +{ "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday" }; +const char * const month[]= +{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +struct tzinfo { + char name[5]; + int offset; /* +/- in minutes */ +}; + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output); + +#define PARSEDATE_OK 0 +#define PARSEDATE_FAIL -1 +#define PARSEDATE_LATER 1 +#define PARSEDATE_SOONER 2 + +/* Here's a bunch of frequently used time zone names. These were supported + by the old getdate parser. */ +#define tDAYZONE -60 /* offset for daylight savings time */ +static const struct tzinfo tz[]= { + {"GMT", 0}, /* Greenwich Mean */ + {"UTC", 0}, /* Universal (Coordinated) */ + {"WET", 0}, /* Western European */ + {"BST", 0 tDAYZONE}, /* British Summer */ + {"WAT", 60}, /* West Africa */ + {"AST", 240}, /* Atlantic Standard */ + {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ + {"EST", 300}, /* Eastern Standard */ + {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ + {"CST", 360}, /* Central Standard */ + {"CDT", 360 tDAYZONE}, /* Central Daylight */ + {"MST", 420}, /* Mountain Standard */ + {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ + {"PST", 480}, /* Pacific Standard */ + {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ + {"YST", 540}, /* Yukon Standard */ + {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ + {"HST", 600}, /* Hawaii Standard */ + {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ + {"CAT", 600}, /* Central Alaska */ + {"AHST", 600}, /* Alaska-Hawaii Standard */ + {"NT", 660}, /* Nome */ + {"IDLW", 720}, /* International Date Line West */ + {"CET", -60}, /* Central European */ + {"MET", -60}, /* Middle European */ + {"MEWT", -60}, /* Middle European Winter */ + {"MEST", -60 tDAYZONE}, /* Middle European Summer */ + {"CEST", -60 tDAYZONE}, /* Central European Summer */ + {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ + {"FWT", -60}, /* French Winter */ + {"FST", -60 tDAYZONE}, /* French Summer */ + {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ + {"WAST", -420}, /* West Australian Standard */ + {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ + {"CCT", -480}, /* China Coast, USSR Zone 7 */ + {"JST", -540}, /* Japan Standard, USSR Zone 8 */ + {"EAST", -600}, /* Eastern Australian Standard */ + {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ + {"GST", -600}, /* Guam Standard, USSR Zone 9 */ + {"NZT", -720}, /* New Zealand */ + {"NZST", -720}, /* New Zealand Standard */ + {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ + {"IDLE", -720}, /* International Date Line East */ + /* Next up: Military timezone names. RFC822 allowed these, but (as noted in + RFC 1123) had their signs wrong. Here we use the correct signs to match + actual military usage. + */ + {"A", +1 * 60}, /* Alpha */ + {"B", +2 * 60}, /* Bravo */ + {"C", +3 * 60}, /* Charlie */ + {"D", +4 * 60}, /* Delta */ + {"E", +5 * 60}, /* Echo */ + {"F", +6 * 60}, /* Foxtrot */ + {"G", +7 * 60}, /* Golf */ + {"H", +8 * 60}, /* Hotel */ + {"I", +9 * 60}, /* India */ + /* "J", Juliet is not used as a timezone, to indicate the observer's local + time */ + {"K", +10 * 60}, /* Kilo */ + {"L", +11 * 60}, /* Lima */ + {"M", +12 * 60}, /* Mike */ + {"N", -1 * 60}, /* November */ + {"O", -2 * 60}, /* Oscar */ + {"P", -3 * 60}, /* Papa */ + {"Q", -4 * 60}, /* Quebec */ + {"R", -5 * 60}, /* Romeo */ + {"S", -6 * 60}, /* Sierra */ + {"T", -7 * 60}, /* Tango */ + {"U", -8 * 60}, /* Uniform */ + {"V", -9 * 60}, /* Victor */ + {"W", -10 * 60}, /* Whiskey */ + {"X", -11 * 60}, /* X-ray */ + {"Y", -12 * 60}, /* Yankee */ + {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* returns: + -1 no day + 0 monday - 6 sunday +*/ + +static int checkday(const char *check, size_t len) +{ + int i; + const char * const *what; + bool found= FALSE; + if(len > 3) + what = &weekday[0]; + else + what = &wkday[0]; + for(i=0; i<7; i++) { + if(raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; +} + +static int checkmonth(const char *check) +{ + int i; + const char * const *what; + bool found= FALSE; + + what = &month[0]; + for(i=0; i<12; i++) { + if(raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; /* return the offset or -1, no real offset is -1 */ +} + +/* return the time zone offset between GMT and the input one, in number + of seconds or -1 if the timezone wasn't found/legal */ + +static int checktz(const char *check) +{ + unsigned int i; + const struct tzinfo *what; + bool found= FALSE; + + what = tz; + for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) { + if(raw_equal(check, what->name)) { + found=TRUE; + break; + } + what++; + } + return found?what->offset*60:-1; +} + +static void skip(const char **date) +{ + /* skip everything that aren't letters or digits */ + while(**date && !ISALNUM(**date)) + (*date)++; +} + +enum assume { + DATE_MDAY, + DATE_YEAR, + DATE_TIME +}; + +/* this is a clone of 'struct tm' but with all fields we don't need or use + cut out */ +struct my_tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; +}; + +/* struct tm to time since epoch in GMT time zone. + * This is similar to the standard mktime function but for GMT only, and + * doesn't suffer from the various bugs and portability problems that + * some systems' implementations have. + */ +static time_t my_timegm(struct my_tm *tm) +{ + static const int month_days_cumulative [12] = + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + int month, year, leap_days; + + if(tm->tm_year < 70) + /* we don't support years before 1970 as they will cause this function + to return a negative value */ + return -1; + + year = tm->tm_year + 1900; + month = tm->tm_mon; + if(month < 0) { + year += (11 - month) / 12; + month = 11 - (11 - month) % 12; + } + else if(month >= 12) { + year -= month / 12; + month = month % 12; + } + + leap_days = year - (tm->tm_mon <= 1); + leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) + - (1969 / 4) + (1969 / 100) - (1969 / 400)); + + return ((((time_t) (year - 1970) * 365 + + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 + + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; +} + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output) +{ + time_t t = 0; + int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */ + int monnum=-1; /* month of the year number, 0-11 */ + int mdaynum=-1; /* day of month, 1 - 31 */ + int hournum=-1; + int minnum=-1; + int secnum=-1; + int yearnum=-1; + int tzoff=-1; + struct my_tm tm; + enum assume dignext = DATE_MDAY; + const char *indate = date; /* save the original pointer */ + int part = 0; /* max 6 parts */ + + while(*date && (part < 6)) { + bool found=FALSE; + + skip(&date); + + if(ISALPHA(*date)) { + /* a name coming up */ + char buf[32]=""; + size_t len; + if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz]", buf)) + len = strlen(buf); + else + len = 0; + + if(wdaynum == -1) { + wdaynum = checkday(buf, len); + if(wdaynum != -1) + found = TRUE; + } + if(!found && (monnum == -1)) { + monnum = checkmonth(buf); + if(monnum != -1) + found = TRUE; + } + + if(!found && (tzoff == -1)) { + /* this just must be a time zone string */ + tzoff = checktz(buf); + if(tzoff != -1) + found = TRUE; + } + + if(!found) + return PARSEDATE_FAIL; /* bad string */ + + date += len; + } + else if(ISDIGIT(*date)) { + /* a digit */ + int val; + char *end; + if((secnum == -1) && + (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) { + /* time stamp! */ + date += 8; + } + else if((secnum == -1) && + (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) { + /* time stamp without seconds */ + date += 5; + secnum = 0; + } + else { + long lval; + int error; + int old_errno; + + old_errno = ERRNO; + SET_ERRNO(0); + lval = strtol(date, &end, 10); + error = ERRNO; + if(error != old_errno) + SET_ERRNO(old_errno); + + if(error) + return PARSEDATE_FAIL; + +#if LONG_MAX != INT_MAX + if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) + return PARSEDATE_FAIL; +#endif + + val = clamp_to_int(lval); + + if((tzoff == -1) && + ((end - date) == 4) && + (val <= 1400) && + (indate< date) && + ((date[-1] == '+' || date[-1] == '-'))) { + /* four digits and a value less than or equal to 1400 (to take into + account all sorts of funny time zone diffs) and it is preceded + with a plus or minus. This is a time zone indication. 1400 is + picked since +1300 is frequently used and +1400 is mentioned as + an edge number in the document "ISO C 200X Proposal: Timezone + Functions" at http://david.tribble.com/text/c0xtimezone.html If + anyone has a more authoritative source for the exact maximum time + zone offsets, please speak up! */ + found = TRUE; + tzoff = (val/100 * 60 + val%100)*60; + + /* the + and - prefix indicates the local time compared to GMT, + this we need ther reversed math to get what we want */ + tzoff = date[-1]=='+'?-tzoff:tzoff; + } + + if(((end - date) == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { + /* 8 digits, no year, month or day yet. This is YYYYMMDD */ + found = TRUE; + yearnum = val/10000; + monnum = (val%10000)/100-1; /* month is 0 - 11 */ + mdaynum = val%100; + } + + if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { + if((val > 0) && (val<32)) { + mdaynum = val; + found = TRUE; + } + dignext = DATE_YEAR; + } + + if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { + yearnum = val; + found = TRUE; + if(yearnum < 1900) { + if(yearnum > 70) + yearnum += 1900; + else + yearnum += 2000; + } + if(mdaynum == -1) + dignext = DATE_MDAY; + } + + if(!found) + return PARSEDATE_FAIL; + + date = end; + } + } + + part++; + } + + if(-1 == secnum) + secnum = minnum = hournum = 0; /* no time, make it zero */ + + if((-1 == mdaynum) || + (-1 == monnum) || + (-1 == yearnum)) + /* lacks vital info, fail */ + return PARSEDATE_FAIL; + +#if SIZEOF_TIME_T < 5 + /* 32 bit time_t can only hold dates to the beginning of 2038 */ + if(yearnum > 2037) { + *output = 0x7fffffff; + return PARSEDATE_LATER; + } +#endif + + if(yearnum < 1970) { + *output = 0; + return PARSEDATE_SOONER; + } + + if((mdaynum > 31) || (monnum > 11) || + (hournum > 23) || (minnum > 59) || (secnum > 60)) + return PARSEDATE_FAIL; /* clearly an illegal date */ + + tm.tm_sec = secnum; + tm.tm_min = minnum; + tm.tm_hour = hournum; + tm.tm_mday = mdaynum; + tm.tm_mon = monnum; + tm.tm_year = yearnum - 1900; + + /* my_timegm() returns a time_t. time_t is often 32 bits, even on many + architectures that feature 64 bit 'long'. + + Some systems have 64 bit time_t and deal with years beyond 2038. However, + even on some of the systems with 64 bit time_t mktime() returns -1 for + dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) + */ + t = my_timegm(&tm); + + /* time zone adjust (cast t to int to compare to negative one) */ + if(-1 != (int)t) { + + /* Add the time zone diff between local time zone and GMT. */ + long delta = (long)(tzoff!=-1?tzoff:0); + + if((delta>0) && (t > LONG_MAX - delta)) + return -1; /* time_t overflow */ + + t += delta; + } + + *output = t; + + return PARSEDATE_OK; +} + +time_t parse_date(const char *p) +{ + time_t parsed; + int rc = parsedate(p, &parsed); + + switch(rc) { + case PARSEDATE_OK: + case PARSEDATE_LATER: + case PARSEDATE_SOONER: + return parsed; + } + /* everything else is fail */ + return -1; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 9a71573d01..7b52c51037 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -1,20 +1,19 @@ -#include <mbgl/util/raster.hpp> - -#include <memory> -#include <cassert> -#include <cstring> - #include <mbgl/platform/platform.hpp> #include <mbgl/platform/gl.hpp> + +#include <mbgl/util/raster.hpp> #include <mbgl/util/time.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/std.hpp> #include <png.h> +#include <cassert> +#include <cstring> + using namespace mbgl; -Raster::Raster(const std::shared_ptr<Texturepool> &texturepool) +Raster::Raster(const util::ptr<Texturepool> &texturepool) : texturepool(texturepool) {} diff --git a/src/util/sqlite3.cpp b/src/util/sqlite3.cpp new file mode 100644 index 0000000000..19e0ce9c79 --- /dev/null +++ b/src/util/sqlite3.cpp @@ -0,0 +1,166 @@ +#include <mbgl/util/sqlite3.hpp> +#include <sqlite3.h> + +#include <cassert> + +namespace mapbox { +namespace sqlite { + +Database::Database(const std::string &filename, int flags) { + const int err = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr); + if (err != SQLITE_OK) { + Exception ex { err, sqlite3_errmsg(db) }; + db = nullptr; + throw ex; + } +} + +Database::Database(Database &&other) { + *this = std::move(other); +} + +Database &Database::operator=(Database &&other) { + std::swap(db, other.db); + return *this; +} + +Database::~Database() { + if (db) { + const int err = sqlite3_close(db); + if (err != SQLITE_OK) { + throw Exception { err, sqlite3_errmsg(db) }; + } + } +} + +Database::operator bool() const { + return db != nullptr; +} + +void Database::exec(const std::string &sql) { + assert(db); + char *msg = nullptr; + const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg); + if (msg) { + Exception ex { err, msg }; + sqlite3_free(msg); + throw ex; + } else if (err != SQLITE_OK) { + throw Exception { err, sqlite3_errmsg(db) }; + } +} + +Statement Database::prepare(const char *query) { + assert(db); + return std::move(Statement(db, query)); +} + +Statement::Statement(sqlite3 *db, const char *sql) { + const int err = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); + if (err != SQLITE_OK) { + stmt = nullptr; + throw Exception { err, sqlite3_errmsg(db) }; + } +} + +#define CHECK_SQLITE_OK(err) \ + if (err != SQLITE_OK) { \ + throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt)) }; \ + } + +Statement::Statement(Statement &&other) { + *this = std::move(other); +} + +Statement &Statement::operator=(Statement &&other) { + std::swap(stmt, other.stmt); + return *this; +} + +Statement::~Statement() { + if (stmt) { + const int err = sqlite3_finalize(stmt); + CHECK_SQLITE_OK(err) + } +} + +Statement::operator bool() const { + return stmt != nullptr; +} + +#define BIND_3(type, value) \ + assert(stmt); \ + const int err = sqlite3_bind_##type(stmt, offset, value); \ + CHECK_SQLITE_OK(err) + +#define BIND_5(type, value, length, param) \ + assert(stmt); \ + const int err = sqlite3_bind_##type(stmt, offset, value, length, param); \ + CHECK_SQLITE_OK(err) + +template <> void Statement::bind(int offset, int value) { + BIND_3(int, value) +} + +template <> void Statement::bind(int offset, int64_t value) { + BIND_3(int64, value) +} + +template <> void Statement::bind(int offset, double value) { + BIND_3(double, value) +} + +template <> void Statement::bind(int offset, bool value) { + BIND_3(int, value) +} + +template <> void Statement::bind(int offset, const char *value) { + BIND_5(text, value, -1, nullptr) +} + +void Statement::bind(int offset, const std::string &value, bool retain) { + BIND_5(blob, value.data(), int(value.size()), retain ? SQLITE_TRANSIENT : SQLITE_STATIC) +} + +bool Statement::run() { + assert(stmt); + const int err = sqlite3_step(stmt); + if (err == SQLITE_DONE) { + return false; + } else if (err == SQLITE_ROW) { + return true; + } else { + throw std::runtime_error("failed to run statement"); + } +} + +template <> int Statement::get(int offset) { + assert(stmt); + return sqlite3_column_int(stmt, offset); +} + +template <> int64_t Statement::get(int offset) { + assert(stmt); + return sqlite3_column_int64(stmt, offset); +} + +template <> double Statement::get(int offset) { + assert(stmt); + return sqlite3_column_double(stmt, offset); +} + +template <> std::string Statement::get(int offset) { + assert(stmt); + return { + reinterpret_cast<const char *>(sqlite3_column_blob(stmt, offset)), + size_t(sqlite3_column_bytes(stmt, offset)) + }; +} + +void Statement::reset() { + assert(stmt); + sqlite3_reset(stmt); +} + +} +} diff --git a/src/util/uv-channel.c b/src/util/uv-channel.c new file mode 100644 index 0000000000..4e3b9fa5ff --- /dev/null +++ b/src/util/uv-channel.c @@ -0,0 +1,69 @@ +#include <mbgl/util/uv-channel.h> +#include <mbgl/util/queue.h> + +#include <stdlib.h> + +// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/ + +typedef struct { + void *data; + void *active_queue[2]; +} uv__chan_item_t; + +int uv_chan_init(uv_chan_t *chan) { + int r = uv_mutex_init(&chan->mutex); + if (r == -1) + return r; + + QUEUE_INIT(&chan->q); + + return uv_cond_init(&chan->cond); +} + +void uv_chan_send(uv_chan_t *chan, void *data) { + uv__chan_item_t *item = (uv__chan_item_t *)malloc(sizeof(uv__chan_item_t)); + item->data = data; + + uv_mutex_lock(&chan->mutex); + QUEUE_INSERT_TAIL(&chan->q, &item->active_queue); + uv_cond_signal(&chan->cond); + uv_mutex_unlock(&chan->mutex); +} + +void *uv_chan_receive(uv_chan_t *chan) { + uv__chan_item_t *item; + QUEUE *head; + void *data = NULL; + + uv_mutex_lock(&chan->mutex); + while (QUEUE_EMPTY(&chan->q)) { + uv_cond_wait(&chan->cond, &chan->mutex); + } + + head = QUEUE_HEAD(&chan->q); + item = QUEUE_DATA(head, uv__chan_item_t, active_queue); + data = item->data; + QUEUE_REMOVE(head); + free(item); + uv_mutex_unlock(&chan->mutex); + return data; +} + +void uv_chan_clear(uv_chan_t *chan) { + uv_mutex_lock(&chan->mutex); + uv__chan_item_t *item = NULL; + QUEUE *head = NULL; + while (!QUEUE_EMPTY(&chan->q)) { + head = QUEUE_HEAD(&chan->q); + item = QUEUE_DATA(head, uv__chan_item_t, active_queue); + QUEUE_REMOVE(head); + free(item); + } + uv_mutex_unlock(&chan->mutex); +} + +void uv_chan_destroy(uv_chan_t *chan) { + uv_chan_clear(chan); + uv_cond_destroy(&chan->cond); + uv_mutex_destroy(&chan->mutex); +} diff --git a/src/util/uv-messenger.c b/src/util/uv-messenger.c new file mode 100644 index 0000000000..a25c84dc59 --- /dev/null +++ b/src/util/uv-messenger.c @@ -0,0 +1,74 @@ +#include <mbgl/util/uv-messenger.h> +#include <mbgl/util/queue.h> + +#include <stdlib.h> + +typedef struct { + void *data; + void *queue[2]; +} uv__messenger_item_t; + +void uv__messenger_callback(uv_async_t *async) { + uv_messenger_t *msgr = (uv_messenger_t *)async->data; + + uv__messenger_item_t *item; + QUEUE *head; + + while (1) { + uv_mutex_lock(&msgr->mutex); + if (QUEUE_EMPTY(&msgr->queue)) { + uv_mutex_unlock(&msgr->mutex); + break; + } + + head = QUEUE_HEAD(&msgr->queue); + item = QUEUE_DATA(head, uv__messenger_item_t, queue); + QUEUE_REMOVE(head); + uv_mutex_unlock(&msgr->mutex); + + msgr->callback(item->data); + + free(item); + } +} + +int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback) { + int ret = uv_mutex_init(&msgr->mutex); + if (ret < 0) { + return ret; + } + + msgr->callback = callback; + + QUEUE_INIT(&msgr->queue); + + msgr->async.data = msgr; + return uv_async_init(loop, &msgr->async, uv__messenger_callback); +} + +void uv_messenger_send(uv_messenger_t *msgr, void *data) { + uv__messenger_item_t *item = (uv__messenger_item_t *)malloc(sizeof(uv__messenger_item_t)); + item->data = data; + + uv_mutex_lock(&msgr->mutex); + QUEUE_INSERT_TAIL(&msgr->queue, &item->queue); + uv_mutex_unlock(&msgr->mutex); + + uv_async_send(&msgr->async); +} + +void uv_messenger_ref(uv_messenger_t *msgr) { + uv_ref((uv_handle_t *)&msgr->async); +} + +void uv_messenger_unref(uv_messenger_t *msgr) { + uv_unref((uv_handle_t *)&msgr->async); +} + +void uv__messenger_stop_callback(uv_handle_t *handle) { + free((uv_messenger_t *)handle->data); +} + +void uv_messenger_stop(uv_messenger_t *msgr) { + uv_close((uv_handle_t *)&msgr->async, uv__messenger_stop_callback); +} diff --git a/src/util/uv-worker.c b/src/util/uv-worker.c new file mode 100644 index 0000000000..8b0cc6dda7 --- /dev/null +++ b/src/util/uv-worker.c @@ -0,0 +1,166 @@ +#include <mbgl/util/uv-worker.h> +#include <mbgl/util/uv-messenger.h> + +#include <stdio.h> +#include <assert.h> + +typedef struct uv__worker_item_s uv__worker_item_t; +struct uv__worker_item_s { + uv_worker_t *worker; + void *data; + uv_worker_cb work_cb; + uv_worker_after_cb after_work_cb; +}; + +typedef struct uv__worker_thread_s uv__worker_thread_t; +struct uv__worker_thread_s { + uv_worker_t *worker; + uv_thread_t thread; +}; + +void uv__worker_thread_finished(uv__worker_thread_t *worker_thread) { + uv_worker_t *worker = worker_thread->worker; + +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // This should at most block very briefly. We are sending the termination + // notification as the last thing in the worker thread, so by now the thread + // has probably terminated already. If not, the waiting time should be + // extremely short. + uv_thread_join(&worker_thread->thread); + + assert(worker->count > 0); + worker->count--; + if (worker->count == 0) { + uv_chan_destroy(&worker->chan); + uv_messenger_stop(worker->msgr); + if (worker->close_cb) { + worker->close_cb(worker); + } + } +} + +void uv__worker_after(void *ptr) { + uv__worker_item_t *item = (uv__worker_item_t *)ptr; + + if (item->work_cb) { + // We are finishing a regular work request. + if (item->after_work_cb) { + assert(item->after_work_cb); + item->after_work_cb(item->data); + } + uv_worker_t *worker = item->worker; + assert(worker->active_items > 0); + if (--worker->active_items == 0) { + uv_messenger_unref(worker->msgr); + } + } else { + // This is a worker thread termination. + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)item->data; + uv__worker_thread_finished(worker_thread); + free(worker_thread); + } + + free(item); +} + +void uv__worker_thread_loop(void *ptr) { + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)ptr; + uv_worker_t *worker = worker_thread->worker; + +#ifdef __APPLE__ + if (worker->name) { + pthread_setname_np(worker->name); + } +#endif + + uv__worker_item_t *item = NULL; + while ((item = (uv__worker_item_t *)uv_chan_receive(&worker->chan)) != NULL) { + assert(item->work_cb); + item->work_cb(item->data); + + // Trigger the after callback in the main thread. + uv_messenger_send(worker->msgr, item); + } + + // Make sure to close all other workers too. + uv_chan_send(&worker->chan, NULL); + + // Create a new worker item that acts as a terminate flag for this thread. + item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t)); + item->data = worker_thread; + item->work_cb = NULL; + item->after_work_cb = NULL; + uv_messenger_send(worker->msgr, item); +} + +int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name) { +#ifndef NDEBUG + worker->thread_id = uv_thread_self(); +#endif + worker->loop = loop; + worker->name = name; + worker->count = 0; + worker->close_cb = NULL; + worker->active_items = 0; + worker->msgr = (uv_messenger_t *)malloc(sizeof(uv_messenger_t)); + int ret = uv_messenger_init(loop, worker->msgr, uv__worker_after); + if (ret < 0) { + free(worker->msgr); + return ret; + } + uv_messenger_unref(worker->msgr); + ret = uv_chan_init(&worker->chan); + if (ret < 0) return ret; + + // Initialize all worker threads. + int i; + for (i = 0; i < count; i++) { + uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)malloc(sizeof(uv__worker_thread_t)); + worker_thread->worker = worker; + ret = uv_thread_create(&worker_thread->thread, uv__worker_thread_loop, worker_thread); + if (ret < 0) return ret; + worker->count++; + } + + return 0; +} + +void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb, + uv_worker_after_cb after_work_cb) { +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // It doesn't make sense to not provide a work callback. On the other hand, the after_work_cb + // may be NULL. In that case, there will be no callback called in the current thread and the + // worker item will instead be freed in the worker thread. + assert(work_cb); + + uv__worker_item_t *item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t)); + item->worker = worker; + item->work_cb = work_cb; + item->after_work_cb = after_work_cb; + item->data = data; + uv_chan_send(&worker->chan, item); + if (worker->active_items++ == 0) { + uv_messenger_ref(worker->msgr); + } +} + +void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb) { +#ifndef NDEBUG + assert(uv_thread_self() == worker->thread_id); +#endif + + // Prevent double calling. + assert(worker->close_cb == NULL); + + worker->close_cb = close_cb; + uv_chan_send(&worker->chan, NULL); + if (worker->active_items++ == 0) { + uv_messenger_ref(worker->msgr); + } +} diff --git a/src/util/uv.cpp b/src/util/uv.cpp index 94f074bfa1..6e15ac4537 100644 --- a/src/util/uv.cpp +++ b/src/util/uv.cpp @@ -16,4 +16,16 @@ std::string cwd() { return dir; } +void deleter::operator()(uv_async_t *async) { + uv_close((uv_handle_t *)async, [](uv_handle_t *handle) { + delete (uv_async_t *)handle; + }); +} + +void deleter::operator()(uv_timer_t *timer) { + uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { + delete (uv_timer_t *)handle; + }); +} + } diff --git a/test/fixtures/fixture_request.cpp b/test/fixtures/fixture_request.cpp index 417958a33d..b37cd92bee 100644 --- a/test/fixtures/fixture_request.cpp +++ b/test/fixtures/fixture_request.cpp @@ -1,10 +1,16 @@ -#include <mbgl/platform/platform.hpp> -#include <mbgl/platform/request.hpp> -#include <mbgl/util/uv_detail.hpp> +#include <mbgl/storage/http_request_baton.hpp> +#include <mbgl/storage/file_request_baton.hpp> +#include <mbgl/storage/response.hpp> #include <mbgl/util/url.hpp> +#include <mbgl/util/std.hpp> #include <mbgl/platform/log.hpp> #include <iostream> +#include <uv.h> + +#include <cassert> + + const std::string base_directory = []{ std::string fn = __FILE__; fn.erase(fn.find_last_of("/")); @@ -13,110 +19,48 @@ const std::string base_directory = []{ return fn + "/node_modules/mapbox-gl-test-suite/"; }(); - namespace mbgl { -std::shared_ptr<platform::Request> -platform::request_http(const std::string &url, - std::function<void(Response *)> callback, - std::shared_ptr<uv::loop> loop) { - uv_loop_t *l = nullptr; - if (loop) { - l = **loop; - } else { - l = uv_default_loop(); - } +void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &baton) { + assert(uv_thread_self() == baton->thread_id); - std::string clean_url = util::percentDecode(url); + std::string clean_url = util::percentDecode(baton->path); if (clean_url.find("local://") == 0) { clean_url = base_directory + clean_url.substr(8); } - std::shared_ptr<Request> req = std::make_shared<Request>(url, callback, loop); - - int err; - - std::string body; - FILE *file = std::fopen(clean_url.c_str(),"rb"); - - if (file != NULL) - { - std::fseek(file, 0, SEEK_END); - std::size_t file_size = std::ftell(file); - std::fseek(file, 0, SEEK_SET); - body.resize(file_size); - std::fread(&body[0], file_size, 1, file); - std::fclose(file); - } - - /* - uv_fs_t open_req; - err = uv_fs_open(l, &open_req, clean_url.c_str(), O_RDONLY, S_IRUSR, nullptr); - uv_fs_req_cleanup(&open_req); - if (err < 0) { - req->res->code = err; - req->res->error_message = uv_strerror(uv_last_error(l)); - Log::Warning(Event::HttpRequest, err, url + ": " + uv_strerror(uv_last_error(l))); - req->complete(); - return req; - } - uv_file fd = err; - - uv_fs_t stat_req; - uv_fs_fstat(l, &stat_req, fd, nullptr); - uv_fs_req_cleanup(&stat_req); - err = uv_fs_fstat(l, &stat_req, fd, nullptr); - if (err < 0) { - req->res->code = err; - req->res->error_message = uv_strerror(uv_last_error(l)); - Log::Warning(Event::HttpRequest, err, url + ": " + uv_strerror(uv_last_error(l))); - req->complete(); - return req; - } - - const uint64_t size = static_cast<const uv_statbuf_t*>(stat_req.ptr)->st_size; - - - std::string body; - body.resize(size); - uv_buf_t uvbuf = uv_buf_init(const_cast<char *>(body.data()), body.size()); - - uv_fs_t read_req; - err = uv_fs_read(l, &read_req, fd, &uvbuf, 1, 0, nullptr); - uv_fs_req_cleanup(&read_req); - if (err < 0) { - req->res->code = err; - req->res->error_message = uv_strerror(uv_last_error(l)); - Log::Warning(Event::HttpRequest, err, url + ": " + uv_strerror(uv_last_error(l))); - req->complete(); - return req; + baton->response = std::make_unique<Response>(); + FILE *file = fopen(clean_url.c_str(),"rb"); + if (file != NULL) { + fseek(file, 0, SEEK_END); + const size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + baton->response->data.resize(size); + fread(&baton->response->data[0], size, 1, file); + fclose(file); + + baton->response->code = 200; + baton->type = HTTPResponseType::Successful; + } else { + baton->type = HTTPResponseType::PermanentError; + baton->response->code = 404; } + uv_async_send(baton->async); +} - uv_fs_t close_req; - err = uv_fs_close(l, &close_req, fd, nullptr); - uv_fs_req_cleanup(&close_req); - if (err < 0) { - req->res->code = err; - req->res->error_message = uv_strerror(uv_last_error(l)); - Log::Warning(Event::HttpRequest, err, url + ": " + uv_strerror(uv_last_error(l))); - req->complete(); - return req; - } - */ +void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &baton) { + fprintf(stderr, "HTTP request cannot be canceled because it is answered immediately"); + abort(); +} - req->res->body.swap(body); - req->res->code = 200; - Log::Info(Event::HttpRequest, 200, url); - req->complete(); +namespace platform { - return req; +std::string defaultCacheDatabase() { + // Disables the cache. + return ""; } -void platform::cancel_request_http(const std::shared_ptr<Request> &req) { - if (req) { - req->cancelled = true; - } } } |