jupyter-widgets-contrib / ipycanvas

Interactive Canvas in Jupyter
https://ipycanvas.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
685 stars 62 forks source link

Proposal: javascript thread with callback #243

Open markriedl opened 2 years ago

markriedl commented 2 years ago

ipycanvas struggles with threading in Google Colab notebooks and the threading examples don't behave properly. To mitigate, and perhaps for other reasons, it would be useful to have a thread running on the javascript side of things at a particular framerate and posting callbacks to be caught on the python side. That way, the python side will not need run multiple threads. This does seem to only be a problem with Google Colab notebooks, however. (Why Google Colab? I'd like to be able to use Google's free GPUs to train neural networks that interact with pygame via ipycanvas.) Thanks for considering my weird request!

martinRenou commented 2 years ago

Thanks for opening an issue :)

I just gave this Notebook a try on Colab, and indeed it's not working properly... Running the loop in the main thread seems to work better, but still is quite slow.

Now back to your original suggestion, I made some attempts in the past to allow running animation loops in the front-end (if that's what you want), see https://github.com/martinRenou/ipycanvas/pull/75. But that was a bit too complicated and I decided to keep the library simple.

Would you be able to provide a code sample that shows what you're trying to do with ipycanvas? I might be able to suggest some tricks to make it more efficient.

markriedl commented 2 years ago

Somewhere during initialization call in CanvasView in widgets.ts:

setInterval(timerUpdate, fps)

This will set a recurring timer. Catch the timer and then make a callback to python.

function timerUpdate() {
      this.model.send({ event: 'tick', compute_actual_time_delta() }, {});
}

On the python side, it would look something like this:

canvas = Canvas(width=800, height=600)
canvas.on_tick(my_on_tick_handler_func)

Then the tick handler could look like this, pushing the image every tick:

def my_on_tick_handler_fun():
   do_whatever_updates()
   image = render_image()
   canvas.put_image_data(image)

(Or whatever canvas draw calls).

This way there will only be one thread on the front-end. It will be slower than two threads, but hopefully gets around whatever google colab is doing to interfere with multi-threading.