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 id: String,
46    /// X coordinate of the top-left corner
47    pub x: usize,
48    /// Y coordinate of the top-left corner
49    pub y: usize,
50    /// Width of the node in grid cells
51    pub w: usize,
52    /// Height of the node in grid cells
53    pub 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 fn new(id: impl Into<String>, x: usize, y: usize, w: usize, h: usize) -> Node {
67        Node {
68            id: id.into(),
69            x,
70            y,
71            w,
72            h,
73        }
74    }
75
76    /// Iterates over all cells occupied by this node.
77    ///
78    /// This method provides a way to perform operations on each cell
79    /// that the node occupies in the grid. The callback is called with
80    /// the coordinates of each cell.
81    ///
82    /// # Arguments
83    ///
84    /// * `callback` - Function to execute for each cell
85    ///
86    /// # Returns
87    ///
88    /// * `Ok(())` if all cells were processed successfully
89    /// * `Err(InnerGridError)` if the callback returns an error
90    pub(crate) fn for_cell(
91        &self,
92        callback: &mut impl FnMut(usize, usize) -> Result<(), InnerGridError>,
93    ) -> Result<(), InnerGridError> {
94        for_cell(
95            ForCellArgs {
96                x: self.x,
97                y: self.y,
98                w: self.w,
99                h: self.h,
100            },
101            callback,
102        )
103    }
104
105    /// Updates the grid state for this node.
106    ///
107    /// Used internally to modify the grid when a node is added, moved,
108    /// or removed. The operation type determines how the grid is updated.
109    ///
110    /// # Arguments
111    ///
112    /// * `grid` - The grid to update
113    /// * `update_operation` - The type of update to perform (Add/Remove)
114    ///
115    /// # Returns
116    ///
117    /// * `Ok(())` if the update was successful
118    /// * `Err(InnerGridError)` if the update fails (e.g., out of bounds)
119    pub(crate) fn update_grid(
120        &self,
121        grid: &mut InnerGrid,
122        update_operation: UpdateGridOperation,
123    ) -> Result<(), InnerGridError> {
124        self.for_cell(&mut |x, y| grid.update(self, x, y, update_operation))?;
125
126        Ok(())
127    }
128
129    /// Returns the unique identifier of the node.
130    pub fn id(&self) -> &str {
131        &self.id
132    }
133
134    /// Returns the x coordinate of the node.
135    pub fn x(&self) -> &usize {
136        &self.x
137    }
138
139    /// Returns the y coordinate of the node.
140    pub fn y(&self) -> &usize {
141        &self.y
142    }
143
144    /// Returns the width of the node.
145    pub fn w(&self) -> &usize {
146        &self.w
147    }
148
149    /// Returns the height of the node.
150    pub fn h(&self) -> &usize {
151        &self.h
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_node_creation() {
161        let node = Node::new("test_node".to_string(), 1, 2, 3, 4);
162
163        assert_eq!(node.id, "test_node");
164        assert_eq!(node.x, 1);
165        assert_eq!(node.y, 2);
166        assert_eq!(node.w, 3);
167        assert_eq!(node.h, 4);
168    }
169
170    #[test]
171    fn test_get_id() {
172        let node = Node::new("test_node".to_string(), 0, 0, 1, 1);
173
174        assert_eq!(node.id.clone(), "test_node");
175        // Test that get_id returns a clone
176        let id1 = node.id.clone();
177        let id2 = node.id.clone();
178        assert_eq!(id1, id2);
179    }
180
181    #[test]
182    fn test_for_cell() {
183        let node = Node::new("test_node".to_string(), 1, 2, 2, 2);
184
185        let mut visited = vec![];
186        node.for_cell(&mut |x, y| {
187            visited.push((x, y));
188            Ok(())
189        })
190        .unwrap();
191
192        assert_eq!(
193            visited,
194            vec![(1, 2), (1, 3), (2, 2), (2, 3)],
195            "Should visit all cells in the node's area"
196        );
197    }
198
199    #[test]
200    fn test_for_cell_error_propagation() {
201        let node = Node::new("test_node".to_string(), 0, 0, 2, 2);
202
203        let result = node.for_cell(&mut |x, _y| {
204            if x > 0 {
205                Err(crate::error::InnerGridError::OutOfBoundsAccess { x: 0, y: 0 })
206            } else {
207                Ok(())
208            }
209        });
210
211        assert!(result.is_err());
212    }
213
214    #[test]
215    fn test_for_cell_zero_dimensions() {
216        let node = Node::new("test_node".to_string(), 0, 0, 0, 0);
217
218        let mut visited = vec![];
219        node.for_cell(&mut |x, y| {
220            visited.push((x, y));
221            Ok(())
222        })
223        .unwrap();
224
225        assert!(
226            visited.is_empty(),
227            "Should not visit any cells for zero dimensions"
228        );
229    }
230}