mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(studio): enable timeline virtualization by default (#2926)
* feat(studio): enable timeline virtualization by default * fix(ci): measure timeline performance in production React
This commit is contained in:
@@ -41,6 +41,10 @@ import { formatTime } from "../lib/time";
|
||||
import { usePlayerStore } from "../store/playerStore";
|
||||
import { TimelineEditProvider } from "../../contexts/TimelineEditContext";
|
||||
|
||||
vi.mock("./timelineRowVirtualizationFlag", () => ({
|
||||
STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED: false,
|
||||
}));
|
||||
|
||||
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -307,9 +307,9 @@ describe("Timeline row virtualization", { timeout: 30_000 }, () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* The flag-off build is the one users get today. It mounts every clip, so the
|
||||
* scroll-time concessions windowing makes are pure cost there: this block pins
|
||||
* the timeline to doing no per-frame work at all while a gesture runs.
|
||||
* The rollback build mounts every clip, so the scroll-time concessions
|
||||
* windowing makes are pure cost there. This block pins that explicit fallback
|
||||
* to doing no per-frame work while a gesture runs.
|
||||
*/
|
||||
describe("Timeline without row virtualization", { timeout: 30_000 }, () => {
|
||||
async function renderUnvirtualizedTimeline() {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
describe("timeline row virtualization flag", () => {
|
||||
it("enables virtualization by default", async () => {
|
||||
const { STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED } =
|
||||
await import("./timelineRowVirtualizationFlag");
|
||||
|
||||
expect(STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps an explicit rollback path", async () => {
|
||||
vi.stubEnv("VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED", "0");
|
||||
const { STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED } =
|
||||
await import("./timelineRowVirtualizationFlag");
|
||||
|
||||
expect(STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* Row virtualization opt-in. Disabled until horizontal windowing and stable
|
||||
* gesture lifetime land.
|
||||
* Row virtualization is the product default. Setting the environment flag to
|
||||
* "0" keeps one explicit rollback path for comparisons and emergencies.
|
||||
*
|
||||
* It lives in its own module so the scroll-viewport hook can read it without
|
||||
* importing the virtualization hook that already imports the viewport snapshot
|
||||
* type back, which would close an import cycle.
|
||||
*/
|
||||
export const STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED =
|
||||
import.meta.env.DEV === true &&
|
||||
import.meta.env.VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED === "1";
|
||||
import.meta.env.VITE_STUDIO_TIMELINE_ROW_VIRTUALIZATION_ENABLED !== "0";
|
||||
|
||||
@@ -23,6 +23,7 @@ describe("timeline viewport budgets", () => {
|
||||
constrainedLongTaskLimitMs: 300,
|
||||
posterCoverageRatio: 0.9,
|
||||
supportedFixtureFallbackRatio: 0.02,
|
||||
scrollSamplesPerRun: 21,
|
||||
warmupRuns: 3,
|
||||
measuredRuns: 5,
|
||||
requiredPassingRuns: 4,
|
||||
@@ -51,6 +52,7 @@ describe("timeline viewport budgets", () => {
|
||||
[{ requiredPassingRuns: 0 }, "requiredPassingRuns"],
|
||||
[{ measuredRuns: 1.5, requiredPassingRuns: 1 }, "measuredRuns"],
|
||||
[{ measuredRuns: 4, requiredPassingRuns: 5 }, "requiredPassingRuns"],
|
||||
[{ scrollSamplesPerRun: 19 }, "scrollSamplesPerRun"],
|
||||
[{ posterCoverageRatio: 1.1 }, "posterCoverageRatio"],
|
||||
] as const)("rejects an invalid override %#", (overrides, message) => {
|
||||
expect(() => resolveTimelineViewportBudgets(overrides)).toThrow(message);
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface TimelineViewportBudgets {
|
||||
richPreviewP95Ms: number;
|
||||
constrainedRichPreviewP95Ms: number;
|
||||
supportedFixtureFallbackRatio: number;
|
||||
scrollSamplesPerRun: number;
|
||||
warmupRuns: number;
|
||||
measuredRuns: number;
|
||||
requiredPassingRuns: number;
|
||||
@@ -95,6 +96,7 @@ export const TIMELINE_VIEWPORT_BUDGETS: Readonly<TimelineViewportBudgets> = Obje
|
||||
richPreviewP95Ms: 750,
|
||||
constrainedRichPreviewP95Ms: 1_200,
|
||||
supportedFixtureFallbackRatio: 0.02,
|
||||
scrollSamplesPerRun: 21,
|
||||
warmupRuns: 3,
|
||||
measuredRuns: 5,
|
||||
requiredPassingRuns: 4,
|
||||
@@ -113,11 +115,21 @@ export function resolveTimelineViewportBudgets(
|
||||
assertValidBudget(name as keyof TimelineViewportBudgets, value);
|
||||
}
|
||||
const resolved = { ...TIMELINE_VIEWPORT_BUDGETS, ...overrides };
|
||||
for (const name of ["warmupRuns", "measuredRuns", "requiredPassingRuns"] as const) {
|
||||
for (const name of [
|
||||
"scrollSamplesPerRun",
|
||||
"warmupRuns",
|
||||
"measuredRuns",
|
||||
"requiredPassingRuns",
|
||||
] as const) {
|
||||
if (!Number.isInteger(resolved[name])) {
|
||||
throw new RangeError(`Timeline viewport budget ${name} must be an integer`);
|
||||
}
|
||||
}
|
||||
if (resolved.scrollSamplesPerRun < 20) {
|
||||
throw new RangeError(
|
||||
"Timeline viewport budget scrollSamplesPerRun must be at least 20 for p95",
|
||||
);
|
||||
}
|
||||
if (resolved.measuredRuns === 0 || resolved.requiredPassingRuns === 0) {
|
||||
throw new RangeError(
|
||||
"Timeline viewport budget measuredRuns and requiredPassingRuns must be greater than zero",
|
||||
|
||||
Reference in New Issue
Block a user