Skip to content

Commit a494a6b

Browse files
authored
Merge pull request #148 from ut-code/copilot/refactor-message-processing-comlink
Refactor worker message handling to use Comlink
2 parents 1c2186c + 1e69bec commit a494a6b

File tree

6 files changed

+185
-333
lines changed

6 files changed

+185
-333
lines changed

app/terminal/worker/jsEval.worker.ts

Lines changed: 34 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/// <reference lib="webworker" />
22

3+
import { expose } from "comlink";
34
import type { ReplOutput } from "../repl";
4-
import type { MessageType, WorkerRequest, WorkerResponse } from "./runtime";
5+
import type { WorkerCapabilities } from "./runtime";
56
import inspect from "object-inspect";
67

78
function format(...args: unknown[]): string {
@@ -29,12 +30,12 @@ self.console = {
2930
},
3031
};
3132

32-
async function init({ id }: WorkerRequest["init"]) {
33+
async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{
34+
capabilities: WorkerCapabilities;
35+
}> {
3336
// Initialize the worker and report capabilities
34-
self.postMessage({
35-
id,
36-
payload: { capabilities: { interrupt: "restart" } },
37-
} satisfies WorkerResponse["init"]);
37+
// interruptBuffer is not used for JavaScript (restart-based interruption)
38+
return { capabilities: { interrupt: "restart" } };
3839
}
3940

4041
async function replLikeEval(code: string): Promise<unknown> {
@@ -72,8 +73,10 @@ async function replLikeEval(code: string): Promise<unknown> {
7273
}
7374
}
7475

75-
async function runCode({ id, payload }: WorkerRequest["runCode"]) {
76-
const { code } = payload;
76+
async function runCode(code: string): Promise<{
77+
output: ReplOutput[];
78+
updatedFiles: Record<string, string>;
79+
}> {
7780
try {
7881
const result = await replLikeEval(code);
7982
jsOutput.push({
@@ -99,14 +102,13 @@ async function runCode({ id, payload }: WorkerRequest["runCode"]) {
99102
const output = [...jsOutput];
100103
jsOutput = []; // Clear output
101104

102-
self.postMessage({
103-
id,
104-
payload: { output, updatedFiles: [] },
105-
} satisfies WorkerResponse["runCode"]);
105+
return { output, updatedFiles: {} as Record<string, string> };
106106
}
107107

108-
function runFile({ id, payload }: WorkerRequest["runFile"]) {
109-
const { name, files } = payload;
108+
function runFile(
109+
name: string,
110+
files: Record<string, string>
111+
): { output: ReplOutput[]; updatedFiles: Record<string, string> } {
110112
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
111113
try {
112114
self.eval(files[name]);
@@ -129,23 +131,17 @@ function runFile({ id, payload }: WorkerRequest["runFile"]) {
129131
const output = [...jsOutput];
130132
jsOutput = []; // Clear output
131133

132-
self.postMessage({
133-
id,
134-
payload: { output, updatedFiles: [] },
135-
} satisfies WorkerResponse["runFile"]);
134+
return { output, updatedFiles: {} as Record<string, string> };
136135
}
137136

138-
async function checkSyntax({ id, payload }: WorkerRequest["checkSyntax"]) {
139-
const { code } = payload;
140-
137+
async function checkSyntax(
138+
code: string
139+
): Promise<{ status: "complete" | "incomplete" | "invalid" }> {
141140
try {
142141
// Try to create a Function to check syntax
143142
// new Function(code); // <- not working
144143
self.eval(`() => {${code}}`);
145-
self.postMessage({
146-
id,
147-
payload: { status: "complete" },
148-
} satisfies WorkerResponse["checkSyntax"]);
144+
return { status: "complete" };
149145
} catch (e) {
150146
// Check if it's a syntax error or if more input is expected
151147
if (e instanceof SyntaxError) {
@@ -154,28 +150,18 @@ async function checkSyntax({ id, payload }: WorkerRequest["checkSyntax"]) {
154150
e.message.includes("Unexpected token '}'") ||
155151
e.message.includes("Unexpected end of input")
156152
) {
157-
self.postMessage({
158-
id,
159-
payload: { status: "incomplete" },
160-
} satisfies WorkerResponse["checkSyntax"]);
153+
return { status: "incomplete" };
161154
} else {
162-
self.postMessage({
163-
id,
164-
payload: { status: "invalid" },
165-
} satisfies WorkerResponse["checkSyntax"]);
155+
return { status: "invalid" };
166156
}
167157
} else {
168-
self.postMessage({
169-
id,
170-
payload: { status: "invalid" },
171-
} satisfies WorkerResponse["checkSyntax"]);
158+
return { status: "invalid" };
172159
}
173160
}
174161
}
175162

176-
async function restoreState({ id, payload }: WorkerRequest["restoreState"]) {
163+
async function restoreState(commands: string[]): Promise<object> {
177164
// Re-execute all previously successful commands to restore state
178-
const { commands } = payload;
179165
jsOutput = []; // Clear output for restoration
180166

181167
for (const command of commands) {
@@ -188,32 +174,15 @@ async function restoreState({ id, payload }: WorkerRequest["restoreState"]) {
188174
}
189175

190176
jsOutput = []; // Clear any output from restoration
191-
self.postMessage({
192-
id,
193-
payload: {},
194-
} satisfies WorkerResponse["restoreState"]);
177+
return {};
195178
}
196179

197-
self.onmessage = async (event: MessageEvent<WorkerRequest[MessageType]>) => {
198-
switch (event.data.type) {
199-
case "init":
200-
await init(event.data);
201-
return;
202-
case "runCode":
203-
await runCode(event.data);
204-
return;
205-
case "runFile":
206-
runFile(event.data);
207-
return;
208-
case "checkSyntax":
209-
await checkSyntax(event.data);
210-
return;
211-
case "restoreState":
212-
await restoreState(event.data);
213-
return;
214-
default:
215-
event.data satisfies never;
216-
originalConsole.error(`Unknown message: ${event.data}`);
217-
return;
218-
}
180+
const api = {
181+
init,
182+
runCode,
183+
runFile,
184+
checkSyntax,
185+
restoreState,
219186
};
187+
188+
expose(api);

app/terminal/worker/pyodide.worker.ts

Lines changed: 37 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/// <reference lib="webworker" />
22
/// <reference lib="ES2023" />
33

4+
import { expose } from "comlink";
45
import type { PyodideInterface } from "pyodide";
56
// import { loadPyodide } from "pyodide"; -> Reading from "node:child_process" is not handled by plugins
67
import { version as pyodideVersion } from "pyodide/package.json";
78
import type { PyCallable } from "pyodide/ffi";
8-
import type { MessageType, WorkerRequest, WorkerResponse } from "./runtime";
9+
import type { WorkerCapabilities } from "./runtime";
910
import type { ReplOutput } from "../repl";
1011

1112
import execfile_py from "./pyodide/execfile.py?raw";
@@ -36,8 +37,9 @@ function readAllFiles(): Record<string, string> {
3637
return updatedFiles;
3738
}
3839

39-
async function init({ id, payload }: WorkerRequest["init"]) {
40-
const { interruptBuffer } = payload;
40+
async function init(
41+
interruptBuffer: Uint8Array
42+
): Promise<{ capabilities: WorkerCapabilities }> {
4143
if (!pyodide) {
4244
self.importScripts(`${PYODIDE_CDN}pyodide.js`);
4345

@@ -57,20 +59,15 @@ async function init({ id, payload }: WorkerRequest["init"]) {
5759

5860
pyodide.setInterruptBuffer(interruptBuffer);
5961
}
60-
self.postMessage({
61-
id,
62-
payload: { capabilities: { interrupt: "buffer" } },
63-
} satisfies WorkerResponse["init"]);
62+
return { capabilities: { interrupt: "buffer" } };
6463
}
6564

66-
async function runCode({ id, payload }: WorkerRequest["runCode"]) {
67-
const { code } = payload;
65+
async function runCode(code: string): Promise<{
66+
output: ReplOutput[];
67+
updatedFiles: Record<string, string>;
68+
}> {
6869
if (!pyodide) {
69-
self.postMessage({
70-
id,
71-
error: "Pyodide not initialized",
72-
} satisfies WorkerResponse["runCode"]);
73-
return;
70+
throw new Error("Pyodide not initialized");
7471
}
7572
try {
7673
const result = await pyodide.runPythonAsync(code);
@@ -115,20 +112,15 @@ async function runCode({ id, payload }: WorkerRequest["runCode"]) {
115112
const output = [...pyodideOutput];
116113
pyodideOutput = []; // 出力をクリア
117114

118-
self.postMessage({
119-
id,
120-
payload: { output, updatedFiles },
121-
} satisfies WorkerResponse["runCode"]);
115+
return { output, updatedFiles };
122116
}
123117

124-
async function runFile({ id, payload }: WorkerRequest["runFile"]) {
125-
const { name, files } = payload;
118+
async function runFile(
119+
name: string,
120+
files: Record<string, string>
121+
): Promise<{ output: ReplOutput[]; updatedFiles: Record<string, string> }> {
126122
if (!pyodide) {
127-
self.postMessage({
128-
id,
129-
error: "Pyodide not initialized",
130-
} satisfies WorkerResponse["runFile"]);
131-
return;
123+
throw new Error("Pyodide not initialized");
132124
}
133125
try {
134126
// Use Pyodide FS API to write files to the file system
@@ -175,70 +167,41 @@ async function runFile({ id, payload }: WorkerRequest["runFile"]) {
175167
const updatedFiles = readAllFiles();
176168
const output = [...pyodideOutput];
177169
pyodideOutput = []; // 出力をクリア
178-
self.postMessage({
179-
id,
180-
payload: { output, updatedFiles },
181-
} satisfies WorkerResponse["runFile"]);
170+
return { output, updatedFiles };
182171
}
183172

184-
async function checkSyntax({ id, payload }: WorkerRequest["checkSyntax"]) {
185-
const { code } = payload;
173+
async function checkSyntax(
174+
code: string
175+
): Promise<{ status: "complete" | "incomplete" | "invalid" }> {
186176
if (!pyodide) {
187-
self.postMessage({
188-
id,
189-
payload: { status: "invalid" },
190-
} satisfies WorkerResponse["checkSyntax"]);
191-
return;
177+
return { status: "invalid" };
192178
}
193179

194180
// 複数行コマンドは最後に空行を入れないと完了しないものとする
195181
if (code.includes("\n") && code.split("\n").at(-1) !== "") {
196-
self.postMessage({
197-
id,
198-
payload: { status: "incomplete" },
199-
} satisfies WorkerResponse["checkSyntax"]);
200-
return;
182+
return { status: "incomplete" };
201183
}
202184

203185
try {
204186
// Pythonのコードを実行して結果を受け取る
205187
const status = (pyodide.runPython(check_syntax_py) as PyCallable)(code);
206-
self.postMessage({
207-
id,
208-
payload: { status },
209-
} satisfies WorkerResponse["checkSyntax"]);
188+
return { status };
210189
} catch (e) {
211190
console.error("Syntax check error:", e);
212-
self.postMessage({
213-
id,
214-
payload: { status: "invalid" },
215-
} satisfies WorkerResponse["checkSyntax"]);
191+
return { status: "invalid" };
216192
}
217193
}
218194

219-
self.onmessage = async (event: MessageEvent<WorkerRequest[MessageType]>) => {
220-
switch (event.data.type) {
221-
case "init":
222-
await init(event.data);
223-
return;
224-
case "runCode":
225-
await runCode(event.data);
226-
return;
227-
case "runFile":
228-
await runFile(event.data);
229-
return;
230-
case "checkSyntax":
231-
await checkSyntax(event.data);
232-
return;
233-
case "restoreState":
234-
self.postMessage({
235-
id: event.data.id,
236-
error: "not implemented",
237-
} satisfies WorkerResponse["restoreState"]);
238-
return;
239-
default:
240-
event.data satisfies never;
241-
console.error(`Unknown message: ${event.data}`);
242-
return;
243-
}
195+
async function restoreState(): Promise<object> {
196+
throw new Error("not implemented");
197+
}
198+
199+
const api = {
200+
init,
201+
runCode,
202+
runFile,
203+
checkSyntax,
204+
restoreState,
244205
};
206+
207+
expose(api);

0 commit comments

Comments
 (0)