Skip to content

Commit

Permalink
Add internal API docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jfirebaugh committed Nov 15, 2017
1 parent 0ed1c7a commit d20c81b
Showing 1 changed file with 203 additions and 8 deletions.
211 changes: 203 additions & 8 deletions src/style/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,72 @@ import type {

type TimePoint = number;

type TransitionParameters = {
now: TimePoint,
transition: TransitionSpecification
};

export type EvaluationParameters = GlobalProperties & {
now?: TimePoint,
defaultFadeDuration?: number,
zoomHistory?: ZoomHistory
};

/**
* Implements a number of classes that define state and behavior for paint and layout properties, most
* importantly their respective evaluation chains:
*
* Transitionable paint property value
* → Transitioning paint property value
* → Possibly evaluated paint property value
* → Fully evaluated paint property value
*
* Layout property value
* → Possibly evaluated layout property value
* → Fully evaluated layout property value
*
* @module
* @private
*/

/**
* Implementations of the `Property` interface:
*
* * Hold metadata about a property that's independent of any specific value: stuff like the type of the value,
* the default value, etc. This comes from the style specification JSON.
* * Define behavior that needs to be polymorphic across different properties: "possibly evaluating"
* an input value (see below), and interpolating between two possibly-evaluted values.
*
* The type `T` is the fully-evaluated value type (e.g. `number`, `string`, `Color`).
* The type `R` is the intermediate "possibly evaluated" value type. See below.
*
* There are two main implementations of the interface -- one for properties that allow data-driven values,
* and one for properties that don't. There are a few "special case" implementations as well: one for properties
* which cross-fade between two values rather than interpolating, one for `heatmap-color`, and one for
* `light-position`.
*
* @private
*/
export interface Property<T, R> {
specification: StylePropertySpecification;
possiblyEvaluate(value: PropertyValue<T, R>, parameters: EvaluationParameters): R;
interpolate(a: R, b: R, t: number): R;
}

/**
* `PropertyValue` represents the value part of a property key-value unit. It's used to represent both
* paint and layout property values, and regardless of whether or not their property supports data-driven
* expressions.
*
* `PropertyValue` stores the raw input value as seen in a style or a runtime styling API call, i.e. one of the
* following:
*
* * A constant value of the type appropriate for the property
* * A function which produces a value of that type (but functions are quasi-deprecated in favor of expressions)
* * An expression which produces a value of that type
* * "undefined"/"not present", in which case the property is assumed to take on its default value.
*
* In addition to storing the original input value, `PropertyValue` also stores a normalized representation,
* effectively treating functions as if they are expressions, and constant or default values as if they are
* (constant) expressions.
*
* @private
*/
class PropertyValue<T, R> {
property: Property<T, R>;
value: PropertyValueSpecification<T> | void;
Expand All @@ -60,6 +109,23 @@ class PropertyValue<T, R> {

// ------- Transitionable -------

type TransitionParameters = {
now: TimePoint,
transition: TransitionSpecification
};

/**
* Paint properties are _transitionable_: they can change in a fluid manner, interpolating or cross-fading between
* old and new value. The duration of the transition, and the delay before it begins, is configurable.
*
* `TransitionablePropertyValue` is a compositional class that stores both the property value and that transition
* configuration.
*
* A `TransitionablePropertyValue` can calculate the next step in the evaluation chain for paint property values:
* `TransitioningPropertyValue`.
*
* @private
*/
class TransitionablePropertyValue<T, R> {
property: Property<T, R>;
value: PropertyValue<T, R>;
Expand All @@ -72,17 +138,31 @@ class TransitionablePropertyValue<T, R> {

transitioned(parameters: TransitionParameters,
prior: TransitioningPropertyValue<T, R>): TransitioningPropertyValue<T, R> {
return new TransitioningPropertyValue(this.property, this.value, prior, extend({}, this.transition, parameters.transition), parameters.now); // eslint-disable-line no-use-before-define
return new TransitioningPropertyValue(this.property, this.value, prior, // eslint-disable-line no-use-before-define
extend({}, this.transition, parameters.transition), parameters.now);
}

untransitioned(): TransitioningPropertyValue<T, R> {
return new TransitioningPropertyValue(this.property, this.value, null, {}, 0); // eslint-disable-line no-use-before-define
}
}

/**
* A helper type: given an object type `Properties` whose values are each of type `Property<T, R>`, it calculates
* an object type with the same keys and values of type `TransitionablePropertyValue<T, R>`.
*
* @private
*/
type TransitionablePropertyValues<Properties: Object>
= $Exact<$ObjMap<Properties, <T, R>(p: Property<T, R>) => TransitionablePropertyValue<T, R>>>

/**
* `Transitionable` stores a map of all (property name, `TransitionablePropertyValue`) pairs for paint properties of a
* given layer type. It can calculate the `TransitioningPropertyValue`s for all of them at once, producing a
* `Transitioning` instance for the same set of properties.
*
* @private
*/
class Transitionable<Properties: Object> {
_values: TransitionablePropertyValues<Properties>;

Expand All @@ -93,11 +173,11 @@ class Transitionable<Properties: Object> {
}
}

getValue<S: string>(name: S) {
getValue<S: string, T>(name: S): PropertyValueSpecification<T> | void {
return this._values[name].value.value;
}

setValue<S: string>(name: S, value: *) {
setValue<S: string, T>(name: S, value: PropertyValueSpecification<T> | void) {
this._values[name].value = new PropertyValue(this._values[name].property, value === null ? undefined : value);
}

Expand Down Expand Up @@ -144,6 +224,15 @@ class Transitionable<Properties: Object> {

// ------- Transitioning -------

/**
* `TransitioningPropertyValue` implements the first of two intermediate steps in the evaluation chain of a paint
* property value. In this step, transitions between old and new values are handled: as long as the transition is in
* progress, `TransitioningPropertyValue` maintains a reference to the prior value, and interpolates between it and
* the new value based on the current time and the configured transition duration and delay. The product is the next
* step in the evaluation chain: the "possibly evaluated" result type `R`. See below for more on this concept.
*
* @private
*/
class TransitioningPropertyValue<T, R> {
property: Property<T, R>;
value: PropertyValue<T, R>;
Expand Down Expand Up @@ -193,9 +282,22 @@ class TransitioningPropertyValue<T, R> {
}
}

/**
* A helper type: given an object type `Properties` whose values are each of type `Property<T, R>`, it calculates
* an object type with the same keys and values of type `TransitioningPropertyValue<T, R>`.
*
* @private
*/
type TransitioningPropertyValues<Properties: Object>
= $Exact<$ObjMap<Properties, <T, R>(p: Property<T, R>) => TransitioningPropertyValue<T, R>>>

/**
* `Transitioning` stores a map of all (property name, `TransitioningPropertyValue`) pairs for paint properties of a
* given layer type. It can calculate the possibly-evaluated values for all of them at once, producing a
* `PossiblyEvaluated` instance for the same set of properties.
*
* @private
*/
class Transitioning<Properties: Object> {
_values: TransitioningPropertyValues<Properties>;

Expand Down Expand Up @@ -223,9 +325,26 @@ class Transitioning<Properties: Object> {

// ------- Layout -------

/**
* A helper type: given an object type `Properties` whose values are each of type `Property<T, R>`, it calculates
* an object type with the same keys and values of type `PropertyValue<T, R>`.
*
* @private
*/
type LayoutPropertyValues<Properties: Object>
= $Exact<$ObjMap<Properties, <T, R>(p: Property<T, R>) => PropertyValue<T, R>>>

/**
* Because layout properties are not transitionable, they have a simpler representation and evaluation chain than
* paint properties: `PropertyValue`s are possibly evaluated, producing possibly evaluated values, which are then
* fully evaluated.
*
* `Layout` stores a map of all (property name, `PropertyValue`) pairs for layout properties of a
* given layer type. It can calculate the possibly-evaluated values for all of them at once, producing a
* `PossiblyEvaluated` instance for the same set of properties.
*
* @private
*/
class Layout<Properties: Object> {
_values: LayoutPropertyValues<Properties>;

Expand Down Expand Up @@ -266,11 +385,40 @@ class Layout<Properties: Object> {

// ------- PossiblyEvaluated -------

/**
* "Possibly evaluated value" is an intermediate stage in the evaluation chain for both paint and layout property
* values. The purpose of this stage is to optimize away unnecessary recalculations for data-driven properties. Code
* which uses data-driven property values must assume that the value is dependent on feature data, and request that it
* be evaluated for each feature. But when that property value is in fact a constant or camera function, the calculation
* will not actually depend on the feature, and we can benefit from returning the prior result of having done the
* evaluation once, ahead of time, in an intermediate step whose inputs are just the value and "global" parameters
* such as current zoom level.
*
* `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
* the source and composite cases because they are handled differently when generating GL attributes, buffers, and
* uniforms.
*
* Note that `PossiblyEvaluatedValue` (and `PossiblyEvaluatedPropertyValue`, below) are _not_ used for properties that
* do not allow data-driven values. For such properties, we know that the "possibly evaluated" result is always a constant
* scalar value. See below.
*
* @private
*/
export type PossiblyEvaluatedValue<T> =
| {kind: 'constant', value: T}
| SourceExpression
| CompositeExpression;

/**
* `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.
*
* @private
*/
class PossiblyEvaluatedPropertyValue<T> {
property: DataDrivenProperty<T>;
value: PossiblyEvaluatedValue<T>;
Expand Down Expand Up @@ -299,9 +447,30 @@ class PossiblyEvaluatedPropertyValue<T> {
}
}

/**
* A helper type: given an object type `Properties` whose values are each of type `Property<T, R>`, it calculates
* an object type with the same keys, and values of type `R`.
*
* For properties that don't allow data-driven values, `R` is a scalar type such as `number`, `string`, or `Color`.
* For data-driven properties, it is `PossiblyEvaluatedPropertyValue`. Critically, the type definitions are set up
* in a way that allows flow to know which of these two cases applies for any given property name, and if you attempt
* to use a `PossiblyEvaluatedPropertyValue` as if it was a scalar, or vice versa, you will get a type error. (However,
* there's at least one case in which flow fails to produce a type error that you should be aware of: in a context such
* as `layer.paint.get('foo-opacity') === 0`, if `foo-opacity` is data-driven, than the left-hand side is of type
* `PossiblyEvaluatedPropertyValue<number>`, but flow will not complain about comparing this to a number using `===`.
* See https://github.com/facebook/flow/issues/2359.)
*
* There's also a third, special case possiblity for `R`: for cross-faded properties, it's `?CrossFaded<T>`.
*
* @private
*/
type PossiblyEvaluatedPropertyValues<Properties: Object>
= $Exact<$ObjMap<Properties, <T, R>(p: Property<T, R>) => R>>

/**
* `PossiblyEvaluated` stores a map of all (property name, `R`) pairs for paint or layout properties of a
* given layer type.
*/
class PossiblyEvaluated<Properties: Object> {
_values: PossiblyEvaluatedPropertyValues<Properties>;

Expand All @@ -314,6 +483,13 @@ class PossiblyEvaluated<Properties: Object> {
}
}

/**
* An implementation of `Property` for properties that do not permit data-driven (source or composite) expressions.
* This restriction allows us to declare statically that the result of possibly evaluating this kind of property
* is in fact always the scalar type `T`, and can be used without further evaluating the value on a per-feature basis.
*
* @private
*/
class DataConstantProperty<T> implements Property<T, T> {
specification: StylePropertySpecification;

Expand All @@ -336,6 +512,13 @@ class DataConstantProperty<T> implements Property<T, T> {
}
}

/**
* An implementation of `Property` for properties that permit data-driven (source or composite) expressions.
* The result of possibly evaluating this kind of property is `PossiblyEvaluatedPropertyValue<T>`; obtaining
* a scalar value `T` requires further evaluation on a per-feature basis.
*
* @private
*/
class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPropertyValue<T>> {
specification: StylePropertySpecification;
useIntegerZoom: boolean;
Expand All @@ -359,6 +542,7 @@ class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPropertyValu
interpolate(a: PossiblyEvaluatedPropertyValue<T>,
b: PossiblyEvaluatedPropertyValue<T>,
t: number): PossiblyEvaluatedPropertyValue<T> {
// If either possibly-evaluated value is non-constant, give up: we aren't able to interpolate data-driven values.
if (a.value.kind !== 'constant' || b.value.kind !== 'constant') {
return a;
}
Expand Down Expand Up @@ -387,6 +571,12 @@ class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPropertyValu
}
}

/**
* An implementation of `Property` for `*-pattern` and `line-dasharray`, which are transitioned by cross-fading
* rather than interpolation.
*
* @private
*/
class CrossFadedProperty<T> implements Property<T, ?CrossFaded<T>> {
specification: StylePropertySpecification;

Expand Down Expand Up @@ -425,6 +615,11 @@ class CrossFadedProperty<T> implements Property<T, ?CrossFaded<T>> {
}
}

/**
* An implementation of `Property` for `heatmap-color`, which has unique evaluation requirements.
*
* @private
*/
class HeatmapColorProperty implements Property<Color, RGBAImage> {
specification: StylePropertySpecification;

Expand Down

0 comments on commit d20c81b

Please sign in to comment.