Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ let package = Package(
],
swiftSettings: [
.swiftLanguageMode(.v5),
.enableExperimentalFeature("Lifetimes"),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("ConciseMagicFile"),
.enableUpcomingFeature("ImplicitOpenExistentials"),
.enableExperimentalFeature("Lifetimes"),
.enableExperimentalFeature("SuppressedAssociatedTypes"),
]
),
.target(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ enum LifecycleHook {
case onAppearReturningCancelFunction(() -> () -> Void)
}

struct _LifecycleEventView<Wrapped: View>: View {
struct _LifecycleEventView<Wrapped: View>: View, _Mountable {
typealias Tag = Wrapped.Tag
typealias _MountedNode = _StatefulNode<LifecycleState, Wrapped._MountedNode>

Expand Down
10 changes: 4 additions & 6 deletions Sources/ElementaryUI/Data/Lifecycle/_StatefulNode.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
public struct _StatefulNode<State, Child: _Reconcilable> {
public struct _StatefulNode<State: ~Copyable, Child: _Reconcilable & ~Copyable>: ~Copyable, _Reconcilable {
var state: State
var child: Child
private var onUnmount: ((inout _CommitContext) -> Void)?

init(state: State, child: Child) {
init(state: consuming State, child: consuming Child) {
self.state = state
self.child = child
}

private init(state: State, child: Child, onUnmount: ((inout _CommitContext) -> Void)? = nil) {
private init(state: consuming State, child: consuming Child, onUnmount: ((inout _CommitContext) -> Void)? = nil) {
self.state = state
self.child = child
self.onUnmount = onUnmount
}

init(state: State, child: Child) where State: Unmountable {
init(state: consuming State, child: consuming Child) where State: Unmountable {
self.init(state: state, child: child, onUnmount: state.unmount(_:))
}
}

extension _StatefulNode: _Reconcilable {
public consuming func unmount(_ context: inout _CommitContext) {
child.unmount(&context)
onUnmount?(&context)
Expand Down
2 changes: 1 addition & 1 deletion Sources/ElementaryUI/HTMLViews/_ElementNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ enum _ElementAttributes {
case modifier(_AttributeModifier)
}

public struct _ElementNode<Child: _Reconcilable>: _Reconcilable {
public struct _ElementNode<Child: _Reconcilable & ~Copyable>: ~Copyable, _Reconcilable {
private var child: Child
private var attributes: _ElementAttributes
private var mountedModifiers: [AnyUnmountable] = []
Expand Down
2 changes: 1 addition & 1 deletion Sources/ElementaryUI/Reconciling/ApplicationRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ final class ApplicationRuntime<DOMInteractor: DOM.Interactor> {
rootNode.patchWithB(tx: &tx, makeNode: { _, _ in _EmptyNode() }, updateNode: { _, _ in })

// Break the root container/layout cycle after patch-driven removals are committed.
tx.scheduler.addCommitAction { ctx in rootNode.unmount(&ctx) }
tx.scheduler.addCommitAction { ctx in rootNode.container.unmount(&ctx) }
}
}
}
Expand Down
20 changes: 10 additions & 10 deletions Sources/ElementaryUI/Reconciling/MountContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class MountContainer {
self.activeSlots = slots
}

convenience init<Node: _Reconcilable>(
convenience init<Node: _Reconcilable & ~Copyable>(
mountedKey key: _ViewKey,
context: borrowing _ViewContext,
ctx: inout _MountContext,
Expand All @@ -45,7 +45,7 @@ final class MountContainer {
)
}

convenience init<Node: _Reconcilable>(
convenience init<Node: _Reconcilable & ~Copyable>(
mountedKeyStorage keys: borrowing Span<_ViewKey>,
context: borrowing _ViewContext,
ctx: inout _MountContext,
Expand Down Expand Up @@ -120,7 +120,7 @@ final class MountContainer {
keys newKeys: borrowing Span<_ViewKey>,
tx: inout _TransactionContext,
makeNode: @escaping (Int, borrowing _ViewContext, inout _MountContext) -> AnyReconcilable,
patchNode: (Int, AnyReconcilable, inout _TransactionContext) -> Void
patchNode: (Int, inout AnyReconcilable, inout _TransactionContext) -> Void
) {
pendingMakeNode = makeNode
patchPrepared(keys: newKeys, tx: &tx, patchNode: patchNode)
Expand All @@ -130,17 +130,17 @@ final class MountContainer {
key newKey: _ViewKey,
tx: inout _TransactionContext,
makeNode: @escaping (borrowing _ViewContext, inout _MountContext) -> AnyReconcilable,
patchNode: (AnyReconcilable, inout _TransactionContext) -> Void
patchNode: (inout AnyReconcilable, inout _TransactionContext) -> Void
) {
pendingMakeNode = { _, viewContext, mountCtx in makeNode(viewContext, &mountCtx) }

patchPrepared(keys: CollectionOfOne(newKey).span, tx: &tx) { _, node, tx in patchNode(node, &tx) }
patchPrepared(keys: CollectionOfOne(newKey).span, tx: &tx) { _, node, tx in patchNode(&node, &tx) }
}

private func patchPrepared(
keys: borrowing Span<_ViewKey>,
tx: inout _TransactionContext,
patchNode: (Int, AnyReconcilable, inout _TransactionContext) -> Void
patchNode: (Int, inout AnyReconcilable, inout _TransactionContext) -> Void
) {
prepareLaneCapacities(newCount: keys.count)

Expand Down Expand Up @@ -327,21 +327,21 @@ extension MountContainer {
newKeyIndex: Int,
tx: inout _TransactionContext,
containerHandle: LayoutContainer.Handle?,
patchNode: (Int, AnyReconcilable, inout _TransactionContext) -> Void
patchNode: (Int, inout AnyReconcilable, inout _TransactionContext) -> Void
) {
let state = takeState()

switch consume state {
case .pending:
setPending(transaction: tx.transaction, newKeyIndex: newKeyIndex)
case .mounted(let mounted):
patchNode(newKeyIndex, mounted.node, &tx)
case .mounted(var mounted):
patchNode(newKeyIndex, &mounted.node, &tx)
setMounted(mounted)
case .reviving(var mounted):
mounted.transitionCoordinator?.cancelRemoval(tx: &tx)
mounted.didMove = true
Self.reportReenteringElements(of: mounted, handle: containerHandle, tx: &tx)
patchNode(newKeyIndex, mounted.node, &tx)
patchNode(newKeyIndex, &mounted.node, &tx)
setMounted(mounted)
case .removed:
preconditionFailure("active lane contains removed slot")
Expand Down
6 changes: 3 additions & 3 deletions Sources/ElementaryUI/Reconciling/PendingFunctionQueue.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import BasicContainers

struct PendingFunctionQueue: ~Copyable {
private var functionsToRun: UniqueArray<(AnyFunctionNode, Transaction?)> = .init()
private var functionsToRun: UniqueArray<(_SchedulableNode, Transaction?)> = .init()

var isEmpty: Bool { functionsToRun.isEmpty }

// TODO: add transaction here?
mutating func registerFunctionForUpdate(_ node: AnyFunctionNode, transaction: Transaction?) {
mutating func registerFunctionForUpdate(_ node: _SchedulableNode, transaction: Transaction?) {
logTrace("registering function run \(node.identifier)")
// sorted insert by depth in reverse order, avoiding duplicates
var inserted = false
Expand All @@ -30,7 +30,7 @@ struct PendingFunctionQueue: ~Copyable {
}
}

mutating func next() -> (AnyFunctionNode, Transaction?)? {
mutating func next() -> (_SchedulableNode, Transaction?)? {
functionsToRun.popLast()
}

Expand Down
27 changes: 27 additions & 0 deletions Sources/ElementaryUI/Reconciling/SchedulableNode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Reactivity

public class _SchedulableNode {
let depthInTree: Int
var trackingSession: TrackingSession?

init(depthInTree: Int) {
self.depthInTree = depthInTree
}

var identifier: ObjectIdentifier { ObjectIdentifier(self) }

func runUpdate(tx: inout _TransactionContext) {}

func progressAnimation(tx: inout _TransactionContext) -> AnimationProgressResult {
.completed
}

func startTracking(for accessList: ReactivePropertyAccessList, scheduler: Scheduler) {
let session = ReactiveTrackingSession()
session.trackWillSet(for: accessList) {
[self, scheduler] _ in
scheduler.invalidateFunction(self)
}
self.trackingSession = TrackingSession(session.cancel)
}
}
22 changes: 6 additions & 16 deletions Sources/ElementaryUI/Reconciling/Scheduler.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import BasicContainers

struct AnyFunctionNode {
let identifier: ObjectIdentifier
let depthInTree: Int
let runUpdate: (inout _TransactionContext) -> Void
}

enum AnimationProgressResult {
case stillRunning
case completed
}

struct AnyAnimatable {
let progressAnimation: (inout _TransactionContext) -> AnimationProgressResult
}

enum CommitAction {
case patchText(node: DOM.Node, text: String)
case patchAttributes(node: DOM.Node, from: _AttributeStorage, to: _AttributeStorage)
Expand Down Expand Up @@ -49,7 +39,7 @@ final class Scheduler {
private var pendingUpdates: UniqueArray<(inout _TransactionContext) -> Void> = .init()
private var pendingCommitActions: UniqueArray<CommitAction> = .init()
private var pendingEffects: UniqueArray<() -> Void> = .init()
private var runningAnimations: [AnyAnimatable] = []
private var runningAnimations: [_SchedulableNode] = []

// Scheduling state
// True while an update cycle is either scheduled or currently running.
Expand Down Expand Up @@ -88,7 +78,7 @@ final class Scheduler {

// MARK: - Public API

func invalidateFunction(_ function: AnyFunctionNode) {
func invalidateFunction(_ function: _SchedulableNode) {
if ambientContext != nil {
ambientContext!.addFunction(function)
return
Expand Down Expand Up @@ -124,8 +114,8 @@ final class Scheduler {
ensureUpdateCycleScheduled()
}

func registerAnimation(_ animation: AnyAnimatable) {
runningAnimations.append(animation)
func registerAnimation(_ node: _SchedulableNode) {
runningAnimations.append(node)
ensureAnimationFrameScheduled()
}

Expand Down Expand Up @@ -286,8 +276,8 @@ final class Scheduler {
transaction: transaction
)

runningAnimations.removeAll(where: { animation in
let result = animation.progressAnimation(&context)
runningAnimations.removeAll(where: { node in
let result = node.progressAnimation(tx: &context)
return result == .completed
})

Expand Down
2 changes: 1 addition & 1 deletion Sources/ElementaryUI/Reconciling/_MountContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public struct _MountContext: ~Copyable, ~Escapable {
}
}

mutating func withTransitionBoundary<R>(_ body: (inout _MountContext) -> R) -> R {
mutating func withTransitionBoundary<R: ~Copyable>(_ body: (inout _MountContext) -> R) -> R {
let previousIsRoot = isRoot
isRoot = false
let result = body(&self)
Expand Down
18 changes: 9 additions & 9 deletions Sources/ElementaryUI/Reconciling/_Reconcilable.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// TODO: either get rid of this protocol entirely, or turn it into a dedicated
// mount lifecycle owner type.
public protocol _Reconcilable {
public protocol _Reconcilable: ~Copyable {
consuming func unmount(_ context: inout _CommitContext)
}

struct AnyReconcilable {
struct AnyReconcilable: ~Copyable {
class _Box {
func unmount(_ context: inout _CommitContext) {}
}

final class _TypedBox<R: _Reconcilable>: _Box {
var node: R
final class _TypedBox<R: _Reconcilable & ~Copyable>: _Box {
var node: R?

init(_ node: consuming R) { self.node = node }
init(_ node: consuming R) { self.node = .some(node) }

override func unmount(_ context: inout _CommitContext) {
node.unmount(&context)
node.take()?.unmount(&context)
}
}

private var box: _Box

init<R: _Reconcilable>(_ node: R) {
init<R: _Reconcilable & ~Copyable>(_ node: consuming R) {
self.box = _TypedBox(node)

}
Expand All @@ -30,8 +30,8 @@ struct AnyReconcilable {
}

// TODO: make this mutating to prepare for ~Copyable all the way
func modify<R: _Reconcilable>(as type: R.Type = R.self, _ body: (inout R) -> Void) {
func modify<R: _Reconcilable & ~Copyable>(as type: R.Type = R.self, _ body: (inout R) -> Void) {
let box = unsafeDowncast(self.box, to: _TypedBox<R>.self)
body(&box.node)
body(&box.node!)
}
}
4 changes: 2 additions & 2 deletions Sources/ElementaryUI/Reconciling/_TransactionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public struct _TransactionContext: ~Copyable {
self.transaction = transaction ?? .init()
}

mutating func addFunction(_ function: AnyFunctionNode) {
mutating func addFunction(_ function: _SchedulableNode) {
pendingFunctions.registerFunctionForUpdate(function, transaction: transaction)
}

Expand All @@ -32,7 +32,7 @@ public struct _TransactionContext: ~Copyable {
consuming func drain() {
while let (node, transaction) = pendingFunctions.next() {
self.transaction = transaction ?? .init()
node.runUpdate(&self)
node.runUpdate(tx: &self)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension PlaceholderContentView: _Mountable {
public final class _PlaceholderNode: _Reconcilable {
var node: AnyReconcilable

init(node: AnyReconcilable) {
init(node: consuming AnyReconcilable) {
self.node = node
}

Expand Down
Loading
Loading