Skip to content

Instantly share code, notes, and snippets.

@jasonLaster
Created September 1, 2021 21:58
Show Gist options
  • Save jasonLaster/5ee94b990ae2d952dbaf158fd7621eda to your computer and use it in GitHub Desktop.
Save jasonLaster/5ee94b990ae2d952dbaf158fd7621eda to your computer and use it in GitHub Desktop.
diff --git a/src/protocol/graphics.ts b/src/protocol/graphics.ts
index 1fc24c23..7159fdcf 100644
--- a/src/protocol/graphics.ts
+++ b/src/protocol/graphics.ts
@@ -29,66 +29,6 @@ interface Timed {
time: number;
}
-// Given a sorted array of items with "time" properties, find the index of
-// the most recent item at or preceding a given time.
-function mostRecentIndex<T extends Timed>(array: T[], time: number): number | undefined {
- if (!array.length || time < array[0].time) {
- return undefined;
- }
- const index = binarySearch(0, array.length, (index: number) => {
- return time - array[index].time;
- });
- assert(array[index].time <= time);
- if (index + 1 < array.length) {
- assert(array[index + 1].time >= time);
- }
- return index;
-}
-
-function mostRecentEntry<T extends Timed>(array: T[], time: number) {
- const index = mostRecentIndex(array, time);
- return index !== undefined ? array[index] : null;
-}
-
-function nextEntry<T extends Timed>(array: T[], time: number) {
- const index = mostRecentIndex(array, time);
- if (index === undefined) {
- return array.length ? array[0] : null;
- }
- return index + 1 < array.length ? array[index + 1] : null;
-}
-
-// Add an entry with a "time" property to an array that is sorted by time.
-function insertEntrySorted<T extends Timed>(array: T[], entry: T) {
- if (!array.length || array[array.length - 1].time <= entry.time) {
- array.push(entry);
- } else {
- const index = mostRecentIndex(array, entry.time);
- if (index !== undefined) {
- array.splice(index + 1, 0, entry);
- } else {
- array.unshift(entry);
- }
- }
-}
-
-function closerEntry<T1 extends Timed, T2 extends Timed>(
- time: number,
- entry1: T1 | null,
- entry2: T2 | null
-) {
- if (!entry1) {
- return entry2;
- }
- if (!entry2) {
- return entry1;
- }
- if (Math.abs(time - entry1.time) < Math.abs(time - entry2.time)) {
- return entry1;
- }
- return entry2;
-}
-
//////////////////////////////
// Paint / Mouse Event Points
//////////////////////////////
@@ -117,24 +57,6 @@ const gMouseClickEvents: MouseEvent[] = [];
// Device pixel ratio used by recording screen shots.
let gDevicePixelRatio: number;
-function onPaints({ paints }: paintPoints) {
- paints.forEach(({ point, time, screenShots }) => {
- const paintHash = screenShots.find(desc => desc.mimeType == "image/jpeg")!.hash;
- insertEntrySorted(gPaintPoints, { point, time, paintHash });
- });
-}
-
-function onMouseEvents(events: MouseEvent[], store: UIStore) {
- events.forEach(entry => {
- insertEntrySorted(gMouseEvents, entry);
- if (entry.kind == "mousedown") {
- insertEntrySorted(gMouseClickEvents, entry);
- }
- });
-
- store.dispatch(actions.setEventsForType(gMouseClickEvents, "mousedown"));
-}
-
class VideoPlayer {
store: UIStore | null = null;
video: HTMLVideoElement | null = null;
@@ -211,7 +133,6 @@ export function setupGraphics(store: UIStore) {
ThreadFront.sessionWaiter.promise.then((sessionId: string) => {
paintPointsWaiter = client.Graphics.findPaints({}, sessionId);
- client.Graphics.addPaintPointsListener(onPaints);
client.Session.findMouseEvents({}, sessionId);
client.Session.addMouseEventsListener(({ events }) => onMouseEvents(events, store));
@@ -262,40 +183,6 @@ export function addLastScreen(screen: ScreenShot | null, point: string, time: nu
}
}
-export function mostRecentPaintOrMouseEvent(time: number) {
- const paintEntry = mostRecentEntry(gPaintPoints, time);
- const mouseEntry = mostRecentEntry(gMouseEvents, time);
- return closerEntry(time, paintEntry, mouseEntry);
-}
-
-export function nextPaintOrMouseEvent(time: number) {
- const paintEntry = nextEntry(gPaintPoints, time);
- const mouseEntry = nextEntry(gMouseEvents, time);
- return closerEntry(time, paintEntry, mouseEntry);
-}
-
-export function nextPaintEvent(time: number) {
- return nextEntry(gPaintPoints, time);
-}
-
-export function previousPaintEvent(time: number) {
- const entry = mostRecentEntry(gPaintPoints, time);
- if (entry && entry.time == time) {
- return mostRecentEntry(gPaintPoints, time - 1);
- }
- return entry;
-}
-
-export function getMostRecentPaintPoint(time: number) {
- return mostRecentEntry(gPaintPoints, time);
-}
-
-export function getClosestPaintPoint(time: number) {
- const entryBefore = mostRecentEntry(gPaintPoints, time);
- const entryAfter = nextEntry(gPaintPoints, time);
- return closerEntry(time, entryBefore, entryAfter);
-}
-
export function getDevicePixelRatio() {
return gDevicePixelRatio;
}
@@ -318,43 +205,6 @@ export interface MouseAndClickPosition {
clickY?: number;
}
-export async function getGraphicsAtTime(
- time: number,
- forPlayback = false
-): Promise<{ screen?: ScreenShot; mouse?: MouseAndClickPosition }> {
- const paintIndex = mostRecentIndex(gPaintPoints, time);
- if (paintIndex === undefined) {
- // There are no graphics to paint here.
- return {};
- }
-
- const { point, paintHash } = gPaintPoints[paintIndex];
- if (!paintHash) {
- return {};
- }
-
- const screenPromise = forPlayback
- ? features.videoPlayback
- ? Promise.resolve(undefined)
- : screenshotCache.getScreenshotForPlayback(point, paintHash)
- : screenshotCache.getScreenshotForPreview(point, paintHash);
-
- const screen = await screenPromise;
-
- let mouse: MouseAndClickPosition | undefined;
- const mouseEvent = mostRecentEntry(gMouseEvents, time);
- if (mouseEvent) {
- mouse = { x: mouseEvent.clientX, y: mouseEvent.clientY };
- const clickEvent = mostRecentEntry(gMouseClickEvents, time);
- if (clickEvent && clickEvent.time + ClickThresholdMs >= time) {
- mouse.clickX = clickEvent.clientX;
- mouse.clickY = clickEvent.clientY;
- }
- }
-
- return { screen, mouse };
-}
-
//////////////////////////////
// Rendering State
//////////////////////////////
diff --git a/src/protocol/paints-manager.ts b/src/protocol/paints-manager.ts
new file mode 100644
index 00000000..f5cdfb96
--- /dev/null
+++ b/src/protocol/paints-manager.ts
@@ -0,0 +1,159 @@
+// Given a sorted array of items with "time" properties, find the index of
+// the most recent item at or preceding a given time.
+function mostRecentIndex<T extends Timed>(array: T[], time: number): number | undefined {
+ if (!array.length || time < array[0].time) {
+ return undefined;
+ }
+ const index = binarySearch(0, array.length, (index: number) => {
+ return time - array[index].time;
+ });
+ assert(array[index].time <= time);
+ if (index + 1 < array.length) {
+ assert(array[index + 1].time >= time);
+ }
+ return index;
+}
+
+function mostRecentEntry<T extends Timed>(array: T[], time: number) {
+ const index = this.mostRecentIndex(array, time);
+ return index !== undefined ? array[index] : null;
+}
+
+function nextEntry<T extends Timed>(array: T[], time: number) {
+ const index = this.mostRecentIndex(array, time);
+ if (index === undefined) {
+ return array.length ? array[0] : null;
+ }
+ return index + 1 < array.length ? array[index + 1] : null;
+}
+
+// Add an entry with a "time" property to an array that is sorted by time.
+function insertEntrySorted<T extends Timed>(array: T[], entry: T) {
+ if (!array.length || array[array.length - 1].time <= entry.time) {
+ array.push(entry);
+ } else {
+ const index = this.mostRecentIndex(array, entry.time);
+ if (index !== undefined) {
+ array.splice(index + 1, 0, entry);
+ } else {
+ array.unshift(entry);
+ }
+ }
+}
+
+function closerEntry<T1 extends Timed, T2 extends Timed>(
+ time: number,
+ entry1: T1 | null,
+ entry2: T2 | null
+) {
+ if (!entry1) {
+ return entry2;
+ }
+ if (!entry2) {
+ return entry1;
+ }
+ if (Math.abs(time - entry1.time) < Math.abs(time - entry2.time)) {
+ return entry1;
+ }
+ return entry2;
+}
+
+class PaintsManager {
+ paintPoints: TimeStampedPointWithPaintHash[] = [{ point: "0", time: 0, paintHash: "" }];
+
+ // All mouse events that have occurred in the recording, in order.
+ mouseEvents: MouseEvent[] = [];
+
+ constructor(client) {
+ client.Graphics.addPaintPointsListener(this.onPaints);
+ }
+
+ onPaints({ paints }: paintPoints) {
+ paints.forEach(({ point, time, screenShots }) => {
+ const paintHash = screenShots.find(desc => desc.mimeType == "image/jpeg")!.hash;
+ insertEntrySorted(this.paintPoints, { point, time, paintHash });
+ });
+ }
+
+ onMouseEvents(events: MouseEvent[], store: UIStore) {
+ events.forEach(entry => {
+ insertEntrySorted(this.mouseEvents, entry);
+ if (entry.kind == "mousedown") {
+ insertEntrySorted(this.mouseClickEvents, entry);
+ }
+ });
+
+ // store.dispatch(actions.setEventsForType(this.mouseClickEvents, "mousedown"));
+ }
+
+ mostRecentPaintOrMouseEvent(time: number) {
+ const paintEntry = mostRecentEntry(this.paintPoints, time);
+ const mouseEntry = mostRecentEntry(this.mouseEvents, time);
+ return closerEntry(time, paintEntry, mouseEntry);
+ }
+
+ nextPaintOrMouseEvent(time: number) {
+ const paintEntry = nextEntry(this.paintPoints, time);
+ const mouseEntry = nextEntry(this.mouseEvents, time);
+ return closerEntry(time, paintEntry, mouseEntry);
+ }
+
+ nextPaintEvent(time: number) {
+ return nextEntry(this.paintPoints, time);
+ }
+
+ previousPaintEvent(time: number) {
+ const entry = mostRecentEntry(this.paintPoints, time);
+ if (entry && entry.time == time) {
+ return mostRecentEntry(this.paintPoints, time - 1);
+ }
+ return entry;
+ }
+
+ getMostRecentPaintPoint(time: number) {
+ return mostRecentEntry(this.paintPoints, time);
+ }
+
+ getClosestPaintPoint(time: number) {
+ const entryBefore = mostRecentEntry(this.paintPoints, time);
+ const entryAfter = nextEntry(this.paintPoints, time);
+ return closerEntry(time, entryBefore, entryAfter);
+ }
+
+ async getGraphicsAtTime(
+ time: number,
+ forPlayback = false
+ ): Promise<{ screen?: ScreenShot; mouse?: MouseAndClickPosition }> {
+ const paintIndex = mostRecentIndex(this.paintPoints, time);
+ if (paintIndex === undefined) {
+ // There are no graphics to paint here.
+ return {};
+ }
+
+ const { point, paintHash } = this.paintPoints[paintIndex];
+ if (!paintHash) {
+ return {};
+ }
+
+ const screenPromise = forPlayback
+ ? features.videoPlayback
+ ? Promise.resolve(undefined)
+ : screenshotCache.getScreenshotForPlayback(point, paintHash)
+ : screenshotCache.getScreenshotForPreview(point, paintHash);
+
+ const screen = await screenPromise;
+
+ let mouse: MouseAndClickPosition | undefined;
+ const mouseEvent = mostRecentEntry(this.mouseEvents, time);
+ if (mouseEvent) {
+ mouse = { x: mouseEvent.clientX, y: mouseEvent.clientY };
+ const clickEvent = mostRecentEntry(gMouseClickEvents, time);
+ if (clickEvent && clickEvent.time + ClickThresholdMs >= time) {
+ mouse.clickX = clickEvent.clientX;
+ mouse.clickY = clickEvent.clientY;
+ }
+ }
+
+ return { screen, mouse };
+ }
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment