|
|
|
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_s32(d) {
|
|
|
|
const value = d.view.getInt32(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_align(d, to) {
|
|
|
|
// TODO: non-stupid version of this
|
|
|
|
while (d.offset % to != 0) {
|
|
|
|
d.offset++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function des_f32array(d, count) {
|
|
|
|
const result = new Float32Array(d.buffer, d.offset, count);
|
|
|
|
d.offset += 4 * count;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function des_u8array(d, count) {
|
|
|
|
const result = new Uint8Array(d.buffer, d.offset, count);
|
|
|
|
d.offset += count;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function des_event(d, state = null) {
|
|
|
|
const event = {};
|
|
|
|
|
|
|
|
event.type = des_u32(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.USER_JOINED:
|
|
|
|
case EVENT.LEAVE:
|
|
|
|
case EVENT.CLEAR:
|
|
|
|
case EVENT.LIFT: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CURSOR: {
|
|
|
|
event.x = des_f32(d);
|
|
|
|
event.y = des_f32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CANVAS: {
|
|
|
|
event.offset_x = des_s32(d);
|
|
|
|
event.offset_y = des_s32(d);
|
|
|
|
event.zoom_level = des_s32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.ZOOM_CANVAS: {
|
|
|
|
event.zoom_level = des_s32(d);
|
|
|
|
event.zoom_cx = des_f32(d);
|
|
|
|
event.zoom_cy = des_f32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.SET_COLOR: {
|
|
|
|
event.color = des_u32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.SET_WIDTH: {
|
|
|
|
event.width = 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);
|
|
|
|
|
|
|
|
event.coords = des_f32array(d, point_count * 2);
|
|
|
|
event.press = des_u8array(d, point_count);
|
|
|
|
|
|
|
|
des_align(d, 4);
|
|
|
|
|
|
|
|
// TODO: remove, this is duplicate data
|
|
|
|
|
|
|
|
event.stroke_id = stroke_id;
|
|
|
|
|
|
|
|
event.color = color;
|
|
|
|
event.width = width;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE: {
|
|
|
|
event.image_id = des_u32(d);
|
|
|
|
event.x = des_f32(d);
|
|
|
|
event.y = des_f32(d);
|
|
|
|
event.width = des_u32(d);
|
|
|
|
event.height = des_u32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE_MOVE: {
|
|
|
|
event.image_id = des_u32(d);
|
|
|
|
event.x = des_f32(d);
|
|
|
|
event.y = des_f32(d);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE_SCALE: {
|
|
|
|
event.image_id = des_u32(d);
|
|
|
|
event.corner = 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
function init_player_defaults(state, player_id, color = config.default_color, width = config.default_width) {
|
|
|
|
state.players[player_id] = {
|
|
|
|
'color': color,
|
|
|
|
'width': width,
|
|
|
|
'points': [],
|
|
|
|
'online': false,
|
|
|
|
'cursor': {'x': 0, 'y': 0},
|
|
|
|
'strokes': [],
|
|
|
|
'current_prestroke': false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function handle_event(state, context, event, options = {}) {
|
|
|
|
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`);
|
|
|
|
|
|
|
|
let need_draw = false;
|
|
|
|
|
|
|
|
if (!(event.user_id in state.players)) {
|
|
|
|
init_player_defaults(state, event.user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (event.type) {
|
|
|
|
case EVENT.USER_JOINED: {
|
|
|
|
state.players[event.user_id].online = true;
|
|
|
|
draw_html(state);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.PREDRAW: {
|
|
|
|
const player = state.players[event.user_id];
|
|
|
|
|
|
|
|
if (!player.current_prestroke) {
|
|
|
|
geometry_start_prestroke(state, event.user_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
geometry_add_prepoint(state, context, event.user_id, {'x': event.x, 'y': event.y, 'pressure': 128}, false); // TODO: add pressure to predraw events
|
|
|
|
need_draw = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.CLEAR: {
|
|
|
|
// TODO: @touch
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.LIFT: {
|
|
|
|
// Current stroke from player ended. Handle following PREDRAWN events as next stroke
|
|
|
|
geometry_end_prestroke(state, event.user_id);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.LEAVE: {
|
|
|
|
if (event.user_id in state.players) {
|
|
|
|
state.players[event.user_id].online = false;
|
|
|
|
draw_html(state);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CURSOR: {
|
|
|
|
if (event.user_id in state.players) {
|
|
|
|
state.players[event.user_id].cursor.x = event.x;
|
|
|
|
state.players[event.user_id].cursor.y = event.y;
|
|
|
|
state.players[event.user_id].online = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should we syncronize this to RAF?
|
|
|
|
draw_html(state);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.MOVE_CANVAS: {
|
|
|
|
// Double-check just in case
|
|
|
|
// Non-triple equals in on purpose
|
|
|
|
if (event.user_id == state.following_player) {
|
|
|
|
state.canvas.offset.x = event.offset_x;
|
|
|
|
state.canvas.offset.y = event.offset_y;
|
|
|
|
|
|
|
|
const zoom_level = event.zoom_level;
|
|
|
|
const dz = (zoom_level > 0 ? config.zoom_delta : -config.zoom_delta);
|
|
|
|
const zoom = Math.pow(1.0 + dz, Math.abs(zoom_level))
|
|
|
|
|
|
|
|
state.canvas.zoom_level = zoom_level;
|
|
|
|
state.canvas.zoom = zoom;
|
|
|
|
state.canvas.target_zoom = zoom;
|
|
|
|
|
|
|
|
need_draw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.ZOOM_CANVAS: {
|
|
|
|
if (event.user_id == state.following_player) {
|
|
|
|
const zoom_level = event.zoom_level;
|
|
|
|
const zoom_center = {'x': event.zoom_cx, 'y': event.zoom_cy};
|
|
|
|
const dz = (zoom_level > 0 ? config.zoom_delta : -config.zoom_delta);
|
|
|
|
const zoom = Math.pow(1.0 + dz, Math.abs(zoom_level))
|
|
|
|
|
|
|
|
state.canvas.zoom_level = zoom_level;
|
|
|
|
state.canvas.target_zoom = zoom;
|
|
|
|
state.canvas.zoom_screenp = canvas_to_screen(state, zoom_center);
|
|
|
|
|
|
|
|
need_draw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.SET_COLOR: {
|
|
|
|
state.players[event.user_id].color = event.color;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.SET_WIDTH: {
|
|
|
|
state.players[event.user_id].width = event.width;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.STROKE: {
|
|
|
|
const point_count = event.coords.length / 2;
|
|
|
|
|
|
|
|
if (point_count === 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let last_stroke = null;
|
|
|
|
|
|
|
|
for (let i = state.events.length - 1; i >= 0; --i) {
|
|
|
|
if (state.events[i].type === EVENT.STROKE) {
|
|
|
|
last_stroke = state.events[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const index_difference = state.events.length - (last_stroke === null ? -1 : last_stroke.index);
|
|
|
|
wasm_ensure_by(state, index_difference, event.coords.length);
|
|
|
|
|
|
|
|
const pressures = state.wasm.buffers['pressures'];
|
|
|
|
const xs = state.wasm.buffers['xs'];
|
|
|
|
const ys = state.wasm.buffers['ys'];
|
|
|
|
|
|
|
|
event.coords_from = xs.tv.size;
|
|
|
|
event.coords_to = xs.tv.size + point_count;
|
|
|
|
|
|
|
|
for (let i = 0; i < index_difference - 1; ++i) {
|
|
|
|
// Create empty records for all non-stroke events that happened since the last stroke
|
|
|
|
tv_add(state.wasm.buffers['coords_from'].tv, xs.tv.size);
|
|
|
|
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create actual records for this stroke
|
|
|
|
tv_add(state.wasm.buffers['coords_from'].tv, xs.tv.size + point_count);
|
|
|
|
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
|
|
|
|
|
|
|
|
for (let i = 0; i < event.coords.length; i += 2) {
|
|
|
|
tv_add(xs.tv, event.coords[i + 0]);
|
|
|
|
tv_add(ys.tv, event.coords[i + 1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.wasm.buffers['xs'].used += point_count * 4;
|
|
|
|
state.wasm.buffers['ys'].used += point_count * 4;
|
|
|
|
|
|
|
|
tv_append(pressures.tv, event.press);
|
|
|
|
state.wasm.buffers['pressures'].used += point_count;
|
|
|
|
|
|
|
|
delete event.coords;
|
|
|
|
delete event.press;
|
|
|
|
|
|
|
|
need_draw = true;
|
|
|
|
|
|
|
|
event.index = state.events.length;
|
|
|
|
|
|
|
|
geometry_clear_oldest_prestroke(state, context, event.user_id);
|
|
|
|
geometry_add_stroke(state, context, event, state.events.length, options.skip_bvh === true);
|
|
|
|
|
|
|
|
state.stroke_count++;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.UNDO: {
|
|
|
|
geometry_add_dummy_stroke(context);
|
|
|
|
|
|
|
|
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;
|
|
|
|
if (other_event.bvh_node && !options.skip_bvh) {
|
|
|
|
bvh_delete_stroke(state, other_event);
|
|
|
|
}
|
|
|
|
need_draw = true;
|
|
|
|
break;
|
|
|
|
} else if (other_event.type === EVENT.UNDO) {
|
|
|
|
// do not undo an undo, we are not Notepad
|
|
|
|
} else if (other_event.type === EVENT.IMAGE) {
|
|
|
|
other_event.deleted = true;
|
|
|
|
const image = get_image(context, other_event.image_id);
|
|
|
|
if (image !== null) {
|
|
|
|
image.deleted = true;
|
|
|
|
}
|
|
|
|
need_draw = true;
|
|
|
|
break;
|
|
|
|
} else if (other_event.type === EVENT.IMAGE_MOVE) {
|
|
|
|
other_event.deleted = true;
|
|
|
|
const image = get_image(context, other_event.image_id);
|
|
|
|
if (image !== null) {
|
|
|
|
image.move_head -= 2;
|
|
|
|
image.at.x = image.move_history[image.move_head - 2];
|
|
|
|
image.at.y = image.move_history[image.move_head - 1];
|
|
|
|
need_draw = true;
|
|
|
|
} else {
|
|
|
|
console.warning('Undo image move for a non-existent image');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (other_event.type === EVENT.IMAGE_SCALE) {
|
|
|
|
other_event.deleted = true;
|
|
|
|
const image = get_image(context, other_event.image_id);
|
|
|
|
if (image !== null) {
|
|
|
|
image.scale_head -= 4;
|
|
|
|
image.at.x = image.scale_history[image.scale_head - 4];
|
|
|
|
image.at.y = image.scale_history[image.scale_head - 3];
|
|
|
|
image.width = image.scale_history[image.scale_head - 2];
|
|
|
|
image.height = image.scale_history[image.scale_head - 1];
|
|
|
|
need_draw = true;
|
|
|
|
} else {
|
|
|
|
console.warning('Undo image scale for a non-existent image');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (other_event.type === EVENT.ERASER) {
|
|
|
|
const stroke = state.events[other_event.stroke_id];
|
|
|
|
stroke.deleted = false;
|
|
|
|
if (!options.skip_bvh) {
|
|
|
|
bvh_undelete_stroke(state, stroke);
|
|
|
|
}
|
|
|
|
need_draw = true;
|
|
|
|
other_event.deleted = true;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
console.error('cant undo event type', other_event.type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE: {
|
|
|
|
const p = {'x': event.x, 'y': event.y};
|
|
|
|
|
|
|
|
geometry_add_dummy_stroke(context);
|
|
|
|
add_image(context, event.image_id, null, p, event.width, event.height);
|
|
|
|
|
|
|
|
try {
|
|
|
|
(async () => {
|
|
|
|
const url = config.image_url + event.image_id;
|
|
|
|
const r = await fetch(config.image_url + event.image_id);
|
|
|
|
const blob = await r.blob();
|
|
|
|
|
|
|
|
// NOTE: this will resolve when bitmap is ready, which will be much later
|
|
|
|
const bitmap = await createImageBitmap(blob);
|
|
|
|
|
|
|
|
event.width = bitmap.width;
|
|
|
|
event.height = bitmap.height;
|
|
|
|
|
|
|
|
add_image(context, event.image_id, bitmap, p, bitmap.width, bitmap.height);
|
|
|
|
|
|
|
|
// God knows when this will actually complete (it loads the image from the server)
|
|
|
|
// so do not set need_draw. Instead just schedule the draw ourselves when done
|
|
|
|
schedule_draw(state, context);
|
|
|
|
})();
|
|
|
|
} catch (e) {
|
|
|
|
console.log('Could not load image bitmap:', e);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE_MOVE: {
|
|
|
|
geometry_add_dummy_stroke(context);
|
|
|
|
const image_id = event.image_id;
|
|
|
|
const image = get_image(context, image_id);
|
|
|
|
|
|
|
|
if (image) {
|
|
|
|
// if (config.debug_print) console.debug('move image', image_id, 'to', image_event.x, image_event.y);
|
|
|
|
if (image.move_head < image.move_history.length) {
|
|
|
|
image.move_history[image.move_head] = event.x;
|
|
|
|
image.move_history[image.move_head + 1] = event.y;
|
|
|
|
} else {
|
|
|
|
image.move_history.push(event.x, event.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
image.move_head += 2;
|
|
|
|
image.at.x = event.x;
|
|
|
|
image.at.y = event.y;
|
|
|
|
need_draw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.IMAGE_SCALE: {
|
|
|
|
geometry_add_dummy_stroke(context);
|
|
|
|
const image_id = event.image_id;
|
|
|
|
const image = get_image(context, image_id);
|
|
|
|
|
|
|
|
if (image !== null) {
|
|
|
|
if (image.scale_head < image.scale_history.length) {
|
|
|
|
image.scale_history[image.scale_head] = image.at.x;
|
|
|
|
image.scale_history[image.scale_head + 1] = image.at.y;
|
|
|
|
image.scale_history[image.scale_head + 2] = image.width;
|
|
|
|
image.scale_history[image.scale_head + 3] = image.height;
|
|
|
|
} else {
|
|
|
|
image.scale_history.push(image.at.x, image.at.y, image.width, image.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
image.scale_head += 4;
|
|
|
|
|
|
|
|
scale_image(context, image, event.corner, {'x': event.x, 'y': event.y});
|
|
|
|
need_draw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case EVENT.ERASER: {
|
|
|
|
geometry_add_dummy_stroke(context);
|
|
|
|
need_draw = true;
|
|
|
|
const stroke = state.events[event.stroke_id];
|
|
|
|
stroke.deleted = true;
|
|
|
|
if (!options.skip_bvh) {
|
|
|
|
bvh_delete_stroke(state, stroke);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
console.error('fuck');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return need_draw;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handle_message(state, context, d) {
|
|
|
|
const message_type = des_u32(d);
|
|
|
|
let do_draw = false;
|
|
|
|
|
|
|
|
// if (config.debug_print) console.debug(message_type);
|
|
|
|
|
|
|
|
switch (message_type) {
|
|
|
|
case MESSAGE.JOIN:
|
|
|
|
case MESSAGE.INIT: {
|
|
|
|
console.time('init');
|
|
|
|
|
|
|
|
state.online = true;
|
|
|
|
state.server_lsn = des_u32(d);
|
|
|
|
|
|
|
|
if (state.server_lsn > state.lsn) {
|
|
|
|
// Server knows something that we don't
|
|
|
|
state.lsn = state.server_lsn;
|
|
|
|
}
|
|
|
|
|
|
|
|
let color = config.default_color;
|
|
|
|
let width = config.default_width;
|
|
|
|
|
|
|
|
if (message_type === MESSAGE.JOIN) {
|
|
|
|
localStorage.setItem('sessionId', des_u32(d));
|
|
|
|
if (config.debug_print) console.debug('join in');
|
|
|
|
} else {
|
|
|
|
color = des_u32(d);
|
|
|
|
width = des_u16(d);
|
|
|
|
if (config.debug_print) console.debug('init in');
|
|
|
|
}
|
|
|
|
|
|
|
|
state.me = parseInt(localStorage.getItem('sessionId'));
|
|
|
|
|
|
|
|
init_player_defaults(state, state.me);
|
|
|
|
|
|
|
|
set_color_u32(state, color);
|
|
|
|
|
|
|
|
document.querySelector('#stroke-width').value = width;
|
|
|
|
fire_event(state, width_event(width));
|
|
|
|
|
|
|
|
const event_count = des_u32(d);
|
|
|
|
const user_count = des_u32(d);
|
|
|
|
const total_points = des_u32(d);
|
|
|
|
|
|
|
|
wasm_ensure_by(state, event_count, round_to_pow2(total_points * 2, 4096));
|
|
|
|
|
|
|
|
if (config.debug_print) console.debug(`${event_count} events in init`);
|
|
|
|
|
|
|
|
state.events.length = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < user_count; ++i) {
|
|
|
|
const user_id = des_u32(d);
|
|
|
|
const user_color = des_u32(d);
|
|
|
|
const user_width = des_u16(d);
|
|
|
|
const user_online = des_u8(d);
|
|
|
|
|
|
|
|
init_player_defaults(state, user_id, user_color, user_width);
|
|
|
|
state.players[user_id].online = user_online === 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
des_align(d, 4);
|
|
|
|
|
|
|
|
for (let i = 0; i < event_count; ++i) {
|
|
|
|
const event = des_event(d, state);
|
|
|
|
handle_event(state, context, event, {'skip_bvh': true});
|
|
|
|
|
|
|
|
if (event.type !== EVENT.STROKE || event.coords_to - event.coords_from > 0) {
|
|
|
|
state.events.push(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.sn = event_count;
|
|
|
|
|
|
|
|
bvh_construct(state);
|
|
|
|
|
|
|
|
do_draw = true;
|
|
|
|
|
|
|
|
send_ack(event_count);
|
|
|
|
sync_queue(state);
|
|
|
|
|
|
|
|
console.timeEnd('init');
|
|
|
|
|
|
|
|
update_cursor(state);
|
|
|
|
draw_html(state);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MESSAGE.FIRE: {
|
|
|
|
const event = des_event(d);
|
|
|
|
const need_draw = handle_event(state, context, event);
|
|
|
|
|
|
|
|
do_draw = do_draw || need_draw;
|
|
|
|
|
|
|
|
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, state);
|
|
|
|
if (i >= first) {
|
|
|
|
const need_draw = handle_event(state, context, event);
|
|
|
|
do_draw = do_draw || need_draw;
|
|
|
|
state.events.push(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.sn = sn;
|
|
|
|
|
|
|
|
send_ack(sn); // await?
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
console.error('fuck');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (do_draw) {
|
|
|
|
schedule_draw(state, context);
|
|
|
|
}
|
|
|
|
}
|