steveyehtw's picture
Harden demo runtime and add browser Pygame runner
7547388
Raw History Blame Contribute Delete
3.18 kB
let pyodide = null;
let inputControl = null;
let inputBytes = null;
async function initialize() {
try {
importScripts("/classroom-assets/vendor/pyodide/pyodide.js");
pyodide = await loadPyodide({ indexURL: "/classroom-assets/vendor/pyodide/" });
pyodide.setStdout({
write(buffer) {
postMessage({ type: "stream", channel: "stdout", bytes: buffer });
return buffer.length;
},
isatty: true
});
pyodide.setStderr({
write(buffer) {
postMessage({ type: "stream", channel: "stderr", bytes: buffer });
return buffer.length;
},
isatty: true
});
postMessage({ type: "ready" });
} catch (error) {
postMessage({ type: "load_error", text: String(error) });
}
}
function readInteractiveInput() {
if (!inputControl || !inputBytes) return "";
Atomics.store(inputControl, 0, 0);
postMessage({ type: "input_request" });
Atomics.wait(inputControl, 0, 0);
const length = Math.max(0, Math.min(Atomics.load(inputControl, 1), inputBytes.length));
const value = new TextDecoder().decode(inputBytes.slice(0, length));
Atomics.store(inputControl, 0, 0);
return value;
}
self.onmessage = async event => {
if (event.data.type !== "run" || !pyodide) return;
const sharedInput = event.data.inputBuffer;
if (!(sharedInput instanceof SharedArrayBuffer)) {
postMessage({ type: "python_error", text: "瀏覽器未提供互動式 Python 所需的安全執行環境。" });
return;
}
inputControl = new Int32Array(sharedInput, 0, 2);
inputBytes = new Uint8Array(sharedInput, 8);
pyodide.setStdin({ stdin: readInteractiveInput, isatty: true });
try {
const files = event.data.files || { "main.py": event.data.code || "" };
const entry = event.data.entry || "main.py";
pyodide.runPython(`
import os, shutil, sys
root = "/home/pyodide/classroom_project"
shutil.rmtree(root, ignore_errors=True)
os.makedirs(root, exist_ok=True)
for name, module in list(sys.modules.items()):
if str(getattr(module, "__file__", "")).startswith(root):
del sys.modules[name]
`);
for (const [path, content] of Object.entries(files)) {
const fullPath = `/home/pyodide/classroom_project/${path}`;
pyodide.FS.mkdirTree(fullPath.slice(0, fullPath.lastIndexOf("/")));
pyodide.FS.writeFile(fullPath, content, { encoding: "utf8" });
}
pyodide.globals.set("classroom_entry", entry);
const succeeded = await pyodide.runPythonAsync(`
import os, sys, traceback
root = "/home/pyodide/classroom_project"
os.chdir(root)
if root not in sys.path:
sys.path.insert(0, root)
entry = os.path.join(root, classroom_entry)
namespace = {"__name__": "__main__", "__file__": entry}
ok = True
try:
with open(entry, "r", encoding="utf-8") as source:
exec(compile(source.read(), entry, "exec"), namespace)
except BaseException:
ok = False
traceback.print_exc()
finally:
sys.stdout.flush()
sys.stderr.flush()
ok
`);
postMessage({ type: succeeded ? "done" : "python_error", text: succeeded ? "" : "程式執行失敗" });
} catch (error) {
postMessage({ type: "python_error", text: String(error) });
}
};
initialize();