Browse Source

The big unAWAITing! Also found the image scale undo bug, but haven't

fixed yet
sdf
A.Olokhtonov 2 months ago
parent
commit
8bea9593da
  1. 2
      client/aux.js
  2. 70
      client/client_recv.js
  3. 16
      client/client_send.js
  4. 35
      client/config.js
  5. 3
      client/index.html
  6. 39
      client/index.js
  7. 103
      client/undo.js
  8. 10
      client/webgl_listeners.js
  9. 4
      client/websocket.js
  10. 2
      server/recv.js
  11. 8
      server/send.js
  12. 2
      server/server.js

2
client/aux.js

@ -28,7 +28,7 @@ async function insert_image(state, context, file) {
if (resp.ok) { if (resp.ok) {
const image_id = await resp.text(); const image_id = await resp.text();
const event = image_event(image_id, canvasp.x, canvasp.y, bitmap.width, bitmap.height); const event = image_event(image_id, canvasp.x, canvasp.y, bitmap.width, bitmap.height);
await queue_event(state, event); queue_event(state, event);
} }
} }

70
client/client_recv.js

@ -365,71 +365,7 @@ function handle_event(state, context, event, options = {}) {
case EVENT.UNDO: { case EVENT.UNDO: {
geometry_add_dummy_stroke(context); geometry_add_dummy_stroke(context);
need_draw = undo(state, context, event, options);
for (let i = state.events.length - 1; i >=0; --i) {
const other_event = state.events[i];
// Users can only undo their own, undeleted (not already undone) events
if (other_event.user_id === event.user_id && !other_event.deleted) {
if (other_event.type === EVENT.STROKE) {
other_event.deleted = true;
if (other_event.bvh_node && !options.skip_bvh) {
bvh_delete_stroke(state, other_event);
}
need_draw = true;
break;
} else if (other_event.type === EVENT.UNDO) {
// do not undo an undo, we are not Notepad
} else if (other_event.type === EVENT.IMAGE) {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.deleted = true;
}
need_draw = true;
break;
} else if (other_event.type === EVENT.IMAGE_MOVE) {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.move_head -= 2;
image.at.x = image.move_history[image.move_head - 2];
image.at.y = image.move_history[image.move_head - 1];
need_draw = true;
} else {
console.warning('Undo image move for a non-existent image');
}
break;
} else if (other_event.type === EVENT.IMAGE_SCALE) {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.scale_head -= 4;
image.at.x = image.scale_history[image.scale_head - 4];
image.at.y = image.scale_history[image.scale_head - 3];
image.width = image.scale_history[image.scale_head - 2];
image.height = image.scale_history[image.scale_head - 1];
need_draw = true;
} else {
console.warning('Undo image scale for a non-existent image');
}
break;
} else if (other_event.type === EVENT.ERASER) {
const stroke = state.events[other_event.stroke_id];
stroke.deleted = false;
if (!options.skip_bvh) {
bvh_undelete_stroke(state, stroke);
}
need_draw = true;
other_event.deleted = true;
break;
} else {
console.error('cant undo event type', other_event.type);
break;
}
}
}
break; break;
} }
@ -530,7 +466,7 @@ function handle_event(state, context, event, options = {}) {
return need_draw; return need_draw;
} }
async function handle_message(state, context, d) { function handle_message(state, context, d) {
const message_type = des_u32(d); const message_type = des_u32(d);
let do_draw = false; let do_draw = false;
@ -660,7 +596,7 @@ async function handle_message(state, context, d) {
state.sn = sn; state.sn = sn;
send_ack(sn); // await? send_ack(sn);
break; break;
} }

16
client/client_send.js

@ -177,7 +177,7 @@ function ser_event(s, event) {
} }
} }
async function send_ack(sn) { function send_ack(sn) {
const s = serializer_create(4 + 4); const s = serializer_create(4 + 4);
ser_u32(s, MESSAGE.ACK); ser_u32(s, MESSAGE.ACK);
@ -186,13 +186,13 @@ async function send_ack(sn) {
if (config.debug_print) console.debug(`ack ${sn} out`); if (config.debug_print) console.debug(`ack ${sn} out`);
try { try {
if (ws) await ws.send(s.buffer); if (ws) ws.send(s.buffer);
} catch(e) { } catch(e) {
ws.close(); ws.close();
} }
} }
async function send_follow(player_id) { function send_follow(player_id) {
const s = serializer_create(4 + 4); const s = serializer_create(4 + 4);
player_id = player_id === null ? -1 : player_id; player_id = player_id === null ? -1 : player_id;
@ -203,13 +203,13 @@ async function send_follow(player_id) {
if (config.debug_print) console.debug(`follow ${player_id} out`); if (config.debug_print) console.debug(`follow ${player_id} out`);
try { try {
if (ws) await ws.send(s.buffer); if (ws) ws.send(s.buffer);
} catch (e) { } catch (e) {
ws.close(); ws.close();
} }
} }
async function sync_queue(state) { function sync_queue(state) {
if (ws === null) { if (ws === null) {
if (config.debug_print) console.debug('socket has closed, stopping SYNs'); if (config.debug_print) console.debug('socket has closed, stopping SYNs');
return; return;
@ -243,7 +243,7 @@ async function sync_queue(state) {
if (config.debug_print) console.debug(`syn ${state.lsn} out`); if (config.debug_print) console.debug(`syn ${state.lsn} out`);
try { try {
if (ws) await ws.send(s.buffer); if (ws) ws.send(s.buffer);
} catch(e) { } catch(e) {
ws.close(); ws.close();
} }
@ -306,7 +306,7 @@ function queue_event(state, event, skip = false) {
} }
// Fire and forget. Doesn't do anything if we are offline // Fire and forget. Doesn't do anything if we are offline
async function fire_event(state, event) { function fire_event(state, event) {
if (!state.online) { return; } if (!state.online) { return; }
const s = serializer_create(4 + event_size(event)); const s = serializer_create(4 + event_size(event));
@ -315,7 +315,7 @@ async function fire_event(state, event) {
ser_event(s, event); ser_event(s, event);
try { try {
if (ws) await ws.send(s.buffer); if (ws) ws.send(s.buffer);
} catch(e) { } catch(e) {
ws.close(); ws.close();
} }

35
client/config.js

@ -0,0 +1,35 @@
const config = {
ws_url: `wss://${window.location.host}/ws/`,
ping_url: `https://${window.location.host}/api/ping`,
image_url: `https://${window.location.host}/images/`,
sync_timeout: 1000,
ws_reconnect_timeout: 2000,
brush_preview_timeout: 1000,
second_finger_timeout: 500,
buffer_first_touchmoves: 5,
debug_print: false,
zoom_delta: 0.05,
min_zoom_level: -250,
max_zoom_level: 100,
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
bytes_per_instance: 4 * 2 + 4, // axy, stroke_id
bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
initial_wasm_bytes: 4096,
stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K)
dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
ui_texture_size: 16,
bvh_fullnode_depth: 5,
pattern_fadeout_min: 0.3,
pattern_fadeout_max: 0.75,
min_pressure: 50,
benchmark: {
zoom_level: -75,
offset: { x: 425, y: -1195 },
frames: 500,
},
};

3
client/index.html

@ -21,6 +21,8 @@
<script type="text/javascript" src="webgl_shaders.js"></script> <script type="text/javascript" src="webgl_shaders.js"></script>
<script type="text/javascript" src="webgl_listeners.js"></script> <script type="text/javascript" src="webgl_listeners.js"></script>
<script type="text/javascript" src="webgl_draw.js"></script> <script type="text/javascript" src="webgl_draw.js"></script>
<script type="text/javascript" src="undo.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="client_send.js"></script> <script type="text/javascript" src="client_send.js"></script>
@ -42,6 +44,7 @@
<label><input type="checkbox" id="debug-red">Simple shader</label> <label><input type="checkbox" id="debug-red">Simple shader</label>
<label><input type="checkbox" id="do-snap">Snap to grid</label> <label><input type="checkbox" id="do-snap">Snap to grid</label>
<label><input type="checkbox" id="debug-print">Debug print</label>
<button id="debug-begin-benchmark" title="Do not forget to enable recording in your browser!">Benchmark</button> <button id="debug-begin-benchmark" title="Do not forget to enable recording in your browser!">Benchmark</button>
</div> </div>

39
client/index.js

@ -1,43 +1,5 @@
document.addEventListener('DOMContentLoaded', main); document.addEventListener('DOMContentLoaded', main);
const config = {
// ws_url: 'wss://desk.some.website/ws/',
// ping_url: 'https://desk.some.website/api/ping',
// image_url: 'https://desk.some.website/images/',
ws_url: `wss://${window.location.host}/ws/`,
ping_url: `https://${window.location.host}/api/ping`,
image_url: `https://${window.location.host}/images/`,
sync_timeout: 1000,
ws_reconnect_timeout: 2000,
brush_preview_timeout: 1000,
second_finger_timeout: 500,
buffer_first_touchmoves: 5,
debug_print: false,
zoom_delta: 0.05,
min_zoom_level: -250,
max_zoom_level: 100,
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
bytes_per_instance: 4 * 2 + 4, // axy, stroke_id
bytes_per_stroke: 2 * 3 + 2, // r, g, b, width
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
initial_wasm_bytes: 4096,
stroke_texture_size: 1024, // means no more than 1024^2 = 1M strokes in total (this is a LOT. HMH blackboard has like 80K)
dynamic_stroke_texture_size: 128, // means no more than 128^2 = 16K dynamic strokes at once
ui_texture_size: 16,
bvh_fullnode_depth: 5,
pattern_fadeout_min: 0.3,
pattern_fadeout_max: 0.75,
min_pressure: 50,
benchmark: {
zoom_level: -75,
offset: { x: 425, y: -1195 },
frames: 500,
},
};
const EVENT = Object.freeze({ const EVENT = Object.freeze({
PREDRAW: 10, PREDRAW: 10,
SET_COLOR: 11, SET_COLOR: 11,
@ -187,6 +149,7 @@ async function main() {
'active_image': null, 'active_image': null,
'scaling_corner': null, 'scaling_corner': null,
'ruler_origin': null, 'ruler_origin': null,
'image_actually_moved': false,
'current_strokes': {}, 'current_strokes': {},

103
client/undo.js

@ -0,0 +1,103 @@
function undo(state, context, event, options) {
let need_draw = false;
// Remove effect of latest own event, in a way that is recoverable
// Iterate back to front to find the _latest_ event
for (let i = state.events.length - 1; i >=0; --i) {
const other_event = state.events[i];
let skipped = false;
// Users can only undo their own, undeleted (not already undone) events
if (other_event.user_id === event.user_id && !other_event.deleted) {
// All "persistent" events (those that are pushed using SYN messages) should be handled here
// "Transient" events are by design droppable, and should not be undone, nor saved in state.events at all
switch (other_event.type) {
case EVENT.STROKE: {
other_event.deleted = true;
if (other_event.bvh_node && !options.skip_bvh) {
bvh_delete_stroke(state, other_event);
}
need_draw = true;
break;
}
case EVENT.UNDO: {
// do not undo an undo, we are not Notepad
skipped = true;
break;
}
case EVENT.IMAGE: {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.deleted = true;
}
need_draw = true;
break;
}
case EVENT.IMAGE_MOVE: {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.move_head -= 2;
image.at.x = image.move_history[image.move_head - 2];
image.at.y = image.move_history[image.move_head - 1];
need_draw = true;
} else {
console.warning('Undo image move for a non-existent image');
}
break;
}
case EVENT.IMAGE_SCALE: {
other_event.deleted = true;
const image = get_image(context, other_event.image_id);
if (image !== null) {
image.scale_head -= 4;
// NEXT: merge move and scale. Otherwise we can't know
// that there have been move events inbetween scale
image.at.x = image.scale_history[image.scale_head - 4];
image.at.y = image.scale_history[image.scale_head - 3];
image.width = image.scale_history[image.scale_head - 2];
image.height = image.scale_history[image.scale_head - 1];
need_draw = true;
} else {
console.warning('Undo image scale for a non-existent image');
}
break;
}
case EVENT.ERASER: {
other_event.deleted = true;
const stroke = state.events[other_event.stroke_id];
stroke.deleted = false;
if (!options.skip_bvh) {
bvh_undelete_stroke(state, stroke);
}
need_draw = true;
break;
}
default: {
console.error('cant undo event type', other_event.type);
break;
}
}
if (!skipped) {
break;
}
}
}
return need_draw;
}
function redo() {
console.log('TODO');
}

10
client/webgl_listeners.js

@ -25,6 +25,7 @@ function init_listeners(state, context) {
function debug_panel_init(state, context) { function debug_panel_init(state, context) {
document.getElementById('debug-red').checked = state.debug.red; document.getElementById('debug-red').checked = state.debug.red;
document.getElementById('do-snap').checked = state.snap !== null; document.getElementById('do-snap').checked = state.snap !== null;
document.getElementById('debug-print').checked = config.debug_print;
document.getElementById('debug-red').addEventListener('change', (e) => { document.getElementById('debug-red').addEventListener('change', (e) => {
state.debug.red = e.target.checked; state.debug.red = e.target.checked;
@ -35,6 +36,10 @@ function debug_panel_init(state, context) {
state.snap = e.target.checked ? 'grid' : null; state.snap = e.target.checked ? 'grid' : null;
}); });
document.getElementById('debug-print').addEventListener('change', (e) => {
config.debug_print = e.target.checked;
});
document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => { document.getElementById('debug-begin-benchmark').addEventListener('click', (e) => {
state.canvas.zoom_level = config.benchmark.zoom_level; state.canvas.zoom_level = config.benchmark.zoom_level;
state.canvas.offset.x = config.benchmark.offset.x; state.canvas.offset.x = config.benchmark.offset.x;
@ -263,6 +268,7 @@ function mousedown(e, state, context) {
state.active_image = image.key; state.active_image = image.key;
// Allow immediately moving // Allow immediately moving
state.imagemoving = true; state.imagemoving = true;
state.image_actually_moved = false;
image.raw_at.x = image.at.x; image.raw_at.x = image.at.x;
image.raw_at.y = image.at.y; image.raw_at.y = image.at.y;
} else { } else {
@ -403,6 +409,7 @@ function mousemove(e, state, context) {
image.at.y = image.raw_at.y; image.at.y = image.raw_at.y;
} }
state.image_actually_moved = true;
do_draw = true; do_draw = true;
} }
} }
@ -495,11 +502,14 @@ function mouseup(e, state, context) {
if (state.imagemoving) { if (state.imagemoving) {
state.imagemoving = false; state.imagemoving = false;
if (state.image_actually_moved) {
state.image_actually_moved = false;
const image = get_image(context, state.active_image); const image = get_image(context, state.active_image);
image.raw_at.x = image.at.x; image.raw_at.x = image.at.x;
image.raw_at.y = image.at.y; image.raw_at.y = image.at.y;
queue_event(state, image_move_event(state.active_image, image.at.x, image.at.y)); queue_event(state, image_move_event(state.active_image, image.at.x, image.at.y));
schedule_draw(state, context); schedule_draw(state, context);
}
return; return;
} }

4
client/websocket.js

@ -48,7 +48,7 @@ async function on_message(state, context, event) {
message_data = await data.arrayBuffer(); message_data = await data.arrayBuffer();
const view = new DataView(message_data); const view = new DataView(message_data);
const d = deserializer_create(message_data, view); const d = deserializer_create(message_data, view);
await handle_message(state, context, d); handle_message(state, context, d);
} else { } else {
/* For all my Safari < 14 bros out there */ /* For all my Safari < 14 bros out there */
const reader = new FileReader(); const reader = new FileReader();
@ -56,7 +56,7 @@ async function on_message(state, context, event) {
message_data = e.target.result; message_data = e.target.result;
const view = new DataView(message_data); const view = new DataView(message_data);
const d = deserializer_create(message_data, view); const d = deserializer_create(message_data, view);
await handle_message(state, context, d); handle_message(state, context, d);
}; };
reader.readAsArrayBuffer(data); reader.readAsArrayBuffer(data);

2
server/recv.js

@ -163,7 +163,7 @@ function handle_event(session, event) {
} }
} }
export async function handle_message(ws, d) { export function handle_message(ws, d) {
if (!(ws.data.session_id in sessions)) { if (!(ws.data.session_id in sessions)) {
return; return;
} }

8
server/send.js

@ -108,7 +108,7 @@ function create_session(ws, desk_id) {
return session; return session;
} }
export async function send_init(ws) { export function send_init(ws) {
if (!ws) { if (!ws) {
return; return;
} }
@ -185,7 +185,7 @@ export async function send_init(ws) {
ser.event(s, event); ser.event(s, event);
} }
await ws.send(s.buffer); ws.send(s.buffer);
} }
export function send_ack(ws, lsn) { export function send_ack(ws, lsn) {
@ -242,7 +242,7 @@ export function fire_event(from_session, event) {
} }
} }
async function sync_session(session_id) { function sync_session(session_id) {
if (!(session_id in sessions)) { if (!(session_id in sessions)) {
return; return;
} }
@ -284,7 +284,7 @@ async function sync_session(session_id) {
if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`); if (config.DEBUG_PRINT) console.log(`syn ${desk.sn} out`);
await session.ws.send(s.buffer); session.ws.send(s.buffer);
if (session.sync_attempts < config.SYNC_MAX_ATTEMPTS) { if (session.sync_attempts < config.SYNC_MAX_ATTEMPTS) {
session.sync_attempts += 1; session.sync_attempts += 1;

2
server/server.js

@ -58,7 +58,7 @@ export function startup() {
async message(ws, u8array) { async message(ws, u8array) {
const dataview = new DataView(u8array.buffer); const dataview = new DataView(u8array.buffer);
const d = des.create(dataview); const d = des.create(dataview);
await recv.handle_message(ws, d); recv.handle_message(ws, d);
}, },
close(ws, code, message) { close(ws, code, message) {

Loading…
Cancel
Save