This commit is contained in:
vcoppe
2026-10-01 18:42:31 +02:00
parent eb8e09b835
commit 5868595a6c
9 changed files with 175 additions and 70 deletions
+16 -8
View File
@@ -1,3 +1,5 @@
// TODO license in every file
use std::rc::Rc; use std::rc::Rc;
use js_sys::Function; use js_sys::Function;
@@ -9,20 +11,14 @@ use crate::{
io::parse, io::parse,
selection::Selection, selection::Selection,
state::{Stack, StackEntry}, state::{Stack, StackEntry},
statistics::StatisticsBuffer,
}; };
// Control flow
// - get lock to cancel any other updates
// - update files
// - update separate statistics
// - update selection
// - update selection statistics
// - notify UI through callback (changed files, updated selection, new stats)
#[wasm_bindgen] #[wasm_bindgen]
pub struct Engine { pub struct Engine {
stack: Stack, stack: Stack,
selection: Selection, selection: Selection,
statistics: StatisticsBuffer,
callback: Function, callback: Function,
} }
@@ -33,6 +29,7 @@ impl Engine {
Self { Self {
stack: Default::default(), stack: Default::default(),
selection: Default::default(), selection: Default::default(),
statistics: Default::default(),
callback, callback,
} }
} }
@@ -65,4 +62,15 @@ impl Engine {
.create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some()); .create_and_push_next(|entry: &mut StackEntry| entry.remove(&id).is_some());
} }
} }
fn event_loop(&mut self) {
// Control flow
// - get lock to cancel any other updates
// - update files
// - update separate statistics
// - update selection
// - update selection statistics
// self.statistics.update(stats);
// - notify UI through callback (changed files, updated selection, new stats)
}
} }
+36 -2
View File
@@ -1,12 +1,30 @@
use uuid::Uuid;
use crate::gpx::{Trackpoint, Waypoint}; use crate::gpx::{Trackpoint, Waypoint};
static MAX_TRKPT_CHUNK_SIZE: usize = 4096; static MAX_TRKPT_CHUNK_SIZE: usize = 4096;
#[derive(Debug, Default)] #[derive(Debug)]
pub struct TrackpointChunk { pub struct TrackpointChunk {
pub id: Uuid,
pub trkpt: Vec<Trackpoint>, pub trkpt: Vec<Trackpoint>,
} }
impl Default for TrackpointChunk {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
trkpt: Default::default(),
}
}
}
impl PartialEq for TrackpointChunk {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl TrackpointChunk { impl TrackpointChunk {
pub fn is_full(&self) -> bool { pub fn is_full(&self) -> bool {
self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE self.trkpt.len() == MAX_TRKPT_CHUNK_SIZE
@@ -15,11 +33,27 @@ impl TrackpointChunk {
static MAX_WPT_CHUNK_SIZE: usize = 128; static MAX_WPT_CHUNK_SIZE: usize = 128;
#[derive(Debug, Default)] #[derive(Debug)]
pub struct WaypointChunk { pub struct WaypointChunk {
pub id: Uuid,
pub wpt: Vec<Waypoint>, pub wpt: Vec<Waypoint>,
} }
impl Default for WaypointChunk {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
wpt: Default::default(),
}
}
}
impl PartialEq for WaypointChunk {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl WaypointChunk { impl WaypointChunk {
pub fn is_full(&self) -> bool { pub fn is_full(&self) -> bool {
self.wpt.len() == MAX_WPT_CHUNK_SIZE self.wpt.len() == MAX_WPT_CHUNK_SIZE
+1 -1
View File
@@ -1,4 +1,4 @@
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq, Eq)]
pub struct Link { pub struct Link {
pub href: String, pub href: String,
pub text: Option<String>, pub text: Option<String>,
+3 -3
View File
@@ -4,7 +4,7 @@ use uuid::Uuid;
use crate::gpx::{Link, Track, WaypointChunk}; use crate::gpx::{Link, Track, WaypointChunk};
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct GPXFile { pub struct GPXFile {
pub id: Uuid, pub id: Uuid,
pub info: GPXFileInfo, pub info: GPXFileInfo,
@@ -24,7 +24,7 @@ impl Default for GPXFile {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct GPXFileInfo { pub struct GPXFileInfo {
pub name: String, pub name: String,
pub desc: Option<String>, pub desc: Option<String>,
@@ -33,7 +33,7 @@ pub struct GPXFileInfo {
pub time: Option<i64>, pub time: Option<i64>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct Author { pub struct Author {
pub name: Option<String>, pub name: Option<String>,
pub email: Option<String>, pub email: Option<String>,
+1 -1
View File
@@ -2,7 +2,7 @@ use std::{ops::Index, rc::Rc};
use crate::gpx::{Trackpoint, TrackpointChunk}; use crate::gpx::{Trackpoint, TrackpointChunk};
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct TrackSegment { pub struct TrackSegment {
chunks: Vec<Rc<TrackpointChunk>>, chunks: Vec<Rc<TrackpointChunk>>,
cumul_length: Vec<usize>, cumul_length: Vec<usize>,
+2 -2
View File
@@ -1,12 +1,12 @@
use crate::gpx::{Link, TrackSegment}; use crate::gpx::{Link, TrackSegment};
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct Track { pub struct Track {
pub info: TrackInfo, pub info: TrackInfo,
pub trkseg: Vec<TrackSegment>, pub trkseg: Vec<TrackSegment>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct TrackInfo { pub struct TrackInfo {
pub name: Option<String>, pub name: Option<String>,
pub cmt: Option<String>, pub cmt: Option<String>,
+108 -48
View File
@@ -1,4 +1,7 @@
use std::{collections::HashMap, rc::Rc}; use std::{
collections::{HashMap, HashSet},
rc::Rc,
};
use uuid::Uuid; use uuid::Uuid;
@@ -12,42 +15,47 @@ pub struct Stack {
impl Stack { impl Stack {
pub fn current(&self) -> Option<&StackEntry> { pub fn current(&self) -> Option<&StackEntry> {
match self.index { self.index.map_or_default(|i| self.get(i))
Some(i) => Some(&self.entries[i]),
None => None,
}
} }
pub fn create_and_push_next<F>(&mut self, f: F) pub fn create_and_push_next<F>(&mut self, f: F) -> Option<Diff>
where where
F: FnOnce(&mut StackEntry) -> bool, F: FnOnce(&mut StackEntry) -> bool,
{ {
let mut next = self.current().map_or_default(|c| c.clone()); self.record_diff(|stack| {
let mut next = stack.current().map_or_default(|c| c.clone());
if f(&mut next) { if f(&mut next) {
self.push(next); stack.push(next);
} }
})
} }
// pub fn update(&mut self, files: &[Rc<GPXFile>], ids: &[GPXFileId]) { pub fn undo(&mut self) -> Option<Diff> {
// let mut next = match self.current() { self.record_diff(|stack| {
// Some(current) => current.clone(), if let Some(i) = stack.index {
// None => StackEntry::default(), if i == 0 {
// }; stack.index = None;
// for (file, id) in files.iter().zip(ids) { } else {
// next.insert(*id, file.clone()); stack.index = Some(i - 1);
// } }
// self.push(next); }
// } })
}
// pub fn delete(&mut self, files: &[GPXFileId]) { pub fn redo(&mut self) -> Option<Diff> {
// if let Some(current) = self.current() { self.record_diff(|stack| match stack.index {
// let mut next = current.clone(); Some(i) => {
// for file in files { if i + 1 < stack.entries.len() {
// next.remove(file); stack.index = Some(i + 1);
// } }
// self.push(next); }
// } None => {
// } if !stack.entries.is_empty() {
stack.index = Some(0);
}
}
})
}
pub fn can_undo(&self) -> bool { pub fn can_undo(&self) -> bool {
self.index.is_some() self.index.is_some()
@@ -60,28 +68,11 @@ impl Stack {
} }
} }
pub fn undo(&mut self) { fn get(&self, index: usize) -> Option<&StackEntry> {
if let Some(i) = self.index { if index < self.entries.len() {
if i == 0 { Some(&self.entries[index])
self.index = None;
} else { } else {
self.index = Some(i - 1); None
}
}
}
pub fn redo(&mut self) {
match self.index {
Some(i) => {
if i + 1 < self.entries.len() {
self.index = Some(i + 1);
}
}
None => {
if !self.entries.is_empty() {
self.index = Some(0);
}
}
} }
} }
@@ -95,6 +86,75 @@ impl Stack {
self.entries.push(entry); self.entries.push(entry);
self.index = Some(self.entries.len() - 1); self.index = Some(self.entries.len() - 1);
} }
fn record_diff<F>(&mut self, f: F) -> Option<Diff>
where
F: FnOnce(&mut Self),
{
let prev = self.index;
f(self);
let cur = self.index;
if prev == cur {
return None;
}
let prev = prev.map_or_default(|i| self.get(i));
let cur = cur.map_or_default(|i| self.get(i));
let prev_ids: HashSet<Uuid> = prev.map_or_default(|e| e.keys().copied().collect());
let cur_ids: HashSet<Uuid> = cur.map_or_default(|e| e.keys().copied().collect());
let mut modified = vec![];
for id in prev_ids.intersection(&cur_ids) {
let before = prev.map_or_default(|e| e.get(id));
let after = cur.map_or_default(|e| e.get(id));
if before != after {
modified.push(*id);
}
}
Some(Diff {
added: cur_ids.difference(&prev_ids).copied().collect(),
removed: prev_ids.difference(&cur_ids).copied().collect(),
modified,
})
}
} }
pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>; pub type StackEntry = HashMap<Uuid, Rc<GPXFile>>;
#[derive(Debug, Default)]
pub struct Diff {
pub added: Vec<Uuid>,
pub removed: Vec<Uuid>,
pub modified: Vec<Uuid>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_file() {
let mut stack = Stack::default();
assert!(!stack.can_undo());
assert!(!stack.can_redo());
assert!(stack.current().is_none());
let diff = stack.create_and_push_next(|e| {
let file = Rc::new(GPXFile::default());
e.insert(file.id, file);
true
});
assert!(stack.can_undo());
assert!(!stack.can_redo());
assert!(stack.current().is_some());
assert!(diff.is_some());
let diff = diff.unwrap();
assert_eq!(diff.added.len(), 1);
assert!(diff.removed.is_empty());
assert!(diff.modified.is_empty());
}
// TODO more tests
}
+3 -2
View File
@@ -1,6 +1,7 @@
use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options}; use crate::statistics::{GPXStatistics, GlobalStatistics, sum_options};
pub struct Buffer { #[derive(Debug, Default)]
pub struct StatisticsBuffer {
pub total_distance: Vec<f64>, pub total_distance: Vec<f64>,
pub moving_distance: Vec<f64>, pub moving_distance: Vec<f64>,
pub total_time: Vec<i64>, pub total_time: Vec<i64>,
@@ -13,7 +14,7 @@ pub struct Buffer {
pub slope_segment_distance: Vec<f64>, pub slope_segment_distance: Vec<f64>,
} }
impl Buffer { impl StatisticsBuffer {
pub fn update(&mut self, stats: &[&GPXStatistics]) { pub fn update(&mut self, stats: &[&GPXStatistics]) {
self.total_distance.clear(); self.total_distance.clear();
self.moving_distance.clear(); self.moving_distance.clear();
+2
View File
@@ -322,4 +322,6 @@ mod tests {
assert!((speed - 20.0).abs() < 0.1); assert!((speed - 20.0).abs() < 0.1);
} }
} }
// TODO more tests
} }