diff --git a/include/mbgl/ios/MGLMapboxEvents.h b/include/mbgl/ios/MGLMapboxEvents.h index 6f994fea2..0567eb676 100644 --- a/include/mbgl/ios/MGLMapboxEvents.h +++ b/include/mbgl/ios/MGLMapboxEvents.h @@ -1,16 +1,24 @@ #import -extern NSString *const MGLEventMapLoad; -extern NSString *const MGLEventMapTap; -extern NSString *const MGLEventMapSingleTap; -extern NSString *const MGLEventMapDoubleTap; -extern NSString *const MGLEventMapTwoFingerSingleTap; -extern NSString *const MGLEventMapQuickZoom; -extern NSString *const MGLEventMapPanStart; -extern NSString *const MGLEventMapPanEnd; -extern NSString *const MGLEventMapPinchStart; -extern NSString *const MGLEventMapRotateStart; -extern NSString *const MGLEventMapLocation; +extern NSString *const MGLEventTypeMapLoad; +extern NSString *const MGLEventTypeMapTap; +extern NSString *const MGLEventTypeMapDragEnd; +extern NSString *const MGLEventTypeLocation; + +extern NSString *const MGLEventKeyLatitude; +extern NSString *const MGLEventKeyLongitude; +extern NSString *const MGLEventKeyZoomLevel; +extern NSString *const MGLEventKeyPushEnabled; +extern NSString *const MGLEventKeyEmailEnabled; +extern NSString *const MGLEventKeyGestureID; + +extern NSString *const MGLEventGestureSingleTap; +extern NSString *const MGLEventGestureDoubleTap; +extern NSString *const MGLEventGestureTwoFingerSingleTap; +extern NSString *const MGLEventGestureQuickZoom; +extern NSString *const MGLEventGesturePanStart; +extern NSString *const MGLEventGesturePinchStart; +extern NSString *const MGLEventGestureRotateStart; @interface MGLMapboxEvents : NSObject diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index cb9391628..4ce701430 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -159,8 +160,8 @@ public: const std::vector& symbols); void removeAnnotation(uint32_t); void removeAnnotations(const std::vector&); - std::vector getAnnotationsInBounds(const LatLngBounds&) const; - LatLngBounds getBoundsForAnnotations(const std::vector&) const; + std::vector getAnnotationsInBounds(const LatLngBounds&); + LatLngBounds getBoundsForAnnotations(const std::vector&); // Debug void setDebug(bool value); @@ -200,6 +201,12 @@ private: // the stylesheet. void prepare(); + // Runs the function in the map thread. + void invokeTask(std::function&&); + template auto invokeSyncTask(const Fn& fn) -> decltype(fn()); + + void processTasks(); + void updateAnnotationTiles(const std::vector&); enum class Mode : uint8_t { @@ -219,6 +226,7 @@ private: std::thread thread; std::unique_ptr asyncTerminate; std::unique_ptr asyncUpdate; + std::unique_ptr asyncInvoke; std::unique_ptr asyncRender; bool terminating = false; @@ -257,6 +265,9 @@ private: std::set> activeSources; std::atomic updated; + + std::mutex mutexTask; + std::queue> tasks; }; } diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 31d84000a..6f3aec803 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -355,12 +355,12 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; const mbgl::LatLng latLng = mbglMap->getLatLng(); const double zoom = mbglMap->getZoom(); - [MGLMapboxEvents pushEvent:MGLEventMapLoad withAttributes:@{ - @"lat": @(latLng.latitude), - @"lng": @(latLng.longitude), - @"zoom": @(zoom), - @"enabled.push": @([MGLMapboxEvents checkPushEnabled]), - @"enabled.email": @([MGLMapboxEvents checkEmailEnabled]) + [MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{ + MGLEventKeyLatitude: @(latLng.latitude), + MGLEventKeyLongitude: @(latLng.longitude), + MGLEventKeyZoomLevel: @(zoom), + MGLEventKeyPushEnabled: @([MGLMapboxEvents checkPushEnabled]), + MGLEventKeyEmailEnabled: @([MGLMapboxEvents checkEmailEnabled]) }]; return YES; @@ -586,7 +586,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handlePanGesture:(UIPanGestureRecognizer *)pan { - [self trackGestureEvent:MGLEventMapPanStart forRecognizer:pan]; + [self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan]; if ( ! self.isScrollEnabled) return; @@ -653,17 +653,17 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; CLLocationCoordinate2D panCoordinate = [self convertPoint:pointInView toCoordinateFromView:pan.view]; double zoom = [self zoomLevel]; - [MGLMapboxEvents pushEvent:MGLEventMapPanEnd withAttributes:@{ - @"lat": @(panCoordinate.latitude), - @"lng": @(panCoordinate.longitude), - @"zoom": @(zoom) + [MGLMapboxEvents pushEvent:MGLEventTypeMapDragEnd withAttributes:@{ + MGLEventKeyLatitude: @(panCoordinate.latitude), + MGLEventKeyLongitude: @(panCoordinate.longitude), + MGLEventKeyZoomLevel: @(zoom) }]; } } - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch { - [self trackGestureEvent:MGLEventMapPinchStart forRecognizer:pinch]; + [self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch]; if ( ! self.isZoomEnabled) return; @@ -701,7 +701,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate { - [self trackGestureEvent:MGLEventMapRotateStart forRecognizer:rotate]; + [self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate]; if ( ! self.isRotateEnabled) return; @@ -743,7 +743,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap { - [self trackGestureEvent:MGLEventMapSingleTap forRecognizer:singleTap]; + [self trackGestureEvent:MGLEventGestureSingleTap forRecognizer:singleTap]; CGPoint tapPoint = [singleTap locationInView:self]; @@ -866,7 +866,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap { - [self trackGestureEvent:MGLEventMapDoubleTap forRecognizer:doubleTap]; + [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; if ( ! self.isZoomEnabled) return; @@ -897,7 +897,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap { - [self trackGestureEvent:MGLEventMapTwoFingerSingleTap forRecognizer:twoFingerTap]; + [self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap]; if ( ! self.isZoomEnabled) return; @@ -930,7 +930,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; - (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom { - [self trackGestureEvent:MGLEventMapQuickZoom forRecognizer:quickZoom]; + [self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom]; if ( ! self.isZoomEnabled) return; @@ -984,11 +984,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr; CLLocationCoordinate2D gestureCoordinate = [self convertPoint:pointInView toCoordinateFromView:recognizer.view]; double zoom = [self zoomLevel]; - [MGLMapboxEvents pushEvent:MGLEventMapTap withAttributes:@{ - @"lat": @(gestureCoordinate.latitude), - @"lng": @(gestureCoordinate.longitude), - @"zoom": @(zoom), - @"gesture": gestureID + [MGLMapboxEvents pushEvent:MGLEventTypeMapTap withAttributes:@{ + MGLEventKeyLatitude: @(gestureCoordinate.latitude), + MGLEventKeyLongitude: @(gestureCoordinate.longitude), + MGLEventKeyZoomLevel: @(zoom), + MGLEventKeyGestureID: gestureID }]; } diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m index 1ce8288f5..94374b9da 100644 --- a/platform/ios/MGLMapboxEvents.m +++ b/platform/ios/MGLMapboxEvents.m @@ -12,17 +12,25 @@ static NSString *const MGLMapboxEventsUserAgent = @"MapboxEventsiOS/1.0"; static NSString *const MGLMapboxEventsAPIBase = @"https://api.tiles.mapbox.com"; -NSString *const MGLEventMapLoad = @"map.load"; -NSString *const MGLEventMapTap = @"map.click"; -NSString *const MGLEventMapSingleTap = @"SingleTap"; -NSString *const MGLEventMapDoubleTap = @"DoubleTap"; -NSString *const MGLEventMapTwoFingerSingleTap = @"TwoFingerTap"; -NSString *const MGLEventMapQuickZoom = @"QuickZoom"; -NSString *const MGLEventMapPanStart = @"Pan"; -NSString *const MGLEventMapPanEnd = @"map.dragend"; -NSString *const MGLEventMapPinchStart = @"Pinch"; -NSString *const MGLEventMapRotateStart = @"Rotation"; -NSString *const MGLEventMapLocation = @"Location"; +NSString *const MGLEventTypeMapLoad = @"map.load"; +NSString *const MGLEventTypeMapTap = @"map.click"; +NSString *const MGLEventTypeMapDragEnd = @"map.dragend"; +NSString *const MGLEventTypeLocation = @"Location"; + +NSString *const MGLEventKeyLatitude = @"lat"; +NSString *const MGLEventKeyLongitude = @"lng"; +NSString *const MGLEventKeyZoomLevel = @"zoom"; +NSString *const MGLEventKeyPushEnabled = @"enabled.push"; +NSString *const MGLEventKeyEmailEnabled = @"enabled.email"; +NSString *const MGLEventKeyGestureID = @"gesture"; + +NSString *const MGLEventGestureSingleTap = @"SingleTap"; +NSString *const MGLEventGestureDoubleTap = @"DoubleTap"; +NSString *const MGLEventGestureTwoFingerSingleTap = @"TwoFingerTap"; +NSString *const MGLEventGestureQuickZoom = @"QuickZoom"; +NSString *const MGLEventGesturePanStart = @"Pan"; +NSString *const MGLEventGesturePinchStart = @"Pinch"; +NSString *const MGLEventGestureRotateStart = @"Rotation"; // // Threadsafety conventions: @@ -216,7 +224,7 @@ NSString *const MGLEventMapLocation = @"Location"; // use of +pushEvent:withAttributes:. // - (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary { - // convert any BOOL types to string values written out like JS + // conve+rt any BOOL types to string values written out like JS NSMutableDictionary *newAttributeDictionary = [NSMutableDictionary dictionaryWithCapacity:[attributeDictionary count]]; NSString *key; NSObject *value; @@ -249,7 +257,7 @@ NSString *const MGLEventMapLocation = @"Location"; if (!event) return; - NSMutableDictionary *evt = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *evt = [[NSMutableDictionary alloc] initWithDictionary:attributeDictionary]; // mapbox-events stock attributes [evt setObject:event forKey:@"event"]; [evt setObject:@(1) forKey:@"version"]; @@ -268,11 +276,7 @@ NSString *const MGLEventMapLocation = @"Location"; [evt setValue:[weakSelf getCurrentCellularNetworkConnectionType] forKey:@"cellularNetworkType"]; [evt setValue:[weakSelf getWifiNetworkName] forKey:@"wifi"]; [evt setValue:@([weakSelf getContentSizeScale]) forKey:@"accessibilityFontScale"]; - - for (NSString *key in [attributeDictionary allKeys]) { - [evt setObject:[attributeDictionary valueForKey:key] forKey:key]; - } - + // Make Immutable Version NSDictionary *finalEvent = [NSDictionary dictionaryWithDictionary:evt]; diff --git a/platform/ios/MGLMetricsLocationManager.m b/platform/ios/MGLMetricsLocationManager.m index bdf6e14ae..c0ff740b3 100644 --- a/platform/ios/MGLMetricsLocationManager.m +++ b/platform/ios/MGLMetricsLocationManager.m @@ -43,9 +43,9 @@ - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { // Iterate through locations to pass all data for (CLLocation *loc in locations) { - [MGLMapboxEvents pushEvent:MGLEventMapLocation withAttributes:@{ - @"lat": @(loc.coordinate.latitude), - @"lng": @(loc.coordinate.longitude) + [MGLMapboxEvents pushEvent:MGLEventTypeLocation withAttributes:@{ + MGLEventKeyLatitude: @(loc.coordinate.latitude), + MGLEventKeyLongitude: @(loc.coordinate.longitude) }]; } } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index a49df0119..47d6ed71d 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -144,6 +144,11 @@ void Map::start(bool startPaused) { update(); }); + asyncInvoke = util::make_unique(env->loop, [this] { + processTasks(); + }); + + asyncRender = util::make_unique(env->loop, [this] { // Must be called in Map thread. assert(Environment::currentlyOn(ThreadType::Map)); @@ -315,6 +320,42 @@ void Map::triggerRender() { asyncRender->send(); } +// Runs the function in the map thread. +void Map::invokeTask(std::function&& fn) { + { + std::lock_guard lock(mutexTask); + tasks.emplace(::std::forward>(fn)); + } + + // TODO: Once we have aligned static and continuous rendering, this should always dispatch + // to the async queue. + if (asyncInvoke) { + asyncInvoke->send(); + } else { + processTasks(); + } +} + +template auto Map::invokeSyncTask(const Fn& fn) -> decltype(fn()) { + std::promise promise; + invokeTask([&fn, &promise] { promise.set_value(fn()); }); + return promise.get_future().get(); +} + +// Processes the functions that should be run in the map thread. +void Map::processTasks() { + std::queue> queue; + { + std::lock_guard lock(mutexTask); + queue.swap(tasks); + } + + while (!queue.empty()) { + queue.front()(); + queue.pop(); + } +} + void Map::checkForPause() { std::unique_lock lockRun (mutexRun); while (pausing) { @@ -542,27 +583,31 @@ std::string Map::getAccessToken() const { void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) { assert(Environment::currentlyOn(ThreadType::Main)); - annotationManager->setDefaultPointAnnotationSymbol(symbol); + invokeTask([=] { + annotationManager->setDefaultPointAnnotationSymbol(symbol); + }); } double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) { - SpritePosition pos = sprite->getSpritePosition(symbol); - - return -pos.height / pos.pixelRatio / 2; + assert(Environment::currentlyOn(ThreadType::Main)); + return invokeSyncTask([&] { + assert(sprite); + const SpritePosition pos = sprite->getSpritePosition(symbol); + return -pos.height / pos.pixelRatio / 2; + }); } uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol) { - assert(Environment::currentlyOn(ThreadType::Main)); - std::vector points({ point }); - std::vector symbols({ symbol }); - return addPointAnnotations(points, symbols)[0]; + return addPointAnnotations({ point }, { symbol }).front(); } std::vector Map::addPointAnnotations(const std::vector& points, const std::vector& symbols) { assert(Environment::currentlyOn(ThreadType::Main)); - auto result = annotationManager->addPointAnnotations(points, symbols, *this); - updateAnnotationTiles(result.first); - return result.second; + return invokeSyncTask([&] { + auto result = annotationManager->addPointAnnotations(points, symbols, *this); + updateAnnotationTiles(result.first); + return result.second; + }); } void Map::removeAnnotation(uint32_t annotation) { @@ -572,22 +617,28 @@ void Map::removeAnnotation(uint32_t annotation) { void Map::removeAnnotations(const std::vector& annotations) { assert(Environment::currentlyOn(ThreadType::Main)); - auto result = annotationManager->removeAnnotations(annotations, *this); - updateAnnotationTiles(result); + invokeTask([=] { + auto result = annotationManager->removeAnnotations(annotations, *this); + updateAnnotationTiles(result); + }); } -std::vector Map::getAnnotationsInBounds(const LatLngBounds& bounds) const { +std::vector Map::getAnnotationsInBounds(const LatLngBounds& bounds) { assert(Environment::currentlyOn(ThreadType::Main)); - return annotationManager->getAnnotationsInBounds(bounds, *this); + return invokeSyncTask([&] { + return annotationManager->getAnnotationsInBounds(bounds, *this); + }); } -LatLngBounds Map::getBoundsForAnnotations(const std::vector& annotations) const { +LatLngBounds Map::getBoundsForAnnotations(const std::vector& annotations) { assert(Environment::currentlyOn(ThreadType::Main)); - return annotationManager->getBoundsForAnnotations(annotations); + return invokeSyncTask([&] { + return annotationManager->getBoundsForAnnotations(annotations); + }); } void Map::updateAnnotationTiles(const std::vector& ids) { - assert(Environment::currentlyOn(ThreadType::Main)); + assert(Environment::currentlyOn(ThreadType::Map)); for (const auto &source : activeSources) { if (source->info.type == SourceType::Annotations) { source->source->invalidateTiles(*this, ids);