class WindowScope
Swift
final class WindowScope
The per-window half of LavaUI's framework state.
ViewInvalidation, FocusManager, NodeVisibility and the caret blink
were written as process-wide statics under an explicit single-window
assumption. With two windows in one process each of them is a bug: window A
consuming the invalidation level swallows window B's redraw, a key event
delivered to B reaches the field focused in A, and A's emitTree clears the
visibility set that B's animations are gated on.
The fix keeps every one of those APIs exactly where it was — forty-odd call
sites across widgets say ViewInvalidation.markNeedsRedraw() with no window
in scope, and threading one through all of them would be both invasive and
wrong, since a widget genuinely does not know which window it is in. Instead
the storage moves here, and the statics resolve to a scope:
- Ambient. The frame loop marks a window current around everything it does for that window — input, body, layout, emit. A widget invalidating from a click handler or a body therefore lands in the right window without ever naming it.
- By node.
markBodyDirty(node)cannot use the ambient scope: an@Observablemodel mutated by a handler in window A can be observed by a node in window B, and B is the one that has to recompute.CompositeNodecaptures its scope at mount, when the ambient one is correct by construction, and routes there for the rest of its life. - Broadcast. Outside any window phase —
MainQueueresults, deferredFrameTasks— there is no answer to "which window", so a coarse invalidation goes to all of them. Wasteful in the case where only one window cared, but never wrong, which is the correct trade for the path that runs once per worker result rather than once per frame.
Single-window behaviour is unchanged: everything resolves to .main.
What deliberately stays global is anything keyed by NodeID, because those
ids are unique process-wide and so cannot collide between windows —
ScrollRouter, DropRouter, PointerCapture, AnimationDriver's
registry. So is anything the loop owns rather than a window:
FrameScheduler (one loop, one sleep, earliest wake wins), FrameTasks,
MainQueue. And so are the resource caches — FontStore, ImageStore,
Theme — which is the whole reason a second window is cheap.
Not thread-safe, and not meant to be: it is read and written by the frame
loop only. That is exactly what MainQueue exists to preserve.
Sources/LavaUI/WindowScope.swift:45
Constructors
init(label: String, windowID: WindowID = .main)
Not documented.
Sources/LavaUI/WindowScope.swift:57
Properties
nonisolated(unsafe) static var current: WindowScope? { get }
The window whose frame is being processed, or nil between windows.
Sources/LavaUI/WindowScope.swift:69
static var currentOrMain: WindowScope { get }
Resolves the scope a static with no window in scope should act on.
Sources/LavaUI/WindowScope.swift:72
let label: String
For diagnostics only — the window title, typically.
Sources/LavaUI/WindowScope.swift:47
nonisolated(unsafe) static let main: WindowScope
The scope every static resolves to when nothing is current: tests,
LavaBench, and the first window of any app.
Sources/LavaUI/WindowScope.swift:64
let windowID: WindowID
The engine window this scope drives.
Here rather than only on LavaWindow so a view can ask which window it
is running in (LavaApp.currentWindow) without the framework threading
an id through every body — the same ambient answer, from the same
place, as the rest of this type.
Sources/LavaUI/WindowScope.swift:55
Methods
static func register(_ scope: WindowScope)
Registered by the frame loop as each window opens. main is not
implicitly a member — a single-window app that never calls this still
works, because broadcast falls back to main.
Sources/LavaUI/WindowScope.swift:93
static func unregister(_ scope: WindowScope)
Not documented.
Sources/LavaUI/WindowScope.swift:98
@discardableResult static func withCurrent<T>(_ scope: WindowScope, _ body: () throws -> T) rethrows -> T
Runs body with scope current, restoring whatever was current
before — nested is fine, and an early return cannot leak the scope.
Sources/LavaUI/WindowScope.swift:77