You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
10 KiB
351 lines
10 KiB
2 years ago
|
function deserializer_create(buffer, dataview) {
|
||
|
return {
|
||
|
'offset': 0,
|
||
|
'size': buffer.byteLength,
|
||
|
'buffer': buffer,
|
||
|
'view': dataview,
|
||
|
'strview': new Uint8Array(buffer),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function des_u8(d) {
|
||
|
const value = d.view.getUint8(d.offset);
|
||
|
d.offset += 1;
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function des_u16(d) {
|
||
|
const value = d.view.getUint16(d.offset, true);
|
||
|
d.offset += 2;
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function des_u32(d) {
|
||
|
const value = d.view.getUint32(d.offset, true);
|
||
|
d.offset += 4;
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function des_f32(d) {
|
||
|
const value = d.view.getFloat32(d.offset, true);
|
||
|
d.offset += 4;
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
function des_f32array(d, count) {
|
||
|
const result = [];
|
||
|
|
||
|
for (let i = 0; i < count; ++i) {
|
||
|
const item = d.view.getFloat32(d.offset, true);
|
||
|
d.offset += 4;
|
||
|
result.push(item);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
function des_event(d) {
|
||
|
const event = {};
|
||
|
|
||
|
event.type = des_u8(d);
|
||
|
event.user_id = des_u32(d);
|
||
|
|
||
|
switch (event.type) {
|
||
|
case EVENT.PREDRAW: {
|
||
|
event.x = des_f32(d);
|
||
|
event.y = des_f32(d);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.STROKE: {
|
||
|
const stroke_id = des_u32(d);
|
||
|
const point_count = des_u16(d);
|
||
|
const width = des_u16(d);
|
||
|
const color = des_u32(d);
|
||
|
const coords = des_f32array(d, point_count * 2);
|
||
|
|
||
|
event.stroke_id = stroke_id;
|
||
|
event.points = [];
|
||
|
|
||
|
for (let i = 0; i < point_count; ++i) {
|
||
|
const x = coords[2 * i + 0];
|
||
|
const y = coords[2 * i + 1];
|
||
|
event.points.push({'x': x, 'y': y});
|
||
|
}
|
||
|
|
||
|
event.color = color;
|
||
|
event.width = width;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.IMAGE:
|
||
|
case EVENT.IMAGE_MOVE: {
|
||
|
event.image_id = des_u32(d);
|
||
|
event.x = des_f32(d);
|
||
|
event.y = des_f32(d);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.UNDO:
|
||
|
case EVENT.REDO: {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.ERASER: {
|
||
|
event.stroke_id = des_u32(d);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default: {
|
||
|
console.error('fuck');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return event;
|
||
|
}
|
||
|
|
||
|
function bitmap_bbox(event) {
|
||
|
const bbox = {
|
||
|
'xmin': event.x,
|
||
|
'xmax': event.x + event.bitmap.width,
|
||
|
'ymin': event.y,
|
||
|
'ymax': event.y + event.bitmap.height
|
||
|
};
|
||
|
|
||
|
return bbox;
|
||
|
}
|
||
|
|
||
|
async function handle_event(state, context, event, relax = false) {
|
||
|
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`);
|
||
|
|
||
|
switch (event.type) {
|
||
|
case EVENT.STROKE: {
|
||
|
if (event.user_id != state.me) {
|
||
|
clear_dynamic_stroke(state, context, event.user_id);
|
||
|
}
|
||
|
|
||
|
add_static_stroke(state, context, event, relax);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.UNDO: {
|
||
|
console.error('todo');
|
||
|
// for (let i = state.events.length - 1; i >=0; --i) {
|
||
|
// const other_event = state.events[i];
|
||
|
|
||
|
// // Users can only undo their own, undeleted (not already undone) events
|
||
|
// if (other_event.user_id === event.user_id && !other_event.deleted) {
|
||
|
// if (other_event.type === EVENT.STROKE) {
|
||
|
// other_event.deleted = true;
|
||
|
// const stats = stroke_stats(other_event.points, state.cursor.width);
|
||
|
// redraw_region(stats.bbox);
|
||
|
// break;
|
||
|
// } else if (other_event.type === EVENT.IMAGE) {
|
||
|
// other_event.deleted = true;
|
||
|
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
|
||
|
// if (item) item.remove();
|
||
|
// break;
|
||
|
// } else if (other_event.type === EVENT.ERASER) {
|
||
|
// other_event.deleted = true;
|
||
|
// const erased = find_stroke_backwards(other_event.stroke_id);
|
||
|
// if (erased) {
|
||
|
// erased.deleted = false;
|
||
|
// const stats = stroke_stats(erased.points, state.cursor.width);
|
||
|
// redraw_region(stats.bbox);
|
||
|
// }
|
||
|
// break;
|
||
|
// } else if (other_event.type === EVENT.IMAGE_MOVE) {
|
||
|
// const item = document.querySelector(`img[data-image-id="${other_event.image_id}"]`);
|
||
|
|
||
|
// const ix = state.images[other_event.image_id].x -= other_event.x;
|
||
|
// const iy = state.images[other_event.image_id].y -= other_event.y;
|
||
|
|
||
|
// item.style.transform = `translate(${ix}px, ${iy}px)`;
|
||
|
|
||
|
// break;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.IMAGE: {
|
||
|
console.error('todo');
|
||
|
// const url = config.image_url + event.image_id;
|
||
|
// const item = document.createElement('img');
|
||
|
|
||
|
// item.classList.add('floating-image');
|
||
|
// item.style['z-index'] = state.events.length;
|
||
|
// item.setAttribute('data-image-id', event.image_id);
|
||
|
// item.setAttribute('src', url);
|
||
|
// item.style.transform = `translate(${event.x}px, ${event.y}px)`;
|
||
|
|
||
|
// elements.images.appendChild(item);
|
||
|
// state.images[event.image_id] = {
|
||
|
// 'x': event.x, 'y': event.y
|
||
|
// };
|
||
|
// const r = await fetch(config.image_url + event.image_id);
|
||
|
// const blob = await r.blob();
|
||
|
// const bitmap = await createImageBitmap(blob);
|
||
|
// event.bitmap = bitmap;
|
||
|
// const bbox = bitmap_bbox(event);
|
||
|
// state.ctx0.drawImage(bitmap, bbox.xmin, bbox.ymin);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.IMAGE_MOVE: {
|
||
|
console.error('todo');
|
||
|
// // Already moved due to local prediction
|
||
|
// if (event.user_id !== state.me.id) {
|
||
|
// const image_id = event.image_id;
|
||
|
// const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
|
||
|
|
||
|
// const ix = state.images[event.image_id].x += event.x;
|
||
|
// const iy = state.images[event.image_id].y += event.y;
|
||
|
|
||
|
// if (item) {
|
||
|
// item.style.transform = `translate(${ix}px, ${iy}px)`;
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EVENT.ERASER: {
|
||
|
console.error('todo');
|
||
|
// if (event.deleted) {
|
||
|
// break;
|
||
|
// }
|
||
|
|
||
|
// for (const other_event of state.events) {
|
||
|
// if (other_event.type === EVENT.STROKE && other_event.stroke_id === event.stroke_id) {
|
||
|
// // Might already be deleted because of local prediction
|
||
|
// if (!other_event.deleted) {
|
||
|
// other_event.deleted = true;
|
||
|
// const stats = stroke_stats(other_event.points, state.cursor.width);
|
||
|
// redraw_region(stats.bbox);
|
||
|
// }
|
||
|
// break;
|
||
|
// }
|
||
|
// }
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default: {
|
||
|
console.error('fuck');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function handle_message(state, context, d) {
|
||
|
const message_type = des_u8(d);
|
||
|
let do_draw = false;
|
||
|
|
||
|
if (config.debug_print) console.debug(message_type);
|
||
|
|
||
|
switch (message_type) {
|
||
|
case MESSAGE.JOIN:
|
||
|
case MESSAGE.INIT: {
|
||
|
state.me = des_u32(d);
|
||
|
state.server_lsn = des_u32(d);
|
||
|
|
||
|
if (state.server_lsn > state.lsn) {
|
||
|
// Server knows something that we don't
|
||
|
state.lsn = state.server_lsn;
|
||
|
}
|
||
|
|
||
|
if (message_type === MESSAGE.JOIN) {
|
||
|
localStorage.setItem('sessionId', des_u32(d));
|
||
|
if (config.debug_print) console.debug('join in');
|
||
|
} else {
|
||
|
if (config.debug_print) console.debug('init in');
|
||
|
}
|
||
|
|
||
|
const event_count = des_u32(d);
|
||
|
|
||
|
if (config.debug_print) console.debug(`${event_count} events in init`);
|
||
|
|
||
|
state.events.length = 0;
|
||
|
|
||
|
for (let i = 0; i < event_count; ++i) {
|
||
|
const event = des_event(d);
|
||
|
await handle_event(state, context, event, true);
|
||
|
state.events.push(event);
|
||
|
}
|
||
|
|
||
|
recompute_static_data(context);
|
||
|
|
||
|
do_draw = true;
|
||
|
|
||
|
send_ack(event_count);
|
||
|
|
||
|
sync_queue(state);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case MESSAGE.FIRE: {
|
||
|
const user_id = des_u32(d);
|
||
|
const predraw_event = des_event(d);
|
||
|
|
||
|
update_dynamic_stroke(state, context, user_id, {'x': predraw_event.x, 'y': predraw_event.y});
|
||
|
|
||
|
do_draw = true;
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case MESSAGE.ACK: {
|
||
|
const lsn = des_u32(d);
|
||
|
|
||
|
if (config.debug_print) console.debug(`ack ${lsn} in`);
|
||
|
|
||
|
if (lsn > state.server_lsn) {
|
||
|
// ACKs may arrive out of order
|
||
|
state.server_lsn = lsn;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case MESSAGE.SYN: {
|
||
|
const sn = des_u32(d);
|
||
|
const count = des_u32(d);
|
||
|
|
||
|
const we_expect = sn - state.sn;
|
||
|
const first = count - we_expect;
|
||
|
|
||
|
if (config.debug_print) console.debug(`syn ${sn} in`);
|
||
|
|
||
|
for (let i = 0; i < count; ++i) {
|
||
|
const event = des_event(d);
|
||
|
if (i >= first) {
|
||
|
handle_event(state, context, event);
|
||
|
state.events.push(event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
do_draw = true;
|
||
|
|
||
|
state.sn = sn;
|
||
|
|
||
|
send_ack(sn); // await?
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default: {
|
||
|
console.error('fuck');
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (do_draw) {
|
||
|
window.requestAnimationFrame(() => draw(state, context));
|
||
|
}
|
||
|
}
|