mirror of
https://github.com/phranck/TUIkit.git
synced 2026-05-21 09:50:35 +00:00
Refactor: Extract shared hydration context setup into StateRegistration.withHydration
- Add StateRegistration.withHydration(context:_:) helper - Replace duplicated save/set/restore pattern in renderToBuffer and measureChild - Single source of truth for hydration context management
This commit is contained in:
@@ -187,29 +187,10 @@ public func measureChild<V: View>(_ view: V, proposal: ProposedSize, context: Re
|
||||
// Skip Renderable views: their rendering logic (including environment
|
||||
// injection) lives in renderToBuffer, not in body. They fall through
|
||||
// to the render-to-measure fallback below, which runs the full pipeline.
|
||||
//
|
||||
// Must set up hydration context before evaluating body, just like
|
||||
// renderToBuffer does. Otherwise @Environment(T.self) lookups and
|
||||
// @State self-hydration crash because activeEnvironment/activeContext
|
||||
// are nil.
|
||||
if !(view is Renderable), V.Body.self != Never.self {
|
||||
let previousContext = StateRegistration.activeContext
|
||||
let previousCounter = StateRegistration.counter
|
||||
let previousEnvironment = StateRegistration.activeEnvironment
|
||||
|
||||
StateRegistration.activeContext = HydrationContext(
|
||||
identity: context.identity,
|
||||
storage: context.environment.stateStorage!
|
||||
)
|
||||
StateRegistration.counter = 0
|
||||
StateRegistration.activeEnvironment = context.environment
|
||||
|
||||
let body = view.body
|
||||
|
||||
StateRegistration.activeContext = previousContext
|
||||
StateRegistration.counter = previousCounter
|
||||
StateRegistration.activeEnvironment = previousEnvironment
|
||||
|
||||
let body = StateRegistration.withHydration(context: context) {
|
||||
view.body
|
||||
}
|
||||
return measureChild(body, proposal: proposal, context: context)
|
||||
}
|
||||
|
||||
|
||||
@@ -177,32 +177,16 @@ public func renderToBuffer<V: View>(_ view: V, context: RenderContext) -> FrameB
|
||||
if V.Body.self != Never.self {
|
||||
let childContext = context.withChildIdentity(type: V.Body.self)
|
||||
|
||||
// Save previous hydration state (supports nested composite views).
|
||||
let previousContext = StateRegistration.activeContext
|
||||
let previousCounter = StateRegistration.counter
|
||||
let previousEnvironment = StateRegistration.activeEnvironment
|
||||
|
||||
// Activate hydration: @State.init will use this to look up persistent storage.
|
||||
// Activate environment: @Environment reads from activeEnvironment during body.
|
||||
StateRegistration.activeContext = HydrationContext(
|
||||
identity: context.identity,
|
||||
storage: context.environment.stateStorage!
|
||||
)
|
||||
StateRegistration.counter = 0
|
||||
StateRegistration.activeEnvironment = context.environment
|
||||
|
||||
// Wrap body evaluation in observation tracking so that any @Observable
|
||||
// property accessed during body triggers a re-render when mutated.
|
||||
let body = withObservationTracking {
|
||||
view.body
|
||||
} onChange: {
|
||||
AppState.shared.setNeedsRenderWithCacheClear()
|
||||
let body = StateRegistration.withHydration(context: context) {
|
||||
withObservationTracking {
|
||||
view.body
|
||||
} onChange: {
|
||||
AppState.shared.setNeedsRenderWithCacheClear()
|
||||
}
|
||||
}
|
||||
|
||||
// Restore previous hydration state and mark this identity as active for GC.
|
||||
StateRegistration.activeContext = previousContext
|
||||
StateRegistration.counter = previousCounter
|
||||
StateRegistration.activeEnvironment = previousEnvironment
|
||||
context.environment.stateStorage!.markActive(context.identity)
|
||||
|
||||
return renderToBuffer(body, context: childContext)
|
||||
|
||||
@@ -176,6 +176,40 @@ public enum StateRegistration {
|
||||
/// Used by `@Environment` to read environment values during `body` evaluation.
|
||||
/// Set alongside ``activeContext`` in `renderToBuffer(_:context:)`.
|
||||
nonisolated(unsafe) public static var activeEnvironment: EnvironmentValues?
|
||||
/// Evaluates a closure with a hydration context active.
|
||||
///
|
||||
/// Sets up `activeContext`, `counter`, and `activeEnvironment` before
|
||||
/// calling the closure, then restores the previous state. This pattern
|
||||
/// is needed whenever `view.body` is evaluated outside the normal
|
||||
/// `renderToBuffer` dispatch (e.g., in `measureChild`).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - context: The render context providing identity and environment.
|
||||
/// - block: The closure to execute with hydration active.
|
||||
/// - Returns: The result of the closure.
|
||||
public static func withHydration<R>(
|
||||
context: RenderContext,
|
||||
_ block: () -> R
|
||||
) -> R {
|
||||
let previousContext = activeContext
|
||||
let previousCounter = counter
|
||||
let previousEnvironment = activeEnvironment
|
||||
|
||||
activeContext = HydrationContext(
|
||||
identity: context.identity,
|
||||
storage: context.environment.stateStorage!
|
||||
)
|
||||
counter = 0
|
||||
activeEnvironment = context.environment
|
||||
|
||||
let result = block()
|
||||
|
||||
activeContext = previousContext
|
||||
counter = previousCounter
|
||||
activeEnvironment = previousEnvironment
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Binding
|
||||
|
||||
Reference in New Issue
Block a user