mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Working websocket updates including cleanup
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user