From adbe4a2693644507893782c65117449ad381a260 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Wed, 7 Oct 2026 08:57:37 +0200 Subject: [PATCH] copy on wasm side to avoid any data corruption --- gpx-rs/wasm/src/lib.rs | 29 ++++++++++------------------ website/src/lib/engine.ts | 40 +++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/gpx-rs/wasm/src/lib.rs b/gpx-rs/wasm/src/lib.rs index 84fbeb49a..f73ba0f2a 100644 --- a/gpx-rs/wasm/src/lib.rs +++ b/gpx-rs/wasm/src/lib.rs @@ -114,8 +114,7 @@ pub fn start() { // Statistics buffers // -// These are views into wasm memory: they are invalidated by the next command (and any -// allocation), so read or copy them right away. +// Each getter returns a copy of the buffer, as a new JS typed array. fn with_stats(f: impl FnOnce(&engine::StatisticsBuffer) -> T) -> Option { ENGINE.with(|engine| engine.borrow().as_ref().map(|e| f(e.statistics()))) @@ -125,8 +124,7 @@ macro_rules! stats_getter { ($name:ident, $array:ident) => { #[wasm_bindgen] pub fn $name() -> $array { - with_stats(|s| unsafe { $array::view(&s.$name) }) - .unwrap_or_else(|| $array::new_with_length(0)) + with_stats(|s| $array::from(&s.$name[..])).unwrap_or_else(|| $array::new_with_length(0)) } }; } @@ -152,12 +150,7 @@ macro_rules! optional_stats_getter { ($name:ident, $field:ident, $array:ident) => { #[wasm_bindgen] pub fn $name() -> Option<$array> { - with_stats(|s| { - s.$field - .as_ref() - .map(|values| unsafe { $array::view(values) }) - }) - .flatten() + with_stats(|s| s.$field.as_deref().map($array::from)).flatten() } }; } @@ -185,13 +178,13 @@ macro_rules! intervals_getters { ($starts:ident, $values:ident, $field:ident) => { #[wasm_bindgen] pub fn $starts() -> Uint32Array { - with_stats(|s| unsafe { Uint32Array::view(&s.$field.starts) }) + with_stats(|s| Uint32Array::from(&s.$field.starts[..])) .unwrap_or_else(|| Uint32Array::new_with_length(0)) } #[wasm_bindgen] pub fn $values() -> Uint8Array { - with_stats(|s| unsafe { Uint8Array::view(&s.$field.values) }) + with_stats(|s| Uint8Array::from(&s.$field.values[..])) .unwrap_or_else(|| Uint8Array::new_with_length(0)) } }; @@ -1214,14 +1207,12 @@ pub fn last_update() -> FilesUpdate { // Coordinates buffers // -// Like the statistics buffers, these are views into wasm memory, invalidated by the next -// command (and any allocation): read or copy them right away. They are flat `[lng, lat, ...]` -// arrays. Compare `rev` / `waypointsRev` of the file structures with the previous ones to know which +// Like the statistics buffers, these are copies. They are flat `[lng, lat, ...]` arrays. Compare `rev` / `waypointsRev` of the file structures with the previous ones to know which // buffers actually changed. -fn coordinates_view(f: impl FnOnce(&Engine) -> Option<&[f64]>) -> Float64Array { +fn coordinates_array(f: impl FnOnce(&Engine) -> Option<&[f64]>) -> Float64Array { ENGINE.with(|engine| match engine.borrow().as_ref().and_then(f) { - Some(coordinates) => unsafe { Float64Array::view(coordinates) }, + Some(coordinates) => Float64Array::from(coordinates), None => Float64Array::new_with_length(0), }) } @@ -1230,7 +1221,7 @@ fn coordinates_view(f: impl FnOnce(&Engine) -> Option<&[f64]>) -> Float64Array { #[wasm_bindgen] pub fn segment_coordinates(segment_id: &str) -> Float64Array { match uuid::Uuid::parse_str(segment_id) { - Ok(id) => coordinates_view(|e| Some(e.segment_coordinates(&engine::TrackSegmentId(id)))), + Ok(id) => coordinates_array(|e| Some(e.segment_coordinates(&engine::TrackSegmentId(id)))), Err(_) => Float64Array::new_with_length(0), } } @@ -1239,7 +1230,7 @@ pub fn segment_coordinates(segment_id: &str) -> Float64Array { #[wasm_bindgen] pub fn waypoint_coordinates(file_id: &str) -> Float64Array { match uuid::Uuid::parse_str(file_id) { - Ok(id) => coordinates_view(|e| Some(e.waypoint_coordinates(&FileId(id)))), + Ok(id) => coordinates_array(|e| Some(e.waypoint_coordinates(&FileId(id)))), Err(_) => Float64Array::new_with_length(0), } } diff --git a/website/src/lib/engine.ts b/website/src/lib/engine.ts index f7b0527ef..205d29996 100644 --- a/website/src/lib/engine.ts +++ b/website/src/lib/engine.ts @@ -566,15 +566,15 @@ class Engine { store.update((state) => ({ ...state, visibility })); } - // Coordinates, as flat [lng, lat, ...] arrays. They are copied out of the WASM memory. + // Coordinates, as flat [lng, lat, ...] arrays. The getters copy them out of the WASM memory. // Refetch them when the `rev` of the segment (or the `waypointsRev` of the file) changed. segmentCoordinates(segmentId: string): Float64Array { - return this.wasm?.segment_coordinates(segmentId).slice() ?? new Float64Array(); + return this.wasm?.segment_coordinates(segmentId) ?? new Float64Array(); } waypointCoordinates(fileId: string): Float64Array { - return this.wasm?.waypoint_coordinates(fileId).slice() ?? new Float64Array(); + return this.wasm?.waypoint_coordinates(fileId) ?? new Float64Array(); } private async run(action: (wasm: Wasm) => boolean): Promise { @@ -640,22 +640,22 @@ class Engine { } /** - * Copies the statistics buffers of the engine (they are overwritten by the next action), but - * only the metrics that were requested. + * Reads the statistics buffers of the engine (the getters copy them), but only the metrics + * that were requested. */ private readStatistics(wasm: Wasm): SelectionStatistics { const version = ++this._statisticsVersion; this._loadedMetrics.clear(); - const totalDistance = wasm.total_distance().slice(); + const totalDistance = wasm.total_distance(); const statistics: SelectionStatistics = { global: wasm.selection_statistics() ?? EMPTY_STATISTICS, length: totalDistance.length, totalDistance, - slope: wasm.slope().slice(), - lng: wasm.lng().slice(), - lat: wasm.lat().slice(), - ele: wasm.ele().slice(), - timestamps: wasm.timestamps()?.slice(), + slope: wasm.slope(), + lng: wasm.lng(), + lat: wasm.lat(), + ele: wasm.ele(), + timestamps: wasm.timestamps(), slice: (start, end) => version === this._statisticsVersion ? wasm.slice_statistics(start, end) : undefined, }; @@ -680,8 +680,8 @@ class Engine { values: Uint8Array, names: () => string[] ): CategoryIntervals => ({ - starts: starts.slice(), - values: values.slice(), + starts, + values, names: names(), }); @@ -692,23 +692,23 @@ class Engine { this._loadedMetrics.add(metric); switch (metric) { case 'speed': - statistics.speed = wasm.speed().slice(); + statistics.speed = wasm.speed(); break; case 'hr': - statistics.hr = wasm.hr()?.slice(); + statistics.hr = wasm.hr(); break; case 'cad': - statistics.cad = wasm.cad()?.slice(); + statistics.cad = wasm.cad(); break; case 'atemp': - statistics.atemp = wasm.atemp()?.slice(); + statistics.atemp = wasm.atemp(); break; case 'power': - statistics.power = wasm.power()?.slice(); + statistics.power = wasm.power(); break; case 'slopeSegment': - statistics.slopeSegmentSlope = wasm.slope_segment_slope().slice(); - statistics.slopeSegmentDistance = wasm.slope_segment_distance().slice(); + statistics.slopeSegmentSlope = wasm.slope_segment_slope(); + statistics.slopeSegmentDistance = wasm.slope_segment_distance(); break; case 'surface': statistics.surface = categories(