@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
78 lines (68 loc) • 2.66 kB
JavaScript
import { seq16_distance } from "./seq16.js";
/**
* Ack bitfield encoding, after the Quake 3 / Glenn Fiedler "ack reliability" trick.
*
* Each outgoing packet carries a header `(latest_received_seq, ack_bitfield)`.
* `ack_bitfield` is a 32-bit unsigned integer in which bit `i` is set if the receiver
* has received the packet with sequence number `latest_received_seq - 1 - i`.
*
* This means a single 16-bit + 32-bit pair conveys positive acknowledgement of up
* to 33 packets in one go, providing very robust ack delivery even under packet
* loss — losing one ack-bearing packet typically just means the receiver has
* already confirmed the same packets via a later ack-bearing packet.
*
* All functions are pure. The bitfield is treated as unsigned via `>>> 0`.
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
/**
* Build an ack bitfield from the current "latest received" seq and a callback that
* answers "did we receive seq N?".
*
* @param {number} latest_received_seq 16-bit sequence
* @param {function(number): boolean} was_received
* @returns {number} unsigned 32-bit bitfield
*/
export function ack_bitfield_build(latest_received_seq, was_received) {
let bits = 0;
for (let i = 0; i < 32; i++) {
const seq = (latest_received_seq - 1 - i) & 0xFFFF;
if (was_received(seq)) {
bits |= (1 << i);
}
}
return bits >>> 0;
}
/**
* Test whether the bitfield reports a specific sequence as received.
*
* @param {number} latest_received_seq 16-bit sequence
* @param {number} bitfield unsigned 32-bit
* @param {number} seq 16-bit sequence to test
* @returns {boolean}
*/
export function ack_bitfield_contains(latest_received_seq, bitfield, seq) {
if (seq === latest_received_seq) return true;
const offset = seq16_distance(seq, latest_received_seq) - 1;
if (offset < 0 || offset >= 32) return false;
return ((bitfield >>> offset) & 1) === 1;
}
/**
* Iterate every sequence number considered acknowledged by the given header,
* invoking `fn(seq)` for each in newest-to-oldest order. `latest_received_seq`
* is yielded first (it is acked by definition), followed by each older seq
* whose bit is set in the bitfield.
*
* @param {number} latest_received_seq 16-bit sequence
* @param {number} bitfield unsigned 32-bit
* @param {function(number): void} fn
*/
export function ack_bitfield_for_each(latest_received_seq, bitfield, fn) {
fn(latest_received_seq & 0xFFFF);
for (let i = 0; i < 32; i++) {
if (((bitfield >>> i) & 1) === 1) {
fn((latest_received_seq - 1 - i) & 0xFFFF);
}
}
}