/// Environment storage: a scope-local map from ``EnvironmentKey`` type to the /// ``StateBox`` that holds that key's value for the scope. /// /// Boxes - not raw values - are what a scope stores, and that is the whole /// trick: every `@Environment` declaration in a subtree resolves to the *same* /// box, so a write from one declaration notifies the trackers registered by /// every other declaration. That reuses the existing ``StateBox`` / /// ``DependencyTracker`` machinery verbatim: no tree diff, no rebuild, just the /// same targeted widget writes that `@State` already performs. /// A type that identifies one environment slot and supplies its fallback value. /// /// Conform a caseless enum and add a computed property on ``EnvironmentValues`` /// so the slot is reachable through a key path. public protocol EnvironmentKey { /// The value stored in this slot. associatedtype Value /// The value seen by readers in scopes where nothing has been injected. static var defaultValue: Value { get } } /// Captures the ``StateBox`` touched by an ``EnvironmentValues`` subscript read. /// /// A key path such as `\.isFullscreen` names a *value*, not the box behind it. /// Resolution therefore reads the key path once with a probe installed; the /// subscript hands the probe the box it resolved, and the read result is /// discarded. @_spi(Portico) @MainActor public final class EnvironmentBoxProbe { /// The box handed over by the subscript read, or `nil` when the key path /// did not route through ``EnvironmentValues/subscript(_:)``. @_spi(Portico) public var captured: AnyObject? /// Creates an unfilled probe. @_spi(Portico) public init() {} } /// A scope's environment: the boxes it overrides, plus the objects it injects. /// /// Value semantics. Overriding a slot in a copy leaves every other scope /// untouched, which is what makes `.environment(_:_:)` subtree-scoped. @MainActor public struct EnvironmentValues { /// Installed by ``_box(for:)`` around a single key-path read. @_spi(Portico) public static var _probe: EnvironmentBoxProbe? /// Process-wide fallback boxes, one per key type, created on first demand. /// /// Two subtrees that never inject a value share this box, so an unprovided /// slot still behaves as one globally shared cell. private static var defaultBoxes: [ObjectIdentifier: AnyObject] = [:] /// Boxes this scope overrides, keyed by ``EnvironmentKey`` type. private var boxes: [ObjectIdentifier: AnyObject] = [:] /// Objects this scope injects, keyed by dynamic type. private var objects: [ObjectIdentifier: AnyObject] = [:] /// Creates an empty environment - every slot resolves to its key's default. public init() {} /// Reads or overrides the slot identified by `key`. /// /// The getter is a *non-tracking* read: `@Environment` reads through the /// resolved box instead, so that the tracking read happens at the point the /// view actually consumes the value. The setter installs a fresh box in /// this copy, shadowing whatever the parent scope resolved. public subscript(key: K.Type) -> K.Value { get { let box = resolvedBox(K.self) EnvironmentValues._probe?.captured = box return box.peek() } set { boxes[ObjectIdentifier(K.self)] = StateBox(newValue) } } /// Injects `object`, keyed by its dynamic type, for this scope's subtree. @_spi(Portico) public mutating func setObject(_ object: T) { objects[ObjectIdentifier(T.self)] = object } /// The object injected for `type` in this scope, or `nil`. @_spi(Portico) public func object(_ type: T.Type) -> T? { objects[ObjectIdentifier(T.self)] as? T } /// Resolves the box a key path names, without consuming its value. /// /// Returns `nil` only if `keyPath` does not route through /// ``subscript(_:)`` - a malformed environment accessor. @_spi(Portico) public func _box( for keyPath: KeyPath ) -> StateBox? { let probe = EnvironmentBoxProbe() let saved = EnvironmentValues._probe EnvironmentValues._probe = probe defer { EnvironmentValues._probe = saved } _ = self[keyPath: keyPath] return probe.captured as? StateBox } /// This scope's box for `key`, falling back to the shared default box. private func resolvedBox(_ key: K.Type) -> StateBox { if let box = boxes[ObjectIdentifier(K.self)] as? StateBox { return box } return EnvironmentValues.defaultBox(K.self) } /// The process-wide fallback box for `key`, created on first demand. private static func defaultBox(_ key: K.Type) -> StateBox { let id = ObjectIdentifier(K.self) if let box = defaultBoxes[id] as? StateBox { return box } let box = StateBox(K.defaultValue) defaultBoxes[id] = box return box } }