mirror of
https://github.com/logsol/chuck.js.git
synced 2026-05-11 10:37:34 +00:00
Self-contained test under poc-webrtc/ that does not touch the game. Spins up an Express + WebSocket signaling + node-datachannel server alongside a Socket.IO server, serves a simple browser client that runs the same game-like traffic pattern (14Hz worldUpdates, input events, ping/pong) over either transport based on a URL flag. Captures per-session stats to a JSONL file and ships an analyze.js that prints a per-(transport, phase) summary of RTT percentiles, receive rate, and seq-gap counts so the TCP-vs-UDP-style comparison becomes quantitative rather than eyeball. Confirms node-datachannel installs and works on this platform and that the dual-channel (reliable + unreliable) pattern is feasible to maintain — both prerequisites for the real integration.
80 lines
2.9 KiB
JavaScript
80 lines
2.9 KiB
JavaScript
// WebRTC DataChannel client. Signaling over WebSocket, then two channels:
|
|
// - reliable: default settings, used for welcome/control
|
|
// - unreliable: ordered:false, maxRetransmits:0 — used for game state + pings
|
|
|
|
(function () {
|
|
const POC = window.POC;
|
|
POC.setStatus("signaling…", "wait");
|
|
|
|
const wsProto = location.protocol === "https:" ? "wss" : "ws";
|
|
const ws = new WebSocket(wsProto + "://" + location.host + "/signal");
|
|
|
|
const pc = new RTCPeerConnection({
|
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
});
|
|
|
|
// Create both channels client-side so the server's onDataChannel fires for both.
|
|
const reliable = pc.createDataChannel("reliable");
|
|
const unreliable = pc.createDataChannel("unreliable", {
|
|
ordered: false,
|
|
maxRetransmits: 0
|
|
});
|
|
|
|
function wireChannel(ch, label) {
|
|
ch.onopen = () => {
|
|
POC.log(`channel '${label}' open`);
|
|
if (label === "unreliable") {
|
|
POC.attach((obj) => {
|
|
if (ch.readyState === "open") ch.send(JSON.stringify(obj));
|
|
});
|
|
POC.setStatus("connected (webrtc unreliable)", "ok");
|
|
}
|
|
};
|
|
ch.onclose = () => {
|
|
POC.log(`channel '${label}' closed`);
|
|
POC.setStatus("disconnected", "bad");
|
|
};
|
|
ch.onerror = (e) => POC.log(`channel '${label}' error: ${e.message || e}`);
|
|
ch.onmessage = (e) => {
|
|
try { POC.handleMessage(JSON.parse(e.data)); }
|
|
catch (err) { POC.log("parse error: " + err.message); }
|
|
};
|
|
}
|
|
wireChannel(reliable, "reliable");
|
|
wireChannel(unreliable, "unreliable");
|
|
|
|
pc.onicecandidate = (e) => {
|
|
if (e.candidate) {
|
|
ws.send(JSON.stringify({
|
|
type: "ice",
|
|
candidate: e.candidate.candidate,
|
|
mid: e.candidate.sdpMid
|
|
}));
|
|
}
|
|
};
|
|
pc.onconnectionstatechange = () => {
|
|
POC.log("pc state: " + pc.connectionState);
|
|
if (pc.connectionState === "failed") POC.setStatus("failed", "bad");
|
|
};
|
|
|
|
ws.onopen = async () => {
|
|
POC.log("signaling open, creating offer");
|
|
const offer = await pc.createOffer();
|
|
await pc.setLocalDescription(offer);
|
|
ws.send(JSON.stringify({ type: "sdp", sdp: offer.sdp, sdpType: offer.type }));
|
|
};
|
|
ws.onmessage = async (e) => {
|
|
const m = JSON.parse(e.data);
|
|
if (m.type === "sdp") {
|
|
await pc.setRemoteDescription({ type: m.sdpType, sdp: m.sdp });
|
|
POC.log("got remote sdp (" + m.sdpType + ")");
|
|
} else if (m.type === "ice") {
|
|
try {
|
|
await pc.addIceCandidate({ candidate: m.candidate, sdpMid: m.mid });
|
|
} catch (err) {
|
|
POC.log("addIceCandidate error: " + err.message);
|
|
}
|
|
}
|
|
};
|
|
ws.onclose = () => POC.log("signaling closed");
|
|
})();
|