Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

MGLStyle key-value coding compliance #6097

Merged
merged 8 commits into from
Nov 29, 2016
Merged

Conversation

1ec5
Copy link
Contributor

@1ec5 1ec5 commented Aug 19, 2016

This PR makes the runtime styling API’s entry points key-value coding compliant.

The -[MGLMapView style] factory method has been replaced with a KVC-compliant style property whose value changes only when reloading the style or changing the style URL. This makes the API more convenient in Swift and more predictable overall.

New layers and sources properties have been added to MGLStyle that make it easy to enumerate style layers or sources without having to hard-code style-specific identifiers in application code. See #5999, #7030, and #7031 for examples of how these properties would be used.

The layers array is ordered from front to back, for consistency with Cocoa, as opposed to mbgl’s back-to-front ordering. There are complementary -insertLayer:aboveLayer: and -insertLayer:belowLayer: methods, for consistency with MapKit’s -[MKMapView insertOverlay:aboveOverlay:] and -[MKMapView insertOverlay:belowOverlay:]. Please double-check my index arithmetic to make sure I didn’t make any off-by-one errors. The sources property is a unordered set. A dictionary mapping identifiers to source objects would better conform to the style specification but would encourage some antipatterns we want to avoid; see #7011 (comment) for details.

The mutable layers and sources collections comply with KVC conventions for to-many relationships, which entails additional methods that mbgl::Map and mbgl::style::Style wouldn’t ordinarily provide. These KVC methods could be optimized further with corresponding methods in mbgl; however, memory management would become a lot less clear. For example, who owns an mbgl::style::Layer if it’s already part of a style but is included in an array passed into -[MGLStyle setLayers:]? Rather than muddle the pointer ownership model or implement complex array diffing, this PR sticks to the safest, most straightforward implementation despite a possible performance tradeoff.

As a real-world stress test of KVC compliance, a sidebar has been added to macosapp that displays the layers in the current style and updates the list automatically using Cocoa bindings. The sidebar can be opened and closed via View ‣ Show/Hide Layers or ⇧⌘L. Layers are shown with their identifiers and icons representing their types. Hidden layers are shown in italics. To toggle one or more layers’ visibility, select them and double-click on them. Or use the context menu, which also has an option to delete the selected layers.

Finally, to illustrate one common use of the new APIs, macosapp now has an option to localize all labels in the user’s preferred language, and the Style toolbar button displays the name of the current style when a custom style is shown.

outofplace
Hiding layers using the new Layers sidebar in macosapp.

  • Make MGLMapView’s style property KVC-compliant
  • Make layers API KVC-compliant
  • Reverse layers to go from front to back
  • Implement -[MGLStyle insertLayer:aboveLayer:] (reprising [macos, ios] Insert layer above #6730)
  • Make sources API KVC-compliant
  • Fix race condition involving change notifications when switching styles
  • Issue change notifications for layers when adding shape annotations
  • Add layers sidebar to macosapp
  • Allow showing/hiding layers sidebar

Fixes #6003, fixes #6096, fixes #6931.

/cc @frederoni @jfirebaugh

@1ec5 1ec5 added iOS Mapbox Maps SDK for iOS refactor macOS Mapbox Maps SDK for macOS Core The cross-platform C++ core, aka mbgl runtime styling labels Aug 19, 2016
@1ec5 1ec5 added this to the ios-v3.4.0 milestone Aug 19, 2016
@1ec5 1ec5 self-assigned this Aug 19, 2016
@1ec5 1ec5 added the ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold label Aug 19, 2016
@1ec5 1ec5 changed the title MGLStyle.layers; key-value compliance Key-value compliant layer API Aug 21, 2016
style::Layer* getLayer(const std::string& layerID);
void setLayers(std::vector<style::Layer*>&);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map/Style must have ownership of the layers, so this signature needs to be void setLayers(std::vector<std::unique_ptr<style::Layer>>).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e43fbf4.

@@ -794,6 +798,16 @@ style::Layer* Map::getLayer(const std::string& layerID) {
return nullptr;
}

void Map::setLayers(std::vector<std::unique_ptr<style::Layer>> layers) {
impl->view.activate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs an if (!impl->style) guard and impl->styleMutated = true; like the other mutators.

std::vector<std::unique_ptr<mbgl::style::Layer>> rawLayers;
rawLayers.reserve(layers.count);
for (MGLStyleLayer *layer in layers.reverseObjectEnumerator) {
rawLayers.push_back(std::unique_ptr<mbgl::style::Layer>(layer.layer));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructing a std::unique_ptr from anything other than the result of new is usually incorrect (and even then, you should be using std::make_unique). What's the ownership model of layer.layer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s complicated: #6254. 😕 Maybe we need to tackle that issue first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To respect ownership of the layers passed into -setLayers: – some of which may exist in the style and some of which may be new – we need to ability to remove a layer from the style and regain ownership of it in order to re-add it. So we’re blocked on mbgl::Map::removeLayer() returning ownership of the layer: #6959.

@frederoni
Copy link
Contributor

Great work here @1ec5
This PR will help a lot with debugging and taking runtime styling even further.

@1ec5 1ec5 force-pushed the 1ec5-style-layers-6003 branch 2 times, most recently from c45be63 to 3dd8910 Compare November 28, 2016 22:00
if (impl->style) {
impl->styleMutated = true;
return impl->style->getLayer(layerID);
}
return nullptr;
}

void Map::setLayers(std::vector<std::unique_ptr<Layer>> layers) {
if (!impl->style) {
impl->styleMutated = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes in the opposite branch -- we're only mutating the style if we have one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I ended up not using this method in the SDK due to the memory management conundrum in the PR description. Should I keep this method around to complement getLayers() or remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please remove it if it's not used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

@@ -51,6 +51,9 @@ - (void)addToMapView:(MGLMapView *)mapView belowLayer:(MGLStyleLayer *)otherLaye
- (void)removeFromMapView:(MGLMapView *)mapView
{
auto removedLayer = mapView.mbglMap->removeLayer(self.identifier.UTF8String);
if (!removedLayer) {
return;
}
_pendingLayer = std::move(reinterpret_cast<std::unique_ptr<mbgl::style::BackgroundLayer> &>(removedLayer));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not part of this PR, but this cast is undefined behavior. You need to write something like:

template <class T, class U>
std::unique_ptr<T> dynamic_ptr_cast(std::unique_ptr<U> p) {
    T* t = dynamic_cast<T*>(p.get());
    if (!t) return nullptr;
    p.release();
    return t;
}

And be aware that this will return a null unique_ptr (and deallocate the input) if the dynamic_cast fails, i.e. if the input is not the anticipated derived type. In this particular code, it looks like this could happen if the user was aliasing *StyleLayer objects while at the same time removing a layer and adding another layer with the same identifier but a different layer type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#7211

Added new layers and sources properties to MGLStyle that contain all the style’s layers and sources, respectively. These properties are KVC-compliant with all the mutable to-many methods. Layers are ordered from topmost to bottommost, for consistency with Cocoa APIs where front/first means top and back/last means bottom.

Also added storage for mbgl::style::Source in MGLSource proper for wrapping AnnotationSource. Until the style finishes loading, its name property is set to nil.

Fixes #6003.
Replaced -[MGLMapView style] with a property. Keep the MGLStyle object around for the lifetime of the style.

Bracket changes to layers in willChange and didChange calls. The built-in point annotation layer is added after the style is finished loading but before the map is finished loading. Cause a second wave of change notifications to go out, about both sources and layers. Issue change notifications for style layers when shape annotations are added or removed.
Fixed a crash that often occurred when switching styles. MGLMapView was ahead of MapDocument in the responder chain, so calls to -setStyle: were going to MGLMapView’s private -setStyle: method. The sender was being passed in the first parameter instead of an MGLStyle method.
Added a sidebar to the main document window in macosapp that lists the layers in the current style and updates whenever the style changes or a layer is added or removed programmatically. Display an icon beside each layer in the sidebar that indicates the kind of layer. Double-click a layer or layers to toggle their visibility, which is an undoable action. Added a menu item and toolbar button to toggle the Layers sidebar. Added a context menu for toggling visibility of and deleting layers selected in the Layers sidebar.

Checked in the original SVGs for layer icons.
Display the custom style’s name in the Style toolbar button.
Added a setting to macosapp to change all symbol layers in the current style to use the preferred language if available. If the setting is off, change all symbol layers to use local languages.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Core The cross-platform C++ core, aka mbgl iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS refactor release blocker Blocks the next final release runtime styling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants