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. 20
      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) { @@ -28,7 +28,7 @@ async function insert_image(state, context, file) {
if (resp.ok) {
const image_id = await resp.text();
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 = {}) { @@ -365,71 +365,7 @@ function handle_event(state, context, event, options = {}) {
case EVENT.UNDO: {
geometry_add_dummy_stroke(context);
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;
}
}
}
need_draw = undo(state, context, event, options);
break;
}
@ -530,7 +466,7 @@ function handle_event(state, context, event, options = {}) { @@ -530,7 +466,7 @@ function handle_event(state, context, event, options = {}) {
return need_draw;
}
async function handle_message(state, context, d) {
function handle_message(state, context, d) {
const message_type = des_u32(d);
let do_draw = false;
@ -660,7 +596,7 @@ async function handle_message(state, context, d) { @@ -660,7 +596,7 @@ async function handle_message(state, context, d) {
state.sn = sn;
send_ack(sn); // await?
send_ack(sn);
break;
}

16
client/client_send.js

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

35
client/config.js

@ -0,0 +1,35 @@ @@ -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 @@ @@ -21,6 +21,8 @@
<script type="text/javascript" src="webgl_shaders.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="undo.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="client_send.js"></script>
@ -42,6 +44,7 @@ @@ -42,6 +44,7 @@
<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="debug-print">Debug print</label>
<button id="debug-begin-benchmark" title="Do not forget to enable recording in your browser!">Benchmark</button>
</div>

39
client/index.js

@ -1,43 +1,5 @@ @@ -1,43 +1,5 @@
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({
PREDRAW: 10,
SET_COLOR: 11,
@ -187,6 +149,7 @@ async function main() { @@ -187,6 +149,7 @@ async function main() {
'active_image': null,
'scaling_corner': null,
'ruler_origin': null,
'image_actually_moved': false,
'current_strokes': {},

103
client/undo.js

@ -0,0 +1,103 @@ @@ -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');
}

20
client/webgl_listeners.js

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

4
client/websocket.js

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

2
server/recv.js

@ -163,7 +163,7 @@ function handle_event(session, event) { @@ -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)) {
return;
}

8
server/send.js

@ -108,7 +108,7 @@ function create_session(ws, desk_id) { @@ -108,7 +108,7 @@ function create_session(ws, desk_id) {
return session;
}
export async function send_init(ws) {
export function send_init(ws) {
if (!ws) {
return;
}
@ -185,7 +185,7 @@ export async function send_init(ws) { @@ -185,7 +185,7 @@ export async function send_init(ws) {
ser.event(s, event);
}
await ws.send(s.buffer);
ws.send(s.buffer);
}
export function send_ack(ws, lsn) {
@ -242,7 +242,7 @@ export function fire_event(from_session, event) { @@ -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)) {
return;
}
@ -284,7 +284,7 @@ async function sync_session(session_id) { @@ -284,7 +284,7 @@ async function sync_session(session_id) {
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) {
session.sync_attempts += 1;

2
server/server.js

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

Loading…
Cancel
Save