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}