Skip to content

Commit

Permalink
perf(timeline): optimize group render and scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
Akryum committed Nov 29, 2021
1 parent 4bed049 commit f1d534f
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 87 deletions.
147 changes: 74 additions & 73 deletions packages/app-frontend/src/features/timeline/TimelineView.vue
Expand Up @@ -47,6 +47,11 @@ const LAYER_SIZE = 16
const GROUP_SIZE = 6
const MIN_CAMERA_SIZE = 10
const propConfig = {
writable: true,
configurable: false,
}
installUnsafeEval(PIXI)
export default defineComponent({
Expand All @@ -57,15 +62,16 @@ export default defineComponent({
const { startTime, endTime, minTime, maxTime } = useTime()
const { darkMode } = useDarkMode()
// Optimize for read in loops
const nonReactiveTime = {
// Optimize for read in loops and hot code
const nonReactiveState = {
startTime: nonReactive(startTime),
endTime: nonReactive(endTime),
minTime: nonReactive(minTime),
darkMode: nonReactive(darkMode),
}
function getTimePosition (time: number) {
return (time - nonReactiveTime.minTime.value) / (nonReactiveTime.endTime.value - nonReactiveTime.startTime.value) * app.view.width / window.devicePixelRatio
return (time - nonReactiveState.minTime.value) / (nonReactiveState.endTime.value - nonReactiveState.startTime.value) * app.view.width / window.devicePixelRatio
}
// Reset
Expand Down Expand Up @@ -159,7 +165,7 @@ export default defineComponent({
})
function updateBackground () {
if (darkMode.value) {
if (nonReactiveState.darkMode.value) {
app && (app.renderer.backgroundColor = 0x0b1015)
} else {
app && (app.renderer.backgroundColor = 0xffffff)
Expand Down Expand Up @@ -355,6 +361,7 @@ export default defineComponent({
// Events
const { selectedEvent } = useSelectedEvent()
const nonReactiveSelectedEvent = nonReactive(selectedEvent)
let events: TimelineEvent[] = []
Expand Down Expand Up @@ -418,8 +425,15 @@ export default defineComponent({
draw()
}
let isEventIgnoredCache: Record<TimelineEvent['id'], boolean> = {}
function isEventIgnored (event: TimelineEvent) {
return event.layer.ignoreNoDurationGroups && event.group && event.group.duration <= 0
let result = isEventIgnoredCache[event.id]
if (result == null) {
result = event.layer.ignoreNoDurationGroups && event.group?.nonReactiveDuration <= 0
isEventIgnoredCache[event.id] = result
}
return result
}
function computeEventVerticalPosition (event: TimelineEvent) {
Expand All @@ -434,7 +448,7 @@ export default defineComponent({
// Collision offset for non-flamecharts
const offset = event.layer.groupsOnly ? 0 : 12
// For flamechart allow 1-pixel overlap at the end of a group
const lastOffset = event.layer.groupsOnly && event.group?.duration > 0 ? -1 : 0
const lastOffset = event.layer.groupsOnly && event.group?.nonReactiveDuration > 0 ? -1 : 0
// Flamechart uses time instead of pixel position
const getPos = event.layer.groupsOnly ? (time: number) => time : getTimePosition
Expand Down Expand Up @@ -483,7 +497,7 @@ export default defineComponent({
)
)) {
// Collision!
if (event.group && event.group.duration > otherGroup.duration && firstEvent.time <= otherGroup.firstEvent.time) {
if (event.group && event.group.nonReactiveDuration > otherGroup.nonReactiveDuration && firstEvent.time <= otherGroup.firstEvent.time) {
// Invert positions because current group has higher priority
if (!updateEventVerticalPositionQueue.has(otherGroup.firstEvent)) {
queueEventPositionUpdate([otherGroup.firstEvent], event.layer.groupsOnly)
Expand Down Expand Up @@ -524,32 +538,18 @@ export default defineComponent({
if (!event.layer.groupsOnly || (event.group?.firstEvent === event)) {
eventContainer = new PIXI.Container()
Object.defineProperty(event, 'container', {
value: eventContainer,
writable: true,
configurable: false,
})
Object.defineProperty(event, 'container', { value: eventContainer, ...propConfig })
layerContainer.addChild(eventContainer)
}
// Group graphics
if (event.group) {
if (event.group.firstEvent === event) {
const groupG = new PIXI.Graphics()
Object.defineProperty(event, 'groupG', {
value: groupG,
writable: true,
configurable: false,
})
Object.defineProperty(event, 'groupT', {
value: null,
writable: true,
configurable: false,
})
Object.defineProperty(event, 'groupText', {
value: null,
writable: true,
configurable: false,
Object.defineProperties(event, {
groupG: { value: groupG, ...propConfig },
groupT: { value: null, ...propConfig },
groupText: { value: null, ...propConfig },
})
eventContainer.addChild(groupG)
event.group.oldSize = null
Expand All @@ -565,11 +565,7 @@ export default defineComponent({
// Graphics
if (eventContainer) {
const g = new PIXI.Graphics()
Object.defineProperty(event, 'g', {
value: g,
writable: true,
configurable: false,
})
Object.defineProperty(event, 'g', { value: g, ...propConfig })
eventContainer.addChild(g)
}
Expand Down Expand Up @@ -618,6 +614,7 @@ export default defineComponent({
e.container = null
}
events = []
isEventIgnoredCache = {}
}
function resetEvents () {
Expand Down Expand Up @@ -736,8 +733,8 @@ export default defineComponent({
if (selected) {
// Border-only style
size--
g.lineStyle(2, boostColor(color, darkMode.value))
g.beginFill(dimColor(color, darkMode.value))
g.lineStyle(2, boostColor(color, nonReactiveState.darkMode.value))
g.beginFill(dimColor(color, nonReactiveState.darkMode.value))
if (!event.group || event.group.firstEvent !== event) {
event.container.zIndex = 999999999
}
Expand All @@ -759,7 +756,7 @@ export default defineComponent({
const drawUnselectedEvent = drawEvent.bind(null, false)
function refreshEventGraphics (event: TimelineEvent) {
if (selectedEvent.value === event) {
if (nonReactiveSelectedEvent.value === event) {
drawSelectedEvent(event)
} else {
drawUnselectedEvent(event)
Expand All @@ -775,8 +772,8 @@ export default defineComponent({
function selectPreviousEvent () {
let index
if (selectedEvent.value) {
index = events.indexOf(selectedEvent.value)
if (nonReactiveSelectedEvent.value) {
index = events.indexOf(nonReactiveSelectedEvent.value)
} else {
index = events.length
}
Expand All @@ -797,8 +794,8 @@ export default defineComponent({
function selectNextEvent () {
let index
if (selectedEvent.value) {
index = events.indexOf(selectedEvent.value)
if (nonReactiveSelectedEvent.value) {
index = events.indexOf(nonReactiveSelectedEvent.value)
} else {
index = -1
}
Expand Down Expand Up @@ -862,38 +859,40 @@ export default defineComponent({
app.stage.addListener('mousemove', mouseEvent => {
const text: string[] = []
// Event tooltip
const event = getEventAtPosition(mouseEvent.data.global.x, mouseEvent.data.global.y)
if (event) {
text.push(event.title ?? 'Event')
if (event.subtitle) {
text.push(event.subtitle)
}
text.push(formatTime(event.time, 'ms'))
if (!cameraDragging) {
// Event tooltip
const event = getEventAtPosition(mouseEvent.data.global.x, mouseEvent.data.global.y)
if (event) {
text.push(event.title ?? 'Event')
if (event.subtitle) {
text.push(event.subtitle)
}
text.push(formatTime(event.time, 'ms'))
if (event.group) {
text.push(`Group: ${event.group.duration}ms (${event.group.events.length} event${event.group.events.length > 1 ? 's' : ''})`)
}
if (event.group) {
text.push(`Group: ${event.group.nonReactiveDuration}ms (${event.group.events.length} event${event.group.events.length > 1 ? 's' : ''})`)
}
if (event?.container) {
event.container.alpha = 0.5
}
} else {
// Marker tooltip
const marker = getMarkerAtPosition(mouseEvent.data.global.x)
if (marker) {
text.push(marker.label)
text.push(formatTime(marker.time, 'ms'))
text.push('(marker)')
if (event?.container) {
event.container.alpha = 0.5
}
} else {
// Marker tooltip
const marker = getMarkerAtPosition(mouseEvent.data.global.x)
if (marker) {
text.push(marker.label)
text.push(formatTime(marker.time, 'ms'))
text.push('(marker)')
}
}
}
if (event !== hoverEvent) {
if (hoverEvent?.container) {
hoverEvent.container.alpha = 1
if (event !== hoverEvent) {
if (hoverEvent?.container) {
hoverEvent.container.alpha = 1
}
draw()
}
draw()
hoverEvent = event
}
hoverEvent = event
if (text.length) {
// Draw tooltip
Expand Down Expand Up @@ -929,7 +928,7 @@ export default defineComponent({
function drawEventGroup (event: TimelineEvent) {
if (event.groupG) {
const drawAsSelected = event === selectedEvent.value && event.layer.groupsOnly
const drawAsSelected = event === nonReactiveSelectedEvent.value && event.layer.groupsOnly
/** @type {PIXI.Graphics} */
const g = event.groupG
Expand All @@ -938,14 +937,14 @@ export default defineComponent({
g.clear()
if (event.layer.groupsOnly) {
if (drawAsSelected) {
g.lineStyle(2, boostColor(event.layer.color, darkMode.value))
g.beginFill(dimColor(event.layer.color, darkMode.value, 30))
g.lineStyle(2, boostColor(event.layer.color, nonReactiveState.darkMode.value))
g.beginFill(dimColor(event.layer.color, nonReactiveState.darkMode.value, 30))
} else {
g.beginFill(event.layer.color, 0.5)
}
} else {
g.lineStyle(1, dimColor(event.layer.color, darkMode.value))
g.beginFill(dimColor(event.layer.color, darkMode.value, 25))
g.lineStyle(1, dimColor(event.layer.color, nonReactiveState.darkMode.value))
g.beginFill(dimColor(event.layer.color, nonReactiveState.darkMode.value, 25))
}
if (event.layer.groupsOnly) {
g.drawRect(0, -LAYER_SIZE / 2, size - 1, LAYER_SIZE - 1)
Expand All @@ -959,16 +958,18 @@ export default defineComponent({
if (event.layer.groupsOnly && event.title && size > 32) {
let t = event.groupT
let text = event.groupText
if (!t) {
if (!text) {
text = `${SharedData.debugInfo ? `${event.id} ` : ''}${event.title} ${event.subtitle}`
event.groupText = text
}
if (!t) {
t = event.groupT = new PIXI.BitmapText('', {
fontName: 'Roboto Mono',
tint: darkMode.value ? 0xffffff : 0,
tint: nonReactiveState.darkMode.value ? 0xffffff : 0,
})
t.x = 1
t.y = Math.round(-t.height / 2)
t.dirty = false
event.groupText = text
event.container.addChild(t)
}
t.text = text.slice(0, Math.floor((size - 1) / 6))
Expand Down
28 changes: 14 additions & 14 deletions packages/app-frontend/src/features/timeline/composable/events.ts
Expand Up @@ -37,14 +37,15 @@ export function onEventAdd (cb: AddEventCb) {
}

export function addEvent (appId: string, eventOptions: TimelineEvent, layer: Layer) {
const descriptor = {
writable: true,
configurable: false,
}

// Non-reactive content
const event = {} as TimelineEvent
for (const key in eventOptions) {
Object.defineProperty(event, key, {
value: eventOptions[key],
writable: true,
configurable: false,
})
Object.defineProperty(event, key, { value: eventOptions[key], ...descriptor })
}

if (layer.eventsMap[event.id]) return
Expand All @@ -54,8 +55,10 @@ export function addEvent (appId: string, eventOptions: TimelineEvent, layer: Lay
resetTime()
}

event.layer = layer
event.appId = appId
Object.defineProperties(event, {
layer: { value: layer, ...descriptor },
appId: { value: appId, ...descriptor },
})
layer.events.push(event)
layer.eventsMap[event.id] = event

Expand All @@ -65,17 +68,14 @@ export function addEvent (appId: string, eventOptions: TimelineEvent, layer: Lay
if (!group) {
group = layer.groupsMap[event.groupId] = {
events: [],
firstEvent: event,
lastEvent: event,
duration: 0,
} as EventGroup
const descriptor = {
writable: true,
configurable: false,
}
Object.defineProperties(group, {
id: { value: event.groupId, ...descriptor },
y: { value: 0, ...descriptor },
firstEvent: { value: event, ...descriptor },
lastEvent: { value: event, ...descriptor },
nonReactiveDuration: { value: 0, ...descriptor },
oldSize: { value: null, ...descriptor },
oldSelected: { value: null, ...descriptor },
})
Expand All @@ -86,7 +86,7 @@ export function addEvent (appId: string, eventOptions: TimelineEvent, layer: Lay
}
group.events.push(event)
group.lastEvent = event
group.duration = event.time - group.firstEvent.time
group.duration = group.nonReactiveDuration = event.time - group.firstEvent.time
event.group = group
}

Expand Down
Expand Up @@ -17,6 +17,7 @@ export interface EventGroup {
firstEvent: TimelineEvent
lastEvent: TimelineEvent
duration: number
nonReactiveDuration: number
y: number
oldSize?: number
oldSelected?: boolean
Expand Down

0 comments on commit f1d534f

Please sign in to comment.