function serializer_create(size) { const buffer = new ArrayBuffer(size); return { 'offset': 0, 'size': size, 'buffer': buffer, 'view': new DataView(buffer), 'strview': new Uint8Array(buffer), }; } function ser_u8(s, value) { s.view.setUint8(s.offset, value); s.offset += 1; } function ser_u16(s, value) { s.view.setUint16(s.offset, value, true); s.offset += 2; } function ser_f32(s, value) { s.view.setFloat32(s.offset, value, true); s.offset += 4; } function ser_u32(s, value) { s.view.setUint32(s.offset, value, true); s.offset += 4; } function ser_align(s, to) { while (s.offset % to != 0) { s.offset++; } } function ser_event(s, event) { ser_u8(s, event.type); switch (event.type) { case EVENT.PREDRAW: { ser_f32(s, event.x); ser_f32(s, event.y); break; } case EVENT.SET_COLOR: { ser_u32(s, event.color); break; } case EVENT.SET_WIDTH: { ser_u16(s, event.width); break; } case EVENT.STROKE: { ser_u16(s, event.points.length); ser_u16(s, event.width); ser_u32(s, event.color); if (config.debug_print) console.debug('original', event.points); ser_align(s, 4); for (const point of event.points) { ser_f32(s, point.x); ser_f32(s, point.y); } break; } case EVENT.IMAGE: case EVENT.IMAGE_MOVE: { const image_id = parseInt(event.image_id); ser_u32(s, image_id); ser_f32(s, event.x); ser_f32(s, event.y); break; } case EVENT.UNDO: case EVENT.REDO: { break; } case EVENT.ERASER: { ser_u32(s, event.stroke_id); break; } default: { console.error('fuck'); } } } async function send_ack(sn) { const s = serializer_create(1 + 4); ser_u8(s, MESSAGE.ACK); ser_u32(s, sn); if (config.debug_print) console.debug(`ack ${sn} out`); try { if (ws) await ws.send(s.buffer); } catch(e) { ws.close(); } } async function sync_queue(state) { if (ws === null) { if (config.debug_print) console.debug('socket has closed, stopping SYNs'); return; } let size = 1 + 3 + 4 + 4; // opcode + lsn + event count let count = state.lsn - state.server_lsn; if (count === 0) { if (config.debug_print) console.debug('server ACKed all events, clearing queue'); state.queue.length = 0; return; } for (let i = count - 1; i >= 0; --i) { const event = state.queue[state.queue.length - 1 - i]; size += event_size(event); } const s = serializer_create(size); ser_u8(s, MESSAGE.SYN); ser_u32(s, state.lsn); ser_u32(s, count); for (let i = count - 1; i >= 0; --i) { const event = state.queue[state.queue.length - 1 - i]; ser_event(s, event); } if (config.debug_print) console.debug(`syn ${state.lsn} out`); try { if (ws) await ws.send(s.buffer); } catch(e) { ws.close(); } setTimeout(() => sync_queue(state), config.sync_timeout); } function push_event(state, event) { state.lsn += 1; switch (event.type) { case EVENT.STROKE: { state.queue.push({ 'type': EVENT.STROKE, 'points': event.points, 'width': event.width, 'color': event.color, }); break; } case EVENT.RULER: { event.type = EVENT.STROKE; state.queue.push(event); break; } case EVENT.ERASER: case EVENT.IMAGE: case EVENT.IMAGE_MOVE: case EVENT.UNDO: case EVENT.REDO: { state.queue.push(event); break; } default: { console.error('fuck'); } } } // Queue an event and initialize repated sends until ACKed function queue_event(state, event, skip = false) { if (!state.online) { return; } push_event(state, event); if (skip) { return; } if (state.timers.queue_sync) { clearTimeout(state.timers.queue_sync); } sync_queue(state); } // Fire and forget. Doesn't do anything if we are offline async function fire_event(state, event) { if (!state.online) { return; } const s = serializer_create(1 + event_size(event)); ser_u8(s, MESSAGE.FIRE); ser_event(s, event); try { if (ws) await ws.send(s.buffer); } catch(e) { ws.close(); } } function predraw_event(x, y) { return { 'type': EVENT.PREDRAW, 'x': x, 'y': y }; } function color_event(color_u32) { return { 'type': EVENT.SET_COLOR, 'color': color_u32, }; } function width_event(width) { return { 'type': EVENT.SET_WIDTH, 'width': width, }; } function image_event(image_id, x, y) { return { 'type': EVENT.IMAGE, 'image_id': image_id, 'x': x, 'y': y, }; } function image_move_event(image_id, x, y) { return { 'type': EVENT.IMAGE_MOVE, 'image_id': image_id, 'x': x, 'y': y, }; } function stroke_event(state) { return { 'type': EVENT.STROKE, 'points': process_stroke(state.current_strokes[state.me].points), 'width': state.current_strokes[state.me].width, 'color': state.current_strokes[state.me].color, }; }