perf(canvas): cache label hit tests and trace scene renderer
This commit is contained in:
parent
faaa741911
commit
c044d26431
|
|
@ -98,6 +98,14 @@ export class LabelCache {
|
|||
}))
|
||||
}
|
||||
|
||||
getAllSections(): readonly CachedSection[] {
|
||||
return this.sections
|
||||
}
|
||||
|
||||
getAllComponents(): readonly CachedComponent[] {
|
||||
return this.components
|
||||
}
|
||||
|
||||
private rebuild(graph: SceneGraph, pageId: string | null): void {
|
||||
this.sections = []
|
||||
this.components = []
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
import type { SceneGraph, SceneNode } from '#core/scene-graph'
|
||||
import type { Vector } from '#core/types'
|
||||
|
||||
import type { LabelCache } from './cache'
|
||||
|
||||
function measureGlyphWidth(font: Font, text: string): number {
|
||||
const glyphIds = font.getGlyphIDs(text)
|
||||
const widths = font.getGlyphWidths(glyphIds)
|
||||
|
|
@ -76,52 +78,88 @@ function walkLabelTree(
|
|||
return result
|
||||
}
|
||||
|
||||
export function hitTestSectionTitle(
|
||||
function hitCachedLabel<T extends { nodeId: string; absX: number; absY: number }>(
|
||||
graph: SceneGraph,
|
||||
items: readonly T[],
|
||||
hit: (node: SceneNode, item: T) => SceneNode | null
|
||||
): SceneNode | null {
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
const item = items[i]
|
||||
const node = graph.getNode(item.nodeId)
|
||||
if (!node || !node.visible) continue
|
||||
const result = hit(node, item)
|
||||
if (result) return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function hitSectionTitle(
|
||||
child: SceneNode,
|
||||
ax: number,
|
||||
ay: number,
|
||||
insideSection: boolean,
|
||||
canvasX: number,
|
||||
canvasY: number,
|
||||
zoom: number,
|
||||
pageId: string,
|
||||
font: Font | null
|
||||
font: Font
|
||||
): SceneNode | null {
|
||||
const textW = measureGlyphWidth(font, child.name)
|
||||
const pillW = Math.min(textW + SECTION_TITLE_PADDING_X * 2, child.width * zoom) / zoom
|
||||
const pillH = SECTION_TITLE_HEIGHT / zoom
|
||||
const gap = SECTION_TITLE_GAP / zoom
|
||||
const hit = rotatePoint(canvasX - ax, canvasY - ay, child.rotation)
|
||||
const pillY = insideSection ? gap : -pillH - gap
|
||||
|
||||
return hitInRect(hit.x, hit.y, 0, pillY, pillW, pillH) ? child : null
|
||||
}
|
||||
|
||||
export function hitTestSectionTitle(graph: SceneGraph, canvasX: number, canvasY: number, zoom: number, pageId: string, font: Font | null, labelCache?: LabelCache): SceneNode | null {
|
||||
if (!font) return null
|
||||
|
||||
if (labelCache) {
|
||||
return hitCachedLabel(graph, labelCache.getAllSections(), (child, section) =>
|
||||
hitSectionTitle(child, section.absX, section.absY, section.nested, canvasX, canvasY, zoom, font)
|
||||
)
|
||||
}
|
||||
|
||||
return walkLabelTree(graph, pageId, (child, _parent, ax, ay, insideSection) => {
|
||||
if (child.type !== 'SECTION') return undefined
|
||||
|
||||
const textW = measureGlyphWidth(font, child.name)
|
||||
const pillW = Math.min(textW + SECTION_TITLE_PADDING_X * 2, child.width * zoom) / zoom
|
||||
const pillH = SECTION_TITLE_HEIGHT / zoom
|
||||
const gap = SECTION_TITLE_GAP / zoom
|
||||
const hit = rotatePoint(canvasX - ax, canvasY - ay, child.rotation)
|
||||
const pillY = insideSection ? gap : -pillH - gap
|
||||
|
||||
return hitInRect(hit.x, hit.y, 0, pillY, pillW, pillH) ? child : undefined
|
||||
return hitSectionTitle(child, ax, ay, insideSection, canvasX, canvasY, zoom, font)
|
||||
})
|
||||
}
|
||||
|
||||
export function hitTestComponentLabel(
|
||||
graph: SceneGraph,
|
||||
function hitComponentLabel(
|
||||
child: SceneNode,
|
||||
ax: number,
|
||||
ay: number,
|
||||
canvasX: number,
|
||||
canvasY: number,
|
||||
zoom: number,
|
||||
pageId: string,
|
||||
font: Font | null
|
||||
font: Font
|
||||
): SceneNode | null {
|
||||
const textW = measureGlyphWidth(font, child.name)
|
||||
const labelW = (COMPONENT_LABEL_ICON_SIZE + COMPONENT_LABEL_ICON_GAP + textW) / zoom
|
||||
const labelH = COMPONENT_LABEL_FONT_SIZE / zoom
|
||||
const gap = COMPONENT_LABEL_GAP / zoom
|
||||
const labelY = ay - labelH - gap
|
||||
|
||||
return hitInRect(canvasX, canvasY, ax, labelY, labelW, labelH) ? child : null
|
||||
}
|
||||
|
||||
export function hitTestComponentLabel(graph: SceneGraph, canvasX: number, canvasY: number, zoom: number, pageId: string, font: Font | null, labelCache?: LabelCache): SceneNode | null {
|
||||
if (!font) return null
|
||||
|
||||
if (labelCache) {
|
||||
return hitCachedLabel(graph, labelCache.getAllComponents(), (child, component) =>
|
||||
hitComponentLabel(child, component.absX, component.absY, canvasX, canvasY, zoom, font)
|
||||
)
|
||||
}
|
||||
|
||||
const LABEL_TYPES = new Set(['COMPONENT', 'COMPONENT_SET'])
|
||||
|
||||
return walkLabelTree(graph, pageId, (child, _parent, ax, ay) => {
|
||||
if (!LABEL_TYPES.has(child.type)) return undefined
|
||||
|
||||
const textW = measureGlyphWidth(font, child.name)
|
||||
const labelW = (COMPONENT_LABEL_ICON_SIZE + COMPONENT_LABEL_ICON_GAP + textW) / zoom
|
||||
const labelH = COMPONENT_LABEL_FONT_SIZE / zoom
|
||||
const gap = COMPONENT_LABEL_GAP / zoom
|
||||
const labelY = ay - labelH - gap
|
||||
|
||||
return hitInRect(canvasX, canvasY, ax, labelY, labelW, labelH) ? child : undefined
|
||||
return hitComponentLabel(child, ax, ay, canvasX, canvasY, zoom, font)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -416,7 +416,8 @@ export class SkiaRenderer {
|
|||
canvasY,
|
||||
this.zoom,
|
||||
this.pageId ?? graph.rootId,
|
||||
this.sectionTitleFont
|
||||
this.sectionTitleFont,
|
||||
this.labelCache
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +428,8 @@ export class SkiaRenderer {
|
|||
canvasY,
|
||||
this.zoom,
|
||||
this.pageId ?? graph.rootId,
|
||||
this.componentLabelFont
|
||||
this.componentLabelFont,
|
||||
this.labelCache
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@ export function createEditor(options?: EditorOptions) {
|
|||
get renderer() {
|
||||
return _renderer
|
||||
},
|
||||
get canvasRenderers() {
|
||||
return [..._renderers]
|
||||
},
|
||||
get textEditor() {
|
||||
return _textEditor
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,13 +33,17 @@ export class RenderProfiler {
|
|||
this.hud = new HudController(ck)
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.hudVisible = !this.hudVisible
|
||||
this.enabled = this.hudVisible
|
||||
setVisible(visible: boolean): void {
|
||||
this.hudVisible = visible
|
||||
this.enabled = visible
|
||||
this.phases.enabled = this.enabled
|
||||
this.syncInstrumentation()
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.setVisible(!this.hudVisible)
|
||||
}
|
||||
|
||||
beginFrame(): void {
|
||||
if (!this.enabled) return
|
||||
this.renderStartTime = now()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ export function createProfilerActions(editor: Editor) {
|
|||
}
|
||||
|
||||
function toggleProfiler() {
|
||||
editor.renderer?.profiler.toggle()
|
||||
const visible = !(editor.renderer?.profiler.hudVisible ?? false)
|
||||
for (const renderer of editor.canvasRenderers) {
|
||||
renderer.profiler.setVisible(visible)
|
||||
}
|
||||
editor.requestRepaint()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue