Skip to content

Commit

Permalink
Implement for loop and info
Browse files Browse the repository at this point in the history
  • Loading branch information
spaaaacccee committed Nov 15, 2023
1 parent 03a1cd6 commit 993ef9d
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 131 deletions.
5 changes: 2 additions & 3 deletions client/src/components/generic/Property.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { truncate } from "lodash";
import { ReactNode } from "react";
import { Flex } from "./Flex";
import { Space } from "./Space";
import YAML from "js-yaml";

type Props = {
label?: ReactNode;
Expand All @@ -24,7 +23,7 @@ function stringify(obj: any) {
default:
return (
<code>
{truncate(YAML.dump(obj).replace("\n", ", "), {
{truncate(JSON.stringify(obj).replace("\n", ", "), {
length: 30,
})}
</code>
Expand All @@ -34,7 +33,7 @@ function stringify(obj: any) {

export function Property({ label, value, type }: Props) {
return (
<Flex width="auto" mr={3} mt={0.5} key={`${label}::${stringify(value)}`}>
<Flex width="auto" mr={3} mb={0.5} key={`${label}::${stringify(value)}`}>
<Type
component="div"
variant="body2"
Expand Down
19 changes: 8 additions & 11 deletions client/src/components/renderer/parser/__tests__/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,31 @@ import { Context } from "../Context";
import { parse } from "../parse";

describe("parse", () => {
const ctx: Context = { a: 1 };
const result = parse<{
rect: { x: number; y: string; z: number; w: number };
}>([{ $: "rect2", y: 4 }], {
rect2: [
{ $: "rect", x: 2, y: "{{ctx.y + 1}}" },
{ $: "rect", y: "{{1 + 2}} a", z: "{{1 + 2}}", w: "{{ctx.a}}" },
],
});
const ctx: Context = { a: 1 };
})(ctx);
it("flattens correctly", () => {
expect(result).toMatchObject([{ $: "rect" }, { $: "rect" }]);
});
it("creates a deferred property", () => {
expect(result[0].x(ctx)).toEqual(2);
expect(result[0].x).toEqual(2);
});
it("evaluates a computed property", () => {
expect(result[1].z(ctx)).toEqual(3);
expect(result[1].z).toEqual(3);
});
it("evaluates a template string computed property", () => {
expect(result[1].y(ctx)).toEqual("3 a");
expect(result[1].y).toEqual("3 a");
});
it("reads from context", () => {
expect(result[1].w(ctx)).toEqual(1);
expect(result[1].w).toEqual(1);
});
it("propagates context correctly", () => {
expect(result[0].y(ctx)).toEqual(5);
});
it("propagates context correctly, despite an override in context", () => {
expect(result[0].y({ ...ctx, y: 12 })).toEqual(5);
expect(result[0].y).toEqual(5);
});
});
});
7 changes: 1 addition & 6 deletions client/src/components/renderer/parser/applyScope.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Properties as Props } from "protocol";
import { Context, PropMap } from "./Context";
import { mapProperties } from "./mapProperties";
import { normalizeConstant } from "./normalize";

export function applyScope<T extends Props>(
scope: PropMap<T>,
props: PropMap<T>
): PropMap<T> {
return Object.setPrototypeOf(
mapProperties(
props,
(prop) => (provided: Context<T>) =>
prop(applyScope(normalizeConstant(provided), scope))
),
mapProperties(props, (prop) => (provided: Context<T>) => prop(scope)),
scope
);
}
74 changes: 48 additions & 26 deletions client/src/components/renderer/parser/parse.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { Dictionary as Dict, range } from "lodash";
import { Dictionary as Dict, flatMap, map, mapValues, range } from "lodash";
import {
CompiledComponent,
ComponentDefinition,
ComponentDefinitionMap,
IntrinsicComponentMap,
ParsedComponent,
ParsedComponentDefinition,
TraceComponent,
} from "protocol";
import { Context, Key } from "./Context";
import { Context } from "./Context";
import { applyScope } from "./applyScope";
import { normalize, normalizeConstant } from "./normalize";
import { mapProperties } from "./mapProperties";
import { normalize } from "./normalize";
import { parseProperty } from "./parseProperty";

function parseFor(component: TraceComponent<string, Dict<any>>) {
function transformOne(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(normalizeConstant({ [$let]: i }), normalize(rest as any))
);
const from = parseProperty($from);
const to = parseProperty($to);
const step = parseProperty($step);
return (context: Context = {}) => {
return range(from(context), to(context), step(context)).map((i) => {
const scoped = applyScope(context, normalize({ [$let]: i }));
return applyScope(scoped, normalize(rest as any));
});
};
} else {
return [component];
return (context: Context = {}) => {
const scoped = applyScope(context, normalize(rest as any));
return [scoped];
};
}
}

type Compiled<T extends string> = (
context: Context
) => Compiled<T>[] | CompiledComponent<T, Dict<any>>[];

/**
* A parser for a list of Components
* @param definition a list of Components
Expand All @@ -31,20 +45,28 @@ function parseFor(component: TraceComponent<string, Dict<any>>) {
*/
export function parse<T extends IntrinsicComponentMap>(
definition: ComponentDefinition<string, Dict<any>>,
components: ComponentDefinitionMap,
context: Context<T[Key<T>]> = {}
): ParsedComponentDefinition<T> {
return definition.flatMap((c) => {
const { $ } = c;
const c2 = parseFor(c);
return c2.flatMap((component) => {
const scoped = applyScope(
normalizeConstant(context),
normalize(component)
);
return $ in components
? parse(components[$], components, scoped)
: [scoped as ParsedComponent<Key<T>, T[Key<T>]>];
});
});
components: ComponentDefinitionMap
): (
context: Context
) => CompiledComponent<Extract<keyof T, string>, Record<string, any>>[] {
const parseOne = (element: TraceComponent<string, Dict<any>>) => {
const { $ } = element;
const scoped = transformOne(element);
return $ in components
? (context: Context) =>
flatMap(scoped(context), (elem) => flatMap(store[$], (f) => f(elem)))
: (context: Context) =>
map(scoped(context), (elem) =>
Object.setPrototypeOf(
mapProperties(elem, (prop) => prop(elem)),
null
)
);
};
const store: Dict<Compiled<Extract<keyof T, string>>[]> = mapValues(
components,
(elements) => map(elements, parseOne)
);
const entry = flatMap(definition, parseOne);
return (context) => flatMap(entry, (e) => e(context));
}
30 changes: 14 additions & 16 deletions client/src/components/renderer/parser/parseToken.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import memo from "memoizee";
import { Prop } from "./Context";
import { normalizeConstant } from "./normalize";
import { normalize } from "./normalize";

export const parseToken = memo(
(token: string): Prop<any> => {
const f = Function("$", `return ${token};`);
return (ctx) =>
f(
new Proxy(normalizeConstant(ctx), {
get(target, prop: string) {
return target[prop]?.({});
},
})
);
},
{ primitive: true }
);
export const parseToken = (token: string): Prop<any> => {
const f = Function("$", `return ${token};`);
return (ctx) =>
f(
new Proxy(normalize(ctx), {
get(target, prop: string) {
return typeof target?.[prop] === "function"
? target[prop]({})
: target?.[prop];
},
})
);
};
39 changes: 26 additions & 13 deletions client/src/components/renderer/parser/parseTrace.worker.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { chunk, flatMap, map } from "lodash";
import { usingWorkerTask } from "workers/usingWorker";
import { chunk, flatMap, map, range } from "lodash";
import { usingWorkerTask } from "../../../workers/usingWorker";
import {
ParseTraceWorkerParameters,
ParseTraceWorkerReturnType,
} from "./parseTraceSlave.worker";
import parseTraceWorkerUrl from "./parseTraceSlave.worker.ts?worker&url";

const { min } = Math;

const SLAVE_COUNT = navigator.hardwareConcurrency ?? 8;
const CHUNK_SIZE = 16;

export class ParseTraceWorker extends Worker {
constructor() {
super(parseTraceWorkerUrl, { type: "module" });
Expand All @@ -22,17 +27,25 @@ async function parse({
context,
view = "main",
}: 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 chunks = range(0, trace?.events?.length, CHUNK_SIZE);
const tasks = chunk(chunks, SLAVE_COUNT);
const outs = [];

for (const task of tasks) {
outs.push(
...(await Promise.all(
map(task, (i) =>
parseTraceWorker({
trace,
context,
view,
from: i,
to: min(i + CHUNK_SIZE, trace?.events?.length ?? 0),
})
)
))
);
}

return {
stepsPersistent: flatMap(outs, "stepsPersistent"),
Expand Down
72 changes: 29 additions & 43 deletions client/src/components/renderer/parser/parseTraceSlave.worker.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { chain, findLast, map, mapValues, negate } from "lodash";
import {
CompiledComponent,
EventContext,
ParsedComponent,
Trace,
TraceEvent,
} from "protocol";
import { chain, findLast, map, mapValues, negate, range } from "lodash";
import { CompiledComponent, EventContext, Trace } from "protocol";
import { ComponentEntry } from "renderer";
import { mapProperties } from "./mapProperties";
import { normalizeConstant } from "./normalize";
import { normalize, normalizeConstant } from "./normalize";
import { parse as parseComponents } from "./parse";

const isNullish = (x: KeyRef): x is Exclude<KeyRef, Key> =>
Expand All @@ -25,56 +18,47 @@ function parse({
trace,
context,
view = "main",
from = 0,
to = trace?.events?.length ?? 0,
}: ParseTraceWorkerParameters): ParseTraceWorkerReturnType {
const parsed = parseComponents(
trace?.render?.views?.[view]?.components ?? [],
trace?.render?.components ?? {}
);

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 },
});
(component: CompiledComponent<string, Record<string, any>>) => {
return {
component,
meta: { source: "trace", step, info: component.$info },
};
};

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 steps = chain(range(from, to))
.map((i) => {
const e = trace!.events![i]!;
const esx = trace!.events!;
const component = parsed(
normalizeConstant({
alpha: 1,
...context,
step: i,
parent: !isNullish(e.pId)
? esx[findLast(r[e.pId], (x) => x.step <= i)?.step ?? 0]
: undefined,
event: e,
events: esx,
})
);
const persistent = component.filter(isPersistent);
const transient = component.filter(negate(isPersistent));
return { persistent, transient };
Expand All @@ -92,6 +76,8 @@ export type ParseTraceWorkerParameters = {
trace?: Trace;
context: EventContext;
view?: string;
from?: number;
to?: number;
};

export type ParseTraceWorkerReturnType = {
Expand Down
Loading

0 comments on commit 993ef9d

Please sign in to comment.