Skip to content

Commit

Permalink
Make root properties revertible for all styles (#2162)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjleonard37 authored Jun 7, 2024
1 parent 82a1186 commit 0d214c2
Show file tree
Hide file tree
Showing 10 changed files with 777 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Mapbox welcomes participation and contributions from everyone.

* Expose `clusterMinPoints` property for `GeoJSONSource` and for `ClusterOptions`

* Root properties (`Atmosphere`, `Lights`, `Projection`, `Terrain`, `Transition`) are now revertible for all styles.

## 11.4.0 - 22 May, 2024

* Live performance metrics collection. Mapbox Maps SDK v11.4.0 collects certain performance and feature usage counters so we can better benchmark the MapboxMaps library and invest in its performance. The performance counters have been carefully designed so that user-level metrics and identifiers are not collected.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import os

/// The style properties that can be applied once in the whole style.
/// If multiple properties are applied, the last wins.
struct MapContentUniqueProperties {
struct MapContentUniqueProperties: Decodable {
struct Lights: Encodable, Equatable {
var flat: FlatLight?
var directional: DirectionalLight?
Expand All @@ -17,48 +17,79 @@ struct MapContentUniqueProperties {
var location: LocationOptions?
var lights = Lights()

private func update<T: Equatable & Encodable>(_ label: String, old: T?, new: T?, setter: (Any) -> Expected<NSNull, NSString>) {
private func update<T: Equatable & Encodable>(_ label: String, old: T?, new: T?, initial: T?, setter: (Any) -> Expected<NSNull, NSString>) {
guard old != new else { return }
wrapStyleDSLError {
let value: Any
if let new {
os_log(.debug, log: .contentDSL, "set %s", label)
value = try new.toJSON()
} else {
os_log(.debug, log: .contentDSL, "remove %s", label)
value = NSNull()
os_log(.debug, log: .contentDSL, "set back to initial %s", label)
value = try initial.toJSON()
}
try handleExpected {
setter(value)
}
}
}

func update(from old: Self, style: StyleManagerProtocol, locationManager: LocationManager?) {
update("atmosphere", old: old.atmosphere, new: atmosphere, setter: style.setStyleAtmosphereForProperties(_:))
update("projection", old: old.projection, new: projection, setter: style.setStyleProjectionForProperties(_:))
update("terrain", old: old.terrain, new: terrain, setter: style.setStyleTerrainForProperties(_:))
func update(from old: Self, style: StyleManagerProtocol, initial: Self?, locationManager: LocationManager?) {
update("atmosphere", old: old.atmosphere, new: atmosphere, initial: initial?.atmosphere, setter: style.setStyleAtmosphereForProperties(_:))
update("projection", old: old.projection, new: projection, initial: initial?.projection, setter: style.setStyleProjectionForProperties(_:))
update("terrain", old: old.terrain, new: terrain, initial: initial?.terrain, setter: style.setStyleTerrainForProperties(_:))

lights.update(from: old.lights, style: style)
lights.update(from: old.lights, style: style, initialLights: initial?.lights)

if old.location != location {
locationManager?.options = location ?? LocationOptions()
}

if old.transition != transition {
wrapStyleDSLError {
if let transition {
style.setStyleTransitionFor(transition.coreOptions)
} else {
style.setStyleTransitionFor(TransitionOptions().coreOptions)
let transitionToSet: TransitionOptions = transition ?? initial?.transition ?? TransitionOptions()
style.setStyleTransitionFor(transitionToSet.coreOptions)
}
}
}
}

extension MapContentUniqueProperties {
enum CodingKeys: String, CodingKey {
case terrain
case atmosphere = "fog"
case projection
case lights = "lights"
}

/// Decode from a StyleJSON
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.terrain = try container.decodeIfPresent(Terrain.self, forKey: .terrain)
self.atmosphere = try container.decodeIfPresent(Atmosphere.self, forKey: .atmosphere)
self.projection = try container.decodeIfPresent(StyleProjection.self, forKey: .projection)
if var lightContainer = try? container.nestedUnkeyedContainer(forKey: .lights) {
while !lightContainer.isAtEnd {
var lightInfoContainer = lightContainer
let lightInfo = try lightInfoContainer.decode(LightInfo.self)

switch lightInfo.type {
case .ambient:
lights.ambient = try? lightContainer.decode(AmbientLight.self)
case .directional:
lights.directional = try? lightContainer.decode(DirectionalLight.self)
case .flat:
lights.flat = try? lightContainer.decode(FlatLight.self)
default:
Log.warning(forMessage: "Incorrect light configuration. Specify both directional and ambient lights OR flat light.", category: "StyleDSL")
}
}
}
}
}

private extension MapContentUniqueProperties.Lights {
func update(from old: Self, style: StyleManagerProtocol) {
func update(from old: Self, style: StyleManagerProtocol, initialLights: Self?) {
if self != old {
wrapStyleDSLError {
if let directional = directional, let ambient = ambient {
Expand All @@ -70,10 +101,24 @@ private extension MapContentUniqueProperties.Lights {
os_log(.debug, log: .contentDSL, "set flat light")
try style.setLights(flat)
} else {
os_log(.debug, log: .contentDSL, "remove lights")
try handleExpected { style.setStyleLightsForLights(NSNull()) }
os_log(.debug, log: .contentDSL, "set initial lights")
try setInitialLights(style: style, initialLights: initialLights)
}
}
}
}

func setInitialLights(style: StyleManagerProtocol, initialLights: Self?) throws {
if let initialDirectional = initialLights?.directional,
let initialAmbient = initialLights?.ambient {
os_log(.debug, log: .contentDSL, "re-set initial 3d lights")
try style.setLights(ambient: initialAmbient, directional: initialDirectional)
} else if let initialFlat = initialLights?.flat {
os_log(.debug, log: .contentDSL, "re-set initial flat lights")
try style.setLights(initialFlat)
} else {
os_log(.debug, log: .contentDSL, "remove lights")
try handleExpected { style.setStyleLightsForLights(NSNull()) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ final class MapContentNodeContext {

var lastImportId: String?
var initialStyleImports: [String] = []
var initialUniqueProperties: MapContentUniqueProperties?

var uniqueProperties = MapContentUniqueProperties()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private extension MapContentNodeContext {
uniqueProperties.update(
from: oldProperties,
style: style.styleManager,
initial: initialUniqueProperties,
locationManager: content?.location.value
)
}
Expand All @@ -85,21 +86,39 @@ private extension MapContentNodeContext {
lastLayerId = nil

/// On style reload we need to traverse the whole tree to reconstruct non-persistent layers
/// On style we need to identifty bottom position in the style in order to stack content above
/// On style reload we need to identify the bottom position in the style in order to stack content above
/// Position must take into account only non-persistent layers, which was not added in runtime
isEqualContent = { _, _ in false }
initialStyleLayers = getInitialStyleLayers() ?? []
initialStyleImports = style.styleManager.getStyleImports().map(\.id)
initialUniqueProperties = getInitialUniqueProperties()

mapContent.update(root)
isEqualContent = arePropertiesEqual

uniqueProperties.update(
from: MapContentUniqueProperties(),
style: style.styleManager,
initial: initialUniqueProperties,
locationManager: content?.location.value
)
}

func getInitialUniqueProperties() -> MapContentUniqueProperties? {
if let jsonData = style.styleManager.getStyleJSON().data(using: .utf8) {
do {
var initialMapUniqueProperties = try JSONDecoder().decode(MapContentUniqueProperties.self, from: jsonData)
// Transition options are not included in the StyleJSON
initialMapUniqueProperties.transition = TransitionOptions(style.styleManager.getStyleTransition())
return initialMapUniqueProperties
} catch {
Log.warning(forMessage: "Unable to decode initial MapContentUniqueProperties \(error) from StyleJSON", category: "StyleDSL")
return nil
}
}
return nil
}

func getInitialStyleLayers() -> [String]? {
try? style.styleManager
.getStyleLayers()
Expand Down
6 changes: 6 additions & 0 deletions Sources/MapboxMaps/Style/Generated/Terrain.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Sources/MapboxMaps/Style/LightInfo.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// Information about a light
public struct LightInfo {
public struct LightInfo: Decodable {
/// The identifier of the light
public var id: String

Expand Down
Loading

0 comments on commit 0d214c2

Please sign in to comment.