mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
perf(ink): cache text measurements across yoga flex re-passes
Adds a per-ink-text measurement cache keyed by width|widthMode to avoid re-squashing and re-wrapping the same text when yoga calls measureFunc multiple times per frame with different widths during flex layout re-pass.
This commit is contained in:
parent
ef5eaf8d87
commit
bd929ea514
1 changed files with 46 additions and 3 deletions
|
|
@ -83,6 +83,10 @@ export type DOMElement = {
|
|||
// Only set on ink-root. The document owns focus — any node can
|
||||
// reach it by walking parentNode, like browser getRootNode().
|
||||
focusManager?: FocusManager
|
||||
// Measurement cache for ink-text nodes: avoids re-squashing and re-wrapping
|
||||
// text when yoga calls measureFunc multiple times per frame with different
|
||||
// widths during flex re-pass. Keyed by `${width}|${widthMode}`.
|
||||
_textMeasureCache?: { gen: number; entries: Map<string, { _gen: number; result: { width: number; height: number } }> }
|
||||
} & InkNode
|
||||
|
||||
export type TextNode = {
|
||||
|
|
@ -311,10 +315,42 @@ export const createTextNode = (text: string): TextNode => {
|
|||
return node
|
||||
}
|
||||
|
||||
const MEASURE_CACHE_CAP = 16
|
||||
|
||||
const measureTextNode = function (
|
||||
node: DOMNode,
|
||||
width: number,
|
||||
widthMode: LayoutMeasureMode
|
||||
): { width: number; height: number } {
|
||||
const elem = node.nodeName !== '#text' ? (node as DOMElement) : node.parentNode
|
||||
if (elem && elem.nodeName === 'ink-text') {
|
||||
let cache = elem._textMeasureCache
|
||||
if (!cache) {
|
||||
cache = { gen: 0, entries: new Map() }
|
||||
elem._textMeasureCache = cache
|
||||
}
|
||||
const key = `${width}|${widthMode}`
|
||||
const hit = cache.entries.get(key)
|
||||
if (hit && hit._gen === cache.gen) {
|
||||
return hit.result
|
||||
}
|
||||
const result = computeTextMeasure(node, width, widthMode)
|
||||
// Enforce cap with FIFO eviction to avoid unbounded growth during
|
||||
// pathological frames where yoga probes many widths.
|
||||
if (cache.entries.size >= MEASURE_CACHE_CAP) {
|
||||
const firstKey = cache.entries.keys().next().value
|
||||
cache.entries.delete(firstKey)
|
||||
}
|
||||
cache.entries.set(key, { _gen: cache.gen, result })
|
||||
return result
|
||||
}
|
||||
return computeTextMeasure(node, width, widthMode)
|
||||
}
|
||||
|
||||
const computeTextMeasure = function (
|
||||
node: DOMNode,
|
||||
width: number,
|
||||
widthMode: LayoutMeasureMode
|
||||
): { width: number; height: number } {
|
||||
const rawText = node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node)
|
||||
|
||||
|
|
@ -378,13 +414,19 @@ export const markDirty = (node?: DOMNode): void => {
|
|||
|
||||
while (current) {
|
||||
if (current.nodeName !== '#text') {
|
||||
;(current as DOMElement).dirty = true
|
||||
const elem = current as DOMElement
|
||||
elem.dirty = true
|
||||
|
||||
// Only mark yoga dirty on leaf nodes that have measure functions
|
||||
if (!markedYoga && (current.nodeName === 'ink-text' || current.nodeName === 'ink-raw-ansi') && current.yogaNode) {
|
||||
current.yogaNode.markDirty()
|
||||
if (!markedYoga && (elem.nodeName === 'ink-text' || elem.nodeName === 'ink-raw-ansi') && elem.yogaNode) {
|
||||
elem.yogaNode.markDirty()
|
||||
markedYoga = true
|
||||
}
|
||||
|
||||
// Invalidate text measurement cache — child text or style changed.
|
||||
if (elem._textMeasureCache) {
|
||||
elem._textMeasureCache.gen++
|
||||
}
|
||||
}
|
||||
|
||||
current = current.parentNode
|
||||
|
|
@ -433,6 +475,7 @@ export const clearYogaNodeReferences = (node: DOMElement | TextNode): void => {
|
|||
for (const child of node.childNodes) {
|
||||
clearYogaNodeReferences(child)
|
||||
}
|
||||
node._textMeasureCache = undefined
|
||||
}
|
||||
|
||||
node.yogaNode = undefined
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue