TokamakUI / Tokamak

SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
Apache License 2.0
2.62k stars 111 forks source link

Fiber layout/reconciliation issues #523

Open ahti opened 2 years ago

ahti commented 2 years ago
Sample code

``` 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)") } } } } ```

Describe the bug Using the fiber reconciler, the sample code above misbehaves in various ways:

To Reproduce Steps to reproduce the behavior:

  1. Run the sample code
  2. Click "go to b"
  3. => The correct buttons for state b appear, but the text does not
  4. Click any button
  5. => The "i = number" buttons for state c appear, but some from state b are left over, still no text

Changing the configuration to use dynamic layout puts all the buttons on top of each other.

If you add the VStack to wrap the button for state a, and apply the change from #522 the buttons update properly, but the text is still missing.

Expected behavior The way the code behaves when commenting out the _configuration. No lingering buttons, all texts show up.

Desktop:

ahti commented 2 years ago

I did a little bit of digging, and I think the underlying issue here occurs when the view a fiber represents changes between one not being represented by an element (button) and one that should have an element (vstack). ReconcilePass.reconcile(...) doesn't return any mutations when the fiber element is nil, but the tree reducer doesn't create the element when updating an existing fiber, it only sets up newContent.

If I find some more time I'll have a go at actually trying to find a fix, but I don't really know the code base well yet so that might be a while.