Mercurial > code > home > repos > light9
view blender/time_sync/time_from_graph.py @ 2457:d94480bfb179
more work on blender time sync. Might be working, aside from blender play-button
author | drewp@bigasterisk.com |
---|---|
date | Tue, 20 May 2025 13:48:07 -0700 |
parents | 2d454737a916 |
children | 0e27ba33118c |
line wrap: on
line source
import asyncio import threading import time from typing import Coroutine from attr import dataclass 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 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 setGraphPlaying(self, isBlenderPlaying: bool): return # bg thread log.info(f'set graph playing to {isBlenderPlaying}') self.graph.patchObject(self.ctx, L9['ascoltami'], L9['playing'], Literal(isBlenderPlaying)) async def setInGraph(self, t: float, isBlenderPlaying: bool): # bg thread log.info(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 log.info(f'setInGraph {p.shortSummary()}') await self.graph.patch(p)