feat: pong spectator viewer + stream proxy reactivity fix
- New pong-viewer.ds: full visual court rendered from streamed state
- Extended container prop signal detection: js_val.contains('.value')
fixes DotAccess on stream proxies (game.value.p1y) not being reactive
- Verified two-tab relay demo: player → relay → viewer syncs in real-time
- All 46 examples compile, 136 tests pass
This commit is contained in:
parent
bd926b9e0a
commit
25b960fa29
2 changed files with 64 additions and 2 deletions
|
|
@ -564,9 +564,14 @@ impl JsEmitter {
|
|||
// Layout props (x, y, width, height) or arbitrary style
|
||||
let js_val = self.emit_expr(val);
|
||||
// Check if this is a signal reference: emit_expr on signal `foo`
|
||||
// returns `foo.value`, so also check with `.value` stripped
|
||||
// returns `foo.value`, so also check with `.value` stripped.
|
||||
// For dotted access like `game.value.p1y`, check if .value appears anywhere.
|
||||
let raw_name = js_val.strip_suffix(".value").unwrap_or(&js_val);
|
||||
if graph.name_to_id.contains_key(raw_name) || graph.name_to_id.contains_key(&js_val) || self.is_signal_ref(&js_val) {
|
||||
if graph.name_to_id.contains_key(raw_name)
|
||||
|| graph.name_to_id.contains_key(&js_val)
|
||||
|| self.is_signal_ref(&js_val)
|
||||
|| js_val.contains(".value")
|
||||
{
|
||||
self.emit_line(&format!(
|
||||
"DS.effect(() => {{ {}.style.{} = {} + 'px'; }});",
|
||||
node_var, key, js_val
|
||||
|
|
|
|||
57
examples/pong-viewer.ds
Normal file
57
examples/pong-viewer.ds
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
-- DreamStack Pong Viewer (Spectator)
|
||||
-- Watches the pong game live via streaming relay.
|
||||
-- Renders the full visual court from streamed signal state.
|
||||
--
|
||||
-- Run with:
|
||||
-- Tab 1: cargo run -p ds-stream (relay on :9100)
|
||||
-- Tab 2: dreamstack dev examples/game-pong.ds (player)
|
||||
-- Tab 3: dreamstack dev examples/pong-viewer.ds --port 3001 (viewer)
|
||||
|
||||
import { Badge } from "../registry/components/badge"
|
||||
|
||||
-- Connect to the pong game stream
|
||||
let game = stream from "ws://localhost:9100/stream/pong"
|
||||
|
||||
view viewer = column [
|
||||
text "👁️ Pong Spectator" { variant: "title" }
|
||||
|
||||
-- Score bar
|
||||
row [
|
||||
Badge { label: "LIVE 🔴", variant: "error" }
|
||||
Badge { label: "P1: {game.score1}", variant: "info" }
|
||||
Badge { label: "Rally: {game.rally}", variant: "warning" }
|
||||
Badge { label: "AI: {game.score2}", variant: "error" }
|
||||
]
|
||||
|
||||
-- ── Spectator Court ──
|
||||
stack { style: "position:relative; width:600px; height:400px; background:linear-gradient(180deg,#0f172a,#1e293b); border:2px solid #334155; border-radius:16px; margin:0.5rem auto; overflow:hidden; box-shadow:0 0 40px rgba(99,102,241,0.15)" } [
|
||||
-- Center line
|
||||
column { style: "position:absolute; left:298px; top:0; width:4px; height:400px; background:repeating-linear-gradient(to bottom, #475569 0px, #475569 12px, transparent 12px, transparent 24px)" } []
|
||||
|
||||
-- Center circle
|
||||
column { style: "position:absolute; left:260px; top:160px; width:80px; height:80px; border:2px solid #475569; border-radius:50%" } []
|
||||
|
||||
-- P1 paddle (blue, left)
|
||||
column { style: "position:absolute; left:8px; width:12px; height:80px; background:linear-gradient(180deg,#818cf8,#6366f1); border-radius:6px; box-shadow:0 0 12px rgba(129,140,248,0.5); transition:top 0.1s", top: game.p1y } []
|
||||
|
||||
-- P2/AI paddle (red, right)
|
||||
column { style: "position:absolute; left:580px; width:12px; height:80px; background:linear-gradient(180deg,#f87171,#ef4444); border-radius:6px; box-shadow:0 0 12px rgba(248,113,113,0.5); transition:top 0.1s", top: game.p2y } []
|
||||
|
||||
-- Ball (yellow glow)
|
||||
column { style: "position:absolute; width:14px; height:14px; background:radial-gradient(circle,#fde68a,#f59e0b); border-radius:50%; box-shadow:0 0 16px #fbbf24,0 0 4px #fbbf24", top: game.ballY, left: game.ballX } []
|
||||
|
||||
-- Score overlay on court
|
||||
row { style: "position:absolute; top:12px; left:0; width:100%; justify-content:center; gap:140px; pointer-events:none" } [
|
||||
text "{game.score1}" { style: "font-size:56px; color:rgba(129,140,248,0.2); font-weight:900; font-family:monospace" }
|
||||
text "{game.score2}" { style: "font-size:56px; color:rgba(248,113,113,0.2); font-weight:900; font-family:monospace" }
|
||||
]
|
||||
|
||||
-- SPECTATING overlay
|
||||
row { style: "position:absolute; bottom:12px; left:0; width:100%; justify-content:center; pointer-events:none" } [
|
||||
text "SPECTATING" { style: "font-size:12px; color:rgba(255,255,255,0.15); letter-spacing:4px; font-weight:700" }
|
||||
]
|
||||
]
|
||||
|
||||
when game.paused -> text "⏸ Game is paused" { variant: "muted" }
|
||||
text "View-only • no controls • game state received via relay" { variant: "muted" }
|
||||
]
|
||||
Loading…
Add table
Reference in a new issue