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.
 
 
 

345 lines
10 KiB

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_s16(d) {
const value = d.view.getInt16(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_u16array(d, count) {
const result = [];
for (let i = 0; i < count; ++i) {
const item = d.view.getUint16(d.offset, true);
d.offset += 2;
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_u16(d);
event.y = des_u16(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_u16array(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_s16(d); // stored as u16, but actually is s16
event.y = des_s16(d); // stored as u16, but actually is s16
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 x = (event.x <= storage.canvas.width ? event.x : event.x - 65536);
const y = (event.y <= storage.canvas.height ? event.y : event.y - 65536);
const bbox = {
'xmin': x,
'xmax': x + event.bitmap.width,
'ymin': y,
'ymax': y + event.bitmap.height
};
return bbox;
}
async function handle_event(event) {
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`);
// TODO(@speed): do not handle locally predicted events
switch (event.type) {
case EVENT.STROKE: {
if (event.user_id in storage.predraw || event.user_id === storage.me.id) {
storage.predraw[event.user_id] = [];
redraw_predraw();
}
draw_stroke(event);
break;
}
case EVENT.UNDO: {
for (let i = storage.events.length - 1; i >=0; --i) {
const other_event = storage.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, storage.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, storage.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 = storage.images[other_event.image_id].x -= other_event.x;
const iy = storage.images[other_event.image_id].y -= other_event.y;
item.style.transform = `translate(${ix}px, ${iy}px)`;
break;
}
}
}
break;
}
case EVENT.IMAGE: {
const url = config.image_url + event.image_id;
const item = document.createElement('img');
item.classList.add('floating-image');
item.style['z-index'] = storage.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);
storage.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);
// storage.ctx0.drawImage(bitmap, bbox.xmin, bbox.ymin);
break;
}
case EVENT.IMAGE_MOVE: {
// Already moved due to local prediction
if (event.user_id !== storage.me.id) {
const image_id = event.image_id;
const item = document.querySelector(`.floating-image[data-image-id="${image_id}"]`);
const ix = storage.images[event.image_id].x += event.x;
const iy = storage.images[event.image_id].y += event.y;
if (item) {
item.style.transform = `translate(${ix}px, ${iy}px)`;
}
}
break;
}
case EVENT.ERASER: {
if (event.deleted) {
break;
}
for (const other_event of storage.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, storage.cursor.width);
redraw_region(stats.bbox);
}
break;
}
}
break;
}
default: {
console.error('fuck');
}
}
}
async function handle_message(d) {
const message_type = des_u8(d);
if (config.debug_print) console.debug(message_type);
switch (message_type) {
case MESSAGE.JOIN:
case MESSAGE.INIT: {
elements.canvas0.classList.add('white');
storage.me.id = des_u32(d);
storage.server_lsn = des_u32(d);
if (storage.server_lsn > storage.lsn) {
// Server knows something that we don't
storage.lsn = storage.server_lsn;
}
if (message_type === MESSAGE.JOIN) {
ls.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`);
storage.ctx0.clearRect(0, 0, storage.ctx0.canvas.width, storage.ctx0.canvas.height);
elements.images.innerHTML = '';
storage.events.length = 0;
for (let i = 0; i < event_count; ++i) {
const event = des_event(d);
await handle_event(event);
storage.events.push(event);
}
elements.canvas0.classList.remove('white');
send_ack(event_count);
sync_queue();
break;
}
case MESSAGE.FIRE: {
const user_id = des_u32(d);
const predraw_event = des_event(d);
predraw_user(user_id, predraw_event);
break;
}
case MESSAGE.ACK: {
const lsn = des_u32(d);
if (config.debug_print) console.debug(`ack ${lsn} in`);
if (lsn > storage.server_lsn) {
// ACKs may arrive out of order
storage.server_lsn = lsn;
}
break;
}
case MESSAGE.SYN: {
const sn = des_u32(d);
const count = des_u32(d);
const we_expect = sn - storage.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(event);
storage.events.push(event);
}
}
storage.sn = sn;
await send_ack(sn);
break;
}
default: {
console.error('fuck');
return;
}
}
}