-
Notifications
You must be signed in to change notification settings - Fork 8
[WIP] proof of concept: HTMLCanvas bitmap context in pyodide #115
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
base: main
Are you sure you want to change the base?
Conversation
feel like some stuff breaks due to the python name mangling when using double underscores in combination with the subclass... which happens quite a bit - so I am sorta surprised it still works this far. But I can't find any such references for Pyodide which would be odd if that is a known limitation. |
that wasn't the case. I actually fell through the line here rendercanvas/rendercanvas/base.py Lines 450 to 451 in fa7defc
I also registered the auto backend successfully - meaning if you build the wheel and then load it statically. The examples noise.py and snake.py work out of the box (although not events yet). But the weekend has a few more days :)
auto_demo.mp4E: turns out that wasn't true either and I am using the existing |
singular keydown event works... so more events and other types shouldn't be impossible. However I will get to that another day. snake_events.mp4 |
tried all day to make it work for the docs ... but either the .whl don't get included as static files or pyodide has trouble importing the wheel. I also wanted to automate the iframe inclusion with sphinx-gallery but seems like you need to either modify the classic "works on my machine", so have a video of what could have been instead: doc_embed.mp4 |
Even so it's awesome how far you managed to take this! |
pythonCode = ` | ||
# Use python script as normally | ||
from rendercanvas.auto import RenderCanvas, loop | ||
canvas = RenderCanvas(title="Example") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that providing a constructor like this would be more conventional for the web:
canvas = RenderCanvas(title="Example") | |
canvas_el = document.getElementById("canvas") | |
canvas = RenderCanvas(canvas_el, title="Example") |
Since often there are multiple canvas elements on the page, users should be able to control which is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the goal was to keep the python code portable between auto backends. So passing a string to __init__()
would work even on backends where this kwarg isn't used like glfw
and the user doesn't need to use any pyodide specific code in python. (Once we have a wgpu-py
version for browser, most examples should just work without changes to shadertoy, pygfx or fastplotlib etc).
I also losely followed the idea of https://pyodide.org/en/stable/usage/sdl.html#setting-canvas where they provide a specific API to accessing the canvas, although I not using it.
Maybe I can write a little multi canvas example to see if my approach works.
I have zero webdev experience, so my design decisions are directed to the python devs wanting to write their python code (like myself).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have zero webdev experience, so my design decisions are directed to the python devs wanting to write their python code (like myself).
I hear you, but just because you can use python the language, doesn't mean you can "ignore" the environment it's running in! I don't mind what kind of API you choose (I value portability as well) as long as the user can control which <canvas>
is used.
I imagine python devs turning to browsers will often do so because they want to use the browser's capabilities to build the UI they have in mind. It's easy to envision applications with multiple canvases embedded in a richer UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it shouldn't be impossible to support both.
canvas_el: [str|HTMLCanvasElement] = "canvas"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would work even on backends where this kwarg isn't used like
glfw
apparently kwargs don't get ignored when a different auto backend is selected because the base class calls super.__init__(*args, *kwargs)
. We could use the title arg as I am not sure if that has a use in the browser, but that seems janky.
from pyodide.ffi import run_sync, create_proxy | ||
from js import document, ImageData, Uint8ClampedArray, window | ||
|
||
# TODO event loop for js? https://rendercanvas.readthedocs.io/stable/backendapi.html#rendercanvas.stub.StubLoop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think asyncio can be used in Pyodide, right?
edit: should have read more before reacting. So is pyodide.webloop a sort of more native loop implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, they overwrite a couple of functions.
But I should try to use the async loop class and see if this whole class can be avoided.
edit: you can't find much on it in the docs so I looked at the source and went from there. Perhaps other prominent pyodide packages provide some specific insight. I pretty much learned all my async and js knowledge from attempting this. Surprised myself it sorta works. But this obviously means I might be doing something wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I ask why a loop is needed at all? Why not just implement call_later with setTimeout and leave everything else to the browser's native loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loop is made to fit into the existing rendercanvas implementation, although it can likely be simpler with more changes. Very much out of my depth here.
The docstrings here says as much, although I am not sure if that's happening all the way down. https://github.com/pyodide/pyodide/blob/bcd0235bff5351d1dc383e4f3e34b9917fdf3281/src/py/pyodide/webloop.py#L280L294
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point is that in the browser there already is an event loop driving the page, and you can not get a reference to it. So I am just wondering what the point of having an event loop abstraction is inside a browser, apart from compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, so I tried with the asyncio loop and it works just fine. Also not calling loop.run()
at the end of an example still works.
so should I drop my loop class and just import the asyncio loop instead?
#38
two evenings of tinkering but I can feel a bit of progress. Now has a "working" loop.
Rendercanvas looks very web inspired, so I am reading a lot of things between the docs, pyodide docs, pyodide source that sound close but are ever so slightly different. Plus I don't have any webdev experience, it's more like a learning opportunity.
for those that want to give it a try - you can essentially just load the .html as a static page with the python script inserted.
some todos: