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.
436 lines
9.2 KiB
436 lines
9.2 KiB
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_s32(s, value) { |
|
s.view.setInt32(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: |
|
case EVENT.LIFT: { |
|
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_s32(s, event.zoom_level); |
|
break; |
|
} |
|
|
|
case EVENT.ZOOM_CANVAS: { |
|
ser_s32(s, event.zoom_level); |
|
ser_f32(s, event.zoom_cx); |
|
ser_f32(s, event.zoom_cy); |
|
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); |
|
ser_u32(s, event.width); |
|
ser_u32(s, event.height); |
|
break; |
|
} |
|
|
|
case EVENT.IMAGE_SCALE: { |
|
const image_id = parseInt(event.image_id); |
|
ser_u32(s, image_id); |
|
ser_u32(s, event.corner); // which corner was moved |
|
ser_f32(s, event.x); // where corner was moved to (canvas coordinates) |
|
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.IMAGE_SCALE: |
|
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 lift_event() { |
|
return { |
|
'type': EVENT.LIFT, |
|
}; |
|
} |
|
|
|
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, width, height) { |
|
return { |
|
'type': EVENT.IMAGE, |
|
'image_id': image_id, |
|
'x': x, |
|
'y': y, |
|
'width': width, |
|
'height': height, |
|
}; |
|
} |
|
|
|
function image_move_event(image_id, x, y) { |
|
return { |
|
'type': EVENT.IMAGE_MOVE, |
|
'image_id': image_id, |
|
'x': x, |
|
'y': y, |
|
}; |
|
} |
|
|
|
function image_scale_event(image_id, corner, x, y) { |
|
return { |
|
'type': EVENT.IMAGE_SCALE, |
|
'image_id': image_id, |
|
'corner': corner, |
|
'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_level': state.canvas.zoom_level, |
|
}; |
|
} |
|
|
|
function zoomcanvas_event(state, zoom_cx, zoom_cy) { |
|
return { |
|
'type': EVENT.ZOOM_CANVAS, |
|
'zoom_level': state.canvas.zoom_level, |
|
'zoom_cx': zoom_cx, |
|
'zoom_cy': zoom_cy, |
|
}; |
|
} |
|
|
|
function undo_event(state) { |
|
return { |
|
'type': EVENT.UNDO, |
|
}; |
|
} |
|
|
|
function eraser_event(stroke_id) { |
|
return { |
|
'type': EVENT.ERASER, |
|
'stroke_id': stroke_id, |
|
} |
|
}
|
|
|