fly_b/simul/plane/
sector.rs1use crate::{
4 color,
5 simul::obstacles::{self, Pole},
6};
7use bevy::prelude::*;
8use std::{
9 backtrace::{Backtrace, BacktraceStatus},
10 sync::atomic::AtomicBool,
11};
12
13pub mod err;
14
15pub static DROP_ERR_HAS_BEEN_DISPLAYED: AtomicBool = AtomicBool::new(false);
16
17#[derive(Default, Debug)]
25pub struct Sector {
26 entity: Option<Entity>,
30 translation_x: f32,
32 upper_pole: obstacles::Pole,
34 lower_pole: obstacles::Pole,
36}
37
38impl Sector {
39 pub const SCALE: Vec3 = Vec3::new(300.0, 900.0, 0.1);
43 pub const MIN_GAP: f32 = 0.2;
48 pub const TRANSLATION_Y: f32 = 0.;
50 pub const DISPLAY_LAYER: f32 = 0.;
51
52 pub const MAX_OCCUPIED_HEIGHT: f32 = 1.0 - Self::MIN_GAP;
59
60 pub const UPPER_BOUND_Y: f32 = Self::TRANSLATION_Y + Self::SCALE.y / 2.;
61 pub const LOWER_BOUND_Y: f32 = Self::TRANSLATION_Y - Self::SCALE.y / 2.;
62
63 pub fn empty_with_translation_x(translation_x: f32) -> Sector {
67 Self {
68 translation_x,
69 ..default()
70 }
71 }
72
73 pub fn rand_with_translation_x<R: rand::Rng + ?Sized>(rng: &mut R, translation_x: f32) -> Self {
75 let lower_pole = Pole::new([
76 rng.gen_range(Pole::STD_SCALE_X),
77 rng.gen_range(0.0..=left_vertical_space(0.0)),
78 ]);
79 let upper_pole = Pole::new([
80 rng.gen_range(Pole::STD_SCALE_X),
81 rng.gen_range(0.0..=left_vertical_space(lower_pole.scale().y)),
82 ]);
83
84 Sector {
85 entity: None,
86 translation_x,
87 upper_pole,
88 lower_pole,
89 }
90 }
91
92 pub fn upper_pole(&self) -> &obstacles::Pole {
95 &self.upper_pole
96 }
97
98 pub fn lower_pole(&self) -> &obstacles::Pole {
99 &self.lower_pole
100 }
101
102 pub fn entity(&self) -> Result<&Entity, err::EntityNotSpawned> {
103 self.entity.as_ref().ok_or(err::EntityNotSpawned::new())
104 }
105 #[allow(dead_code)]
106 fn entity_mut(&mut self) -> Result<&mut Entity, err::EntityNotSpawned> {
107 self.entity.as_mut().ok_or(err::EntityNotSpawned::new())
108 }
109
110 pub fn translation_x(&self) -> f32 {
111 self.translation_x
112 }
113
114 pub fn entity_present(&self) -> bool {
120 self.entity().is_ok()
121 }
122
123 pub fn right_bound_x(&self) -> f32 {
124 self.translation_x() + 0.5 * crate::simul::Sector::SCALE.x
125 }
126
127 pub fn upper_pole_left_bound_x(&self) -> f32 {
129 self.upper_pole().left_bound_x(self.translation_x)
130 }
131 pub fn upper_pole_right_bound_x(&self) -> f32 {
132 self.upper_pole().right_bound_x(self.translation_x)
133 }
134 pub fn upper_pole_lower_bound_y(&self) -> f32 {
135 self.upper_pole().lower_bound_y(Self::UPPER_BOUND_Y)
136 }
137
138 pub fn lower_pole_left_bound_x(&self) -> f32 {
140 self.lower_pole().left_bound_x(self.translation_x)
141 }
142 pub fn lower_pole_right_bound_x(&self) -> f32 {
143 self.lower_pole().right_bound_x(self.translation_x)
144 }
145 pub fn lower_pole_upper_bound_y(&self) -> f32 {
146 self.lower_pole().upper_bound_y(Self::LOWER_BOUND_Y)
147 }
148
149 pub fn spawn(
155 &mut self,
156 color_rbg: [f32; 3],
157 cmds: &mut Commands,
158 ) -> Result<(), err::EntityAlreadySpawned> {
159 if self.entity_present() {
160 Err(err::EntityAlreadySpawned::new())
161 } else {
162 let _ =
163 self.entity
164 .insert(self.spawn_sector_entity(cmds, self.translation_x(), color_rbg));
165 Ok(())
166 }
167 }
168
169 pub fn spawn_with_rand_color(
170 &mut self,
171 hero_color_rbg: [f32; 3],
172 cmds: &mut Commands,
173 ) -> Result<(), crate::simul::plane::sector::err::EntityAlreadySpawned> {
174 let next_sect_color_rbg =
175 color::rand_rbg_contrasting(hero_color_rbg, crate::SimulPlane::MIN_SECT_COLOR_CONTRAST);
176 self.spawn(next_sect_color_rbg.into(), cmds)
177 }
178
179 pub fn despawn(&mut self, cmds: &mut Commands) -> Result<(), err::EntityNotSpawned> {
181 if let Some(entity) = self.entity.take() {
182 cmds.entity(entity).despawn_recursive();
183 Ok(())
184 } else {
185 Err(err::EntityNotSpawned::new())
186 }
187 }
188
189 fn spawn_sector_entity(
192 &self,
193 cmds: &mut Commands<'_, '_>,
194 translation_x: f32,
195 color_rbg: [f32; 3],
196 ) -> Entity {
197 cmds.spawn((
198 Name::from("Sector"),
199 SpriteBundle {
200 sprite: Sprite {
201 color: Color::rgb_from_array(color_rbg),
202 ..default()
203 },
204 transform: Transform {
205 translation: [translation_x, Self::TRANSLATION_Y, Self::DISPLAY_LAYER].into(),
206 scale: Self::SCALE,
207 rotation: default(),
208 },
209 ..default()
210 },
211 ))
212 .with_children(|child_builder: &mut ChildBuilder| {
213 self.upper_pole.spawn_as_upper(
214 child_builder,
215 color::rand_rbg_contrasting(color_rbg, crate::SimulPlane::MIN_SECT_COLOR_CONTRAST),
216 );
217 self.lower_pole.spawn_as_lower(
218 child_builder,
219 color::rand_rbg_contrasting(color_rbg, crate::SimulPlane::MIN_SECT_COLOR_CONTRAST),
220 )
221 })
222 .id()
223 }
224}
225
226pub fn left_vertical_space(taken_space: f32) -> f32 {
232 Sector::MAX_OCCUPIED_HEIGHT - taken_space
233}
234
235impl Drop for Sector {
238 fn drop(&mut self) {
239 if self.entity_present() {
240 if !DROP_ERR_HAS_BEEN_DISPLAYED.swap(true, std::sync::atomic::Ordering::SeqCst) {
242 let backtrace = Backtrace::capture();
244 let backtrace_info = match backtrace.status() {
245 BacktraceStatus::Captured => format!("\n{backtrace}"),
246 BacktraceStatus::Disabled => format!(" Note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace."),
247 BacktraceStatus::Unsupported | _ => format!(" Note: Backtrace is unsupported on your platform."),
248 };
249 use std::any::type_name;
251 warn!(
252 "Internal logic error: Instance of `{sector_t:}` dropped without despawning the corresponding `{entity_t:}`. This might cause misbehaviour or be just a memory leak. Note: This error won't be shown anymore untill the app is restarted.{backtrace_info}",
253 sector_t = type_name::<Self>(),
254 entity_t = type_name::<Entity>()
255 )
256 }
257 }
258 }
259}