• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Contains CellOccupancyMatrix used to track occupied cells during grid placement
2 use super::TrackCounts;
3 use crate::compute::grid::OriginZeroLine;
4 use crate::geometry::AbsoluteAxis;
5 use crate::geometry::Line;
6 use crate::util::sys::Vec;
7 use core::cmp::{max, min};
8 use core::fmt::Debug;
9 use core::ops::Range;
10 use grid::Grid;
11 
12 /// The occupancy state of a single grid cell
13 #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
14 pub(crate) enum CellOccupancyState {
15     #[default]
16     /// Indicates that a grid cell is unoccupied
17     Unoccupied,
18     /// Indicates that a grid cell is occupied by a definitely placed item
19     DefinitelyPlaced,
20     /// Indicates that a grid cell is occupied by an item that was placed by the auto placement algorithm
21     AutoPlaced,
22 }
23 
24 /// A dynamically sized matrix (2d grid) which tracks the occupancy of each grid cell during auto-placement
25 /// It also keeps tabs on how many tracks there are and which tracks are implicit and which are explicit.
26 pub(crate) struct CellOccupancyMatrix {
27     /// The grid of occupancy states
28     inner: Grid<CellOccupancyState>,
29     /// The counts of implicit and explicit columns
30     columns: TrackCounts,
31     /// The counts of implicit and explicit rows
32     rows: TrackCounts,
33 }
34 
35 /// Debug impl that represents the matrix in a compact 2d text format
36 impl Debug for CellOccupancyMatrix {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result37     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38         writeln!(
39             f,
40             "Rows: neg_implicit={} explicit={} pos_implicit={}",
41             self.rows.negative_implicit, self.rows.explicit, self.rows.positive_implicit
42         )?;
43         writeln!(
44             f,
45             "Cols: neg_implicit={} explicit={} pos_implicit={}",
46             self.columns.negative_implicit, self.columns.explicit, self.columns.positive_implicit
47         )?;
48         writeln!(f, "State:")?;
49 
50         for row_idx in 0..self.inner.rows() {
51             for cell in self.inner.iter_row(row_idx) {
52                 let letter = match *cell {
53                     CellOccupancyState::Unoccupied => '_',
54                     CellOccupancyState::DefinitelyPlaced => 'D',
55                     CellOccupancyState::AutoPlaced => 'A',
56                 };
57                 write!(f, "{letter}")?;
58             }
59             writeln!(f)?;
60         }
61 
62         Ok(())
63     }
64 }
65 
66 impl CellOccupancyMatrix {
67     /// Create a CellOccupancyMatrix given a set of provisional track counts. The grid can expand as needed to fit more tracks,
68     /// the provisional track counts represent a best effort attempt to avoid the extra allocations this requires.
with_track_counts(columns: TrackCounts, rows: TrackCounts) -> Self69     pub fn with_track_counts(columns: TrackCounts, rows: TrackCounts) -> Self {
70         Self { inner: Grid::new(rows.len(), columns.len()), rows, columns }
71     }
72 
73     /// Determines whether the specified area fits within the tracks currently represented by the matrix
is_area_in_range( &self, primary_axis: AbsoluteAxis, primary_range: Range<i16>, secondary_range: Range<i16>, ) -> bool74     pub fn is_area_in_range(
75         &self,
76         primary_axis: AbsoluteAxis,
77         primary_range: Range<i16>,
78         secondary_range: Range<i16>,
79     ) -> bool {
80         if primary_range.start < 0 || primary_range.end > self.track_counts(primary_axis).len() as i16 {
81             return false;
82         }
83         if secondary_range.start < 0 || secondary_range.end > self.track_counts(primary_axis.other_axis()).len() as i16
84         {
85             return false;
86         }
87         true
88     }
89 
90     /// Expands the grid (potentially in all 4 directions) in order to ensure that the specified range fits within the allocated space
expand_to_fit_range(&mut self, row_range: Range<i16>, col_range: Range<i16>)91     fn expand_to_fit_range(&mut self, row_range: Range<i16>, col_range: Range<i16>) {
92         // Calculate number of rows and columns missing to accommodate ranges (if any)
93         let req_negative_rows = min(row_range.start, 0);
94         let req_positive_rows = max(row_range.end - self.rows.len() as i16, 0);
95         let req_negative_cols = min(col_range.start, 0);
96         let req_positive_cols = max(col_range.end - self.columns.len() as i16, 0);
97 
98         let old_row_count = self.rows.len();
99         let old_col_count = self.columns.len();
100         let new_row_count = old_row_count + (req_negative_rows + req_positive_rows) as usize;
101         let new_col_count = old_col_count + (req_negative_cols + req_positive_cols) as usize;
102 
103         let mut data = Vec::with_capacity(new_row_count * new_col_count);
104 
105         // Push new negative rows
106         for _ in 0..(req_negative_rows as usize * new_col_count) {
107             data.push(CellOccupancyState::Unoccupied);
108         }
109 
110         // Push existing rows
111         for row in 0..old_row_count {
112             // Push new negative columns
113             for _ in 0..req_negative_cols {
114                 data.push(CellOccupancyState::Unoccupied);
115             }
116             // Push existing columns
117             for col in 0..old_col_count {
118                 data.push(*self.inner.get(row, col).unwrap());
119             }
120             // Push new positive columns
121             for _ in 0..req_positive_cols {
122                 data.push(CellOccupancyState::Unoccupied);
123             }
124         }
125 
126         // Push new negative rows
127         for _ in 0..(req_positive_rows as usize * new_col_count) {
128             data.push(CellOccupancyState::Unoccupied);
129         }
130 
131         // Update self with new data
132         self.inner = Grid::from_vec(data, new_col_count);
133         self.rows.negative_implicit += req_negative_rows as u16;
134         self.rows.positive_implicit += req_positive_rows as u16;
135         self.columns.negative_implicit += req_negative_cols as u16;
136         self.columns.positive_implicit += req_positive_cols as u16;
137     }
138 
139     /// Mark an area of the matrix as occupied, expanding the allocated space as necessary to accommodate the passed area.
mark_area_as( &mut self, primary_axis: AbsoluteAxis, primary_span: Line<OriginZeroLine>, secondary_span: Line<OriginZeroLine>, value: CellOccupancyState, )140     pub fn mark_area_as(
141         &mut self,
142         primary_axis: AbsoluteAxis,
143         primary_span: Line<OriginZeroLine>,
144         secondary_span: Line<OriginZeroLine>,
145         value: CellOccupancyState,
146     ) {
147         let (row_span, column_span) = match primary_axis {
148             AbsoluteAxis::Horizontal => (secondary_span, primary_span),
149             AbsoluteAxis::Vertical => (primary_span, secondary_span),
150         };
151 
152         let mut col_range = self.columns.oz_line_range_to_track_range(column_span);
153         let mut row_range = self.rows.oz_line_range_to_track_range(row_span);
154 
155         // Check that if the resolved ranges fit within the allocated grid. And if they don't then expand the grid to fit
156         // and then re-resolve the ranges once the grid has been expanded as the resolved indexes may have changed
157         let is_in_range = self.is_area_in_range(AbsoluteAxis::Horizontal, col_range.clone(), row_range.clone());
158         if !is_in_range {
159             self.expand_to_fit_range(row_range.clone(), col_range.clone());
160             col_range = self.columns.oz_line_range_to_track_range(column_span);
161             row_range = self.rows.oz_line_range_to_track_range(row_span);
162         }
163 
164         for x in row_range {
165             for y in col_range.clone() {
166                 *self.inner.get_mut(x as usize, y as usize).unwrap() = value;
167             }
168         }
169     }
170 
171     /// Determines whether a grid area specified by the bounding grid lines in OriginZero coordinates
172     /// is entirely unnocupied. Returns true if all grid cells within the grid area are unnocupied, else false.
line_area_is_unoccupied( &self, primary_axis: AbsoluteAxis, primary_span: Line<OriginZeroLine>, secondary_span: Line<OriginZeroLine>, ) -> bool173     pub fn line_area_is_unoccupied(
174         &self,
175         primary_axis: AbsoluteAxis,
176         primary_span: Line<OriginZeroLine>,
177         secondary_span: Line<OriginZeroLine>,
178     ) -> bool {
179         let primary_range = self.track_counts(primary_axis).oz_line_range_to_track_range(primary_span);
180         let secondary_range = self.track_counts(primary_axis.other_axis()).oz_line_range_to_track_range(secondary_span);
181         self.track_area_is_unoccupied(primary_axis, primary_range, secondary_range)
182     }
183 
184     /// Determines whether a grid area specified by a range of indexes into this CellOccupancyMatrix
185     /// is entirely unnocupied. Returns true if all grid cells within the grid area are unnocupied, else false.
track_area_is_unoccupied( &self, primary_axis: AbsoluteAxis, primary_range: Range<i16>, secondary_range: Range<i16>, ) -> bool186     pub fn track_area_is_unoccupied(
187         &self,
188         primary_axis: AbsoluteAxis,
189         primary_range: Range<i16>,
190         secondary_range: Range<i16>,
191     ) -> bool {
192         let (row_range, col_range) = match primary_axis {
193             AbsoluteAxis::Horizontal => (secondary_range, primary_range),
194             AbsoluteAxis::Vertical => (primary_range, secondary_range),
195         };
196 
197         // Search for occupied cells in the specified area. Out of bounds cells are considered unoccupied.
198         for x in row_range {
199             for y in col_range.clone() {
200                 match self.inner.get(x as usize, y as usize) {
201                     None | Some(CellOccupancyState::Unoccupied) => continue,
202                     _ => return false,
203                 }
204             }
205         }
206 
207         true
208     }
209 
210     /// Determines whether the specified row contains any items
row_is_occupied(&self, row_index: usize) -> bool211     pub fn row_is_occupied(&self, row_index: usize) -> bool {
212         self.inner.iter_row(row_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
213     }
214 
215     /// Determines whether the specified column contains any items
column_is_occupied(&self, column_index: usize) -> bool216     pub fn column_is_occupied(&self, column_index: usize) -> bool {
217         self.inner.iter_col(column_index).any(|cell| !matches!(cell, CellOccupancyState::Unoccupied))
218     }
219 
220     /// Returns the track counts of this CellOccunpancyMatrix in the relevant axis
track_counts(&self, track_type: AbsoluteAxis) -> &TrackCounts221     pub fn track_counts(&self, track_type: AbsoluteAxis) -> &TrackCounts {
222         match track_type {
223             AbsoluteAxis::Horizontal => &self.columns,
224             AbsoluteAxis::Vertical => &self.rows,
225         }
226     }
227 
228     /// Given an axis and a track index
229     /// Search backwards from the end of the track and find the last grid cell matching the specified state (if any)
230     /// Return the index of that cell or None.
last_of_type( &self, track_type: AbsoluteAxis, start_at: OriginZeroLine, kind: CellOccupancyState, ) -> Option<OriginZeroLine>231     pub fn last_of_type(
232         &self,
233         track_type: AbsoluteAxis,
234         start_at: OriginZeroLine,
235         kind: CellOccupancyState,
236     ) -> Option<OriginZeroLine> {
237         let track_counts = self.track_counts(track_type.other_axis());
238         let track_computed_index = track_counts.oz_line_to_next_track(start_at);
239 
240         let maybe_index = match track_type {
241             AbsoluteAxis::Horizontal => {
242                 self.inner.iter_row(track_computed_index as usize).rposition(|item| *item == kind)
243             }
244             AbsoluteAxis::Vertical => {
245                 self.inner.iter_col(track_computed_index as usize).rposition(|item| *item == kind)
246             }
247         };
248 
249         maybe_index.map(|idx| track_counts.track_to_prev_oz_line(idx as u16))
250     }
251 }
252