Skip to content

Commit

Permalink
Restructure ValueProviderStore to not accumulate multiple providers f…
Browse files Browse the repository at this point in the history
…or the same key
  • Loading branch information
adamastern committed Jun 21, 2023
1 parent 1f48b65 commit 9d40487
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 8 deletions.
19 changes: 12 additions & 7 deletions Sources/Private/CoreAnimation/ValueProviderStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ final class ValueProviderStore {
properties. Supported properties are: \(supportedProperties.joined(separator: ", ")).
""")

valueProviders.append((keypath: keypath, valueProvider: valueProvider))
orderedKeyPaths.remove(keypath)
orderedKeyPaths.insert(keypath, at: orderedKeyPaths.count)
valueProviders[keypath] = valueProvider
}

// Retrieves the custom value keyframes for the given property,
Expand Down Expand Up @@ -93,16 +95,19 @@ final class ValueProviderStore {
// MARK: Private

private let logger: LottieLogger
private var valueProviders = [AnimationKeypath: AnyValueProvider]()
private var orderedKeyPaths = NSMutableOrderedSet()

private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]()

/// Retrieves the most-recently-registered Value Provider that matches the given keypat
/// Retrieves the most-recently-registered Value Provider that matches the given keypath.
private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? {
// Find the last keypath matching the given keypath,
// so we return the value provider that was registered most-recently
valueProviders.last(where: { registeredKeypath, _ in
keypath.matches(registeredKeypath)
})?.valueProvider
guard
let matchingPath = orderedKeyPaths.reversed.first(where: { element in
guard let registeredKeypath = (element as? AnimationKeypath) else { return false }
return keypath.matches(registeredKeypath)
}) as? AnimationKeypath else { return nil }
return valueProviders[matchingPath]
}

}
Expand Down
1 change: 1 addition & 0 deletions Tests/SnapshotConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,6 @@ extension SnapshotConfiguration {
extension LottieColor {
static let black = LottieColor(r: 0, g: 0, b: 0, a: 1)
static let red = LottieColor(r: 1, g: 0, b: 0, a: 1)
static let blue = LottieColor(r: 0, g: 0, b: 1, a: 1)
}
#endif
51 changes: 50 additions & 1 deletion Tests/ValueProvidersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Created by Marcelo Fabri on 5/5/22.
//

import Lottie
import XCTest
@testable import Lottie

@MainActor
final class ValueProvidersTests: XCTestCase {
Expand All @@ -27,4 +27,53 @@ final class ValueProvidersTests: XCTestCase {
XCTAssertEqual(originalColor, LottieColor(r: 0.4, g: 0.16, b: 0.7, a: 1))
}

func testValueProviderStore() async throws {
let optionalAnimationView = await SnapshotConfiguration.makeAnimationView(
for: "HamburgerArrow",
configuration: .init(renderingEngine: .mainThread))
let animation = try XCTUnwrap(optionalAnimationView?.animation)

let store = ValueProviderStore(logger: .printToConsole)
let animationContext = LayerAnimationContext(
animation: animation,
timingConfiguration: .init(),
startFrame: 0,
endFrame: 100,
valueProviderStore: store,
compatibilityTracker: .init(mode: .track, logger: .printToConsole),
logger: .printToConsole,
currentKeypath: .init(keys: []),
textProvider: DictionaryTextProvider([:]))

// Test that the store returns the expected value for the provider.
store.setValueProvider(ColorValueProvider(.red), keypath: "**.Color")
let keyFramesQuery1 = try store.customKeyframes(
of: .color,
for: "Layer.Shape Group.Stroke 1.Color",
context: animationContext)
XCTAssertEqual(keyFramesQuery1?.keyframes.map(\.value.components), [[1, 0, 0, 1]])

// Test a different provider/keypath.
store.setValueProvider(ColorValueProvider(.blue), keypath: "A1.Shape 1.Stroke 1.Color")
let keyFramesQuery2 = try store.customKeyframes(
of: .color,
for: "A1.Shape 1.Stroke 1.Color",
context: animationContext)
XCTAssertEqual(keyFramesQuery2?.keyframes.map(\.value.components), [[0, 0, 1, 1]])

// Test that adding a different keypath didn't disrupt the original one.
let keyFramesQuery3 = try store.customKeyframes(
of: .color,
for: "Layer.Shape Group.Stroke 1.Color",
context: animationContext)
XCTAssertEqual(keyFramesQuery3?.keyframes.map(\.value.components), [[1, 0, 0, 1]])

// Test overriding the original keypath with a new provider stores the new provider.
store.setValueProvider(ColorValueProvider(.black), keypath: "**.Color")
let keyFramesQuery4 = try store.customKeyframes(
of: .color,
for: "Layer.Shape Group.Stroke 1.Color",
context: animationContext)
XCTAssertEqual(keyFramesQuery4?.keyframes.map(\.value.components), [[0, 0, 0, 1]])
}
}

0 comments on commit 9d40487

Please sign in to comment.