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}