1212_TARGET_FPS = 60
1313
1414
15+ @dataclass
16+ class _DebounceData :
17+ t_last : float | None = None
18+ timer : threading .Timer | None = None
19+
20+
1521def debounce (arg = None ):
1622 def decorator (func ):
1723 if platform .is_pyodide :
1824 return arg
1925
2026 # Render only once every 1/_TARGET_FPS seconds
2127 @functools .wraps (func )
22- def debounced (* args , ** kwargs ):
23- if debounced .timer is not None :
28+ def debounced (obj , * args , ** kwargs ):
29+ if not hasattr (obj , "_debounce_data" ):
30+ obj ._debounce_data = {}
31+
32+ fname = func .__name__
33+ if obj ._debounce_data .get (fname , None ) is None :
34+ obj ._debounce_data [fname ] = _DebounceData (None , None )
35+
36+ data = obj ._debounce_data [fname ]
37+
38+ if data .timer is not None :
2439 # we already have a render scheduled, so do nothing
2540 return
2641
2742 def f ():
2843 # clear the timer, so we can schedule a new one with the next function call
2944 t = time .time ()
30- func (* args , ** kwargs )
31- debounced .timer = None
32- debounced .t_last = t
45+ func (obj , * args , ** kwargs )
46+ data .timer = None
47+ data .t_last = t
3348
34- if debounced .t_last is None :
49+ if data .t_last is None :
3550 # first call -> just call the function immediately
36- debounced .t_last = time .time ()
51+ data .t_last = time .time ()
3752 f ()
3853 return
3954
40- t_wait = max (1 / target_fps - (time .time () - debounced .t_last ), 0 )
41- debounced .timer = threading .Timer (t_wait , f )
42- debounced .timer .start ()
55+ t_wait = max (1 / target_fps - (time .time () - data .t_last ), 0 )
56+ data .timer = threading .Timer (t_wait , f )
57+ data .timer .start ()
4358
44- debounced .timer = None
45- debounced .t_last = None
46- debounced ._original = func
4759 return debounced
4860
4961 if callable (arg ):
@@ -77,7 +89,7 @@ class Canvas:
7789 _on_update_html_canvas : list [Callable ]
7890
7991 def __init__ (self , device , canvas , multisample_count = 4 ):
80- self ._update_mutex = threading .Lock ()
92+ self ._update_mutex = threading .RLock ()
8193 self .target_texture = None
8294
8395 self ._on_resize_callbacks = []
@@ -168,11 +180,11 @@ def on_intersection(observer_entry, args):
168180 self ._resize_observer .observe (self .canvas )
169181 self ._intersection_observer .observe (self .canvas )
170182
171- for func in self ._on_update_html_canvas :
172- func (html_canvas )
183+ for func in self ._on_update_html_canvas :
184+ func (html_canvas )
173185
174- self .width = self .height = 0 # force resize
175- self .resize . _original ( self )
186+ self .width = self .height = 0 # force resize
187+ self .resize ( )
176188
177189 def on_resize (self , func : Callable ):
178190 self ._on_resize_callbacks .append (func )
@@ -181,9 +193,6 @@ def on_update_html_canvas(self, func: Callable):
181193 self ._on_update_html_canvas .append (func )
182194
183195 def save_screenshot (self , filename : str ):
184- if self .target_texture is None :
185- self .resize ._original (self )
186-
187196 with self ._update_mutex :
188197 path = pathlib .Path (filename )
189198 format = path .suffix [1 :]
0 commit comments