Mercurial > code > home > repos > light9
view blender/time_sync/time_from_graph.py @ 2458:0e27ba33118c default tip
better blender<->asco playback cooperation. still no play support in blender; only seek
author | drewp@bigasterisk.com |
---|---|
date | Tue, 20 May 2025 16:25:06 -0700 |
parents | d94480bfb179 |
children |
line wrap: on
line source
import asyncio import threading import time from typing import Coroutine from rdfdb.patch import Patch from rdfdb.syncedgraph.syncedgraph import SyncedGraph from rdflib import Literal from light9 import networking, showconfig from light9.ascoltami.graph_context import ascoltamiContext from light9.ascoltami.play_state import AscoPlayState from light9.namespaces import L9 from light9.newtypes import decimalLiteral from light9.run_local import log from light9.typedgraph import typedValue from ..asyncio_thread import startLoopInThread from .blender_time import ( BlenderTime, PausedGotoTime, PlayingGotoTime, SceneLoaded, _TimeEvent, ) class Sync: """asco is the authority on playback status. Sync maintains a copy of the state""" lock = threading.Lock() # this is edited ONLY by bg thread ascoPlayState: AscoPlayState def __init__(self): # main thread # one mutable instance; modified by bg thread self.ascoPlayState = AscoPlayState(None, None, False, False, 1.0) self.blenderTime = BlenderTime(self.onBlenderEvent, self.ascoPlayState) self.blenderTime.start() self.ctx = ascoltamiContext(showconfig.showUri()) log.debug('🚋3 startLoopInThread') self._loop = startLoopInThread(self.connectGraph()) log.info('🚋10 Sync initd') def onBlenderEvent(self, event: _TimeEvent): # main thread match event: case SceneLoaded(): self.blenderTime.setRange(self.ascoPlayState.duration) self.blenderTime.setCurrentTime(self.ascoPlayState.getCurrentSongTime() or 0.0) case PausedGotoTime(t): self.runInBackgroundLoop(self.setInGraph(t, False)) case PlayingGotoTime(t): self.runInBackgroundLoop(self.setInGraph(t, True)) def runInBackgroundLoop(self, f: Coroutine): # main thread asyncio.run_coroutine_threadsafe(f, self._loop) async def connectGraph(self): # bg thread log.info('🚋11 start SyncedGraph') self.graph = SyncedGraph(networking.rdfdb.url, "time_sync") self.graph.addHandler(self.syncFromGraph) def syncFromGraph(self): # bg thread with self.lock: asco = L9['ascoltami'] self.ascoPlayState.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime']) self.ascoPlayState.pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime']) self.ascoPlayState.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0 self.blenderTime.durationDirty = True # todo: called too often self.ascoPlayState.playing = typedValue(bool | None, self.graph, asco, L9['playing']) or False self.ascoPlayState.endOfSong = typedValue(bool | None, self.graph, asco, L9['endOfSong']) or False log.info(f'🍇 syncFromGraph {self.ascoPlayState=}') self.blenderTime.curFrameDirty = True async def setInGraph(self, t: float, isBlenderPlaying: bool): # bg thread log.debug(f'set graph time to {t:.2f} {isBlenderPlaying=}') p = Patch() if isBlenderPlaying: p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['playing'], Literal(True))) p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], decimalLiteral(round(time.time() - t, 1)))) p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], None)) else: p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['playing'], Literal(False))) p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], None)) p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], decimalLiteral(round(t, 1)))) if p.isEmpty(): return await self.graph.patch(p)