This commit is contained in:
Lukas Stabe 2022-10-28 02:42:48 +02:00
parent ce73da1bcb
commit 5d5e67f020
8 changed files with 213 additions and 67 deletions

View File

@ -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",

View File

@ -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
}
}
}
}
}

View File

@ -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()
}
}

View File

@ -270,6 +270,14 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
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 {

View File

@ -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)
}

View File

@ -298,7 +298,6 @@ public struct DOMFiberRenderer: FiberRenderer {
public func commit(_ mutations: [Mutation<Self>]) {
for mutation in mutations {
print(mutation)
switch mutation {
case let .insert(newElement, parent, index):
let element = createElement(newElement)

View File

@ -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

View File

@ -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<Text>.self).action?()
reconciler.findView(id: "a.1", as: Button<Text>.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<Text>.self).action?()
reconciler.findView(id: "b.zwei", as: Button<Text>.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<Text>.self).action?()
reconciler.findView(id: "c.i = 2", as: Button<Text>.self).action?()
XCTAssertEqual(root.children[0].children.count, 3) // stack content
reconciler.findView(id: "d.back", as: Button<Text>.self).action?()
reconciler.findView(id: "d.back", as: Button<Text>.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<Text>.self).action?()
reconciler.findView(id: "a.1", as: Button<Text>.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"))
}
}