chrisxx commited on
Commit
b4f85f2
·
1 Parent(s): 62f4ec3

Add blocking mechanism: only one player can use stream at a time

Browse files
Files changed (2) hide show
  1. app.py +47 -9
  2. 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
- # Note: We don't stop the stream on disconnect since multiple users can be connected
 
 
 
 
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
- action = int(data.get('action', 1))
 
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
- print('Stopping stream')
 
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