From cb783db614ea684304acbb3566623e77d9f8ba44 Mon Sep 17 00:00:00 2001 From: "A.Olokhtonov" Date: Sat, 22 Apr 2023 23:24:27 +0300 Subject: [PATCH] Per-user stroke width and color (for dynamic strokes) kinda work --- client/client_recv.js | 47 ++++++++++++++++++++++++++++++++++----- client/client_send.js | 26 +++++++++++++++++++++- client/index.html | 22 +++++++++--------- client/tools.js | 19 ++++++++++++---- client/webgl.js | 19 +++++++++++++--- client/webgl_geometry.js | 34 ++++++++++++++-------------- client/webgl_listeners.js | 8 +++---- server/deserializer.js | 10 +++++++++ server/enums.js | 2 ++ server/recv.js | 4 +++- server/send.js | 13 +++++++++-- server/serializer.js | 10 +++++++++ server/texput.log | 21 +++++++++++++++++ 13 files changed, 187 insertions(+), 48 deletions(-) create mode 100644 server/texput.log diff --git a/client/client_recv.js b/client/client_recv.js index 04afcf3..4eed95d 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -57,6 +57,16 @@ function des_event(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); @@ -116,12 +126,39 @@ function bitmap_bbox(event) { return bbox; } +function init_player_defaults(state, player_id) { + state.players[player_id] = { + 'color': config.default_color, + 'width': config.default_width, + }; +} + function handle_event(state, context, event, relax = false) { 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.PREDRAW: { + update_dynamic_stroke(state, context, event.user_id, {'x': event.x, 'y': event.y}); + 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: { if (event.user_id != state.me) { clear_dynamic_stroke(state, context, event.user_id); @@ -262,6 +299,8 @@ async function handle_message(state, context, d) { state.me = des_u32(d); state.server_lsn = des_u32(d); + init_player_defaults(state, state.me); + if (state.server_lsn > state.lsn) { // Server knows something that we don't state.lsn = state.server_lsn; @@ -298,12 +337,10 @@ async function handle_message(state, context, d) { } case MESSAGE.FIRE: { - const user_id = des_u32(d); - const predraw_event = des_event(d); - - update_dynamic_stroke(state, context, user_id, {'x': predraw_event.x, 'y': predraw_event.y}); + const event = des_event(d); + const need_draw = handle_event(state, context, event); - do_draw = true; + do_draw = do_draw || need_draw; break; } diff --git a/client/client_send.js b/client/client_send.js index 91a4ea5..1083da4 100644 --- a/client/client_send.js +++ b/client/client_send.js @@ -45,6 +45,16 @@ function ser_event(s, event) { 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); @@ -217,6 +227,20 @@ function predraw_event(x, y) { }; } +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 stroke_event(state) { return { 'type': EVENT.STROKE, @@ -224,4 +248,4 @@ function stroke_event(state) { 'width': state.current_strokes[state.me].width, 'color': state.current_strokes[state.me].color, }; -} \ No newline at end of file +} diff --git a/client/index.html b/client/index.html index c43ce2f..097686c 100644 --- a/client/index.html +++ b/client/index.html @@ -6,19 +6,19 @@ - + - - - - - - - + + + + + + + - - - + + + diff --git a/client/tools.js b/client/tools.js index 0d01ab2..ac23a4d 100644 --- a/client/tools.js +++ b/client/tools.js @@ -17,7 +17,12 @@ function switch_color(state, item) { state.colors.active_element.classList.remove('active'); } - state.colors.active = color_to_u32(color); + if (state.me in state.players) { + const color_u32 = color_to_u32(color); + state.players[state.me].color = color_u32 + fire_event(color_event(color_u32)); + } + state.colors.active_element = item; state.colors.active_element.classList.add('active'); } @@ -27,7 +32,7 @@ function show_stroke_preview(state, size) { preview.style.width = size * state.canvas.zoom + 'px'; preview.style.height = size * state.canvas.zoom + 'px'; - preview.style.background = color_from_u32(state.colors.active); + preview.style.background = color_from_u32(state.players[state.me].color); preview.classList.remove('dhide'); } @@ -38,7 +43,7 @@ function hide_stroke_preview() { function switch_stroke_width(e, state) { const value = e.target.value; - state.stroke_width = value; + state.players[state.me].width = value; show_stroke_preview(state, value); if (state.hide_preview) { @@ -48,6 +53,11 @@ function switch_stroke_width(e, state) { state.hide_preview = setTimeout(hide_stroke_preview, config.brush_preview_timeout); } +function broadcast_stroke_width(e, state) { + const value = e.target.value; + fire_event(width_event(value)); +} + function init_tools(state) { const tools = document.querySelectorAll('.tools .tool'); const colors = document.querySelectorAll('.pallete .color'); @@ -61,8 +71,9 @@ function init_tools(state) { const slider = document.querySelector('#stroke-width'); - slider.value = state.stroke_width; + // slider.value = state.players[state.me].width; slider.addEventListener('input', (e) => switch_stroke_width(e, state)); + slider.addEventListener('change', (e) => broadcast_stroke_width(e, state)); document.querySelector('.phone-extra-controls').addEventListener('click', zenmode); } \ No newline at end of file diff --git a/client/webgl.js b/client/webgl.js index b975119..0485ce3 100644 --- a/client/webgl.js +++ b/client/webgl.js @@ -90,10 +90,16 @@ const config = { debug_print: true, min_zoom: 0.01, max_zoom: 100.0, + + default_color: 0x00, + default_width: 8, }; const EVENT = Object.freeze({ PREDRAW: 10, + SET_COLOR: 11, + SET_WIDTH: 12, + STROKE: 20, RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ UNDO: 30, @@ -121,6 +127,16 @@ function event_size(event) { break; } + case EVENT.SET_COLOR: { + size += 4; + break; + } + + case EVENT.SET_WIDTH: { + size += 2; + break; + } + case EVENT.STROKE: { size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points break; @@ -168,8 +184,6 @@ function main() { 'lsn': 0, 'server_lsn': 0, - 'stroke_width': 8, - 'touch': { 'moves': 0, 'drawing': false, @@ -197,7 +211,6 @@ function main() { }, 'colors': { - 'active': null, 'active_element': null, }, diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 6bf06a5..8d14afb 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -141,24 +141,21 @@ function recompute_dynamic_data(state, context) { context.dynamic_positions_f32 = new Float32Array(total_dynamic_length); context.dynamic_colors_u8 = new Uint8Array(total_dynamic_length / 2 * 3); - // TODO: preview stroke colors for other users - context.dynamic_colors_u8.fill(0); - let at = 0; for (const player_id in context.dynamic_positions) { context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at); + + const color_u32 = state.players[player_id].color; - if (parseInt(player_id) === state.me) { - const color_u32 = state.colors.active; - const r = (color_u32 >> 16) & 0xFF; - const g = (color_u32 >> 8) & 0xFF; - const b = color_u32 & 0xFF; - for (let i = 0; i < context.dynamic_positions[player_id].length; ++i) { - context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 0] = r; - context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 1] = g; - context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b; - } + const r = (color_u32 >> 16) & 0xFF; + const g = (color_u32 >> 8) & 0xFF; + const b = color_u32 & 0xFF; + + for (let i = 0; i < context.dynamic_positions[player_id].length; ++i) { + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 0] = r; + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 1] = g; + context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b; } at += context.dynamic_positions[player_id].length; @@ -169,14 +166,17 @@ function update_dynamic_stroke(state, context, player_id, point) { if (!(player_id in state.current_strokes)) { state.current_strokes[player_id] = { 'points': [], - 'width': state.stroke_width, - 'color': state.colors.active, + 'width': state.players[player_id].width, + 'color': state.players[player_id].color, }; context.dynamic_positions[player_id] = []; context.dynamic_colors[player_id] = []; } + state.current_strokes[player_id].color = state.players[player_id].color; + state.current_strokes[player_id].width = state.players[player_id].width; + // TODO: incremental context.dynamic_positions[player_id].length = 0; context.dynamic_colors[player_id].length = 0; @@ -190,8 +190,8 @@ function update_dynamic_stroke(state, context, player_id, point) { function clear_dynamic_stroke(state, context, player_id) { if (player_id in state.current_strokes) { state.current_strokes[player_id].points.length = 0; - state.current_strokes[player_id].color = state.colors.active; - state.current_strokes[player_id].width = state.stroke_width; + state.current_strokes[player_id].color = state.players[state.me].color; + state.current_strokes[player_id].width = state.players[state.me].width; context.dynamic_positions[player_id].length = 0; recompute_dynamic_data(state, context); } diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 5fcd94f..4638db8 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -108,8 +108,8 @@ function mouseup(e, state, context) { if (state.drawing) { const stroke = { - 'color': state.colors.active, - 'width': state.stroke_width, + 'color': state.players[state.me].color, + 'width': state.players[state.me].width, 'points': process_stroke(state.current_strokes[state.me].points), 'user_id': state.me, }; @@ -321,8 +321,8 @@ function touchend(e, state, context) { // await queue_event(event); const stroke = { - 'color': state.colors.active, - 'width': state.stroke_width, + 'color': state.players[state.me].color, + 'width': state.players[state.me].width, 'points': process_stroke(state.current_strokes[state.me].points), 'user_id': state.me, }; diff --git a/server/deserializer.js b/server/deserializer.js index 76461ce..b269417 100644 --- a/server/deserializer.js +++ b/server/deserializer.js @@ -56,6 +56,16 @@ export function event(d) { break; } + case EVENT.SET_COLOR: { + event.color = u32(d); + break; + } + + case EVENT.SET_WIDTH: { + event.width = u16(d); + break; + } + case EVENT.STROKE: { // point_count + width align to 4 bytes :D const point_count = u16(d); diff --git a/server/enums.js b/server/enums.js index 1fa3f20..431c146 100644 --- a/server/enums.js +++ b/server/enums.js @@ -6,6 +6,8 @@ export const SESSION = Object.freeze({ export const EVENT = Object.freeze({ PREDRAW: 10, + SET_COLOR: 11, + SET_WIDTH: 12, STROKE: 20, UNDO: 30, REDO: 31, diff --git a/server/recv.js b/server/recv.js index 6c0805a..f114365 100644 --- a/server/recv.js +++ b/server/recv.js @@ -77,6 +77,8 @@ async function recv_syn(d, session) { function recv_fire(d, session) { const event = des.event(d); + event.user_id = session.user_id; + for (const sid in sessions) { const other = sessions[sid]; @@ -92,7 +94,7 @@ function recv_fire(d, session) { continue; } - send.send_fire(other.ws, session.user_id, event); + send.send_fire(other.ws, event); } } diff --git a/server/send.js b/server/send.js index 5b31fa5..dee83ed 100644 --- a/server/send.js +++ b/server/send.js @@ -15,6 +15,16 @@ function event_size(event) { 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; @@ -139,7 +149,7 @@ export function send_ack(ws, lsn) { ws.send(s.buffer); } -export function send_fire(ws, user_id, event) { +export function send_fire(ws, event) { if (!ws) { return; } @@ -147,7 +157,6 @@ export function send_fire(ws, user_id, event) { const s = ser.create(1 + 4 + event_size(event)); ser.u8(s, MESSAGE.FIRE); - ser.u32(s, user_id); ser.event(s, event); ws.send(s.buffer); diff --git a/server/serializer.js b/server/serializer.js index afee7c0..5f4099c 100644 --- a/server/serializer.js +++ b/server/serializer.js @@ -47,6 +47,16 @@ export function event(s, event) { break; } + case EVENT.SET_COLOR: { + u32(s, event.color); + break; + } + + case EVENT.SET_WIDTH: { + u16(s, event.width); + break; + } + case EVENT.STROKE: { const points_bytes = event.points; u32(s, event.stroke_id); diff --git a/server/texput.log b/server/texput.log new file mode 100644 index 0000000..cbc8c3d --- /dev/null +++ b/server/texput.log @@ -0,0 +1,21 @@ +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022/Debian) (preloaded format=pdflatex 2023.4.13) 16 APR 2023 21:20 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +** + +! Emergency stop. +<*> + +End of file on the terminal! + + +Here is how much of TeX's memory you used: + 3 strings out of 476091 + 111 string characters out of 5794081 + 1849330 words of memory out of 5000000 + 20488 multiletter control sequences out of 15000+600000 + 512287 words of font info for 32 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 0i,0n,0p,1b,6s stack positions out of 10000i,1000n,20000p,200000b,200000s +! ==> Fatal error occurred, no output PDF file produced!