fly_b/simul/plane/
sector.rs

1//! Module encapsulating logic around `Sector` struct.
2
3use 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/// A division of the simulation plane.
18///
19/// Can & is meant to be bound to a concrete entity that represents it. Contains cool properties which are used for the sector based calculations like: location detection, collision detection.
20///
21/// # States
22/// * Spawned – contains handle to corresponding, spawned `bevy::prelude::Entity`
23/// * NotSpawned – doesn't contain handle to any entity, nor any living entity is assigned to it.
24#[derive(Default, Debug)]
25pub struct Sector {
26    /// The concrete entity to which this logical object is bound to.
27    ///
28    /// Stores the state of `Sector` by (optionally) storing a handle to living entity.
29    entity: Option<Entity>,
30    /// The translation of the concrete entity.
31    translation_x: f32,
32    /// Obstacle restricting passage through the upper part of this sector.
33    upper_pole: obstacles::Pole,
34    /// Obstacle restricting passage through the lower part of this sector.
35    lower_pole: obstacles::Pole,
36}
37
38impl Sector {
39    // Constants – Given
40
41    /// The scale of all spawned sectors.
42    pub const SCALE: Vec3 = Vec3::new(300.0, 900.0, 0.1);
43    /// The minimal gap between the 2 poles that restrict passage through this sector.
44    ///
45    /// It is encoded as a fraction of this sector's height.
46    /// The positive (and high enough) value of this constant ensures that the hero can always pass through the gap and remain untouched by the obstacles.
47    pub const MIN_GAP: f32 = 0.2;
48    /// The y coordinate for all spawned sectors.
49    pub const TRANSLATION_Y: f32 = 0.;
50    pub const DISPLAY_LAYER: f32 = 0.;
51
52    // Constants – Calculated
53
54    /// The maximum fraction of height that can be occupied by obstacles.
55    ///
56    /// Because this value is < 1 (enough less then), hero can pass through every sector untouched.
57    /// This constant faciliates the compution of quantity of space/passage that is free to be occupied by some obstacles.
58    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    // CRUD-C: Constructors
64
65    /// Creates an empty sector with given `translation.x` .
66    pub fn empty_with_translation_x(translation_x: f32) -> Sector {
67        Self {
68            translation_x,
69            ..default()
70        }
71    }
72
73    /// Random instance constructor.
74    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    // CRUD-R: Getters
93
94    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    // CRUD-R: Properties
115
116    // Properties
117
118    /// Returns true <==> this logical sector is bound to concrete entity.
119    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    // Upper pole bounds
128    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    // Lower pole bounds
139    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    // CRUD-U: Updaters
150
151    /// Spawns the corresponding concrete entity for this logical sector object.
152    ///
153    /// Binds `self` to spawned entity.
154    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    /// Despawns the concrete entity `self` is bounded to. Unbinds `self`.
180    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    // CRUD-U: Aiding updaters.
190
191    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
226// CRUD-R: Properties
227
228// Calculations
229
230/// States how much vertical space/passage is still free to be occupied by some obstacles.
231pub fn left_vertical_space(taken_space: f32) -> f32 {
232    Sector::MAX_OCCUPIED_HEIGHT - taken_space
233}
234
235// CRUD-D: Destructors
236
237impl Drop for Sector {
238    fn drop(&mut self) {
239        if self.entity_present() {
240            // If the error hasn't been started being displayed before entering this `if`.
241            if !DROP_ERR_HAS_BEEN_DISPLAYED.swap(true, std::sync::atomic::Ordering::SeqCst) {
242                // Prepare for displaying the error.
243                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                // Display the error.
250                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}