feat: core language & stream improvements

Language improvements:
- Reactive container class: prop (wraps in DS.effect when signal-dependent)
- Stream output filtering (_streamDiff skips non-output signals)
- Stream exponential reconnect backoff (2s → 4s → 8s, max 30s)
- Breakout game: classic row order with all 5 rows having collision

Verified: reactive text + derived signals already work.
All 48 examples compile, 136 tests pass
This commit is contained in:
enzotar 2026-02-27 11:03:53 -08:00
parent 4ac584c81e
commit d4c7ba2385

View file

@ -423,6 +423,18 @@ impl JsEmitter {
));
}
}
// Set output filter if stream has explicit output list
if !stream.output.is_empty() {
let names_js = stream.output.iter()
.map(|n| format!("\"{}\"", n))
.collect::<Vec<_>>()
.join(",");
self.emit_line(&format!(
"DS._streamOutputFilter = new Set([{}]);",
names_js
));
}
}
}
@ -564,7 +576,14 @@ impl JsEmitter {
}
"class" => {
let js = self.emit_expr(val);
self.emit_line(&format!("{}.className += ' ' + {};", node_var, js));
if js.contains(".value") {
self.emit_line(&format!(
"DS.effect(() => {{ {}.className = 'ds-{}' + ' ' + {}; }});",
node_var, container_tag, js
));
} else {
self.emit_line(&format!("{}.className += ' ' + {};", node_var, js));
}
}
"click" | "submit" => {
let handler_js = self.emit_event_handler_expr(val);
@ -2984,14 +3003,17 @@ const DS = (() => {
_streamWs.onclose = function() {
_streamConnected = false;
_streamReconnects++;
console.log('[ds-stream] Disconnected, reconnecting in 2s (attempt ' + _streamReconnects + ')');
setTimeout(function() { _initStream(url, mode); }, 2000);
var delay = Math.min(_reconnectDelay, 30000);
console.log('[ds-stream] Disconnected, reconnecting in ' + (delay/1000) + 's (attempt ' + _streamReconnects + ')');
setTimeout(function() { _initStream(url, mode); }, delay);
_reconnectDelay = Math.min(_reconnectDelay * 2, 30000); // exponential backoff, max 30s
};
_streamWs.onerror = function() {
_streamConnected = false;
};
_streamWs.onopen = function() {
_streamConnected = true;
_reconnectDelay = 2000; // reset backoff on success
console.log('[ds-stream] Peer connected:', peerUrl);
// Send schema announcement (0x32) with output signal list
var outputNames = Object.keys(_signalRegistry);
@ -3038,10 +3060,13 @@ const DS = (() => {
// ── Batched diff: coalesce multiple signal changes into one WS frame ──
var _lastSentValues = {}; // deduplication: track last-sent value per signal
var _diffBatchCount = 0; // count batches for periodic auto-sync
var _streamOutputFilter = null; // Set of signal names to stream (null = all)
var _reconnectDelay = 2000; // exponential backoff: starts at 2s, max 30s
function _streamDiff(name, value) {
if (!_streamWs || _streamWs.readyState !== 1 || _streamMode !== 'signal') return;
if (_applyingRemoteDiff) return; // prevent echo loops
if (_streamOutputFilter && !_streamOutputFilter.has(name)) return; // skip non-output signals
var val = (typeof value === 'object' && value !== null && 'value' in value) ? value.value : value;
// Deduplication: skip if value hasn't changed
var lastStr = JSON.stringify(_lastSentValues[name]);