Skip to content

Commit

Permalink
Switch worker communication to comlink (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
siefkenj authored Dec 20, 2023
1 parent b073e9a commit 007189a
Show file tree
Hide file tree
Showing 33 changed files with 990 additions and 799 deletions.
505 changes: 231 additions & 274 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@
"rollup-plugin-visualizer": "^5.9.2",
"ts-morph": "^21.0.1",
"typescript": "^5.2.2",
"wasm-pack": "^0.12.1",
"vite-node": "^0.34.6",
"vitest": "^0.34.6"
"vitest": "^0.34.6",
"wasm-pack": "^0.12.1"
},
"prettier": {
"tabWidth": 4
},
"dependencies": {
"@reduxjs/toolkit": "^2.0.1",
"comlink": "^4.4.1",
"react-redux": "^9.0.4",
"@uiw/react-codemirror": "^4.21.21",
"micromark": "^4.0.0",
"wireit": "^0.14.1"
Expand Down
26 changes: 24 additions & 2 deletions packages/doenetml-prototype/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,33 @@
},
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "wireit",
"preview": "vite preview",
"test": "echo \"No tests \"",
"test": "vitest",
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0"
},
"wireit": {
"build": {
"command": "vite build",
"files": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.css",
"tsconfig.json"
],
"output": [
"dist/**/*.js",
"dist/**/*.d.ts",
"dist/**/*.json"
],
"dependencies": [
"../doenetml-worker-rust:build",
"../parser:build"
]
}
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
122 changes: 8 additions & 114 deletions packages/doenetml-prototype/src/DoenetML.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useRef } from "react";
//@ts-ignore
import { prng_alea } from "esm-seedrandom";
import { ActivityViewer } from "./viewer/ActivityViewer";
import { MathJaxContext } from "better-react-mathjax";
import { mathjaxConfig } from "@doenet/utils";
import { PageViewer } from "./viewer/page-viewer";
import { Provider } from "react-redux";
import { store } from "./state/store";
let rngClass = prng_alea;

export type DoenetMLFlags = {
Expand All @@ -20,19 +20,7 @@ export type DoenetMLFlags = {
autoSubmit: boolean;
};

type DoenetMLFlagsSubset = {
showCorrectness?: boolean;
readOnly?: boolean;
solutionDisplayMode?: string;
showFeedback?: boolean;
showHints?: boolean;
allowLoadState?: boolean;
allowSaveState?: boolean;
allowLocalState?: boolean;
allowSaveSubmissions?: boolean;
allowSaveEvents?: boolean;
autoSubmit?: boolean;
};
type DoenetMLFlagsSubset = Partial<DoenetMLFlags>;

const defaultFlags: DoenetMLFlags = {
showCorrectness: true,
Expand All @@ -48,124 +36,30 @@ const defaultFlags: DoenetMLFlags = {
autoSubmit: false,
};

/**
* this is a hack for react-mathqill
* error: global is not defined
*/
window.global = window.global || window;
export function DoenetML({
doenetML,
flags: specifiedFlags = {},
cid,
activityId = "",
userId,
attemptNumber = 1,
requestedVariantIndex,
apiURLs,
generatedVariantCallback,
setErrorsAndWarningsCallback,
forceDisable,
forceShowCorrectness,
forceShowSolution,
forceUnsuppressCheckwork,
addVirtualKeyboard = true,
addBottomPadding = false,
idsIncludeActivityId = true,
darkMode,
}: {
doenetML: string;
flags?: DoenetMLFlagsSubset;
cid?: string;
activityId?: string;
userId?: string;
attemptNumber?: number;
requestedVariantIndex?: number;
apiURLs?: {};
generatedVariantCallback?: Function;
setErrorsAndWarningsCallback?: Function;
forceDisable?: boolean;
forceShowCorrectness?: boolean;
forceShowSolution?: boolean;
forceUnsuppressCheckwork?: boolean;
addVirtualKeyboard?: boolean;
addBottomPadding?: boolean;
idsIncludeActivityId?: boolean;
darkMode?: string;
}) {
const thisPropSet = [
doenetML,
cid || "CID",
activityId || "",
userId || "UID",
requestedVariantIndex || 0,
];
const lastPropSet: React.MutableRefObject<(string | number)[]> = useRef([]);

const variantIndex = useRef(0);

const flags: DoenetMLFlags = { ...defaultFlags, ...specifiedFlags };

if (userId) {
// if userId was specified, then we're viewing results of someone other than the logged in person
// so disable saving state
// and disable even looking up state from local storage (as we want to get the state from the database)
flags.allowLocalState = false;
flags.allowSaveState = false;
} else if (flags.allowSaveState) {
// allowSaveState implies allowLoadState
// Rationale: saving state will result in loading a new state if another device changed it
flags.allowLoadState = true;
}

// Normalize variant index to an integer.
// Generate a random variant index if the requested variant index is undefined.
// To preserve the generated variant index on rerender,
// regenerate only if one of the props in propSet has changed
if (thisPropSet.some((v, i) => v !== lastPropSet.current[i])) {
if (requestedVariantIndex === undefined) {
let rng = new rngClass(new Date());
requestedVariantIndex = Math.floor(rng() * 1000000) + 1;
}
variantIndex.current = Math.round(requestedVariantIndex);
if (!Number.isInteger(variantIndex.current)) {
variantIndex.current = 1;
}
lastPropSet.current = thisPropSet;
}

let keyboard = null;

return (
<MathJaxContext
version={2}
config={mathjaxConfig}
onStartup={(mathJax) => (mathJax.Hub.processSectionDelay = 0)}
>
<ActivityViewer
doenetML={doenetML}
<Provider store={store}>
<PageViewer
source={doenetML}
flags={flags}
cid={cid || "CID"}
activityId={activityId}
userId={userId || "UID"}
attemptNumber={attemptNumber}
requestedVariantIndex={variantIndex.current}
apiURLs={apiURLs || {}}
generatedVariantCallback={
generatedVariantCallback || (() => {})
}
setErrorsAndWarningsCallback={
setErrorsAndWarningsCallback || (() => {})
}
forceDisable={!!forceDisable}
forceShowCorrectness={!!forceShowCorrectness}
forceShowSolution={!!forceShowSolution}
forceUnsuppressCheckwork={!!forceUnsuppressCheckwork}
idsIncludeActivityId={idsIncludeActivityId}
addBottomPadding={addBottomPadding}
darkMode={darkMode || "auto"}
/>
<div className="before-keyboard" />
{keyboard}
</MathJaxContext>
</Provider>
);
}
2 changes: 1 addition & 1 deletion packages/doenetml-prototype/src/global-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const doenetGlobalConfig = {
doenetWorkerUrl: new URL(
"/doenetml-worker/CoreWorker.js",
"/doenetml-worker/index.js",
window.location.href,
).href,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/doenetml-prototype/src/index-inline-worker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from "./index";
import { doenetGlobalConfig } from "./global-config";
// @ts-ignore
import workerSource from "@doenet/doenetml-worker-rust/CoreWorker.js?raw";
import workerSource from "@doenet/doenetml-worker-rust/index.js?raw";

// We make a blob URL directly from the source code of the worker. This way we don't
// need to load any other files
Expand Down
29 changes: 29 additions & 0 deletions packages/doenetml-prototype/src/state/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";
import { AsyncThunkPayloadCreator, createAsyncThunk } from "@reduxjs/toolkit";
import { AsyncThunkConfig } from "@reduxjs/toolkit/dist/createAsyncThunk";

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

/**
* A wrapper around `createAsyncThunk` that logs to the console
* when an error is thrown. Errors thrown in thunks created with `createAsyncThunk` are
* throw silently. (You are expected to catch the `rejected` status to deal with the error.)
*/
export const createLoggingAsyncThunk = <Returned, ThunkArg = void>(
typePrefix: string,
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, AsyncThunkConfig & {state: RootState}>,
) => {
const wrappedPayloadCreator: typeof payloadCreator = (async (...args) => {
try {
return await payloadCreator(...args);
} catch (e) {
console.warn(e);
throw e;
}
}) as typeof payloadCreator;
return createAsyncThunk(typePrefix, wrappedPayloadCreator);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./slice";
import { _coreReducerActions } from "./slice";
import { coreThunks } from "./thunks";

export const coreActions = { ..._coreReducerActions, ...coreThunks };
53 changes: 53 additions & 0 deletions packages/doenetml-prototype/src/state/redux-slices/core/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "../../store";

export interface CoreState {
/**
* Whether core has been initialized.
*/
initialized: boolean;
/**
* Whether core has been started and initialized with a DAST tree
*/
launched: boolean;
/**
* The webworker used by core is stored outside of the redux store (since it is not serializable).
* This key is used to retrieve it.
*/
workerCacheKey?: number;
inErrorState: boolean;
}

// Define the initial state using that type
const initialState: CoreState = {
initialized: false,
launched: false,
workerCacheKey: undefined,
inErrorState: false,
};

const coreSlice = createSlice({
name: "core",
initialState,
reducers: {
_setInitialized: (state, action: PayloadAction<boolean>) => {
state.initialized = action.payload;
},
_setWorkerCacheKey: (state, action: PayloadAction<number>) => {
state.workerCacheKey = action.payload;
},
_setInErrorState: (state, action: PayloadAction<boolean>) => {
state.inErrorState = action.payload;
},
},
});

export const coreReducer = coreSlice.reducer;

/**
* Synchronous actions that directly manipulate data in the store.
*/
export const _coreReducerActions = { ...coreSlice.actions };

export const selfSelector = (state: RootState) => state.core;
Loading

0 comments on commit 007189a

Please sign in to comment.