-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Conversation
@1ec5, thanks for your PR! By analyzing the history of the files in this pull request, we identified @frederoni, @incanus and @boundsj to be potential reviewers. |
Here are the current linker errors:
|
The mangled version:
Indeed, there’s no trace of |
As I work through the linker issues, a design question for everyone: am I swinging the pendulum too far towards type safety at the expense of concision? Something like this: // MGLMapViewDelegate.mapViewDidFinishLoadingMap(_:)
let layer = mapView.style().layer(identifier: "motorway") as? MGLLineStyleLayer
layer?.lineGapWidth = 5 as MGLStyleAttributeValue
layer?.lineColor = UIColor.blue
let fn = MGLStyleAttributeFunction()
fn.stops = [1: 5, 18: 3]
layer?.lineWidth = fn will probably turn into this: // MGLMapViewDelegate.mapViewDidFinishLoadingMap(_:)
let layer = mapView.style().layer(identifier: "motorway") as? MGLLineStyleLayer
layer?.lineGapWidth = MGLStyleValue(rawValue: 5)
layer?.lineColor = MGLStyleValue(rawValue: .blue)
layer?.lineWidth = MGLStyleValue(stops: [
1: MGLStyleValue(rawValue: 5),
18: MGLStyleValue(rawValue: 3),
]) The equivalent code in Objective-C: // -[MGLMapViewDelegate mapViewDidFinishLoadingMap:]
MGLLineStyleLayer *layer = (MGLLineStyleLayer *)[mapView.style layerWithIdentifier:@"motorway"];
layer.lineGapWidth = @5;
layer.lineColor = UIColor.blueColor;
MGLStyleAttributeFunction *fn = [[MGLStyleAttributeFunction alloc] init];
fn.stops = @{@1: @5, @18: @3};
layer.lineWidth = fn; will probably turn into this: // -[MGLMapViewDelegate mapViewDidFinishLoadingMap:]
MGLLineStyleLayer *layer = (MGLLineStyleLayer *)[mapView.style layerWithIdentifier:@"motorway"];
layer.lineGapWidth = [MGLStyleValue<NSNumber *> valueWithRawValue:@5];
layer.lineColor = [MGLStyleValue<UIColor> valueWithRawValue:UIColor.blueColor];
MGLStyleAttributeFunction *fn = [[MGLStyleAttributeFunction alloc] init];
NSDictionary *stops = @{
@1: [MGLStyleValue<NSNumber *> valueWithRawValue:@5],
@18: [MGLStyleValue<NSNumber *> valueWithRawValue:@3],
};
layer.lineWidth = [MGLStyleValue<NSNumber *> valueWithStops:stops]; (Swift is able to infer the This recursive use of MGLStyleValue is pretty ugly, although it’s flexible enough to accommodate property functions (#4860) and future extensions to the style specification syntax, such as token defaults (mapbox/mapbox-gl-style-spec#104) and conditionals (mapbox/mapbox-gl-style-spec#402). As a compromise, we could allow the stops dictionary to contain values of type e.g. |
The new Swift implementation above is slightly more awkward. Things like this, mentioning layer?.lineColor = MGLStyleValue(rawValue: .blue) The new Objective-C implementation feels a lot more awkward. layer.lineColor = [MGLStyleValue<UIColor> valueWithRawValue:UIColor.blueColor]; This is merely setting a property (a short one at that) to a color, but it's verbose even for Objective-C. The ideal here is: layer.lineColor = [UIColor blueColor]; Something I've been working with lately is SceneKit, which features a number of types that can be passed as Notably:
This is even broader than our property APIs, yet still relies on an untyped These are presumably meant to be used dynamically at runtime, else If you use a wrong type, everything compiles but the bad setter is just silently ignored. I don't feel like this is user-hostile and really keeps things simple, no? override func viewDidLoad() {
super.viewDidLoad()
let ball = SCNSphere(radius: 0.5)
let node = SCNNode(geometry: ball)
let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.scene?.rootNode.addChildNode(node)
view.addSubview(sceneView)
ball.firstMaterial?.diffuse.contents = UIColor.red // good
ball.firstMaterial?.specular.contents = UIView(frame: view.bounds) // bad, ignored
} Are we overthinking this API? |
Thank you for this example @incanus. It certainly speaks to a concern I’ve had as we’ve been building out this API. There’s a lot to be said for the simplicity of just using One alternative I proposed awhile back, in #5970 (comment), was to rely just as heavily on protocols as we do now, but make the protocols more specific. If we go that route, I think we need to avoid the mess of private categories that we currently rely on: it’s too error-prone on our end and so easy to misuse on the developer’s. |
I’m not wedded to the “raw value” terminology, although it’s consistent with raw values of Swift enumerations. For this PR, I was considering some helper classes like MGLStyleNumber, the same way NSNumber is a convenience subclass for NSValue. |
FWIW I'll dig around a bit to see if Apple is introducing any new APIs like this, just as more data. Still thinking on this a bit. |
912a1e6
to
97ccdb8
Compare
Great rework @1ec5 layer.lineGapWidth = [MGLStyleValue<NSNumber *> valueWithRawValue:@5];
layer.lineColor = [MGLStyleValue<UIColor> valueWithRawValue:UIColor.blueColor];
MGLStyleAttributeFunction *fn = [[MGLStyleAttributeFunction alloc] init];
NSDictionary *stops = @{
@1: [MGLStyleValue<NSNumber *> valueWithRawValue:@5],
@18: [MGLStyleValue<NSNumber *> valueWithRawValue:@3],
}; into this: layer.lineGapWidth = @5.mglValue;
layer.lineColor = UIColor.blueColor.mglColor;
MGLStyleAttributeFunction *fn = [[MGLStyleAttributeFunction alloc] init];
NSDictionary *stops = @{
@1: @5.mglValue,
@18: @3.mglValue,
}; |
That’s a clever idea. |
With @boundsj’s help, I was able to get past the linker issues over the weekend. I also fixed a number of silly mistakes related to switching a method to use an inout parameter instead of a return value. This has been the sole remaining beta blocker for awhile now. The criteria for a beta blocker is that it prevents us from being able to encourage developers to start developing against a new feature, because we expect to break their code significantly in a future prerelease. Given the problems we’ve already identified with the current API, like #5970 (comment), I believe #5970 still deserves to be a beta blocker – we need to change something. How about we land these changes, verbosity and all, and release the beta? Feedback we get from the beta can inform whether we:
We can layer all three approaches atop the changes in this PR. MGLStyleValue will continue to be accepted by the API in future betas, so any changes to client code will be optional. After all, the bulk of this PR is about improving internal type safety, which we want regardless of external ergonomics. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
57e4165
to
65d2c92
Compare
Thanks everyone for your feedback. I’m going to finish the documentation and land this PR for the beta. Then we’ll continue the discussion in #5970. |
37266e5
to
bd25c25
Compare
bd25c25
to
42faaa6
Compare
Filed #6712 about the Qt5 build bot failure. |
42faaa6
to
4e8328b
Compare
MGLStyleValue is an umbrella class serving as a variant container for MGLStyleConstantValue and MGLStyleFunction. These classes use lightweight generics to indicate the underlying type. A templated C++ class concisely converts between mbgl::style::PropertyValue and MGLStyleValue.
Added designated initializers to MGLStyleValue and friends. Restored custom equality and descriptions to MGLStyleFunction. Raise an exception if an unrecognized subclass of MGLStyleValue or MGLStyleValue itself is passed into a style attribute property.
This makes runtime styling category method naming consistent with the rest of the SDK.
4e8328b
to
49ca625
Compare
🚢 🚀 |
This PR reworks how style attribute values are represented in the runtime styling API. The intricate system of protocols adopted by Foundation classes via categories has been replaced with a simple umbrella value type, MGLStyleValue, that adopts lightweight generics. The parallel – and often incomplete – private protocols adopted by Foundation classes via categories has been replaced by a single templated C++ class, MGLStyleValueTransformer. C++ templates were chosen over C macros because they’re far easier to debug (once they compile, that is).
Everywhere the developer previously used a string literal, for instance, they must now wrap that string literal in an
MGLStyleValue<NSString *>
. That includes style functions: MGLStyleFunction, one of the classes under the MGLStyleValue umbrella, expects stop values to be wrapped in MGLStyleValues. See #6601 (comment) for a concrete example in both Swift and Objective-C. All this is necessary because the Objective-C language lacks variant support.The goal is to add a veneer of type safety, even when working with style functions, yet avoid a proliferation of abstract classes or private protocols. This approach should also extend rather straightforwardly to property functions and future extensions to the style specification syntax. More importantly, this PR ensures internal type safety, avoiding what are in essence unchecked casts throughout the API implementation.
Fixes #5970. #6588 cleans up the style layer classes themselves, whereas this PR cleans up each style layer class’s properties.
Fixes #6078.
Lots to do:
-[MGLStyleFunction rawValueAtZoomLevel:]
/cc @frederoni @boundsj @incanus