Working websocket updates including cleanup

This commit is contained in:
Yeicor
2024-02-07 21:13:14 +01:00
parent 3fbf6ea497
commit 792ab687f6
3 changed files with 56 additions and 26 deletions

View File

@@ -2,6 +2,7 @@ import os
import time import time
from OCP.TopoDS import TopoDS_Shape from OCP.TopoDS import TopoDS_Shape
from aiohttp import web
from server import Server from server import Server
@@ -14,16 +15,20 @@ if 'YACV_DISABLE_SERVER' not in os.environ:
server.start() server.start()
def get_app() -> web.Application:
"""Required by aiohttp-devtools"""
from logo.logo import build_logo
server.show_object(build_logo())
return server.app
def show_object(obj: TopoDS_Shape): def show_object(obj: TopoDS_Shape):
"""Show a CAD object in the default server""" """Show a CAD object in the default server"""
server.show_object(obj) server.show_object(obj)
if __name__ == '__main__': if __name__ == '__main__':
# Publish the logo to the server # Publish the logo to the server (reusing code from the aiohttp-devtools)
from logo.logo import build_logo get_app()
# Keep the server running for testing
assert server is not None
server.show_object(build_logo())
time.sleep(60) time.sleep(60)

View File

@@ -3,4 +3,4 @@ from os import system
if __name__ == '__main__': if __name__ == '__main__':
# Just a reminder that a hot-reloading server can be started with the following command: # Just a reminder that a hot-reloading server can be started with the following command:
# Need to disable auto-start to avoid conflicts with the hot-reloading server # Need to disable auto-start to avoid conflicts with the hot-reloading server
system('YACV_DISABLE_SERVER=true aiohttp-devtools runserver server.py --port 32323') system('YACV_DISABLE_SERVER=true aiohttp-devtools runserver __init__.py --port 32323')

View File

@@ -3,18 +3,31 @@ import atexit
import os import os
import signal import signal
import sys import sys
import time
from dataclasses import dataclass
from threading import Thread from threading import Thread
from typing import Optional, Tuple from typing import Optional
from OCP.TopoDS import TopoDS_Shape from OCP.TopoDS import TopoDS_Shape
from aiohttp import web from aiohttp import web
from dataclasses_json import dataclass_json
from pubsub import BufferedPubSub from pubsub import BufferedPubSub
from tessellate import _hashcode from tessellate import _hashcode
FRONTEND_BASE_PATH = os.getenv('FRONTEND_BASE_PATH', '../dist') FRONTEND_BASE_PATH = os.getenv('FRONTEND_BASE_PATH', '../dist')
UPDATES_API_PATH = '/api/updates' UPDATES_API_PATH = '/api/updates'
OBJECTS_API_PATH = '/api/objects' # /{name} OBJECTS_API_PATH = '/api/object' # /{name}
@dataclass_json
@dataclass
class UpdatesApiData:
"""Data sent to the client through the updates API"""
name: str
"""Name of the object. Should be unique unless you want to overwrite the previous object"""
hash: str
"""Hash of the object, to detect changes without rebuilding the object"""
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@@ -27,7 +40,7 @@ class Server:
runner: web.AppRunner runner: web.AppRunner
thread: Optional[Thread] = None thread: Optional[Thread] = None
do_shutdown = asyncio.Event() do_shutdown = asyncio.Event()
show_events = BufferedPubSub[Tuple[TopoDS_Shape, str]]() show_events = BufferedPubSub[UpdatesApiData]()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# --- Routes --- # --- Routes ---
@@ -86,14 +99,31 @@ class Server:
ws = web.WebSocketResponse() ws = web.WebSocketResponse()
await ws.prepare(request) await ws.prepare(request)
print('New client connected') async def _send_api_updates():
async for (obj, name) in self.show_events.subscribe(): subscription = self.show_events.subscribe()
hash_code = _hashcode(obj) try:
url = f'{UPDATES_API_PATH}/{name}' first = True
print('New object:', name, hash_code, url) async for data in subscription:
await ws.send_json({'name': name, 'hash': hash_code, 'url': url}) if first:
print('Started sending updates to client (%d subscribers)' % len(self.show_events._subscribers))
first = False
# noinspection PyUnresolvedReferences
await ws.send_str(data.to_json())
finally:
print('Stopped sending updates to client (%d subscribers)' % len(self.show_events._subscribers))
await subscription.aclose()
# TODO: Start previous loop in a separate task and detect connection close to stop it # Start sending updates to the client automatically
send_task = asyncio.create_task(_send_api_updates())
try:
print('Client connected: %s' % request.remote)
# Wait for the client to close the connection (or send a message)
await ws.receive()
finally:
# Make sure to stop sending updates to the client and close the connection
send_task.cancel()
await ws.close()
print('Client disconnected: %s' % request.remote)
return ws return ws
@@ -101,17 +131,12 @@ class Server:
def show_object(self, obj: TopoDS_Shape, name: Optional[str] = None): def show_object(self, obj: TopoDS_Shape, name: Optional[str] = None):
"""Publishes a CAD object to the server""" """Publishes a CAD object to the server"""
start = time.time()
name = name or f'object_{self.obj_counter}' name = name or f'object_{self.obj_counter}'
self.obj_counter += 1 self.obj_counter += 1
self.show_events.publish_nowait((obj, name)) precomputed_info = UpdatesApiData(name=name, hash=_hashcode(obj))
print(f'show_object {precomputed_info} took {time.time() - start:.3f} seconds')
self.show_events.publish_nowait(precomputed_info)
async def api_objects(self, request: web.Request) -> web.Response: async def api_objects(self, request: web.Request) -> web.Response:
return web.Response(body='TODO: Serve the object file here') return web.Response(body='TODO: Serve the object file here')
def get_app() -> web.Application:
"""Required by aiohttp-devtools"""
from logo.logo import build_logo
server = Server()
server.show_object(build_logo())
return server.app