diff --git a/server/config.js b/server/config.js index 885c236..1ce0326 100644 --- a/server/config.js +++ b/server/config.js @@ -2,4 +2,5 @@ export const HOST = '127.0.0.1'; export const PORT = 3003; export const DATADIR = 'data'; export const SYNC_TIMEOUT = 1000; -export const IMAGEDIR = 'images'; \ No newline at end of file +export const IMAGEDIR = 'images'; +export const DEBUG_PRINT = true; \ No newline at end of file diff --git a/server/enums.js b/server/enums.js index 431c146..60da7d5 100644 --- a/server/enums.js +++ b/server/enums.js @@ -23,9 +23,4 @@ export const MESSAGE = Object.freeze({ FULL: 103, FIRE: 104, JOIN: 105, -}); - -export const SNS = Object.freeze({ - DESK: 1, - SESSION: 2, }); \ No newline at end of file diff --git a/server/http.js b/server/http.js index aae6e27..4197083 100644 --- a/server/http.js +++ b/server/http.js @@ -7,13 +7,11 @@ export async function route(req) { if (url.pathname === '/api/image') { const desk_id = url.searchParams.get('deskId') || '0'; - const formData = await req.formData(); - const file = formData.get('file'); + const formdata = await req.formData(); + const file = formdata.get('file'); const image_id = math.fast_random32(); - await Bun.write(config.IMAGEDIR + '/' + image_id, file); - - storage.put_image(image_id, desk_id); + Bun.write(config.IMAGEDIR + '/' + image_id, file); return new Response(image_id); } else if (url.pathname === '/api/ping') { diff --git a/server/recv.js b/server/recv.js index f114365..5cb9362 100644 --- a/server/recv.js +++ b/server/recv.js @@ -2,9 +2,10 @@ import * as des from './deserializer'; import * as send from './send'; import * as math from './math'; import * as storage from './storage'; +import * as config from './config'; import { SESSION, MESSAGE, EVENT } from './enums'; -import { sessions, desks } from './storage'; +import { sessions, desks, queries } from './storage'; // Session ACKed events up to SN function recv_ack(d, session) { @@ -13,45 +14,20 @@ function recv_ack(d, session) { session.state = SESSION.READY; session.sn = sn; - console.log(`ack ${sn} in`); -} - -function handle_event(session, event) { - switch (event.type) { - case EVENT.STROKE: { - event.stroke_id = math.fast_random32(); - storage.put_stroke(event.stroke_id, session.desk_id, event.points, event.width, event.color); - storage.put_event(event); - break; - } - - case EVENT.ERASER: - case EVENT.IMAGE: - case EVENT.IMAGE_MOVE: - case EVENT.UNDO: { - storage.put_event(event); - break; - } - - default: { - console.error('fuck'); - console.trace(); - process.exit(1); - } - } + if (config.DEBUG_PRINT) console.log(`ack ${sn} in`); } async function recv_syn(d, session) { const lsn = des.u32(d); const count = des.u32(d); - console.log(`syn ${lsn} in, total size = ${d.size}`); + if (config.DEBUG_PRINT) console.log(`syn ${lsn} in, total size = ${d.size}`); const we_expect = lsn - session.lsn; const first = count - we_expect; const events = []; - console.log(`we expect ${we_expect}, count ${count}`); + if (config.DEBUG_PRINT) console.log(`we expect ${we_expect}, count ${count}`); for (let i = 0; i < count; ++i) { const event = des.event(d); @@ -67,8 +43,15 @@ async function recv_syn(d, session) { desks[session.desk_id].events.push(...events); session.lsn = lsn; - storage.save_desk_sn(session.desk_id, desks[session.desk_id].sn); - storage.save_session_lsn(session.id, lsn); + storage.queries.update_desk_sn.run({ + '$id': session.desk_id, + '$sn': desks[session.desk_id].sn + }); + + storage.queries.update_session_lsn.run({ + '$id': session.id, + '$lsn': lsn + }); send.send_ack(session.ws, lsn); send.sync_desk(session.desk_id); @@ -79,6 +62,30 @@ function recv_fire(d, session) { event.user_id = session.user_id; + switch (event.type) { + case EVENT.SET_COLOR: { + session.color = event.color; + + storage.queries.update_session_color.run({ + '$id': session.id, + '$color': event.color + }); + + break; + } + + case EVENT.SET_WIDTH: { + session.width = event.width; + + storage.queries.update_session_width.run({ + '$id': session.id, + 'width': event.width + }); + + break; + } + } + for (const sid in sessions) { const other = sessions[sid]; @@ -98,6 +105,56 @@ function recv_fire(d, session) { } } +function handle_event(session, event) { + switch (event.type) { + case EVENT.STROKE: { + event.stroke_id = math.fast_random32(); + + storage.queries.insert_stroke.run({ + '$id': event.stroke_id, + '$width': event.width, + '$color': event.color, + '$points': event.points + }); + + storage.queries.insert_event.run({ + '$type': event.type, + '$desk_id': session.desk_id, + '$session_id': session.id, + '$stroke_id': event.stroke_id, + '$image_id': 0, + '$x': 0, + '$y': 0, + }); + + break; + } + + case EVENT.ERASER: + case EVENT.IMAGE: + case EVENT.IMAGE_MOVE: + case EVENT.UNDO: { + storage.queries.insert_event.run({ + '$type': event.type, + '$desk_id': session.desk_id, + '$session_id': session.id, + '$stroke_id': event.stroke_id || 0, + '$image_id': event.image_id || 0, + '$x': event.x || 0, + '$y': event.y || 0, + }); + + break; + } + + default: { + console.error('fuck'); + console.trace(); + process.exit(1); + } + } +} + export async function handle_message(ws, d) { if (!(ws.data.session_id in sessions)) { return; diff --git a/server/send.js b/server/send.js index dee83ed..f76edcd 100644 --- a/server/send.js +++ b/server/send.js @@ -71,10 +71,14 @@ function create_session(ws, desk_id) { sn: 0, lsn: 0, ws: ws, + color: 0x00, + width: 8, }; - storage.create_user(user); - storage.create_session(session); + storage.queries.insert_session.run({ + '$id': session.id, + '$desk_id': desk_id + }); sessions[session.id] = session; @@ -91,7 +95,7 @@ export async function send_init(ws) { const desk = desks[desk_id]; let opcode = MESSAGE.INIT; - let size = 1 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + let size = 1 + 4 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count let session = null; if (session_id in sessions && sessions[session_id].desk_id == desk_id) { @@ -107,8 +111,20 @@ export async function send_init(ws) { session.ws = ws; session.sn = 0; // Always re-send everything on reconnect session.state = SESSION.OPENED; + session.color = 0x00; + session.width = 8; - console.log(`session ${session.id} 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; // user id + color + width + } + } for (const event of desk.events) { size += event_size(event); @@ -125,6 +141,17 @@ export async function send_init(ws) { } ser.u32(s, desk.events.length); + ser.u32(s, user_count); + + 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); + } + } for (const event of desk.events) { ser.event(s, event); @@ -144,7 +171,7 @@ export function send_ack(ws, lsn) { ser.u8(s, MESSAGE.ACK); ser.u32(s, lsn); - console.log(`ack ${lsn} out`); + if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`); ws.send(s.buffer); } @@ -182,7 +209,7 @@ async function sync_session(session_id) { let count = desk.sn - session.sn; if (count === 0) { - console.log('client ACKed all events'); + if (config.DEBUG_PRINT) console.log('client ACKed all events'); return; } @@ -202,7 +229,7 @@ async function sync_session(session_id) { ser.event(s, event); } - console.debug(`syn ${desk.sn} out`); + if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`); await session.ws.send(s.buffer); diff --git a/server/server.js b/server/server.js index 54a6df2..a685e80 100644 --- a/server/server.js +++ b/server/server.js @@ -1,13 +1,11 @@ import * as config from './config'; import * as storage from './storage'; import * as http_server from './http'; -import * as math from './math'; -import * as ser from './serializer'; import * as des from './deserializer'; import * as send from './send'; import * as recv from './recv'; -import { MESSAGE, EVENT, SESSION, SNS } from './enums'; +import { MESSAGE, EVENT, SESSION } from './enums'; import { sessions, desks } from './storage'; export function startup() { @@ -29,7 +27,11 @@ export function startup() { events: [], }; - storage.create_desk(desk_id); + storage.queries.insert_desk.run({ + '$id': desk_id, + '$title': `Desk ${desk_id}` + }); + desks[desk_id] = desk; } diff --git a/server/storage.js b/server/storage.js index b04a316..14086ac 100644 --- a/server/storage.js +++ b/server/storage.js @@ -3,11 +3,12 @@ import * as sqlite from 'bun:sqlite'; import { EVENT, SESSION } from './enums'; +// In-memory views export const sessions = {}; export const desks = {}; +export const queries = {}; let db = null; -const queries = {}; export function startup() { const path = `${config.DATADIR}/db.sqlite`; @@ -20,31 +21,12 @@ export function startup() { title TEXT );`).run(); - db.query(`CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - login TEXT - );`).run(); - db.query(`CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY, - user_id INTEGER, desk_id INTEGER, lsn INTEGER, - - FOREIGN KEY (user_id) - REFERENCES users (id) - ON DELETE CASCADE - ON UPDATE NO ACTION, - - FOREIGN KEY (desk_id) - REFERENCES desks (id) - ON DELETE CASCADE - ON UPDATE NO ACTION - );`).run(); - - db.query(`CREATE TABLE IF NOT EXISTS images ( - id INTEGER PRIMARY KEY, - desk_id INTEGER, + color INTEGER, + width INTEGER, FOREIGN KEY (desk_id) REFERENCES desks (id) @@ -54,22 +36,16 @@ export function startup() { db.query(`CREATE TABLE IF NOT EXISTS strokes ( id INTEGER PRIMARY KEY, - desk_id INTEGER, - points BLOB, width INTEGER, color INTEGER, - - FOREIGN KEY (desk_id) - REFERENCES desks (id) - ON DELETE CASCADE - ON UPDATE NO ACTION + points BLOB );`).run(); db.query(`CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY, type INTEGER, desk_id INTEGER, - user_id INTEGER, + session_id INTEGER, stroke_id INTEGER, image_id INTEGER, x INTEGER, @@ -80,71 +56,52 @@ export function startup() { ON DELETE CASCADE ON UPDATE NO ACTION - FOREIGN KEY (user_id) - REFERENCES users (id) - ON DELETE CASCADE + FOREIGN KEY (session_id) + REFERENCES sessions (id) + ON DELETE NO ACTION ON UPDATE NO ACTION FOREIGN KEY (stroke_id) REFERENCES strokes (id) ON DELETE CASCADE ON UPDATE NO ACTION - - FOREIGN KEY (image_id) - REFERENCES images (id) - ON DELETE CASCADE - ON UPDATE NO ACTION );`).run(); - db.query(`CREATE INDEX IF NOT EXISTS idx_events_desk_id - ON events (desk_id); - `).run(); + // INSERT + queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0)'); + queries.insert_stroke = db.query('INSERT INTO strokes (id, width, color, points) VALUES ($id, $width, $color, $points)'); + queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0)'); + queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y)'); - db.query(`CREATE INDEX IF NOT EXISTS idx_strokes_desk_id - ON strokes (desk_id); - `).run(); + // UPDATE + queries.update_desk_sn = db.query('UPDATE desks SET sn = $sn WHERE id = $id'); + queries.update_session_lsn = db.query('UPDATE sessions SET lsn = $lsn WHERE id = $id'); + queries.update_session_color = db.query('UPDATE sessions SET color = $color WHERE id = $id'); + queries.update_session_width = db.query('UPDATE sessions SET width = $width WHERE id = $id'); const res1 = db.query('SELECT COUNT(id) as count FROM desks').get(); const res2 = db.query('SELECT COUNT(id) as count FROM events').get(); const res3 = db.query('SELECT COUNT(id) as count FROM strokes').get(); - const res4 = db.query('SELECT COUNT(id) as count FROM users').get(); - const res5 = db.query('SELECT COUNT(id) as count FROM sessions').get(); - const res6 = db.query('SELECT COUNT(id) as count FROM images').get(); - - queries.desks = db.query('SELECT id, sn FROM desks'); - queries.events = db.query('SELECT * FROM events'); - queries.sessions = db.query('SELECT id, lsn, user_id, desk_id FROM sessions'); - queries.strokes = db.query('SELECT * FROM strokes'); - queries.empty_desk = db.query('INSERT INTO desks (id, title, sn) VALUES (?1, ?2, 0)'); - queries.desk_strokes = db.query('SELECT id, points FROM strokes WHERE desk_id = ?1'); - queries.put_desk_stroke = db.query('INSERT INTO strokes (id, desk_id, points, width, color) VALUES (?1, ?2, ?3, ?4, ?5)'); - queries.clear_desk_events = db.query('DELETE FROM events WHERE desk_id = ?1'); - queries.set_desk_sn = db.query('UPDATE desks SET sn = ?1 WHERE id = ?2'); - queries.save_session_lsn = db.query('UPDATE sessions SET lsn = ?1 WHERE id = ?2'); - queries.create_session = db.query('INSERT INTO sessions (id, lsn, user_id, desk_id) VALUES (?1, 0, ?2, ?3)'); - queries.create_user = db.query('INSERT INTO users (id, login) VALUES (?1, ?2)'); - queries.put_event = db.query('INSERT INTO events (type, desk_id, user_id, stroke_id, image_id, x, y) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)'); - queries.put_image = db.query('INSERT INTO images (id, desk_id) VALUES (?1, ?2)'); + const res4 = db.query('SELECT COUNT(id) as count FROM sessions').get(); console.log(`Storing data in ${path}`); console.log(`Entity count at startup: ${res1.count} desks ${res2.count} events ${res3.count} strokes - ${res4.count} users - ${res5.count} sessions - ${res6.count} images` - ); + ${res4.count} sessions + `); - const stored_desks = get_desks(); - const stored_events = get_events(); - const stored_strokes = get_strokes(); - const stored_sessions = get_sessions(); + // Init in-memory view: merge strokes into events, set all sessions to closed + const stored_desks = db.query('SELECT * FROM desks').all(); + const stored_events = db.query('SELECT * FROM events').all(); + const stored_strokes = db.query('SELECT * FROM strokes').all(); + const stored_sessions = db.query('SELECT * FROM sessions').all(); const stroke_dict = {}; for (const stroke of stored_strokes) { - stroke.points = new Uint16Array(stroke.points.buffer); + stroke.points = new Float32Array(stroke.points.buffer); stroke_dict[stroke.id] = stroke; } @@ -161,7 +118,6 @@ export function startup() { event.width = stroke.width; } - desks[event.desk_id].events.push(event); } @@ -170,60 +126,4 @@ export function startup() { session.ws = null; sessions[session.id] = session; } -} - -export function get_strokes() { - return queries.strokes.all(); -} - -export function get_sessions() { - return queries.sessions.all(); -} - -export function get_desks() { - return queries.desks.all(); -} - -export function get_events() { - return queries.events.all(); -} - -export function get_desk_strokes(desk_id) { - return queries.desk_strokes.all(desk_id); -} - -export function put_event(event) { - return queries.put_event.get(event.type, event.desk_id || 0, event.user_id || 0, event.stroke_id || 0, event.image_id || 0, event.x || 0, event.y || 0); -} - -export function put_stroke(stroke_id, desk_id, points, width, color) { - return queries.put_desk_stroke.get(stroke_id, desk_id, new Uint8Array(points.buffer, points.byteOffset, points.byteLength), width, color); -} - -export function clear_events(desk_id) { - return queries.clear_desk_events.get(desk_id); -} - -export function create_desk(desk_id, title = 'untitled') { - return queries.empty_desk.get(desk_id, title); -} - -export function save_desk_sn(desk_id, sn) { - return queries.set_desk_sn.get(sn, desk_id); -} - -export function create_session(session) { - return queries.create_session.get(session.id, session.user_id, session.desk_id); -} - -export function create_user(user) { - return queries.create_user.get(user.id, user.login); -} - -export function save_session_lsn(session_id, lsn) { - return queries.save_session_lsn.get(lsn, session_id); -} - -export function put_image(image_id, desk_id) { - return queries.put_image.get(image_id, desk_id); } \ No newline at end of file