Browse Source

Recompute LODs and instance data on demand - ??? - it works???

ssao
A.Olokhtonov 11 months ago
parent
commit
a60c3d1948
  1. 53
      client/bvh.js
  2. 44
      client/client_recv.js
  3. 1
      client/client_send.js
  4. 18
      client/index.js
  5. 96
      client/math.js
  6. 50
      client/webgl_draw.js
  7. 58
      client/webgl_geometry.js
  8. 2
      client/webgl_listeners.js
  9. 1
      client/webgl_shaders.js
  10. BIN
      server/data-local.sqlite
  11. 2
      server/recv.js
  12. 7
      server/send.js
  13. 9
      server/serializer.js
  14. 15
      server/storage.js

53
client/bvh.js

@ -155,9 +155,9 @@ function bvh_add_stroke(bvh, index, stroke) { @@ -155,9 +155,9 @@ function bvh_add_stroke(bvh, index, stroke) {
}
}
function bvh_intersect_quad(bvh, quad) {
function bvh_intersect_quad(bvh, quad, result_buffer) {
if (bvh.root === null) {
return [];
return;
}
const stack = [bvh.root];
@ -172,20 +172,25 @@ function bvh_intersect_quad(bvh, quad) { @@ -172,20 +172,25 @@ function bvh_intersect_quad(bvh, quad) {
}
if (node.is_leaf) {
result.push(node.stroke_index);
result_buffer.data[result_buffer.count] = node.stroke_index;
result_buffer.count += 1;
} else {
stack.push(node.child1, node.child2);
}
}
return result;
}
function bvh_clip(state, context, lod_level) {
const lod = context.lods[lod_level];
function bvh_clip(state, context) {
if (state.stroke_count === 0) {
return;
}
lod.indices = ser_ensure(lod.indices, lod.total_points * 6 * 4);
ser_clear(lod.indices);
if (context.clipped_indices.cap < state.stroke_count) {
context.clipped_indices.cap = round_to_pow2(state.stroke_count, 4096);
context.clipped_indices.data = new Uint32Array(context.clipped_indices.cap);
}
context.clipped_indices.count = 0;
const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0});
const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height});
@ -199,35 +204,9 @@ function bvh_clip(state, context, lod_level) { @@ -199,35 +204,9 @@ function bvh_clip(state, context, lod_level) {
'y2': screen_bottomright.y
};
const stroke_indices = bvh_intersect_quad(state.bvh, screen);
stroke_indices.sort((a, b) => a - b);
for (const i of stroke_indices) {
if (state.debug.limit_to && i >= state.debug.render_to) break;
const event = state.events[i];
if (!(state.debug.limit_from && i < state.debug.render_from)) {
if (event.type === EVENT.STROKE && !event.deleted && event.points.length > 0) {
const points = event.lods[lod_level].points;
for (let j = 0; j < points.length - 1; ++j) {
const base = event.lods[lod_level].starting_index + j * 4;
// We draw quads as [1, 2, 3, 4, 3, 2]
ser_u32(lod.indices, base + 0);
ser_u32(lod.indices, base + 1);
ser_u32(lod.indices, base + 2);
ser_u32(lod.indices, base + 3);
ser_u32(lod.indices, base + 2);
ser_u32(lod.indices, base + 1);
}
}
}
}
bvh_intersect_quad(state.bvh, screen, context.clipped_indices);
return lod.indices.offset / 4;
new Uint32Array(context.clipped_indices.data.buffer, 0, context.clipped_indices.count).sort(); // we need to draw back to front still!
}
function bvh_construct_rec(bvh, vertical, strokes) {

44
client/client_recv.js

@ -32,22 +32,23 @@ function des_f32(d) { @@ -32,22 +32,23 @@ function des_f32(d) {
return value;
}
function des_f32array(d, count) {
const result = [];
for (let i = 0; i < count; ++i) {
const item = d.view.getFloat32(d.offset, true);
d.offset += 4;
result.push(item);
function des_align(d, to) {
// TODO: non-stupid version of this
while (d.offset % to != 0) {
d.offset++;
}
}
function des_f32array(d, count) {
const result = new Float32Array(d.buffer, d.offset, count);
d.offset += 4 * count;
return result;
}
function des_event(d) {
function des_event(d, state = null) {
const event = {};
event.type = des_u8(d);
event.type = des_u32(d);
event.user_id = des_u32(d);
switch (event.type) {
@ -76,18 +77,18 @@ function des_event(d) { @@ -76,18 +77,18 @@ function des_event(d) {
const point_count = des_u16(d);
const width = des_u16(d);
const color = des_u32(d);
const coords = des_f32array(d, point_count * 2);
event.coords_from = state.coordinates.count;
event.coords_to = state.coordinates.count + point_count * 2;
state.coordinates.data.set(coords, state.coordinates.count);
state.coordinates.count += point_count * 2;
event.stroke_id = stroke_id;
event.points = [];
event.lods = [];
for (let i = 0; i < point_count; ++i) {
const x = coords[2 * i + 0];
const y = coords[2 * i + 1];
event.points.push({'x': x, 'y': y});
}
event.color = color;
event.width = width;
@ -178,6 +179,8 @@ function handle_event(state, context, event, options = {}) { @@ -178,6 +179,8 @@ function handle_event(state, context, event, options = {}) {
need_draw = true;
//}
event.index = state.events.length;
geometry_add_stroke(state, context, event, state.events.length, options.skip_bvh === true);
state.stroke_count++;
@ -342,6 +345,9 @@ async function handle_message(state, context, d) { @@ -342,6 +345,9 @@ async function handle_message(state, context, d) {
const event_count = des_u32(d);
const user_count = des_u32(d);
const total_points = des_u32(d);
state.coordinates.data = new Float32Array(round_to_pow2(total_points * 2, 4096));
if (config.debug_print) console.debug(`${event_count} events in init`);
@ -355,11 +361,13 @@ async function handle_message(state, context, d) { @@ -355,11 +361,13 @@ async function handle_message(state, context, d) {
init_player_defaults(state, user_id, user_color, user_width);
}
des_align(d, 4);
for (let i = 0; i < event_count; ++i) {
const event = des_event(d);
const event = des_event(d, state);
handle_event(state, context, event, {'skip_bvh': true});
if (event.type !== EVENT.STROKE || event.points.length > 0) {
if (event.type !== EVENT.STROKE || event.coords_to - event.coords_from > 0) {
state.events.push(event);
}
}

1
client/client_send.js

@ -152,6 +152,7 @@ async function send_ack(sn) { @@ -152,6 +152,7 @@ async function send_ack(sn) {
}
async function sync_queue(state) {
if (ws === null) {
if (config.debug_print) console.debug('socket has closed, stopping SYNs');
return;

18
client/index.js

@ -24,7 +24,7 @@ const config = { @@ -24,7 +24,7 @@ const config = {
initial_offline_timeout: 1000,
default_color: 0x00,
default_width: 8,
bytes_per_quad: 4 * 4 + 4, // axy, bxy, stroke_id
bytes_per_instance: 4 * 4 + 4, // axy, bxy, stroke_id
bytes_per_stroke: 3 + 1, // r, g, b, width
initial_static_bytes: 4096 * 16,
initial_dynamic_bytes: 4096,
@ -172,6 +172,11 @@ function main() { @@ -172,6 +172,11 @@ function main() {
'starting_index': 0,
'total_points': 0,
'coordinates': {
'data': null,
'count': 0,
},
'bvh': {
'nodes': [],
'root': null,
@ -215,10 +220,19 @@ function main() { @@ -215,10 +220,19 @@ function main() {
'buffers': {},
'locations': {},
'textures': {},
'dynamic_serializer': serializer_create(config.initial_dynamic_bytes),
'dynamic_index_serializer': serializer_create(config.initial_dynamic_bytes),
// TODO: i seem to have a lot of these, maybe make a few utility functions? similar to serializer, but for pure typedarray
'clipped_indices': {
'data': null,
'count': 0,
'cap': 0,
},
'instance_data': serializer_create(config.initial_static_bytes),
'lods': [],
'stroke_data': serializer_create(config.initial_static_bytes),

96
client/math.js

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
function round_to_pow2(value, multiple) {
return (value + multiple - 1) & -multiple;
}
function screen_to_canvas(state, p) {
// should be called with coordinates obtained from MouseEvent.clientX/clientY * window.devicePixelRatio
const xc = (p.x - state.canvas.offset.x) / state.canvas.zoom;
@ -6,36 +10,39 @@ function screen_to_canvas(state, p) { @@ -6,36 +10,39 @@ function screen_to_canvas(state, p) {
return {'x': xc, 'y': yc};
}
function rdp_find_max(zoom, points, start, end) {
function rdp_find_max(state, zoom, stroke, start, end) {
const EPS = 1.0 / zoom;
// const EPS = 10.0;
let result = -1;
let max_dist = 0;
const a = points[start];
const b = points[end];
const ax = state.coordinates.data[stroke.coords_from + start * 2 + 0];
const ay = state.coordinates.data[stroke.coords_from + start * 2 + 1];
const bx = state.coordinates.data[stroke.coords_from + end * 2 + 0];
const by = state.coordinates.data[stroke.coords_from + end * 2 + 1];
const dx = b.x - a.x;
const dy = b.y - a.y;
const dx = bx - ax;
const dy = by - ay;
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 px = state.coordinates.data[stroke.coords_from + i * 2 + 0];
const py = state.coordinates.data[stroke.coords_from + i * 2 + 1];
const ox = p.x - a.x;
const oy = p.y - a.y;
const ox = px - ax;
const oy = py - ay;
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 x = rx + ax;
const y = ry + ay;
const dist = Math.abs(y - a.y);
const dist = Math.abs(y - ay);
if (dist > EPS && dist > max_dist) {
result = i;
@ -46,45 +53,37 @@ function rdp_find_max(zoom, points, start, end) { @@ -46,45 +53,37 @@ function rdp_find_max(zoom, points, start, end) {
return result;
}
function process_rdp_r(zoom, mask, points, start, end) {
function process_rdp_indices_r(state, zoom, mask, stroke, start, end) {
let result = 0;
const max = rdp_find_max(zoom, points, start, end);
const max = rdp_find_max(state, zoom, stroke, start, end);
if (max !== -1) {
mask[max] = 1;
result += 1;
result += process_rdp_r(zoom, mask, points, start, max);
result += process_rdp_r(zoom, mask, points, max, end);
result += process_rdp_indices_r(state, zoom, mask, stroke, start, max);
result += process_rdp_indices_r(state, zoom, mask, stroke, max, end);
}
return result;
}
function process_rdp(state, zoom, points) {
if (state.rdp_mask.length < points.length) {
state.rdp_mask = new Uint8Array(points.length);
function process_rdp_indices(state, zoom, stroke) {
const point_count = (stroke.coords_to - stroke.coords_from) / 2;
if (state.rdp_mask.length < point_count) {
state.rdp_mask = new Uint8Array(point_count);
}
state.rdp_mask.fill(0, 0, points.length);
state.rdp_mask.fill(0, 0, point_count);
const mask = state.rdp_mask;
const npoints = process_rdp_r(zoom, mask, points, 0, points.length - 1);
const npoints = 2 + process_rdp_indices_r(state, zoom, mask, stroke, 0, point_count - 1); // 2 is for the first and last vertex, which do not get included by the recursive functions, but should always be there at any lod level
mask[0] = 1;
mask[points.length - 1] = 1;
const result = new Array(npoints);
let j = 0;
for (let i = 0; i < points.length; ++i) {
if (mask[i] === 1) {
result[j] = points[i];
++j;
}
}
mask[point_count - 1] = 1;
return result;
return npoints;
}
function process_ewmv(points, round = false) {
@ -103,9 +102,9 @@ function process_ewmv(points, round = false) { @@ -103,9 +102,9 @@ function process_ewmv(points, round = false) {
return result;
}
function process_stroke(state, zoom, points) {
function process_stroke(state, zoom, stroke) {
// const result0 = process_ewmv(points);
const result1 = process_rdp(state, zoom, points, true);
const result1 = process_rdp_indices(state, zoom, stroke, true);
return result1;
}
@ -202,20 +201,23 @@ function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_top @@ -202,20 +201,23 @@ function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_top
return false;
}
function stroke_bbox(stroke) {
function stroke_bbox(state, stroke) {
const radius = stroke.width / 2;
let min_x = stroke.points[0].x - radius;
let max_x = stroke.points[0].x + radius;
let min_y = stroke.points[0].y - radius;
let max_y = stroke.points[0].y + radius;
let min_x = state.coordinates.data[stroke.coords_from + 0] - radius;
let max_x = state.coordinates.data[stroke.coords_from + 0] + radius;
let min_y = state.coordinates.data[stroke.coords_from + 1] - radius;
let max_y = state.coordinates.data[stroke.coords_from + 1] + radius;
for (const p of stroke.points) {
min_x = Math.min(min_x, p.x - radius);
min_y = Math.min(min_y, p.y - radius);
max_x = Math.max(max_x, p.x + radius);
max_y = Math.max(max_y, p.y + radius);
for (let i = stroke.coords_from + 2; i < stroke.coords_to; i += 2) {
const px = state.coordinates.data[i + 0];
const py = state.coordinates.data[i + 1];
min_x = Math.min(min_x, px - radius);
min_y = Math.min(min_y, py - radius);
max_x = Math.max(max_x, px + radius);
max_y = Math.max(max_y, py + radius);
}
return {'x1': min_x, 'y1': min_y, 'x2': max_x, 'y2': max_y, 'cx': (max_x + min_x) / 2, 'cy': (max_y + min_y) / 2};
@ -246,6 +248,10 @@ function quad_union(a, b) { @@ -246,6 +248,10 @@ function quad_union(a, b) {
};
}
function box_area(box) {
return (box.x2 - box.x1) * (box.y2 - box.y1);
}
function segments_onscreen(state, context, do_clip) {
// TODO: handle stroke width

50
client/webgl_draw.js

@ -48,18 +48,6 @@ function upload_square_rgba16ui_texture(gl, serializer, texture_size) { @@ -48,18 +48,6 @@ function upload_square_rgba16ui_texture(gl, serializer, texture_size) {
function draw(state, context) {
const cpu_before = performance.now();
let lod_level = -1;
for (let i = context.lods.length - 1; i >= 0; --i) {
const level = context.lods[i];
if (state.canvas.zoom <= level.max_zoom) {
lod_level = i;
break;
}
}
const lod = context.lods[lod_level];
state.timers.raf = false;
const gl = context.gl;
@ -79,19 +67,19 @@ function draw(state, context) { @@ -79,19 +67,19 @@ function draw(state, context) {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, lod.index_buffer);
// static data, per-quad: points, stroke_ids
// static data, per-stroke (texture): color, width (radius)
upload_if_needed(gl, gl.ARRAY_BUFFER, lod.segments);
locations = context.locations['sdf'].main;
buffers = context.buffers['sdf'];
gl.useProgram(context.programs['sdf'].main);
const segment_count = lod.segments.offset / config.bytes_per_quad;
bvh_clip(state, context);
const segment_count = geometry_write_instances(state, context);
// TODO: maybe have a pool of buffers (pow2?) and select an appropriate one
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(context.instance_data.buffer, 0, segment_count * config.bytes_per_instance), gl.DYNAMIC_DRAW);
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);
@ -103,23 +91,26 @@ function draw(state, context) { @@ -103,23 +91,26 @@ function draw(state, context) {
gl.enableVertexAttribArray(locations['a_ab']);
gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, 5 * 4, 0);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 5 * 4, 4 * 4);
gl.vertexAttribPointer(locations['a_ab'], 4, gl.FLOAT, false, config.bytes_per_instance, 0);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, config.bytes_per_instance, 4 * 4);
gl.vertexAttribDivisor(locations['a_ab'], 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 = `
<span>Segments onscreen: ${segment_count}</span>
<span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
<span>Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}</span>`;
/*
const before_clip = performance.now();
const index_count = bvh_clip(state, context, lod_level);
const after_clip = performance.now();
gl.bindBuffer(gl.ARRAY_BUFFER, lod.data_buffer);
@ -128,12 +119,7 @@ function draw(state, context) { @@ -128,12 +119,7 @@ function draw(state, context) {
upload_if_needed(gl, gl.ARRAY_BUFFER, lod.vertices);
upload_if_needed(gl, gl.ELEMENT_ARRAY_BUFFER, lod.indices);
document.getElementById('debug-stats').innerHTML = `
<span>LOD level: ${lod_level}</span>
<span>Segments onscreen: ${index_count}</span>
<span>Canvas offset: (${state.canvas.offset.x}, ${state.canvas.offset.y})</span>
<span>Canvas zoom: ${Math.round(state.canvas.zoom * 100000) / 100000}</span>`;
if (index_count > 0) {
// DEPTH PREPASS
if (state.debug.do_prepass) {

58
client/webgl_geometry.js

@ -36,40 +36,50 @@ function geometry_prepare_stroke(state) { @@ -36,40 +36,50 @@ function geometry_prepare_stroke(state) {
};
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
if (!state.online || !stroke || stroke.points.length === 0) return;
stroke.index = state.events.length;
function geometry_write_instances(state, context) {
context.instance_data = ser_ensure(context.instance_data, state.coordinates.count / 2 * config.bytes_per_instance);
ser_clear(context.instance_data);
for (let i = 0; i < config.lod_levels; ++i) {
const lod = context.lods[i];
let segment_count = 0;
const points = (i > 0 ? process_stroke(state, lod.max_zoom, stroke.points) : stroke.points);
const segment_serializer = lod.segments = ser_ensure_by(lod.segments, (points.length - 1) * config.bytes_per_quad);
for (let i = 0; i < context.clipped_indices.count; ++i) {
const stroke_index = context.clipped_indices.data[i];
const stroke = state.events[stroke_index];
const lod_indices_count = process_stroke(state, state.canvas.zoom, stroke);
let starting_index = 0;
segment_count += lod_indices_count - 1;
if (state.events.length > 0) {
const last_stroke = state.events[stroke_index - 1].lods[i];
starting_index = last_stroke.starting_index + (last_stroke.points.length - 1) * 4;
}
let base_this = 0;
let base_next = 0;
stroke.lods.push({
'points': points,
'starting_index': starting_index,
'width': stroke.width,
'color': stroke.color,
});
for (let j = 0; j < lod_indices_count - 1; ++j) {
while (state.rdp_mask[base_this] == 0) base_this++;
base_next = base_this + 1;
while (state.rdp_mask[base_next] == 0) base_next++;
context.lods[i].total_points += points.length;
const ax = state.coordinates.data[stroke.coords_from + base_this * 2 + 0];
const ay = state.coordinates.data[stroke.coords_from + base_this * 2 + 1];
const bx = state.coordinates.data[stroke.coords_from + base_next * 2 + 0];
const by = state.coordinates.data[stroke.coords_from + base_next * 2 + 1];
push_stroke(segment_serializer, stroke.lods[stroke.lods.length - 1], stroke_index);
ser_f32(context.instance_data, ax);
ser_f32(context.instance_data, ay);
ser_f32(context.instance_data, bx);
ser_f32(context.instance_data, by);
ser_u32(context.instance_data, stroke_index);
if (i === 0) {
stroke.bbox = stroke_bbox(stroke);
stroke.area = (stroke.bbox.x2 - stroke.bbox.x1) * (stroke.bbox.y2 - stroke.bbox.y1);
base_this = base_next;
}
}
return segment_count;
}
function geometry_add_stroke(state, context, stroke, stroke_index, skip_bvh = false) {
if (!state.online || !stroke || stroke.coords_to - stroke.coords_from === 0) return;
stroke.bbox = stroke_bbox(state, stroke);
stroke.area = box_area(stroke.bbox);
context.stroke_data = ser_ensure_by(context.stroke_data, config.bytes_per_stroke);

2
client/webgl_listeners.js

@ -451,7 +451,7 @@ function touchend(e, state, context) { @@ -451,7 +451,7 @@ function touchend(e, state, context) {
const stroke = geometry_prepare_stroke(state);
if (stroke) {
if (false && stroke) { // TODO: FIX!
geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index
queue_event(state, stroke_event(state));
geometry_clear_player(state, context, state.me);

1
client/webgl_shaders.js

@ -333,6 +333,7 @@ function init_webgl(state, context) { @@ -333,6 +333,7 @@ function init_webgl(state, context) {
context.buffers['sdf'] = {
'b_packed_dynamic': gl.createBuffer(),
'b_packed_dynamic_index': gl.createBuffer(),
'b_instance': gl.createBuffer(),
};
context.textures = {

BIN
server/data-local.sqlite

Binary file not shown.

2
server/recv.js

@ -127,6 +127,8 @@ function handle_event(session, event) { @@ -127,6 +127,8 @@ function handle_event(session, event) {
'$y': 0,
});
desks[session.desk_id].total_points += event.points.length;
break;
}

7
server/send.js

@ -7,7 +7,7 @@ import { MESSAGE, SESSION, EVENT } from './enums'; @@ -7,7 +7,7 @@ import { MESSAGE, SESSION, EVENT } from './enums';
import { sessions, desks } from './storage';
function event_size(event) {
let size = 1 + 4; // type + user_id
let size = 4 + 4; // type + user_id
switch (event.type) {
case EVENT.PREDRAW: {
@ -94,7 +94,7 @@ export async function send_init(ws) { @@ -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; // opcode + user_id + lsn + event count + stroke count + user count
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 session = null;
if (session_id in sessions && sessions[session_id].desk_id == desk_id) {
@ -142,6 +142,7 @@ export async function send_init(ws) { @@ -142,6 +142,7 @@ export async function send_init(ws) {
ser.u32(s, desk.events.length);
ser.u32(s, user_count);
ser.u32(s, desk.total_points);
for (const sid in sessions) {
const other_session = sessions[sid];
@ -153,6 +154,8 @@ export async function send_init(ws) { @@ -153,6 +154,8 @@ export async function send_init(ws) {
}
}
ser.align(s, 4);
for (const event of desk.events) {
ser.event(s, event);
}

9
server/serializer.js

@ -36,8 +36,15 @@ export function bytes(s, bytes) { @@ -36,8 +36,15 @@ export function bytes(s, bytes) {
s.offset += bytes.byteLength;
}
export function align(s, to) {
// TODO: non-stupid version of this
while (s.offset % to != 0) {
s.offset++;
}
}
export function event(s, event) {
u8(s, event.type);
u32(s, event.type); // for alignment reasons
u32(s, event.user_id);
switch (event.type) {

15
server/storage.js

@ -100,22 +100,25 @@ export function startup() { @@ -100,22 +100,25 @@ export function startup() {
const stroke_dict = {};
for (const stroke of stored_strokes) {
stroke.points = new Float32Array(stroke.points.buffer);
stroke_dict[stroke.id] = stroke;
}
for (const desk of stored_desks) {
desks[desk.id] = desk;
desks[desk.id].events = [];
desks[desk.id].total_points = 0;
}
for (const stroke of stored_strokes) {
stroke.points = new Float32Array(stroke.points.buffer);
stroke_dict[stroke.id] = stroke;
}
for (const event of stored_events) {
if (event.type === EVENT.STROKE) {
const stroke = stroke_dict[event.stroke_id];
event.points = stroke.points;
event.color = stroke.color;
event.width = stroke.width;
desks[event.desk_id].total_points += stroke.points.length / 2;
}
desks[event.desk_id].events.push(event);

Loading…
Cancel
Save