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}