40dabfdInitial commit: shared-view hit-and-run samplerJeremy Magland 1// Binary codec for the sample sets. The samples are the big part of the
2// shared state (up to 100k points), so instead of riding in the JSON view
3// message they travel as a Float32 blob, streamed in chunks over the data
4// channel (see Peer.sendBinary) and verified with the SHA-256 announced in the
5// signed 'blob' header.
7import type {Points} from '../types'
9/** Points -> [x0..xn-1, y0..yn-1] as Float32 (plenty for display). */
10export const encodeSamples = (p: Points): ArrayBuffer => {
11 const n = Math.min(p.x.length, p.y.length)
12 const f = new Float32Array(2 * n)
13 for (let i = 0; i < n; i++) {
14 f[i] = p.x[i]
15 f[n + i] = p.y[i]
16 }
17 return f.buffer
18}
20export const decodeSamples = (buf: ArrayBuffer): Points => {
21 const f = new Float32Array(buf)
22 const n = f.length >> 1
23 const x = new Array<number>(n)
24 const y = new Array<number>(n)
25 for (let i = 0; i < n; i++) {
26 x[i] = f[i]
27 y[i] = f[n + i]
28 }
29 return {x, y}
30}
32/** Accumulates the chunks of one announced blob on one connection. The data
33 * channel is ordered, so chunks simply arrive in sequence after the header. */
34export class BlobReceiver {
35 private parts: Uint8Array[] = []
36 private received = 0
38 constructor(
39 readonly id: number,
40 readonly bytes: number,
41 readonly hash: string
42 ) {}
44 /** Append a chunk; returns the assembled buffer once complete, else null. */
45 append(chunk: ArrayBuffer): ArrayBuffer | null {
46 this.parts.push(new Uint8Array(chunk))
47 this.received += chunk.byteLength
48 if (this.received < this.bytes) return null
49 const out = new Uint8Array(this.bytes)
50 let off = 0
51 for (const part of this.parts) {
52 // Tolerate a final chunk that would overrun (corrupt stream): truncate;
53 // the hash check will reject it.
54 const take = Math.min(part.length, this.bytes - off)
55 out.set(take === part.length ? part : part.subarray(0, take), off)
56 off += take
57 }
58 return out.buffer
59 }
60}