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.
295 lines
6.9 KiB
295 lines
6.9 KiB
import * as math from './math'; |
|
import * as ser from './serializer'; |
|
import * as storage from './storage'; |
|
import * as config from './config'; |
|
|
|
import { MESSAGE, SESSION, EVENT } from './enums'; |
|
import { sessions, desks } from './storage'; |
|
|
|
function event_size(event) { |
|
let size = 4 + 4; // type + user_id |
|
|
|
switch (event.type) { |
|
case EVENT.PREDRAW: |
|
case EVENT.MOVE_CURSOR: { |
|
size += 4 * 2; |
|
break; |
|
} |
|
|
|
case EVENT.MOVE_CANVAS: { |
|
size += 4 * 2 + 4; |
|
break; |
|
} |
|
|
|
case EVENT.ZOOM_CANVAS: { |
|
size += 4 + 4 * 2; |
|
break; |
|
} |
|
|
|
case EVENT.USER_JOINED: |
|
case EVENT.LEAVE: |
|
case EVENT.CLEAR: { |
|
break; |
|
} |
|
|
|
case EVENT.SET_COLOR: { |
|
size += 4; |
|
break; |
|
} |
|
|
|
case EVENT.SET_WIDTH: { |
|
size += 2; |
|
break; |
|
} |
|
|
|
case EVENT.STROKE: { |
|
size += 4 + 2 + 2 + 4; // stroke id + point count + width + color |
|
size += event.points.byteLength; |
|
size += math.round_to_pow2(event.pressures.byteLength, 4); |
|
break; |
|
} |
|
|
|
case EVENT.IMAGE: |
|
case EVENT.IMAGE_MOVE: { |
|
size += 4 + 4 + 4; // file id + x + y |
|
break; |
|
} |
|
|
|
case EVENT.UNDO: |
|
case EVENT.REDO: { |
|
break; |
|
} |
|
|
|
case EVENT.ERASER: { |
|
size += 4; // stroke id |
|
break; |
|
} |
|
|
|
default: { |
|
console.error(event.desk_id); |
|
console.error('fuck'); |
|
console.trace(); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
return size; |
|
} |
|
|
|
function create_session(ws, desk_id) { |
|
const session = { |
|
id: math.crypto_random32(), |
|
desk_id: desk_id, |
|
state: SESSION.OPENED, |
|
sn: 0, |
|
lsn: 0, |
|
ws: ws, |
|
color: 0x00, |
|
width: 8, |
|
}; |
|
|
|
storage.queries.insert_session.run({ |
|
'$id': session.id, |
|
'$desk_id': desk_id |
|
}); |
|
|
|
sessions[session.id] = session; |
|
|
|
return session; |
|
} |
|
|
|
export async function send_init(ws) { |
|
if (!ws) { |
|
return; |
|
} |
|
|
|
const session_id = ws.data.session_id; |
|
const desk_id = ws.data.desk_id; |
|
const desk = desks[desk_id]; |
|
|
|
let opcode = MESSAGE.INIT; |
|
let size = 4 + 4 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count |
|
let session = null; |
|
|
|
if (session_id in sessions && sessions[session_id].desk_id == desk_id) { |
|
session = sessions[session_id]; |
|
size += 4 + 2; // color + width |
|
} else { |
|
size += 4; // session id |
|
opcode = MESSAGE.JOIN; |
|
session = create_session(ws, desk_id); |
|
ws.data.session_id = session.id; |
|
} |
|
|
|
session.desk_id = desk_id; |
|
session.ws = ws; |
|
session.sn = 0; // Always re-send everything on reconnect |
|
session.state = SESSION.OPENED; |
|
|
|
if (config.DEBUG_PRINT) console.log(`session ${session.id} opened`); |
|
|
|
let user_count = 0; |
|
|
|
for (const sid in sessions) { |
|
const other_session = sessions[sid]; |
|
if (other_session.id !== session_id && other_session.desk_id === desk_id) { |
|
++user_count; |
|
size += 4 + 4 + 2 + 1; // user id + color + width + online |
|
} |
|
} |
|
|
|
for (const event of desk.events) { |
|
size += event_size(event); |
|
} |
|
|
|
const s = ser.create(size); |
|
|
|
ser.u32(s, opcode); |
|
ser.u32(s, session.lsn); |
|
|
|
if (opcode === MESSAGE.JOIN) { |
|
ser.u32(s, session.id); |
|
} else { |
|
ser.u32(s, session.color); |
|
ser.u16(s, session.width); |
|
} |
|
|
|
ser.u32(s, desk.events.length); |
|
ser.u32(s, user_count); |
|
ser.u32(s, desk.total_points); |
|
|
|
for (const sid in sessions) { |
|
const other_session = sessions[sid]; |
|
if (other_session.id !== session_id && other_session.desk_id === desk_id) { |
|
// console.log(other_session.id, other_session.color, other_session.width); |
|
ser.u32(s, other_session.id); |
|
ser.u32(s, other_session.color); |
|
ser.u16(s, other_session.width); |
|
ser.u8(s, other_session.state === SESSION.READY); |
|
} |
|
} |
|
|
|
ser.align(s, 4); |
|
|
|
for (const event of desk.events) { |
|
ser.event(s, event); |
|
} |
|
|
|
await ws.send(s.buffer); |
|
} |
|
|
|
export function send_ack(ws, lsn) { |
|
if (!ws) { |
|
return; |
|
} |
|
|
|
const size = 4 + 4; // opcode + lsn |
|
const s = ser.create(size); |
|
|
|
ser.u32(s, MESSAGE.ACK); |
|
ser.u32(s, lsn); |
|
|
|
if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`); |
|
|
|
ws.send(s.buffer); |
|
} |
|
|
|
function send_fire(ws, event) { |
|
if (!ws) { |
|
return; |
|
} |
|
|
|
const s = ser.create(4 + 4 + event_size(event)); |
|
|
|
ser.u32(s, MESSAGE.FIRE); |
|
ser.event(s, event); |
|
|
|
ws.send(s.buffer); |
|
} |
|
|
|
export function fire_event(from_session, event) { |
|
for (const sid in sessions) { |
|
const other = sessions[sid]; |
|
|
|
if (other.id === from_session.id) { |
|
continue; |
|
} |
|
|
|
if (other.state !== SESSION.READY) { |
|
continue; |
|
} |
|
|
|
if (other.desk_id != from_session.desk_id) { |
|
continue; |
|
} |
|
|
|
if (event.type === EVENT.MOVE_CANVAS && other.follow !== from_session.id) { |
|
// Do not spam canvas move events to those who don't follow us |
|
continue; |
|
} |
|
|
|
send_fire(other.ws, event); |
|
} |
|
} |
|
|
|
async function sync_session(session_id) { |
|
if (!(session_id in sessions)) { |
|
return; |
|
} |
|
|
|
const session = sessions[session_id]; |
|
const desk = desks[session.desk_id]; |
|
|
|
if (!session.ws) { |
|
return; |
|
} |
|
|
|
if (session.state !== SESSION.READY) { |
|
return; |
|
} |
|
|
|
let size = 4 + 4 + 4; // opcode + sn + event count |
|
let count = desk.sn - session.sn; |
|
|
|
if (count === 0) { |
|
if (config.DEBUG_PRINT) console.log('client ACKed all events'); |
|
return; |
|
} |
|
|
|
for (let i = count - 1; i >= 0; --i) { |
|
const event = desk.events[desk.events.length - 1 - i]; |
|
size += event_size(event); |
|
} |
|
|
|
const s = ser.create(size); |
|
|
|
ser.u32(s, MESSAGE.SYN); |
|
ser.u32(s, desk.sn); |
|
ser.u32(s, count); |
|
|
|
for (let i = count - 1; i >= 0; --i) { |
|
const event = desk.events[desk.events.length - 1 - i]; |
|
ser.event(s, event); |
|
} |
|
|
|
if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`); |
|
|
|
await session.ws.send(s.buffer); |
|
|
|
if (session.sync_attempts < config.SYNC_MAX_ATTEMPTS) { |
|
session.sync_attempts += 1; |
|
session.sync_timer = setTimeout(() => sync_session(session_id), config.SYNC_TIMEOUT); |
|
} |
|
} |
|
|
|
export function sync_desk(desk_id) { |
|
for (const sid in sessions) { |
|
const session = sessions[sid]; |
|
if (session.state === SESSION.READY && session.desk_id == desk_id) { // NOT ===, because might be string or int IDK |
|
if (session.sync_timer) { |
|
clearTimeout(session.sync_timer); |
|
} |
|
sync_session(sid); |
|
} |
|
} |
|
}
|
|
|