From f640e720f91ca84c10ed6736338940722c709443 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Wed, 5 Oct 2022 16:38:25 +0200 Subject: [PATCH 01/16] prints n stuff --- Package.swift | 19 +++++++++++++++++++ .../Fiber+CustomDebugStringConvertible.swift | 12 ++++++------ .../TokamakCore/Fiber/FiberReconciler.swift | 4 ++++ .../Fiber/Passes/ReconcilePass.swift | 10 ++++++++++ Sources/TokamakDOM/DOMFiberRenderer.swift | 10 +++++++++- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index c062c573c..388daf77b 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,7 @@ let package = Package( name: "TokamakDemo", targets: ["TokamakDemo"] ), + .executable(name: "RecRep", targets: ["RecRep"]), .library( name: "TokamakDOM", targets: ["TokamakDOM"] @@ -167,6 +168,24 @@ let package = Package( "OpenCombineJS", ] ), + .executableTarget( + name: "RecRep", + dependencies: [ + "TokamakShim", + .product( + name: "JavaScriptKit", + package: "JavaScriptKit", + condition: .when(platforms: [.wasi]) + ), + ] + // resources: [.copy("logo-header.png")], + // linkerSettings: [ + // .unsafeFlags( + // ["-Xlinker", "--stack-first", "-Xlinker", "-z", "-Xlinker", "stack-size=16777216"], + // .when(platforms: [.wasi]) + // ), + // ] + ), .executableTarget( name: "TokamakDemo", dependencies: [ diff --git a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift index 49f98bb04..765761077 100644 --- a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift +++ b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift @@ -34,13 +34,13 @@ extension FiberReconciler.Fiber: CustomDebugStringConvertible { proposal: .unspecified ) return """ - \(spaces)\(String(describing: typeInfo?.type ?? Any.self) - .split(separator: "<")[0])\(element != nil ? "(\(element!))" : "") {\(element != nil ? - "\n\(spaces)geometry: \(geometry)" : - "") - \(child?.flush(level: level + 2) ?? "") - \(spaces)} + \(spaces)\(debugDescription)\(element != nil ? "(\(element!))" : "") + \(child?.flush(level: level + 2) ?? "")\ \(sibling?.flush(level: level) ?? "") """ } + + public var recursiveDescription: String { + flush() + } } diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index 0fc52e5e3..f25955f8b 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -243,10 +243,12 @@ public final class FiberReconciler { /// /// A `reconcile()` call is queued from `fiberChanged` once per run loop. func reconcile() { + print("xxx============== BEFORE\n\(current.recursiveDescription)\n\nxxx============== AFTER\n\(alternate.recursiveDescription)") isReconciling = true let changedFibers = changedFibers self.changedFibers.removeAll() // Create a list of mutations. + print(changedFibers) let visitor = ReconcilerVisitor(root: current, changedFibers: changedFibers, reconciler: self) switch current.content { case let .view(_, visit): @@ -266,6 +268,8 @@ public final class FiberReconciler { // Essentially, making the work in progress tree the current, // and leaving the current available to be the work in progress // on our next update. + + print("============== BEFORE\n\(current.recursiveDescription)\n\n============== AFTER\n\(alternate.recursiveDescription)") let alternate = alternate self.alternate = current current = alternate diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index c2efdef4b..c067742a1 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -72,6 +72,7 @@ struct ReconcilePass: FiberReconcilerPass { var shouldReconcile = false while true { + print(node.fiber as Any) if !shouldReconcile { if let fiber = node.fiber, changedFibers.contains(ObjectIdentifier(fiber)) @@ -99,6 +100,12 @@ struct ReconcilePass: FiberReconcilerPass { caches.mutations.append(mutation) } + + ///////------------_!!!!!!!!!!!!!!!!!!!! + //////// wheeeere are new fiber elements creted????? + ////.how is element formed? + ////------------------- + // Ensure the `TreeReducer` can access any necessary state. node.elementIndices = caches.elementIndices // Pass view traits down to the nearest element fiber. @@ -223,10 +230,12 @@ struct ReconcilePass: FiberReconcilerPass { in reconciler: FiberReconciler, caches: FiberReconciler.Caches ) -> Mutation? { + print("rec node:", node.fiber?.typeInfo?.type, node.fiber?.alternate?.typeInfo?.type) if let element = node.fiber?.element, let index = node.fiber?.elementIndex, let parent = node.fiber?.elementParent?.element { + print("actually do it") if node.fiber?.alternate == nil { // This didn't exist before (no alternate) if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) @@ -258,6 +267,7 @@ struct ReconcilePass: FiberReconcilerPass { ) } } + print("nvm el", node.fiber?.element != nil, "ind", node.fiber?.elementIndex != nil, "par", node.fiber?.elementParent?.element != nil) return nil } diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index 450b4d882..0cdaed102 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -57,6 +57,12 @@ public final class DOMElement: FiberElement { } } + +extension DOMElement: CustomStringConvertible { + public var description: String { + "DOMElement(tag: \(content.tag), attributes: \(content.attributes.filter { $0.key != "style" }), innerHTML: \(content.innerHTML ?? "nil"))" + } +} public extension DOMElement.Content { init(from primitiveView: V, useDynamicLayout: Bool) where V: View { guard let primitiveView = primitiveView as? HTMLConvertible else { fatalError() } @@ -291,6 +297,7 @@ public struct DOMFiberRenderer: FiberRenderer { public func commit(_ mutations: [Mutation]) { for mutation in mutations { + print(mutation) switch mutation { case let .insert(newElement, parent, index): let element = createElement(newElement) @@ -310,7 +317,8 @@ public struct DOMFiberRenderer: FiberRenderer { fatalError("The previous element does not exist (trying to replace element).") } let replacementElement = createElement(replacement) - _ = parentElement.replaceChild?(previousElement, replacementElement) + _ = parentElement.replaceChild?(replacementElement, previousElement) +// _ = parentElement.replaceChild?(previousElement, replacementElement) case let .update(previous, newContent, geometry): previous.update(with: newContent) guard let previousElement = previous.reference From 2608249672925ffdd566d468ea8f83c6c37d0832 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Wed, 5 Oct 2022 16:39:05 +0200 Subject: [PATCH 02/16] reproducer --- Sources/RecRep/fiber-reproducer.swift | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Sources/RecRep/fiber-reproducer.swift diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift new file mode 100644 index 000000000..fff5caf6d --- /dev/null +++ b/Sources/RecRep/fiber-reproducer.swift @@ -0,0 +1,63 @@ +import TokamakDOM +import Foundation + +@main +struct TokamakApp: App { + static let _configuration: _AppConfiguration = .init(reconciler: .fiber(useDynamicLayout: false)) + var body: some Scene { WindowGroup("Tokamak App") { ContentView() } } +} + +enum State { + case a + case b([String]) + case c(String, [Int]) + case d(String, [Int], String) +} + +final class StateManager: ObservableObject { + private init() { } + static let shared = StateManager() + + @Published var state = State.a +} + +struct ContentView: View { + @ObservedObject var sm = StateManager.shared + + var body: some View { + switch sm.state { + case .a: +// VStack { + Button("go to b") { + sm.state = .b(["eins", "zwei", "drei"]) + } +// } + case .b(let arr): + VStack { + Text("b:") + ForEach(arr, id: \.self) { s in + Button(s) { + sm.state = .c(s, s == "zwei" ? [1, 2] : [1]) + } + } + } + case .c(let str, let ints): + VStack { + Text("c \(str)") + .font(.headline) + Text("hello there") + ForEach(ints, id: \.self) { i in + let d = "i = \(i)" + Button(d) { + sm.state = .d(str, ints, d) + } + } + } + case .d(_, let ints, let other): + VStack { + Text("\(other)") + Text("count \(ints.count)") + } + } + } +} From 0700d78fbd5a2a3d78c3df27d0e7d011333936b7 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Sat, 8 Oct 2022 19:12:15 +0200 Subject: [PATCH 03/16] wip --- Sources/RecRep/fiber-reproducer.swift | 2 +- .../Fiber/FiberReconciler+TreeReducer.swift | 13 +++++++++++++ Sources/TokamakCore/Fiber/FiberReconciler.swift | 7 ++++--- .../TokamakCore/Fiber/Passes/ReconcilePass.swift | 14 +++++++++----- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift index fff5caf6d..07fd6054c 100644 --- a/Sources/RecRep/fiber-reproducer.swift +++ b/Sources/RecRep/fiber-reproducer.swift @@ -18,7 +18,7 @@ final class StateManager: ObservableObject { private init() { } static let shared = StateManager() - @Published var state = State.a + @Published var state = State.a //b(["eins", "2", "III"]) } struct ContentView: View { diff --git a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift index 1d85dc541..e11a47b69 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift @@ -130,8 +130,15 @@ extension FiberReconciler { // Create the node and its element. var nextValue = nextValue + print("TreeReducer visiting view \(nextValue)") + let resultChild: Result if let existing = partialResult.nextExisting { + print("updating existing \(existing)") + let elementParent = partialResult.fiber?.element != nil + ? partialResult.fiber + : partialResult.fiber?.elementParent + existing.elementParent = elementParent // If a fiber already exists, simply update it with the new view. let key: ObjectIdentifier? if let elementParent = existing.elementParent { @@ -145,6 +152,11 @@ extension FiberReconciler { key.map { partialResult.elementIndices[$0, default: 0] }, partialResult.nextTraits ) + + if let n = newContent { + existing.element = .init(from: n) + } + resultChild = Result( fiber: existing, visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue), @@ -158,6 +170,7 @@ extension FiberReconciler { partialResult.nextExisting = existing.sibling partialResult.nextExistingAlternate = partialResult.nextExistingAlternate?.sibling } else { + print("creating new") let elementParent = partialResult.fiber?.element != nil ? partialResult.fiber : partialResult.fiber?.elementParent diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index f25955f8b..d1be941f2 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -243,12 +243,12 @@ public final class FiberReconciler { /// /// A `reconcile()` call is queued from `fiberChanged` once per run loop. func reconcile() { - print("xxx============== BEFORE\n\(current.recursiveDescription)\n\nxxx============== AFTER\n\(alternate.recursiveDescription)") + print("reconcile(), changedFibers: \(changedFibers)") + isReconciling = true let changedFibers = changedFibers self.changedFibers.removeAll() // Create a list of mutations. - print(changedFibers) let visitor = ReconcilerVisitor(root: current, changedFibers: changedFibers, reconciler: self) switch current.content { case let .view(_, visit): @@ -269,11 +269,12 @@ public final class FiberReconciler { // and leaving the current available to be the work in progress // on our next update. - print("============== BEFORE\n\(current.recursiveDescription)\n\n============== AFTER\n\(alternate.recursiveDescription)") let alternate = alternate self.alternate = current current = alternate + print("============== reconcile done, current is now:\n\(current.recursiveDescription)") + isReconciling = false for action in afterReconcileActions { diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index c067742a1..c0162b017 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -72,7 +72,6 @@ struct ReconcilePass: FiberReconcilerPass { var shouldReconcile = false while true { - print(node.fiber as Any) if !shouldReconcile { if let fiber = node.fiber, changedFibers.contains(ObjectIdentifier(fiber)) @@ -85,6 +84,8 @@ struct ReconcilePass: FiberReconcilerPass { } } + print("run(...) iteration, shouldReconcile: \(shouldReconcile), node:", node.fiber ?? "") + // If this fiber has an element, set its `elementIndex` // and increment the `elementIndices` value for its `elementParent`. if node.fiber?.element != nil, @@ -118,6 +119,7 @@ struct ReconcilePass: FiberReconcilerPass { // Update `DynamicProperty`s before accessing the `View`'s body. node.fiber?.updateDynamicProperties() // Compute the children of the node. + print("run(...): reducing tree") let reducer = FiberReconciler.TreeReducer.SceneVisitor(initialResult: node) node.visitChildren(reducer) @@ -230,16 +232,16 @@ struct ReconcilePass: FiberReconcilerPass { in reconciler: FiberReconciler, caches: FiberReconciler.Caches ) -> Mutation? { - print("rec node:", node.fiber?.typeInfo?.type, node.fiber?.alternate?.typeInfo?.type) + print("reconcile(...), node:", node.fiber ?? "", "alternate:", node.fiber?.alternate ?? "") if let element = node.fiber?.element, let index = node.fiber?.elementIndex, let parent = node.fiber?.elementParent?.element { - print("actually do it") - if node.fiber?.alternate == nil { // This didn't exist before (no alternate) + if node.fiber?.alternate?.element == nil { // This didn't exist before (no alternate) if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } + print(" -> .insert") return .insert(element: element, parent: parent, index: index) } else if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type, let previous = node.fiber?.alternate?.element @@ -247,6 +249,7 @@ struct ReconcilePass: FiberReconcilerPass { if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } + print(" -> .replace") // This is a completely different type of view. return .replace(parent: parent, previous: previous, replacement: element) } else if let newContent = node.newContent, @@ -255,6 +258,7 @@ struct ReconcilePass: FiberReconcilerPass { if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } + print(" -> .update") // This is the same type of view, but its backing data has changed. return .update( previous: element, @@ -267,7 +271,7 @@ struct ReconcilePass: FiberReconcilerPass { ) } } - print("nvm el", node.fiber?.element != nil, "ind", node.fiber?.elementIndex != nil, "par", node.fiber?.elementParent?.element != nil) + print(" -> nil, element", node.fiber?.element != nil, "elementIndex", node.fiber?.elementIndex != nil, "elementParent", node.fiber?.elementParent?.element != nil) return nil } From 3d8e2f79f5ca220aaac2989f3c814c770fbf3b44 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Tue, 18 Oct 2022 23:16:41 +0200 Subject: [PATCH 04/16] wip --- .../Fiber/FiberReconciler+TreeReducer.swift | 8 ++++---- Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift | 6 ++++-- Sources/TokamakDOM/DOMFiberRenderer.swift | 12 +++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift index e11a47b69..3ebcb1fb6 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift @@ -153,10 +153,8 @@ extension FiberReconciler { partialResult.nextTraits ) - if let n = newContent { - existing.element = .init(from: n) - } - +// existing.element = newContent.map { .init(from: $0) } + resultChild = Result( fiber: existing, visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue), @@ -169,6 +167,8 @@ extension FiberReconciler { ) partialResult.nextExisting = existing.sibling partialResult.nextExistingAlternate = partialResult.nextExistingAlternate?.sibling + + existing.sibling = nil } else { print("creating new") let elementParent = partialResult.fiber?.element != nil diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index c0162b017..a43550b53 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -233,7 +233,7 @@ struct ReconcilePass: FiberReconcilerPass { caches: FiberReconciler.Caches ) -> Mutation? { print("reconcile(...), node:", node.fiber ?? "", "alternate:", node.fiber?.alternate ?? "") - if let element = node.fiber?.element, + if let element = node.fiber?.element ?? node.newContent.map({ .init(from: $0) }), let index = node.fiber?.elementIndex, let parent = node.fiber?.elementParent?.element { @@ -242,6 +242,7 @@ struct ReconcilePass: FiberReconcilerPass { invalidateCache(for: fiber, in: reconciler, caches: caches) } print(" -> .insert") + node.fiber?.element = element return .insert(element: element, parent: parent, index: index) } else if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type, let previous = node.fiber?.alternate?.element @@ -253,7 +254,7 @@ struct ReconcilePass: FiberReconcilerPass { // This is a completely different type of view. return .replace(parent: parent, previous: previous, replacement: element) } else if let newContent = node.newContent, - newContent != element.content + node.fiber?.element?.content != node.fiber?.alternate?.element?.content { if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) @@ -270,6 +271,7 @@ struct ReconcilePass: FiberReconcilerPass { ) ) } + print(" ! didn't update, content \(element.content), new content \(node.newContent as Any)") } print(" -> nil, element", node.fiber?.element != nil, "elementIndex", node.fiber?.elementIndex != nil, "elementParent", node.fiber?.elementParent?.element != nil) return nil diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index 0cdaed102..7d906afc9 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -317,8 +317,18 @@ public struct DOMFiberRenderer: FiberRenderer { fatalError("The previous element does not exist (trying to replace element).") } let replacementElement = createElement(replacement) + var grandchildren: [JSObject] = [] + + while let g = previousElement.firstChild.object { + grandchildren.append(g) + _ = g.remove!() + } + _ = parentElement.replaceChild?(replacementElement, previousElement) -// _ = parentElement.replaceChild?(previousElement, replacementElement) + + for g in grandchildren { + _ = replacementElement.appendChild!(g) + } case let .update(previous, newContent, geometry): previous.update(with: newContent) guard let previousElement = previous.reference From c38a05a0def2a1292ace868cbd601e63cb1e4630 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Wed, 19 Oct 2022 03:55:32 +0200 Subject: [PATCH 05/16] fixed! --- Sources/RecRep/fiber-reproducer.swift | 2 +- .../Fiber+CustomDebugStringConvertible.swift | 12 +- .../Fiber/FiberReconciler+TreeReducer.swift | 18 +-- .../TokamakCore/Fiber/FiberReconciler.swift | 5 - .../Fiber/Passes/ReconcilePass.swift | 114 ++++++++++++------ Sources/TokamakDOM/DOMFiberRenderer.swift | 7 -- 6 files changed, 89 insertions(+), 69 deletions(-) diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift index 07fd6054c..ea14cbfbe 100644 --- a/Sources/RecRep/fiber-reproducer.swift +++ b/Sources/RecRep/fiber-reproducer.swift @@ -55,7 +55,7 @@ struct ContentView: View { } case .d(_, let ints, let other): VStack { - Text("\(other)") + Text("d \(other)") Text("count \(ints.count)") } } diff --git a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift index 765761077..49f98bb04 100644 --- a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift +++ b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift @@ -34,13 +34,13 @@ extension FiberReconciler.Fiber: CustomDebugStringConvertible { proposal: .unspecified ) return """ - \(spaces)\(debugDescription)\(element != nil ? "(\(element!))" : "") - \(child?.flush(level: level + 2) ?? "")\ + \(spaces)\(String(describing: typeInfo?.type ?? Any.self) + .split(separator: "<")[0])\(element != nil ? "(\(element!))" : "") {\(element != nil ? + "\n\(spaces)geometry: \(geometry)" : + "") + \(child?.flush(level: level + 2) ?? "") + \(spaces)} \(sibling?.flush(level: level) ?? "") """ } - - public var recursiveDescription: String { - flush() - } } diff --git a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift index 3ebcb1fb6..cc4c37e08 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift @@ -130,16 +130,13 @@ extension FiberReconciler { // Create the node and its element. var nextValue = nextValue - print("TreeReducer visiting view \(nextValue)") - let resultChild: Result if let existing = partialResult.nextExisting { - print("updating existing \(existing)") - let elementParent = partialResult.fiber?.element != nil - ? partialResult.fiber - : partialResult.fiber?.elementParent - existing.elementParent = elementParent // If a fiber already exists, simply update it with the new view. + let elementParent = partialResult.fiber?.element != nil + ? partialResult.fiber + : partialResult.fiber?.elementParent + existing.elementParent = elementParent let key: ObjectIdentifier? if let elementParent = existing.elementParent { key = ObjectIdentifier(elementParent) @@ -152,9 +149,6 @@ extension FiberReconciler { key.map { partialResult.elementIndices[$0, default: 0] }, partialResult.nextTraits ) - -// existing.element = newContent.map { .init(from: $0) } - resultChild = Result( fiber: existing, visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue), @@ -167,10 +161,8 @@ extension FiberReconciler { ) partialResult.nextExisting = existing.sibling partialResult.nextExistingAlternate = partialResult.nextExistingAlternate?.sibling - - existing.sibling = nil + existing.sibling = nil } else { - print("creating new") let elementParent = partialResult.fiber?.element != nil ? partialResult.fiber : partialResult.fiber?.elementParent diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index d1be941f2..0fc52e5e3 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -243,8 +243,6 @@ public final class FiberReconciler { /// /// A `reconcile()` call is queued from `fiberChanged` once per run loop. func reconcile() { - print("reconcile(), changedFibers: \(changedFibers)") - isReconciling = true let changedFibers = changedFibers self.changedFibers.removeAll() @@ -268,13 +266,10 @@ public final class FiberReconciler { // Essentially, making the work in progress tree the current, // and leaving the current available to be the work in progress // on our next update. - let alternate = alternate self.alternate = current current = alternate - print("============== reconcile done, current is now:\n\(current.recursiveDescription)") - isReconciling = false for action in afterReconcileActions { diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index a43550b53..bf7ece9be 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -84,11 +84,9 @@ struct ReconcilePass: FiberReconcilerPass { } } - print("run(...) iteration, shouldReconcile: \(shouldReconcile), node:", node.fiber ?? "") - // If this fiber has an element, set its `elementIndex` // and increment the `elementIndices` value for its `elementParent`. - if node.fiber?.element != nil, + if node.newContent != nil || node.fiber?.element != nil, let elementParent = node.fiber?.elementParent { node.fiber?.elementIndex = caches.elementIndex(for: elementParent, increment: true) @@ -101,12 +99,6 @@ struct ReconcilePass: FiberReconcilerPass { caches.mutations.append(mutation) } - - ///////------------_!!!!!!!!!!!!!!!!!!!! - //////// wheeeere are new fiber elements creted????? - ////.how is element formed? - ////------------------- - // Ensure the `TreeReducer` can access any necessary state. node.elementIndices = caches.elementIndices // Pass view traits down to the nearest element fiber. @@ -119,7 +111,6 @@ struct ReconcilePass: FiberReconcilerPass { // Update `DynamicProperty`s before accessing the `View`'s body. node.fiber?.updateDynamicProperties() // Compute the children of the node. - print("run(...): reducing tree") let reducer = FiberReconciler.TreeReducer.SceneVisitor(initialResult: node) node.visitChildren(reducer) @@ -157,15 +148,19 @@ struct ReconcilePass: FiberReconcilerPass { if let parent = node.fiber?.element != nil ? node.fiber : node.fiber?.elementParent { invalidateCache(for: parent, in: reconciler, caches: caches) } - walk(alternateChild) { node in - if let element = node.element, - let parent = node.elementParent?.element - { - // Removals must happen in reverse order, so a child element - // is removed before its parent. - caches.mutations.insert(.remove(element: element, parent: parent), at: 0) + var nextChildOrSibling: FiberReconciler.Fiber? = alternateChild + while let child = nextChildOrSibling { + walk(child) { node in + if let element = node.element, + let parent = node.elementParent?.element + { + // Removals must happen in reverse order, so a child element + // is removed before its parent. + caches.mutations.insert(.remove(element: element, parent: parent), at: 0) + } + return true } - return true + nextChildOrSibling = child.sibling } } if reducer.result.child == nil { @@ -198,18 +193,21 @@ struct ReconcilePass: FiberReconcilerPass { var alternateSibling = node.fiber?.alternate?.sibling // The alternate had siblings that no longer exist. - while alternateSibling != nil { - if let fiber = alternateSibling?.elementParent { + while let currentAltSibling = alternateSibling { + if let fiber = currentAltSibling.elementParent { invalidateCache(for: fiber, in: reconciler, caches: caches) } - if let element = alternateSibling?.element, - let parent = alternateSibling?.elementParent?.element - { - // Removals happen in reverse order, so a child element is removed before - // its parent. - caches.mutations.insert(.remove(element: element, parent: parent), at: 0) + walk(currentAltSibling) { node in + if let element = node.element, + let parent = node.elementParent?.element + { + // Removals happen in reverse order, so a child element is removed before + // its parent. + caches.mutations.insert(.remove(element: element, parent: parent), at: 0) + } + return true } - alternateSibling = alternateSibling?.sibling + alternateSibling = currentAltSibling.sibling } guard let parent = node.parent else { return } // When we walk back to the root, exit @@ -232,17 +230,63 @@ struct ReconcilePass: FiberReconcilerPass { in reconciler: FiberReconciler, caches: FiberReconciler.Caches ) -> Mutation? { - print("reconcile(...), node:", node.fiber ?? "", "alternate:", node.fiber?.alternate ?? "") - if let element = node.fiber?.element ?? node.newContent.map({ .init(from: $0) }), + if node.fiber?.element == nil, + node.fiber?.alternate?.element == nil, + let content = node.newContent, + let index = node.fiber?.elementIndex, + let parent = node.fiber?.elementParent?.element + { + if let fiber = node.fiber { + invalidateCache(for: fiber, in: reconciler, caches: caches) + } + let el = R.ElementType(from: content) + node.fiber?.element = el + return .insert(element: el, parent: parent, index: index) + } + + if node.fiber?.element == nil, + let altElement = node.fiber?.alternate?.element, + let content = node.newContent, + let parent = node.fiber?.elementParent?.element + { + if let fiber = node.fiber { + invalidateCache(for: fiber, in: reconciler, caches: caches) + } + + if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type { + let el = R.ElementType(from: content) + node.fiber?.element = el + return .replace(parent: parent, previous: altElement, replacement: el) + } else if content != altElement.content { + node.fiber?.element = altElement + return .update( + previous: altElement, + newContent: content, + geometry: node.fiber?.geometry ?? .init( + origin: .init(origin: .zero), + dimensions: .init(size: .zero, alignmentGuides: [:]), + proposal: .unspecified + ) + ) + } + } + + if let alt = node.fiber?.alternate?.element, + let parent = node.fiber?.elementParent?.element, + node.newContent == nil + { + node.fiber?.element = nil + return .remove(element: alt, parent: parent) + } + + if let element = node.fiber?.element, let index = node.fiber?.elementIndex, let parent = node.fiber?.elementParent?.element { - if node.fiber?.alternate?.element == nil { // This didn't exist before (no alternate) + if node.fiber?.alternate == nil { // This didn't exist before (no alternate) if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } - print(" -> .insert") - node.fiber?.element = element return .insert(element: element, parent: parent, index: index) } else if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type, let previous = node.fiber?.alternate?.element @@ -250,16 +294,14 @@ struct ReconcilePass: FiberReconcilerPass { if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } - print(" -> .replace") // This is a completely different type of view. return .replace(parent: parent, previous: previous, replacement: element) } else if let newContent = node.newContent, - node.fiber?.element?.content != node.fiber?.alternate?.element?.content + newContent != element.content { if let fiber = node.fiber { invalidateCache(for: fiber, in: reconciler, caches: caches) } - print(" -> .update") // This is the same type of view, but its backing data has changed. return .update( previous: element, @@ -271,9 +313,7 @@ struct ReconcilePass: FiberReconcilerPass { ) ) } - print(" ! didn't update, content \(element.content), new content \(node.newContent as Any)") } - print(" -> nil, element", node.fiber?.element != nil, "elementIndex", node.fiber?.elementIndex != nil, "elementParent", node.fiber?.elementParent?.element != nil) return nil } diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index 7d906afc9..ea57ca416 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -57,12 +57,6 @@ public final class DOMElement: FiberElement { } } - -extension DOMElement: CustomStringConvertible { - public var description: String { - "DOMElement(tag: \(content.tag), attributes: \(content.attributes.filter { $0.key != "style" }), innerHTML: \(content.innerHTML ?? "nil"))" - } -} public extension DOMElement.Content { init(from primitiveView: V, useDynamicLayout: Bool) where V: View { guard let primitiveView = primitiveView as? HTMLConvertible else { fatalError() } @@ -297,7 +291,6 @@ public struct DOMFiberRenderer: FiberRenderer { public func commit(_ mutations: [Mutation]) { for mutation in mutations { - print(mutation) switch mutation { case let .insert(newElement, parent, index): let element = createElement(newElement) From ce73da1bcb4d030cea4c412f4ee3922fd6fbc37f Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Wed, 19 Oct 2022 06:57:28 +0200 Subject: [PATCH 06/16] whoops new issue --- Sources/RecRep/fiber-reproducer.swift | 3 +++ Sources/TokamakDOM/DOMFiberRenderer.swift | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift index ea14cbfbe..022cbe18f 100644 --- a/Sources/RecRep/fiber-reproducer.swift +++ b/Sources/RecRep/fiber-reproducer.swift @@ -57,6 +57,9 @@ struct ContentView: View { VStack { Text("d \(other)") Text("count \(ints.count)") + Button("back") { + sm.state = .a + } } } } diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index ea57ca416..a6504609e 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -57,6 +57,13 @@ public final class DOMElement: FiberElement { } } +extension DOMElement: CustomStringConvertible { + public var description: String { + "DOMElement(tag: \(content.tag), attributes: \(content.attributes.filter { $0.key != "style" }), innerHTML: \(content.innerHTML ?? "nil"))" + } +} + + public extension DOMElement.Content { init(from primitiveView: V, useDynamicLayout: Bool) where V: View { guard let primitiveView = primitiveView as? HTMLConvertible else { fatalError() } @@ -291,6 +298,7 @@ public struct DOMFiberRenderer: FiberRenderer { public func commit(_ mutations: [Mutation]) { for mutation in mutations { + print(mutation) switch mutation { case let .insert(newElement, parent, index): let element = createElement(newElement) From 5d5e67f0202ca07e0a6d0fce7b4296076bb9b907 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Fri, 28 Oct 2022 02:42:48 +0200 Subject: [PATCH 07/16] wip --- Package.swift | 7 - Sources/RecRep/fiber-reproducer.swift | 92 ++++++------- .../Fiber+CustomDebugStringConvertible.swift | 13 +- .../TokamakCore/Fiber/FiberReconciler.swift | 8 ++ .../Fiber/Passes/ReconcilePass.swift | 7 +- Sources/TokamakDOM/DOMFiberRenderer.swift | 1 - .../TestFiberRenderer.swift | 24 +++- .../VaryingPrimitivenessTests.swift | 128 ++++++++++++++++++ 8 files changed, 213 insertions(+), 67 deletions(-) create mode 100644 Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift diff --git a/Package.swift b/Package.swift index 388daf77b..cd1ce60ae 100644 --- a/Package.swift +++ b/Package.swift @@ -178,13 +178,6 @@ let package = Package( condition: .when(platforms: [.wasi]) ), ] - // resources: [.copy("logo-header.png")], - // linkerSettings: [ - // .unsafeFlags( - // ["-Xlinker", "--stack-first", "-Xlinker", "-z", "-Xlinker", "stack-size=16777216"], - // .when(platforms: [.wasi]) - // ), - // ] ), .executableTarget( name: "TokamakDemo", diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift index 022cbe18f..b778664a4 100644 --- a/Sources/RecRep/fiber-reproducer.swift +++ b/Sources/RecRep/fiber-reproducer.swift @@ -3,64 +3,62 @@ import Foundation @main struct TokamakApp: App { - static let _configuration: _AppConfiguration = .init(reconciler: .fiber(useDynamicLayout: false)) - var body: some Scene { WindowGroup("Tokamak App") { ContentView() } } + static let _configuration: _AppConfiguration = .init(reconciler: .fiber(useDynamicLayout: false)) + var body: some Scene { WindowGroup("Tokamak App") { ContentView() } } } enum State { - case a - case b([String]) - case c(String, [Int]) - case d(String, [Int], String) + case a + case b([String]) + case c(String, [Int]) + case d(String, [Int], String) } final class StateManager: ObservableObject { - private init() { } - static let shared = StateManager() + private init() { } + static let shared = StateManager() - @Published var state = State.a //b(["eins", "2", "III"]) + @Published var state = State.a //b(["eins", "2", "III"]) } struct ContentView: View { - @ObservedObject var sm = StateManager.shared + @ObservedObject var sm = StateManager.shared - var body: some View { - switch sm.state { - case .a: -// VStack { - Button("go to b") { - sm.state = .b(["eins", "zwei", "drei"]) - } -// } - case .b(let arr): - VStack { - Text("b:") - ForEach(arr, id: \.self) { s in - Button(s) { - sm.state = .c(s, s == "zwei" ? [1, 2] : [1]) - } - } - } - case .c(let str, let ints): - VStack { - Text("c \(str)") - .font(.headline) - Text("hello there") - ForEach(ints, id: \.self) { i in - let d = "i = \(i)" - Button(d) { - sm.state = .d(str, ints, d) - } - } - } - case .d(_, let ints, let other): - VStack { - Text("d \(other)") - Text("count \(ints.count)") - Button("back") { - sm.state = .a - } - } + var body: some View { + switch sm.state { + case .a: + Button("go to b") { + sm.state = .b(["eins", "zwei", "drei"]) + } + case .b(let arr): + VStack { + Text("b:") + ForEach(arr, id: \.self) { s in + Button(s) { + sm.state = .c(s, s == "zwei" ? [1, 2] : [1]) + } } + } + case .c(let str, let ints): + VStack { + Text("c \(str)") + .font(.headline) + Text("hello there") + ForEach(ints, id: \.self) { i in + let d = "i = \(i)" + Button(d) { + sm.state = .d(str, ints, d) + } + } + } + case .d(_, let ints, let other): + VStack { + Text("d \(other)") + Text("count \(ints.count)") + Button("back") { + sm.state = .a + } + } } + } } diff --git a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift index 49f98bb04..da907a25c 100644 --- a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift +++ b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift @@ -34,13 +34,14 @@ extension FiberReconciler.Fiber: CustomDebugStringConvertible { proposal: .unspecified ) return """ - \(spaces)\(String(describing: typeInfo?.type ?? Any.self) - .split(separator: "<")[0])\(element != nil ? "(\(element!))" : "") {\(element != nil ? - "\n\(spaces)geometry: \(geometry)" : - "") - \(child?.flush(level: level + 2) ?? "") - \(spaces)} + \(spaces)\(debugDescription)\(element != nil ? "(\(element!))" : "") + \(child?.flush(level: level + 2) ?? "")\ \(sibling?.flush(level: level) ?? "") """ } + + public var recursiveDescription: String { + flush() + } + } diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index 0fc52e5e3..2951c9cf6 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -270,6 +270,14 @@ public final class FiberReconciler { self.alternate = current current = alternate + print(""" + reconcile done + mutations were: + \(visitor.mutations.map { " \($0)" }.joined(separator: "\n")) + alternate is \(self.alternate.recursiveDescription) + current is \(current.recursiveDescription) + """) + isReconciling = false for action in afterReconcileActions { diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index bf7ece9be..79edd2766 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -209,6 +209,7 @@ struct ReconcilePass: FiberReconcilerPass { } alternateSibling = currentAltSibling.sibling } + node.fiber?.alternate?.sibling = nil guard let parent = node.parent else { return } // When we walk back to the root, exit guard parent !== root.fiber?.alternate else { return } @@ -241,6 +242,7 @@ struct ReconcilePass: FiberReconcilerPass { } let el = R.ElementType(from: content) node.fiber?.element = el + node.fiber?.alternate?.element = el return .insert(element: el, parent: parent, index: index) } @@ -256,6 +258,7 @@ struct ReconcilePass: FiberReconcilerPass { if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type { let el = R.ElementType(from: content) node.fiber?.element = el + node.fiber?.alternate?.element = el return .replace(parent: parent, previous: altElement, replacement: el) } else if content != altElement.content { node.fiber?.element = altElement @@ -272,10 +275,12 @@ struct ReconcilePass: FiberReconcilerPass { } if let alt = node.fiber?.alternate?.element, - let parent = node.fiber?.elementParent?.element, + let parent = node.fiber?.alternate?.elementParent?.element, node.newContent == nil { node.fiber?.element = nil + node.fiber?.elementIndex = 0 + if let p = node.fiber?.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) } diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index a6504609e..dc1814695 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -298,7 +298,6 @@ public struct DOMFiberRenderer: FiberRenderer { public func commit(_ mutations: [Mutation]) { for mutation in mutations { - print(mutation) switch mutation { case let .insert(newElement, parent, index): let element = createElement(newElement) diff --git a/Sources/TokamakTestRenderer/TestFiberRenderer.swift b/Sources/TokamakTestRenderer/TestFiberRenderer.swift index 50cc09e09..3f4b3a121 100644 --- a/Sources/TokamakTestRenderer/TestFiberRenderer.swift +++ b/Sources/TokamakTestRenderer/TestFiberRenderer.swift @@ -106,11 +106,23 @@ public final class TestFiberElement: FiberElement, CustomStringConvertible { } public var description: String { - """ - \(content.renderedValue) - \(children.map { " \($0.description)" }.joined(separator: "\n")) - \(content.closingTag) - """ + let memoryAddress = String(format: "%010p", unsafeBitCast(self, to: Int.self)) + return content.renderedValue + " (\(memoryAddress)) [\(children.count)]" + } + + public var recursiveDescription: String { + var d = description + if !children.isEmpty { + d.append("\n") + d.append( + children + .flatMap { $0.recursiveDescription.components(separatedBy:"\n").map { " \($0)"} } + .joined(separator: "\n") + ) + d.append("\n") + } + d.append(content.closingTag) + return d } public init(renderedValue: String, closingTag: String) { @@ -169,6 +181,8 @@ public struct TestFiberRenderer: FiberRenderer { case let .replace(parent, previous, replacement): guard let index = parent.children.firstIndex(where: { $0 === previous }) else { continue } + let grandchildren = parent.children[index].children + replacement.children = grandchildren parent.children[index] = replacement case let .layout(element, geometry): element.geometry = geometry diff --git a/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift b/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift new file mode 100644 index 000000000..b693f12cc --- /dev/null +++ b/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift @@ -0,0 +1,128 @@ +// Copyright 2022 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Lukas Stabe on 10/30/22. +// + +import XCTest + +@_spi(TokamakCore) @testable import TokamakCore +import TokamakTestRenderer + +final class VaryingPrimitivenessTests: XCTestCase { + func testVaryingPrimitiveness() { + enum State { + case a + case b([String]) + case c(String, [Int]) + case d(String, [Int], String) + } + + final class StateManager: ObservableObject { + private init() { } + static let shared = StateManager() + + @Published var state = State.a //b(["eins", "2", "III"]) + } + + struct ContentView: View { + @ObservedObject var sm = StateManager.shared + + var body: some View { + switch sm.state { + case .a: + Button("go to b") { + sm.state = .b(["eins", "zwei", "drei"]) + }.identified(by: "a.1") + case .b(let arr): + VStack { + Text("b:") + ForEach(arr, id: \.self) { s in + Button(s) { + sm.state = .c(s, s == "zwei" ? [1, 2] : [1]) + }.identified(by: "b.\(s)") + } + } + case .c(let str, let ints): + VStack { + Text("c \(str)") + .font(.headline) + Text("hello there") + ForEach(ints, id: \.self) { i in + let d = "i = \(i)" + Button(d) { + sm.state = .d(str, ints, d) + }.identified(by: "c." + d) + } + } + case .d(_, let ints, let other): + VStack { + Text("d \(other)") + Text("count \(ints.count)") + Button("back") { + sm.state = .a + }.identified(by: "d.back") + } + } + } + } + + let reconciler = TestFiberRenderer(.root, size: .zero).render(ContentView()) + let root = reconciler.renderer.rootElement + + XCTAssertEqual(root.children.count, 1) // button style + XCTAssertEqual(root.children[0].children.count, 1) // text + + reconciler.findView(id: "a.1", as: Button.self).action?() + reconciler.findView(id: "a.1", as: Button.self).action?() + + XCTAssertEqual(root.children.count, 1) + XCTAssert(root.children[0].description.contains("VStack")) + XCTAssertEqual(root.children[0].children.count, 4) // stack content + XCTAssert(root.children[0].children[0].description.contains("Text")) + XCTAssert(root.children[0].children[1].description.contains("ButtonStyle")) + + reconciler.findView(id: "b.zwei", as: Button.self).action?() + reconciler.findView(id: "b.zwei", as: Button.self).action?() + + XCTAssertEqual(root.children.count, 1) + XCTAssert(root.children[0].description.contains("VStack")) + XCTAssertEqual(root.children[0].children.count, 4) // stack content + XCTAssert(root.children[0].children[0].description.contains("Text")) + XCTAssert(root.children[0].children[1].description.contains("Text")) + XCTAssert(root.children[0].children[2].description.contains("ButtonStyle")) + + reconciler.findView(id: "c.i = 2", as: Button.self).action?() + reconciler.findView(id: "c.i = 2", as: Button.self).action?() + + XCTAssertEqual(root.children[0].children.count, 3) // stack content + + reconciler.findView(id: "d.back", as: Button.self).action?() + reconciler.findView(id: "d.back", as: Button.self).action?() + + XCTAssertEqual(root.children.count, 1) + XCTAssert(root.children[0].description.contains("ButtonStyle")) + XCTAssertEqual(root.children[0].children.count, 1) + XCTAssert(root.children[0].children[0].description.contains("Text")) + + reconciler.findView(id: "a.1", as: Button.self).action?() + reconciler.findView(id: "a.1", as: Button.self).action?() + + XCTAssertEqual(root.children.count, 1) + XCTAssert(root.children[0].description.contains("VStack")) + XCTAssertEqual(root.children[0].children.count, 4) // stack content + XCTAssert(root.children[0].children[0].description.contains("Text")) + XCTAssert(root.children[0].children[1].description.contains("ButtonStyle")) + } +} From 7bd170db0ff408e4c5954b3cf0383fd45d31b617 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Sun, 1 Jan 2023 21:52:55 +0100 Subject: [PATCH 08/16] check node.fiber in the beginning --- .../Fiber/Passes/ReconcilePass.swift | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index 79edd2766..3e20a8feb 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -231,41 +231,41 @@ struct ReconcilePass: FiberReconcilerPass { in reconciler: FiberReconciler, caches: FiberReconciler.Caches ) -> Mutation? { - if node.fiber?.element == nil, - node.fiber?.alternate?.element == nil, + guard let fiber = node.fiber else { return nil } + + if fiber.element == nil, + fiber.alternate?.element == nil, let content = node.newContent, - let index = node.fiber?.elementIndex, - let parent = node.fiber?.elementParent?.element + let index = fiber.elementIndex, + let parent = fiber.elementParent?.element { - if let fiber = node.fiber { - invalidateCache(for: fiber, in: reconciler, caches: caches) - } + invalidateCache(for: fiber, in: reconciler, caches: caches) + let el = R.ElementType(from: content) - node.fiber?.element = el - node.fiber?.alternate?.element = el + fiber.element = el + fiber.alternate?.element = el + return .insert(element: el, parent: parent, index: index) } - if node.fiber?.element == nil, - let altElement = node.fiber?.alternate?.element, + if fiber.element == nil, + let altElement = fiber.alternate?.element, let content = node.newContent, - let parent = node.fiber?.elementParent?.element + let parent = fiber.elementParent?.element { - if let fiber = node.fiber { - invalidateCache(for: fiber, in: reconciler, caches: caches) - } + invalidateCache(for: fiber, in: reconciler, caches: caches) - if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type { + if fiber.typeInfo?.type != fiber.alternate?.typeInfo?.type { let el = R.ElementType(from: content) - node.fiber?.element = el - node.fiber?.alternate?.element = el + fiber.element = el + fiber.alternate?.element = el return .replace(parent: parent, previous: altElement, replacement: el) } else if content != altElement.content { - node.fiber?.element = altElement + fiber.element = altElement return .update( previous: altElement, newContent: content, - geometry: node.fiber?.geometry ?? .init( + geometry: fiber.geometry ?? .init( origin: .init(origin: .zero), dimensions: .init(size: .zero, alignmentGuides: [:]), proposal: .unspecified @@ -274,44 +274,38 @@ struct ReconcilePass: FiberReconcilerPass { } } - if let alt = node.fiber?.alternate?.element, - let parent = node.fiber?.alternate?.elementParent?.element, + if let alt = fiber.alternate?.element, + let parent = fiber.alternate?.elementParent?.element, node.newContent == nil { - node.fiber?.element = nil - node.fiber?.elementIndex = 0 - if let p = node.fiber?.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } + fiber.element = nil + fiber.elementIndex = 0 + if let p = fiber.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) } - if let element = node.fiber?.element, - let index = node.fiber?.elementIndex, - let parent = node.fiber?.elementParent?.element + if let element = fiber.element, + let index = fiber.elementIndex, + let parent = fiber.elementParent?.element { - if node.fiber?.alternate == nil { // This didn't exist before (no alternate) - if let fiber = node.fiber { - invalidateCache(for: fiber, in: reconciler, caches: caches) - } + if fiber.alternate == nil { // This didn't exist before (no alternate) + invalidateCache(for: fiber, in: reconciler, caches: caches) return .insert(element: element, parent: parent, index: index) - } else if node.fiber?.typeInfo?.type != node.fiber?.alternate?.typeInfo?.type, - let previous = node.fiber?.alternate?.element + } else if fiber.typeInfo?.type != fiber.alternate?.typeInfo?.type, + let previous = fiber.alternate?.element { - if let fiber = node.fiber { - invalidateCache(for: fiber, in: reconciler, caches: caches) - } + invalidateCache(for: fiber, in: reconciler, caches: caches) // This is a completely different type of view. return .replace(parent: parent, previous: previous, replacement: element) } else if let newContent = node.newContent, newContent != element.content { - if let fiber = node.fiber { - invalidateCache(for: fiber, in: reconciler, caches: caches) - } + invalidateCache(for: fiber, in: reconciler, caches: caches) // This is the same type of view, but its backing data has changed. return .update( previous: element, newContent: newContent, - geometry: node.fiber?.geometry ?? .init( + geometry: fiber.geometry ?? .init( origin: .init(origin: .zero), dimensions: .init(size: .zero, alignmentGuides: [:]), proposal: .unspecified From bd8de91aa51deaddf3fd100b3bc812b22e525ebe Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 2 Jan 2023 00:16:23 +0100 Subject: [PATCH 09/16] refactor ReconcilePass.reconcile(...) --- .../Fiber/Passes/ReconcilePass.swift | 114 ++++++++---------- 1 file changed, 49 insertions(+), 65 deletions(-) diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index 3e20a8feb..293087c65 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -233,86 +233,70 @@ struct ReconcilePass: FiberReconcilerPass { ) -> Mutation? { guard let fiber = node.fiber else { return nil } - if fiber.element == nil, - fiber.alternate?.element == nil, - let content = node.newContent, - let index = fiber.elementIndex, - let parent = fiber.elementParent?.element - { - invalidateCache(for: fiber, in: reconciler, caches: caches) + func canUpdate(_ fiber: FiberReconciler.Fiber) -> Bool { + fiber.typeInfo?.type == fiber.alternate?.typeInfo?.type + } + + invalidateCache(for: fiber, in: reconciler, caches: caches) + + switch (fiber.element, node.newContent, fiber.alternate?.element) { + case let (nil, content?, nil): + guard let index = fiber.elementIndex, let parent = fiber.elementParent?.element else { break } let el = R.ElementType(from: content) fiber.element = el fiber.alternate?.element = el return .insert(element: el, parent: parent, index: index) - } - if fiber.element == nil, - let altElement = fiber.alternate?.element, - let content = node.newContent, - let parent = fiber.elementParent?.element - { - invalidateCache(for: fiber, in: reconciler, caches: caches) - - if fiber.typeInfo?.type != fiber.alternate?.typeInfo?.type { - let el = R.ElementType(from: content) - fiber.element = el - fiber.alternate?.element = el - return .replace(parent: parent, previous: altElement, replacement: el) - } else if content != altElement.content { - fiber.element = altElement - return .update( - previous: altElement, - newContent: content, - geometry: fiber.geometry ?? .init( - origin: .init(origin: .zero), - dimensions: .init(size: .zero, alignmentGuides: [:]), - proposal: .unspecified - ) + case let (nil, content?, altElement?) where !canUpdate(fiber): + guard let parent = fiber.elementParent?.element else { break } + + let el = R.ElementType(from: content) + fiber.element = el + fiber.alternate?.element = el + + return .replace(parent: parent, previous: altElement, replacement: el) + + case let (nil, content?, element?) where canUpdate(fiber) && content != element.content, + let (element?, content?, _) where canUpdate(fiber) && content != element.content: + guard fiber.elementParent?.element != nil else { break } // todo: is this needed? + + fiber.element = element + return .update( + previous: element, + newContent: content, + geometry: fiber.geometry ?? .init( + origin: .init(origin: .zero), + dimensions: .init(size: .zero, alignmentGuides: [:]), + proposal: .unspecified ) - } - } + ) + + case let (_, nil, alt?): + guard let parent = fiber.alternate?.elementParent?.element else { break } // todo: name => altParent? - if let alt = fiber.alternate?.element, - let parent = fiber.alternate?.elementParent?.element, - node.newContent == nil - { fiber.element = nil fiber.elementIndex = 0 if let p = fiber.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) - } - if let element = fiber.element, - let index = fiber.elementIndex, - let parent = fiber.elementParent?.element - { - if fiber.alternate == nil { // This didn't exist before (no alternate) - invalidateCache(for: fiber, in: reconciler, caches: caches) - return .insert(element: element, parent: parent, index: index) - } else if fiber.typeInfo?.type != fiber.alternate?.typeInfo?.type, - let previous = fiber.alternate?.element - { - invalidateCache(for: fiber, in: reconciler, caches: caches) - // This is a completely different type of view. - return .replace(parent: parent, previous: previous, replacement: element) - } else if let newContent = node.newContent, - newContent != element.content - { - invalidateCache(for: fiber, in: reconciler, caches: caches) - // This is the same type of view, but its backing data has changed. - return .update( - previous: element, - newContent: newContent, - geometry: fiber.geometry ?? .init( - origin: .init(origin: .zero), - dimensions: .init(size: .zero, alignmentGuides: [:]), - proposal: .unspecified - ) - ) - } + case let (element?, _, _) where fiber.alternate == nil: // todo: does this do the right thing when newContent != nil? can it even be != nil? + guard let parent = fiber.elementParent?.element, + let index = fiber.elementIndex + else { break } + + return .insert(element: element, parent: parent, index: index) + + case let (element?, _, previous?) where !canUpdate(fiber): + guard let parent = fiber.elementParent?.element else { break } + + return .replace(parent: parent, previous: previous, replacement: element) + + default: + break } + return nil } From 55d8073356f7aa7d704a95cd640d669f1f833f24 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 2 Jan 2023 00:19:29 +0100 Subject: [PATCH 10/16] fix going from state a -> b after going around once in RecRep --- Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index 293087c65..d79630173 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -281,7 +281,7 @@ struct ReconcilePass: FiberReconcilerPass { if let p = fiber.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) - case let (element?, _, _) where fiber.alternate == nil: // todo: does this do the right thing when newContent != nil? can it even be != nil? + case let (element?, _, _) where fiber.alternate?.element == nil: // todo: does this do the right thing when newContent != nil? can it even be != nil? guard let parent = fiber.elementParent?.element, let index = fiber.elementIndex else { break } From 09aa4369b64e04b41bd2ab04e1ecae6d77139f73 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 2 Jan 2023 07:51:24 +0100 Subject: [PATCH 11/16] update reinserted element with newContent --- Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index d79630173..20afb1867 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -281,11 +281,12 @@ struct ReconcilePass: FiberReconcilerPass { if let p = fiber.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) - case let (element?, _, _) where fiber.alternate?.element == nil: // todo: does this do the right thing when newContent != nil? can it even be != nil? + case let (element?, content, _) where fiber.alternate?.element == nil: guard let parent = fiber.elementParent?.element, let index = fiber.elementIndex else { break } + if let c = content { element.update(with: c) } return .insert(element: element, parent: parent, index: index) case let (element?, _, previous?) where !canUpdate(fiber): From e961334ba7311e7f30d894ef36fb31587ec9e022 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 2 Jan 2023 07:55:18 +0100 Subject: [PATCH 12/16] update and extend test --- .../TestFiberRenderer.swift | 24 +++++++++++++++---- .../TokamakTestRenderer/TestViewProxy.swift | 7 +++++- .../VaryingPrimitivenessTests.swift | 24 +++++++++++-------- .../TokamakReconcilerTests/VisitorTests.swift | 11 ++++----- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Sources/TokamakTestRenderer/TestFiberRenderer.swift b/Sources/TokamakTestRenderer/TestFiberRenderer.swift index 3f4b3a121..faa0a7911 100644 --- a/Sources/TokamakTestRenderer/TestFiberRenderer.swift +++ b/Sources/TokamakTestRenderer/TestFiberRenderer.swift @@ -137,7 +137,7 @@ public final class TestFiberElement: FiberElement, CustomStringConvertible { public static var root: Self { .init(renderedValue: "", closingTag: "") } } -public struct TestFiberRenderer: FiberRenderer { +public final class TestFiberRenderer: FiberRenderer { public let sceneSize: CurrentValueSubject public let useDynamicLayout: Bool @@ -171,13 +171,15 @@ public struct TestFiberRenderer: FiberRenderer { view is TestFiberPrimitive } - public func commit(_ mutations: [Mutation]) { + public func commit(_ mutations: [Mutation]) { for mutation in mutations { switch mutation { case let .insert(element, parent, index): parent.children.insert(element, at: index) case let .remove(element, parent): - parent?.children.removeAll(where: { $0 === element }) + guard let idx = parent?.children.firstIndex(where: { $0 === element }) + else { fatalError("remove called with element that doesn't belong to its parent") } + parent?.children.remove(at: idx) case let .replace(parent, previous, replacement): guard let index = parent.children.firstIndex(where: { $0 === previous }) else { continue } @@ -192,7 +194,21 @@ public struct TestFiberRenderer: FiberRenderer { } } + public func render(_ view: V) -> FiberReconciler { + let ret = FiberReconciler(self, view) + flush() + return ret + } + + var scheduledActions: [() -> Void] = [] + public func schedule(_ action: @escaping () -> ()) { - action() + scheduledActions.append(action) + } + + public func flush() { + let actions = scheduledActions + scheduledActions = [] + for a in actions { a() } } } diff --git a/Sources/TokamakTestRenderer/TestViewProxy.swift b/Sources/TokamakTestRenderer/TestViewProxy.swift index 34dd51c73..10843edd2 100644 --- a/Sources/TokamakTestRenderer/TestViewProxy.swift +++ b/Sources/TokamakTestRenderer/TestViewProxy.swift @@ -17,7 +17,7 @@ import Foundation -@_spi(TokamakCore) +@_spi(TokamakCore) @testable import TokamakCore /// A proxy for an identified view in the `TestFiberRenderer`. @@ -63,6 +63,11 @@ public struct TestViewProxy { public subscript(dynamicMember member: KeyPath) -> T? { self.view?[keyPath: member] } + + public func tap() where V == Button { + self.action?() + reconciler.renderer.flush() + } } /// An erased `IdentifiedView`. diff --git a/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift b/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift index b693f12cc..6fb1b6c81 100644 --- a/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift +++ b/Tests/TokamakReconcilerTests/VaryingPrimitivenessTests.swift @@ -84,8 +84,7 @@ final class VaryingPrimitivenessTests: XCTestCase { XCTAssertEqual(root.children.count, 1) // button style XCTAssertEqual(root.children[0].children.count, 1) // text - reconciler.findView(id: "a.1", as: Button.self).action?() - reconciler.findView(id: "a.1", as: Button.self).action?() + reconciler.findView(id: "a.1", as: Button.self).tap() XCTAssertEqual(root.children.count, 1) XCTAssert(root.children[0].description.contains("VStack")) @@ -93,8 +92,7 @@ final class VaryingPrimitivenessTests: XCTestCase { XCTAssert(root.children[0].children[0].description.contains("Text")) XCTAssert(root.children[0].children[1].description.contains("ButtonStyle")) - reconciler.findView(id: "b.zwei", as: Button.self).action?() - reconciler.findView(id: "b.zwei", as: Button.self).action?() + reconciler.findView(id: "b.zwei", as: Button.self).tap() XCTAssertEqual(root.children.count, 1) XCTAssert(root.children[0].description.contains("VStack")) @@ -103,26 +101,32 @@ final class VaryingPrimitivenessTests: XCTestCase { XCTAssert(root.children[0].children[1].description.contains("Text")) XCTAssert(root.children[0].children[2].description.contains("ButtonStyle")) - reconciler.findView(id: "c.i = 2", as: Button.self).action?() - reconciler.findView(id: "c.i = 2", as: Button.self).action?() + reconciler.findView(id: "c.i = 2", as: Button.self).tap() XCTAssertEqual(root.children[0].children.count, 3) // stack content - reconciler.findView(id: "d.back", as: Button.self).action?() - reconciler.findView(id: "d.back", as: Button.self).action?() + reconciler.findView(id: "d.back", as: Button.self).tap() XCTAssertEqual(root.children.count, 1) XCTAssert(root.children[0].description.contains("ButtonStyle")) XCTAssertEqual(root.children[0].children.count, 1) XCTAssert(root.children[0].children[0].description.contains("Text")) - reconciler.findView(id: "a.1", as: Button.self).action?() - reconciler.findView(id: "a.1", as: Button.self).action?() + reconciler.findView(id: "a.1", as: Button.self).tap() XCTAssertEqual(root.children.count, 1) XCTAssert(root.children[0].description.contains("VStack")) XCTAssertEqual(root.children[0].children.count, 4) // stack content XCTAssert(root.children[0].children[0].description.contains("Text")) XCTAssert(root.children[0].children[1].description.contains("ButtonStyle")) + + reconciler.findView(id: "b.zwei", as: Button.self).tap() + + XCTAssertEqual(root.children.count, 1) + XCTAssert(root.children[0].description.contains("VStack")) + XCTAssertEqual(root.children[0].children.count, 4) // stack content + XCTAssert(root.children[0].children[0].description.contains("Text")) + XCTAssert(root.children[0].children[1].description.contains("Text")) + XCTAssert(root.children[0].children[2].description.contains("ButtonStyle")) } } diff --git a/Tests/TokamakReconcilerTests/VisitorTests.swift b/Tests/TokamakReconcilerTests/VisitorTests.swift index f0b3a9748..a6d7e9f36 100644 --- a/Tests/TokamakReconcilerTests/VisitorTests.swift +++ b/Tests/TokamakReconcilerTests/VisitorTests.swift @@ -64,14 +64,14 @@ final class VisitorTests: XCTestCase { // Count up to 5 for i in 0..<5 { XCTAssertEqual(countText.view, Text("\(i)")) - incrementButton.action?() + incrementButton.tap() } XCTAssertNil(incrementButton.view, "'Increment' should be hidden when count >= 5") XCTAssertNotNil(decrementButton.view, "'Decrement' should be visible when count > 0") // Count down to 0. for i in 0..<5 { XCTAssertEqual(countText.view, Text("\(5 - i)")) - decrementButton.action?() + decrementButton.tap() } XCTAssertNil(decrementButton.view, "'Decrement' should be hidden when count <= 0") XCTAssertNotNil(incrementButton.view, "'Increment' should be visible when count < 5") @@ -99,7 +99,7 @@ final class VisitorTests: XCTestCase { let addItemButton = reconciler.findView(id: "addItem", as: Button.self) XCTAssertNotNil(addItemButton) for i in 0..<10 { - addItemButton.action?() + addItemButton.tap() XCTAssertEqual(reconciler.findView(id: i).view, Text("Item \(i)")) } } @@ -195,7 +195,7 @@ final class VisitorTests: XCTestCase { // State let button = reconciler.findView(id: DynamicPropertyTest.state, as: Button.self) XCTAssertEqual(button.label, Text("0")) - button.action?() + button.tap() XCTAssertEqual(button.label, Text("1")) // Environment @@ -210,8 +210,7 @@ final class VisitorTests: XCTestCase { as: Button.self ) XCTAssertEqual(stateObjectButton.label, Text("0")) - stateObjectButton.action?() - stateObjectButton.action?() + stateObjectButton.tap() XCTAssertEqual(stateObjectButton.label, Text("5")) XCTAssertEqual( From 514599fbd9ae1676df4f8326f6cdfc9b1005a360 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 23 Jan 2023 17:50:35 +0100 Subject: [PATCH 13/16] fix wrong parent in remove mutation --- Sources/TokamakCore/Fiber/FiberReconciler.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index 2951c9cf6..f38000da7 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -277,6 +277,13 @@ public final class FiberReconciler { alternate is \(self.alternate.recursiveDescription) current is \(current.recursiveDescription) """) + // copy over elements to the alternate, so when an element is + // replaced, the old element can still be accessed to emit + // removal mutations for its children + walk(current) { node in + node.alternate?.element = node.element + return true + } isReconciling = false From 6583249ab70897dd319c5e503f2f1102562aa516 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 23 Jan 2023 17:52:45 +0100 Subject: [PATCH 14/16] clean up reproducer target --- Package.swift | 12 ----- Sources/RecRep/fiber-reproducer.swift | 64 --------------------------- 2 files changed, 76 deletions(-) delete mode 100644 Sources/RecRep/fiber-reproducer.swift diff --git a/Package.swift b/Package.swift index cd1ce60ae..c062c573c 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,6 @@ let package = Package( name: "TokamakDemo", targets: ["TokamakDemo"] ), - .executable(name: "RecRep", targets: ["RecRep"]), .library( name: "TokamakDOM", targets: ["TokamakDOM"] @@ -168,17 +167,6 @@ let package = Package( "OpenCombineJS", ] ), - .executableTarget( - name: "RecRep", - dependencies: [ - "TokamakShim", - .product( - name: "JavaScriptKit", - package: "JavaScriptKit", - condition: .when(platforms: [.wasi]) - ), - ] - ), .executableTarget( name: "TokamakDemo", dependencies: [ diff --git a/Sources/RecRep/fiber-reproducer.swift b/Sources/RecRep/fiber-reproducer.swift deleted file mode 100644 index b778664a4..000000000 --- a/Sources/RecRep/fiber-reproducer.swift +++ /dev/null @@ -1,64 +0,0 @@ -import TokamakDOM -import Foundation - -@main -struct TokamakApp: App { - static let _configuration: _AppConfiguration = .init(reconciler: .fiber(useDynamicLayout: false)) - var body: some Scene { WindowGroup("Tokamak App") { ContentView() } } -} - -enum State { - case a - case b([String]) - case c(String, [Int]) - case d(String, [Int], String) -} - -final class StateManager: ObservableObject { - private init() { } - static let shared = StateManager() - - @Published var state = State.a //b(["eins", "2", "III"]) -} - -struct ContentView: View { - @ObservedObject var sm = StateManager.shared - - var body: some View { - switch sm.state { - case .a: - Button("go to b") { - sm.state = .b(["eins", "zwei", "drei"]) - } - case .b(let arr): - VStack { - Text("b:") - ForEach(arr, id: \.self) { s in - Button(s) { - sm.state = .c(s, s == "zwei" ? [1, 2] : [1]) - } - } - } - case .c(let str, let ints): - VStack { - Text("c \(str)") - .font(.headline) - Text("hello there") - ForEach(ints, id: \.self) { i in - let d = "i = \(i)" - Button(d) { - sm.state = .d(str, ints, d) - } - } - } - case .d(_, let ints, let other): - VStack { - Text("d \(other)") - Text("count \(ints.count)") - Button("back") { - sm.state = .a - } - } - } - } -} From 0590a9e768117b2b86224d65e7a3db3f1bf11613 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 23 Jan 2023 18:11:52 +0100 Subject: [PATCH 15/16] undo logging changes --- .../Fiber+CustomDebugStringConvertible.swift | 13 +++++------ .../TokamakCore/Fiber/FiberReconciler.swift | 7 ------ Sources/TokamakDOM/DOMFiberRenderer.swift | 7 ------ .../TestFiberRenderer.swift | 22 +++++-------------- 4 files changed, 11 insertions(+), 38 deletions(-) diff --git a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift index da907a25c..49f98bb04 100644 --- a/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift +++ b/Sources/TokamakCore/Fiber/Fiber+CustomDebugStringConvertible.swift @@ -34,14 +34,13 @@ extension FiberReconciler.Fiber: CustomDebugStringConvertible { proposal: .unspecified ) return """ - \(spaces)\(debugDescription)\(element != nil ? "(\(element!))" : "") - \(child?.flush(level: level + 2) ?? "")\ + \(spaces)\(String(describing: typeInfo?.type ?? Any.self) + .split(separator: "<")[0])\(element != nil ? "(\(element!))" : "") {\(element != nil ? + "\n\(spaces)geometry: \(geometry)" : + "") + \(child?.flush(level: level + 2) ?? "") + \(spaces)} \(sibling?.flush(level: level) ?? "") """ } - - public var recursiveDescription: String { - flush() - } - } diff --git a/Sources/TokamakCore/Fiber/FiberReconciler.swift b/Sources/TokamakCore/Fiber/FiberReconciler.swift index f38000da7..c5b1bc910 100644 --- a/Sources/TokamakCore/Fiber/FiberReconciler.swift +++ b/Sources/TokamakCore/Fiber/FiberReconciler.swift @@ -270,13 +270,6 @@ public final class FiberReconciler { self.alternate = current current = alternate - print(""" - reconcile done - mutations were: - \(visitor.mutations.map { " \($0)" }.joined(separator: "\n")) - alternate is \(self.alternate.recursiveDescription) - current is \(current.recursiveDescription) - """) // copy over elements to the alternate, so when an element is // replaced, the old element can still be accessed to emit // removal mutations for its children diff --git a/Sources/TokamakDOM/DOMFiberRenderer.swift b/Sources/TokamakDOM/DOMFiberRenderer.swift index dc1814695..ea57ca416 100644 --- a/Sources/TokamakDOM/DOMFiberRenderer.swift +++ b/Sources/TokamakDOM/DOMFiberRenderer.swift @@ -57,13 +57,6 @@ public final class DOMElement: FiberElement { } } -extension DOMElement: CustomStringConvertible { - public var description: String { - "DOMElement(tag: \(content.tag), attributes: \(content.attributes.filter { $0.key != "style" }), innerHTML: \(content.innerHTML ?? "nil"))" - } -} - - public extension DOMElement.Content { init(from primitiveView: V, useDynamicLayout: Bool) where V: View { guard let primitiveView = primitiveView as? HTMLConvertible else { fatalError() } diff --git a/Sources/TokamakTestRenderer/TestFiberRenderer.swift b/Sources/TokamakTestRenderer/TestFiberRenderer.swift index faa0a7911..d66f541e4 100644 --- a/Sources/TokamakTestRenderer/TestFiberRenderer.swift +++ b/Sources/TokamakTestRenderer/TestFiberRenderer.swift @@ -106,23 +106,11 @@ public final class TestFiberElement: FiberElement, CustomStringConvertible { } public var description: String { - let memoryAddress = String(format: "%010p", unsafeBitCast(self, to: Int.self)) - return content.renderedValue + " (\(memoryAddress)) [\(children.count)]" - } - - public var recursiveDescription: String { - var d = description - if !children.isEmpty { - d.append("\n") - d.append( - children - .flatMap { $0.recursiveDescription.components(separatedBy:"\n").map { " \($0)"} } - .joined(separator: "\n") - ) - d.append("\n") - } - d.append(content.closingTag) - return d + """ + \(content.renderedValue) + \(children.map { " \($0.description)" }.joined(separator: "\n")) + \(content.closingTag) + """ } public init(renderedValue: String, closingTag: String) { From e15de26f0f3fd0cde9df7aca9b341702f2dc1aa1 Mon Sep 17 00:00:00 2001 From: Lukas Stabe Date: Mon, 23 Jan 2023 20:31:25 +0100 Subject: [PATCH 16/16] simplify case pattern --- Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift index 20afb1867..64f9a16f3 100644 --- a/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift +++ b/Sources/TokamakCore/Fiber/Passes/ReconcilePass.swift @@ -281,7 +281,7 @@ struct ReconcilePass: FiberReconcilerPass { if let p = fiber.elementParent { caches.elementIndices[ObjectIdentifier(p)]? -= 1 } return .remove(element: alt, parent: parent) - case let (element?, content, _) where fiber.alternate?.element == nil: + case let (element?, content, nil): guard let parent = fiber.elementParent?.element, let index = fiber.elementIndex else { break }