Add blocking mechanism: only one player can use stream at a time
Browse files- app.py +47 -9
- static/index.html +25 -2
app.py
CHANGED
|
@@ -66,6 +66,7 @@ server_ready = False # <--- readiness flag
|
|
| 66 |
stream_lock = threading.Lock()
|
| 67 |
stream_thread = None
|
| 68 |
stream_running = False
|
|
|
|
| 69 |
latest_action = 1 # 0=init, 1=nothing, 2=up, 3=down
|
| 70 |
target_fps = 30
|
| 71 |
frame_index = 0
|
|
@@ -368,13 +369,16 @@ def generate_frames():
|
|
| 368 |
# --------------------------
|
| 369 |
# Socket events & helpers
|
| 370 |
# --------------------------
|
| 371 |
-
def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True):
|
| 372 |
-
global stream_thread, stream_running, frame_index, target_fps, latest_action
|
| 373 |
if not server_ready:
|
| 374 |
_broadcast_ready()
|
| 375 |
raise RuntimeError("Server not ready")
|
| 376 |
with stream_lock:
|
|
|
|
|
|
|
| 377 |
stop_stream()
|
|
|
|
| 378 |
target_fps = int(fps)
|
| 379 |
frame_index = 0
|
| 380 |
_reset_cache_fresh()
|
|
@@ -384,13 +388,16 @@ def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True):
|
|
| 384 |
stream_thread.start()
|
| 385 |
|
| 386 |
def stop_stream():
|
| 387 |
-
global stream_thread, stream_running
|
| 388 |
with stream_lock:
|
| 389 |
if stream_thread is not None:
|
| 390 |
stream_thread.stop()
|
| 391 |
stream_thread.join(timeout=1.0)
|
| 392 |
stream_thread = None
|
| 393 |
stream_running = False
|
|
|
|
|
|
|
|
|
|
| 394 |
|
| 395 |
@socketio.on_error_default
|
| 396 |
def default_error_handler(e):
|
|
@@ -423,24 +430,44 @@ def handle_connect():
|
|
| 423 |
def handle_disconnect(*args):
|
| 424 |
sid = request.sid
|
| 425 |
print(f'Client disconnected: {sid}')
|
| 426 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
|
| 428 |
@socketio.on('start_stream')
|
| 429 |
def handle_start_stream(data):
|
| 430 |
try:
|
|
|
|
| 431 |
if not server_ready:
|
| 432 |
# Tell client to keep showing spinner
|
| 433 |
emit('server_status', {'ready': server_ready, 'busy': False})
|
| 434 |
return
|
| 435 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
n_steps = int(data.get('n_steps', 8))
|
| 437 |
cfg = float(data.get('cfg', 0))
|
| 438 |
fps = int(data.get('fps', 30))
|
| 439 |
clamp = bool(data.get('clamp', True))
|
| 440 |
-
print(f"Starting stream @ {fps} FPS (n_steps={n_steps}, cfg={cfg}, clamp={clamp})")
|
| 441 |
try:
|
| 442 |
-
start_stream(n_steps=n_steps, cfg=cfg, fps=fps, clamp=clamp)
|
| 443 |
emit('stream_started', {'status': 'ok'})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
except Exception as e:
|
| 445 |
print(f"Error starting stream: {e}")
|
| 446 |
import traceback
|
|
@@ -454,16 +481,27 @@ def handle_start_stream(data):
|
|
| 454 |
|
| 455 |
@socketio.on('action')
|
| 456 |
def handle_action(data):
|
| 457 |
-
global latest_action
|
| 458 |
-
|
|
|
|
| 459 |
with stream_lock:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
latest_action = action
|
| 461 |
emit('action_ack', {'received': action, 'will_apply_to_frame_index': frame_index})
|
| 462 |
|
| 463 |
@socketio.on('stop_stream')
|
| 464 |
def handle_stop_stream():
|
| 465 |
-
|
|
|
|
| 466 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
stop_stream()
|
| 468 |
emit('stream_stopped', {'status': 'ok'})
|
| 469 |
except Exception as e:
|
|
|
|
| 66 |
stream_lock = threading.Lock()
|
| 67 |
stream_thread = None
|
| 68 |
stream_running = False
|
| 69 |
+
current_player_sid = None # Track which session is currently playing
|
| 70 |
latest_action = 1 # 0=init, 1=nothing, 2=up, 3=down
|
| 71 |
target_fps = 30
|
| 72 |
frame_index = 0
|
|
|
|
| 369 |
# --------------------------
|
| 370 |
# Socket events & helpers
|
| 371 |
# --------------------------
|
| 372 |
+
def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True, player_sid=None):
|
| 373 |
+
global stream_thread, stream_running, frame_index, target_fps, latest_action, current_player_sid
|
| 374 |
if not server_ready:
|
| 375 |
_broadcast_ready()
|
| 376 |
raise RuntimeError("Server not ready")
|
| 377 |
with stream_lock:
|
| 378 |
+
if stream_running and current_player_sid is not None:
|
| 379 |
+
raise RuntimeError(f"Another player is already using the stream (session: {current_player_sid[:8]}...)")
|
| 380 |
stop_stream()
|
| 381 |
+
current_player_sid = player_sid
|
| 382 |
target_fps = int(fps)
|
| 383 |
frame_index = 0
|
| 384 |
_reset_cache_fresh()
|
|
|
|
| 388 |
stream_thread.start()
|
| 389 |
|
| 390 |
def stop_stream():
|
| 391 |
+
global stream_thread, stream_running, current_player_sid
|
| 392 |
with stream_lock:
|
| 393 |
if stream_thread is not None:
|
| 394 |
stream_thread.stop()
|
| 395 |
stream_thread.join(timeout=1.0)
|
| 396 |
stream_thread = None
|
| 397 |
stream_running = False
|
| 398 |
+
current_player_sid = None
|
| 399 |
+
# Notify all clients that the stream is available
|
| 400 |
+
socketio.emit('stream_available', {'status': 'available'})
|
| 401 |
|
| 402 |
@socketio.on_error_default
|
| 403 |
def default_error_handler(e):
|
|
|
|
| 430 |
def handle_disconnect(*args):
|
| 431 |
sid = request.sid
|
| 432 |
print(f'Client disconnected: {sid}')
|
| 433 |
+
# If the current player disconnects, free up the stream
|
| 434 |
+
with stream_lock:
|
| 435 |
+
if current_player_sid == sid:
|
| 436 |
+
print(f'Current player disconnected, freeing stream')
|
| 437 |
+
stop_stream()
|
| 438 |
|
| 439 |
@socketio.on('start_stream')
|
| 440 |
def handle_start_stream(data):
|
| 441 |
try:
|
| 442 |
+
sid = request.sid
|
| 443 |
if not server_ready:
|
| 444 |
# Tell client to keep showing spinner
|
| 445 |
emit('server_status', {'ready': server_ready, 'busy': False})
|
| 446 |
return
|
| 447 |
|
| 448 |
+
# Check if someone else is already playing
|
| 449 |
+
with stream_lock:
|
| 450 |
+
if stream_running and current_player_sid is not None and current_player_sid != sid:
|
| 451 |
+
emit('error', {'message': 'Another player is currently using the stream. Please wait for them to finish.'})
|
| 452 |
+
emit('stream_busy', {'current_player': current_player_sid[:8] if current_player_sid else 'unknown'})
|
| 453 |
+
return
|
| 454 |
+
|
| 455 |
n_steps = int(data.get('n_steps', 8))
|
| 456 |
cfg = float(data.get('cfg', 0))
|
| 457 |
fps = int(data.get('fps', 30))
|
| 458 |
clamp = bool(data.get('clamp', True))
|
| 459 |
+
print(f"Starting stream @ {fps} FPS (n_steps={n_steps}, cfg={cfg}, clamp={clamp}) for session {sid[:8]}")
|
| 460 |
try:
|
| 461 |
+
start_stream(n_steps=n_steps, cfg=cfg, fps=fps, clamp=clamp, player_sid=sid)
|
| 462 |
emit('stream_started', {'status': 'ok'})
|
| 463 |
+
# Notify other clients that stream is now busy
|
| 464 |
+
socketio.emit('stream_busy', {'current_player': sid[:8]}, include_self=False)
|
| 465 |
+
except RuntimeError as e:
|
| 466 |
+
if "already using" in str(e):
|
| 467 |
+
emit('error', {'message': str(e)})
|
| 468 |
+
emit('stream_busy', {'current_player': current_player_sid[:8] if current_player_sid else 'unknown'})
|
| 469 |
+
else:
|
| 470 |
+
raise
|
| 471 |
except Exception as e:
|
| 472 |
print(f"Error starting stream: {e}")
|
| 473 |
import traceback
|
|
|
|
| 481 |
|
| 482 |
@socketio.on('action')
|
| 483 |
def handle_action(data):
|
| 484 |
+
global latest_action, current_player_sid
|
| 485 |
+
sid = request.sid
|
| 486 |
+
# Only allow actions from the current player
|
| 487 |
with stream_lock:
|
| 488 |
+
if current_player_sid != sid:
|
| 489 |
+
emit('error', {'message': 'You are not the current player. Please wait your turn.'})
|
| 490 |
+
return
|
| 491 |
+
action = int(data.get('action', 1))
|
| 492 |
latest_action = action
|
| 493 |
emit('action_ack', {'received': action, 'will_apply_to_frame_index': frame_index})
|
| 494 |
|
| 495 |
@socketio.on('stop_stream')
|
| 496 |
def handle_stop_stream():
|
| 497 |
+
sid = request.sid
|
| 498 |
+
print(f'Stopping stream for session {sid[:8]}')
|
| 499 |
try:
|
| 500 |
+
# Only allow the current player to stop
|
| 501 |
+
with stream_lock:
|
| 502 |
+
if current_player_sid != sid:
|
| 503 |
+
emit('error', {'message': 'You are not the current player. Only the current player can stop the stream.'})
|
| 504 |
+
return
|
| 505 |
stop_stream()
|
| 506 |
emit('stream_stopped', {'status': 'ok'})
|
| 507 |
except Exception as e:
|
static/index.html
CHANGED
|
@@ -56,6 +56,9 @@
|
|
| 56 |
<div id="fpsDisplay" style="margin-top: 8px; font-size: 16px; font-family: monospace;">
|
| 57 |
Achieved FPS: <span id="fpsValue">-</span>
|
| 58 |
</div>
|
|
|
|
|
|
|
|
|
|
| 59 |
<div>
|
| 60 |
This is the output of a small frame-autoregressive transformer trained with rectified flow matching to simulate pong frames conditioned on user inputs for the blue paddle. It should reach 12 FPS when using 4 steps for generation unless something else is running on my machine.
|
| 61 |
</div>
|
|
@@ -114,6 +117,20 @@
|
|
| 114 |
|
| 115 |
const actionValue = document.getElementById('actionValue');
|
| 116 |
const fpsValue = document.getElementById('fpsValue');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
// Incoming frames
|
| 119 |
socket.on('frame', ({ frame, frame_index, action, fps }) => {
|
|
@@ -129,10 +146,16 @@
|
|
| 129 |
|
| 130 |
socket.on('error', (e) => {
|
| 131 |
console.warn('server error', e);
|
| 132 |
-
// The server_status event will handle showing the appropriate overlay
|
| 133 |
-
// Just log the error for now
|
| 134 |
if (e && e.message) {
|
| 135 |
console.error('Server error message:', e.message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
}
|
| 137 |
});
|
| 138 |
|
|
|
|
| 56 |
<div id="fpsDisplay" style="margin-top: 8px; font-size: 16px; font-family: monospace;">
|
| 57 |
Achieved FPS: <span id="fpsValue">-</span>
|
| 58 |
</div>
|
| 59 |
+
<div id="waitingMessage" style="margin-top: 12px; padding: 8px; background: #333; border-radius: 4px; display: none; color: #ffa500;">
|
| 60 |
+
⏳ Another player is currently using the stream. Please wait for them to finish.
|
| 61 |
+
</div>
|
| 62 |
<div>
|
| 63 |
This is the output of a small frame-autoregressive transformer trained with rectified flow matching to simulate pong frames conditioned on user inputs for the blue paddle. It should reach 12 FPS when using 4 steps for generation unless something else is running on my machine.
|
| 64 |
</div>
|
|
|
|
| 117 |
|
| 118 |
const actionValue = document.getElementById('actionValue');
|
| 119 |
const fpsValue = document.getElementById('fpsValue');
|
| 120 |
+
const waitingMessage = document.getElementById('waitingMessage');
|
| 121 |
+
|
| 122 |
+
// Handle stream busy/available events
|
| 123 |
+
socket.on('stream_busy', (data) => {
|
| 124 |
+
console.log('Stream is busy:', data);
|
| 125 |
+
waitingMessage.style.display = 'block';
|
| 126 |
+
startBtn.disabled = true;
|
| 127 |
+
});
|
| 128 |
+
|
| 129 |
+
socket.on('stream_available', (data) => {
|
| 130 |
+
console.log('Stream is available:', data);
|
| 131 |
+
waitingMessage.style.display = 'none';
|
| 132 |
+
startBtn.disabled = false;
|
| 133 |
+
});
|
| 134 |
|
| 135 |
// Incoming frames
|
| 136 |
socket.on('frame', ({ frame, frame_index, action, fps }) => {
|
|
|
|
| 146 |
|
| 147 |
socket.on('error', (e) => {
|
| 148 |
console.warn('server error', e);
|
|
|
|
|
|
|
| 149 |
if (e && e.message) {
|
| 150 |
console.error('Server error message:', e.message);
|
| 151 |
+
// Show error message to user
|
| 152 |
+
if (e.message.includes('Another player') || e.message.includes('not the current player')) {
|
| 153 |
+
waitingMessage.style.display = 'block';
|
| 154 |
+
waitingMessage.textContent = '⏳ ' + e.message;
|
| 155 |
+
startBtn.disabled = true;
|
| 156 |
+
} else {
|
| 157 |
+
alert('Error: ' + e.message);
|
| 158 |
+
}
|
| 159 |
}
|
| 160 |
});
|
| 161 |
|