mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Merge pull request #14818 from NousResearch/ink-perf
perf(ink): cache text measurements across yoga flex re-passes
This commit is contained in:
commit
c95c6bdb7c
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
|
// Only set on ink-root. The document owns focus — any node can
|
||||||
// reach it by walking parentNode, like browser getRootNode().
|
// reach it by walking parentNode, like browser getRootNode().
|
||||||
focusManager?: FocusManager
|
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
|
} & InkNode
|
||||||
|
|
||||||
export type TextNode = {
|
export type TextNode = {
|
||||||
|
|
@ -311,10 +315,42 @@ export const createTextNode = (text: string): TextNode => {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MEASURE_CACHE_CAP = 16
|
||||||
|
|
||||||
const measureTextNode = function (
|
const measureTextNode = function (
|
||||||
node: DOMNode,
|
node: DOMNode,
|
||||||
width: number,
|
width: number,
|
||||||
widthMode: LayoutMeasureMode
|
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 } {
|
): { width: number; height: number } {
|
||||||
const rawText = node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node)
|
const rawText = node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node)
|
||||||
|
|
||||||
|
|
@ -378,13 +414,19 @@ export const markDirty = (node?: DOMNode): void => {
|
||||||
|
|
||||||
while (current) {
|
while (current) {
|
||||||
if (current.nodeName !== '#text') {
|
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
|
// Only mark yoga dirty on leaf nodes that have measure functions
|
||||||
if (!markedYoga && (current.nodeName === 'ink-text' || current.nodeName === 'ink-raw-ansi') && current.yogaNode) {
|
if (!markedYoga && (elem.nodeName === 'ink-text' || elem.nodeName === 'ink-raw-ansi') && elem.yogaNode) {
|
||||||
current.yogaNode.markDirty()
|
elem.yogaNode.markDirty()
|
||||||
markedYoga = true
|
markedYoga = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidate text measurement cache — child text or style changed.
|
||||||
|
if (elem._textMeasureCache) {
|
||||||
|
elem._textMeasureCache.gen++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = current.parentNode
|
current = current.parentNode
|
||||||
|
|
@ -433,6 +475,7 @@ export const clearYogaNodeReferences = (node: DOMElement | TextNode): void => {
|
||||||
for (const child of node.childNodes) {
|
for (const child of node.childNodes) {
|
||||||
clearYogaNodeReferences(child)
|
clearYogaNodeReferences(child)
|
||||||
}
|
}
|
||||||
|
node._textMeasureCache = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
node.yogaNode = undefined
|
node.yogaNode = undefined
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue