Skip to main content

serenade_kernel/
kernel.rs

1//! Kernel state machine: register, compile, boot, shutdown.
2
3use std::fmt::{Display, Formatter};
4
5use crate::{BundleInterface, BundleRegistry, Environment, KernelError};
6
7/// Lifecycle phase of a [`Kernel`].
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
9pub enum KernelPhase {
10    /// Bundles may still be registered.
11    Created,
12    /// `build` has run; [`Kernel::boot`] is allowed.
13    Compiled,
14    /// `boot` has run; [`Kernel::shutdown`] is allowed.
15    Booted,
16    /// Terminal phase after shutdown.
17    Shutdown,
18}
19
20impl Display for KernelPhase {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        f.write_str(match self {
23            Self::Created => "created",
24            Self::Compiled => "compiled",
25            Self::Booted => "booted",
26            Self::Shutdown => "shutdown",
27        })
28    }
29}
30
31/// Application kernel: environment, bundle order, and lifecycle.
32///
33/// # Examples
34///
35/// ```
36/// use serenade_kernel::{App, Application, Environment, KernelPhase};
37///
38/// let mut app = App::new(Environment::Test);
39/// app.boot().expect("boot");
40/// assert_eq!(app.kernel().phase(), KernelPhase::Booted);
41/// app.shutdown().expect("shutdown");
42/// assert_eq!(app.kernel().phase(), KernelPhase::Shutdown);
43/// ```
44pub struct Kernel {
45    environment: Environment,
46    debug: bool,
47    registry: BundleRegistry,
48    bundles: Vec<Box<dyn BundleInterface>>,
49    phase: KernelPhase,
50}
51
52impl Kernel {
53    /// Creates a kernel in [`KernelPhase::Created`].
54    ///
55    /// Debug defaults to [`Environment::is_debug`].
56    #[must_use]
57    pub fn new(environment: Environment) -> Self {
58        let debug = environment.is_debug();
59        Self {
60            environment,
61            debug,
62            registry: BundleRegistry::new(),
63            bundles: Vec::new(),
64            phase: KernelPhase::Created,
65        }
66    }
67
68    /// Overrides the debug flag. Call before [`Self::compile`].
69    #[must_use]
70    pub const fn with_debug(mut self, debug: bool) -> Self {
71        self.debug = debug;
72        self
73    }
74
75    /// Runtime environment.
76    #[must_use]
77    pub const fn environment(&self) -> &Environment {
78        &self.environment
79    }
80
81    /// Effective debug flag.
82    #[must_use]
83    pub const fn debug(&self) -> bool {
84        self.debug
85    }
86
87    /// Current lifecycle phase.
88    #[must_use]
89    pub const fn phase(&self) -> KernelPhase {
90        self.phase
91    }
92
93    /// Bundle names in effective order.
94    ///
95    /// Before compile: registration order. After compile: dependency order.
96    #[must_use]
97    pub fn bundle_names(&self) -> Vec<&'static str> {
98        if self.phase == KernelPhase::Created {
99            self.registry.names()
100        } else {
101            self.bundles.iter().map(|bundle| bundle.name()).collect()
102        }
103    }
104
105    /// Registers a bundle. Must run while the kernel is [`KernelPhase::Created`].
106    ///
107    /// # Errors
108    ///
109    /// Returns [`KernelError::InvalidState`] after compile, or
110    /// [`KernelError::DuplicateBundle`] when `bundle.name()` is already registered.
111    pub fn register_bundle(
112        &mut self,
113        bundle: impl BundleInterface + 'static,
114    ) -> Result<(), KernelError> {
115        self.ensure_phase("register", KernelPhase::Created)?;
116        self.registry.register(bundle)
117    }
118
119    /// Sorts bundles by dependencies, then runs [`BundleInterface::build`] in that order.
120    ///
121    /// # Errors
122    ///
123    /// Returns [`KernelError::InvalidState`] unless the kernel is [`KernelPhase::Created`],
124    /// or a dependency-graph / bundle `build` error.
125    pub fn compile(&mut self) -> Result<(), KernelError> {
126        self.ensure_phase("compile", KernelPhase::Created)?;
127        let registry = std::mem::take(&mut self.registry);
128        self.bundles = registry.sorted()?;
129        for bundle in &self.bundles {
130            bundle
131                .build()
132                .map_err(|error| wrap_bundle(bundle.name(), "build", error))?;
133        }
134        self.phase = KernelPhase::Compiled;
135        Ok(())
136    }
137
138    /// Compiles if needed, then runs [`BundleInterface::boot`] in dependency order.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`KernelError::InvalidState`] when already booted or shut down.
143    pub fn boot(&mut self) -> Result<(), KernelError> {
144        if self.phase == KernelPhase::Created {
145            self.compile()?;
146        }
147        self.ensure_phase("boot", KernelPhase::Compiled)?;
148        for bundle in &self.bundles {
149            bundle
150                .boot()
151                .map_err(|error| wrap_bundle(bundle.name(), "boot", error))?;
152        }
153        self.phase = KernelPhase::Booted;
154        Ok(())
155    }
156
157    /// Runs [`BundleInterface::shutdown`] in reverse dependency order.
158    ///
159    /// # Errors
160    ///
161    /// Returns [`KernelError::InvalidState`] unless the kernel is [`KernelPhase::Booted`].
162    pub fn shutdown(&mut self) -> Result<(), KernelError> {
163        self.ensure_phase("shutdown", KernelPhase::Booted)?;
164        for bundle in self.bundles.iter().rev() {
165            bundle
166                .shutdown()
167                .map_err(|error| wrap_bundle(bundle.name(), "shutdown", error))?;
168        }
169        self.phase = KernelPhase::Shutdown;
170        Ok(())
171    }
172
173    fn ensure_phase(&self, action: &'static str, expected: KernelPhase) -> Result<(), KernelError> {
174        if self.phase == expected {
175            Ok(())
176        } else {
177            Err(KernelError::InvalidState {
178                action,
179                state: self.phase,
180            })
181        }
182    }
183}
184
185impl std::fmt::Debug for Kernel {
186    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("Kernel")
188            .field("environment", &self.environment)
189            .field("debug", &self.debug)
190            .field("phase", &self.phase)
191            .field("bundles", &self.bundle_names())
192            .finish_non_exhaustive()
193    }
194}
195
196fn wrap_bundle(bundle: &'static str, phase: &'static str, error: KernelError) -> KernelError {
197    match error {
198        KernelError::Bundle { .. } => error,
199        other => KernelError::Bundle {
200            bundle,
201            phase,
202            message: other.to_string(),
203        },
204    }
205}