Mercurial > code > home > repos > light9
annotate blender/time_sync/time_from_graph.py @ 2454:405abed9a45c
fix up asyncio-in-bg-thread sorcery
author | drewp@bigasterisk.com |
---|---|
date | Mon, 19 May 2025 21:25:32 -0700 |
parents | b23afde50bc2 |
children | 2d454737a916 |
rev | line source |
---|---|
2454 | 1 import asyncio |
2432 | 2 import threading |
3 import time | |
2454 | 4 from typing import Coroutine, cast |
2432 | 5 |
6 import bpy # type: ignore | |
7 from bpy.app.handlers import persistent # type: ignore | |
2454 | 8 from ..asyncio_thread import startLoopInThread |
2432 | 9 from rdfdb.syncedgraph.syncedgraph import SyncedGraph |
2454 | 10 from rdflib import URIRef |
11 from rdflib.graph import _ContextType | |
2432 | 12 |
2454 | 13 from light9 import networking, showconfig |
2432 | 14 from light9.namespaces import L9 |
2454 | 15 from light9.newtypes import decimalLiteral |
2432 | 16 from light9.run_local import log |
2453
b23afde50bc2
blender addons get thier own pdm setup for now. fix time_from_graph startup race
drewp@bigasterisk.com
parents:
2436
diff
changeset
|
17 from light9.typedgraph import typedValue |
2432 | 18 |
19 | |
20 def clamp(lo, hi, x): | |
21 return max(lo, min(hi, x)) | |
22 | |
23 | |
24 UPDATE_PERIOD = 1 / 20 | |
25 | |
26 | |
27 class Sync: | |
28 lock = threading.Lock() | |
29 duration: float = 1 | |
30 wallStartTime: float | None = None | |
31 pausedSongTime: float | None = None | |
2433 | 32 playing = False |
2432 | 33 latestTime: float |
34 | |
35 def __init__(self): | |
36 # main thread | |
37 self.lastSetFrame = -1 | |
38 self.lastGraphFrame = -1 | |
2454 | 39 show = showconfig.showUri() |
40 self.ctx = cast(_ContextType, (URIRef(show + '/ascoltami'))) | |
41 log.debug('🚋3 startLoopInThread') | |
42 self._loop = startLoopInThread(self.task()) | |
43 | |
2453
b23afde50bc2
blender addons get thier own pdm setup for now. fix time_from_graph startup race
drewp@bigasterisk.com
parents:
2436
diff
changeset
|
44 # need persistent because `blender --addons ...` seems to start addon |
b23afde50bc2
blender addons get thier own pdm setup for now. fix time_from_graph startup race
drewp@bigasterisk.com
parents:
2436
diff
changeset
|
45 # before loading scene. |
b23afde50bc2
blender addons get thier own pdm setup for now. fix time_from_graph startup race
drewp@bigasterisk.com
parents:
2436
diff
changeset
|
46 bpy.app.timers.register(self.update, persistent=True) |
2432 | 47 bpy.app.handlers.frame_change_post.append(self.on_frame_change_post) |
2454 | 48 log.info('🚋10 Sync initd') |
2432 | 49 |
2433 | 50 ## updates from graph -> self |
2432 | 51 |
2454 | 52 def runInBackgroundLoop(self, f: Coroutine): |
53 asyncio.run_coroutine_threadsafe(f, self._loop) | |
54 | |
55 | |
2432 | 56 async def task(self): |
57 # bg thread with asyncio loop | |
2454 | 58 log.info('🚋11 start SyncedGraph') |
2432 | 59 self.graph = SyncedGraph(networking.rdfdb.url, "time_sync") |
60 self.graph.addHandler(self.syncFromGraph) | |
61 | |
62 def syncFromGraph(self): | |
63 # bg thread | |
64 with self.lock: | |
65 asco = L9['ascoltami'] | |
2433 | 66 self.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime']) |
67 self.pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime']) | |
68 self.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0 | |
2436
e683b449506b
blender effect that sets lights to match blender lights
drewp@bigasterisk.com
parents:
2434
diff
changeset
|
69 self.playing = typedValue(bool | None, self.graph, asco, L9['playing']) or False |
2432 | 70 |
71 def currentTime(self) -> float | None: | |
72 if self.wallStartTime is not None: | |
73 return time.time() - self.wallStartTime | |
74 if self.pausedSongTime is not None: | |
75 return self.pausedSongTime | |
76 log.warn('no time data') | |
77 return None | |
78 | |
79 ## graph time -> blender time | |
80 | |
2454 | 81 @persistent |
2432 | 82 def update(self): |
83 # main thread? wherever blender runs timers | |
84 if self.playing: | |
85 with self.lock: | |
86 t = self.currentTime() | |
87 if t is not None: | |
88 self.setBlenderTime(t, self.duration) | |
89 return UPDATE_PERIOD | |
90 | |
91 def setBlenderTime(self, t: float, duration: float): | |
92 scene = bpy.context.scene | |
93 fps = scene.render.fps | |
94 scene.frame_start = 0 | |
95 scene.frame_end = int(duration * fps) | |
96 fr = int(clamp(t, 0, duration) * fps) | |
97 self.lastSetFrame = fr | |
98 scene.frame_set(fr) | |
99 | |
100 ## blender time -> graph time | |
101 | |
102 @persistent | |
103 def on_frame_change_post(self, scene, deps): | |
104 if scene.frame_current != self.lastSetFrame: | |
105 # self.setGraphTime(scene.frame_current / scene.render.fps, self.duration) | |
106 self.lastSetFrame = scene.frame_current | |
107 t = scene.frame_current / scene.render.fps | |
2454 | 108 self.runInBackgroundLoop(self.setInGraph(t, bpy.context.screen.is_animation_playing)) |
2432 | 109 |
2454 | 110 async def setInGraph(self, t: float, isBlenderPlaying: bool): |
111 log.info(f'set graph time to {t:.2f}') | |
112 if isBlenderPlaying: | |
113 log.info(' playing mode') | |
114 p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], decimalLiteral(t)) | |
115 else: | |
116 log.info(' paused mode') | |
117 p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], decimalLiteral(t)) | |
118 | |
119 await self.graph.patch(p) |