|
|
|
function serializer_create(size) {
|
|
|
|
const buffer = new ArrayBuffer(size);
|
|
|
|
return {
|
|
|
|
'offset': 0,
|
|
|
|
'size': size,
|
|
|
|
'buffer': buffer,
|
|
|
|
'view': new DataView(buffer),
|
|
|
|
'strview': new Uint8Array(buffer),
|
|
|
|
|
|
|
|
'need_gpu_allocate': true, // need to call glBufferData to create a GPU buffer of size serializer.size
|
|
|
|
'gpu_upload_from': 0, // need to call glBufferSubData for bytes in [serializer.gpu_upload_from, serializer.offset)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function ser_ensure(s, size) {
|
|
|
|
if (s.size < size) {
|
|
|
|
const new_s = serializer_create(Math.ceil(size * 2));
|
|
|
|
|
|
|
|
new_s.strview.set(s.strview);
|
|
|
|
new_s.offset = s.offset;
|
|
|
|
|
|
|
|
return new_s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function ser_ensure_by(s, by) {
|
|
|
|
if (s.offset + by > s.size) {
|
|
|
|
const new_s = serializer_create(Math.ceil((s.size + by) * 2));
|
|
|
|
|
|
|
|
new_s.strview.set(s.strview);
|
|
|
|
new_s.offset = s.offset;
|
|
|
|
|
|
|
|
return new_s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
function ser_clear(s) {
|
|
|
|
s.offset = 0;
|
|
|
|
s.gpu_upload_from = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
// TODO: non-stupid version of this
|
|
|
|
while (s.offset % to != 0) {
|
|
|
|
s.offset++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ser_event(s, event) {
|
|
|
|
ser_u32(s, event.type);
|
|
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
case EVENT.PREDRAW: {
|
|
|
|
ser_f32(s, event.x);
|
|
|
|
ser_f32(s, event.y);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.CLEAR: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CURSOR: {
|
|
|
|
ser_f32(s, event.x);
|
|
|
|
ser_f32(s, event.y);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CANVAS: {
|
|
|
|
ser_u32(s, event.offset_x);
|
|
|
|
ser_u32(s, event.offset_y);
|
|
|
|
ser_f32(s, event.zoom);
|
|
|
|
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);
|
|
|
|
|
|
|
|
for (const point of event.points) {
|
|
|
|
ser_f32(s, point.x);
|
|
|
|
ser_f32(s, point.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const point of event.points) {
|
|
|
|
ser_u8(s, point.pressure);
|
|
|
|
}
|
|
|
|
|
|
|
|
ser_align(s, 4);
|
|
|
|
|
|
|
|
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(4 + 4);
|
|
|
|
|
|
|
|
ser_u32(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 send_follow(player_id) {
|
|
|
|
const s = serializer_create(4 + 4);
|
|
|
|
|
|
|
|
player_id = player_id === null ? -1 : player_id;
|
|
|
|
|
|
|
|
ser_u32(s, MESSAGE.FOLLOW);
|
|
|
|
ser_u32(s, player_id);
|
|
|
|
|
|
|
|
if (config.debug_print) console.debug(`follow ${player_id} 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 = 4 + 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_u32(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(4 + event_size(event));
|
|
|
|
|
|
|
|
ser_u32(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) {
|
|
|
|
const stroke = geometry_prepare_stroke(state);
|
|
|
|
|
|
|
|
return {
|
|
|
|
'type': EVENT.STROKE,
|
|
|
|
'points': stroke.points,
|
|
|
|
'width': stroke.width,
|
|
|
|
'color': stroke.color,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function clear_event(state) {
|
|
|
|
return {
|
|
|
|
'type': EVENT.CLEAR
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function movecursor_event(x, y) {
|
|
|
|
return {
|
|
|
|
'type': EVENT.MOVE_CURSOR,
|
|
|
|
'x': x,
|
|
|
|
'y': y,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function movecanvas_event(state) {
|
|
|
|
return {
|
|
|
|
'type': EVENT.MOVE_CANVAS,
|
|
|
|
'offset_x': state.canvas.offset.x,
|
|
|
|
'offset_y': state.canvas.offset.y,
|
|
|
|
'zoom': state.canvas.zoom,
|
|
|
|
};
|
|
|
|
}
|