Skip to content

Commit 858ff56

Browse files
authored
Merge pull request #109 from ut-code/worker-init-ondemand
runtimeにinit()を追加し、必要な時だけ初期化
2 parents f8dbb41 + 7ab2c46 commit 858ff56

File tree

5 files changed

+75
-30
lines changed

5 files changed

+75
-30
lines changed

app/terminal/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ runtime.tsx の `useRuntime(lang)` は各言語のフックを呼び出し、そ
1010

1111
### 共通
1212

13+
* init?: `() => void`
14+
* useRuntime() 内のuseEffectなどで呼び出されます。ランタイムを使う側では通常呼び出す必要はないです。
15+
* ランタイムの初期化にコストがかかるものは、init()で初期化フラグがトリガーされたときだけ初期化するようにします。
16+
* useRuntime() が複数回使われた場合はinitも複数回呼ばれます。
17+
* init()はフラグを立てるだけにし、完了する前にreturnしてよいです。初期化とcleanupはuseEffect()で非同期に行うのがよいと思います。
1318
* ready: `boolean`
1419
* ランタイムの初期化が完了したか、不要である場合true
1520
* mutex?: `MutexInterface`

app/terminal/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ function MochaTest() {
208208
mocha.setup("bdd");
209209

210210
for (const lang of Object.keys(runtimeRef.current) as RuntimeLang[]) {
211+
runtimeRef.current[lang].init?.();
211212
defineTests(lang, runtimeRef, filesRef);
212213
}
213214

app/terminal/runtime.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
"use client";
2+
13
import { MutexInterface } from "async-mutex";
24
import { ReplOutput, SyntaxStatus, ReplCommand } from "./repl";
35
import { useWandbox, WandboxProvider } from "./wandbox/runtime";
46
import { AceLang } from "./editor";
5-
import { ReactNode } from "react";
7+
import { ReactNode, useEffect } from "react";
68
import { PyodideContext, usePyodide } from "./worker/pyodide";
79
import { RubyContext, useRuby } from "./worker/ruby";
810
import { JSEvalContext, useJSEval } from "./worker/jsEval";
@@ -17,6 +19,7 @@ import { MarkdownLang } from "@/[docs_id]/styledSyntaxHighlighter";
1719
*
1820
*/
1921
export interface RuntimeContext {
22+
init?: () => void;
2023
ready: boolean;
2124
mutex?: MutexInterface;
2225
interrupt?: () => void;
@@ -87,21 +90,32 @@ export function useRuntime(language: RuntimeLang): RuntimeContext {
8790
const typescript = useTypeScript(jsEval);
8891
const wandboxCpp = useWandbox("cpp");
8992

93+
let runtime: RuntimeContext;
9094
switch (language) {
9195
case "python":
92-
return pyodide;
96+
runtime = pyodide;
97+
break;
9398
case "ruby":
94-
return ruby;
99+
runtime = ruby;
100+
break;
95101
case "javascript":
96-
return jsEval;
102+
runtime = jsEval;
103+
break;
97104
case "typescript":
98-
return typescript;
105+
runtime = typescript;
106+
break;
99107
case "cpp":
100-
return wandboxCpp;
108+
runtime = wandboxCpp;
109+
break;
101110
default:
102111
language satisfies never;
103112
throw new Error(`Runtime not implemented for language: ${language}`);
104113
}
114+
const { init } = runtime;
115+
useEffect(() => {
116+
init?.();
117+
}, [init]);
118+
return runtime;
105119
}
106120
export function RuntimeProvider({ children }: { children: ReactNode }) {
107121
return (

app/terminal/typescript/runtime.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ import { RuntimeContext } from "../runtime";
1616

1717
export const compilerOptions: CompilerOptions = {};
1818

19-
const TypeScriptContext = createContext<VirtualTypeScriptEnvironment | null>(
20-
null
21-
);
19+
const TypeScriptContext = createContext<{
20+
init: () => void;
21+
tsEnv: VirtualTypeScriptEnvironment | null;
22+
}>({ init: () => undefined, tsEnv: null });
2223
export function TypeScriptProvider({ children }: { children: ReactNode }) {
2324
const [tsEnv, setTSEnv] = useState<VirtualTypeScriptEnvironment | null>(null);
24-
25+
const [doInit, setDoInit] = useState(false);
26+
const init = useCallback(() => setDoInit(true), []);
2527
useEffect(() => {
2628
// useEffectはサーバーサイドでは実行されないが、
2729
// typeof window !== "undefined" でガードしないとなぜかesbuildが"typescript"を
2830
// サーバーサイドでのインポート対象とみなしてしまう。
29-
if (tsEnv === null && typeof window !== "undefined") {
31+
if (doInit && tsEnv === null && typeof window !== "undefined") {
3032
const abortController = new AbortController();
3133
(async () => {
3234
const ts = await import("typescript");
@@ -67,16 +69,16 @@ export function TypeScriptProvider({ children }: { children: ReactNode }) {
6769
abortController.abort();
6870
};
6971
}
70-
}, [tsEnv, setTSEnv]);
72+
}, [tsEnv, setTSEnv, doInit]);
7173
return (
72-
<TypeScriptContext.Provider value={tsEnv}>
74+
<TypeScriptContext.Provider value={{ init, tsEnv }}>
7375
{children}
7476
</TypeScriptContext.Provider>
7577
);
7678
}
7779

7880
export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
79-
const tsEnv = useContext(TypeScriptContext);
81+
const { init, tsEnv } = useContext(TypeScriptContext);
8082

8183
const { writeFile } = useEmbedContext();
8284
const runFiles = useCallback(
@@ -139,6 +141,7 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
139141
[tsEnv, writeFile, jsEval]
140142
);
141143
return {
144+
init,
142145
ready: tsEnv !== null,
143146
runFiles,
144147
getCommandlineStr,

app/terminal/worker/runtime.tsx

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ReactNode,
66
useCallback,
77
useEffect,
8+
useMemo,
89
useRef,
910
useState,
1011
} from "react";
@@ -69,7 +70,7 @@ export function WorkerProvider({
6970
}) {
7071
const workerRef = useRef<Worker | null>(null);
7172
const [ready, setReady] = useState<boolean>(false);
72-
const mutex = useRef<MutexInterface>(new Mutex());
73+
const mutex = useMemo<MutexInterface>(() => new Mutex(), []);
7374
const { writeFile } = useEmbedContext();
7475

7576
const messageCallbacks = useRef<
@@ -96,6 +97,13 @@ export function WorkerProvider({
9697
}
9798

9899
const initializeWorker = useCallback(async () => {
100+
if (!mutex.isLocked()) {
101+
throw new Error(`mutex of context must be locked for initializeWorker`);
102+
}
103+
if (workerRef.current) {
104+
return;
105+
}
106+
99107
let worker: Worker;
100108
lang satisfies RuntimeLang;
101109
switch (lang) {
@@ -135,15 +143,27 @@ export function WorkerProvider({
135143
}).then((payload) => {
136144
capabilities.current = payload.capabilities;
137145
});
138-
}, [lang]);
146+
}, [lang, mutex]);
147+
148+
const [doInit, setDoInit] = useState(false);
149+
const init = useCallback(() => setDoInit(true), []);
139150

140151
// Initialization effect
141152
useEffect(() => {
142-
initializeWorker().then(() => setReady(true));
143-
return () => {
144-
workerRef.current?.terminate();
145-
};
146-
}, [initializeWorker]);
153+
if (doInit) {
154+
void mutex.runExclusive(async () => {
155+
await initializeWorker();
156+
setReady(true);
157+
});
158+
return () => {
159+
void mutex.runExclusive(async () => {
160+
workerRef.current?.terminate();
161+
workerRef.current = null;
162+
setReady(false);
163+
});
164+
};
165+
}
166+
}, [doInit, initializeWorker, mutex]);
147167

148168
const interrupt = useCallback(() => {
149169
if (!capabilities.current) return;
@@ -161,9 +181,10 @@ export function WorkerProvider({
161181
messageCallbacks.current.clear();
162182

163183
workerRef.current?.terminate();
184+
workerRef.current = null;
164185
setReady(false);
165186

166-
void mutex.current.runExclusive(async () => {
187+
void mutex.runExclusive(async () => {
167188
await initializeWorker();
168189
if (commandHistory.current.length > 0) {
169190
await postMessage("restoreState", {
@@ -178,11 +199,11 @@ export function WorkerProvider({
178199
capabilities.current?.interrupt satisfies never;
179200
break;
180201
}
181-
}, [initializeWorker]);
202+
}, [initializeWorker, mutex]);
182203

183204
const runCommand = useCallback(
184205
async (code: string): Promise<ReplOutput[]> => {
185-
if (!mutex.current.isLocked()) {
206+
if (!mutex.isLocked()) {
186207
throw new Error(`mutex of context must be locked for runCommand`);
187208
}
188209
if (!workerRef.current || !ready) {
@@ -222,18 +243,18 @@ export function WorkerProvider({
222243
return [{ type: "error", message: String(error) }];
223244
}
224245
},
225-
[ready, writeFile]
246+
[ready, writeFile, mutex]
226247
);
227248

228249
const checkSyntax = useCallback(
229250
async (code: string): Promise<SyntaxStatus> => {
230251
if (!workerRef.current || !ready) return "invalid";
231-
const { status } = await mutex.current.runExclusive(() =>
252+
const { status } = await mutex.runExclusive(() =>
232253
postMessage("checkSyntax", { code })
233254
);
234255
return status;
235256
},
236-
[ready]
257+
[ready, mutex]
237258
);
238259

239260
const runFiles = useCallback(
@@ -263,7 +284,7 @@ export function WorkerProvider({
263284
) {
264285
interruptBuffer.current[0] = 0;
265286
}
266-
return mutex.current.runExclusive(async () => {
287+
return mutex.runExclusive(async () => {
267288
const { output, updatedFiles } = await postMessage("runFile", {
268289
name: filenames[0],
269290
files,
@@ -272,16 +293,17 @@ export function WorkerProvider({
272293
return output;
273294
});
274295
},
275-
[ready, writeFile]
296+
[ready, writeFile, mutex]
276297
);
277298

278299
return (
279300
<context.Provider
280301
value={{
302+
init,
281303
ready,
282304
runCommand,
283305
checkSyntax,
284-
mutex: mutex.current,
306+
mutex,
285307
runFiles,
286308
interrupt,
287309
}}

0 commit comments

Comments
 (0)