common_game/components/
forge.rs

1//! Forge module
2//!
3//! This module defines the [Forge] type, a singleton-like component responsible
4//! for generating [`Asteroid`] and [`Sunray`] instances.
5//!
6//! Only one Forge may exist at a time. Attempting to construct more than one
7//! instance results in an error. The component is designed to centralize object
8//! creation in a controlled manner.
9
10use crate::components::asteroid::Asteroid;
11use crate::components::sunray::Sunray;
12
13/// Internal module containing global state used by the [Forge].
14///
15/// # Internal API - Do not use directly
16///
17/// This module must be public only because Rust requires `pub` visibility for
18/// cross-module access within the crate.
19/// It **is not** considered stable API and must not be used by external code.
20pub(crate) mod internal {
21    use std::sync::{LazyLock, Mutex};
22
23    /// Tracks whether a [Forge] instance has already been created.
24    ///
25    /// # Internal API - Do not use directly
26    pub(crate) static ALREADY_CREATED: LazyLock<Mutex<bool>> = LazyLock::new(|| Mutex::new(false));
27}
28
29/// The `Forge` is a singleton-like generator used to create [`Asteroid`] and
30/// [`Sunray`] instances.
31///
32/// Only one Forge may ever be created at a time. Attempting to create a second
33/// instance will return an error.
34///
35/// This ensures that all energy-related components are produced in a controlled,
36/// centralized manner.
37pub struct Forge {
38    /// Hidden field to prevent external construction.
39    _private: (),
40}
41
42impl Forge {
43    /// Attempts to create a new `Forge`.
44    ///
45    /// # Errors
46    ///
47    /// - Returns `"Another generator has already been created"` if a Forge
48    ///   instance already exists.
49    /// - Returns `"Internal error: forge state mutex poisoned"` if the internal
50    ///   state cannot be accessed.
51    pub fn new() -> Result<Self, String> {
52        let mut check = internal::ALREADY_CREATED
53            .lock()
54            .map_err(|_| "Internal error: forge state mutex poisoned".to_string())?;
55
56        if *check {
57            Err("Another generator has already been created".into())
58        } else {
59            *check = true;
60            Ok(Forge { _private: () })
61        }
62    }
63
64    /// Creates a new [`Asteroid`].
65    ///
66    /// # Returns
67    /// A freshly constructed `Asteroid` instance.
68    #[must_use]
69    pub fn generate_asteroid(&self) -> Asteroid {
70        Asteroid::new()
71    }
72
73    /// Creates a new [`Sunray`].
74    ///
75    /// # Returns
76    /// A freshly constructed `Sunray` instance.
77    #[must_use]
78    pub fn generate_sunray(&self) -> Sunray {
79        Sunray::new()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    //! Unit tests for the [Forge].
86    //!
87    //! These tests validate singleton behavior and basic construction rules.
88
89    use super::internal::ALREADY_CREATED;
90    use super::*;
91
92    /// Resets the global singleton state.
93    ///
94    /// Used only in tests.
95    fn reset_flag() {
96        let mut created = ALREADY_CREATED
97            .lock()
98            .expect("Test setup failed: mutex poisoned");
99        *created = false;
100    }
101
102    /// Verifies that the first Forge creation succeeds.
103    #[test]
104    fn first_creation_succeeds() {
105        reset_flag();
106        assert!(Forge::new().is_ok());
107    }
108
109    /// Ensures that constructing a second Forge returns an error.
110    #[test]
111    fn second_creation_fails() {
112        reset_flag();
113
114        let g0 = Forge::new();
115        assert!(g0.is_ok());
116
117        let g1 = Forge::new();
118        assert!(g1.is_err());
119    }
120}