Mercurial > code > home > repos > light9
changeset 2454:405abed9a45c default tip
fix up asyncio-in-bg-thread sorcery
author | drewp@bigasterisk.com |
---|---|
date | Mon, 19 May 2025 21:25:32 -0700 |
parents | b23afde50bc2 |
children | |
files | blender/__init__.py blender/asyncio_thread.py blender/time_sync/__init__.py blender/time_sync/time_from_graph.py src/light9/ascoltami/main.py |
diffstat | 5 files changed, 74 insertions(+), 13 deletions(-) [+] |
line wrap: on
line diff
--- a/blender/__init__.py Sun May 18 20:08:35 2025 -0700 +++ b/blender/__init__.py Mon May 19 21:25:32 2025 -0700 @@ -26,6 +26,7 @@ sys.path.append(p) for module in modules: + sys.stderr.write(f'🚋1 register {module}\n') module.register()
--- a/blender/asyncio_thread.py Sun May 18 20:08:35 2025 -0700 +++ b/blender/asyncio_thread.py Mon May 19 21:25:32 2025 -0700 @@ -1,16 +1,49 @@ import asyncio -from threading import Thread +import sys +from threading import Thread, get_ident +import time from typing import Coroutine +def log(msg): + try: + rl = hex(id(asyncio.get_running_loop())) + except RuntimeError: + rl = '(no loop ----)' + sys.stderr.write(f"thread={hex(get_ident())} loop={rl} {msg}\n") + + def startLoopInThread(task: Coroutine) -> asyncio.AbstractEventLoop: + """ + run a new event loop in a background thread. `task` is run in the new loop. + Caller should use this (from fg thread) to run further tasks: + asyncio.run_coroutine_threadsafe(task, returned_loop) - def start_background_loop(loop: asyncio.AbstractEventLoop) -> None: - asyncio.set_event_loop(loop) - loop.run_forever() + """ + log('🚋4 startLoopInThread enter ') + loops = [] + + def start_background_loop() -> None: + + async def forever(): + log('🚋6 log_loop') + loops.append(asyncio.get_running_loop()) + while True: + await asyncio.sleep(100) - loop = asyncio.new_event_loop() - t = Thread(target=start_background_loop, args=(loop,), daemon=True) + log('🚋5 make asyncio loop') + asyncio.run(forever()) + log('🚋19 start_background_loop done') + + t = Thread(target=start_background_loop, daemon=True) t.start() + while not loops: + time.sleep(.1) + loop = loops[0] + log('🚋7 loop has started in thread') + asyncio.run_coroutine_threadsafe(task, loop) + log('🚋8 started task') + + log('🚋9 startLoopInThread exit') return loop
--- a/blender/time_sync/__init__.py Sun May 18 20:08:35 2025 -0700 +++ b/blender/time_sync/__init__.py Mon May 19 21:25:32 2025 -0700 @@ -3,12 +3,15 @@ blender-side time changes are sent back to ascoltami """ +import sys + sync: object = None def register(): global sync from .time_from_graph import Sync + sys.stderr.write('🚋2 imported Sync\n') sync = Sync()
--- a/blender/time_sync/time_from_graph.py Sun May 18 20:08:35 2025 -0700 +++ b/blender/time_sync/time_from_graph.py Mon May 19 21:25:32 2025 -0700 @@ -1,13 +1,18 @@ +import asyncio import threading import time +from typing import Coroutine, cast import bpy # type: ignore from bpy.app.handlers import persistent # type: ignore -from light9_sync.asyncio_thread import startLoopInThread +from ..asyncio_thread import startLoopInThread from rdfdb.syncedgraph.syncedgraph import SyncedGraph +from rdflib import URIRef +from rdflib.graph import _ContextType -from light9 import networking +from light9 import networking, showconfig from light9.namespaces import L9 +from light9.newtypes import decimalLiteral from light9.run_local import log from light9.typedgraph import typedValue @@ -31,16 +36,26 @@ # main thread self.lastSetFrame = -1 self.lastGraphFrame = -1 - startLoopInThread(self.task()) + show = showconfig.showUri() + self.ctx = cast(_ContextType, (URIRef(show + '/ascoltami'))) + log.debug('🚋3 startLoopInThread') + self._loop = startLoopInThread(self.task()) + # need persistent because `blender --addons ...` seems to start addon # before loading scene. bpy.app.timers.register(self.update, persistent=True) bpy.app.handlers.frame_change_post.append(self.on_frame_change_post) + log.info('🚋10 Sync initd') ## updates from graph -> self + def runInBackgroundLoop(self, f: Coroutine): + asyncio.run_coroutine_threadsafe(f, self._loop) + + async def task(self): # bg thread with asyncio loop + log.info('🚋11 start SyncedGraph') self.graph = SyncedGraph(networking.rdfdb.url, "time_sync") self.graph.addHandler(self.syncFromGraph) @@ -63,7 +78,7 @@ ## graph time -> blender time - # @persistent + @persistent def update(self): # main thread? wherever blender runs timers if self.playing: @@ -90,7 +105,15 @@ # self.setGraphTime(scene.frame_current / scene.render.fps, self.duration) self.lastSetFrame = scene.frame_current t = scene.frame_current / scene.render.fps - self.setInGraph(t) + self.runInBackgroundLoop(self.setInGraph(t, bpy.context.screen.is_animation_playing)) - def setInGraph(self, t: float): - log.warning(f'todo: set graph to {t:.2f}') + async def setInGraph(self, t: float, isBlenderPlaying: bool): + log.info(f'set graph time to {t:.2f}') + if isBlenderPlaying: + log.info(' playing mode') + p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], decimalLiteral(t)) + else: + log.info(' paused mode') + p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], decimalLiteral(t)) + + await self.graph.patch(p)
--- a/src/light9/ascoltami/main.py Sun May 18 20:08:35 2025 -0700 +++ b/src/light9/ascoltami/main.py Mon May 19 21:25:32 2025 -0700 @@ -42,6 +42,7 @@ g.add((asc, L9['song'], self.playlist.songUri(s.song), self.ctx)) except KeyError: pass + # maybe share the rest of this with time_from_graph.py g.add((asc, L9['duration'], decimalLiteral(s.duration), self.ctx)) g.add((asc, L9['playing'], Literal(s.playing), self.ctx)) if s.wallStartTime is not None: