Skip to content

Commit

Permalink
Performance improvements for search trace parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
spaaaacccee committed Nov 15, 2023
1 parent 0880d8f commit 03a1cd6
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 147 deletions.
22 changes: 14 additions & 8 deletions client/src/components/app-bar/Playback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import { Label } from "components/generic/Label";
import { useSnackbar } from "components/generic/Snackbar";
import { useBreakpoints } from "hooks/useBreakpoints";
import { usePlaybackState } from "hooks/usePlaybackState";
import { range, trimEnd } from "lodash";
import { noop, range, trimEnd } from "lodash";
import { ReactNode, useCallback, useEffect } from "react";
import { useRaf } from "react-use";
import { UploadedTrace } from "slices/UIState";
import { Layer } from "slices/layers";
import { useSettings } from "slices/settings";
Expand All @@ -38,7 +37,6 @@ export function PlaybackService({
children,
value,
}: EditorSetterProps<Layer<PlaybackLayerData>>) {
useRaf();
const { step, tick, end, playing, pause } = usePlaybackState(value?.key);

const notify = useSnackbar();
Expand All @@ -54,8 +52,10 @@ export function PlaybackService({

useEffect(() => {
if (playing) {
return step < end
? cancellable(
let cancel = noop;
const r = setInterval(() => {
if (step < end) {
cancel = cancellable(
async () => {
for (const i of range(playbackRate)) {
const r = shouldBreak(step + i);
Expand All @@ -74,8 +74,15 @@ export function PlaybackService({
pause();
}
}
)
: pause();
);
} else {
pause();
}
}, 1000 / 60);
return () => {
cancel();
clearInterval(r);
};
}
}, [
renderLabel,
Expand Down Expand Up @@ -109,7 +116,6 @@ export function Playback({
stepForward,
stop,
} = usePlaybackState(layer?.key);
useRaf();
return (
<>
<Button
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/inspector/PropertyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function PropertyList({
const a = filter(entries(event), ([, v]) => v !== undefined);
return (
<Flex {...props}>
{map(slice(a, 0, max), ([k, v]) => (
<Property label={k} value={v} type={{ variant }} />
{map(slice(a, 0, max), ([k, v], i) => (
<Property label={k} value={v} key={i} type={{ variant }} />
))}
{a.length > max && (
<Property
Expand Down
14 changes: 8 additions & 6 deletions client/src/components/renderer/parser/applyScope.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Properties as Props } from "protocol";
import { Context, PropMap } from "./Context";
import { mapProperties } from "./mapProperties";
import { normalize } from "./normalize";
import { normalizeConstant } from "./normalize";

export function applyScope<T extends Props>(
scope: PropMap<T>,
props: PropMap<T>
): PropMap<T> {
const scopedComponent = mapProperties(
props,
(prop) => (provided: Context<T>) =>
prop(applyScope(normalize(provided), scope))
return Object.setPrototypeOf(
mapProperties(
props,
(prop) => (provided: Context<T>) =>
prop(applyScope(normalizeConstant(provided), scope))
),
scope
);
return { ...scope, ...scopedComponent };
}
17 changes: 9 additions & 8 deletions client/src/components/renderer/parser/mapProperties.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { mapValues, ObjectIterator } from "lodash";
import { ObjectIterator } from "lodash";
import { Properties as Props } from "protocol";
import { Context } from "./Context";

/**
* Iterates over the properties of a scope or component,
* ignoring the component name property `$`.
* @param context The properties to iterate against.
* @param iterator
* @param f
* @returns
*/
export function mapProperties<T extends Props, TResult>(
context: Context<T> = {},
iterator: ObjectIterator<T, TResult>
f: ObjectIterator<Context<T>, TResult>
): Context<T> & { $: string } {
const { $, ...props } = context;
return {
...mapValues(props, iterator),
$,
};
const out: any = {};
for (const key of Object.keys(context)) {
out[key] = key === "$" ? context[key] : f(context[key], key, context);
}
return Object.setPrototypeOf(out, context);
}
4 changes: 3 additions & 1 deletion client/src/components/renderer/parser/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ export function normalize<T extends Props>(
}

export function normalizeConstant(obj: Context<any> = {}) {
return new Proxy(obj, { get: (obj, p) => () => obj[p] });
return new Proxy(obj, {
get: (obj, p) => (typeof obj[p] === "function" ? obj[p] : () => obj[p]),
});
}
10 changes: 5 additions & 5 deletions client/src/components/renderer/parser/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import {
ParsedComponentDefinition,
TraceComponent,
} from "protocol";
import { applyScope } from "./applyScope";
import { Context, Key } from "./Context";
import { normalize } from "./normalize";
import { applyScope } from "./applyScope";
import { normalize, normalizeConstant } from "./normalize";

function parseFor(component: TraceComponent<string, Dict<any>>) {
const { $for, ...rest } = component;
if ($for) {
const { $let = "i", $from = 0, $to = 1, $step = 1 } = $for;
return range($from, $to, $step).map((i) =>
applyScope(normalize({ [$let]: i }), normalize(rest as any))
applyScope(normalizeConstant({ [$let]: i }), normalize(rest as any))
);
} else {
return [component];
Expand All @@ -39,8 +39,8 @@ export function parse<T extends IntrinsicComponentMap>(
const c2 = parseFor(c);
return c2.flatMap((component) => {
const scoped = applyScope(
normalize(context),
normalize(component) as any
normalizeConstant(context),
normalize(component)
);
return $ in components
? parse(components[$], components, scoped)
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/renderer/parser/parseProperty.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { constant, map, mapValues } from "lodash";
import { map, mapValues } from "lodash";
import { Prop } from "./Context";
import { parseString } from "./parseString";
/**
Expand All @@ -20,6 +20,6 @@ export function parseProperty(prop: any): Prop<any> {
case String:
return parseString(prop);
default:
return constant(prop);
return () => prop;
}
}
17 changes: 9 additions & 8 deletions client/src/components/renderer/parser/parseString.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { join } from "lodash";
import memo from "memoizee";
import {
evaluateParsedString as evaluateTemplate,
parseStringTemplateGenerator as makeParser,
} from "string-template-parser";
import { Prop } from "./Context";
import { parseToken } from "./parseToken";
import memo from "memoizee";

const openBrace = /^\{\{\s*/;
const closeBrace = /^\s*\}\}/;
const neverMatch = /\b\B/;

const parser = makeParser({
VARIABLE_START: openBrace,
VARIABLE_END: closeBrace,
PIPE_START: neverMatch,
PIPE_PARAMETER_START: neverMatch,
QUOTED_STRING: neverMatch,
});

export const parseString = memo(
(str: string): Prop<any> => {
const parser = makeParser({
VARIABLE_START: openBrace,
VARIABLE_END: closeBrace,
PIPE_START: neverMatch,
PIPE_PARAMETER_START: neverMatch,
QUOTED_STRING: neverMatch,
});
const parsed = parser(str);
return join(parsed.literals, "")
? (ctx) => evaluateTemplate(parsed, {}, {}, (v) => parseToken(v)(ctx))
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/renderer/parser/parseToken.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import memo from "memoizee";
import { Prop } from "./Context";
import { normalize } from "./normalize";
import { normalizeConstant } from "./normalize";

export const parseToken = memo(
(token: string): Prop<any> => {
const f = Function("$", `return ${token};`);
return (ctx) =>
f(
new Proxy(normalize(ctx), {
new Proxy(normalizeConstant(ctx), {
get(target, prop: string) {
return target[prop]?.({});
},
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/renderer/parser/parseTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { usingMemoizedWorkerTask } from "workers/usingWorker";
import {
ParseTraceWorkerParameters,
ParseTraceWorkerReturnType,
} from "./parseTrace.worker";
import parseGridWorkerUrl from "./parseTrace.worker.ts?worker&url";
} from "./parseTraceSlave.worker";
import parseTraceWorkerUrl from "./parseTrace.worker.ts?worker&url";
import { dump } from "js-yaml";

export class ParseTraceWorker extends Worker {
constructor() {
super(parseGridWorkerUrl, { type: "module" });
super(parseTraceWorkerUrl, { type: "module" });
}
}

Expand All @@ -37,6 +37,7 @@ export function useTraceParser(params: ParseTraceWorkerParameters) {
);
return output;
} catch (e) {
console.error(e);
push("Error parsing", `${dump(e)}`);
}
}
Expand Down
123 changes: 32 additions & 91 deletions client/src/components/renderer/parser/parseTrace.worker.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,45 @@
import { chain, findLast, mapValues, negate } from "lodash";
import { chunk, flatMap, map } from "lodash";
import { usingWorkerTask } from "workers/usingWorker";
import {
CompiledComponent,
EventContext,
ParsedComponent,
Trace,
TraceEvent,
} from "protocol";
import { ComponentEntry } from "renderer";
import { mapProperties } from "./mapProperties";
import { normalizeConstant } from "./normalize";
import { parse as parseComponents } from "./parse";

const isNullish = (x: KeyRef): x is Exclude<KeyRef, Key> =>
x === undefined || x === null;

type Key = string | number;

type KeyRef = Key | null | undefined;
ParseTraceWorkerParameters,
ParseTraceWorkerReturnType,
} from "./parseTraceSlave.worker";
import parseTraceWorkerUrl from "./parseTraceSlave.worker.ts?worker&url";

export class ParseTraceWorker extends Worker {
constructor() {
super(parseTraceWorkerUrl, { type: "module" });
}
}

const isPersistent = (c: CompiledComponent<string, Record<string, any>>) =>
c.display !== "transient";
const parseTraceWorker = usingWorkerTask<
ParseTraceWorkerParameters,
ParseTraceWorkerReturnType
>(ParseTraceWorker);

function parse({
async function parse({
trace,
context,
view = "main",
}: ParseTraceWorkerParameters): ParseTraceWorkerReturnType {
const parsed = parseComponents(
trace?.render?.views?.[view]?.components ?? [],
trace?.render?.components ?? {}
}: ParseTraceWorkerParameters): Promise<ParseTraceWorkerReturnType> {
const chunks = chunk(trace?.events, (trace?.events?.length ?? 0) / 8);

const outs = await Promise.all(
map(chunks, (chunk1) =>
parseTraceWorker({
trace: { ...trace, events: chunk1 },
context,
view,
})
)
);

const apply = (
event: TraceEvent,
ctx?: EventContext
): CompiledComponent<string, Record<string, any>>[] =>
parsed.map((p) =>
mapProperties<
ParsedComponent<string, any>,
CompiledComponent<string, Record<string, any>>
>(p, (c) =>
c(
normalizeConstant({
alpha: 1,
...context,
...ctx,
event,
events: trace?.events,
})
)
)
);

const isVisible = (c: CompiledComponent<string, { alpha?: number }>) =>
c && Object.hasOwn(c, "alpha") ? c!.alpha! > 0 : true;

const makeEntryIteratee =
(step: number) =>
(component: CompiledComponent<string, Record<string, any>>) => ({
component,
meta: { source: "trace", step: step },
});

const r = chain(trace?.events)
.map((c, i) => ({ step: i, id: c.id, data: c, pId: c.pId }))
.groupBy("id")
.value();

const steps = chain(trace?.events)
.map((e, i, esx) => {
const component = apply(e, {
step: i,
parent: !isNullish(e.pId)
? esx[findLast(r[e.pId], (x) => x.step <= i)?.step ?? 0]
: undefined,
});
const persistent = component.filter(isPersistent);
const transient = component.filter(negate(isPersistent));
return { persistent, transient };
})
.map((c) => mapValues(c, (b) => b.filter(isVisible)))
.map((c, i) => mapValues(c, (b) => b.map(makeEntryIteratee(i))))
.value();
return {
stepsPersistent: steps.map((c) => c.persistent),
stepsTransient: steps.map((c) => c.transient),
stepsPersistent: flatMap(outs, "stepsPersistent"),
stepsTransient: flatMap(outs, "stepsTransient"),
};
}

export type ParseTraceWorkerParameters = {
trace?: Trace;
context: EventContext;
view?: string;
};

export type ParseTraceWorkerReturnType = {
stepsPersistent: ComponentEntry[][];
stepsTransient: ComponentEntry[][];
};

onmessage = ({ data }: MessageEvent<ParseTraceWorkerParameters>) => {
postMessage(parse(data));
onmessage = async ({ data }: MessageEvent<ParseTraceWorkerParameters>) => {
postMessage(await parse(data));
};
Loading

0 comments on commit 03a1cd6

Please sign in to comment.