Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"/> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
| <style> | |
| :root { --bg: transparent; --text: #e8eaf0; --subtext: #8b8fa8; } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { background: var(--bg); font-family: system-ui, sans-serif; color: var(--text); } | |
| .controls { | |
| display: flex; align-items: center; gap: 10px; justify-content: center; | |
| margin: 0 0 4px; font-size: 14px; flex-wrap: wrap; | |
| } | |
| .mode-toggle { | |
| display: flex; border-radius: 6px; overflow: hidden; border: 1px solid #333; | |
| } | |
| .mode-btn { | |
| padding: 6px 16px; font-size: 13px; font-weight: 600; cursor: pointer; | |
| border: none; background: #1a1d2e; color: #666; | |
| transition: all .2s; font-family: inherit; | |
| } | |
| .mode-btn.active { background: #252540; color: #e8eaf0; } | |
| .mode-btn:hover:not(.active) { background: #1f2238; color: #999; } | |
| .play-btn { | |
| width: 26px; height: 26px; border-radius: 50%; border: 1px solid #444; | |
| background: #1a1d2e; color: #e8eaf0; cursor: pointer; font-size: 11px; | |
| display: flex; align-items: center; justify-content: center; font-family: inherit; | |
| transition: background .2s; | |
| } | |
| .play-btn:hover { background: #252540; } | |
| .time-display { font-weight: 700; font-size: 14px; min-width: 70px; text-align: right; } | |
| .legend-row { | |
| display: flex; gap: 14px; justify-content: center; font-size: 12px; | |
| color: #8b8fa8; margin-top: 4px; flex-wrap: wrap; | |
| } | |
| .legend-row span { display: flex; align-items: center; gap: 4px; } | |
| .legend-swatch { width: 12px; height: 8px; border-radius: 2px; flex-shrink: 0; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="controls"> | |
| <div class="mode-toggle"> | |
| <button class="mode-btn active" data-mode="sync">Synchronous</button> | |
| <button class="mode-btn" data-mode="rtc">RTC</button> | |
| </div> | |
| <button class="play-btn" id="play-btn">▶</button> | |
| <input type="range" id="speed-slider" min="0.5" max="3" step="0.5" value="1" style="width:60px;accent-color:#6366f1;cursor:pointer;" title="Playback speed"/> | |
| <span class="time-display" id="time-display"></span> | |
| </div> | |
| <svg id="rtc-svg"></svg> | |
| <div class="legend-row" id="legend-row"> | |
| <span><span class="legend-swatch" style="background:#7c7fff"></span>Inference</span> | |
| <span><span class="legend-swatch" style="background:#22d3ee"></span>Executing</span> | |
| <span id="legend-idle"><span class="legend-swatch" style="background:transparent;border:1px dashed #555"></span>Idle</span> | |
| </div> | |
| <script> | |
| function _initRTC() { | |
| const CHUNK = 10, INF = 3, N_CHUNKS = 4; | |
| function buildSync() { | |
| const inf = [], exec = [], idle = []; | |
| let t = 0; | |
| for (let i = 0; i < N_CHUNKS; i++) { | |
| inf.push({ s: t, e: t + INF, i: i }); | |
| idle.push({ s: t, e: t + INF }); | |
| t += INF; | |
| exec.push({ s: t, e: t + CHUNK, i: i, lock: 0 }); | |
| t += CHUNK; | |
| } | |
| return { inf, exec, idle, T: t }; | |
| } | |
| function buildRTC() { | |
| const inf = [], exec = [], idle = []; | |
| inf.push({ s: 0, e: INF, i: 0 }); | |
| idle.push({ s: 0, e: INF }); | |
| let t = INF; | |
| for (let i = 0; i < N_CHUNKS; i++) { | |
| exec.push({ s: t, e: t + CHUNK, i: i, lock: i > 0 ? INF : 0 }); | |
| if (i + 1 < N_CHUNKS) inf.push({ s: t, e: t + INF, i: i + 1 }); | |
| t += CHUNK - (i + 1 < N_CHUNKS ? INF : 0); | |
| } | |
| return { inf, exec, idle, T: exec[exec.length - 1].e }; | |
| } | |
| const SYNC = buildSync(), RTC_ = buildRTC(); | |
| let mode = "sync", playing = false, playT = 0, speed = 1, animId = null, lastFrame = null; | |
| const container = document.getElementById("rtc-svg").parentElement; | |
| const svg = d3.select("#rtc-svg"); | |
| const playBtn = document.getElementById("play-btn"); | |
| const speedSlider = document.getElementById("speed-slider"); | |
| const timeDisp = document.getElementById("time-display"); | |
| const legendIdle = document.getElementById("legend-idle"); | |
| const legendRow = document.getElementById("legend-row"); | |
| const infPalette = ["#7c7fff", "#9590ff", "#7c7fff", "#9590ff"]; | |
| const execPalette = ["#22d3ee", "#14b8a6", "#06b6d4", "#0ea5e9"]; | |
| const lockColor = "#0c5e6f"; | |
| function data() { return mode === "sync" ? SYNC : RTC_; } | |
| function render() { | |
| svg.selectAll("*").remove(); | |
| const d = data(); | |
| const W = container.clientWidth || 600; | |
| const ml = 72, mr = 12, mt = 6, mb = 24; | |
| const laneH = 32, gap = 20; | |
| const w = W - ml - mr; | |
| const H = mt + laneH * 2 + gap + mb; | |
| svg.attr("width", W).attr("height", H).attr("viewBox", `0 0 ${W} ${H}`); | |
| const g = svg.append("g").attr("transform", `translate(${ml},${mt})`); | |
| const maxT = Math.max(SYNC.T, RTC_.T); | |
| const x = d3.scaleLinear().domain([0, maxT]).range([0, w]); | |
| const iy = 0, ey = laneH + gap; | |
| g.append("rect").attr("x", 0).attr("y", iy).attr("width", w).attr("height", laneH) | |
| .attr("fill", "#ffffff").attr("opacity", 0.02).attr("rx", 4); | |
| g.append("rect").attr("x", 0).attr("y", ey).attr("width", w).attr("height", laneH) | |
| .attr("fill", "#ffffff").attr("opacity", 0.02).attr("rx", 4); | |
| g.append("text").attr("x", -10).attr("y", iy + laneH / 2) | |
| .attr("text-anchor", "end").attr("dominant-baseline", "middle") | |
| .attr("fill", "#8b8fa8").attr("font-size", 12).attr("font-weight", 500).text("Predict"); | |
| g.append("text").attr("x", -10).attr("y", ey + laneH / 2) | |
| .attr("text-anchor", "end").attr("dominant-baseline", "middle") | |
| .attr("fill", "#8b8fa8").attr("font-size", 12).attr("font-weight", 500).text("Execute"); | |
| d.idle.forEach(id => { | |
| const bx = x(id.s), bw = x(id.e) - x(id.s); | |
| g.append("rect").attr("x", bx + 1).attr("y", ey + 3).attr("width", Math.max(0, bw - 2)).attr("height", laneH - 6) | |
| .attr("fill", "none").attr("stroke", "#444").attr("stroke-width", 1) | |
| .attr("stroke-dasharray", "4,3").attr("rx", 3); | |
| }); | |
| d.inf.forEach(b => { | |
| const bx = x(b.s), bw = x(b.e) - x(b.s); | |
| const col = infPalette[b.i % infPalette.length]; | |
| g.append("rect").attr("x", bx).attr("y", iy + 3).attr("width", bw).attr("height", laneH - 6) | |
| .attr("fill", col).attr("opacity", 0.9).attr("rx", 4); | |
| const connX = bx + bw / 2; | |
| const execBlock = d.exec.find(e => e.i === b.i); | |
| if (execBlock) { | |
| const targetX = x(execBlock.s) + (x(execBlock.e) - x(execBlock.s)) / 2; | |
| g.append("line") | |
| .attr("x1", connX).attr("y1", iy + laneH - 2) | |
| .attr("x2", targetX).attr("y2", ey + 2) | |
| .attr("stroke", col).attr("stroke-width", 1) | |
| .attr("stroke-dasharray", "2,3").attr("opacity", 0.3); | |
| } | |
| }); | |
| d.exec.forEach(b => { | |
| const bx = x(b.s), bw = x(b.e) - x(b.s); | |
| const col = execPalette[b.i % execPalette.length]; | |
| if (b.lock > 0) { | |
| const lw = x(b.s + b.lock) - bx; | |
| g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", bw).attr("height", laneH - 6) | |
| .attr("fill", col).attr("opacity", 0.85).attr("rx", 4); | |
| g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", lw).attr("height", laneH - 6) | |
| .attr("fill", lockColor).attr("opacity", 0.95).attr("rx", 4); | |
| for (let sx = bx + 3; sx < bx + lw - 1; sx += 5) { | |
| g.append("line").attr("x1", sx).attr("y1", ey + 3) | |
| .attr("x2", sx + (laneH - 6) * 0.4).attr("y2", ey + laneH - 3) | |
| .attr("stroke", "#1a8a9e").attr("stroke-width", 0.8).attr("opacity", 0.5); | |
| } | |
| g.append("line").attr("x1", bx + lw).attr("y1", ey + 5) | |
| .attr("x2", bx + lw).attr("y2", ey + laneH - 5) | |
| .attr("stroke", "#fff").attr("stroke-width", 0.5).attr("opacity", 0.3); | |
| } else { | |
| g.append("rect").attr("x", bx).attr("y", ey + 3).attr("width", bw).attr("height", laneH - 6) | |
| .attr("fill", col).attr("opacity", 0.85).attr("rx", 4); | |
| } | |
| if (bw > 30) { | |
| g.append("text").attr("x", bx + bw / 2).attr("y", ey + laneH / 2) | |
| .attr("text-anchor", "middle").attr("dominant-baseline", "middle") | |
| .attr("fill", "#fff").attr("font-size", 12).attr("font-weight", 600) | |
| .attr("opacity", 0.9).text("C" + (b.i + 1)); | |
| } | |
| }); | |
| const ticks = []; | |
| const step = maxT > 30 ? 10 : 5; | |
| for (let t = 0; t <= maxT; t += step) ticks.push(t); | |
| ticks.forEach(t => { | |
| const tx = x(t); | |
| g.append("line").attr("x1", tx).attr("y1", ey + laneH + 2).attr("x2", tx).attr("y2", ey + laneH + 5) | |
| .attr("stroke", "#555").attr("stroke-width", 0.5); | |
| g.append("text").attr("x", tx).attr("y", ey + laneH + 14) | |
| .attr("text-anchor", "middle").attr("fill", "#666").attr("font-size", 11) | |
| .text(Math.round(t * 33) + "ms"); | |
| }); | |
| const playhead = g.append("line") | |
| .attr("x1", x(playT)).attr("x2", x(playT)) | |
| .attr("y1", -2).attr("y2", ey + laneH + 2) | |
| .attr("stroke", "#f87171").attr("stroke-width", 1.5).attr("opacity", playing || playT > 0 ? 0.9 : 0); | |
| g.append("circle").attr("cx", x(playT)).attr("cy", -2).attr("r", 3) | |
| .attr("fill", "#f87171").attr("opacity", playing || playT > 0 ? 0.9 : 0).attr("class", "ph-dot"); | |
| const totalMs = Math.round(d.T * 33); | |
| const syncMs = Math.round(SYNC.T * 33); | |
| if (mode === "rtc") { | |
| const pct = Math.round((syncMs - totalMs) / syncMs * 100); | |
| timeDisp.innerHTML = `<span style="color:#10b981">${totalMs}ms</span> <span style="color:#666">(${pct}% faster)</span>`; | |
| } else { | |
| timeDisp.innerHTML = `<span style="color:#666">${totalMs}ms</span>`; | |
| } | |
| if (mode === "rtc") { | |
| legendIdle.style.display = "none"; | |
| if (!document.getElementById("legend-locked")) { | |
| const sp = document.createElement("span"); | |
| sp.id = "legend-locked"; | |
| sp.innerHTML = `<span class="legend-swatch" style="background:${lockColor}"></span>Committed`; | |
| legendRow.appendChild(sp); | |
| } | |
| } else { | |
| legendIdle.style.display = ""; | |
| const lk = document.getElementById("legend-locked"); | |
| if (lk) lk.remove(); | |
| } | |
| window._ph = { line: playhead, dot: g.select(".ph-dot"), x }; | |
| } | |
| function animate(ts) { | |
| if (!playing) return; | |
| if (!lastFrame) lastFrame = ts; | |
| const dt = (ts - lastFrame) / 1000; | |
| lastFrame = ts; | |
| playT += dt * speed * 8; | |
| if (playT > data().T) { playT = 0; lastFrame = null; } | |
| if (window._ph) { | |
| const px = window._ph.x(playT); | |
| window._ph.line.attr("x1", px).attr("x2", px).attr("opacity", 0.9); | |
| window._ph.dot.attr("cx", px).attr("opacity", 0.9); | |
| } | |
| animId = requestAnimationFrame(animate); | |
| } | |
| playBtn.addEventListener("click", () => { | |
| playing = !playing; | |
| playBtn.textContent = playing ? "⏸" : "▶"; | |
| if (playing) { lastFrame = null; animId = requestAnimationFrame(animate); } | |
| else cancelAnimationFrame(animId); | |
| }); | |
| speedSlider.addEventListener("input", () => { | |
| speed = parseFloat(speedSlider.value); | |
| }); | |
| document.querySelectorAll(".mode-btn").forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| document.querySelectorAll(".mode-btn").forEach(b => b.classList.remove("active")); | |
| btn.classList.add("active"); | |
| mode = btn.dataset.mode; | |
| playT = 0; | |
| render(); | |
| }); | |
| }); | |
| render(); | |
| window.addEventListener("resize", render); | |
| } | |
| if (typeof d3 !== "undefined") { _initRTC(); } | |
| else { var s = document.createElement("script"); s.src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"; s.onload=_initRTC; document.head.appendChild(s); } | |
| </script> | |
| </body> | |
| </html> | |