minirt service done
This commit is contained in:
parent
0083f356f2
commit
88fd970938
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "noVNC"]
|
||||
path = noVNC
|
||||
url = https://github.com/novnc/noVNC
|
||||
226
backend.py
Normal file
226
backend.py
Normal file
@ -0,0 +1,226 @@
|
||||
import atexit
|
||||
import subprocess
|
||||
import time
|
||||
from aiohttp import web
|
||||
import os
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
web_root = './noVNC/'
|
||||
app_path = './out/miniRT'
|
||||
HOST = '0.0.0.0'
|
||||
PORT = 7080
|
||||
SESSION_TTL = 60
|
||||
|
||||
service_name = 'minirt'
|
||||
|
||||
class SessionManager:
|
||||
def __init__(self):
|
||||
self.session = None # single session
|
||||
self.display_counter = 100
|
||||
|
||||
def create_session(self):
|
||||
if self.session is not None:
|
||||
raise RuntimeError("Session already active")
|
||||
|
||||
display_num = self.display_counter
|
||||
rfb_port = 5900 + display_num
|
||||
ws_port = 6080 + display_num
|
||||
|
||||
try:
|
||||
xvnc_proc = subprocess.Popen(
|
||||
['Xvnc', f':{display_num}',
|
||||
'-geometry', '1200x900',
|
||||
'-SecurityTypes', 'None',
|
||||
'-rfbport', str(rfb_port)],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# Wait until Xvnc is actually listening on rfb_port
|
||||
import socket
|
||||
for attempt in range(20):
|
||||
if xvnc_proc.poll() is not None:
|
||||
raise RuntimeError(f"Xvnc exited with code {xvnc_proc.returncode}")
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', rfb_port), timeout=0.5):
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
raise RuntimeError(f"Xvnc not listening on port {rfb_port} after 5s")
|
||||
|
||||
env = os.environ.copy()
|
||||
env['DISPLAY'] = f':{display_num}'
|
||||
app_proc = subprocess.Popen(
|
||||
[app_path],
|
||||
env=env,
|
||||
# stdout=subprocess.DEVNULL,
|
||||
# stderr=subprocess.DEVNULL,
|
||||
)
|
||||
websockify_proc = subprocess.Popen(
|
||||
['websockify', str(ws_port), f'127.0.0.1:{rfb_port}'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
self.session = {
|
||||
'display': display_num,
|
||||
'rfb_port': rfb_port,
|
||||
'ws_port': ws_port,
|
||||
'xvnc_proc': xvnc_proc,
|
||||
'websockify_proc': websockify_proc,
|
||||
'app_proc': app_proc,
|
||||
'created_at': time.time(),
|
||||
}
|
||||
print(f"Created session on display :{display_num}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating session: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def get_session(self):
|
||||
return self.session
|
||||
|
||||
def get_display(self):
|
||||
return self.display_counter
|
||||
|
||||
def stop_session(self):
|
||||
sess = self.session
|
||||
if sess is None:
|
||||
return
|
||||
self.session = None
|
||||
for name in ('app_proc', 'xvnc_proc', 'websockify_proc'):
|
||||
proc = sess.get(name)
|
||||
if proc and proc.poll() is None:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
print("Stopped session")
|
||||
|
||||
|
||||
async def websockify_handler(request: web.Request):
|
||||
sess = manager.get_session()
|
||||
if sess is None:
|
||||
try:
|
||||
manager.create_session()
|
||||
sess = manager.get_session()
|
||||
except RuntimeError:
|
||||
return web.Response(status=503, text='Session already active')
|
||||
else:
|
||||
return web.Response(status=503, text='Session already active')
|
||||
if sess is None:
|
||||
return web.Response(status=403, text='Invalid Session')
|
||||
|
||||
ws_server = web.WebSocketResponse(protocols=['binary'])
|
||||
await ws_server.prepare(request)
|
||||
try:
|
||||
async with aiohttp.ClientSession() as client:
|
||||
ws_client = None
|
||||
for attempt in range(10):
|
||||
try:
|
||||
ws_client = await client.ws_connect(f'ws://127.0.0.1:{sess["ws_port"]}')
|
||||
break
|
||||
except (aiohttp.ClientConnectorError, OSError):
|
||||
await asyncio.sleep(0.5)
|
||||
async with client.ws_connect(f'ws://127.0.0.1:{sess["ws_port"]}') as ws_client:
|
||||
async def forward(src, dst):
|
||||
async for msg in src:
|
||||
if msg.type == aiohttp.WSMsgType.BINARY:
|
||||
await dst.send_bytes(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.TEXT:
|
||||
await dst.send_str(msg.data)
|
||||
else:
|
||||
break
|
||||
|
||||
async def check_process():
|
||||
while True:
|
||||
proc = sess.get('app_proc')
|
||||
if proc is None:
|
||||
break
|
||||
ret = proc.poll()
|
||||
if ret is not None:
|
||||
print(f"App exited with code {ret}, restarting...")
|
||||
env = os.environ.copy()
|
||||
env['DISPLAY'] = f':{sess["display"]}'
|
||||
new_proc = subprocess.Popen([app_path], env=env)
|
||||
print(f"New PID: {new_proc.pid}")
|
||||
sess['app_proc'] = new_proc
|
||||
await asyncio.sleep(1)
|
||||
|
||||
tasks = [
|
||||
asyncio.create_task(forward(ws_server, ws_client)),
|
||||
asyncio.create_task(forward(ws_client, ws_server)),
|
||||
asyncio.create_task(check_process()),
|
||||
]
|
||||
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
except Exception as e:
|
||||
print(f'Caught exception: {e}')
|
||||
finally:
|
||||
manager.stop_session()
|
||||
if not ws_server.closed:
|
||||
await ws_server.close()
|
||||
return ws_server
|
||||
|
||||
manager = SessionManager()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def static_handler(request: web.Request):
|
||||
"""Serve noVNC static files."""
|
||||
path_info = request.match_info.get('path_info', '')
|
||||
if path_info == '':
|
||||
path_info = 'index.html'
|
||||
|
||||
serve_path = os.path.normpath(os.path.join(web_root, path_info))
|
||||
root_abs = os.path.abspath(web_root)
|
||||
serve_abs = os.path.abspath(serve_path)
|
||||
|
||||
if not serve_abs.startswith(root_abs):
|
||||
return web.Response(status=403, text='Forbidden')
|
||||
if not os.path.exists(serve_abs):
|
||||
return web.Response(status=404, text='Not Found')
|
||||
if os.path.isdir(serve_abs):
|
||||
serve_abs = os.path.join(serve_abs, 'index.html')
|
||||
if not os.path.exists(serve_abs):
|
||||
return web.Response(status=404, text='Not Found')
|
||||
|
||||
return web.FileResponse(serve_abs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# App setup
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def cleanup():
|
||||
manager.stop_session()
|
||||
|
||||
atexit.register(cleanup)
|
||||
|
||||
@web.middleware
|
||||
async def print_req(req, handler):
|
||||
print(req)
|
||||
response = await handler(req);
|
||||
return response
|
||||
|
||||
|
||||
async def start_app():
|
||||
app = web.Application(middlewares=[print_req])
|
||||
|
||||
app.router.add_get('/websockify', websockify_handler)
|
||||
app.router.add_get('/{path_info:.+}', static_handler)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
web.run_app(start_app(), host=HOST, port=PORT)
|
||||
5
minirt.h
5
minirt.h
@ -363,6 +363,9 @@ typedef struct s_cone
|
||||
t_disk bottom_cap;
|
||||
} t_cone;
|
||||
|
||||
# define ASSETS_PATH ROOT_DIRECTORY"/assets"
|
||||
# define SCENES_PATH ASSETS_PATH"/scenes"
|
||||
|
||||
# ifndef TEXTURE_PATH
|
||||
# define TEXTURE_PATH ASSETS_PATH"/textures/earth.ppm"
|
||||
# endif
|
||||
@ -371,8 +374,6 @@ typedef struct s_cone
|
||||
# define SKYBOX_PATH ASSETS_PATH"/textures/sky.ppm"
|
||||
# endif
|
||||
|
||||
# define ASSETS_PATH ROOT_DIRECTORY"/assets"
|
||||
# define SCENES_PATH ASSETS_PATH"/scenes"
|
||||
|
||||
typedef struct s_texture
|
||||
{
|
||||
|
||||
1
noVNC
Submodule
1
noVNC
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8e1ebdffba02e651c399dacef841f8941f6ad6e4
|
||||
BIN
out/miniRT
BIN
out/miniRT
Binary file not shown.
@ -22,11 +22,13 @@ uint max_res_x = 1;
|
||||
uint max_res_y = 1;
|
||||
|
||||
void create_display() {
|
||||
SetConfigFlags(FLAG_WINDOW_UNDECORATED);
|
||||
InitWindow(800, 600, "miniRT");
|
||||
int monitor = GetCurrentMonitor();
|
||||
int w = GetMonitorWidth(monitor);
|
||||
int h = GetMonitorHeight(monitor);
|
||||
SetWindowSize(w, h);
|
||||
SetWindowPosition(0, 0);
|
||||
data.scene.w = w;
|
||||
data.scene.h = h;
|
||||
SetTargetFPS(60);
|
||||
@ -98,8 +100,8 @@ t_container *menus_create(t_data *data)
|
||||
|
||||
void initialize_data(t_data *data, char *path)
|
||||
{
|
||||
scene_create(path, &data->scene);
|
||||
create_display();
|
||||
scene_create(path, &data->scene);
|
||||
data->pixel = pixel_plane_create();
|
||||
data->func_ptr = help_menu_draw;
|
||||
data->mouse.data = data;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user