webgpu 1.2.1.dev0__py3-none-any.whl → 1.2.2.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
webgpu/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.2.1.dev0'
22
- __version_tuple__ = version_tuple = (1, 2, 1, 'dev0')
21
+ __version__ = version = '1.2.2.dev0'
22
+ __version_tuple__ = version_tuple = (1, 2, 2, 'dev0')
23
23
 
24
- __commit_id__ = commit_id = 'g7d909a7c5'
24
+ __commit_id__ = commit_id = 'gb9e08e45e'
webgpu/canvas.py CHANGED
@@ -147,10 +147,12 @@ class Canvas:
147
147
  self.update_html_canvas(canvas)
148
148
 
149
149
  def __del__(self):
150
- if self._resize_observer is not None:
151
- self._resize_observer.disconnect()
152
- if self._intersection_observer is not None:
153
- self._intersection_observer.disconnect()
150
+ disconnect = getattr(self._resize_observer, "disconnect", None)
151
+ if callable(disconnect):
152
+ disconnect()
153
+ disconnect = getattr(self._intersection_observer, "disconnect", None)
154
+ if callable(disconnect):
155
+ disconnect()
154
156
 
155
157
  def update_html_canvas(self, html_canvas):
156
158
  """Reconfigure the canvas with the current HTML canvas element. This is necessary when the HTML canvas element changes, disappears (e.g. when switching a tab) and appears again."""
webgpu/link/base.py CHANGED
@@ -475,13 +475,13 @@ class LinkBaseAsync(LinkBase):
475
475
  _callback_loop: asyncio.AbstractEventLoop
476
476
  _callback_queue: asyncio.Queue
477
477
  _callback_thread: threading.Thread
478
+ _callback_task: asyncio.Task | None
478
479
 
479
480
  def __init__(self):
480
481
  super().__init__()
481
482
  self._send_loop = asyncio.new_event_loop()
482
483
  self._callback_loop = asyncio.new_event_loop()
483
- self._callback_queue = asyncio.Queue()
484
-
484
+ self._callback_task = None
485
485
  self._callback_thread = threading.Thread(target=self._start_callback_thread, daemon=True)
486
486
  self._callback_thread.start()
487
487
 
@@ -516,7 +516,15 @@ class LinkBaseAsync(LinkBase):
516
516
  event = threading.Event()
517
517
  self._requests[request_id] = event, key
518
518
 
519
- asyncio.run_coroutine_threadsafe(self._send_async(data), self._send_loop)
519
+ coro = self._send_async(data)
520
+ try:
521
+ asyncio.run_coroutine_threadsafe(coro, self._send_loop)
522
+ except RuntimeError:
523
+ coro.close()
524
+ # Event loop is closed — connection is dead, clean up and bail.
525
+ if event:
526
+ self._requests.pop(request_id, None)
527
+ return None
520
528
  if event:
521
529
  event.wait()
522
530
  return self._requests.pop(request_id)
@@ -530,16 +538,28 @@ class LinkBaseAsync(LinkBase):
530
538
  try:
531
539
  func, args = await self._callback_queue.get()
532
540
  func(*args)
541
+ except asyncio.CancelledError:
542
+ break
543
+ except RuntimeError:
544
+ break
533
545
  except asyncio.QueueEmpty:
534
546
  pass
535
547
  except Exception as e:
536
548
  print("error in callback", type(e), str(e))
537
- # await asyncio.sleep(0.01)
538
549
 
539
550
  try:
540
551
  self._callback_loop = asyncio.new_event_loop()
541
552
  asyncio.set_event_loop(self._callback_loop)
542
- self._callback_loop.create_task(handle_callbacks())
543
- self._callback_loop.run_forever()
553
+ self._callback_queue = asyncio.Queue()
554
+ self._callback_task = self._callback_loop.create_task(handle_callbacks())
555
+ try:
556
+ self._callback_loop.run_forever()
557
+ finally:
558
+ if self._callback_task is not None and not self._callback_task.done():
559
+ self._callback_task.cancel()
560
+ self._callback_loop.run_until_complete(
561
+ asyncio.gather(self._callback_task, return_exceptions=True)
562
+ )
563
+ self._callback_loop.close()
544
564
  except Exception as e:
545
565
  print("exception in _start_callback_thread", e)
webgpu/link/websocket.py CHANGED
@@ -22,7 +22,7 @@ class WebsocketLinkBase(LinkBaseAsync):
22
22
  self._start_handling_messages = threading.Event()
23
23
  self._send_loop = asyncio.new_event_loop()
24
24
 
25
- self._websocket_thread = threading.Thread(target=self._connect)
25
+ self._websocket_thread = threading.Thread(target=self._connect, daemon=True)
26
26
  self._websocket_thread.start()
27
27
 
28
28
  def wait_for_server_running(self):
@@ -89,7 +89,7 @@ class WebsocketLinkServer(WebsocketLinkBase):
89
89
  self._connection = websocket
90
90
  self._event_is_connected.set()
91
91
  async for message in websocket:
92
- thread = threading.Thread(target=self._on_message, args=(message,))
92
+ thread = threading.Thread(target=self._on_message, args=(message,), daemon=True)
93
93
  thread.start()
94
94
  finally:
95
95
  self._connection = None
@@ -118,7 +118,41 @@ class WebsocketLinkServer(WebsocketLinkBase):
118
118
  self._send_loop.run_until_complete(start_websocket())
119
119
  except Exception as e:
120
120
  print("exception in _start_websocket_server", e)
121
- print("stopped websocket")
121
+ finally:
122
+ pending = [
123
+ task for task in asyncio.all_tasks(self._send_loop)
124
+ if not task.done()
125
+ ]
126
+ for task in pending:
127
+ task.cancel()
128
+ if pending:
129
+ self._send_loop.run_until_complete(
130
+ asyncio.gather(*pending, return_exceptions=True)
131
+ )
132
+ self._send_loop.run_until_complete(self._send_loop.shutdown_asyncgens())
133
+ self._send_loop.run_until_complete(self._send_loop.shutdown_default_executor())
134
+ self._send_loop.close()
122
135
 
123
136
  def stop(self):
124
- self._send_loop.call_soon_threadsafe(self._stop.set_result, None)
137
+ try:
138
+ if not self._stop.done():
139
+ self._send_loop.call_soon_threadsafe(self._stop.set_result, None)
140
+ except RuntimeError:
141
+ pass # Event loop already closed
142
+ if threading.current_thread() is not self._websocket_thread:
143
+ self._websocket_thread.join(timeout=2)
144
+
145
+ # Stop the callback event loop so the _callback_thread exits.
146
+ try:
147
+ self._callback_loop.call_soon_threadsafe(self._callback_loop.stop)
148
+ except RuntimeError:
149
+ pass # Event loop already closed
150
+ if threading.current_thread() is not self._callback_thread:
151
+ self._callback_thread.join(timeout=1)
152
+
153
+ # Unblock any threads stuck waiting for websocket RPC responses.
154
+ for rid, val in list(self._requests.items()):
155
+ if isinstance(val, tuple):
156
+ event, key = val
157
+ if isinstance(event, threading.Event):
158
+ event.set()
webgpu/platform.py CHANGED
@@ -206,3 +206,32 @@ def init_pyodide(link_):
206
206
 
207
207
  LinkBase.register_serializer(BaseWebGPUHandle, lambda _, v: v.handle)
208
208
  LinkBase.register_serializer(BaseWebGPUObject, lambda _, v: v.__dict__ or None)
209
+
210
+
211
+ def reset():
212
+ """Reset the platform globals so that init can be called again.
213
+
214
+ Used by test runners that start and stop multiple app instances
215
+ in the same process.
216
+ """
217
+ global js, websocket_server, link, create_proxy, destroy_proxy
218
+ if websocket_server is not None:
219
+ try:
220
+ websocket_server.stop()
221
+ except RuntimeError:
222
+ pass # Event loop already closed
223
+ js = None
224
+ websocket_server = None
225
+ link = None
226
+ if not is_pyodide:
227
+ create_proxy = None
228
+ destroy_proxy = None
229
+
230
+ # Reset cached WebGPU device so it is re-requested on the new connection.
231
+ from . import utils as _utils
232
+ _utils._device = None
233
+
234
+ # Reset the cached font atlas — its GPU texture is tied to the old
235
+ # connection and would deadlock when accessed on a new one.
236
+ from . import font as _font
237
+ _font._default_font_atlas = None
webgpu/scene.py CHANGED
@@ -97,7 +97,13 @@ class Scene:
97
97
  self.options.timestamp = time.time()
98
98
  self.options.update_buffers()
99
99
  for obj in self.render_objects:
100
- obj._update_and_create_render_pipeline(self.options)
100
+ if not obj.active:
101
+ continue
102
+ try:
103
+ obj._update_and_create_render_pipeline(self.options)
104
+ except Exception as e:
105
+ print(f'Warning: failed to init renderer {type(obj).__name__}: {e}')
106
+ obj.active = False
101
107
 
102
108
  camera = self.options.camera
103
109
  self._js_render = platform.create_proxy(self._render_direct)
@@ -308,8 +314,11 @@ class Scene:
308
314
  self.options.camera._render_function = None
309
315
  self.options.camera._get_position_function = None
310
316
  self.input_handler.unregister_callbacks()
311
- platform.destroy_proxy(self._js_render)
312
- del self._js_render
313
- self.canvas._on_resize_callbacks.remove(self.render)
314
- self.canvas._on_update_html_canvas.remove(self.__on_update_html_canvas)
317
+ if hasattr(self, '_js_render'):
318
+ platform.destroy_proxy(self._js_render)
319
+ del self._js_render
320
+ if self.render in self.canvas._on_resize_callbacks:
321
+ self.canvas._on_resize_callbacks.remove(self.render)
322
+ if self.__on_update_html_canvas in self.canvas._on_update_html_canvas:
323
+ self.canvas._on_update_html_canvas.remove(self.__on_update_html_canvas)
315
324
  self.canvas = None
webgpu/vectors.py CHANGED
@@ -31,13 +31,8 @@ class BaseVectorRenderer(Renderer):
31
31
  super().__init__(label=label)
32
32
  self.gpu_objects.colormap = Colormap()
33
33
 
34
- def update(self, timestamp):
35
- if timestamp == self._timestamp:
36
- return
37
- self._timestamp = timestamp
38
-
39
- self.gpu_objects.colormap.options = self.options
40
- self.gpu_objects.colormap.update(timestamp)
34
+ def update(self, options):
35
+ pass
41
36
 
42
37
  def get_bindings(self):
43
38
  return [
@@ -68,9 +63,7 @@ class VectorRenderer(BaseVectorRenderer):
68
63
  )
69
64
  self.size = size or 1 / 10 * np.linalg.norm(self.bounding_box[1] - self.bounding_box[0])
70
65
 
71
- def update(self, timestamp):
72
- super().update(timestamp)
73
-
66
+ def update(self, options):
74
67
  self._buffers = {
75
68
  "points": buffer_from_array(self.points),
76
69
  "vectors": buffer_from_array(self.vectors),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webgpu
3
- Version: 1.2.1.dev0
3
+ Version: 1.2.2.dev0
4
4
  Summary: A Python based WebGPU API using pyodide
5
5
  Author-email: Matthias Hochsteger <mhochsteger@cerbsim.com>, Christopher Lackner <clackner@cerbsim.com>
6
6
  License-Expression: LGPL-2.1-or-later
@@ -1,7 +1,7 @@
1
1
  webgpu/__init__.py,sha256=h0nQ2lBaCOWz6UpveZuFnJophgAKbVFUsL2QO1_uoTw,302
2
- webgpu/_version.py,sha256=wn-w5PgTjiNi-tEtjZzaAv8vvCbjnxBePaSba7V9LaY,541
2
+ webgpu/_version.py,sha256=nSwG2ivutkcNO6WO5b1Wwu5l2YnLq5Jz8hN6lYWD6v0,541
3
3
  webgpu/camera.py,sha256=jt4SNNtmZhy4PfbTlGvY30eEwy3wkckjmiTA0s8QrtE,11125
4
- webgpu/canvas.py,sha256=PlrJQQtv32YPBakkZnOvMYcJ_HYi_4iW4ZPIm9eYXnA,12165
4
+ webgpu/canvas.py,sha256=GRQ96ENrFDyiTQQcX4_1Q-yeHWdn8hsocMbIeYOXd5g,12233
5
5
  webgpu/clipping.py,sha256=RituZ-WJdUX7IubAhGORfQ5NM56QnsMau3G9ld2F6fI,4066
6
6
  webgpu/colormap.py,sha256=FwpEZE1MU-ws8TndYpvFl_VaBR1U_bw6pbsYxaueSZM,13169
7
7
  webgpu/draw.py,sha256=0JRBRf0HcA6O8yTGYXCnI8TvnhVMjn4VZ8mFQp9xO4M,555
@@ -13,21 +13,21 @@ webgpu/jupyter_pyodide.py,sha256=e7u8nvUwfHF6ME07RAiO8L-oto7owyBzwsa2RBJIh0I,969
13
13
  webgpu/labels.py,sha256=OSYvBvjjXRIX4fPOcAtDt6Xk6GdI1ghekOLIIYxNIhg,4209
14
14
  webgpu/light.py,sha256=sideBOi2vSWj1OJUvhCTG47N8S06GhkJAUBnfGGgaqA,2798
15
15
  webgpu/lilgui.py,sha256=xLOrt3RZEMNXO4OgSnBcZZXelLRYdg3laKIMpqBHyIM,1979
16
- webgpu/platform.py,sha256=7QsdlTzWDZrSpAXmjux_zRoKWjk2l7gOjXnlna8ZogA,6043
16
+ webgpu/platform.py,sha256=OaDxjAJaHEHZcd1DUYMxZDP2a72gnYZpQncym_RVLvI,6940
17
17
  webgpu/renderer.py,sha256=6fwj9VpR22FrV2Ua3F5Etdt1zli8MeXHknFQfBUTdMA,12482
18
- webgpu/scene.py,sha256=J3jHX9grPGTJpi64nLASZv-DcGIq-OwMtCo_-_QUUqY,11695
18
+ webgpu/scene.py,sha256=Si_XpQiuuDv3h6bss75kAZnRNN30hgtEzyDkEvxgdd4,12170
19
19
  webgpu/shapes.py,sha256=Yf-ca0iZm_cAb0l4T95ZM1JCB1CXoqkzFT8mQPxA0vM,15716
20
20
  webgpu/testing.py,sha256=z25Q_mO-HzdedFDmd2JXHemK3Hl_shQQ42IBTRLKTR0,13014
21
21
  webgpu/triangles.py,sha256=EVYU6kjz56LJCVEXeOE6ZEaa4oZ9RPeYdf_-hXMneoY,1865
22
22
  webgpu/uniforms.py,sha256=5wm06tjZsEAtIxkdCe-5W0hQl3yFc6w4V-X_2SQIa1A,3031
23
23
  webgpu/utils.py,sha256=pV9j8Os-XV79TPcKri_WQqUnv9ST_Dim__JoHzBKS6o,20038
24
- webgpu/vectors.py,sha256=SEXGvdOPQHXbYuMWvpjH_wJqMEgd3tEu7l9ZX5X4OGM,2970
24
+ webgpu/vectors.py,sha256=tjPijM9npqhz_Mu-2qzI_61ZQPnBC7nf8-t-Z9c2o_M,2738
25
25
  webgpu/webgpu_api.py,sha256=EgudES5MxMWMZHKovF9-bnfNEWyCQPTQkXNiXFh6l9s,51491
26
26
  webgpu/link/__init__.py,sha256=3i6KrP6iOJFJSm7_VUTdLV91A0h8qXNtuUiaEsxoS3I,107
27
- webgpu/link/base.py,sha256=H5VUGkgNZFb4SZCBX2_r8HJEZREeALiVkKAG1qjaWGI,17874
27
+ webgpu/link/base.py,sha256=n_zd8rNvXKp_UY64S0X-haXKAS4jseVomBHBv-OE1fY,18722
28
28
  webgpu/link/link.js,sha256=kN4csmhMvinnVwskITgOceQcvPpOVoqg6jEQD0hdD3A,19225
29
29
  webgpu/link/proxy.py,sha256=FdnKKpudTJGjzchPuOkiDrx4wj5HosrUPGfZaw7rgOU,2519
30
- webgpu/link/websocket.py,sha256=Rr-QBSGJMt9jvW7jp4KbUWNjDtdcq201yR7u8Bhdrn4,3852
30
+ webgpu/link/websocket.py,sha256=DfSfGSSDwTFGSbEAc9yTVRDOvkYwRZ8UIs_oGo7pY30,5337
31
31
  webgpu/shaders/camera.wgsl,sha256=zryLSCd_rF04oC5WeNbNgETCzUM-40O6HAT_ksc9e2s,704
32
32
  webgpu/shaders/clipping.wgsl,sha256=XiSITaxuJtNHUrJ-Z8qpXgtFS8K4u-NXiobhm0V5xkQ,1049
33
33
  webgpu/shaders/colormap.wgsl,sha256=l6VEGD3ap-cB3168nENCNoypa0bMfPD8AnSXVstC_r0,2195
@@ -37,8 +37,8 @@ webgpu/shaders/shapes.wgsl,sha256=g6ZzqSP_ngfh575ed6s-D3BtGbdC53a0f7DZV90PC0k,29
37
37
  webgpu/shaders/text.wgsl,sha256=25jld-LVAGIJbYiPIiSWWrzL_l2zWUHpu9a0C7_JvaA,2204
38
38
  webgpu/shaders/triangulation.wgsl,sha256=EFMDTdvI7PJ1Fgrg4LQi5bUYRp-TLeZYhUDpkMDj6OA,1340
39
39
  webgpu/shaders/vector.wgsl,sha256=SJL_d8fky_lzFOL-YyJoAqad5mbuBHEPnBW_0fRBLzo,3862
40
- webgpu-1.2.1.dev0.dist-info/licenses/LICENSE,sha256=oZDcnIBDdV2Q-LCnX6ZrnkLUr0yYC_XdxjPwEk2zzuc,26430
41
- webgpu-1.2.1.dev0.dist-info/METADATA,sha256=Z4eSyfmNkvyejUBsq0iYIIaqEVHTDiJTCOL5hRZ_F6A,3477
42
- webgpu-1.2.1.dev0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
- webgpu-1.2.1.dev0.dist-info/top_level.txt,sha256=x6CotXenAnd3P6fHgyUW3ezc05V65HLlYqqMHbA7kYQ,7
44
- webgpu-1.2.1.dev0.dist-info/RECORD,,
40
+ webgpu-1.2.2.dev0.dist-info/licenses/LICENSE,sha256=oZDcnIBDdV2Q-LCnX6ZrnkLUr0yYC_XdxjPwEk2zzuc,26430
41
+ webgpu-1.2.2.dev0.dist-info/METADATA,sha256=LRYVYTjQNOhnwcNnlSbgtdAOZV_F4DcflH1Fa2XeD0k,3477
42
+ webgpu-1.2.2.dev0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
43
+ webgpu-1.2.2.dev0.dist-info/top_level.txt,sha256=x6CotXenAnd3P6fHgyUW3ezc05V65HLlYqqMHbA7kYQ,7
44
+ webgpu-1.2.2.dev0.dist-info/RECORD,,