Skip to content

Commit c63d1db

Browse files
committed
feat(webui): Reduce render churn
1 parent 335c690 commit c63d1db

1 file changed

Lines changed: 243 additions & 4 deletions

File tree

packages/webui/src/client/ui/SegmentTimeline/Parts/SegmentTimelinePart.tsx

Lines changed: 243 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,251 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
312312
if (this.highlightTimeout) clearTimeout(this.highlightTimeout)
313313
}
314314

315-
shouldComponentUpdate(nextProps: Readonly<WithTiming<IProps>>, nextState: Readonly<IState>): boolean {
316-
if (!_.isMatch(this.props, nextProps) || !_.isMatch(this.state, nextState)) {
317-
return true
318-
} else {
315+
private static getPlaylistRenderSelector(playlist: DBRundownPlaylist) {
316+
const quickLoopStartId =
317+
playlist.quickLoop?.start && 'id' in playlist.quickLoop.start ? playlist.quickLoop.start.id : undefined
318+
const quickLoopEndId =
319+
playlist.quickLoop?.end && 'id' in playlist.quickLoop.end ? playlist.quickLoop.end.id : undefined
320+
321+
return {
322+
activationId: playlist.activationId,
323+
currentPartInstanceId: playlist.currentPartInfo?.partInstanceId,
324+
nextPartInstanceId: playlist.nextPartInfo?.partInstanceId,
325+
previousPartInstanceId: playlist.previousPartInfo?.partInstanceId,
326+
nextTimeOffset: playlist.nextTimeOffset,
327+
quickLoopRunning: playlist.quickLoop?.running,
328+
quickLoopStartType: playlist.quickLoop?.start?.type,
329+
quickLoopStartId,
330+
quickLoopEndType: playlist.quickLoop?.end?.type,
331+
quickLoopEndId,
332+
}
333+
}
334+
335+
private static getPartRenderSelector(part: PartUi) {
336+
return {
337+
partId: part.partId,
338+
startsAt: part.startsAt,
339+
renderedDuration: part.renderedDuration,
340+
willProbablyAutoNext: part.willProbablyAutoNext,
341+
instanceId: part.instance._id,
342+
instanceTimings: part.instance.timings,
343+
instancePart: part.instance.part,
344+
pieces: part.pieces,
345+
}
346+
}
347+
348+
private static getSegmentRenderSelector(segment: SegmentUi) {
349+
return {
350+
segmentId: segment._id,
351+
outputLayers: segment.outputLayers,
352+
}
353+
}
354+
355+
private static getTimingRenderSelector(props: Readonly<WithTiming<IProps>>) {
356+
const timingId = getPartInstanceTimingId(props.part.instance)
357+
const firstPartTimingId = props.firstPartInSegment
358+
? getPartInstanceTimingId(props.firstPartInSegment.instance)
359+
: undefined
360+
const lastPartTimingId = props.lastPartInSegment
361+
? getPartInstanceTimingId(props.lastPartInSegment.instance)
362+
: undefined
363+
364+
return {
365+
partDuration: props.timingDurations.partDurations?.[timingId],
366+
partDisplayStart: props.timingDurations.partDisplayStartsAt?.[timingId],
367+
partDisplayDuration: props.timingDurations.partDisplayDurations?.[timingId],
368+
partInQuickLoop: props.timingDurations.partsInQuickLoop?.[timingId],
369+
firstPartDisplayStart: firstPartTimingId
370+
? props.timingDurations.partDisplayStartsAt?.[firstPartTimingId]
371+
: undefined,
372+
firstPartDisplayDuration: firstPartTimingId
373+
? props.timingDurations.partDisplayDurations?.[firstPartTimingId]
374+
: undefined,
375+
lastPartDisplayStart: lastPartTimingId
376+
? props.timingDurations.partDisplayStartsAt?.[lastPartTimingId]
377+
: undefined,
378+
lastPartDisplayDuration: lastPartTimingId
379+
? props.timingDurations.partDisplayDurations?.[lastPartTimingId]
380+
: undefined,
381+
}
382+
}
383+
384+
private static hasImmediatePropChanges(
385+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
386+
nextProps: Readonly<Translated<WithTiming<IProps>>>
387+
): boolean {
388+
return (
389+
prevProps.timeToPixelRatio !== nextProps.timeToPixelRatio ||
390+
prevProps.scrollLeft !== nextProps.scrollLeft ||
391+
prevProps.scrollWidth !== nextProps.scrollWidth ||
392+
prevProps.followLiveLine !== nextProps.followLiveLine ||
393+
prevProps.autoNextPart !== nextProps.autoNextPart ||
394+
prevProps.liveLineHistorySize !== nextProps.liveLineHistorySize ||
395+
prevProps.livePosition !== nextProps.livePosition ||
396+
prevProps.isCollapsed !== nextProps.isCollapsed ||
397+
prevProps.isLastInSegment !== nextProps.isLastInSegment ||
398+
prevProps.isAfterLastValidInSegmentAndItsLive !== nextProps.isAfterLastValidInSegmentAndItsLive ||
399+
prevProps.isLastSegment !== nextProps.isLastSegment ||
400+
prevProps.isBudgetGap !== nextProps.isBudgetGap ||
401+
prevProps.isPreview !== nextProps.isPreview ||
402+
prevProps.cropDuration !== nextProps.cropDuration ||
403+
prevProps.className !== nextProps.className ||
404+
prevProps.isLiveSegment !== nextProps.isLiveSegment ||
405+
prevProps.anyPriorPartWasLive !== nextProps.anyPriorPartWasLive ||
406+
prevProps.livePartStartsAt !== nextProps.livePartStartsAt ||
407+
prevProps.livePartDisplayDuration !== nextProps.livePartDisplayDuration ||
408+
prevProps.budgetDuration !== nextProps.budgetDuration ||
409+
prevProps.t !== nextProps.t ||
410+
prevProps.i18n?.language !== nextProps.i18n?.language ||
411+
prevProps.onScroll !== nextProps.onScroll ||
412+
prevProps.onFollowLiveLine !== nextProps.onFollowLiveLine ||
413+
prevProps.onPieceClick !== nextProps.onPieceClick ||
414+
prevProps.onPieceDoubleClick !== nextProps.onPieceDoubleClick ||
415+
prevProps.onPartTooSmallChanged !== nextProps.onPartTooSmallChanged ||
416+
prevProps.onContextMenu !== nextProps.onContextMenu
417+
)
418+
}
419+
420+
private static hasPlaylistRenderChanges(
421+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
422+
nextProps: Readonly<Translated<WithTiming<IProps>>>
423+
): boolean {
424+
return !_.isEqual(
425+
SegmentTimelinePartClass.getPlaylistRenderSelector(prevProps.playlist),
426+
SegmentTimelinePartClass.getPlaylistRenderSelector(nextProps.playlist)
427+
)
428+
}
429+
430+
private static hasPartRenderChanges(
431+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
432+
nextProps: Readonly<Translated<WithTiming<IProps>>>
433+
): boolean {
434+
return !_.isEqual(
435+
SegmentTimelinePartClass.getPartRenderSelector(prevProps.part),
436+
SegmentTimelinePartClass.getPartRenderSelector(nextProps.part)
437+
)
438+
}
439+
440+
private static hasSegmentRenderChanges(
441+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
442+
nextProps: Readonly<Translated<WithTiming<IProps>>>
443+
): boolean {
444+
return !_.isEqual(
445+
SegmentTimelinePartClass.getSegmentRenderSelector(prevProps.segment),
446+
SegmentTimelinePartClass.getSegmentRenderSelector(nextProps.segment)
447+
)
448+
}
449+
450+
private static hasStudioRenderChanges(
451+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
452+
nextProps: Readonly<Translated<WithTiming<IProps>>>
453+
): boolean {
454+
return (
455+
prevProps.studio._id !== nextProps.studio._id ||
456+
!_.isEqual(prevProps.studio.settings, nextProps.studio.settings)
457+
)
458+
}
459+
460+
private static hasCollapsedOutputChanges(
461+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
462+
nextProps: Readonly<Translated<WithTiming<IProps>>>
463+
): boolean {
464+
return !_.isEqual(prevProps.collapsedOutputs, nextProps.collapsedOutputs)
465+
}
466+
467+
private static hasBoundaryPartChanges(
468+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
469+
nextProps: Readonly<Translated<WithTiming<IProps>>>
470+
): boolean {
471+
return (
472+
prevProps.firstPartInSegment?.instance._id !== nextProps.firstPartInSegment?.instance._id ||
473+
prevProps.lastPartInSegment?.instance._id !== nextProps.lastPartInSegment?.instance._id
474+
)
475+
}
476+
477+
private static hasDurationSourceLayerChanges(
478+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
479+
nextProps: Readonly<Translated<WithTiming<IProps>>>
480+
): boolean {
481+
return !_.isEqual(prevProps.showDurationSourceLayers, nextProps.showDurationSourceLayers)
482+
}
483+
484+
private static hasTimingRenderChanges(
485+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
486+
nextProps: Readonly<Translated<WithTiming<IProps>>>
487+
): boolean {
488+
return !_.isEqual(
489+
SegmentTimelinePartClass.getTimingRenderSelector(prevProps),
490+
SegmentTimelinePartClass.getTimingRenderSelector(nextProps)
491+
)
492+
}
493+
494+
private static hasRelevantStateChanges(prevState: Readonly<IState>, nextState: Readonly<IState>): boolean {
495+
return (
496+
prevState.isLive !== nextState.isLive ||
497+
prevState.isNext !== nextState.isNext ||
498+
prevState.isDurationSettling !== nextState.isDurationSettling ||
499+
prevState.durationSettlingStartsAt !== nextState.durationSettlingStartsAt ||
500+
prevState.liveDuration !== nextState.liveDuration ||
501+
prevState.isInQuickLoop !== nextState.isInQuickLoop ||
502+
prevState.isInsideViewport !== nextState.isInsideViewport ||
503+
prevState.isTooSmallForText !== nextState.isTooSmallForText ||
504+
prevState.isTooSmallForDisplay !== nextState.isTooSmallForDisplay ||
505+
prevState.highlight !== nextState.highlight ||
506+
prevState.dropActive !== nextState.dropActive
507+
)
508+
}
509+
510+
private static arePropsEquivalent(
511+
prevProps: Readonly<Translated<WithTiming<IProps>>>,
512+
nextProps: Readonly<Translated<WithTiming<IProps>>>
513+
): boolean {
514+
if (prevProps === nextProps) return true
515+
516+
if (SegmentTimelinePartClass.hasImmediatePropChanges(prevProps, nextProps)) {
517+
return false
518+
}
519+
520+
if (SegmentTimelinePartClass.hasPlaylistRenderChanges(prevProps, nextProps)) {
521+
return false
522+
}
523+
524+
if (SegmentTimelinePartClass.hasPartRenderChanges(prevProps, nextProps)) {
525+
return false
526+
}
527+
528+
if (SegmentTimelinePartClass.hasSegmentRenderChanges(prevProps, nextProps)) {
319529
return false
320530
}
531+
532+
if (SegmentTimelinePartClass.hasStudioRenderChanges(prevProps, nextProps)) {
533+
return false
534+
}
535+
536+
if (SegmentTimelinePartClass.hasCollapsedOutputChanges(prevProps, nextProps)) {
537+
return false
538+
}
539+
540+
if (SegmentTimelinePartClass.hasBoundaryPartChanges(prevProps, nextProps)) {
541+
return false
542+
}
543+
544+
if (SegmentTimelinePartClass.hasDurationSourceLayerChanges(prevProps, nextProps)) {
545+
return false
546+
}
547+
548+
if (SegmentTimelinePartClass.hasTimingRenderChanges(prevProps, nextProps)) {
549+
return false
550+
}
551+
552+
return true
553+
}
554+
555+
shouldComponentUpdate(nextProps: Readonly<Translated<WithTiming<IProps>>>, nextState: Readonly<IState>): boolean {
556+
return (
557+
!SegmentTimelinePartClass.arePropsEquivalent(this.props, nextProps) ||
558+
SegmentTimelinePartClass.hasRelevantStateChanges(this.state, nextState)
559+
)
321560
}
322561

323562
componentDidUpdate(prevProps: Readonly<Translated<WithTiming<IProps>>>, prevState: IState, snapshot?: unknown): void {

0 commit comments

Comments
 (0)