-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Property refactor #5682
Property refactor #5682
Conversation
3dce1db
to
0ed1c7a
Compare
d20c81b
to
1349285
Compare
The |
1455dd5
to
1ca6949
Compare
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.
@jfirebaugh I haven't looked beyond properties.js
yet, but reading through that, I think the architecture looks great
return finalValue; | ||
} else if (now > this.end) { | ||
// Transition from prior value is now complete. | ||
this.prior = null; |
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.
It looks like the reason for clearing prior is that we want it to be destroyed once we don't need it anymore -- is that right?
That makes sense internally, but it does feel a bit weird to me that possiblyEvaluate({now, ...})
has a side effect. I can't think of a good way to make this class immutable, but if it is going to have temporal state, I think it might be worthwhile to make it more explicit: hold a _lastEvaluated
and disallow going "backwards in time", i.e. assert(parameters.now >= _lastEvaluated)
// Transitions to data-driven properties are not supported. | ||
// We snap immediately to the data-driven value so that, when we perform layout, | ||
// we see the data-driven function and can use it to populate vertex buffers. | ||
this.prior = null; |
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.
Could this happen in the constructor? Or else, if value
could be modified, would it be better to encapsulate behind a setValue()
?
* `PossiblyEvaluatedPropertyValue` is used for data-driven paint and layout property values. It holds a | ||
* `PossiblyEvaluatedValue` and the `GlobalProperties` that were used to generate it. You're not allowed to supply | ||
* a different set of `GlobalProperties` when performing the final evaluation because they would be ignored in the | ||
* case where the input value was a constant or camera function. |
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.
👍 good call.
src/style/properties.js
Outdated
export type PossiblyEvaluatedValue<T> = | ||
| {kind: 'constant', value: T} | ||
| SourceExpression | ||
| CompositeExpression; |
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.
Do we need to export this type? Having both PossiblyEvaluatedValue<T>
and PossiblyEvaluatedPropertyValue<T>
as public types from this module might be confusing (or at least add conceptual overhead), and anyhow I think that any time you have a PossiblyEvaluatedValue<T>
, you might need to evaluate it and thus would need the owning PossiblyEvaluatedPropertyValue<T>
.
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.
It's used only in query_utils.js
, to quarantine an any
cast. We could probably remove this with some additional refactoring.
It's not really a layout property -- it can't be an expression or function. Thus we can make it a top-level property on StyleLayer and give it a strong type. This matches GL Native.
1ca6949
to
51aace0
Compare
this.textSizeData = getSizeData(this.zoom, layer, 'text-size'); | ||
this.iconSizeData = getSizeData(this.zoom, layer, 'icon-size'); | ||
const layer: SymbolStyleLayer = this.layers[0]; | ||
const unevaluatedLayoutValues = layer._unevaluatedLayout._values; |
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.
Why not use
const textSize = layer.getLayoutProperty('text-size');
const iconSize = layer.getLayoutProperty('icon-size');
instead of accessing _unevaluatedValues
directly?
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.
Oh, is this because getLayoutProperty
's return type isn't parameterized on the property name?
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.
It's because layer.getLayoutProperty
returns the raw style spec value (as needed for the public runtime styling API), and we need the PropertyValue
.
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.
Gotcha. I guess we could add SymbolStyleLayer#get{Text,Icon}Size()
... but treating *_bucket as a 'friend' of *_style_layer is probably reasonable, too.
src/data/program_configuration.js
Outdated
start: number, | ||
length: number, | ||
feature: Feature) { | ||
const value = layer.getPaintValue(this.property, {zoom: 0}, feature); | ||
const value: any = this.expression.evaluate({zoom: 0}, feature); |
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.
The return type of SourceExpression.evaluate()
is already any
-- do we need the annotation here?
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.
Nope, this was just copypasta.
src/render/draw_symbol.js
Outdated
@@ -168,8 +166,7 @@ function drawTileSymbols(program, programConfiguration, painter, layer, tile, bu | |||
const tr = painter.transform; | |||
|
|||
if (isSDF) { | |||
const haloWidthProperty = `${isText ? 'text' : 'icon'}-halo-width`; | |||
const hasHalo = !layer.isPaintValueFeatureConstant(haloWidthProperty) || layer.paint[haloWidthProperty]; | |||
const hasHalo = layer.paint.get(`${isText ? 'text' : 'icon'}-halo-width`).constantOr(1) !== 0; |
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.
I was gonna be pretty impressed that Flow could handle this, but it turns out it's just bailing -- adding typo to the property name expression here doesn't produce a flow error. It does work if the conditional logic is used to select a string literal; i.e., isText ? 'text-halo-width' : 'icon-halooooo-width'
produces an error, whereas ${isText ? 'text' : 'icon'}-haloooo-width
does not.
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.
👍 , will switch to isText ? 'text-halo-width' : 'icon-halo-width'
.
src/shaders/describe_shaders
Outdated
@@ -49,7 +48,7 @@ style.layers.forEach((layer) => { | |||
|
|||
function addProgramInfo(layer, programInterface, shaderDefinition) { | |||
const styleLayer = new StyleLayer(layer); | |||
styleLayer.updatePaintTransitions([], {}, { zoom: zoom }, new AnimationLoop(), {}); | |||
styleLayer.updatePaintTransitions([], {}, { zoom: zoom }, {}); | |||
const configuration = ProgramConfiguration.createDynamic( | |||
programInterface, styleLayer, zoom); | |||
const key = `${layer.type}${configuration.cacheKey || ''}`; |
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.
I'd be okay with just removing describe_shaders
-- it's not part of any regular workflows, and that makes it very likely to go stale
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.
👍
} | ||
} | ||
return false; | ||
} |
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.
Along the lines of the point above re: the temporal state in TransitioningPropertyValue
, would it make sense for hasTransition()
to accept a now
parameter (here and elsewhere)?
* heatmap-color default is no longer a special case
Instead, at the end of each frame, calculate whether another one is needed.
Instead make it a member variable that gets set when the PossiblyEvaluated is created as a result of (possibly) evaluating properties at a certain zoom level. This makes it clear that you can't supply a different zoom level at the final evaluation step. The SymbolBucket constructor now creates individual PossiblyEvaluatedPropertyValues for all the necessary zoom levels. See code comment for details.
588b1d0
to
9588222
Compare
I figured out how to restore https://bl.ocks.org/anonymous/raw/aad0ba1349e552599828bc7e5feadae4/ |
9588222
to
6be367e
Compare
src/style/style.js
Outdated
} | ||
} | ||
|
||
// if (this.placement.hasTransition()) { |
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.
Need to uncomment and fix this. @ChrisLoer Is this.placement.stillFading()
the right thing to check? It looks a bit different than native's hasTransitions()
.
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.
Err.... hopefully we can improve this with a back-port from native. I think right now, the answer may be that you should ignore the placement state in this hasTransitions
. They key logic for keeping animation running for placement/collision fading is the needsRerender = !this.placement.isDone() || this.placement.stillFading()
part that sets this._placementDirty
in Map#_render
. That accounts for both collision fade animations (that's what stillFading()
checks) but also for the map being in a state where it's waiting for the next placement to occur or in the middle of an incremental placement (kind of an analog of stale
on the native side although it's not quite the same).
For now, if you just leave this out of style.hasTransitions
, it should be fine because the render logic checks this state separately. /cc @ansis
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.
I did the best I could with my limited familiarity with this area of the codebase. I really appreciate the time you took to write these detailed explanatory comments!
// 2. `icon-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `icon-size` | ||
// expressions. | ||
// 3. `text-size` and `icon-size` at the zoom level of the bucket, plus one. Used to calculate collision boxes. | ||
// 4. `text-size` at zoom level 18. Used for something line-symbol-placement-related. |
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.
Should we add more detail to why we need the max-size for line-symbol placement?
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.
We should, but nobody seems remember the details! Tracking this in #5683.
src/style/properties.js
Outdated
* | ||
* `PossiblyEvaluatedValue` represents the three possible outcomes of this step: if the input value was a constant or | ||
* camera expression, then the "possibly evaluated" result is a constant value. Otherwise, the input value was either | ||
* a source expression or a camera expression, and we must defer final evaluation until supplied a feature. We separate |
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.
do you mean:
Otherwise, the input value was either a source expression or a composite expression
if not, otherwise I'm confused bc it seems the previous line contradicts this one.
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.
Yes, good catch, will fix.
@@ -43,12 +46,11 @@ function packColor(color: Color): [number, number] { | |||
]; | |||
} | |||
|
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.
a brief explanatory comment on the Binder interface might be useful here.
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.
👍, will port the docs from native.
* on the default value: using `for (const property of Object.keys(this._values))`, they can iterate over | ||
* only the _own_ properties of `_values`, skipping repeated calculation of transitions and possible/final | ||
* evaluations for defaults, the result of which will always be the same. | ||
* |
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.
This is a very cool solution 🥇
Introduces a `Properties` class which holds objects containing default values for the layout or paint property set of a given layer type. These objects are immutable, and they are used as the prototypes for the `_values` members of `Transitionable`, `Transitioning`, `Layout`, and `PossiblyEvaluated`. This allows these classes to avoid doing work in the common case where a property has no explicit value set and should be considered to take on the default value: using `for (const property of Object.keys(this._values))`, they can iterate over only the _own_ properties of `_values`, skipping repeated calculation of transitions and possible/final evaluations for defaults, the result of which will always be the same.
97d579c
to
7315489
Compare
The remaining unaddressed review comments were @anandthakker's observations about the interior mutability of |
Ports
PropertyValue
,TransitionablePropertyValue
,PossiblyEvaluatedPropertyValue
, etc. from native.Fixes #2739
Fixes #3044
Fixes #2769
Fixes #5627
Fixes not #3425 -- exception no longer occurs, but the layer turns blank. Probably a variant of #4012.
Paint
benchmark performance[ ] Include fix for Synchronize StyleLayer and Bucket/Buffer state #4012?Nah, followup PR.[ ] Include fix for Sharing StyleLayers between WorkerTiles is dangerous #3479?Nah, wait until the style/render split.