Skip to content

Commit

Permalink
refactor: Extract animation frame as a hook for strudel
Browse files Browse the repository at this point in the history
  • Loading branch information
munshkr committed Apr 2, 2024
1 parent 1895542 commit 4d77f1c
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 61 deletions.
31 changes: 31 additions & 0 deletions packages/web/src/hooks/use-strudel-codemirror-extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Session } from "@flok-editor/session";
import {
highlightMiniLocations,
updateMiniLocations,
} from "@strudel/codemirror";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { useCallback } from "react";
import { useAnimationFrame } from "./use-animation-frame";
import { forEachDocumentContext } from "@/lib/utils";

export function useStrudelCodemirrorExtensions(
session: Session | null,
editorRefs: React.RefObject<ReactCodeMirrorRef>[]
) {
useAnimationFrame(
useCallback(() => {
if (!session) return;

forEachDocumentContext(
(ctx, editor) => {
const view = editor?.view;
if (!view) return;
updateMiniLocations(view, ctx.miniLocations || []);
highlightMiniLocations(view, ctx.phase || 0, ctx.haps || []);
},
session,
editorRefs
);
}, [session, editorRefs])
);
}
46 changes: 15 additions & 31 deletions packages/web/src/lib/strudel-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EvalMessage, Session } from "@flok-editor/session";
import type { EvalMessage } from "@flok-editor/session";
import {
Framer,
Pattern,
Expand All @@ -18,42 +18,31 @@ import {
samples,
webaudioOutput,
} from "@strudel/webaudio";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { updateDocumentsContext } from "./utils";

export type ErrorHandler = (error: string) => void;

controls.createParam("docId");

const getDocumentIndex = (docId: string, session: Session | null) =>
session?.getDocuments().findIndex((d) => d.id === docId) ?? -1;

export class StrudelWrapper {
initialized: boolean = false;

protected _onError: ErrorHandler;
protected _onWarning: ErrorHandler;
protected _repl: any;
protected _docPatterns: any;
protected _editorRefs: React.RefObject<ReactCodeMirrorRef>[];
protected _session: Session | null;
protected framer?: any;

constructor({
onError,
onWarning,
editorRefs,
session,
}: {
onError: ErrorHandler;
onWarning: ErrorHandler;
editorRefs: React.RefObject<ReactCodeMirrorRef>[];
session: Session | null;
}) {
this._docPatterns = {};
this._onError = onError || (() => {});
this._onWarning = onWarning || (() => {});
this._editorRefs = editorRefs;
this._session = session;
}

async importModules() {
Expand Down Expand Up @@ -92,9 +81,6 @@ export class StrudelWrapper {
lastFrame = phase;
return;
}
if (!this._editorRefs) {
return;
}
if (!this._repl.scheduler.pattern) {
return;
}
Expand All @@ -109,19 +95,14 @@ export class StrudelWrapper {
);
// iterate over each strudel doc
Object.keys(this._docPatterns).forEach((docId: any) => {
const index = getDocumentIndex(docId, this._session);
const editorRef = this._editorRefs?.[index];
const view = editorRef?.current?.view;
if (!view) return;
// filter out haps belonging to this document (docId is set in tryEval)
const haps = currentFrame.filter((h: any) => h.value.docId === docId);
// update codemirror view to highlight this frame's haps
window.parent.phase = phase;
window.parent.haps = haps;
updateDocumentsContext(docId, { haps, phase });
});
},
(err: any) => {
console.error("strudel draw error", err);
console.error("[strudel] draw error", err);
}
);

Expand All @@ -130,12 +111,9 @@ export class StrudelWrapper {
afterEval: (options: any) => {
// assumes docId is injected at end end as a comment
const docId = options.code.split("//").slice(-1)[0];
const index = getDocumentIndex(docId, this._session);
const editorRef = this._editorRefs?.[index];
if (editorRef?.current) {
const miniLocations = options.meta?.miniLocations;
window.parent.miniLocations = miniLocations;
}
if (!docId) return;
const miniLocations = options.meta?.miniLocations;
updateDocumentsContext(docId, { miniLocations });
},
beforeEval: () => {},
onSchedulerError: (e: unknown) => this._onError(`${e}`),
Expand All @@ -144,16 +122,22 @@ export class StrudelWrapper {
transpiler,
});

this.framer.start(); // TODO: when to start stop?
this.framer.start();

// For some reason, we need to make a no-op evaluation ("silence") to make
// sure everything is loaded correctly.
const pattern = await this._repl.evaluate(`silence`);
const pattern = await this._repl.evaluate(`silence//`);
await this._repl.scheduler.setPattern(pattern, true);

this.initialized = true;
}

async dispose() {
if (this.framer) {
this.framer.stop();
}
}

async tryEval(msg: EvalMessage) {
if (!this.initialized) await this.initialize();
try {
Expand Down
31 changes: 29 additions & 2 deletions packages/web/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { type Session } from "@flok-editor/session";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import {
uniqueNamesGenerator,
adjectives,
colors,
animals,
colors,
uniqueNamesGenerator,
} from "unique-names-generator";
import { v4 as uuidv4 } from "uuid";

Expand Down Expand Up @@ -93,3 +95,28 @@ export function sendToast(
"*"
);
}

export function updateDocumentsContext(docId: string, context: object) {
if (typeof window.parent.documentsContext === "undefined") {
window.parent.documentsContext = {};
}
const prevContext = window.parent.documentsContext[docId] || {};
window.parent.documentsContext[docId] = { ...prevContext, ...context };
}

export function forEachDocumentContext(
callback: (context: any, editor: ReactCodeMirrorRef | null) => void,
session: Session,
editorRefs: React.RefObject<ReactCodeMirrorRef>[]
) {
const documentsContext = window.documentsContext || {};
for (const docId in documentsContext) {
const context = documentsContext[docId];
const index = getDocumentIndex(docId, session);
const editor = editorRefs[index]?.current;
callback(context, editor);
}
}

export const getDocumentIndex = (docId: string, session: Session | null) =>
session?.getDocuments().findIndex((d) => d.id === docId) ?? -1;
2 changes: 0 additions & 2 deletions packages/web/src/routes/frames/strudel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ export function Component() {
onWarning: (msg) => {
sendToast("warning", "Strudel warning", msg);
},
session: window.parent.session,
editorRefs: window.parent.editorRefs,
});

await instance.importModules();
Expand Down
29 changes: 3 additions & 26 deletions packages/web/src/routes/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { Toaster } from "@/components/ui/toaster";
import UsernameDialog from "@/components/username-dialog";
import { WebTargetIframe } from "@/components/web-target-iframe";
import { WelcomeDialog } from "@/components/welcome-dialog";
import { useAnimationFrame } from "@/hooks/use-animation-frame";
import { useHash } from "@/hooks/use-hash";
import { useQuery } from "@/hooks/use-query";
import { useShortcut } from "@/hooks/use-shortcut";
import { useStrudelCodemirrorExtensions } from "@/hooks/use-strudel-codemirror-extensions";
import { useToast } from "@/hooks/use-toast";
import {
cn,
Expand All @@ -33,10 +33,6 @@ import {
webTargets,
} from "@/settings.json";
import { Session, type Document } from "@flok-editor/session";
import {
highlightMiniLocations,
updateMiniLocations,
} from "@strudel/codemirror";
import { type ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Helmet } from "react-helmet-async";
Expand All @@ -48,11 +44,7 @@ import {

declare global {
interface Window {
session: Session | null;
editorRefs: React.RefObject<ReactCodeMirrorRef>[];
miniLocations: any;
phase: any;
haps: any;
documentsContext: { [docId: string]: any };
}
}

Expand Down Expand Up @@ -108,20 +100,7 @@ export function Component() {
useRef<ReactCodeMirrorRef>(null)
);

useEffect(() => {
window.editorRefs = editorRefs;
}, [editorRefs]);

useAnimationFrame(
useCallback(() => {
editorRefs.forEach((editorRef) => {
const view = editorRef?.current?.view;
if (!view) return;
updateMiniLocations(view, window.miniLocations || []);
highlightMiniLocations(view, window.phase || 0, window.haps || []);
});
}, [editorRefs])
);
useStrudelCodemirrorExtensions(session, editorRefs);

const { toast: _toast } = useToast();
const hideErrors = !!query.get("hideErrors");
Expand Down Expand Up @@ -158,8 +137,6 @@ export function Component() {
isSecure,
});

window.session = newSession;

// Default documents
newSession.on("sync", (protocol: string) => {
setSyncState(newSession.wsConnected ? "synced" : "partiallySynced");
Expand Down

0 comments on commit 4d77f1c

Please sign in to comment.