From 2e800bdf77a35f074e02e4a92191cf35a207acf6 Mon Sep 17 00:00:00 2001 From: Simon Leeb <52261246+sliemeobn@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:18:44 +0200 Subject: [PATCH] maybe fixed embedded crasher --- .../ElementaryUI/HTMLViews/_ElementNode.swift | 97 ++++++++++++------- .../Transition/_TransitionableNode.swift | 53 ++++------ 2 files changed, 81 insertions(+), 69 deletions(-) diff --git a/Sources/ElementaryUI/HTMLViews/_ElementNode.swift b/Sources/ElementaryUI/HTMLViews/_ElementNode.swift index f413997..9bd4930 100644 --- a/Sources/ElementaryUI/HTMLViews/_ElementNode.swift +++ b/Sources/ElementaryUI/HTMLViews/_ElementNode.swift @@ -1,8 +1,3 @@ -enum _ElementAttributes { - case inline(node: DOM.Node, lastApplied: _AttributeStorage) - case modifier(_AttributeModifier) -} - public struct _ElementNode: ~Copyable, _Reconcilable @@ -32,10 +27,10 @@ public struct _ElementNode: ctx.appendStaticElement(domNode) guard !viewContext.hasNoUpstreamModifiers else { - addHTMLAttributes(attributes, to: domNode, using: ctx.dom) - self.attributes = .inline( - node: domNode, - lastApplied: attributes + self.attributes = .mountInline( + attributes, + on: domNode, + using: ctx.dom ) self.child = ctx.withChildContext { (mctx: consuming _MountContext) in @@ -47,19 +42,18 @@ public struct _ElementNode: } var childContext = copy viewContext - if childContext.modifiers[_AttributeModifier.key] != nil { let modifier = _AttributeModifier( value: attributes, upstream: childContext.modifiers ) - self.attributes = .modifier(modifier) + self.attributes = .mountModifier(modifier) childContext.modifiers[_AttributeModifier.key] = modifier } else { - addHTMLAttributes(attributes, to: domNode, using: ctx.dom) - self.attributes = .inline( - node: domNode, - lastApplied: attributes + self.attributes = .mountInline( + attributes, + on: domNode, + using: ctx.dom ) } @@ -87,24 +81,7 @@ public struct _ElementNode: _ context: inout _TransactionContext, block: (inout Child, inout _TransactionContext) -> Void ) { - switch self.attributes { - case .modifier(let modifier): - modifier.updateValue(attributes, &context) - case .inline(let node, let lastApplied): - if attributes != lastApplied { - context.scheduler.addCommitAction( - .patchAttributes( - node: node, - from: lastApplied, - to: attributes - ) - ) - self.attributes = .inline( - node: node, - lastApplied: attributes - ) - } - } + self.attributes.patch(attributes, context: &context) block(&child, &context) } @@ -123,3 +100,57 @@ private extension _ViewContext { modifiers.isEmpty && layoutObservers.isEmpty } } + +struct _ElementAttributes { + private enum Storage { + case inline(node: DOM.Node, lastApplied: _AttributeStorage) + case modifier(_AttributeModifier) + } + + private var storage: Storage + + private init(storage: consuming Storage) { + self.storage = storage + } + + static func mountInline( + _ attributes: _AttributeStorage, + on node: DOM.Node, + using dom: DOMInteractor + ) -> Self { + addHTMLAttributes(attributes, to: node, using: dom) + return Self( + storage: .inline(node: node, lastApplied: attributes) + ) + } + + static func mountModifier( + _ modifier: consuming _AttributeModifier + ) -> Self { + Self(storage: .modifier(modifier)) + } + + @inline(never) + mutating func patch( + _ attributes: _AttributeStorage, + context: inout _TransactionContext + ) { + switch storage { + case .modifier(let modifier): + modifier.updateValue(attributes, &context) + case .inline(let node, let lastApplied): + guard attributes != lastApplied else { return } + context.scheduler.addCommitAction( + .patchAttributes( + node: node, + from: lastApplied, + to: attributes + ) + ) + storage = .inline( + node: node, + lastApplied: attributes + ) + } + } +} diff --git a/Sources/ElementaryUI/Transition/_TransitionableNode.swift b/Sources/ElementaryUI/Transition/_TransitionableNode.swift index 0779cda..36ed153 100644 --- a/Sources/ElementaryUI/Transition/_TransitionableNode.swift +++ b/Sources/ElementaryUI/Transition/_TransitionableNode.swift @@ -4,13 +4,10 @@ public struct _TransitionableNode: ~Copyable, _Reconcilable { - private enum Storage: ~Copyable { - case direct(Node) - case transitioned(_TransitionElement) - case _movedOut - } - - private var storage: Storage + // FIXME: this should be an enum, but 6.3 has serious embedded miscompiles with enums and ownership + // revisit in 6.4 + private var node: Node? + private var transitionedElement: _TransitionElement? init( context: borrowing _ViewContext, @@ -21,8 +18,13 @@ public struct _TransitionableNode: inout _MountContext ) -> Node ) { + defer { + precondition(node != nil || transitionedElement != nil) + precondition(node == nil || transitionedElement == nil) + } + guard let transition = context.transition else { - storage = .direct(makeNode(context, &ctx)) + self.node = makeNode(context, &ctx) return } @@ -33,11 +35,11 @@ public struct _TransitionableNode: // Without a structural owner there is nothing that can defer removal. guard ctx.slotTransitions != nil else { - storage = .direct(makeNode(nodeContext, &ctx)) + self.node = makeNode(nodeContext, &ctx) return } - let element = _TransitionElement.make( + self.transitionedElement = _TransitionElement.make( transition: transition.value, context: nodeContext, ctx: &ctx, @@ -45,46 +47,25 @@ public struct _TransitionableNode: AnyReconcilable(makeNode(context, &ctx)) } ) - - storage = .transitioned(element) } mutating func update( _ tx: inout _TransactionContext, body: (inout Node, inout _TransactionContext) -> Void ) { - var storage = Storage._movedOut - swap(&storage, &self.storage) - - switch consume storage { - case .direct(var node): - body(&node, &tx) - self.storage = .direct(node) - case .transitioned(let transitionedElement): - transitionedElement.forEachPlaceholder { placeholder in + if node != nil { body(&node!, &tx) } + if transitionedElement != nil { + transitionedElement!.forEachPlaceholder { placeholder in placeholder.modify(as: Node.self) { node in body(&node, &tx) } } - self.storage = .transitioned(transitionedElement) - case ._movedOut: - preconditionFailure( - "_TransitionableNode storage was already moved out" - ) } } public consuming func unmount(_ context: inout _CommitContext) { - switch consume storage { - case .direct(let node): - node.unmount(&context) - case .transitioned(let transitionedElement): - transitionedElement.unmount(&context) - case ._movedOut: - preconditionFailure( - "_TransitionableNode storage was already moved out" - ) - } + node.take()?.unmount(&context) + transitionedElement.take()?.unmount(&context) } }