///|
/// Stable uid assignment for incremental reflow.
///
/// `LayoutTree` keys its per-node layout cache by `Node.uid`, but the dynamic-
/// rendering path rebuilds the render node tree from scratch on every DOM
/// mutation, so a plain `next_uid()` would hand every node a fresh uid and the
/// cache would never match (see docs/incremental-reflow-design.md). A
/// `UidRegistry` persists across rebuilds and maps a node's stable identity to a
/// stable uid:
///
/// - elements carry their originating DomTree node id (`Node.dom_id`, phase A),
/// so they key on that and survive moves / re-renders;
/// - anonymous / text boxes (no `dom_id`) key on their owning node's stable uid
/// plus their child index, which is stable as long as the surrounding
/// structure is.
///
/// uids are allocated from the registry's own monotonic counter, so they are
/// unique within the registry (and therefore within any one `LayoutTree` built
/// from a stabilized tree).
pub struct UidRegistry {
dom_to_uid : Map[Int, Int]
anon_to_uid : Map[String, Int]
mut next : Int
}
///|
pub fn UidRegistry::new() -> UidRegistry {
{ dom_to_uid: {}, anon_to_uid: {}, next: 0 }
}
///|
fn UidRegistry::alloc(self : UidRegistry) -> Int {
let uid = self.next
self.next = uid + 1
uid
}
///|
/// The stable uid for an element with DomTree node id `dom_id`.
fn UidRegistry::for_dom(self : UidRegistry, dom_id : Int) -> Int {
match self.dom_to_uid.get(dom_id) {
Some(uid) => uid
None => {
let uid = self.alloc()
self.dom_to_uid[dom_id] = uid
uid
}
}
}
///|
/// The stable uid for an anonymous / text node, keyed by its parent's stable uid
/// and its position among siblings.
fn UidRegistry::for_anon(
self : UidRegistry,
parent_uid : Int,
index : Int,
) -> Int {
let key = parent_uid.to_string() + ":" + index.to_string()
match self.anon_to_uid.get(key) {
Some(uid) => uid
None => {
let uid = self.alloc()
self.anon_to_uid[key] = uid
uid
}
}
}
///|
fn stabilize_walk(
node : Node,
registry : UidRegistry,
parent_uid : Int,
index : Int,
) -> Unit {
let uid = match node.dom_id {
Some(dom_id) => registry.for_dom(dom_id)
None => registry.for_anon(parent_uid, index)
}
node.set_uid(uid)
for i, child in node.children {
stabilize_walk(child, registry, uid, i)
}
}
///|
/// Rewrite every uid in `root` to its stable value from `registry`, in place.
/// Call this on a freshly built render node tree before handing it to
/// `LayoutTree::from_node` / `reconcile_from`: nodes that map to the same DomTree
/// element (or the same anonymous slot) keep their uid across rebuilds, so the
/// incremental layout cache matches.
pub fn stabilize_uids(root : Node, registry : UidRegistry) -> Unit {
stabilize_walk(root, registry, -1, 0)
}