-
Notifications
You must be signed in to change notification settings - Fork 49
Utilize cache infra #587
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
base: maxg/cache_2_envcache
Are you sure you want to change the base?
Utilize cache infra #587
Changes from 49 commits
3abebaf
9b87e2a
6802a22
6429e56
76aee10
44aa682
10d11e9
ee32e1c
23a1dee
dad612b
7c6b2dc
e46e778
5faf9e1
5ef770e
6dafc50
56b751d
4a126e7
499ee96
15d0c5d
0d413d3
94e9b23
1c45f7f
9afcdd1
42104f9
398dc40
22ed032
be9dd21
942c1fa
6519418
e8c27c0
872bcaa
afbbc3c
f5ea2eb
f4c5c2e
c2e2ddc
9c0da9f
1842de6
0f19b47
707cc8d
3ec654b
76cac01
ea63760
f35c210
a599c4a
993355b
094dbcd
33979ef
1f9b9be
c8ab1d0
7dc49fc
6e8ffbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,8 @@ public final class BlueprintView: UIView { | |
|
|
||
| private var sizesThatFit: [SizeConstraint: CGSize] = [:] | ||
|
|
||
| private var cacheStorage = Environment.CacheStorageEnvironmentKey.defaultValue | ||
|
|
||
| /// A base environment used when laying out and rendering the element tree. | ||
| /// | ||
| /// Some keys will be overridden with the traits from the view itself. Eg, `windowSize`, `safeAreaInsets`, etc. | ||
|
|
@@ -52,6 +54,13 @@ public final class BlueprintView: UIView { | |
| didSet { | ||
| // Shortcut: If both environments were empty, nothing changed. | ||
| if oldValue.isEmpty && environment.isEmpty { return } | ||
| // Shortcut: If there are no changes to the environment, then, well, nothing changed. | ||
| if let layoutMode, layoutMode.options.skipUnneededSetNeedsViewHierarchyUpdates && oldValue.isEquivalent( | ||
| to: environment, | ||
| in: .all | ||
| ) { | ||
| return | ||
| } | ||
|
|
||
| setNeedsViewHierarchyUpdate() | ||
| } | ||
|
|
@@ -86,6 +95,13 @@ public final class BlueprintView: UIView { | |
| if oldValue == nil && element == nil { | ||
| return | ||
| } | ||
| if let layoutMode, layoutMode.options.skipUnneededSetNeedsViewHierarchyUpdates, let contextuallyEquivalent = element as? ContextuallyEquivalent, contextuallyEquivalent.isEquivalent( | ||
| to: oldValue as? ContextuallyEquivalent, | ||
| in: .all | ||
| ) { | ||
| return | ||
| } | ||
| cacheStorage = Environment.CacheStorageEnvironmentKey.defaultValue | ||
|
|
||
| Logger.logElementAssigned(view: self) | ||
|
|
||
|
|
@@ -148,6 +164,7 @@ public final class BlueprintView: UIView { | |
|
|
||
| self.element = element | ||
| self.environment = environment | ||
| self.environment.cacheStorage = cacheStorage | ||
|
||
|
|
||
| rootController = NativeViewController( | ||
| node: NativeViewNode( | ||
|
|
@@ -542,9 +559,13 @@ public final class BlueprintView: UIView { | |
| environment.layoutMode = layoutMode | ||
| } | ||
|
|
||
| environment.cacheStorage = cacheStorage | ||
|
|
||
| return environment | ||
| } | ||
|
|
||
|
|
||
|
|
||
| private func handleAppeared() { | ||
| rootController.traverse { node in | ||
| node.onAppear?() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -249,6 +249,17 @@ extension ElementContent { | |
| storage = MeasurableStorage(measurer: measureFunction) | ||
| } | ||
|
|
||
| /// Initializes a new `ElementContent` with no children that delegates to the provided measure function. | ||
| /// | ||
| /// - parameter validationKey: If present, measureFunction will attempt to cache sizing based on the path of the node. validationKey will be evaluated to ensure that the result is valid. | ||
| /// - parameter measureFunction: How to measure the `ElementContent` in the given `SizeConstraint` and `Environment`. | ||
| public init( | ||
| validationKey: some ContextuallyEquivalent, | ||
|
||
| measureFunction: @escaping (SizeConstraint, Environment) -> CGSize | ||
| ) { | ||
| storage = MeasurableStorage(validationKey: validationKey, measurer: measureFunction) | ||
| } | ||
|
|
||
| /// Initializes a new `ElementContent` with no children that uses the provided intrinsic size for measuring. | ||
| public init(intrinsicSize: CGSize) { | ||
| self = ElementContent(measureFunction: { _ in intrinsicSize }) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,18 @@ struct MeasurableStorage: ContentStorage { | |
|
|
||
| let childCount = 0 | ||
|
|
||
| let validationKey: AnyContextuallyEquivalent? | ||
| let measurer: (SizeConstraint, Environment) -> CGSize | ||
|
|
||
| init(validationKey: some ContextuallyEquivalent, measurer: @escaping (SizeConstraint, Environment) -> CGSize) { | ||
| self.validationKey = AnyContextuallyEquivalent(validationKey) | ||
| self.measurer = measurer | ||
| } | ||
|
|
||
| init(measurer: @escaping (SizeConstraint, Environment) -> CGSize) { | ||
| validationKey = nil | ||
| self.measurer = measurer | ||
| } | ||
| } | ||
|
|
||
| extension MeasurableStorage: CaffeinatedContentStorage { | ||
|
|
@@ -17,7 +28,19 @@ extension MeasurableStorage: CaffeinatedContentStorage { | |
| environment: Environment, | ||
| node: LayoutTreeNode | ||
| ) -> CGSize { | ||
| measurer(proposal, environment) | ||
| guard environment.layoutMode.options.measureableStorageCache, let validationKey else { | ||
| return measurer(proposal, environment) | ||
| } | ||
|
|
||
| let key = MeasurableSizeKey(path: node.path, max: proposal.maximum) | ||
|
||
| return environment.cacheStorage.measurableStorageCache.retrieveOrCreate( | ||
| key: key, | ||
| environment: environment, | ||
| validationValue: validationKey, | ||
| context: .elementSizing, | ||
| ) { environment in | ||
| measurer(proposal, environment) | ||
| } | ||
| } | ||
|
|
||
| func performCaffeinatedLayout( | ||
|
|
@@ -28,3 +51,40 @@ extension MeasurableStorage: CaffeinatedContentStorage { | |
| [] | ||
| } | ||
| } | ||
|
|
||
| extension MeasurableStorage { | ||
|
|
||
| fileprivate struct MeasurableSizeKey: Hashable { | ||
|
|
||
| let path: String | ||
| let max: CGSize | ||
|
|
||
| func hash(into hasher: inout Hasher) { | ||
| path.hash(into: &hasher) | ||
| max.hash(into: &hasher) | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
|
|
||
| extension CacheStorage { | ||
|
|
||
| private struct MeasurableStorageCacheKey: CacheStorage.Key { | ||
| static var emptyValue = EnvironmentAndValueValidatingCache< | ||
| MeasurableStorage.MeasurableSizeKey, | ||
| CGSize, | ||
| AnyContextuallyEquivalent | ||
| >() | ||
| } | ||
|
|
||
| fileprivate var measurableStorageCache: EnvironmentAndValueValidatingCache< | ||
| MeasurableStorage.MeasurableSizeKey, | ||
| CGSize, | ||
| AnyContextuallyEquivalent | ||
| > { | ||
| get { self[MeasurableStorageCacheKey.self] } | ||
| set { self[MeasurableStorageCacheKey.self] = newValue } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // | ||
| // EnvironmentEntangledCacheTests.swift | ||
| // Development | ||
| // | ||
| // Created by Max Goedjen on 7/23/25. | ||
| // | ||
|
|
||
|
||
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.
self.layoutModeis just one customization point — you should probably use something like:which I think will match the actual mode used during layout.