Browse Source

Per-user stroke width and color (for dynamic strokes) kinda work

infinite
A.Olokhtonov 2 years ago
parent
commit
cb783db614
  1. 47
      client/client_recv.js
  2. 24
      client/client_send.js
  3. 22
      client/index.html
  4. 19
      client/tools.js
  5. 19
      client/webgl.js
  6. 34
      client/webgl_geometry.js
  7. 8
      client/webgl_listeners.js
  8. 10
      server/deserializer.js
  9. 2
      server/enums.js
  10. 4
      server/recv.js
  11. 13
      server/send.js
  12. 10
      server/serializer.js
  13. 21
      server/texput.log

47
client/client_recv.js

@ -57,6 +57,16 @@ function des_event(d) {
break; break;
} }
case EVENT.SET_COLOR: {
event.color = des_u32(d);
break;
}
case EVENT.SET_WIDTH: {
event.width = des_u16(d);
break;
}
case EVENT.STROKE: { case EVENT.STROKE: {
const stroke_id = des_u32(d); const stroke_id = des_u32(d);
const point_count = des_u16(d); const point_count = des_u16(d);
@ -116,12 +126,39 @@ function bitmap_bbox(event) {
return bbox; 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) { function handle_event(state, context, event, relax = false) {
if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`); if (config.debug_print) console.debug(`event type ${event.type} from user ${event.user_id}`);
let need_draw = false; let need_draw = false;
if (!(event.user_id in state.players)) {
init_player_defaults(state, event.user_id);
}
switch (event.type) { 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: { case EVENT.STROKE: {
if (event.user_id != state.me) { if (event.user_id != state.me) {
clear_dynamic_stroke(state, context, event.user_id); 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.me = des_u32(d);
state.server_lsn = des_u32(d); state.server_lsn = des_u32(d);
init_player_defaults(state, state.me);
if (state.server_lsn > state.lsn) { if (state.server_lsn > state.lsn) {
// Server knows something that we don't // Server knows something that we don't
state.lsn = state.server_lsn; state.lsn = state.server_lsn;
@ -298,12 +337,10 @@ async function handle_message(state, context, d) {
} }
case MESSAGE.FIRE: { case MESSAGE.FIRE: {
const user_id = des_u32(d); const event = des_event(d);
const predraw_event = des_event(d); const need_draw = handle_event(state, context, event);
update_dynamic_stroke(state, context, user_id, {'x': predraw_event.x, 'y': predraw_event.y});
do_draw = true; do_draw = do_draw || need_draw;
break; break;
} }

24
client/client_send.js

@ -45,6 +45,16 @@ function ser_event(s, event) {
break; break;
} }
case EVENT.SET_COLOR: {
ser_u32(s, event.color);
break;
}
case EVENT.SET_WIDTH: {
ser_u16(s, event.width);
break;
}
case EVENT.STROKE: { case EVENT.STROKE: {
ser_u16(s, event.points.length); ser_u16(s, event.points.length);
ser_u16(s, event.width); 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) { function stroke_event(state) {
return { return {
'type': EVENT.STROKE, 'type': EVENT.STROKE,

22
client/index.html

@ -6,19 +6,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="icons/favicon.svg" id="favicon"> <link rel="shortcut icon" href="icons/favicon.svg" id="favicon">
<link rel="stylesheet" type="text/css" href="default.css?v=14"> <link rel="stylesheet" type="text/css" href="default.css?v=17">
<script type="text/javascript" src="math.js?v=14"></script> <script type="text/javascript" src="math.js?v=17"></script>
<script type="text/javascript" src="aux.js?v=14"></script> <script type="text/javascript" src="aux.js?v=17"></script>
<script type="text/javascript" src="tools.js?v=14"></script> <script type="text/javascript" src="tools.js?v=17"></script>
<script type="text/javascript" src="webgl_geometry.js?v=14"></script> <script type="text/javascript" src="webgl_geometry.js?v=17"></script>
<script type="text/javascript" src="webgl_shaders.js?v=14"></script> <script type="text/javascript" src="webgl_shaders.js?v=17"></script>
<script type="text/javascript" src="webgl_listeners.js?v=14"></script> <script type="text/javascript" src="webgl_listeners.js?v=17"></script>
<script type="text/javascript" src="webgl.js?v=14"></script> <script type="text/javascript" src="webgl.js?v=17"></script>
<script type="text/javascript" src="client_send.js?v=14"></script> <script type="text/javascript" src="client_send.js?v=17"></script>
<script type="text/javascript" src="client_recv.js?v=14"></script> <script type="text/javascript" src="client_recv.js?v=17"></script>
<script type="text/javascript" src="websocket.js?v=14"></script> <script type="text/javascript" src="websocket.js?v=17"></script>
</head> </head>
<body> <body>
<canvas id="c"></canvas> <canvas id="c"></canvas>

19
client/tools.js

@ -17,7 +17,12 @@ function switch_color(state, item) {
state.colors.active_element.classList.remove('active'); 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 = item;
state.colors.active_element.classList.add('active'); 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.width = size * state.canvas.zoom + 'px';
preview.style.height = 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'); preview.classList.remove('dhide');
} }
@ -38,7 +43,7 @@ function hide_stroke_preview() {
function switch_stroke_width(e, state) { function switch_stroke_width(e, state) {
const value = e.target.value; const value = e.target.value;
state.stroke_width = value; state.players[state.me].width = value;
show_stroke_preview(state, value); show_stroke_preview(state, value);
if (state.hide_preview) { 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); 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) { function init_tools(state) {
const tools = document.querySelectorAll('.tools .tool'); const tools = document.querySelectorAll('.tools .tool');
const colors = document.querySelectorAll('.pallete .color'); const colors = document.querySelectorAll('.pallete .color');
@ -61,8 +71,9 @@ function init_tools(state) {
const slider = document.querySelector('#stroke-width'); 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('input', (e) => switch_stroke_width(e, state));
slider.addEventListener('change', (e) => broadcast_stroke_width(e, state));
document.querySelector('.phone-extra-controls').addEventListener('click', zenmode); document.querySelector('.phone-extra-controls').addEventListener('click', zenmode);
} }

19
client/webgl.js

@ -90,10 +90,16 @@ const config = {
debug_print: true, debug_print: true,
min_zoom: 0.01, min_zoom: 0.01,
max_zoom: 100.0, max_zoom: 100.0,
default_color: 0x00,
default_width: 8,
}; };
const EVENT = Object.freeze({ const EVENT = Object.freeze({
PREDRAW: 10, PREDRAW: 10,
SET_COLOR: 11,
SET_WIDTH: 12,
STROKE: 20, STROKE: 20,
RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */
UNDO: 30, UNDO: 30,
@ -121,6 +127,16 @@ function event_size(event) {
break; break;
} }
case EVENT.SET_COLOR: {
size += 4;
break;
}
case EVENT.SET_WIDTH: {
size += 2;
break;
}
case EVENT.STROKE: { 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 size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points
break; break;
@ -168,8 +184,6 @@ function main() {
'lsn': 0, 'lsn': 0,
'server_lsn': 0, 'server_lsn': 0,
'stroke_width': 8,
'touch': { 'touch': {
'moves': 0, 'moves': 0,
'drawing': false, 'drawing': false,
@ -197,7 +211,6 @@ function main() {
}, },
'colors': { 'colors': {
'active': null,
'active_element': null, 'active_element': null,
}, },

34
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_positions_f32 = new Float32Array(total_dynamic_length);
context.dynamic_colors_u8 = new Uint8Array(total_dynamic_length / 2 * 3); 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; let at = 0;
for (const player_id in context.dynamic_positions) { for (const player_id in context.dynamic_positions) {
context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at); context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at);
if (parseInt(player_id) === state.me) { const color_u32 = state.players[player_id].color;
const color_u32 = state.colors.active;
const r = (color_u32 >> 16) & 0xFF; const r = (color_u32 >> 16) & 0xFF;
const g = (color_u32 >> 8) & 0xFF; const g = (color_u32 >> 8) & 0xFF;
const b = color_u32 & 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; for (let i = 0; i < context.dynamic_positions[player_id].length; ++i) {
context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 1] = g; context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 0] = r;
context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b; 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; 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)) { if (!(player_id in state.current_strokes)) {
state.current_strokes[player_id] = { state.current_strokes[player_id] = {
'points': [], 'points': [],
'width': state.stroke_width, 'width': state.players[player_id].width,
'color': state.colors.active, 'color': state.players[player_id].color,
}; };
context.dynamic_positions[player_id] = []; context.dynamic_positions[player_id] = [];
context.dynamic_colors[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 // TODO: incremental
context.dynamic_positions[player_id].length = 0; context.dynamic_positions[player_id].length = 0;
context.dynamic_colors[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) { function clear_dynamic_stroke(state, context, player_id) {
if (player_id in state.current_strokes) { if (player_id in state.current_strokes) {
state.current_strokes[player_id].points.length = 0; state.current_strokes[player_id].points.length = 0;
state.current_strokes[player_id].color = state.colors.active; state.current_strokes[player_id].color = state.players[state.me].color;
state.current_strokes[player_id].width = state.stroke_width; state.current_strokes[player_id].width = state.players[state.me].width;
context.dynamic_positions[player_id].length = 0; context.dynamic_positions[player_id].length = 0;
recompute_dynamic_data(state, context); recompute_dynamic_data(state, context);
} }

8
client/webgl_listeners.js

@ -108,8 +108,8 @@ function mouseup(e, state, context) {
if (state.drawing) { if (state.drawing) {
const stroke = { const stroke = {
'color': state.colors.active, 'color': state.players[state.me].color,
'width': state.stroke_width, 'width': state.players[state.me].width,
'points': process_stroke(state.current_strokes[state.me].points), 'points': process_stroke(state.current_strokes[state.me].points),
'user_id': state.me, 'user_id': state.me,
}; };
@ -321,8 +321,8 @@ function touchend(e, state, context) {
// await queue_event(event); // await queue_event(event);
const stroke = { const stroke = {
'color': state.colors.active, 'color': state.players[state.me].color,
'width': state.stroke_width, 'width': state.players[state.me].width,
'points': process_stroke(state.current_strokes[state.me].points), 'points': process_stroke(state.current_strokes[state.me].points),
'user_id': state.me, 'user_id': state.me,
}; };

10
server/deserializer.js

@ -56,6 +56,16 @@ export function event(d) {
break; break;
} }
case EVENT.SET_COLOR: {
event.color = u32(d);
break;
}
case EVENT.SET_WIDTH: {
event.width = u16(d);
break;
}
case EVENT.STROKE: { case EVENT.STROKE: {
// point_count + width align to 4 bytes :D // point_count + width align to 4 bytes :D
const point_count = u16(d); const point_count = u16(d);

2
server/enums.js

@ -6,6 +6,8 @@ export const SESSION = Object.freeze({
export const EVENT = Object.freeze({ export const EVENT = Object.freeze({
PREDRAW: 10, PREDRAW: 10,
SET_COLOR: 11,
SET_WIDTH: 12,
STROKE: 20, STROKE: 20,
UNDO: 30, UNDO: 30,
REDO: 31, REDO: 31,

4
server/recv.js

@ -77,6 +77,8 @@ async function recv_syn(d, session) {
function recv_fire(d, session) { function recv_fire(d, session) {
const event = des.event(d); const event = des.event(d);
event.user_id = session.user_id;
for (const sid in sessions) { for (const sid in sessions) {
const other = sessions[sid]; const other = sessions[sid];
@ -92,7 +94,7 @@ function recv_fire(d, session) {
continue; continue;
} }
send.send_fire(other.ws, session.user_id, event); send.send_fire(other.ws, event);
} }
} }

13
server/send.js

@ -15,6 +15,16 @@ function event_size(event) {
break; break;
} }
case EVENT.SET_COLOR: {
size += 4;
break;
}
case EVENT.SET_WIDTH: {
size += 2;
break;
}
case EVENT.STROKE: { case EVENT.STROKE: {
size += 4 + 2 + 2 + 4; // stroke id + point count + width + color size += 4 + 2 + 2 + 4; // stroke id + point count + width + color
size += event.points.byteLength; size += event.points.byteLength;
@ -139,7 +149,7 @@ export function send_ack(ws, lsn) {
ws.send(s.buffer); ws.send(s.buffer);
} }
export function send_fire(ws, user_id, event) { export function send_fire(ws, event) {
if (!ws) { if (!ws) {
return; return;
} }
@ -147,7 +157,6 @@ export function send_fire(ws, user_id, event) {
const s = ser.create(1 + 4 + event_size(event)); const s = ser.create(1 + 4 + event_size(event));
ser.u8(s, MESSAGE.FIRE); ser.u8(s, MESSAGE.FIRE);
ser.u32(s, user_id);
ser.event(s, event); ser.event(s, event);
ws.send(s.buffer); ws.send(s.buffer);

10
server/serializer.js

@ -47,6 +47,16 @@ export function event(s, event) {
break; break;
} }
case EVENT.SET_COLOR: {
u32(s, event.color);
break;
}
case EVENT.SET_WIDTH: {
u16(s, event.width);
break;
}
case EVENT.STROKE: { case EVENT.STROKE: {
const points_bytes = event.points; const points_bytes = event.points;
u32(s, event.stroke_id); u32(s, event.stroke_id);

21
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!
Loading…
Cancel
Save