feat: bidirectional signal streaming sync
- _signalRegistry: maps signal names → signal objects - _registerSignal: called after each DS.signal() declaration - _applyRemoteDiff: JSON diff → update local signals + flush - _initStreamReceiver: parallel receiver WS for incoming diffs - Echo loop guard: _applyingRemoteDiff prevents re-broadcasting - 0x31 handler in _handleRemoteInput for signal diff frames Changes sync both directions: laptop → phone, phone → laptop.
This commit is contained in:
parent
0369bf831f
commit
69c7ff1e22
2 changed files with 58 additions and 2 deletions
|
|
@ -104,6 +104,8 @@ impl JsEmitter {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.emit_line(&format!("const {} = DS.signal({});", node.name, init));
|
self.emit_line(&format!("const {} = DS.signal({});", node.name, init));
|
||||||
|
// Register signal for bidirectional streaming sync
|
||||||
|
self.emit_line(&format!("DS._registerSignal(\"{}\", {});", node.name, node.name));
|
||||||
}
|
}
|
||||||
SignalKind::Derived => {
|
SignalKind::Derived => {
|
||||||
// Find the let declaration to get the expression
|
// Find the let declaration to get the expression
|
||||||
|
|
@ -1814,6 +1816,29 @@ const DS = (() => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Signal registry for bidirectional sync ──
|
||||||
|
var _signalRegistry = {};
|
||||||
|
var _applyingRemoteDiff = false;
|
||||||
|
|
||||||
|
function _registerSignal(name, sig) {
|
||||||
|
_signalRegistry[name] = sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _applyRemoteDiff(json) {
|
||||||
|
_applyingRemoteDiff = true;
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(json);
|
||||||
|
for (var name in data) {
|
||||||
|
var sig = _signalRegistry[name];
|
||||||
|
if (sig) {
|
||||||
|
sig.value = data[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
} catch(e) { console.warn('[ds-stream] bad diff:', e); }
|
||||||
|
_applyingRemoteDiff = false;
|
||||||
|
}
|
||||||
|
|
||||||
function _initStream(url, mode) {
|
function _initStream(url, mode) {
|
||||||
_streamMode = mode || 'signal';
|
_streamMode = mode || 'signal';
|
||||||
_streamStart = performance.now();
|
_streamStart = performance.now();
|
||||||
|
|
@ -1829,6 +1854,30 @@ const DS = (() => {
|
||||||
};
|
};
|
||||||
_streamWs.onclose = function() { setTimeout(function() { _initStream(url, mode); }, 2000); };
|
_streamWs.onclose = function() { setTimeout(function() { _initStream(url, mode); }, 2000); };
|
||||||
console.log('[ds-stream] Source connected:', url, 'mode:', mode);
|
console.log('[ds-stream] Source connected:', url, 'mode:', mode);
|
||||||
|
|
||||||
|
// Also open a receiver connection for bidirectional sync
|
||||||
|
var receiverUrl = url.replace(/\/source/, '/stream');
|
||||||
|
_initStreamReceiver(receiverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receiver WS — listens for diffs from other clients via relay
|
||||||
|
function _initStreamReceiver(url) {
|
||||||
|
var recvWs = new WebSocket(url);
|
||||||
|
recvWs.binaryType = 'arraybuffer';
|
||||||
|
recvWs.onmessage = function(e) {
|
||||||
|
if (!(e.data instanceof ArrayBuffer) || e.data.byteLength < HEADER_SIZE) return;
|
||||||
|
var bytes = new Uint8Array(e.data);
|
||||||
|
var view = new DataView(bytes.buffer);
|
||||||
|
var type = view.getUint8(0);
|
||||||
|
// Signal diff frame
|
||||||
|
if (type === 0x31) {
|
||||||
|
var payload = bytes.subarray(HEADER_SIZE);
|
||||||
|
var json = new TextDecoder().decode(payload);
|
||||||
|
_applyRemoteDiff(json);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
recvWs.onclose = function() { setTimeout(function() { _initStreamReceiver(url); }, 2000); };
|
||||||
|
console.log('[ds-stream] Receiver connected:', url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _streamSend(type, flags, payload) {
|
function _streamSend(type, flags, payload) {
|
||||||
|
|
@ -1847,6 +1896,7 @@ const DS = (() => {
|
||||||
|
|
||||||
function _streamDiff(name, value) {
|
function _streamDiff(name, value) {
|
||||||
if (!_streamWs || _streamMode !== 'signal') return;
|
if (!_streamWs || _streamMode !== 'signal') return;
|
||||||
|
if (_applyingRemoteDiff) return; // prevent echo loops
|
||||||
var obj = {};
|
var obj = {};
|
||||||
obj[name] = (typeof value === 'object' && value !== null && 'value' in value) ? value.value : value;
|
obj[name] = (typeof value === 'object' && value !== null && 'value' in value) ? value.value : value;
|
||||||
_streamSend(0x31, 0, new TextEncoder().encode(JSON.stringify(obj)));
|
_streamSend(0x31, 0, new TextEncoder().encode(JSON.stringify(obj)));
|
||||||
|
|
@ -1896,6 +1946,12 @@ const DS = (() => {
|
||||||
case 0x50:
|
case 0x50:
|
||||||
emit('remote_scroll', { dx: view.getInt16(0, true), dy: view.getInt16(2, true) });
|
emit('remote_scroll', { dx: view.getInt16(0, true), dy: view.getInt16(2, true) });
|
||||||
break;
|
break;
|
||||||
|
case 0x31: {
|
||||||
|
// Signal diff — apply to local signals
|
||||||
|
var json = new TextDecoder().decode(payload);
|
||||||
|
_applyRemoteDiff(json);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2107,7 +2163,7 @@ const DS = (() => {
|
||||||
scene: scene, circle: circle, rect: rect, line: line,
|
scene: scene, circle: circle, rect: rect, line: line,
|
||||||
_initStream: _initStream, _streamDiff: _streamDiff, _streamSync: _streamSync,
|
_initStream: _initStream, _streamDiff: _streamDiff, _streamSync: _streamSync,
|
||||||
_streamSceneState: _streamSceneState, _connectStream: _connectStream,
|
_streamSceneState: _streamSceneState, _connectStream: _connectStream,
|
||||||
_initWebRTC: _initWebRTC,
|
_initWebRTC: _initWebRTC, _registerSignal: _registerSignal,
|
||||||
Signal: Signal, Derived: Derived, Effect: Effect, Spring: Spring };
|
Signal: Signal, Derived: Derived, Effect: Effect, Spring: Spring };
|
||||||
Object.defineProperty(_ds, '_streamWs', { get: function() { return _streamWs; } });
|
Object.defineProperty(_ds, '_streamWs', { get: function() { return _streamWs; } });
|
||||||
Object.defineProperty(_ds, '_rtcDc', { get: function() { return _rtcDc; } });
|
Object.defineProperty(_ds, '_rtcDc', { get: function() { return _rtcDc; } });
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ let filter_mode = "all"
|
||||||
let total = len(todos)
|
let total = len(todos)
|
||||||
|
|
||||||
-- Stream: any device on the network can view / interact
|
-- Stream: any device on the network can view / interact
|
||||||
stream todo on "ws://localhost:9100" { mode: signal }
|
stream todo on "ws://192.168.1.235:9100" { mode: signal }
|
||||||
|
|
||||||
view app =
|
view app =
|
||||||
column [
|
column [
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue