Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fiber fixes attempt 2 #542

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
31 changes: 23 additions & 8 deletions Sources/TokamakCore/Fiber/Fiber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ public extension FiberReconciler {
/// Stored as an IUO because it uses `bindProperties` to create the underlying instance.
var layout: AnyLayout?

/// The identity of this `View`
var id: Identity?

/// The mounted element, if this is a `Renderer` primitive.
var element: Renderer.ElementType?

Expand Down Expand Up @@ -135,9 +132,26 @@ public extension FiberReconciler {
}
}

public enum Identity: Hashable {
case explicit(AnyHashable)
case structural(index: Int)
/// The explicit identity of this `View`, if provided
var explicitId: AnyHashable? {
guard case let .view(v as _AnyIDView, _) = content else { return nil }
return v.anyId
}

/// Direct children of this fiber, keyed by their index
var mappedChildren: [Int: Fiber] {
var map = [Int: Fiber]()

var currentIndex = 0
var currentChild = child

while let aChild = currentChild {
map[currentIndex] = aChild
currentIndex += 1
currentChild = aChild.sibling
}

return map
}

init<V: View>(
Expand Down Expand Up @@ -390,8 +404,9 @@ public extension FiberReconciler {
layout = (view as? _AnyLayout)?._erased() ?? DefaultLayout.shared
}

if Renderer.isPrimitive(view) {
return .init(from: view, useDynamicLayout: reconciler?.renderer.useDynamicLayout ?? false)
if Renderer.isPrimitive(view), let element = element {
let newContent = Renderer.ElementType.Content(from: view, useDynamicLayout: reconciler?.renderer.useDynamicLayout ?? false)
return (element.content != newContent) ? newContent : nil
} else {
return nil
}
Expand Down
101 changes: 49 additions & 52 deletions Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,35 @@ extension FiberReconciler {
unowned var parent: Result?
var child: Result?
var sibling: Result?
var newContent: Renderer.ElementType.Content?
var elementIndices: [ObjectIdentifier: Int]
var nextTraits: _ViewTraitStore

// For reducing
var lastSibling: Result?
var nextExisting: Fiber?
var nextExistingAlternate: Fiber?
var processedChildCount: Int
var unclaimedCurrentChildren: [Int: Fiber]

// Side-effects
var didInsert: Bool
var newContent: Renderer.ElementType.Content?

init(
fiber: Fiber?,
currentChildren: [Int: Fiber],
visitChildren: @escaping (TreeReducer.SceneVisitor) -> (),
parent: Result?,
child: Fiber?,
alternateChild: Fiber?,
newContent: Renderer.ElementType.Content? = nil,
elementIndices: [ObjectIdentifier: Int],
nextTraits: _ViewTraitStore
) {
self.fiber = fiber
self.visitChildren = visitChildren
self.parent = parent
nextExisting = child
nextExistingAlternate = alternateChild
self.newContent = newContent
self.elementIndices = elementIndices
self.nextTraits = nextTraits

processedChildCount = 0
unclaimedCurrentChildren = currentChildren

didInsert = false
self.newContent = nil
}
}

Expand All @@ -75,7 +77,8 @@ extension FiberReconciler {
update: { fiber, scene, _, _ in
fiber.update(with: &scene)
},
visitChildren: { $1._visitChildren }
visitChildren: { $1._visitChildren },
isPrimitive: { _ in false }
)
}

Expand Down Expand Up @@ -107,6 +110,9 @@ extension FiberReconciler {
},
visitChildren: { reconciler, view in
reconciler?.renderer.viewVisitor(for: view) ?? view._visitChildren
},
isPrimitive: { view in
Renderer.isPrimitive(view)
}
)
}
Expand All @@ -125,78 +131,69 @@ extension FiberReconciler {
FiberReconciler?
) -> Fiber,
update: (Fiber, inout T, Int?, _ViewTraitStore) -> Renderer.ElementType.Content?,
visitChildren: (FiberReconciler?, T) -> (TreeReducer.SceneVisitor) -> ()
visitChildren: (FiberReconciler?, T) -> (TreeReducer.SceneVisitor) -> (),
isPrimitive: (T) -> Bool
) {
// Create the node and its element.
var nextValue = nextValue

let explicitId = (nextValue as? _AnyIDView)?.anyId
let childIndex = partialResult.processedChildCount

let resultChild: Result
if let existing = partialResult.nextExisting {
// If a fiber already exists, simply update it with the new view.
let key: ObjectIdentifier?
if let elementParent = existing.elementParent {
key = ObjectIdentifier(elementParent)
} else {
key = nil
}
let newContent = update(
existing,
&nextValue,
key.map { partialResult.elementIndices[$0, default: 0] },
partialResult.nextTraits
)
if let existing = partialResult.unclaimedCurrentChildren[childIndex],
existing.typeInfo?.type == typeInfo(of: T.self)?.type,
existing.explicitId == explicitId
{
partialResult.unclaimedCurrentChildren.removeValue(forKey: childIndex)
existing.sibling = nil
let traits = isPrimitive(nextValue) ? .init() : partialResult.nextTraits
let c = update(existing, &nextValue, nil, traits)
resultChild = Result(
fiber: existing,
currentChildren: existing.mappedChildren,
visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue),
parent: partialResult,
child: existing.child,
alternateChild: existing.alternate?.child,
newContent: newContent,
elementIndices: partialResult.elementIndices,
nextTraits: existing.element != nil ? .init() : partialResult.nextTraits
nextTraits: traits
)
partialResult.nextExisting = existing.sibling
partialResult.nextExistingAlternate = partialResult.nextExistingAlternate?.sibling
resultChild.newContent = c
} else {
let elementParent = partialResult.fiber?.element != nil
? partialResult.fiber
: partialResult.fiber?.elementParent
let preferenceParent = partialResult.fiber?.preferences != nil
? partialResult.fiber
: partialResult.fiber?.preferenceParent
let key: ObjectIdentifier?
if let elementParent = elementParent {
key = ObjectIdentifier(elementParent)
} else {
key = nil
}
// Otherwise, create a new fiber for this child.
let fiber = createFiber(
&nextValue,
partialResult.nextExistingAlternate?.element,
nil,
partialResult.fiber,
elementParent,
preferenceParent,
key.map { partialResult.elementIndices[$0, default: 0] },
nil,
partialResult.nextTraits,
partialResult.fiber?.reconciler
)

// If a fiber already exists for an alternate, link them.
if let alternate = partialResult.nextExistingAlternate {
fiber.alternate = alternate
partialResult.nextExistingAlternate = alternate.sibling
let traits: _ViewTraitStore
if isPrimitive(nextValue) {
traits = .init()
} else {
traits = partialResult.nextTraits
}

resultChild = Result(
fiber: fiber,
currentChildren: [:],
visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue),
parent: partialResult,
child: nil,
alternateChild: fiber.alternate?.child,
elementIndices: partialResult.elementIndices,
nextTraits: fiber.element != nil ? .init() : partialResult.nextTraits
nextTraits: traits
)

resultChild.didInsert = true
}

partialResult.processedChildCount += 1

// Get the last child element we've processed, and add the new child as its sibling.
if let lastSibling = partialResult.lastSibling {
lastSibling.fiber?.sibling = resultChild.fiber
Expand Down
5 changes: 2 additions & 3 deletions Sources/TokamakCore/Fiber/FiberReconciler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,10 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
}
let rootResult = TreeReducer.Result(
fiber: alternateRoot, // The alternate is the WIP node.
currentChildren: root.mappedChildren,
visitChildren: visitChildren,
parent: nil,
child: alternateRoot?.child,
alternateChild: root.child,
elementIndices: [:],
newContent: nil,
nextTraits: .init()
)
reconciler.caches.clear()
Expand Down
5 changes: 0 additions & 5 deletions Sources/TokamakCore/Fiber/Mutation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ public enum Mutation<Renderer: FiberRenderer> {
index: Int
)
case remove(element: Renderer.ElementType, parent: Renderer.ElementType?)
case replace(
parent: Renderer.ElementType,
previous: Renderer.ElementType,
replacement: Renderer.ElementType
)
case update(
previous: Renderer.ElementType,
newContent: Renderer.ElementType.Content,
Expand Down
Loading
Loading