Skip to content

Commit

Permalink
VNodeDiscriminator
Browse files Browse the repository at this point in the history
  • Loading branch information
omochi committed Apr 24, 2024
1 parent 7cfd801 commit b4f1a48
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 165 deletions.
52 changes: 27 additions & 25 deletions Sources/React/Renderer/ReactRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,40 +382,42 @@ public final class ReactRoot {
) throws {
var patchedOldChildren: [VNode?] = oldChildren

let match = VNode.match(newChildren: newChildren, oldChildren: oldChildren)

for remove in match.removes {
patchedOldChildren.remove(at: remove.offset)

if remove.isMove {
// process on insert
} else {
try renderNode(new: nil, old: remove.node)
}
}
let diff = VNode.match(newChildren: newChildren, oldChildren: oldChildren)

var nextIndex = 0

for insert in match.inserts {
for index in nextIndex..<insert.offset {
let newNode = newChildren[index]
let oldNode = try patchedOldChildren[index].unwrap("updating oldNode")
try renderNode(new: newNode, old: oldNode)
}
nextIndex = insert.offset + 1
for patch in diff {
switch patch {
case .remove(offset: let offset, element: let old, associatedWith: let assoc):
patchedOldChildren.remove(at: offset)

patchedOldChildren.insert(nil, at: insert.offset)
if assoc != nil {
// process on insert
} else {
try renderNode(new: nil, old: old)
}
case .insert(offset: let offset, element: let newNode, associatedWith: let assoc):
while nextIndex < offset {
let newNode = newChildren[nextIndex]
let oldNode = try patchedOldChildren[nextIndex].unwrap("updating oldNode")
try renderNode(new: newNode, old: oldNode)
nextIndex += 1
}

try renderNode(new: insert.newNode, old: insert.oldNode, isMove: insert.oldNode != nil)
patchedOldChildren.insert(nil, at: offset)

let oldNode = assoc.map { oldChildren[$0] }
try renderNode(new: newNode, old: oldNode, isMove: oldNode != nil)
nextIndex += 1
}
}

for index in nextIndex..<newChildren.count {
let newNode = newChildren[index]
let oldNode = try patchedOldChildren[index].unwrap("updating oldNode")
while nextIndex < newChildren.count {
let newNode = newChildren[nextIndex]
let oldNode = try patchedOldChildren[nextIndex].unwrap("updating oldNode")
try renderNode(new: newNode, old: oldNode)
nextIndex += 1
}

nextIndex = newChildren.count
}

private func skipRenderChildren(newTree: VNode, oldTree: VNode?, isMove: Bool) throws {
Expand Down
42 changes: 1 addition & 41 deletions Sources/React/VDOM/VNode.swift
Original file line number Diff line number Diff line change
@@ -1,47 +1,15 @@
import SRTCore
import SRTDOM

package final class VNode: Hashable {
public struct Equality: Hashable {
enum Kind: Hashable {
case tag(String)
case component(ObjectIdentifier)
}

var kind: Kind
var key: AnyHashable?

init(component: any Component) {
self.kind = if let tag = component as? HTMLElement {
.tag(tag.tagName)
} else {
.component(ObjectIdentifier(type(of: component)))
}

self.key = component.key
}
}

package final class VNode {
public init(
component: any Component
) {
self.component = component
self.kind = switch component {
case let html as HTMLElement: .html(html.tagName)
case is TextElement: .text
default: .component(ObjectIdentifier(type(of: component)))
}
self.discriminator = VNodeDiscriminator(kind: kind, key: component.key)
self.equality = Equality(component: component)
}

public let component: any Component

let kind: VNodeKind
let discriminator: VNodeDiscriminator

public let equality: Equality

package var instance: Instance? {
get { _instance }
set {
Expand All @@ -55,14 +23,6 @@ package final class VNode: Hashable {
internal weak var _parent: VNode?
private var _children: [VNode] = []

public static func ==(a: VNode, b: VNode) -> Bool {
a.equality == b.equality
}

public func hash(into hasher: inout Hasher) {
hasher.combine(equality)
}

public var parent: VNode? { _parent }

public func removeFromParent() {
Expand Down
141 changes: 42 additions & 99 deletions Sources/React/VDOM/VNodeMatch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,128 +4,71 @@ enum VNodeKind: Hashable {
case component(ObjectIdentifier)
}

extension Component {
var vnodeKind: VNodeKind {
switch self {
case let html as HTMLElement: .html(html.tagName)
case is TextElement: .text
default: .component(ObjectIdentifier(type(of: self)))
}
}

var vnodeDiscriminator: VNodeDiscriminator {
VNodeDiscriminator(kind: vnodeKind, key: key)
}
}

struct VNodeDiscriminator: Hashable {
public init(
init(
kind: VNodeKind,
key: AnyHashable?
) {
self.kind = kind
self.key = key
}

public var kind: VNodeKind
public var key: AnyHashable?
var kind: VNodeKind
var key: AnyHashable?
}

struct VNodeMatchRemove {
var offset: Int
struct VNodeMatchAdapter: Hashable {
init(node: VNode) {
self.node = node
self.disc = node.component.vnodeDiscriminator
}

var node: VNode
var isMove: Bool
}
let disc: VNodeDiscriminator

struct VNodeMatchInsert {
var offset: Int
var newNode: VNode
var oldNode: VNode?
}
static func ==(a: Self, b: Self) -> Bool { a.disc == b.disc }

struct VNodeMatch {
var removes: [VNodeMatchRemove]
var inserts: [VNodeMatchInsert]
func hash(into hasher: inout Hasher) {
hasher.combine(disc)
}
}

extension VNode {
static func match(newChildren: [VNode], oldChildren: [VNode]) -> VNodeMatch {
static func match(newChildren: [VNode], oldChildren: [VNode]) -> CollectionDifference<VNode> {
matchStdlib(newChildren: newChildren, oldChildren: oldChildren)
}

static func matchFast(newChildren: [VNode], oldChildren: [VNode]) {
var oldDest: [Int?] = Array(repeating: nil, count: oldChildren.count)
var newSource: [Int?] = Array(repeating: nil, count: newChildren.count)

var newKeyTable: [VNodeDiscriminator: Int] = [:]
for (index, new) in newChildren.enumerated() {
if new.discriminator.key != nil,
newKeyTable[new.discriminator] == nil
{
newKeyTable[new.discriminator] = index
}
}

for (oldIndex, old) in oldChildren.enumerated() {
if old.discriminator.key != nil {
if let newIndex = newKeyTable[old.discriminator] {
oldDest[oldIndex] = newIndex
newSource[newIndex] = oldIndex
}
}
}
static func matchStdlib(newChildren: [VNode], oldChildren: [VNode]) -> CollectionDifference<VNode> {
let newChildren = newChildren.map { VNodeMatchAdapter(node: $0) }
let oldChildren = oldChildren.map { VNodeMatchAdapter(node: $0) }

var newTable: [VNodeDiscriminator: [Int]] = [:]
for (index, new) in newChildren.enumerated() {
if newSource[index] != nil { continue }
newTable[new.discriminator, default: []].append(index)
}

for (oldIndex, old) in oldChildren.enumerated() {
if oldDest[oldIndex] != nil { continue }

if var array = newTable[old.discriminator], !array.isEmpty {
let newIndex = array.removeFirst()
newTable[old.discriminator] = array

oldDest[oldIndex] = newIndex
newSource[newIndex] = oldIndex
}
}

for (oldIndex, old) in oldChildren.enumerated().reversed() {
if oldDest[oldIndex] == nil {
// remove operation

// offset
newSource = newSource.map { (source) in
if let source,
source >= oldIndex
{
return source - 1
} else {
return source
}
}
}
}

// ...?
}

static func matchStdlib(newChildren: [VNode], oldChildren: [VNode]) -> VNodeMatch {
let diff = newChildren
let diff: CollectionDifference<VNodeMatchAdapter> = newChildren
.difference(from: oldChildren)
.inferringMoves()

var removes: [VNodeMatchRemove] = []
var inserts: [VNodeMatchInsert] = []

for patch in diff {
switch patch {
case .remove(offset: let offset, element: let oldNode, associatedWith: let dest):
let x = VNodeMatchRemove(
offset: offset,
node: oldNode,
isMove: dest != nil
)
removes.append(x)
case .insert(offset: let offset, element: let newNode, associatedWith: let source):
let x = VNodeMatchInsert(
offset: offset,
newNode: newNode,
oldNode: source.map { oldChildren[$0] }
)
inserts.append(x)
return CollectionDifference<VNode>(
diff.map { (patch) in
switch patch {
case .remove(offset: let o, element: let e, associatedWith: let a):
.remove(offset: o, element: e.node, associatedWith: a)
case .insert(offset: let o, element: let e, associatedWith: let a):
.insert(offset: o, element: e.node, associatedWith: a)
}
}
}

return VNodeMatch(removes: removes, inserts: inserts)
)!
}
}

0 comments on commit b4f1a48

Please sign in to comment.