Skip to content

Commit 8b27458

Browse files
committed
perf(studio): keep only script shapes and refresh them on reuse
1 parent 6536364 commit 8b27458

2 files changed

Lines changed: 27 additions & 31 deletions

File tree

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import { describe, expect, it, vi } from "vitest";
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
22

3-
const reads = vi.hoisted(() => ({ parse: [] as string[], shape: [] as string[] }));
3+
const reads = vi.hoisted(() => ({ shape: [] as string[] }));
44

55
vi.mock("@hyperframes/parsers/gsap-parser-acorn", async (importOriginal) => {
66
const real = await importOriginal<typeof import("@hyperframes/parsers/gsap-parser-acorn")>();
77
return {
88
...real,
9-
parseGsapScriptAcorn: (code: string) => (
10-
reads.parse.push(code), real.parseGsapScriptAcorn(code)
11-
),
129
scriptShape: (code: string, mask?: boolean) => (
1310
reads.shape.push(code), real.scriptShape(code, mask)
1411
),
@@ -21,11 +18,22 @@ const at = (start: number) =>
2118
`var tl = gsap.timeline({ paused: true });\ntl.to("#a", { x: 1, duration: 1 }, ${start});\nwindow.__timelines["t"] = tl;`;
2219

2320
describe("planLiveRetime", () => {
24-
it("reads each script once across two drags in a row", () => {
21+
beforeEach(() => {
22+
reads.shape.length = 0;
23+
});
24+
25+
it("reads each script's shape once across two drags in a row", () => {
2526
expect(planLiveRetime(at(0), at(1)).kind).toBe("retime");
2627
expect(planLiveRetime(at(1), at(2)).kind).toBe("retime");
2728

28-
expect(reads.parse).toEqual([at(0), at(1), at(2)]);
2929
expect(reads.shape).toEqual([at(0), at(1), at(2)]);
3030
});
31+
32+
it("keeps the live script's shape while saves land before the preview catches up", () => {
33+
planLiveRetime(at(10), at(11));
34+
planLiveRetime(at(10), at(12));
35+
planLiveRetime(at(10), at(13));
36+
37+
expect(reads.shape).toEqual([at(10), at(11), at(12), at(13)]);
38+
});
3139
});

‎packages/studio/src/utils/gsapLiveRetime.ts‎

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,26 @@ const placedAlone = (animation: GsapAnimation) =>
5050
const lengthOf = (animation: GsapAnimation) =>
5151
animation.method === "set" ? 0 : animation.durationUnresolved ? undefined : animation.duration;
5252

53-
interface ReadScript {
54-
shape: string | null;
55-
animations?: GsapAnimation[];
56-
}
57-
58-
/** The last two scripts read, by text: a repeat drag starts from the script the last one saved. */
59-
const readScripts = new Map<string, ReadScript>();
53+
/** The last two script shapes, by text: a repeat drag starts from the script the last one saved. */
54+
const shapes = new Map<string, string | null>();
6055

61-
function readScript(code: string): ReadScript {
62-
let read = readScripts.get(code);
63-
if (!read) {
64-
read = { shape: scriptShape(code, true) };
65-
if (readScripts.size >= 2) readScripts.delete(readScripts.keys().next().value!);
66-
readScripts.set(code, read);
67-
}
68-
return read;
56+
function shapeOf(code: string): string | null {
57+
const shape = shapes.has(code) ? (shapes.get(code) as string | null) : scriptShape(code, true);
58+
shapes.delete(code);
59+
shapes.set(code, shape);
60+
if (shapes.size > 2) shapes.delete(shapes.keys().next().value!);
61+
return shape;
6962
}
7063

71-
const timedAnimations = (code: string): GsapAnimation[] => {
72-
const read = readScript(code);
73-
return (read.animations ??= parseGsapScriptAcorn(code).animations.filter(onTimeline));
74-
};
75-
7664
/** Decides, from the two scripts alone, whether a saved edit moved tweens and nothing else. */
7765
export function planLiveRetime(before: string, after: string): LiveRetimePlan {
7866
// The preview runs the script re-printed by the bundler; a timing edit rewrites numbers only.
79-
const { shape } = readScript(before);
80-
if (shape === null || shape !== readScript(after).shape) return RERUN;
67+
const shape = shapeOf(before);
68+
if (shape === null || shape !== shapeOf(after)) return RERUN;
8169
const keys = timelineKeys(after);
8270
if (keys.length !== 1 || timelineKeys(before).join() !== keys.join()) return RERUN;
83-
const was = timedAnimations(before);
84-
const now = timedAnimations(after);
71+
const was = parseGsapScriptAcorn(before).animations.filter(onTimeline);
72+
const now = parseGsapScriptAcorn(after).animations.filter(onTimeline);
8573
if (was.length !== now.length) return RERUN;
8674
const tweens: TweenTiming[] = [];
8775
for (const [index, next] of now.entries()) {

0 commit comments

Comments
 (0)