From 435bb373faa299e1663e7bc344bc73b23542c9dc Mon Sep 17 00:00:00 2001 From: Federico Lolli <federico.lolli@skywarder.eu> Date: Sun, 13 Apr 2025 17:23:07 +0200 Subject: [PATCH] fixed bug with removal of elements and connections related crashes --- src/ui/panes/pid_drawing_tool.rs | 72 ++++++++++++-------- src/ui/panes/pid_drawing_tool/connections.rs | 16 ++--- src/utils.rs | 1 + src/utils/id.rs | 31 +++++++++ 4 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 src/utils/id.rs diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs index 9257cf4..3d4ed43 100644 --- a/src/ui/panes/pid_drawing_tool.rs +++ b/src/ui/panes/pid_drawing_tool.rs @@ -3,17 +3,14 @@ mod elements; mod grid; mod symbols; -use connections::Connection; use core::f32; use egui::{ Button, Color32, Context, CursorIcon, PointerButton, Response, Sense, Theme, Ui, Widget, }; -use elements::Element; use glam::Vec2; -use grid::GridInfo; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use strum::IntoEnumIterator; -use symbols::{Symbol, icons::Icon}; use crate::{ MAVLINK_PROFILE, @@ -22,15 +19,21 @@ use crate::{ ui::{ app::PaneResponse, cache::ChangeTracker, shortcuts::ShortcutHandler, utils::egui_to_glam, }, + utils::id::IdGenerator, }; use super::PaneBehavior; +use connections::Connection; +use elements::Element; +use grid::GridInfo; +use symbols::{Symbol, icons::Icon}; + #[derive(Clone, Debug)] enum Action { - Connect(usize), + Connect(u32), ContextMenu(Vec2), - DragElement(usize), + DragElement(u32), DragConnection(usize, usize), DragGrid, } @@ -39,7 +42,7 @@ enum Action { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct PidPane { // Persistent internal state - elements: Vec<Element>, + elements: HashMap<u32, Element>, connections: Vec<Connection>, grid: GridInfo, message_subscription_id: u32, @@ -49,6 +52,8 @@ pub struct PidPane { // Temporary internal state #[serde(skip)] + id_generator: IdGenerator, + #[serde(skip)] action: Option<Action>, #[serde(skip)] editable: bool, @@ -59,11 +64,12 @@ pub struct PidPane { impl Default for PidPane { fn default() -> Self { Self { - elements: Vec::new(), + elements: HashMap::new(), connections: Vec::new(), grid: GridInfo::default(), message_subscription_id: GSE_TM_DATA::ID, center_content: false, + id_generator: IdGenerator::new(), action: None, editable: false, is_subs_window_visible: false, @@ -146,7 +152,7 @@ impl PaneBehavior for PidPane { fn update(&mut self, messages: &[&TimedMessage]) { if let Some(msg) = messages.last() { - for element in &mut self.elements { + for element in self.elements.values_mut() { element.update(&msg.message, self.message_subscription_id); } } @@ -181,10 +187,11 @@ impl PidPane { } /// Returns the index of the element the point is on, if any - fn hovers_element(&self, p_s: Vec2) -> Option<usize> { + fn hovers_element(&self, p_s: Vec2) -> Option<u32> { self.elements .iter() - .position(|elem| elem.contains(self.grid.screen_to_grid(p_s))) + .find(|(_, elem)| elem.contains(self.grid.screen_to_grid(p_s))) + .map(|(idx, _)| *idx) } /// Return the connection and segment indexes where the position is on, if any @@ -245,7 +252,7 @@ impl PidPane { } fn elements_ui(&mut self, ui: &mut Ui, theme: Theme) { - for element in &mut self.elements { + for element in self.elements.values_mut() { ui.scope(|ui| { element.ui(ui, &self.grid, theme, self.message_subscription_id); }); @@ -271,7 +278,10 @@ impl PidPane { ui.close_menu(); } let btn_response = Button::new("Delete").ui(ui); - self.elements[elem_idx].context_menu(ui); + self.elements + .get_mut(&elem_idx) + .log_unwrap() + .context_menu(ui); // Handle the delete button if btn_response.clicked() { self.delete_element(elem_idx); @@ -287,14 +297,14 @@ impl PidPane { if ui.button("Change start anchor").clicked() { let conn = &mut self.connections[conn_idx]; conn.start_anchor = - (conn.start_anchor + 1) % self.elements[conn.start].anchor_points_len(); + (conn.start_anchor + 1) % self.elements[&conn.start].anchor_points_len(); self.action.take(); ui.close_menu(); } if ui.button("Change end anchor").clicked() { let conn = &mut self.connections[conn_idx]; conn.end_anchor = - (conn.end_anchor + 1) % self.elements[conn.end].anchor_points_len(); + (conn.end_anchor + 1) % self.elements[&conn.end].anchor_points_len(); self.action.take(); ui.close_menu(); } @@ -305,20 +315,23 @@ impl PidPane { ui.menu_button("Icons", |ui| { for icon in Icon::iter() { if ui.button(icon.to_string()).clicked() { - self.elements.push(Element::new( - self.grid.screen_to_grid(pointer_pos).round(), - Symbol::Icon(icon), - )); + self.elements.insert( + self.id_generator.next_id(), + Element::new( + self.grid.screen_to_grid(pointer_pos).round(), + Symbol::Icon(icon), + ), + ); self.action.take(); ui.close_menu(); } } }); } else if ui.button(symbol.to_string()).clicked() { - self.elements.push(Element::new( - self.grid.screen_to_grid(pointer_pos).round(), - symbol, - )); + self.elements.insert( + self.id_generator.next_id(), + Element::new(self.grid.screen_to_grid(pointer_pos).round(), symbol), + ); self.action.take(); ui.close_menu(); } @@ -338,12 +351,12 @@ impl PidPane { } /// Removes an element from the diagram - fn delete_element(&mut self, elem_idx: usize) { + fn delete_element(&mut self, elem_idx: u32) { // First delete connection referencing this element self.connections.retain(|elem| !elem.connected(elem_idx)); // Then the element - self.elements.remove(elem_idx); + self.elements.remove(&elem_idx); } fn center(&mut self, ui: &Ui) { @@ -352,7 +365,7 @@ impl PidPane { // Chain elements positions and connection mid points let points: Vec<Vec2> = self .elements - .iter() + .values() .map(|e| e.center()) .chain(self.connections.iter().flat_map(|conn| conn.points())) .collect(); @@ -436,7 +449,10 @@ impl PidPane { } Some(Action::DragElement(idx)) => { let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round(); - self.elements[idx].set_center_at(pointer_pos_g); + self.elements + .get_mut(&idx) + .log_unwrap() + .set_center_at(pointer_pos_g); } Some(Action::DragConnection(conn_idx, point_idx)) => { let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round(); @@ -452,7 +468,7 @@ impl PidPane { } fn reset_subscriptions(&mut self) { - for element in &mut self.elements { + for element in self.elements.values_mut() { element.reset_subscriptions(); } } diff --git a/src/ui/panes/pid_drawing_tool/connections.rs b/src/ui/panes/pid_drawing_tool/connections.rs index 3cde5f8..942984f 100644 --- a/src/ui/panes/pid_drawing_tool/connections.rs +++ b/src/ui/panes/pid_drawing_tool/connections.rs @@ -12,11 +12,11 @@ use super::{ #[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] pub struct Connection { /// Index of the start element - pub start: usize, + pub start: u32, pub start_anchor: usize, /// Index of the end element - pub end: usize, + pub end: u32, pub end_anchor: usize, /// Mid points in grid coordinates @@ -24,7 +24,7 @@ pub struct Connection { } impl Connection { - pub fn new(start: usize, start_anchor: usize, end: usize, end_anchor: usize) -> Self { + pub fn new(start: u32, start_anchor: usize, end: u32, end_anchor: usize) -> Self { Self { start, start_anchor, @@ -45,13 +45,13 @@ impl Connection { let mut points = Vec::new(); // Append start point - points.push(pid.elements[self.start].anchor_point(self.start_anchor)); + points.push(pid.elements[&self.start].anchor_point(self.start_anchor)); // Append all midpoints self.points_g.iter().for_each(|&p| points.push(p)); // Append end point - points.push(pid.elements[self.end].anchor_point(self.end_anchor)); + points.push(pid.elements[&self.end].anchor_point(self.end_anchor)); // Check each segment for i in 0..(points.len() - 1) { @@ -66,7 +66,7 @@ impl Connection { } /// Checks if the connection references the given element index - pub fn connected(&self, elem_idx: usize) -> bool { + pub fn connected(&self, elem_idx: u32) -> bool { self.start == elem_idx || self.end == elem_idx } @@ -97,9 +97,9 @@ impl Connection { pub fn draw(&self, pid: &PidPane, painter: &Painter, theme: Theme) { let color = Connection::line_color(theme); - let start = pid.elements[self.start].anchor_point(self.start_anchor); + let start = pid.elements[&self.start].anchor_point(self.start_anchor); let start = pid.grid.grid_to_screen(start); - let end = pid.elements[self.end].anchor_point(self.end_anchor); + let end = pid.elements[&self.end].anchor_point(self.end_anchor); let end = pid.grid.grid_to_screen(end); // Draw line segments diff --git a/src/utils.rs b/src/utils.rs index 503a473..3ff5656 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,2 +1,3 @@ +pub mod id; mod ring_buffer; pub mod units; diff --git a/src/utils/id.rs b/src/utils/id.rs new file mode 100644 index 0000000..04b06ea --- /dev/null +++ b/src/utils/id.rs @@ -0,0 +1,31 @@ +use std::collections::HashSet; + +/// A simple id generator that wraps around when it reaches the maximum value. +#[derive(Debug, Clone, Default)] +pub struct IdGenerator { + current: u32, + already_used: HashSet<u32>, +} + +impl IdGenerator { + pub fn new() -> Self { + Self::default() + } + + /// Get the next id, wrapping around if necessary. + pub fn next_id(&mut self) -> u32 { + loop { + self.current = self.current.wrapping_add(1); + if !self.already_used.contains(&self.current) { + self.already_used.insert(self.current); + return self.current; + } + } + } + + /// Release an id, making it available for reuse. Do not change the current + /// id to avoid useless checks and exploit wrapping increment. + pub fn release_id(&mut self, id: u32) { + self.already_used.remove(&id); + } +} -- GitLab