Skip to content

Commit 1496dab

Browse files
committed
runtimeにinit()を追加し、必要な時だけ初期化
1 parent 59ce490 commit 1496dab

File tree

5 files changed

+60
-27
lines changed

5 files changed

+60
-27
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+
* 初期化は非同期に行い、完了する前にreturnしてよいです。
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
@@ -163,6 +163,7 @@ function MochaTest() {
163163
mocha.setup("bdd");
164164

165165
for (const lang of Object.keys(runtimeRef.current) as RuntimeLang[]) {
166+
runtimeRef.current[lang].init?.();
166167
defineTests(lang, runtimeRef, filesRef);
167168
}
168169

app/terminal/runtime.tsx

Lines changed: 24 additions & 7 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";
@@ -16,6 +18,7 @@ import { TypeScriptProvider, useTypeScript } from "./typescript/runtime";
1618
*
1719
*/
1820
export interface RuntimeContext {
21+
init?: () => void;
1922
ready: boolean;
2023
mutex?: MutexInterface;
2124
interrupt?: () => void;
@@ -24,7 +27,10 @@ export interface RuntimeContext {
2427
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
2528
splitReplExamples?: (content: string) => ReplCommand[];
2629
// file
27-
runFiles: (filenames: string[], files: Readonly<Record<string, string>>) => Promise<ReplOutput[]>;
30+
runFiles: (
31+
filenames: string[],
32+
files: Readonly<Record<string, string>>
33+
) => Promise<ReplOutput[]>;
2834
getCommandlineStr?: (filenames: string[]) => string;
2935
}
3036
export interface LangConstants {
@@ -73,21 +79,32 @@ export function useRuntime(language: RuntimeLang): RuntimeContext {
7379
const typescript = useTypeScript(jsEval);
7480
const wandboxCpp = useWandbox("cpp");
7581

82+
let runtime: RuntimeContext;
7683
switch (language) {
7784
case "python":
78-
return pyodide;
85+
runtime = pyodide;
86+
break;
7987
case "ruby":
80-
return ruby;
88+
runtime = ruby;
89+
break;
8190
case "javascript":
82-
return jsEval;
91+
runtime = jsEval;
92+
break;
8393
case "typescript":
84-
return typescript;
94+
runtime = typescript;
95+
break;
8596
case "cpp":
86-
return wandboxCpp;
97+
runtime = wandboxCpp;
98+
break;
8799
default:
88100
language satisfies never;
89101
throw new Error(`Runtime not implemented for language: ${language}`);
90102
}
103+
const { init } = runtime;
104+
useEffect(() => {
105+
init?.();
106+
}, [init]);
107+
return runtime;
91108
}
92109
export function RuntimeProvider({ children }: { children: ReactNode }) {
93110
return (

app/terminal/typescript/runtime.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
useCallback,
99
useContext,
1010
useEffect,
11+
useRef,
1112
useState,
1213
} from "react";
1314
import { useEmbedContext } from "../embedContext";
@@ -16,18 +17,21 @@ import { RuntimeContext } from "../runtime";
1617

1718
export const compilerOptions: CompilerOptions = {};
1819

19-
const TypeScriptContext = createContext<VirtualTypeScriptEnvironment | null>(
20-
null
21-
);
20+
const TypeScriptContext = createContext<{
21+
init: () => void;
22+
tsEnv: VirtualTypeScriptEnvironment | null;
23+
}>({ init: () => undefined, tsEnv: null });
2224
export function TypeScriptProvider({ children }: { children: ReactNode }) {
2325
const [tsEnv, setTSEnv] = useState<VirtualTypeScriptEnvironment | null>(null);
24-
25-
useEffect(() => {
26-
// useEffectはサーバーサイドでは実行されないが、
26+
const initializing = useRef(false);
27+
const init = useCallback(() => {
28+
if (initializing.current) {
29+
return;
30+
}
31+
initializing.current = true;
2732
// typeof window !== "undefined" でガードしないとなぜかesbuildが"typescript"を
2833
// サーバーサイドでのインポート対象とみなしてしまう。
2934
if (tsEnv === null && typeof window !== "undefined") {
30-
const abortController = new AbortController();
3135
(async () => {
3236
const ts = await import("typescript");
3337
const vfs = await import("@typescript/vfs");
@@ -39,8 +43,7 @@ export function TypeScriptProvider({ children }: { children: ReactNode }) {
3943
const libFileContents = await Promise.all(
4044
libFiles.map(async (libFile) => {
4145
const response = await fetch(
42-
`/typescript/${ts.version}/${libFile}`,
43-
{ signal: abortController.signal }
46+
`/typescript/${ts.version}/${libFile}`
4447
);
4548
if (response.ok) {
4649
return response.text();
@@ -63,20 +66,17 @@ export function TypeScriptProvider({ children }: { children: ReactNode }) {
6366
);
6467
setTSEnv(env);
6568
})();
66-
return () => {
67-
abortController.abort();
68-
};
6969
}
7070
}, [tsEnv, setTSEnv]);
7171
return (
72-
<TypeScriptContext.Provider value={tsEnv}>
72+
<TypeScriptContext.Provider value={{ init, tsEnv }}>
7373
{children}
7474
</TypeScriptContext.Provider>
7575
);
7676
}
7777

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

8181
const { writeFile } = useEmbedContext();
8282
const runFiles = useCallback(
@@ -139,6 +139,7 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
139139
[tsEnv, writeFile, jsEval]
140140
);
141141
return {
142+
init,
142143
ready: tsEnv !== null,
143144
runFiles,
144145
getCommandlineStr,

app/terminal/worker/runtime.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ export function WorkerProvider({
9696
}
9797

9898
const initializeWorker = useCallback(async () => {
99+
if (!mutex.current.isLocked()) {
100+
throw new Error(`mutex of context must be locked for initializeWorker`);
101+
}
102+
if (workerRef.current) {
103+
return;
104+
}
105+
99106
let worker: Worker;
100107
lang satisfies RuntimeLang;
101108
switch (lang) {
@@ -137,12 +144,12 @@ export function WorkerProvider({
137144
});
138145
}, [lang]);
139146

140-
// Initialization effect
141-
useEffect(() => {
142-
initializeWorker().then(() => setReady(true));
143-
return () => {
144-
workerRef.current?.terminate();
145-
};
147+
// First initialization
148+
const init = useCallback(() => {
149+
// すでに初期化済みだった場合initializeWorker()がreturnしなにもしない
150+
void mutex.current.runExclusive(() =>
151+
initializeWorker().then(() => setReady(true))
152+
);
146153
}, [initializeWorker]);
147154

148155
const interrupt = useCallback(() => {
@@ -161,6 +168,7 @@ export function WorkerProvider({
161168
messageCallbacks.current.clear();
162169

163170
workerRef.current?.terminate();
171+
workerRef.current = null;
164172
setReady(false);
165173

166174
void mutex.current.runExclusive(async () => {
@@ -278,6 +286,7 @@ export function WorkerProvider({
278286
return (
279287
<context.Provider
280288
value={{
289+
init,
281290
ready,
282291
runCommand,
283292
checkSyntax,

0 commit comments

Comments
 (0)