Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hack open function to write files in main thread #1146

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions src/PyodideWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const PyodideWorker = () => {
// Import scripts dynamically based on the environment
importScripts(
`${process.env.ASSETS_URL}/pyodide/shims/_internal_sense_hat.js`,
`${process.env.ASSETS_URL}/pyodide/shims/_internal_sense_hat.js`

Check failure on line 7 in src/PyodideWorker.js

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
);
importScripts(`${process.env.ASSETS_URL}/pyodide/shims/pygal.js`);
importScripts("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.js");
Expand All @@ -29,7 +29,7 @@
"Please refer to these code snippets for registering a service worker:",
" - https://github.com/RaspberryPiFoundation/python-execution-prototypes/blob/fd2c50e032cba3bb0e92e19a88eb62e5b120fe7a/pyodide/index.html#L92-L98",
" - https://github.com/RaspberryPiFoundation/python-execution-prototypes/blob/fd2c50e032cba3bb0e92e19a88eb62e5b120fe7a/pyodide/serviceworker.js",
].join("\n"),
].join("\n")

Check failure on line 32 in src/PyodideWorker.js

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
);
}
let pyodide, pyodidePromise, stdinBuffer, interruptBuffer, stopped;
Expand Down Expand Up @@ -61,11 +61,12 @@
const runPython = async (python) => {
stopped = false;
await pyodide.loadPackage("pyodide_http");
// pyodide.registerJsModule("basthon", fakeBasthonPackage);

await pyodide.runPythonAsync(`
import pyodide_http
pyodide_http.patch_all()
`);
// await pyodide.runPythonAsync(`
// import pyodide_http
// pyodide_http.patch_all()
// `);

try {
await withSupportForPackages(python, async () => {
Expand All @@ -89,7 +90,7 @@

const withSupportForPackages = async (
python,
runPythonFn = async () => {},
runPythonFn = async () => {}

Check failure on line 93 in src/PyodideWorker.js

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
) => {
const imports = await pyodide._api.pyodide_code.find_imports(python).toJs();
await Promise.all(imports.map((name) => loadDependency(name)));
Expand Down Expand Up @@ -162,7 +163,7 @@
enigma: {
before: async () => {
await pyodide.loadPackage(
`${process.env.ASSETS_URL}/pyodide/packages/py_enigma-0.1-py3-none-any.whl`,
`${process.env.ASSETS_URL}/pyodide/packages/py_enigma-0.1-py3-none-any.whl`

Check failure on line 166 in src/PyodideWorker.js

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
);
},
after: () => {},
Expand All @@ -171,7 +172,7 @@
before: async () => {
pyodide.registerJsModule("basthon", fakeBasthonPackage);
await pyodide.loadPackage(
`${process.env.ASSETS_URL}/pyodide/packages/turtle-0.0.1-py3-none-any.whl`,
`${process.env.ASSETS_URL}/pyodide/packages/turtle-0.0.1-py3-none-any.whl`

Check failure on line 175 in src/PyodideWorker.js

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
);
},
after: () =>
Expand Down Expand Up @@ -334,8 +335,10 @@
display_event: (event) => {
const origin = event.toJs().get("display_type");
const content = event.toJs().get("content");
const filename = event.toJs().get("filename");

postMessage({ method: "handleVisual", origin, content });
console.log({ origin, content, filename });
postMessage({ method: "handleVisual", origin, content, filename });
},
locals: () => pyodide.runPython("globals()"),
},
Expand Down Expand Up @@ -364,6 +367,8 @@

pyodide = await pyodidePromise;

pyodide.registerJsModule("basthon", fakeBasthonPackage);

await pyodide.runPythonAsync(`
__old_input__ = input
def __patched_input__(prompt=False):
Expand All @@ -373,6 +378,36 @@
__builtins__.input = __patched_input__
`);

await pyodide.runPythonAsync(`
import basthon
import builtins

# Save the original open function
_original_open = builtins.open

def _custom_open(filename, mode="r", *args, **kwargs):
if "w" in mode or "a" in mode:
class CustomFile:
def __init__(self, filename):
self.filename = filename
self.content = ""

def write(self, content):
self.content += content
# print(f"{self.filename} {self.content}")
basthon.kernel.display_event({ "display_type": "file", "filename": self.filename, "content": str({"filename": self.filename, "content": self.content}) })

def close(self):
pass

return CustomFile(filename)
else:
return _original_open(filename, mode, *args, **kwargs)

# Override the built-in open function
builtins.open = _custom_open
`);

if (supportsAllFeatures) {
stdinBuffer =
stdinBuffer || new Int32Array(new SharedArrayBuffer(1024 * 1024)); // 1 MiB
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React, { useEffect, useRef } from "react";
import { useSelector } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import AstroPiModel from "../../../../AstroPiModel/AstroPiModel";
import Highcharts from "highcharts";
import { updateProjectComponent } from "../../../../../redux/EditorSlice";

const VisualOutputPane = ({ visuals, setVisuals }) => {
const senseHatEnabled = useSelector((s) => s.editor.senseHatEnabled);
const senseHatAlways = useSelector((s) => s.editor.senseHatAlwaysEnabled);
// const projectComponents = useSelector((s) => s.editor.project.components);
const output = useRef();
const dispatch = useDispatch();

useEffect(() => {
if (visuals.length === 0) {
Expand All @@ -14,8 +17,72 @@
} else if (visuals.some((v) => !v.showing)) {
setVisuals((visuals) => showVisuals(visuals, output));
}
}, [visuals, setVisuals]);

Check warning on line 20 in src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'showVisuals'. Either include it or remove the dependency array

const showVisuals = (visuals, output) =>
visuals.map((v) => (v.showing ? v : showVisual(v, output)));

const showVisual = (visual, output) => {
switch (visual.origin) {
case "sense_hat":
output.current.textContent = JSON.stringify(visual.content);
break;
case "pygal":
const chartContent = {
...visual.content,
chart: {
...visual.content.chart,
events: {
...visual.content.chart.events,
load: function () {
this.renderTo.style.overflow = "visible";
},
},
},
tooltip: {
...visual.content.tooltip,
formatter:
visual.content.chart.type === "pie"
? function () {
return this.key + ": " + this.y;
}
: null,
},
};
Highcharts.chart(output.current, chartContent);
break;
case "turtle":
output.current.innerHTML = elementFromProps(visual.content).outerHTML;
break;
case "matplotlib":
// convert visual.content from Uint8Array to jpg
const img = document.createElement("img");
img.style = "max-width: 100%; max-height: 100%;";
img.src = `data:image/jpg;base64,${window.btoa(
String.fromCharCode(...new Uint8Array(visual.content))

Check failure on line 62 in src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
)}`;
output.current.innerHTML = img.outerHTML;
break;
case "file":
console.log("from the main thread:", visual);
const content = JSON.parse(visual.content.replace(/'/g, '"'));
const [name, extension] = content.filename.split(".");
dispatch(
updateProjectComponent({
extension: extension,
name: name,
code: content.content,
})

Check failure on line 75 in src/components/Editor/Runners/PythonRunner/PyodideRunner/VisualOutputPane.jsx

View workflow job for this annotation

GitHub Actions / lint

Insert `,`
);
break;
default:
throw new Error(`Unsupported origin: ${visual.origin}`);
}

visual.showing = true;
return visual;
};

return (
<div className="visual-output">
<div ref={output} className="pythonrunner-graphic" />
Expand All @@ -24,58 +91,6 @@
);
};

const showVisuals = (visuals, output) =>
visuals.map((v) => (v.showing ? v : showVisual(v, output)));

const showVisual = (visual, output) => {
switch (visual.origin) {
case "sense_hat":
output.current.textContent = JSON.stringify(visual.content);
break;
case "pygal":
const chartContent = {
...visual.content,
chart: {
...visual.content.chart,
events: {
...visual.content.chart.events,
load: function () {
this.renderTo.style.overflow = "visible";
},
},
},
tooltip: {
...visual.content.tooltip,
formatter:
visual.content.chart.type === "pie"
? function () {
return this.key + ": " + this.y;
}
: null,
},
};
Highcharts.chart(output.current, chartContent);
break;
case "turtle":
output.current.innerHTML = elementFromProps(visual.content).outerHTML;
break;
case "matplotlib":
// convert visual.content from Uint8Array to jpg
const img = document.createElement("img");
img.style = "max-width: 100%; max-height: 100%;";
img.src = `data:image/jpg;base64,${window.btoa(
String.fromCharCode(...new Uint8Array(visual.content)),
)}`;
output.current.innerHTML = img.outerHTML;
break;
default:
throw new Error(`Unsupported origin: ${visual.origin}`);
}

visual.showing = true;
return visual;
};

const elementFromProps = (map) => {
const tag = map.get("tag");
if (!tag) {
Expand Down
Loading