grid_engine/
node.rs

1// Copyright (c) 2025 Thiago Ramos
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Node representation and management for grid items.
22//!
23//! This module provides the [`Node`] type which represents an item in the grid.
24//! Each node has a position (x, y), dimensions (width, height), and a unique identifier.
25//! Nodes are managed by the grid engine and can be added, moved, or removed from the grid.
26
27use crate::{
28    error::InnerGridError,
29    inner_grid::{InnerGrid, UpdateGridOperation},
30    utils::{ForCellArgs, for_cell},
31};
32
33/// Represents an item in the grid with position and dimensions.
34///
35/// A node occupies a rectangular area in the grid defined by:
36/// - Its top-left corner position (x, y)
37/// - Its dimensions (width, height)
38/// - A unique identifier
39///
40/// The node's area can be iterated over using the `for_cell` method,
41/// which visits each cell in the node's occupied space.
42#[derive(Clone, Debug, PartialEq, Eq, Hash)]
43pub struct Node {
44    /// Unique identifier for the node
45    pub(crate) id: String,
46    /// X coordinate of the top-left corner
47    pub(crate) x: usize,
48    /// Y coordinate of the top-left corner
49    pub(crate) y: usize,
50    /// Width of the node in grid cells
51    pub(crate) w: usize,
52    /// Height of the node in grid cells
53    pub(crate) h: usize,
54}
55
56impl Node {
57    /// Creates a new Node with the specified parameters.
58    ///
59    /// # Arguments
60    ///
61    /// * `id` - Unique identifier for the node
62    /// * `x` - X coordinate of the top-left corner
63    /// * `y` - Y coordinate of the top-left corner
64    /// * `w` - Width in grid cells
65    /// * `h` - Height in grid cells
66    pub(crate) fn new(id: String, x: usize, y: usize, w: usize, h: usize) -> Node {
67        Node { id, x, y, w, h }
68    }
69
70    /// Iterates over all cells occupied by this node.
71    ///
72    /// This method provides a way to perform operations on each cell
73    /// that the node occupies in the grid. The callback is called with
74    /// the coordinates of each cell.
75    ///
76    /// # Arguments
77    ///
78    /// * `callback` - Function to execute for each cell
79    ///
80    /// # Returns
81    ///
82    /// * `Ok(())` if all cells were processed successfully
83    /// * `Err(InnerGridError)` if the callback returns an error
84    pub(crate) fn for_cell(
85        &self,
86        callback: &mut impl FnMut(usize, usize) -> Result<(), InnerGridError>,
87    ) -> Result<(), InnerGridError> {
88        for_cell(
89            ForCellArgs {
90                x: self.x,
91                y: self.y,
92                w: self.w,
93                h: self.h,
94            },
95            callback,
96        )
97    }
98
99    /// Updates the grid state for this node.
100    ///
101    /// Used internally to modify the grid when a node is added, moved,
102    /// or removed. The operation type determines how the grid is updated.
103    ///
104    /// # Arguments
105    ///
106    /// * `grid` - The grid to update
107    /// * `update_operation` - The type of update to perform (Add/Remove)
108    ///
109    /// # Returns
110    ///
111    /// * `Ok(())` if the update was successful
112    /// * `Err(InnerGridError)` if the update fails (e.g., out of bounds)
113    pub(crate) fn update_grid(
114        &self,
115        grid: &mut InnerGrid,
116        update_operation: UpdateGridOperation,
117    ) -> Result<(), InnerGridError> {
118        self.for_cell(&mut |x, y| grid.update(self, x, y, update_operation))?;
119
120        Ok(())
121    }
122
123    /// Returns the unique identifier of the node.
124    pub fn id(&self) -> &str {
125        &self.id
126    }
127
128    /// Returns the x coordinate of the node.
129    pub fn x(&self) -> &usize {
130        &self.x
131    }
132
133    /// Returns the y coordinate of the node.
134    pub fn y(&self) -> &usize {
135        &self.y
136    }
137
138    /// Returns the width of the node.
139    pub fn w(&self) -> &usize {
140        &self.w
141    }
142
143    /// Returns the height of the node.
144    pub fn h(&self) -> &usize {
145        &self.h
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_node_creation() {
155        let node = Node::new("test_node".to_string(), 1, 2, 3, 4);
156
157        assert_eq!(node.id, "test_node");
158        assert_eq!(node.x, 1);
159        assert_eq!(node.y, 2);
160        assert_eq!(node.w, 3);
161        assert_eq!(node.h, 4);
162    }
163
164    #[test]
165    fn test_get_id() {
166        let node = Node::new("test_node".to_string(), 0, 0, 1, 1);
167
168        assert_eq!(node.id.clone(), "test_node");
169        // Test that get_id returns a clone
170        let id1 = node.id.clone();
171        let id2 = node.id.clone();
172        assert_eq!(id1, id2);
173    }
174
175    #[test]
176    fn test_for_cell() {
177        let node = Node::new("test_node".to_string(), 1, 2, 2, 2);
178
179        let mut visited = vec![];
180        node.for_cell(&mut |x, y| {
181            visited.push((x, y));
182            Ok(())
183        })
184        .unwrap();
185
186        assert_eq!(
187            visited,
188            vec![(1, 2), (1, 3), (2, 2), (2, 3)],
189            "Should visit all cells in the node's area"
190        );
191    }
192
193    #[test]
194    fn test_for_cell_error_propagation() {
195        let node = Node::new("test_node".to_string(), 0, 0, 2, 2);
196
197        let result = node.for_cell(&mut |x, _y| {
198            if x > 0 {
199                Err(crate::error::InnerGridError::OutOfBoundsAccess { x: 0, y: 0 })
200            } else {
201                Ok(())
202            }
203        });
204
205        assert!(result.is_err());
206    }
207
208    #[test]
209    fn test_for_cell_zero_dimensions() {
210        let node = Node::new("test_node".to_string(), 0, 0, 0, 0);
211
212        let mut visited = vec![];
213        node.for_cell(&mut |x, y| {
214            visited.push((x, y));
215            Ok(())
216        })
217        .unwrap();
218
219        assert!(
220            visited.is_empty(),
221            "Should not visit any cells for zero dimensions"
222        );
223    }
224}