portico/Sources/Portico/Core/NodeRegistry.swift

78 lines
3.5 KiB
Swift

import Adw
import GObject
import Gtk
/// Collects the reactive resources created while mounting one subtree so
/// they can be released together when that subtree unmounts.
///
/// Holds subscription tokens, signal handles, and dependency trackers, plus
/// child registries for independently-unmountable sub-subtrees (e.g. each
/// `ForEach` row). `teardown()` cancels tokens, disconnects handles, tears
/// down trackers, and recurses into children.
@_spi(Portico) @MainActor public final class NodeRegistry {
private var tokens: [SubscriptionToken] = []
private var gtkHandles: [Gtk.SignalHandle] = []
private var adwHandles: [Adw.SignalHandle] = []
private var gobjectHandles: [GObject.SignalHandle] = []
private var trackers: [DependencyTracker] = []
private var children: [NodeRegistry] = []
private var owners: [AnyObject] = []
@_spi(Portico) public init() {}
/// Registers a subscription token to cancel on teardown.
@_spi(Portico) public func add(_ token: SubscriptionToken) { tokens.append(token) }
/// Registers a Gtk signal handle to disconnect on teardown.
@_spi(Portico) public func add(_ handle: Gtk.SignalHandle) { gtkHandles.append(handle) }
/// Registers an Adw signal handle to disconnect on teardown.
@_spi(Portico) public func add(_ handle: Adw.SignalHandle) { adwHandles.append(handle) }
/// Registers a GObject signal handle to disconnect on teardown.
@_spi(Portico) public func add(_ handle: GObject.SignalHandle) { gobjectHandles.append(handle) }
/// Registers a dependency tracker to detach from its state boxes on teardown.
@_spi(Portico) public func add(_ tracker: DependencyTracker) { trackers.append(tracker) }
/// Registers a child registry for an independently-unmountable sub-subtree.
@_spi(Portico) public func addChild(_ child: NodeRegistry) { children.append(child) }
/// Retains a stateful mount owner until this registry tears down.
@_spi(Portico) public func addOwner(_ owner: AnyObject) { owners.append(owner) }
/// `true` when nothing has registered here and no child registry exists.
///
/// Lets a caller check that a mount left no reactive residue - the
/// "a static value costs nothing after mount" property.
@_spi(Portico) public var isEmpty: Bool {
tokens.isEmpty && gtkHandles.isEmpty && adwHandles.isEmpty
&& gobjectHandles.isEmpty && trackers.isEmpty && children.isEmpty
&& owners.isEmpty
}
/// Removes a child registry (called after the child has been torn down,
/// e.g. a removed `ForEach` row) so this registry no longer retains it.
@_spi(Portico) public func removeChild(_ child: NodeRegistry) {
children.removeAll { $0 === child }
}
/// Releases every registered resource and recurses into child registries.
/// Safe to call more than once (token cancel and handle disconnect are
/// idempotent).
@_spi(Portico) public func teardown() {
for t in tokens { t.cancel() }
tokens.removeAll()
for i in gtkHandles.indices { gtkHandles[i].disconnect() }
gtkHandles.removeAll()
for i in adwHandles.indices { adwHandles[i].disconnect() }
adwHandles.removeAll()
for i in gobjectHandles.indices { gobjectHandles[i].disconnect() }
gobjectHandles.removeAll()
for tr in trackers { tr.teardown() }
trackers.removeAll()
for c in children { c.teardown() }
children.removeAll()
owners.removeAll()
}
}