jupylet
jupylet copied to clipboard
how to run an App() in a separate thread?
Hi,
I'd like to run an (headless) App in a separate thread.
I'd like to render some shaders, and use the framebuffer into another part of my application.
I tried this simple code:
#!/usr/bin/python3
import time
from jupylet.app import App
if __name__ == '__main__':
def create_and_run_app():
app = App(width=512, height=512, quality=100, fullscreen=False, mode="hidden") #, log_level=logging.INFO)
app.start()
import _thread
_thread.start_new_thread(create_and_run_app, ())
time.sleep(10)
but I get this error:
Exception ignored in thread started by: <function create_and_run_app at 0x7f807b0060d0>
Traceback (most recent call last):
File "p.py", line 9, in create_and_run_app
app = App(width=512, height=512, quality=100, fullscreen=False, mode="hidden") #, log_level=logging.INFO)
File "/home/eric/tmp/jupylet/jupylet/app.py", line 184, in __init__
self.window = window_cls(size=(width, height), **conf)
File "/home/eric/tmp/jupylet/jupylet/event.py", line 81, in __init__
self.init_mgl_context()
File "/home/eric/tmp/jupylet/jupylet/event.py", line 103, in init_mgl_context
self._ctx = moderngl.create_standalone_context(
File "/home/eric/.local/lib/python3.9/site-packages/moderngl/context.py", line 1672, in create_standalone_context
raise ValueError('Requested OpenGL version {}, got version {}'.format(
ValueError: Requested OpenGL version 330, got version 0
I also tried to use mode="auto"
and app.run()
, but I get:
Exception ignored in thread started by: <function create_and_run_app at 0x7f34e3fc30d0>
Traceback (most recent call last):
File "p.py", line 10, in create_and_run_app
app.run()
File "/home/eric/tmp/jupylet/jupylet/app.py", line 333, in run
loop = asyncio.get_event_loop()
File "/usr/lib/python3.9/asyncio/events.py", line 642, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Dummy-1'.
Is there a way to call a render iteration x times per second, or in a separate thread?
Thank you
Hi, under the hood Jupylet and the App object employ Python's async programming. It is a programming paradigm that is not entirely compatible with threading.
You may run code and functionality in Python threads, but the App object itself needs an async event loop and I believe this should be done in the main program thread.
The reason for this design choice is that Jupyter notebooks are asynchronous and Jupylet was designed to fit in.
See here for more info:
https://docs.python.org/3/library/asyncio.html https://realpython.com/async-io-python/
Thanks for reporting!
Hi, I've read the documentation you provided, thank you.
However, I still cannot run the app
in a separate thread.
I tried to call asyncio.run_coroutine_threadsafe(app.run(), loop)
, but the render()
function seems to be called only once.
Do I need to start the app somehow?
Here are the logs:
2021-06-23 18:28:02,888 - jupylet.event - INFO - Enter EventLeg.event(*args=(<function create_app.<locals>.key_event at 0x7f94687f70d0>,)).
2021-06-23 18:28:02,889 - jupylet.clock - INFO - Enter ClockLeg.schedule_interval(interval=0.041666666666666664, **kwargs={}).
2021-06-23 18:28:02,889 - jupylet.clock - INFO - Enter Scheduler.schedule_interval(foo=<function create_app.<locals>.modify_volume at 0x7f94687f71f0>, interval=0.041666666666666664, **kwargs={}).
2021-06-23 18:28:02,975 - jupylet.event - INFO - Enter EventLeg.event(*args=(<function create_app.<locals>.render at 0x7f94687f7310>,)).
Thanks
It seems it should be possible to start an event loop in another thread. If you can do that, the app object should run in that "side" thread:
https://docs.python.org/3/library/asyncio-dev.html#concurrency-and-multithreading
Thanks, I tried use asyncio
in a thread, I can see the window but it stays black
import time
from jupylet.app import App
from threading import Thread
import logging
import asyncio
import sys
if __name__ == '__main__':
def new_app():
from jupylet.shadertoy import Shadertoy
app = App(width=512, height=512, quality=100, mode="auto", log_level=logging.INFO)
st = Shadertoy("""
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
fragColor = vec4(col,1.0);
}
""")
@app.event
def render(ct, dt):
app.window.clear()
st.draw(ct, dt)
return app
# in main thread, this works:
# app = new_app()
# app.run()
# this doesn't work: the window is there but stays black
def start_loop(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
print("exiting thread")
app = new_app()
app_loop = asyncio.new_event_loop()
t = Thread(target=start_loop, args=(app_loop,), daemon=True)
t.start()
app_loop.call_soon(app.run)
time.sleep(3)
sys.exit(0)
Just a small update, I have successfully run the app like this:
from jupylet.clock import setup_fake_time
app = new_app()
app.mode = "hidden"
app.fake_time=setup_fake_time()
app.start()
while True:
time.sleep(0.1)
app.step()
It is a bit hackish but it works and I can see he result on screen.
I could also do that in hidden
mode:
app = new_app()
app.start()
while True:
time.sleep(0.1)
app.step()
print(app.observe())
unfortunately, I'd like to use another (Panda3D) OpenGL app at the same time, and I don't think it's possible
But at least this issue is solved now.
Thank you for your support.