diff --git a/README.md b/README.md index aa8d7b8..c518925 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Release: * Engine + Benchmark harness + Reuse points, pack "nodraw" in high bit of stroke id (probably have at least one more bit, so up to 4 flag configurations) - - Draw dynamic data (strokes in progress) + + Draw dynamic data (strokes in progress) - Textured quads (pictures, code already written in older version) - Resize and move pictures (draw handles) - Z-prepass fringe bug (also, when do we enable the prepass?) @@ -26,6 +26,8 @@ Release: - Polish - Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - Settings panel (including the setting for "offline mode") + - Use typedvector where appropriate + - Set up VAOs - Presentation / "marketing" - Title - Icon diff --git a/client/aux.js b/client/aux.js index faa0fa5..2d533fb 100644 --- a/client/aux.js +++ b/client/aux.js @@ -33,7 +33,7 @@ async function insert_image(state, context, file) { } function event_size(event) { - let size = 1 + 3; // type + padding + let size = 4; // type switch (event.type) { case EVENT.PREDRAW: { diff --git a/client/client_recv.js b/client/client_recv.js index 97075bc..2a178ae 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -87,7 +87,6 @@ function des_event(d, state = null) { state.coordinates.count += point_count * 2; event.stroke_id = stroke_id; - event.lods = []; event.color = color; event.width = width; @@ -304,7 +303,7 @@ function handle_event(state, context, event, options = {}) { } async function handle_message(state, context, d) { - const message_type = des_u8(d); + const message_type = des_u32(d); let do_draw = false; // if (config.debug_print) console.debug(message_type); @@ -421,7 +420,7 @@ async function handle_message(state, context, d) { if (config.debug_print) console.debug(`syn ${sn} in`); for (let i = 0; i < count; ++i) { - const event = des_event(d); + const event = des_event(d, state); if (i >= first) { const need_draw = handle_event(state, context, event); do_draw = do_draw || need_draw; diff --git a/client/client_send.js b/client/client_send.js index e0a9a15..07d33b3 100644 --- a/client/client_send.js +++ b/client/client_send.js @@ -43,11 +43,6 @@ function ser_clear(s) { s.gpu_upload_from = 0; } -function ser_u8(s, value) { - s.view.setUint8(s.offset, value); - s.offset += 1; -} - function ser_u16(s, value) { s.view.setUint16(s.offset, value, true); s.offset += 2; @@ -71,7 +66,7 @@ function ser_align(s, to) { } function ser_event(s, event) { - ser_u8(s, event.type); + ser_u32(s, event.type); switch (event.type) { case EVENT.PREDRAW: { @@ -101,8 +96,6 @@ function ser_event(s, event) { if (config.debug_print) console.debug('original', event.points); - ser_align(s, 4); - for (const point of event.points) { ser_f32(s, point.x); ser_f32(s, point.y); @@ -137,9 +130,9 @@ function ser_event(s, event) { } async function send_ack(sn) { - const s = serializer_create(1 + 4); + const s = serializer_create(4 + 4); - ser_u8(s, MESSAGE.ACK); + ser_u32(s, MESSAGE.ACK); ser_u32(s, sn); if (config.debug_print) console.debug(`ack ${sn} out`); @@ -158,7 +151,7 @@ async function sync_queue(state) { return; } - let size = 1 + 3 + 4 + 4; // opcode + lsn + event count + let size = 4 + 4 + 4; // opcode + lsn + event count let count = state.lsn - state.server_lsn; if (count === 0) { @@ -174,7 +167,7 @@ async function sync_queue(state) { const s = serializer_create(size); - ser_u8(s, MESSAGE.SYN); + ser_u32(s, MESSAGE.SYN); ser_u32(s, state.lsn); ser_u32(s, count); @@ -251,9 +244,9 @@ function queue_event(state, event, skip = false) { async function fire_event(state, event) { if (!state.online) { return; } - const s = serializer_create(1 + event_size(event)); + const s = serializer_create(4 + event_size(event)); - ser_u8(s, MESSAGE.FIRE); + ser_u32(s, MESSAGE.FIRE); ser_event(s, event); try { diff --git a/client/index.js b/client/index.js index ba26382..22de0e0 100644 --- a/client/index.js +++ b/client/index.js @@ -22,7 +22,8 @@ const config = { bytes_per_stroke: 2 * 3 + 2, // r, g, b, width initial_static_bytes: 4096 * 16, initial_dynamic_bytes: 4096, - stroke_texture_size: 1024, + 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 benchmark: { zoom: 0.035, offset: { x: 900, y: 400 }, @@ -245,10 +246,15 @@ function main() { 'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_ids': tv_create(Uint32Array, 4096), + + 'dynamic_instance_points': tv_create(Float32Array, 4096), + 'dynamic_instance_ids': tv_create(Uint32Array, 4096), - 'lods': [], - 'stroke_data': serializer_create(config.initial_static_bytes), + 'dynamic_stroke_data': serializer_create(config.initial_static_bytes), + + 'dynamic_stroke_count': 0, + 'dynamic_segment_count': 0, 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, @@ -261,18 +267,6 @@ function main() { const url = new URL(window.location.href); const parts = url.pathname.split('/'); - config.lod_levels = Math.ceil(Math.log2(1.0 / config.min_zoom)); - - for (let i = 0; i < config.lod_levels; ++i) { - context.lods.push({ - 'max_zoom': Math.pow(0.25, i), // use this LOD level when current canvas.zoom is less than this value, but not less than the next level max_zoom (or if this is the last zoom level) - 'total_points': 0, - 'segments': serializer_create(config.initial_static_bytes), - 'data_buffer': null, - 'index_buffer': null, - }); - } - state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; init_webgl(state, context); diff --git a/client/math.js b/client/math.js index 8d44689..62fd7b5 100644 --- a/client/math.js +++ b/client/math.js @@ -50,6 +50,7 @@ function rdp_find_max(state, zoom, stroke, start, end) { return result; } */ + function process_rdp_indices_r(state, zoom, mask, stroke, start, end) { // Looks like the recursive implementation spends most of its time in the function call overhead // Let's try to use an explicit stack instead to give the js engine more room to play with @@ -124,6 +125,72 @@ function process_stroke(state, zoom, stroke) { return npoints; } +function rdp_find_max2(points, start, end) { + const EPS = 0.5; + + let result = -1; + let max_dist = 0; + + const a = points[start]; + const b = points[end]; + + const dx = b.x - a.x; + const dy = b.y - a.y; + + const dist_ab = Math.sqrt(dx * dx + dy * dy); + const sin_theta = dy / dist_ab; + const cos_theta = dx / dist_ab; + + for (let i = start; i < end; ++i) { + const p = points[i]; + + const ox = p.x - a.x; + const oy = p.y - a.y; + + const rx = cos_theta * ox + sin_theta * oy; + const ry = -sin_theta * ox + cos_theta * oy; + + const x = rx + a.x; + const y = ry + a.y; + + const dist = Math.abs(y - a.y); + + if (dist > EPS && dist > max_dist) { + result = i; + max_dist = dist; + } + } + + return result; +} + +function process_rdp_r2(points, start, end) { + let result = []; + + const max = rdp_find_max2(points, start, end); + + if (max !== -1) { + const before = process_rdp_r2(points, start, max); + const after = process_rdp_r2(points, max, end); + result = [...before, points[max], ...after]; + } + + return result; +} + +function process_rdp2(points) { + const result = process_rdp_r2(points, 0, points.length - 1); + result.unshift(points[0]); + result.push(points[points.length - 1]); + return result; +} + +// TODO: unify with regular process stroke +function process_stroke2(points) { + const result = process_rdp2(points); + return result; +} + function strokes_intersect_line(state, a, b) { // TODO: handle stroke / eraser width const result = []; diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 31a8937..80ff898 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -73,11 +73,17 @@ function draw(state, context) { bvh_clip(state, context); const segment_count = geometry_write_instances(state, context); + const dynamic_segment_count = context.dynamic_segment_count; + const dynamic_stroke_count = context.dynamic_stroke_count; + // "Static" data upload gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW); gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points)); gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids)); + gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); + // TODO: this is stable data, only upload new strokes as they arrive + upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); @@ -91,7 +97,7 @@ function draw(state, context) { gl.enableVertexAttribArray(locations['a_b']); gl.enableVertexAttribArray(locations['a_stroke_id']); - // Points (a, b) and stroke ids are not stored in separate cpu buffers so that points can be resued + // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4); gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4); @@ -99,81 +105,47 @@ function draw(state, context) { gl.vertexAttribDivisor(locations['a_a'], 1); gl.vertexAttribDivisor(locations['a_b'], 1); gl.vertexAttribDivisor(locations['a_stroke_id'], 1); - - gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); - // TODO: this is stable data, only upload new strokes as they arrive - upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); - gl.activeTexture(gl.TEXTURE0); - - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count); // TODO: based on clipping results - document.getElementById('debug-stats').innerHTML = ` - Segments onscreen: ${segment_count} - Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y}) - Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}`; - - /* - // Dynamic data (stroke previews that are currently in progress) - const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point; - - if (dynamic_points > 0) { - gl.drawBuffers([gl.BACK]); - - locations = context.locations['sdf'].main; - - gl.useProgram(context.programs['sdf'].main); - - gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); - gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); - gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); - gl.uniform1i(locations['u_stroke_count'], state.stroke_count); - gl.uniform1i(locations['u_debug_mode'], state.debug.red); - - gl.clear(gl.DEPTH_BUFFER_BIT); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed_dynamic']); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_dynamic_index']); - - gl.enableVertexAttribArray(locations['a_pos']); - gl.enableVertexAttribArray(locations['a_line']); - gl.enableVertexAttribArray(locations['a_color']); - gl.enableVertexAttribArray(locations['a_stroke_id']); + // Static draw (everything already bound) + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count); + + // Dynamic strokes should be drawn above static strokes + gl.clear(gl.DEPTH_BUFFER_BIT); - gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0); - gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3); - gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4); - gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4); + // Dynamic draw (strokes currently being drawn) + gl.uniform1i(locations['u_stroke_count'], dynamic_stroke_count); + gl.uniform1i(locations['u_stroke_data'], 0); + gl.uniform1i(locations['u_stroke_texture_size'], config.dynamic_stroke_texture_size); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']); - const dynamic_indices = []; - let base = 0; + // Dynamic data upload + gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4, gl.STREAM_DRAW); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points)); + gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids)); + gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']); + upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size); - for (const player_id in state.players) { - // player has the same data as their current stroke: points, color, width - const player = state.players[player_id]; - if (player.points.length > 1) { - for (let i = 0; i < player.points.length - 1; ++i) { - dynamic_indices.push(base + 0); - dynamic_indices.push(base + 1); - dynamic_indices.push(base + 2); - dynamic_indices.push(base + 3); - dynamic_indices.push(base + 2); - dynamic_indices.push(base + 1); + gl.enableVertexAttribArray(locations['a_a']); + gl.enableVertexAttribArray(locations['a_b']); + gl.enableVertexAttribArray(locations['a_stroke_id']); + + // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) + gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); + gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4); + gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4); - base += 4; - } - } - } + gl.vertexAttribDivisor(locations['a_a'], 1); + gl.vertexAttribDivisor(locations['a_b'], 1); + gl.vertexAttribDivisor(locations['a_stroke_id'], 1); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count); - if (context.need_dynamic_upload) { - gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.dynamic_serializer.buffer, 0, context.dynamic_serializer.offset), gl.DYNAMIC_DRAW); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(dynamic_indices), gl.DYNAMIC_DRAW); - context.need_dynamic_upload = false; - } + document.getElementById('debug-stats').innerHTML = ` + Segments onscreen: ${segment_count} + Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y}) + Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}`; - gl.drawElements(gl.TRIANGLES, dynamic_indices.length, gl.UNSIGNED_INT, 0); - } -*/ if (context.gpu_timer_ext) { gl.endQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT); diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 20c2dd6..20649cc 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -24,9 +24,9 @@ function geometry_prepare_stroke(state) { if (state.players[state.me].points.length === 0) { return null; - } + } - const points = process_stroke(state, state.canvas.zoom, state.players[state.me].points); + const points = process_stroke2(state.players[state.me].points); return { 'color': state.players[state.me].color, @@ -223,30 +223,62 @@ function geometry_delete_stroke(state, context, stroke_index) { } function recompute_dynamic_data(state, context) { - let bytes_needed = 0; + let total_points = 0; + let total_strokes = 0; for (const player_id in state.players) { const player = state.players[player_id]; if (player.points.length > 0) { - bytes_needed += player.points.length * 6 * config.bytes_per_point; + total_points += player.points.length; + total_strokes += 1; } } - if (bytes_needed > context.dynamic_serializer.size) { - context.dynamic_serializer = serializer_create(Math.ceil(bytes_needed * 1.62)); - } else { - context.dynamic_serializer.offset = 0; - } + context.dynamic_instance_data = tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096)); + context.dynamic_instance_ids = tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096)); + + tv_clear(context.dynamic_instance_points); + tv_clear(context.dynamic_instance_ids); + + context.dynamic_stroke_data = ser_ensure(context.dynamic_stroke_data, config.bytes_per_stroke * total_strokes); + ser_clear(context.dynamic_stroke_data); + + let stroke_index = 0; for (const player_id in state.players) { // player has the same data as their current stroke: points, color, width const player = state.players[player_id]; - if (player.points.length > 1) { - push_stroke(context.dynamic_serializer, player, 0); + + for (let i = 0; i < player.points.length; ++i) { + const p = player.points[i]; + + tv_add(context.dynamic_instance_points, p.x); + tv_add(context.dynamic_instance_points, p.y); + + if (i !== player.points.length - 1) { + tv_add(context.dynamic_instance_ids, stroke_index); + } else { + tv_add(context.dynamic_instance_ids, stroke_index | (1 << 31)); + } + } + + if (player.points.length > 0) { + const color_u32 = player.color; + const r = (color_u32 >> 16) & 0xFF; + const g = (color_u32 >> 8) & 0xFF; + const b = color_u32 & 0xFF; + + ser_u16(context.dynamic_stroke_data, r); + ser_u16(context.dynamic_stroke_data, g); + ser_u16(context.dynamic_stroke_data, b); + ser_u16(context.dynamic_stroke_data, player.width); + + stroke_index += 1; // TODO: proper player Z order } } - context.need_dynamic_upload = true; + context.dynamic_segment_count = total_points; + context.dynamic_stroke_count = total_strokes; } function geometry_add_point(state, context, player_id, point) { diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 3cc8cad..50bfe89 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -279,9 +279,9 @@ function mouseup(e, state, context) { const stroke = geometry_prepare_stroke(state); if (stroke) { - //geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index? + //geometry_add_stroke(state, context, stroke, 0); queue_event(state, stroke_event(state)); - //geometry_clear_player(state, context, state.me); + geometry_clear_player(state, context, state.me); schedule_draw(state, context); } diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 1520e9e..c03f614 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -323,24 +323,18 @@ function init_webgl(state, context) { } }; - for (let i = 0; i < context.lods.length; ++i) { - const level = context.lods[i]; - level.data_buffer = gl.createBuffer(); - level.index_buffer = gl.createBuffer(); - } - context.buffers['debug'] = { 'b_packed': gl.createBuffer(), }; context.buffers['sdf'] = { - 'b_packed_dynamic': gl.createBuffer(), - 'b_packed_dynamic_index': gl.createBuffer(), 'b_instance': gl.createBuffer(), + 'b_dynamic_instance': gl.createBuffer(), }; context.textures = { 'stroke_data': gl.createTexture(), + 'dynamic_stroke_data': gl.createTexture(), }; gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); @@ -348,6 +342,11 @@ function init_webgl(state, context) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.stroke_texture_size, config.stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, null); + gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16UI, config.dynamic_stroke_texture_size, config.dynamic_stroke_texture_size, 0, gl.RGBA_INTEGER, gl.UNSIGNED_SHORT, null); + const resize_canvas = (entries) => { // https://www.khronos.org/webgl/wiki/HandlingHighDPI const entry = entries[0]; diff --git a/server/deserializer.js b/server/deserializer.js index feafca4..c404971 100644 --- a/server/deserializer.js +++ b/server/deserializer.js @@ -47,7 +47,7 @@ export function align(d, to) { export function event(d) { const event = {}; - event.type = u8(d); + event.type = u32(d); switch (event.type) { case EVENT.PREDRAW: { @@ -75,7 +75,6 @@ export function event(d) { const point_count = u16(d); const width = u16(d); const color = u32(d); - align(d, 4); event.width = width; event.color = color; event.points = f32array(d, point_count * 2); @@ -108,4 +107,4 @@ export function event(d) { } return event; -} \ No newline at end of file +} diff --git a/server/recv.js b/server/recv.js index 9dd29b0..2281fde 100644 --- a/server/recv.js +++ b/server/recv.js @@ -164,7 +164,7 @@ export async function handle_message(ws, d) { const session = sessions[ws.data.session_id]; const desk_id = session.desk_id; - const message_type = des.u8(d); + const message_type = des.u32(d); switch (message_type) { case MESSAGE.FIRE: { diff --git a/server/send.js b/server/send.js index b040c26..1ff6157 100644 --- a/server/send.js +++ b/server/send.js @@ -94,7 +94,7 @@ export async function send_init(ws) { const desk = desks[desk_id]; let opcode = MESSAGE.INIT; - let size = 1 + 4 + 4 + 4 + 4 + 4 + 3; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count + align on 4 + let size = 4 + 4 + 4 + 4 + 4 + 4; // opcode + user_id + lsn + event count + stroke count + user count + total_point_count let session = null; if (session_id in sessions && sessions[session_id].desk_id == desk_id) { @@ -130,7 +130,7 @@ export async function send_init(ws) { const s = ser.create(size); - ser.u8(s, opcode); + ser.u32(s, opcode); ser.u32(s, session.lsn); if (opcode === MESSAGE.JOIN) { @@ -168,10 +168,10 @@ export function send_ack(ws, lsn) { return; } - const size = 1 + 4; // opcode + lsn + const size = 4 + 4; // opcode + lsn const s = ser.create(size); - ser.u8(s, MESSAGE.ACK); + ser.u32(s, MESSAGE.ACK); ser.u32(s, lsn); if (config.DEBUG_PRINT) console.log(`ack ${lsn} out`); @@ -184,9 +184,9 @@ export function send_fire(ws, event) { return; } - const s = ser.create(1 + 4 + event_size(event)); + const s = ser.create(4 + 4 + event_size(event)); - ser.u8(s, MESSAGE.FIRE); + ser.u32(s, MESSAGE.FIRE); ser.event(s, event); ws.send(s.buffer); @@ -208,7 +208,7 @@ async function sync_session(session_id) { return; } - let size = 1 + 4 + 4; // opcode + sn + event count + let size = 4 + 4 + 4; // opcode + sn + event count let count = desk.sn - session.sn; if (count === 0) { @@ -223,7 +223,7 @@ async function sync_session(session_id) { const s = ser.create(size); - ser.u8(s, MESSAGE.SYN); + ser.u32(s, MESSAGE.SYN); ser.u32(s, desk.sn); ser.u32(s, count);