aboutsummaryrefslogtreecommitdiff
path: root/mingling_core
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core')
-rw-r--r--mingling_core/src/any/group.rs81
-rw-r--r--mingling_core/src/asset/chain.rs44
-rw-r--r--mingling_core/src/asset/chain/error.rs11
-rw-r--r--mingling_core/src/asset/dispatcher.rs254
-rw-r--r--mingling_core/src/asset/enum_tag.rs49
-rw-r--r--mingling_core/src/asset/global_resource.rs1138
-rw-r--r--mingling_core/src/asset/help.rs45
-rw-r--r--mingling_core/src/asset/lazy_resource.rs563
-rw-r--r--mingling_core/src/asset/metadata.rs31
-rw-r--r--mingling_core/src/asset/node.rs31
-rw-r--r--mingling_core/src/asset/renderer.rs31
-rw-r--r--mingling_core/src/asset/routable.rs110
12 files changed, 2214 insertions, 174 deletions
diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs
index 5e5e347..71fe65d 100644
--- a/mingling_core/src/any/group.rs
+++ b/mingling_core/src/any/group.rs
@@ -1,32 +1,67 @@
-use crate::{AnyOutput, ChainProcess, ProgramCollect, Routable};
-
-/// Used to mark a type with a unique enum ID, assisting dynamic dispatch
+/// Member ID for types within a program
+///
+/// This trait provides a member ID for program-internal types, used to determine
+/// the downcast type during dispatch, routing, rendering, and other stages.
///
/// # Safety
///
-/// The returned `Group` value is an enum variant created by `register_type!` when
-/// registering the type's ID. Whether the variant matches correctly is guaranteed
-/// by `Grouped derive` or macros like `pack!`. If implemented manually, and the
-/// type name written in `member_id()` does not match the actually registered type,
-/// dispatching to that type will result in **100% undefined behavior**.
+/// This trait is typically provided by the corresponding [`Grouped Derive`](https://docs.rs/mingling/latest/mingling/derive.Grouped.html).
+/// If implemented manually, **make sure** the ID is **exactly identical** to
+/// the name registered by the `register_type!` macro; otherwise, undefined
+/// behavior will inevitably occur when the program routes to that type!
+///
+/// # Manual impl
+///
+/// In general, we recommend using [`#[derive(Grouped)]`](https://docs.rs/mingling/latest/mingling/derive.Grouped.html) to implement it.
+/// However, if you must implement it manually, please follow exactly this pattern:
+///
+/// ```
+/// # use mingling_core::Grouped;
+/// enum ThisProgram {
+/// // Global ID registered by `register_type!`
+/// StateMyType,
+/// }
+///
+/// struct StateMyType;
+///
+/// // SAFETY: This ensures the StateMyType variant during ThisProgram dispatch always corresponds to this type
+/// unsafe impl Grouped<ThisProgram> for StateMyType {
+/// fn member_id() -> ThisProgram {
+/// // must semantically correspond to the type itself!
+/// ThisProgram::StateMyType
+/// }
+/// }
+/// ```
pub unsafe trait Grouped<Group>
where
Self: Sized + 'static,
{
- /// Returns the specific enum value representing its ID within that enum
+ /// Get the member ID for this type
+ ///
+ /// # Safety
+ ///
+ /// The returned enum variant must exactly correspond to this type itself,
+ /// i.e., the returned `Group` enum variant must semantically represent this
+ /// type itself. If an incorrect variant is returned, it will cause a type
+ /// casting error and lead to undefined behavior.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Grouped;
+ /// # enum ThisProgram {
+ /// # StateMyType,
+ /// # }
+ /// # struct StateMyType;
+ /// # unsafe impl Grouped<ThisProgram> for StateMyType {
+ /// // The following macro registers the type ID
+ /// // mingling::macros::register_type!(StateMyType);
+ ///
+ /// fn member_id() -> ThisProgram {
+ /// // must semantically correspond to the type itself!
+ /// ThisProgram::StateMyType
+ /// }
+ /// # }
+ /// ```
fn member_id() -> Group;
}
-
-impl<T, C> Routable<C> for T
-where
- C: ProgramCollect<Enum = C>,
- T: Grouped<C> + Send,
-{
- fn to_chain(self) -> ChainProcess<C> {
- AnyOutput::new(self).route_chain()
- }
-
- fn to_render(self) -> ChainProcess<C> {
- AnyOutput::new(self).route_renderer()
- }
-}
diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs
index bd504d0..5276809 100644
--- a/mingling_core/src/asset/chain.rs
+++ b/mingling_core/src/asset/chain.rs
@@ -3,16 +3,52 @@ use crate::ChainProcess;
#[doc(hidden)]
pub mod error;
-/// Takes over a type (G: Previous) and converts it to another [`AnyOutput`](./struct.AnyOutput.html)
+/// Mingling's program logic execution unit
+///
+/// Binds a chain to a type. When the program is scheduled to that type, the
+/// `proc` function in the chain will be executed to convert it to the next
+/// type and send it to the scheduler.
+///
+/// # Async
+///
+/// When the `async` feature is enabled, the `proc` function of this trait no
+/// longer requires returning a [`ChainProcess`], but rather a Future whose
+/// output is a [`ChainProcess`].
+///
+/// # Manual impl
+///
+/// If you need to implement it manually, please do so as follows:
+///
+/// ```
+/// # use mingling_core::Chain;
+/// # use mingling_core::ChainProcess;
+/// # enum ThisProgram {}
+/// struct MyChain;
+/// struct StateMyType;
+///
+/// impl Chain<ThisProgram> for MyChain {
+/// type Previous = StateMyType;
+///
+/// fn proc(prev: Self::Previous) -> ChainProcess<ThisProgram> {
+/// // Specific type conversion logic
+/// # return mingling_core::ChainProcess::<ThisProgram>::Err(mingling_core::error::ChainProcessError::Other("test".to_string()));
+/// }
+/// }
+/// ```
pub trait Chain<G> {
- /// The previous type in the chain
+ /// The previous type bound to the chain, used to convert to the next arbitrary type in this chain
type Previous;
- /// Process the previous value and return a future that resolves to a [`ChainProcess<G>`](./enum.ChainProcess.html)
#[cfg(feature = "async")]
+ /// The execution logic of the chain, converting the type `Previous` into the next type and returning an asynchronous [`ChainProcess`].
+ ///
+ /// Called when the `async` feature is enabled, this method returns a result that implements `Future`,
+ /// whose output is a [`ChainProcess<G>`].
fn proc(p: Self::Previous) -> impl Future<Output = ChainProcess<G>> + Send;
- /// Process the previous value and return a future that resolves to a [`ChainProcess<G>`](./enum.ChainProcess.html)
#[cfg(not(feature = "async"))]
+ /// The execution logic of the chain, converting the type `Previous` into the next type and returning a [`ChainProcess`].
+ ///
+ /// Called when the `async` feature is disabled, this method synchronously returns a [`ChainProcess<G>`].
fn proc(p: Self::Previous) -> ChainProcess<G>;
}
diff --git a/mingling_core/src/asset/chain/error.rs b/mingling_core/src/asset/chain/error.rs
index bb5f679..4845dbd 100644
--- a/mingling_core/src/asset/chain/error.rs
+++ b/mingling_core/src/asset/chain/error.rs
@@ -1,12 +1,15 @@
use crate::error::ProgramInternalExecuteError;
-/// Represents errors that can occur during chain processing.
+/// Represents error types that occur in a chained processing pipeline.
+///
+/// This enum is used to uniformly encapsulate various exceptions that may
+/// occur during the execution of an entire chain, including IO errors and
+/// other custom error messages.
#[derive(Debug)]
pub enum ChainProcessError {
- /// An error with a custom description.
+ /// Other unclassified generic errors, stored as a string description.
Other(String),
-
- /// An I/O error that occurred during chain processing.
+ /// Errors resulting from a failed IO operation, holding the standard library's [`std::io::Error`]
IO(std::io::Error),
}
diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs
index cb0987d..d700405 100644
--- a/mingling_core/src/asset/dispatcher.rs
+++ b/mingling_core/src/asset/dispatcher.rs
@@ -2,19 +2,140 @@ use std::fmt::Display;
use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node};
-/// Dispatches user input commands to specific [`ChainProcess`](./enum.ChainProcess.html)
+/// The entry logic of the Mingling program
///
-/// Note: If you are using [mingling_macros](https://crates.io/crates/mingling_macros),
-/// you can use the `dispatcher!("node.subnode", CommandType => Entry)` macro to declare a `Dispatcher`
+/// Dispatcher is the first stop for args after they enter the program:
+/// it is used to wrap the user's raw args into an initial [`ChainProcess`] and feed them into the program loop
+///
+/// # Manual impl
+///
+/// ```
+/// # use mingling_core::ChainProcess;
+/// # use mingling_core::Dispatcher;
+/// # use mingling_core::Grouped;
+/// # use mingling_core::Routable;
+/// # use mingling_core::Node;
+/// # use mingling_core::MockProgramCollect as ThisProgram;
+/// # unsafe impl Grouped<ThisProgram> for Foo {
+/// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+/// # }
+/// struct CMDGreet;
+/// struct Foo {
+/// args: Vec<String>
+/// }
+///
+/// impl Dispatcher<ThisProgram> for CMDGreet {
+/// fn node(&self) -> Node {
+/// Node::default().join("greet")
+/// }
+///
+/// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+/// Routable::to_chain(Foo { args })
+/// }
+///
+/// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+/// Box::new(CMDGreet)
+/// }
+/// }
+/// ```
pub trait Dispatcher<C> {
- /// Returns a command node for matching user input
+ /// Get the node of this Dispatcher, used to tell the program loop which arguments should be handled by this Dispatcher
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// # use mingling_core::ChainProcess;
+ /// # use mingling_core::Dispatcher;
+ /// # use mingling_core::Grouped;
+ /// # use mingling_core::Routable;
+ /// # use mingling_core::Node;
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for Foo {
+ /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+ /// # }
+ /// # struct CMDGreet;
+ /// # struct Foo {
+ /// # args: Vec<String>
+ /// # }
+ /// # impl Dispatcher<ThisProgram> for CMDGreet {
+ /// fn node(&self) -> Node {
+ /// // Construct the node
+ /// Node::default().join("greet")
+ /// }
+ /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ /// # Routable::to_chain(Foo { args })
+ /// # }
+ /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+ /// # Box::new(CMDGreet)
+ /// # }
+ /// # }
+ /// ```
fn node(&self) -> Node;
- /// Returns a [`ChainProcess`](./enum.ChainProcess.html) based on user input arguments,
- /// to be sent to the specific invocation
+ /// Begin logic, receives the remaining arguments after the prefix has been stripped
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// # use mingling_core::ChainProcess;
+ /// # use mingling_core::Dispatcher;
+ /// # use mingling_core::Grouped;
+ /// # use mingling_core::Routable;
+ /// # use mingling_core::Node;
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for Foo {
+ /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+ /// # }
+ /// # struct CMDGreet;
+ /// # struct Foo {
+ /// # args: Vec<String>
+ /// # }
+ /// # impl Dispatcher<ThisProgram> for CMDGreet {
+ /// # fn node(&self) -> Node {
+ /// # Node::default().join("greet")
+ /// # }
+ /// fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ /// // Create Foo from args and route it to the next chain
+ /// Routable::to_chain(Foo { args })
+ /// }
+ /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+ /// # Box::new(CMDGreet)
+ /// # }
+ /// # }
+ /// ```
fn begin(&self, args: Vec<String>) -> ChainProcess<C>;
- /// Clones the current dispatcher for implementing the `Clone` trait
+ /// Clone the dispatcher's Box for dynamic dispatch
+ ///
+ /// Example:
+ ///
+ /// ```
+ /// # use mingling_core::ChainProcess;
+ /// # use mingling_core::Dispatcher;
+ /// # use mingling_core::Grouped;
+ /// # use mingling_core::Routable;
+ /// # use mingling_core::Node;
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for Foo {
+ /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+ /// # }
+ /// # struct CMDGreet;
+ /// # struct Foo {
+ /// # args: Vec<String>
+ /// # }
+ /// # impl Dispatcher<ThisProgram> for CMDGreet {
+ /// # fn node(&self) -> Node {
+ /// # Node::default().join("greet")
+ /// # }
+ /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ /// # Routable::to_chain(Foo { args })
+ /// # }
+ /// fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+ /// // Create a new Box
+ /// Box::new(CMDGreet)
+ /// }
+ /// # }
+ /// ```
fn clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>;
}
@@ -31,7 +152,39 @@ impl<C> Program<C>
where
C: ProgramCollect<Enum = C>,
{
- /// Adds a dispatcher to the program.
+ /// Add a Dispatcher to the program
+ ///
+ /// This dynamically registers a Dispatcher into the program, used for command matching at program startup
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// # use mingling_core::ChainProcess;
+ /// # use mingling_core::Dispatcher;
+ /// # use mingling_core::Grouped;
+ /// # use mingling_core::Routable;
+ /// # use mingling_core::Node;
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for Foo {
+ /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+ /// # }
+ /// # struct CMDGreet;
+ /// # struct Foo {
+ /// # args: Vec<String>
+ /// # }
+ /// # impl Dispatcher<ThisProgram> for CMDGreet {
+ /// # fn node(&self) -> Node {
+ /// # Node::default().join("greet")
+ /// # }
+ /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ /// # Routable::to_chain(Foo { args })
+ /// # }
+ /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+ /// # Box::new(CMDGreet)
+ /// # }
+ /// # }
+ /// let mut program = Program::<ThisProgram>::new();
+ /// program.with_dispatcher(CMDGreet);
+ /// ```
#[cfg_attr(
feature = "dispatch_tree",
deprecated(
@@ -53,13 +206,43 @@ where
self
}
- /// Add some dispatchers to the program.
- #[cfg_attr(
- feature = "dispatch_tree",
- deprecated(
- note = "When the `dispatch_tree` feature is enabled, the `dispatcher` field no longer exists inside Program. All types are collected at compile time by the `gen_program!()` macro, so the `with_dispatcher` function is no longer needed"
- )
+ /// Add a group of Dispatchers to the program
+ ///
+ /// This dynamically registers a group of Dispatchers into the program, used for command matching at program startup
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// # use mingling_core::ChainProcess;
+ /// # use mingling_core::Dispatcher;
+ /// # use mingling_core::Grouped;
+ /// # use mingling_core::Routable;
+ /// # use mingling_core::Node;
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for Foo {
+ /// # fn member_id() -> ThisProgram { ThisProgram::Foo }
+ /// # }
+ /// # struct CMDGreet;
+ /// # struct Foo {
+ /// # args: Vec<String>
+ /// # }
+ /// # impl Dispatcher<ThisProgram> for CMDGreet {
+ /// # fn node(&self) -> Node {
+ /// # Node::default().join("greet")
+ /// # }
+ /// # fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ /// # Routable::to_chain(Foo { args })
+ /// # }
+ /// # fn clone_dispatcher(&self) -> Box<dyn Dispatcher<ThisProgram>> {
+ /// # Box::new(CMDGreet)
+ /// # }
+ /// # }
+ /// let mut program = Program::<ThisProgram>::new();
+ /// program.with_dispatchers((CMDGreet, /* Other Dispatchers */));
+ /// ```
+ #[deprecated(
+ note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
)]
+ #[allow(deprecated)]
pub fn with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self
where
D: Into<Dispatchers<C>>,
@@ -77,26 +260,29 @@ where
}
}
-/// A collection of dispatchers.
+/// Represents a group of Dispatchers
///
-/// This struct holds a vector of boxed `Dispatcher` trait objects,
-/// allowing multiple dispatchers to be grouped together and passed
-/// to the program via `Program::with_dispatchers`.
-/// A collection of dispatchers.
+/// It records a group of Dispatchers and implements conversion from tuples `(Disp, ..)` for this type,
+/// allowing for simpler construction syntax when using `with_dispatchers`
///
-/// This struct holds a vector of boxed `Dispatcher` trait objects,
-/// allowing multiple dispatchers to be grouped together and passed
-/// to the program via `Program::with_dispatchers`.
+/// # Limits
+///
+/// Dispatchers supports conversion from tuples of up to 7 Dispatchers
+#[deprecated(
+ note = "with_dispatchers is no longer the recommended way to register Dispatchers, please split into multiple with_dispatcher calls"
+)]
pub struct Dispatchers<G> {
dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>,
}
+#[allow(deprecated)]
impl<G> From<Vec<Box<dyn Dispatcher<G> + Send + Sync>>> for Dispatchers<G> {
fn from(dispatcher: Vec<Box<dyn Dispatcher<G> + Send + Sync>>) -> Self {
Self { dispatcher }
}
}
+#[allow(deprecated)]
impl<G> From<Box<dyn Dispatcher<G> + Send + Sync>> for Dispatchers<G> {
fn from(dispatcher: Box<dyn Dispatcher<G> + Send + Sync>) -> Self {
Self {
@@ -105,6 +291,7 @@ impl<G> From<Box<dyn Dispatcher<G> + Send + Sync>> for Dispatchers<G> {
}
}
+#[allow(deprecated)]
impl<D, G> From<(D,)> for Dispatchers<G>
where
D: Dispatcher<G> + Send + Sync + 'static,
@@ -117,6 +304,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, G> From<(D1, D2)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -130,6 +318,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, D3, G> From<(D1, D2, D3)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -148,6 +337,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, D3, D4, G> From<(D1, D2, D3, D4)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -168,6 +358,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, D3, D4, D5, G> From<(D1, D2, D3, D4, D5)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -190,6 +381,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, D3, D4, D5, D6, G> From<(D1, D2, D3, D4, D5, D6)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -214,6 +406,7 @@ where
}
}
+#[allow(deprecated)]
impl<D1, D2, D3, D4, D5, D6, D7, G> From<(D1, D2, D3, D4, D5, D6, D7)> for Dispatchers<G>
where
D1: Dispatcher<G> + Send + Sync + 'static,
@@ -240,6 +433,7 @@ where
}
}
+#[allow(deprecated)]
impl<G> std::ops::Deref for Dispatchers<G> {
type Target = Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>>;
@@ -248,6 +442,7 @@ impl<G> std::ops::Deref for Dispatchers<G> {
}
}
+#[allow(deprecated)]
impl<G> From<Dispatchers<G>> for Vec<Box<dyn Dispatcher<G> + Send + Sync + 'static>> {
fn from(val: Dispatchers<G>) -> Self {
val.dispatcher
@@ -259,8 +454,6 @@ mod tests {
use super::*;
use crate::ChainProcess;
use std::fmt::Display;
-
- /// A minimal mock Dispatcher for testing Dispatchers conversions.
#[derive(Clone)]
struct MockDispatcher {
name: &'static str,
@@ -279,8 +472,6 @@ mod tests {
Box::new(self.clone())
}
}
-
- /// Minimal mock group for Dispatchers tests
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
enum MockG {
@@ -294,6 +485,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_single_tuple() {
let disp = MockDispatcher { name: "foo" };
let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
@@ -301,6 +493,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_two_tuple() {
let d1 = MockDispatcher { name: "a" };
let d2 = MockDispatcher { name: "b" };
@@ -309,6 +502,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_three_tuple() {
let d1 = MockDispatcher { name: "x" };
let d2 = MockDispatcher { name: "y" };
@@ -318,6 +512,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_four_tuple() {
let d1 = MockDispatcher { name: "1" };
let d2 = MockDispatcher { name: "2" };
@@ -328,6 +523,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_five_tuple() {
let d1 = MockDispatcher { name: "a" };
let d2 = MockDispatcher { name: "b" };
@@ -339,6 +535,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_six_tuple() {
let d1 = MockDispatcher { name: "a" };
let d2 = MockDispatcher { name: "b" };
@@ -351,6 +548,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_seven_tuple() {
let d1 = MockDispatcher { name: "a" };
let d2 = MockDispatcher { name: "b" };
@@ -364,6 +562,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_vec_of_boxed() {
let d1: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "a" });
let d2: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "b" });
@@ -372,6 +571,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_from_single_boxed() {
let d: Box<dyn Dispatcher<MockG> + Send + Sync> = Box::new(MockDispatcher { name: "x" });
let dispatchers: Dispatchers<MockG> = d.into();
@@ -379,6 +579,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_deref() {
let disp = MockDispatcher { name: "test" };
let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
@@ -387,6 +588,7 @@ mod tests {
}
#[test]
+ #[allow(deprecated)]
fn test_dispatchers_into_vec() {
let disp = MockDispatcher { name: "foo" };
let dispatchers: Dispatchers<MockG> = Dispatchers::from((disp,));
diff --git a/mingling_core/src/asset/enum_tag.rs b/mingling_core/src/asset/enum_tag.rs
index d830e62..0639e18 100644
--- a/mingling_core/src/asset/enum_tag.rs
+++ b/mingling_core/src/asset/enum_tag.rs
@@ -1,12 +1,53 @@
-/// Marker trait for `EnumTag`
+/// Marks an enum so that its variants can be recognized by Mingling
+///
+/// By implementing [`EnumTag`], Mingling can obtain the enum's name, information, etc.,
+/// which helps with argument parsing, completion, etc.
+///
+/// # Manual impl
+///
+/// In general, [`EnumTag`] is recommended to be derived using [`#[derive(EnumTag)]`](https://docs.rs/mingling/latest/mingling/derive.EnumTag.html),
+/// but if you need to implement it manually, please refer to the following:
+///
+/// ```
+/// # use mingling_core::EnumTag;
+/// enum Choice {
+/// Foo, Bar
+/// }
+///
+/// impl EnumTag for Choice {
+/// fn enum_info(&self) -> (&'static str, &'static str) {
+/// ("Choice", "Choice enum")
+/// }
+/// fn enums() -> &'static [(&'static str, &'static str)] {
+/// &[("Foo", "Foo variant"), ("Bar", "Bar variant")]
+/// }
+/// fn build_enum(name: String) -> Option<Self> {
+/// match name.as_str() {
+/// "Foo" => Some(Choice::Foo),
+/// "Bar" => Some(Choice::Bar),
+/// _ => None,
+/// }
+/// }
+/// }
+/// ```
pub trait EnumTag {
- /// Get the name and description of this enum
+ /// Returns the name and description of the enum
+ ///
+ /// Returns a tuple `(enum_name, enum_description)`, where `enum_name` is the name of the enum,
+ /// and `enum_description` is a brief description of the enum, used in scenarios such as
+ /// argument parsing error messages and completion.
fn enum_info(&self) -> (&'static str, &'static str);
- /// Get all possible enum variant names and descriptions
+ /// Returns the names and descriptions of all variants of this enum
+ ///
+ /// Returns a slice where each element is a `(variant_name, variant_description)` tuple,
+ /// describing all variants of the enum and their meanings, used for argument completion and parsing.
fn enums() -> &'static [(&'static str, &'static str)];
- /// Build the enum from a name
+ /// Builds the corresponding enum value from a string name
+ ///
+ /// The input `name` is the string argument to be parsed. If it matches a variant name,
+ /// the corresponding `Some(enum_value)` is returned; otherwise `None` is returned.
fn build_enum(name: String) -> Option<Self>
where
Self: Sized;
diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs
index 07d0b5e..06731f7 100644
--- a/mingling_core/src/asset/global_resource.rs
+++ b/mingling_core/src/asset/global_resource.rs
@@ -32,6 +32,13 @@ pub struct GlobalResContainer {
impl GlobalResContainer {
/// Creates an empty resource container.
+ ///
+ /// Usage:
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let container = GlobalResContainer::new();
+ /// ```
#[must_use]
pub fn new() -> Self {
Self {
@@ -54,7 +61,32 @@ impl GlobalResContainer {
entry
}
- /// Insert a resource of the given type, cloning the provided value into the store
+ /// Inserts (or overwrites) a resource into the [`GlobalResContainer`].
+ ///
+ /// # Behavior
+ ///
+ /// - The resource is stored bound to the `TypeId` of its type. That is, **the same type**
+ /// can only have one resource instance — a later inserted resource of the same type will
+ /// **overwrite** the previously inserted old value.
+ /// - Different `Res` types are **completely independent** of each other in the container.
+ /// - Resources are stored as `Arc<Mutex<Arc<Res>>>`: the outer `Mutex` guarantees that
+ /// only one caller can modify the resource at a time (but holding that lock does not
+ /// block the container's global lock); the inner `Arc<Res>` is used to provide immutable
+ /// shared snapshots for read-only APIs such as `res()` / `res_or_default()`.
+ /// - `Res` must satisfy `'static + Send + Sync` (for thread-safe sharing) as well as the
+ /// [`ResourceMarker`] trait (providing default values, cloning, etc.).
+ ///
+ /// # Return Value
+ ///
+ /// Returns `&mut self` for chained calls, for example:
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let mut container = GlobalResContainer::new();
+ /// container
+ /// .with_resource(42i32)
+ /// .with_resource(String::from("hello"));
+ /// ```
pub fn with_resource<Res: 'static + Send + Sync + ResourceMarker>(
&mut self,
res: Res,
@@ -68,7 +100,58 @@ impl GlobalResContainer {
self
}
- /// Modify a resource by type, applying a closure to the resource if present
+ /// Performs a **read-modify-write** operation on an existing resource in the container
+ /// and returns the closure's return value.
+ ///
+ /// # Behavior
+ ///
+ /// 1. First, looks up the resource entry corresponding to the `Res` type in the container.
+ /// 2. If the entry **does not exist** (has not been inserted via [`with_resource`] or
+ /// [`__store_res`]), returns `Return::default()` directly, **without** calling `f`,
+ /// and without inserting any new resource.
+ /// 3. If the entry exists, locks the resource's own mutex (note: this lock is **separate**
+ /// from the container's global lock, so nested calls will not deadlock).
+ /// 4. Takes the resource **out** of the container (attempting to take ownership directly
+ /// via `Arc::try_unwrap`):
+ /// - If the `Arc` has no other holders (i.e., no `GlobalResource` snapshot references it),
+ /// the original value is taken directly, without cloning.
+ /// - If the `Arc` has other holders (e.g., `res()` was called elsewhere and holds a
+ /// shared snapshot), a **clone** is made via `__resource_marker_clone()` for
+ /// modification; the original snapshot is unaffected.
+ /// 5. Passes the cloned/taken value to the closure `f(&mut new_res)` for modification and
+ /// collects the closure's return value `r`.
+ /// 6. Writes the modified new value **back** into the resource slot, then releases the
+ /// resource lock.
+ /// 7. Returns the closure's result `r`.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Default + ResourceMarker + Send + Sync`.
+ /// `ResourceMarker` guarantees the resource can be cloned (when a shared snapshot exists)
+ /// and can be default-instantiated.
+ /// - `Return` must implement `Default`, because when the resource does not exist or the
+ /// lock is poisoned, this method returns `Return::default()` as a fallback value.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(10i32);
+ ///
+ /// // Resource exists: modify and return the value
+ /// let double = container.modify_res(|v: &mut i32| { *v *= 2; *v });
+ /// assert_eq!(double, 20);
+ /// assert_eq!(*container.res::<i32>().unwrap(), 20);
+ ///
+ /// // Resource does not exist: returns Default, closure is not called
+ /// let missing = container.modify_res::<String, i32>(|_| 42);
+ /// assert_eq!(missing, 0);
+ /// ```
+ ///
+ /// [`with_resource`]: Self::with_resource
+ /// [`__store_res`]: Self::__store_res
+ /// [`res()`]: Self::res
pub fn modify_res<Res, Return>(&self, f: impl FnOnce(&mut Res) -> Return) -> Return
where
Res: 'static + Default + ResourceMarker + Send + Sync,
@@ -89,7 +172,102 @@ impl GlobalResContainer {
r
}
- /// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly
+ /// Performs a **read-modify-write** operation on an existing resource in the container
+ /// and directly passes the closure's [`ChainProcess<C>`] routing result to the caller.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method used by `#[chain]`, `#[resource]`, and similar macros;
+ /// it typically does not appear directly in user business code.
+ ///
+ /// This method behaves very similarly to [`modify_res`](Self::modify_res), with the
+ /// only differences being:
+ ///
+ /// - The closure `f`'s return type is [`ChainProcess<C>`], rather than an arbitrary generic
+ /// `Return`. This means it is designed for **chained program routing/jumping**
+ /// scenarios — the closure can return `ChainProcess::Ok(...)` /
+ /// `ChainProcess::Err(...)` / jump targets, etc., to route program execution to the
+ /// next stage.
+ /// - When the resource does not exist or the container/resource lock is poisoned,
+ /// `modify_res` returns `Return::default()`, whereas this method constructs a
+ /// **default resource** (via `ResourceMarker::__resource_marker_default()`),
+ /// still calls `f`, and returns the `ChainProcess<C>` produced by `f` directly.
+ /// That is, this method **always** calls the closure `f` and returns its routing result.
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the container.
+ /// - If the entry **does not exist**, constructs a **temporary default resource**
+ /// via `ResourceMarker::__resource_marker_default()`, directly calls
+ /// `f(&mut default_res)`, and returns its result.
+ /// - If the entry exists, continues to step 2.
+ /// 2. Locks the resource's **own** `Mutex` (independent of the container's global lock;
+ /// nested calls will not deadlock).
+ /// - If the lock is poisoned, also goes down the "default resource" branch:
+ /// constructs a default instance and calls `f`.
+ /// 3. Attempts to take the resource's **ownership** via `Arc::try_unwrap`:
+ /// - If the `Arc` has no other holders (no shared snapshots), takes the original value;
+ /// - If there are other holders (e.g., a [`GlobalResource`] snapshot exists elsewhere),
+ /// calls `ResourceMarker::__resource_marker_clone()` to **clone** a copy for
+ /// modification; the original snapshot is unaffected.
+ /// 4. Passes the taken value to the closure `f(&mut new_res)` to execute the modification
+ /// logic, obtaining the routing result `r`.
+ /// 5. Writes the modified new value **back** into the resource slot, releases the
+ /// resource lock.
+ /// 6. Returns `r` to the caller.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Default + ResourceMarker + Send + Sync`.
+ /// `ResourceMarker` provides cloning and default instantiation capabilities;
+ /// `Default` is used to construct fallback values.
+ /// - `C` must implement `ProgramCollect<Enum = C>`, i.e., the current program's
+ /// collector type.
+ /// - `ChainProcess<C>` represents an intermediate/terminal state of chained program
+ /// execution, decoded by macro-expanded code to determine the next step.
+ ///
+ /// # Role as a Macro-Expansion Internal Method
+ ///
+ /// In code generated by `#[chain]` and similar macros, when a resource needs to be
+ /// injected as a **mutable reference** (`&mut Res`) into a procedure/step, while also
+ /// ensuring that the procedure can return a `ChainProcess<C>` to drive program-flow
+ /// routing, the macro expansion generates a call to `__modify_res_and_return_route`.
+ /// This ensures:
+ ///
+ /// - The resource modification and the `ChainProcess` routing result are completed in
+ /// **one atomic operation**;
+ /// - Regardless of whether the resource exists, macro-expanded code can obtain a
+ /// `ChainProcess<C>` to continue program execution flow;
+ /// - Macro-generated code does not need to worry about internal locking, `Arc`
+ /// ownership, or cloning details, all encapsulated by this method.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{GlobalResContainer, ChainProcess, error::ChainProcessError};
+ /// # use mingling_core::MockProgramCollect;
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(1i32);
+ ///
+ /// // After macro expansion, this is equivalent to: take out the i32 resource,
+ /// // modify it, and return the routing result
+ /// let route: ChainProcess<MockProgramCollect> =
+ /// container.__modify_res_and_return_route(|v: &mut i32| {
+ /// *v += 1;
+ /// ChainProcess::Err(ChainProcessError::Other("done".into()))
+ /// });
+ /// assert!(matches!(route, ChainProcess::Err(_)));
+ /// assert_eq!(*container.res::<i32>().unwrap(), 2);
+ /// ```
+ ///
+ /// [`ChainProcess<C>`]: crate::ChainProcess
+ /// [`Program::__modify_res_and_return_route`]: crate::Program::__modify_res_and_return_route
+ ///
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`**, and the API will not be publicly exposed in
+ /// stable documentation. Do not call it directly in business code; use public macros or
+ /// the public [`Program`] methods to operate on resources.
#[doc(hidden)]
pub fn __modify_res_and_return_route<Res, C>(
&self,
@@ -116,10 +294,81 @@ impl GlobalResContainer {
r
}
- /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly.
+ /// **Takes** a `Res`-typed resource value **out** of the container and returns its
+ /// ownership.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method used by `#[chain]`, `#[resource]`, and similar macros;
+ /// it typically does not appear directly in user business code.
+ ///
+ /// This method differs from the public [`modify_res`](Self::modify_res):
+ ///
+ /// - `modify_res` is a **modify-and-write-back** operation, where the value modified by
+ /// the closure is **written back** into the container's resource slot;
+ /// - This method performs a **take-and-return** operation — it requires no closure and
+ /// does not write the modified value back into the container. It **moves** the resource
+ /// **out of** the container into the caller's hands, granting the caller full ownership
+ /// (or an independent copy) of the resource, so it can be used independently of the
+ /// container.
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the container:
+ /// - If the entry **does not exist**, constructs and returns a default instance via
+ /// `ResourceMarker::__resource_marker_default()`.
+ /// - If the entry exists, continues to step 2.
+ /// 2. Locks the resource's **own** `Mutex`. If the lock is poisoned, also returns a
+ /// default instance.
+ /// 3. Attempts to take the resource's **ownership** via `Arc::try_unwrap`:
+ /// - If the `Arc` has no other holders (no shared snapshots), returns the original
+ /// value directly;
+ /// - If there are other holders (e.g., a [`GlobalResource`] shared snapshot exists),
+ /// calls `ResourceMarker::__resource_marker_clone()` to **clone** a copy and returns
+ /// it; the original value in the container is unaffected (not written back).
+ /// 4. Returns the taken `Res` value.
+ ///
+ /// # Resource Slot State
+ ///
+ /// This method does **not** write a new value back into the container, so the resource
+ /// slot is **cleared** after being taken (since it internally uses `mem::take`, the
+ /// slot is emptied). Subsequent calls to [`res()`](Self::res) /
+ /// [`modify_res`](Self::modify_res) for the same resource will find that the entry
+ /// **still exists** (the `TypeId` key is still present), but the `Arc` inside the slot
+ /// has been emptied and replaced with a `Default::default()`-typed empty value — the
+ /// exact behavior depends on internal implementation details, and macro-expanded code
+ /// should not rely on the precise state of the slot after extraction.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Default + ResourceMarker + Send + Sync`.
+ ///
+ /// # Role as a Macro-Expansion Internal Method
+ ///
+ /// In macro-generated code, when a resource needs to be **taken out at once** from the
+ /// container and handed to an operation that requires ownership (rather than borrowing)
+ /// — e.g., moving the resource to another container, passing it to an
+ /// `impl FnOnce(Res)`-style closure, or participating in special `Arc::try_unwrap`
+ /// semantics — the macro expansion calls this method. It avoids borrow-lifetime
+ /// entanglement and delivers ownership of the value directly.
+ ///
+ /// # Example (simulating macro-expansion internal calls)
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(3i32);
///
- /// Extracts a mutable resource from the store (clone-out), returning an
- /// owned value. The caller must call [`__store_res`] to write back modifications.
+ /// let extracted: i32 = container.__extract_res_mut();
+ /// assert_eq!(extracted, 3);
+ /// ```
+ ///
+ /// [`Program::__extract_res_mut`]: crate::Program::__extract_res_mut
+ ///
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`**, and the API will not be publicly exposed in
+ /// stable documentation. Do not call it directly in business code.
#[doc(hidden)]
#[must_use]
pub fn __extract_res_mut<Res: 'static + Default + ResourceMarker + Send + Sync>(&self) -> Res {
@@ -135,9 +384,87 @@ impl GlobalResContainer {
}
}
- /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly.
+ /// **Overwrites** a resource value into the container.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method used by `#[chain]`, `#[resource]`, and similar macros;
+ /// it typically does not appear directly in user business code.
+ ///
+ /// This method behaves **almost identically** to the public
+ /// [`with_resource`](Self::with_resource) (both store/overwrite a resource by type), but
+ /// the two differ in their method signatures:
+ ///
+ /// - [`with_resource`](Self::with_resource) takes `&mut self` and returns `&mut Self`,
+ /// suitable for chained **initialization** scenarios (e.g., registering multiple
+ /// resources at once during `Program` construction);
+ /// - `__store_res` takes `&self` and returns no value, suitable for **runtime**
+ /// dynamic overwrite/update of a resource's value (e.g., macro-expanded code already
+ /// holding an immutable reference).
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Acquires the container's global lock. If the lock is poisoned, returns directly
+ /// without doing anything.
+ /// 2. Looks up the existing entry by `Res` type in the container:
+ /// - If the entry **does not exist**, creates a new `Arc<Mutex<Arc<Res>>>` wrapper
+ /// under the `TypeId::of::<Res>()` key and inserts it into the container.
+ /// - If the entry **already exists**, attempts to downcast `boxed_any` to
+ /// `Arc<Mutex<Arc<Res>>>` and attempts to lock the resource's own `Mutex`:
+ /// * If downcasting succeeds and locking succeeds, writes the new `Arc::new(val)`
+ /// directly into that slot, completing the overwrite update;
+ /// * If downcasting fails (which should not happen in theory, since the type is
+ /// guaranteed by `TypeId`) or the lock is poisoned, **replaces the entire entry**
+ /// (constructs a new `Arc<Mutex<Arc<Res>>>` and inserts it).
+ /// 3. Releases the container's global lock; the operation is complete.
+ ///
+ /// # Concurrency Semantics
+ ///
+ /// - The container's global lock and the resource's own lock are **two independent
+ /// locks**. This method briefly holds the container's global lock to locate the
+ /// entry, then writes the new value **only** under the protection of the resource's
+ /// own lock — other threads calling `res()` / `modify_res()` for the same resource
+ /// will not see intermediate states.
+ /// - If the container's global lock is poisoned, this method silently returns; if the
+ /// resource's own lock is poisoned, this method **replaces the entire entry** (discard
+ /// the old lock state), ensuring the new value can still be written successfully.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Send + Sync + ResourceMarker`.
+ ///
+ /// # Role as a Macro-Expansion Internal Method
+ ///
+ /// In macro-generated code, when a resource of a certain type needs to be **overwritten
+ /// at runtime** (e.g., writing a `&mut Res` parameter back into the container after a
+ /// procedure ends, or committing a newly computed resource snapshot back to the
+ /// container), the macro expansion calls this method. It allows updating a resource's
+ /// value in an environment where only `&self` (an immutable reference) is available,
+ /// decoupling the resource's lifetime from the caller's borrow of the container.
///
- /// Stores a modified resource value back into the store.
+ /// # Example (simulating macro-expansion internal calls)
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let container = GlobalResContainer::new();
+ ///
+ /// // Runtime write
+ /// container.__store_res(8i32);
+ /// assert_eq!(*container.res::<i32>().unwrap(), 8);
+ ///
+ /// // Overwrite
+ /// container.__store_res(9i32);
+ /// assert_eq!(*container.res::<i32>().unwrap(), 9);
+ /// ```
+ ///
+ /// [`Program::__store_res`]: crate::Program::__store_res
+ ///
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`**, and the API will not be publicly exposed in
+ /// stable documentation. Do not call it directly in business code; use the public
+ /// [`with_resource`](Self::with_resource) or [`modify_res`](Self::modify_res) public
+ /// methods instead.
#[doc(hidden)]
pub fn __store_res<Res: 'static + Send + Sync + ResourceMarker>(&self, val: Res) {
let Ok(mut guard) = self.map.lock() else {
@@ -164,7 +491,63 @@ impl GlobalResContainer {
);
}
- /// Get an resources by type, returning `Res` if present
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the container.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the container:
+ /// - If the entry **does not exist** (has not been inserted via
+ /// [`with_resource`](Self::with_resource) or [`__store_res`](Self::__store_res)),
+ /// returns `None`.
+ /// - If the entry exists, continues to step 2.
+ /// 2. Locks the resource's **own** `Mutex` (independent of the container's global lock;
+ /// nested calls will not deadlock).
+ /// 3. If locking succeeds, clones the resource's internal `Arc<Res>` and wraps it as a
+ /// [`GlobalResource<Res>`] to return.
+ ///
+ /// The returned [`GlobalResource<Res>`] can be dereferenced like `&Res` via `Deref`.
+ /// When multiple callers each hold their own returned [`GlobalResource<Res>`], they share
+ /// the same underlying `Arc<Res>` data, maintaining **read-only consistency** with each
+ /// other.
+ ///
+ /// # Relationship with Modification Operations
+ ///
+ /// - This method returns an **immutable snapshot** (`Arc<Res>`) of the resource and does
+ /// not block or wait for other threads to modify the resource (modification operations
+ /// lock the resource's own `Mutex`).
+ /// - When a [`GlobalResource<Res>`] snapshot is held externally (`Arc` strong reference
+ /// count > 1), subsequent [`modify_res`](Self::modify_res) operations on the same
+ /// resource will **clone** a copy for modification, without affecting the snapshot
+ /// obtained here.
+ ///
+ /// # Return Value
+ ///
+ /// Returns `Option<GlobalResource<Res>>`:
+ /// - If the resource exists and the lock can be acquired normally, returns
+ /// `Some(GlobalResource<Res>)`;
+ /// - If the resource does not exist or the resource lock is poisoned, returns `None`.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Send + Sync`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(10i32);
+ ///
+ /// let res = container.res::<i32>();
+ /// assert_eq!(*res.unwrap(), 10);
+ ///
+ /// // Resource does not exist: returns None
+ /// assert!(container.res::<String>().is_none());
+ /// ```
+ ///
+ /// If you want a default value when the entry does not exist, use
+ /// [`res_or_default`](Self::res_or_default); if you need to route to a specific path
+ /// when the resource is missing, use [`res_or_route`](Self::res_or_route).
#[must_use]
pub fn res<Res: 'static + Send + Sync>(&self) -> Option<GlobalResource<Res>> {
let entry = self.res_entry::<Res>()?;
@@ -172,13 +555,75 @@ impl GlobalResContainer {
Some(GlobalResource::from(Arc::clone(&*guard)))
}
- /// Get a resource by type, returning `GlobalResource<Res>` if present.
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the container;
+ /// returns the provided routing result if the resource does not exist.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the container:
+ /// - If the entry **exists** and the resource lock can be acquired normally, clones
+ /// the resource's internal `Arc<Res>` (wrapped via [`GlobalResource`]) and returns
+ /// it as `Ok(...)`.
+ /// - If the entry **does not exist** (has not been inserted via
+ /// [`with_resource`](Self::with_resource) or [`__store_res`](Self::__store_res)),
+ /// returns `Err(route)` directly — the caller-provided `ChainProcess<C>` is
+ /// returned as-is, without calling any closure.
+ /// 2. A poisoned lock is treated the same as a missing resource: returns `Err(route)`.
+ ///
+ /// # Return Value
+ ///
+ /// Returns `Result<GlobalResource<Res>, ChainProcess<C>>`:
+ /// - When the resource exists, returns `Ok(GlobalResource<Res>)` (a shared immutable
+ /// snapshot);
+ /// - When the resource does not exist or the resource lock is poisoned, returns
+ /// `Err(route)` (the provided routing result is returned as-is).
///
- /// If the resource is not present, returns the provided [`ChainProcess`] as an `Err`.
+ /// # Purpose
+ ///
+ /// In chained program-routing (`#[chain]` and similar macros) scenarios, when a resource
+ /// is missing, it is usually desirable to route the program flow to some error-handling
+ /// branch or default path. This method allows the caller to pre-construct a
+ /// [`ChainProcess<C>`] route: when the resource exists, processing continues normally;
+ /// when the resource is missing, routing jumps to that path in place, avoiding
+ /// additional nested checks.
///
/// # Errors
///
- /// Returns `Err(route)` when the resource of type `Res` is not present in the store.
+ /// Returns `Err(route)` (the provided `ChainProcess<C>` is returned as-is) when:
+ /// - The resource entry does not exist in the container;
+ /// - The resource's own lock is poisoned.
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Send + Sync`.
+ /// - `C` must implement `ProgramCollect<Enum = C>`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{GlobalResContainer, ChainProcess, error::ChainProcessError};
+ /// # use mingling_core::MockProgramCollect;
+ /// let container = GlobalResContainer::new();
+ /// let route: ChainProcess<MockProgramCollect> =
+ /// ChainProcess::Err(ChainProcessError::Other("missing".into()));
+ ///
+ /// // Resource missing: returns Err(route)
+ /// // Note: ChainProcess is not Clone, so pass each route by value.
+ /// assert!(container.res_or_route::<i32, MockProgramCollect>(route).is_err());
+ ///
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(42i32);
+ /// // Resource exists: returns Ok(GlobalResource)
+ /// let route: ChainProcess<MockProgramCollect> =
+ /// ChainProcess::Err(ChainProcessError::Other("missing".into()));
+ /// match container.res_or_route::<i32, MockProgramCollect>(route) {
+ /// Ok(res) => assert_eq!(*res, 42),
+ /// Err(_) => panic!("expected Ok"),
+ /// }
+ /// ```
+ ///
+ /// [`ChainProcess<C>`]: crate::ChainProcess
+ /// [`GlobalResource`]: crate::GlobalResource
pub fn res_or_route<Res, C>(
&self,
route: ChainProcess<C>,
@@ -190,7 +635,61 @@ impl GlobalResContainer {
self.res().map_or_else(|| Err(route), Ok)
}
- /// Get a resource by type, returning `GlobalResource<Res>` or inserting a default
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the container;
+ /// returns a default instance if the resource does not exist.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the container:
+ /// - If the entry **exists** and the resource lock can be acquired normally, clones
+ /// the resource's internal `Arc<Res>`, wraps it as a [`GlobalResource<Res>`], and
+ /// returns it.
+ /// - If the entry **does not exist** (has not been inserted via
+ /// [`with_resource`](Self::with_resource) or [`__store_res`](Self::__store_res)),
+ /// constructs a **default instance** via
+ /// `ResourceMarker::__resource_marker_default()`, wraps it as a
+ /// [`GlobalResource<Res>`], and returns it.
+ /// 2. A poisoned lock is treated the same as a missing resource: it also returns a
+ /// default instance.
+ ///
+ /// The returned [`GlobalResource<Res>`] can be dereferenced like `&Res` via `Deref`.
+ /// When multiple callers each hold their own returned [`GlobalResource<Res>`], they share
+ /// the same underlying `Arc<Res>` data, maintaining **read-only consistency** with each
+ /// other.
+ ///
+ /// # Difference from [`res()`](Self::res)
+ ///
+ /// - [`res()`](Self::res) returns `None` when the resource is missing or the lock is
+ /// poisoned, requiring the caller to handle the `Option` themselves;
+ /// - This method returns a default value constructed by
+ /// `ResourceMarker::__resource_marker_default()` in the same scenario, so the caller
+ /// does not need to handle the missing branch and can use the return value directly.
+ ///
+ /// If you need to route to a specific path (rather than return a default value) when the
+ /// resource is missing, use [`res_or_route`](Self::res_or_route).
+ ///
+ /// # Constraints
+ ///
+ /// - `Res` must satisfy `'static + Send + Sync + ResourceMarker` (`ResourceMarker`
+ /// provides default instantiation capability).
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResContainer;
+ /// let mut container = GlobalResContainer::new();
+ /// container.with_resource(10i32);
+ ///
+ /// // Resource exists: returns the actual value
+ /// assert_eq!(*container.res_or_default::<i32>(), 10);
+ ///
+ /// // Resource does not exist: returns the default value (i32::default() == 0)
+ /// assert_eq!(*container.res_or_default::<String>(), "");
+ /// ```
+ ///
+ /// [`GlobalResource<Res>`]: crate::GlobalResource
+ /// [`with_resource`]: Self::with_resource
+ /// [`__store_res`]: Self::__store_res
#[must_use]
pub fn res_or_default<Res: 'static + Send + Sync + ResourceMarker>(
&self,
@@ -210,7 +709,39 @@ impl<C> Program<C>
where
C: ProgramCollect<Enum = C>,
{
- /// Insert a resource of the given type, cloning the provided value into the store
+ /// Inserts (or overwrites) a resource into the program's global resource container.
+ ///
+ /// This is a convenience wrapper around [`GlobalResContainer::with_resource`] that
+ /// delegates to the `Program`'s internal `resources` container. The resource is stored
+ /// keyed by its type with the same semantics as [`GlobalResContainer::with_resource`]:
+ /// the same `Res` type can only hold one instance, and inserting the same type twice
+ /// overwrites the previous value.
+ ///
+ /// # Parameters
+ ///
+ /// - `res`: The resource value to store. It must satisfy:
+ /// - `'static` — the value cannot contain borrowed data tied to a temporary lifetime.
+ /// - `Send + Sync` — it must be safe to share across threads.
+ /// - [`ResourceMarker`] — providing default value, clone, and modify capabilities.
+ ///
+ /// # Return Value
+ ///
+ /// Returns `&mut self` to allow method chaining, e.g.:
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program
+ /// .with_resource(42i32)
+ /// .with_resource(String::from("hello"));
+ /// ```
+ ///
+ /// # See Also
+ ///
+ /// - [`Self::res`] — read a shared immutable snapshot of a resource.
+ /// - [`Self::modify_res`] — read-modify-write a resource in place.
+ /// - [`GlobalResContainer::with_resource`] — the underlying implementation.
pub fn with_resource<Res: 'static + Send + Sync + ResourceMarker>(
&mut self,
res: Res,
@@ -219,7 +750,51 @@ where
self
}
- /// Modify a resource by type, applying a closure to the resource if present
+ /// Performs a **read-modify-write** operation on a resource stored in the program's
+ /// resource container and returns the closure's return value.
+ ///
+ /// This method delegates to [`GlobalResContainer::modify_res`], which provides the
+ /// full semantics of the read-modify-write operation. In summary:
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the program's resource container.
+ /// 2. If the entry does **not** exist (has not been inserted via [`with_resource`] or
+ /// [`__store_res`]), returns `Return::default()` immediately **without** calling `f`.
+ /// 3. If the entry exists, locks the resource's **own** mutex (distinct from the
+ /// container's global lock, so nested `modify_res` calls do not deadlock).
+ /// 4. Attempts to take ownership of the value via `Arc::try_unwrap`:
+ /// - If no other shared snapshot exists, the original value is taken directly.
+ /// - If another `GlobalResource` holds a snapshot, a **clone** is made via
+ /// `ResourceMarker::__resource_marker_clone()` for modification.
+ /// 5. Calls the closure `f(&mut res)` with the available (`&mut`) reference.
+ /// 6. Writes the modified value back into the resource slot and releases the lock.
+ /// 7. Returns the closure's return value `r`.
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type to modify. Must satisfy
+ /// `'static + Default + ResourceMarker + Send + Sync`.
+ /// - `Return`: The closure's return type. Must implement [`Default`] since a fallback
+ /// value is returned when the resource does not exist.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(10i32);
+ ///
+ /// let doubled = program.modify_res(|v: &mut i32| { *v *= 2; *v });
+ /// assert_eq!(doubled, 20);
+ /// assert_eq!(*program.res::<i32>().unwrap(), 20);
+ ///
+ /// // Resource does not exist: returns Default without invoking the closure.
+ /// let missing = program.modify_res::<String, i32>(|_| 42);
+ /// assert_eq!(missing, 0);
+ /// ```
+ ///
+ /// [`with_resource`]: Self::with_resource
+ /// [`__store_res`]: Self::__store_res
pub fn modify_res<Res, Return>(&self, f: impl FnOnce(&mut Res) -> Return) -> Return
where
Res: 'static + Default + ResourceMarker + Send + Sync,
@@ -228,7 +803,63 @@ where
self.resources.modify_res(f)
}
- /// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly
+ /// Performs a **read-modify-write** operation on a resource and returns a
+ /// [`ChainProcess<C>`] routing result.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method primarily used by `#[chain]` and related macros. It
+ /// behaves similarly to [`modify_res`](Self::modify_res) but is designed for
+ /// **chained program-routing** scenarios: the closure returns a [`ChainProcess<C>`]
+ /// that drives program execution to the next stage. Unlike `modify_res`, this method
+ /// **always** invokes the closure `f`, even when the resource does not exist or the
+ /// lock is poisoned (in those cases it constructs a temporary default resource via
+ /// `ResourceMarker::__resource_marker_default()`).
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Looks up the resource entry by `Res` type in the program's resource container.
+ /// - If missing, constructs a **default resource**, calls `f(&mut def)`, and returns
+ /// its [`ChainProcess<C>`] result.
+ /// - If present, continues to step 2.
+ /// 2. Locks the resource's **own** mutex (independent of the container's global lock,
+ /// so nested calls do not deadlock). If the lock is poisoned, also goes down the
+ /// default-resource branch above.
+ /// 3. Attempts to take ownership via `Arc::try_unwrap`; otherwise clones a copy for
+ /// modification using `ResourceMarker::__resource_marker_clone()`.
+ /// 4. Calls `f(&mut new_res)`, obtains the routing result `r`, and writes the modified
+ /// value back into the resource slot.
+ /// 5. Returns `r`.
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type. Must satisfy
+ /// `'static + Default + ResourceMarker + Send + Sync`.
+ /// - `C`: The program collector type, constrained by the outer `impl` block to
+ /// `ProgramCollect<Enum = C>`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{Program, ChainProcess, error::ChainProcessError};
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(1i32);
+ ///
+ /// let route: ChainProcess<ThisProgram> =
+ /// program.__modify_res_and_return_route(|v: &mut i32| {
+ /// *v += 1;
+ /// ChainProcess::Err(ChainProcessError::Other("done".into()))
+ /// });
+ /// assert!(matches!(route, ChainProcess::Err(_)));
+ /// assert_eq!(*program.res::<i32>().unwrap(), 2);
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`** and is not intended for direct use in
+ /// business code. Use public macro-generated code or public APIs (e.g.
+ /// [`modify_res`](Self::modify_res)) instead.
#[doc(hidden)]
pub fn __modify_res_and_return_route<Res>(
&self,
@@ -240,37 +871,214 @@ where
self.resources.__modify_res_and_return_route(f)
}
- /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly.
+ /// **Takes** a `Res`-typed resource value **out** of the program's resource container
+ /// and returns its ownership.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method used by `#[chain]`, `#[resource]`, and similar macros.
+ /// Unlike [`modify_res`](Self::modify_res), which modifies a resource in place and
+ /// writes it back, this method **moves** the resource out of the container (or clones
+ /// an independent copy when shared snapshots exist) and returns it to the caller.
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Looks up the resource entry by `Res` type.
+ /// - If missing, constructs and returns a **default instance** via
+ /// `ResourceMarker::__resource_marker_default()`.
+ /// 2. Locks the resource's own mutex. If poisoned, also returns a default instance.
+ /// 3. Tries `Arc::try_unwrap` to move the original value out; otherwise clones a copy
+ /// via `ResourceMarker::__resource_marker_clone()`.
+ /// 4. Returns the taken `Res` value. Note that the resource slot is **cleared** by
+ /// this operation; subsequent calls to [`res`](Self::res) for the same resource may
+ /// observe an emptied/default-valued slot.
+ ///
+ /// # Type Parameters
///
- /// Extracts a mutable resource from the global store (clone-out), returning an
- /// owned value. The caller must call [`__store_res`] to write back modifications.
+ /// - `Res`: The resource type to extract. Must satisfy
+ /// `'static + Default + ResourceMarker + Send + Sync`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(3i32);
+ /// let extracted: i32 = program.__extract_res_mut();
+ /// assert_eq!(extracted, 3);
+ /// ```
+ ///
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`** and is not intended for direct use in
+ /// business code.
#[doc(hidden)]
#[must_use]
pub fn __extract_res_mut<Res: 'static + Default + ResourceMarker + Send + Sync>(&self) -> Res {
self.resources.__extract_res_mut()
}
- /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly.
+ /// **Overwrites** a resource value into the program's resource container.
+ ///
+ /// # Purpose
+ ///
+ /// This is an internal method used by `#[chain]`, `#[resource]`, and similar macros.
+ /// It behaves almost identically to [`with_resource`](Self::with_resource) (both
+ /// store/overwrite a resource by type), but differs in signature:
+ ///
+ /// - [`with_resource`](Self::with_resource) takes `&mut self` and returns `&mut Self`,
+ /// suitable for chained initialization.
+ /// - `__store_res` takes `&self` and returns no value, suitable for **runtime**
+ /// dynamic overwrite/update when only an immutable reference is available.
+ ///
+ /// # Execution Steps
+ ///
+ /// 1. Acquires the container's global lock (silently returns if poisoned).
+ /// 2. Looks up the existing entry by `Res` type:
+ /// - If the entry does **not** exist, creates a new `Arc<Mutex<Arc<Res>>>` wrapper
+ /// under `TypeId::of::<Res>()` and inserts it.
+ /// - If it already exists, attempts to update it in place under the resource's own
+ /// lock. If that fails (type mismatch or poisoned lock), replaces the entire entry.
+ /// 3. Releases the container's global lock.
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type. Must satisfy
+ /// `'static + Send + Sync + ResourceMarker`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ ///
+ /// // Runtime write
+ /// program.__store_res(8i32);
+ /// assert_eq!(*program.res::<i32>().unwrap(), 8);
+ ///
+ /// // Overwrite
+ /// program.__store_res(9i32);
+ /// assert_eq!(*program.res::<i32>().unwrap(), 9);
+ /// ```
///
- /// Stores a modified resource value back into the global store.
+ /// # Note
+ ///
+ /// This method is **`#[doc(hidden)]`** and is not intended for direct use in
+ /// business code. Use [`with_resource`](Self::with_resource) or
+ /// [`modify_res`](Self::modify_res) instead.
#[doc(hidden)]
pub fn __store_res<Res: 'static + Send + Sync + ResourceMarker>(&self, val: Res) {
self.resources.__store_res(val);
}
- /// Get an resources by type, returning `Res` if present
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the
+ /// program's resource container.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type:
+ /// - If the entry **does not exist** (has not been inserted via
+ /// [`with_resource`](Self::with_resource) or [`__store_res`](Self::__store_res)),
+ /// returns `None`.
+ /// - If the entry exists, continues to step 2.
+ /// 2. Locks the resource's **own** mutex (independent of the container's global lock,
+ /// so nested calls do not deadlock).
+ /// 3. On success, clones the internal `Arc<Res>` and wraps it as a
+ /// [`GlobalResource<Res>`] to return.
+ ///
+ /// The returned [`GlobalResource<Res>`] can be dereferenced like `&Res` via `Deref`.
+ /// Multiple callers holding their own [`GlobalResource<Res>`] share the same underlying
+ /// `Arc<Res>` data, providing **read-only consistency**.
+ ///
+ /// # Return Value
+ ///
+ /// Returns `Option<GlobalResource<Res>>`:
+ /// - `Some(GlobalResource<Res>)` when the resource exists and the lock can be acquired;
+ /// - `None` when the resource does not exist or its lock is poisoned.
+ ///
+ /// If you want a default value in the missing case, use [`res_or_default`](Self::res_or_default);
+ /// if you need to route to a specific path, use [`res_or_route`](Self::res_or_route).
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type to read. Must satisfy `'static + Send + Sync`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(10i32);
+ ///
+ /// let res = program.res::<i32>();
+ /// assert_eq!(*res.unwrap(), 10);
+ ///
+ /// // Resource does not exist: returns None
+ /// assert!(program.res::<String>().is_none());
+ /// ```
#[must_use]
pub fn res<Res: 'static + Send + Sync>(&self) -> Option<GlobalResource<Res>> {
self.resources.res()
}
- /// Get a resource by type, returning `GlobalResource<Res>` if present.
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the
+ /// program's resource container; returns the provided routing result if the resource
+ /// does not exist.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type:
+ /// - If the entry **exists** and the resource lock can be acquired, clones the
+ /// internal `Arc<Res>` and returns it as `Ok(GlobalResource<Res>)`.
+ /// - If the entry **does not exist** (or its lock is poisoned), returns
+ /// `Err(route)` — the caller-provided [`ChainProcess<C>`] is returned as-is,
+ /// without calling any closure.
+ ///
+ /// # Purpose
///
- /// If the resource is not present, returns the provided [`ChainProcess`] as an `Err`.
+ /// In chained program-routing scenarios (e.g. `#[chain]`), when a resource is missing
+ /// it is desirable to route program flow to an error-handling branch or default path.
+ /// This method allows the caller to pre-construct a [`ChainProcess<C>`] route so that
+ /// missing-resource handling is encapsulated in a single call.
///
/// # Errors
///
- /// Returns `Err(route)` when the resource of type `Res` is not present in the store.
+ /// Returns `Err(route)` (the provided [`ChainProcess<C>`] as-is) when:
+ /// - The resource entry does not exist in the container.
+ /// - The resource's own lock is poisoned.
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type to read. Must satisfy `'static + Send + Sync`.
+ /// - `C`: The program collector type, constrained by the outer `impl` block to
+ /// `ProgramCollect<Enum = C>`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{Program, ChainProcess, error::ChainProcessError};
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// let route: ChainProcess<ThisProgram> =
+ /// ChainProcess::Err(ChainProcessError::Other("missing".into()));
+ ///
+ /// // Resource missing: returns Err(route)
+ /// // Note: ChainProcess is not Clone, so pass the route by value.
+ /// assert!(program.res_or_route::<i32>(route).is_err());
+ ///
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(42i32);
+ /// // Resource exists: returns Ok(GlobalResource)
+ /// let route: ChainProcess<ThisProgram> =
+ /// ChainProcess::Err(ChainProcessError::Other("missing".into()));
+ /// match program.res_or_route::<i32>(route) {
+ /// Ok(res) => assert_eq!(*res, 42),
+ /// Err(_) => panic!("expected Ok"),
+ /// }
+ /// ```
pub fn res_or_route<Res: 'static + Send + Sync>(
&self,
route: ChainProcess<C>,
@@ -278,7 +1086,49 @@ where
self.resources.res_or_route(route)
}
- /// Get a resource by type, returning `GlobalResource<Res>` or inserting a default
+ /// Retrieves an **immutable shared snapshot** of a `Res`-typed resource from the
+ /// program's resource container; returns a default instance if the resource does not
+ /// exist.
+ ///
+ /// # Behavior
+ ///
+ /// 1. Looks up the resource entry by `Res` type:
+ /// - If the entry **exists** and the resource lock can be acquired, clones the
+ /// internal `Arc<Res>`, wraps it as a [`GlobalResource<Res>`], and returns it.
+ /// - If the entry **does not exist** (or its lock is poisoned), constructs a
+ /// default instance via `ResourceMarker::__resource_marker_default()`, wraps it
+ /// as a [`GlobalResource<Res>`], and returns it.
+ ///
+ /// # Difference from [`res`](Self::res)
+ ///
+ /// - [`res`](Self::res) returns `None` when the resource is missing or the lock is
+ /// poisoned, requiring the caller to handle the `Option` themselves.
+ /// - This method returns a default value in the same scenario, so the caller does not
+ /// need to handle the missing branch.
+ ///
+ /// If you need to route to a specific path (rather than return a default value) when
+ /// the resource is missing, use [`res_or_route`](Self::res_or_route).
+ ///
+ /// # Type Parameters
+ ///
+ /// - `Res`: The resource type to read. Must satisfy
+ /// `'static + Send + Sync + ResourceMarker` (the latter provides default
+ /// instantiation capability).
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::Program;
+ /// use mingling_core::MockProgramCollect as ThisProgram;
+ /// let mut program = Program::<ThisProgram>::new_with_args(Vec::<String>::new());
+ /// program.with_resource(10i32);
+ ///
+ /// // Resource exists: returns the actual value
+ /// assert_eq!(*program.res_or_default::<i32>(), 10);
+ ///
+ /// // Resource does not exist: returns the default value (i32::default() == 0)
+ /// assert_eq!(*program.res_or_default::<String>(), "");
+ /// ```
#[must_use]
pub fn res_or_default<Res: 'static + Send + Sync + ResourceMarker>(
&self,
@@ -287,13 +1137,109 @@ where
}
}
-/// Global assets for storing Program global state information
+/// Global type wrapper.
+///
+/// `GlobalResource` is a **thread-safe shared immutable snapshot** wrapper around a resource value.
+/// It internally holds an `Arc<ResType>`, allowing multiple callers to simultaneously hold read-only
+/// access to the same underlying data without worrying about ownership transfer or lifetime entanglement.
+///
+/// # Why `GlobalResource` is Needed
+///
+/// In the [`GlobalResContainer`] (global resource container), resources are stored in a three-layer
+/// `Arc<Mutex<Arc<Res>>>` structure:
+///
+/// - The outer `Mutex` ensures that only one modifier can exclusively access the resource at a time;
+/// - The innermost `Arc<Res>` provides an **immutable shared snapshot**;
+/// - `GlobalResource` is the safe exposure wrapper around that innermost `Arc<Res>`.
+///
+/// When multiple callers each obtain a `GlobalResource` via [`GlobalResContainer::res`], they share
+/// the same underlying `Arc<Res>`, thus guaranteeing consistency of data between them (all being the
+/// same snapshot).
+///
+/// # Usage
+///
+/// `GlobalResource<ResType>` implements [`Deref`] (with target type `ResType`), so it can be
+/// dereferenced directly like `&ResType` to access the underlying value:
+///
+/// ```
+/// # use mingling_core::GlobalResource;
+/// let res = GlobalResource::new(42i32);
+/// assert_eq!(*res, 42);
+/// ```
+///
+/// It can also be used with [`AsRef`] to obtain a `&ResType` reference:
+///
+/// ```
+/// # use mingling_core::GlobalResource;
+/// let res = GlobalResource::new(String::from("hello"));
+/// assert_eq!(res.as_ref(), "hello");
+/// ```
+///
+/// # Relationship with Modification Operations
+///
+/// - `GlobalResource` provides **read-only** access only; the underlying data is immutable.
+/// - When an external caller holds a `GlobalResource` (causing the `Arc` strong reference count
+/// to be > 1), subsequent [`modify_res`](GlobalResContainer::modify_res) operations on the same
+/// resource will **clone** a copy for modification and **will not affect** the snapshot held here.
+/// - This means that `GlobalResource` can serve as a stable view of the resource, retaining the
+/// data content as of the initial read even after modification operations occur.
+///
+/// # Type Constraints
+///
+/// - `ResType` must satisfy `'static + Send + Sync` to ensure safe sharing across threads.
+/// - The resource itself typically also needs to implement [`ResourceMarker`] (providing default
+/// values, cloning, etc.) so that the container can perform default instantiation and
+/// clone-based modification.
+///
+/// # See Also
+///
+/// - [`GlobalResContainer::res`] — obtain a `GlobalResource` snapshot from the container.
+/// - [`GlobalResContainer::res_or_default`] — obtain a snapshot, or return a default value when missing.
+/// - [`GlobalResContainer::res_or_route`] — obtain a snapshot, or route to a specified path when missing.
+///
+/// [`GlobalResContainer`]: crate::GlobalResContainer
+/// [`GlobalResContainer::res`]: crate::GlobalResContainer::res
+/// [`GlobalResContainer::res_or_default`]: crate::GlobalResContainer::res_or_default
+/// [`GlobalResContainer::res_or_route`]: crate::GlobalResContainer::res_or_route
+/// [`ResourceMarker`]: crate::ResourceMarker
pub struct GlobalResource<ResType: 'static + Send + Sync> {
res_arc: Arc<ResType>,
}
impl<ResType: 'static + Send + Sync> GlobalResource<ResType> {
- /// Create a new `GlobalAsset` from an `AssetType` value.
+ /// Creates a new [`GlobalResource`], wrapping the given value directly.
+ ///
+ /// # Parameters
+ ///
+ /// - `res`: The resource value to wrap. The value must be of a `'static + Send + Sync` type
+ /// to ensure it can be safely shared across threads.
+ ///
+ /// # Return Value
+ ///
+ /// Returns a [`GlobalResource<ResType>`] holding `Arc::new(res)`, which can be dereferenced
+ /// via [`Deref`] or [`AsRef`] to access the underlying value.
+ ///
+ /// # Difference from `From<Arc<ResType>>`
+ ///
+ /// - `new` accepts an **owned value** `ResType`, automatically wrapping it into `Arc<ResType>`;
+ /// - `From<Arc<ResType>>` accepts an **already-wrapped `Arc`**, reusing it directly without an
+ /// additional heap allocation.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::GlobalResource;
+ /// let res = GlobalResource::new(42i32);
+ /// assert_eq!(*res, 42);
+ /// ```
+ ///
+ /// # See Also
+ ///
+ /// - [`GlobalResource::from`] (`From<Arc<ResType>>`) — construct from an existing `Arc`.
+ /// - [`Deref`] — dereference to access the underlying value.
+ ///
+ /// [`Deref`]: std::ops::Deref
+ /// [`AsRef`]: std::convert::AsRef
pub fn new(res: ResType) -> Self {
Self {
res_arc: Arc::new(res),
@@ -321,21 +1267,143 @@ impl<ResType: 'static + Send + Sync> AsRef<ResType> for GlobalResource<ResType>
}
}
-/// Resource marker trait, types that implement the Clone and Default traits can be considered as resources
+/// Marks a type as a **program global resource** (`Res`) that can be stored in the
+/// [`GlobalResContainer`].
+///
+/// # Purpose
+///
+/// `ResourceMarker` is a marker trait that resource types must implement, providing three fundamental
+/// capabilities:
+///
+/// 1. **Cloning** (`__resource_marker_clone`) — when a resource in the container is held by an external
+/// shared snapshot (such as a [`GlobalResource`]), modification operations need to clone an independent
+/// copy for modification, to avoid affecting the external snapshot.
+/// 2. **Default instantiation** (`__resource_marker_default`) — when a resource entry is missing, or the
+/// lock is poisoned, the container needs a default instance as a fallback value.
+/// 3. **Modification through the global program container** (`__resource_marker_modify`) — locates the
+/// currently active [`Program<C>`] via the type parameter `C` and performs a read-modify-write
+/// operation on the `&mut Self` resource within it.
+///
+/// # Relationship with `Default + Clone`
+///
+/// This trait provides an **automatic blanket implementation** for all types satisfying
+/// `T: Default + Clone + Send + Sync + 'static`, so ordinary user-defined data types do not need to
+/// manually implement `ResourceMarker`. If a type has custom "default value" or "cloning" semantics
+/// (for example, if the resource internally contains `Arc`, `Rc`, singleton references, etc.), users may
+/// also **manually implement** this trait to override the default behavior.
+///
+/// # Comparison of the Three Methods' Uses
+///
+/// | Method | Invocation Scenario | Corresponding Blanket Implementation |
+/// |--------|-------------------|--------------------------------------|
+/// | `__resource_marker_clone` | When the resource is held by a shared snapshot, clone a copy before modification | `Clone::clone` |
+/// | `__resource_marker_default` | Fallback value when the resource is missing or the lock is poisoned | `Default::default` |
+/// | `__resource_marker_modify` | Perform a read-modify-write on `&mut Self` in the global program container | Calls `this::<C>().modify_res(f)` |
+///
+/// # Role in Macro Expansion
+///
+/// In code expanded from `#[chain]`, `#[resource]`, and similar macros, wherever a type needs to be
+/// treated as a resource for injection, modification, extraction, or storage, this trait's constraint
+/// (`Res: ResourceMarker`) is implicitly relied upon. The macro code itself does not care about the
+/// specific type of the resource; it only requires that the type implements the three capabilities
+/// of this trait.
+///
+/// # Constraints
+///
+/// Types implementing this trait must simultaneously satisfy `'static + Send + Sync`, to ensure
+/// that the resource can be safely shared across threads and does not carry a non-static lifetime.
+///
+/// # Note
+///
+/// All methods of this trait are **`#[doc(hidden)]`** internal methods and should not be called directly
+/// in business code. The public APIs exposed are [`GlobalResContainer::with_resource`],
+/// [`GlobalResContainer::modify_res`], [`GlobalResContainer::res`], etc.
+///
+/// [`GlobalResContainer`]: crate::GlobalResContainer
+/// [`GlobalResource`]: crate::GlobalResource
+/// [`Program<C>`]: crate::Program
+/// [`this`]: crate::this
+/// [`GlobalResContainer::with_resource`]: crate::GlobalResContainer::with_resource
+/// [`GlobalResContainer::modify_res`]: crate::GlobalResContainer::modify_res
+/// [`GlobalResContainer::res`]: crate::GlobalResContainer::res
pub trait ResourceMarker {
- /// Clone the resource. This is an internal method used by the resource injection system
- /// and should not be called directly by user code.
+ /// Clones the current resource value and returns an independent new instance.
+ ///
+ /// # Invocation Scenario
+ ///
+ /// When an external caller holds a shared snapshot of the resource (with an `Arc` strong reference
+ /// count > 1), the container's modification operation (such as [`modify_res`](GlobalResContainer::modify_res))
+ /// cannot directly take ownership of the resource, so this method is called to **clone a copy**
+ /// for modification, ensuring the external snapshot is not affected.
+ ///
+ /// # Implementation Conventions
+ ///
+ /// - Must return an independent instance that is logically equivalent (with the same value) to
+ /// `self`; modifying the return value must not affect the original value.
+ /// - The blanket implementation directly delegates to `Clone::clone(self)`.
+ /// - For types containing reference-counted structures such as `Arc` or `Rc`, the underlying data
+ /// should be deep-cloned rather than merely copying the reference, unless sharing is explicitly
+ /// the intended semantics.
#[must_use]
#[doc(hidden)]
fn __resource_marker_clone(&self) -> Self;
- /// Create a default instance of the resource. This is an internal method used by the
- /// resource injection system and should not be called directly by user code.
+ /// Constructs a default instance of the type, used as a fallback value when the resource is missing
+ /// or the lock is poisoned.
+ ///
+ /// # Invocation Scenario
+ ///
+ /// This method is called in the following scenarios:
+ /// - When obtaining a resource snapshot via [`res_or_default`](GlobalResContainer::res_or_default),
+ /// but the resource entry does not exist in the container or the lock is poisoned;
+ /// - When performing a read-modify-write via
+ /// [`__modify_res_and_return_route`](GlobalResContainer::__modify_res_and_return_route),
+ /// if the resource entry does not exist or the lock is poisoned, a temporary default instance
+ /// needs to be constructed for use by the closure;
+ /// - When extracting a resource via [`__extract_res_mut`](GlobalResContainer::__extract_res_mut),
+ /// if the resource entry does not exist or the lock is poisoned.
+ ///
+ /// # Implementation Conventions
+ ///
+ /// - Each call should return a **brand new** default instance and accept no parameters.
+ /// - The blanket implementation directly delegates to `Default::default()`.
+ /// - If the type's default value has special semantics (for example, default configuration,
+ /// empty collection, zero value, etc.), this should be reflected here.
#[doc(hidden)]
fn __resource_marker_default() -> Self;
- /// Modify the resource using a closure. This is an internal method used by the resource
- /// injection system and should not be called directly by user code.
+ /// Performs a **read-modify-write** operation on a resource of type `Self` in the currently active
+ /// global program container.
+ ///
+ /// # Generic Parameters
+ ///
+ /// - `C`: The program collector type. Must satisfy `ProgramCollect<Enum = C> + 'static`,
+ /// used to locate the currently active [`Program<C>`](crate::Program).
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: A closure receiving `&mut Self`, within which the resource value is modified. The closure's
+ /// return value is not used (returns `()`).
+ ///
+ /// # Behavior
+ ///
+ /// This method is internally equivalent to calling:
+ ///
+ /// ```text
+ /// this::<C>().modify_res(f)
+ /// ```
+ ///
+ /// where `this::<C>()` obtains the thread-bound global [`Program<C>`](crate::Program) instance,
+ /// and then calls its [`modify_res`](crate::Program::modify_res) method to complete the
+ /// read-modify-write. If the resource does not exist, `modify_res` returns `()` (the `Default`
+ /// value of `Return`) without calling `f`.
+ ///
+ /// # Role in Macro Expansion
+ ///
+ /// In code expanded from `#[chain]` and similar macros, when a resource needs to be modified via
+ /// the global program container without explicitly obtaining a container reference, this method is
+ /// called. It automatically locates the correct program instance via the type parameter `C`,
+ /// simplifying code generation logic.
#[doc(hidden)]
fn __resource_marker_modify<C>(f: impl FnOnce(&mut Self))
where
diff --git a/mingling_core/src/asset/help.rs b/mingling_core/src/asset/help.rs
index b3742f2..78da1d1 100644
--- a/mingling_core/src/asset/help.rs
+++ b/mingling_core/src/asset/help.rs
@@ -1,10 +1,49 @@
use crate::RenderResult;
-/// Handles help rendering for command-line arguments
+/// Mingling's program help request.
+///
+/// It provides help capability to a program by binding an entry type. When [`Program`]'s `user_context.help` is `true`,
+/// the first Entry produced by [`Dispatcher`] will be sent into [`HelpRequest`] and rendered into a [`RenderResult`] for the user.
+///
+/// # Manual impl
+///
+/// Normally, [`HelpRequest`] is generated by [`#[help]`](https://docs.rs/mingling/latest/mingling/macros/attr.help.html),
+/// but if you need to implement it manually, please follow the example below:
+///
+/// ```
+/// # use mingling_core::HelpRequest;
+/// # use mingling_core::RenderResult;
+/// # use mingling_core::MockProgramCollect as ThisProgram;
+/// struct GreetHelp;
+/// struct EntryGreet;
+///
+/// impl HelpRequest for GreetHelp {
+/// type Entry = EntryGreet;
+///
+/// fn render_help(p: Self::Entry) -> RenderResult {
+/// let mut result = RenderResult::new();
+/// result.eprintln("USAGE: greet <PARAM...>");
+/// result
+/// }
+/// }
+///
+/// // Register help with the program
+/// // mingling::register_help!(EntryGreet, GreetHelp);
+/// ```
pub trait HelpRequest {
- /// The entry type
+ /// The entry type corresponding to this help request.
+ ///
+ /// This associated type indicates which entry the help request will handle. When the program needs to display help,
+ /// the first entry produced by the `Dispatcher` will be passed to the corresponding help request for processing.
type Entry;
- /// Process the previous value and write the result into the provided [`RenderResult`](./struct.RenderResult.html)
+ /// Render the entry as help information.
+ ///
+ /// This function receives an entry of type [`Self::Entry`] and renders it into a [`RenderResult`].
+ /// Implementors should output help content (such as usage instructions, parameter descriptions, etc.)
+ /// into the [`RenderResult`].
+ ///
+ /// # Return
+ /// Returns a [`RenderResult`] containing the help text, ready to be displayed directly to the user by the caller.
fn render_help(p: Self::Entry) -> RenderResult;
}
diff --git a/mingling_core/src/asset/lazy_resource.rs b/mingling_core/src/asset/lazy_resource.rs
index e8ec55b..bc256c3 100644
--- a/mingling_core/src/asset/lazy_resource.rs
+++ b/mingling_core/src/asset/lazy_resource.rs
@@ -1,30 +1,114 @@
use crate::{ProgramCollect, ResourceMarker, this};
+/// Internal state enum for lazily-loaded resources.
+///
+/// This enum represents the two possible states of `LazyRes`:
+/// - [`LazyInner::Uninit`]: The resource has not been initialized, holding an initialization factory function (`FnMut`) and an optional drop callback.
+/// - [`LazyInner::Init`]: The resource has been initialized, holding the actual value `T` and an optional drop callback.
+///
+/// The optional drop callback has type `FnOnce(T)` and is invoked when the resource is dropped,
+/// allowing the user to obtain final ownership of the resource value for cleanup.
enum LazyInner<T> {
- /// Not yet initialized — holds the factory function.
- /// After init, the factory is **dropped**, no wasted memory.
+ /// Uninitialized state.
+ ///
+ /// The first field holds a callable factory function `FnMut()` that lazily creates the resource value `T` when needed.
+ /// The second field holds an optional drop callback `FnOnce(T)`; if set,
+ /// the cleanup logic will run when the resource is dropped (whether or not it has been initialized).
+ ///
+ /// # Thread Safety
+ ///
+ /// Both the factory function and the drop callback must be `Send + Sync` to ensure that `LazyInner` can be safely
+ /// shared or moved between threads.
Uninit(
+ /// The resource initialization factory function. This function is invoked once when the resource is first accessed,
+ /// and is used to produce the actual resource value.
Box<dyn FnMut() -> T + Send + Sync>,
+ /// Optional drop callback. When the resource is dropped (`drop`), if the resource has been initialized,
+ /// the resource value will be passed to this callback so that custom cleanup logic can run.
Option<Box<dyn FnOnce(T) + Send + Sync>>,
),
- /// Initialized and ready to go.
+ /// Initialized state.
+ ///
+ /// The first field holds the actually created resource value `T`.
+ /// The second field holds the optional drop callback `FnOnce(T)`, which is invoked when the resource is dropped,
+ /// receiving the resource value so that custom cleanup logic can run.
Init(T, Option<Box<dyn FnOnce(T) + Send + Sync>>),
}
-/// A lazily initialized resource that only creates its value on first access.
+/// A lazily-loaded program resource.
+///
+/// `LazyRes<T>` is a container that holds a resource value `T`, which is lazily initialized through a factory
+/// function the first time it is accessed. This type is suitable for scenarios where resource creation needs to be
+/// deferred (such as global configuration, database connection pools, render pipelines, etc.), avoiding unnecessary
+/// initialization overhead.
+///
+/// # Features
+///
+/// - **Lazy initialization**: The resource is created only upon the first call to [`LazyRes::get_ref`], [`LazyRes::get_mut`], or
+/// [`LazyRes::get_clone`]; the factory function runs exactly once.
+/// - **Custom drop callback**: A `FnOnce(T)` callback can be set via [`LazyRes::new_with_drop`], [`LazyRes::with_on_drop`],
+/// or [`LazyRes::set_on_drop`], obtaining final ownership of the resource value and executing cleanup logic when the resource is dropped.
+/// - **Thread safety**: `T` must satisfy `Send + Sync`, and the factory function and drop callback must also be `Send + Sync`,
+/// ensuring `LazyRes<T>` can be safely shared or moved between threads.
+/// - **Rich value extraction**: Supports multiple ways to extract the resource value, including [`LazyRes::into_inner`], [`LazyRes::unwrap`],
+/// [`LazyRes::unwrap_or`], and [`LazyRes::unwrap_or_default`].
+///
+/// # Examples
+///
+/// ```
+/// use mingling_core::LazyRes;
///
-/// Unlike `Option<T>` + persistent `Box<dyn FnMut>`, the factory function is
-/// **consumed and dropped** after initialization. No leftover allocation.
+/// // Create a lazily initialized configuration object
+/// let mut config = LazyRes::new(|| {
+/// // Expensive initialization runs here, only once on first access
+/// String::from("default-config")
+/// });
///
-/// Initialization is triggered by `get_ref()`, `get_mut()`, or `get_clone()`.
+/// assert!(!config.is_initialized());
+/// let value = config.get_ref();
+/// assert_eq!(value, "default-config");
+/// assert!(config.is_initialized());
+/// ```
+///
+/// # Performance Considerations
+///
+/// First access requires a `&mut self` mutable borrow because the internal state must transition from uninitialized to
+/// initialized. If the resource is never accessed (i.e., initialization is never triggered), the factory function is never
+/// called and no initialization overhead is incurred.
+///
+/// # Generic Constraints
+///
+/// - `T: Send + Sync + 'static` — The resource value must be safely shareable between threads and must not hold
+/// references with a non-'static lifetime.
+/// - The factory function type is `FnMut() -> T + Send + Sync + 'static`, meaning the factory may be invoked
+/// multiple times (though in practice it runs only once) and must not capture non-'static lifetime references.
pub struct LazyRes<T: Send + Sync + 'static> {
+ /// Internal state, which may be either uninitialized (`LazyInner::Uninit`) or initialized (`LazyInner::Init`).
inner: LazyInner<T>,
}
impl<T: Send + Sync + 'static> LazyRes<T> {
- /// Creates a new lazily initialized resource with a custom initializer.
+ /// Creates a new lazy resource, whose value is initialized via the factory function `f` on first access.
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: The resource initialization factory function. This function runs once on the first call to `get_ref`, `get_mut`, or
+ /// `get_clone`, and is used to produce the actual resource value.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `LazyRes<T>` instance that is not yet initialized.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
///
- /// The factory `f` is called on first access, then dropped.
+ /// let mut res = LazyRes::new(|| String::from("hello"));
+ /// assert!(!res.is_initialized());
+ /// assert_eq!(*res.get_ref(), "hello");
+ /// assert!(res.is_initialized());
+ /// ```
#[must_use]
pub fn new(f: impl FnMut() -> T + Send + Sync + 'static) -> Self {
Self {
@@ -32,9 +116,32 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Creates a new lazily initialized resource with a custom initializer and
- /// an optional on-drop callback that receives ownership of the inner value when
- /// the `LazyRes` is dropped.
+ /// Creates a new lazy resource and simultaneously sets a drop callback.
+ ///
+ /// When the resource is dropped (`drop`), if the resource was initialized via the factory function,
+ /// ownership of the resource value will be handed to the `on_drop` callback so that custom cleanup logic can run.
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: The resource initialization factory function, executed on first access.
+ /// - `on_drop`: The drop callback. When the resource is dropped and has been initialized, the resource value is passed to it.
+ ///
+ /// # Returns
+ ///
+ /// Returns a `LazyRes<T>` instance that has a drop callback set and is not yet initialized.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new_with_drop(
+ /// || 42,
+ /// |val| println!("Resource {} is being dropped", val),
+ /// );
+ /// res.get_ref();
+ /// drop(res); // triggers on_drop callback
+ /// ```
#[must_use]
pub fn new_with_drop(
f: impl FnMut() -> T + Send + Sync + 'static,
@@ -45,15 +152,30 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Chains an on-drop callback onto the lazy resource, returning ownership.
+ /// Sets a drop callback using a consuming builder-style chained call.
+ ///
+ /// Similar to [`LazyRes::new_with_drop`], but uses a builder style:
+ /// `with_on_drop` allows chaining a drop callback after the resource has been created.
+ /// If a drop callback was previously set, the new callback replaces the old one.
+ ///
+ /// # Parameters
///
- /// This method consumes `self` and returns it with the callback attached,
- /// allowing a builder‑style pattern.
+ /// - `self`: The current `LazyRes` instance.
+ /// - `on_drop`: The drop callback, executed when the resource is dropped.
///
- /// The callback receives ownership of the inner value when the `LazyRes` is
- /// dropped. If the resource has not yet been initialized, the callback is stored
- /// and will be invoked once initialization happens *and* the `LazyRes` is later
- /// dropped.
+ /// # Returns
+ ///
+ /// Returns the same `LazyRes` instance with the drop callback set.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let res = LazyRes::new(|| 100).with_on_drop(|val| {
+ /// println!("Dropping value: {}", val);
+ /// });
+ /// ```
#[must_use]
pub fn with_on_drop(mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) -> Self {
match &mut self.inner {
@@ -64,10 +186,23 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
self
}
- /// Sets or replaces the on-drop callback for the initialized value.
- /// The callback is called with ownership of the inner value when the `LazyRes` is dropped.
- /// If the resource has not been initialized yet, the callback is stored and applied
- /// upon initialization.
+ /// Sets a drop callback via a mutable reference.
+ ///
+ /// Unlike [`LazyRes::with_on_drop`], `set_on_drop` modifies `&mut self` via mutable borrow,
+ /// without consuming ownership of `self`. If a drop callback was previously set, the new callback replaces the old one.
+ ///
+ /// # Parameters
+ ///
+ /// - `on_drop`: The drop callback, executed when the resource is dropped.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| 42);
+ /// res.set_on_drop(|val| println!("Final value: {}", val));
+ /// ```
pub fn set_on_drop(&mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) {
match &mut self.inner {
LazyInner::Uninit(_, existing_opt) | LazyInner::Init(_, existing_opt) => {
@@ -76,11 +211,42 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Returns `true` if the resource has been initialized.
+ /// Checks whether the resource has been fully initialized.
+ ///
+ /// Returns `true` if the resource has been initialized (i.e., the factory function has already been invoked once).
+ /// Otherwise returns `false`.
+ ///
+ /// # Returns
+ ///
+ /// - `true`: The resource has been initialized.
+ /// - `false`: The resource has not yet been initialized.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| 42);
+ /// assert!(!res.is_initialized());
+ /// res.get_ref();
+ /// assert!(res.is_initialized());
+ /// ```
pub const fn is_initialized(&self) -> bool {
matches!(&self.inner, LazyInner::Init(_, _))
}
+ /// Forces initialization of the resource (if not already initialized).
+ ///
+ /// This is an internal method that ensures the resource is in an initialized state. If the resource is already
+ /// initialized, it returns immediately without doing anything; if not yet initialized, it calls the factory function
+ /// to create the resource value and transitions to the [`LazyInner::Init`] state.
+ ///
+ /// If the factory function panics during execution, the internal state is replaced with a "poisoned" placeholder
+ /// value, and any subsequent access to the resource will trigger an `unreachable!()` panic.
+ ///
+ /// # Returns
+ ///
+ /// A `&mut` reference to the initialized internal state.
fn force_init(&mut self) -> &mut LazyInner<T> {
if matches!(&self.inner, LazyInner::Uninit(_, _)) {
// Replace with a temporary poison value so the real factory can be moved out.
@@ -104,8 +270,23 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
&mut self.inner
}
- /// Returns an immutable reference to the inner value,
- /// calling the initializer on first access.
+ /// Obtains an immutable reference to the resource.
+ ///
+ /// If the resource has not been initialized, this method first invokes the factory function to complete lazy
+ /// initialization, then returns an immutable reference to the resource value.
+ ///
+ /// # Returns
+ ///
+ /// An immutable reference `&T` to the resource value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| String::from("config"));
+ /// assert_eq!(res.get_ref(), "config");
+ /// ```
pub fn get_ref(&mut self) -> &T {
self.force_init();
match &self.inner {
@@ -114,8 +295,24 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Returns a mutable reference to the inner value,
- /// calling the initializer on first access.
+ /// Obtains a mutable reference to the resource.
+ ///
+ /// If the resource has not been initialized, this method first invokes the factory function to complete lazy
+ /// initialization, then returns a mutable reference to the resource value, allowing modification of the resource.
+ ///
+ /// # Returns
+ ///
+ /// A mutable reference `&mut T` to the resource value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| 10);
+ /// *res.get_mut() = 20;
+ /// assert_eq!(*res.get_ref(), 20);
+ /// ```
pub fn get_mut(&mut self) -> &mut T {
self.force_init();
match &mut self.inner {
@@ -124,7 +321,28 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Returns a clone of the inner value, calling the initializer if necessary.
+ /// Obtains a cloned copy of the resource value.
+ ///
+ /// If the resource has not been initialized, this method first completes lazy initialization, then clones the
+ /// resource value and returns it. This method requires `T` to implement the [`Clone`] trait.
+ ///
+ /// # Returns
+ ///
+ /// A cloned copy `T` of the resource value.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `T: Clone` — The resource type must support cloning.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| vec![1, 2, 3]);
+ /// let cloned = res.get_clone();
+ /// assert_eq!(cloned, vec![1, 2, 3]);
+ /// ```
pub fn get_clone(&mut self) -> T
where
T: Clone,
@@ -132,10 +350,31 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
self.get_ref().clone()
}
- /// Consumes the lazy resource and returns the inner value, if initialized.
+ /// Extracts the internal resource value, returning `Option<T>`.
///
- /// Unlike `reset()`, this **drops** the lazy wrapper entirely.
- /// If you need to re-initialize, just construct a new `LazyRes::new(f)`.
+ /// Consumes the current `LazyRes`; if the resource has been initialized, returns `Some(value)`;
+ /// if the resource has not been initialized, returns `None`.
+ ///
+ /// Note: Unlike [`LazyRes::unwrap`], this method does not force initialization,
+ /// nor does it invoke the drop callback.
+ ///
+ /// # Returns
+ ///
+ /// - `Some(T)`: The resource has been initialized; returns the internal resource value.
+ /// - `None`: The resource has not been initialized.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| 7);
+ /// res.get_ref();
+ /// assert_eq!(res.into_inner(), Some(7));
+ ///
+ /// let res2 = LazyRes::<i32>::new(|| 7);
+ /// assert_eq!(res2.into_inner(), None);
+ /// ```
pub fn into_inner(mut self) -> Option<T> {
// Take a temporary replacement to avoid moving out of a Drop type.
let inner = std::mem::replace(
@@ -148,14 +387,28 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Consumes the lazy resource and returns the inner value.
+ /// Unwraps the internal resource value, panicking if the resource has not been initialized.
///
- /// If the resource has not been initialized, the initializer is called first.
- /// This is different from `into_inner()` which returns `None` if uninitialized.
+ /// Consumes the current `LazyRes` and returns the internal resource value `T`. Unlike [`LazyRes::into_inner`],
+ /// this method requires the resource to already be initialized; otherwise it triggers a panic.
///
/// # Panics
///
- /// Panics if the resource has not been initialized.
+ /// Calling this method when the resource has not been initialized will trigger a panic.
+ ///
+ /// # Returns
+ ///
+ /// The internal resource value `T`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::new(|| 13);
+ /// res.get_ref();
+ /// assert_eq!(res.unwrap(), 13);
+ /// ```
pub fn unwrap(mut self) -> T {
match std::mem::replace(
&mut self.inner,
@@ -168,7 +421,31 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Consumes the lazy resource, calling the initializer first if not yet initialized.
+ /// Unwraps the internal resource value, returning a default value if the resource has not been initialized.
+ ///
+ /// Consumes the current `LazyRes`; if the resource has been initialized, returns the internal resource value `T`;
+ /// otherwise returns the provided `default` value as a substitute. This method does not force initialization.
+ ///
+ /// # Parameters
+ ///
+ /// - `default`: The substitute value returned when the resource has not been initialized.
+ ///
+ /// # Returns
+ ///
+ /// The internal resource value if the resource has been initialized; otherwise `default`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let res: LazyRes<i32> = LazyRes::new(|| 5);
+ /// assert_eq!(res.unwrap_or(100), 100);
+ ///
+ /// let mut res2 = LazyRes::new(|| 5);
+ /// res2.get_ref();
+ /// assert_eq!(res2.unwrap_or(100), 5);
+ /// ```
pub fn unwrap_or(mut self, default: T) -> T {
if matches!(&self.inner, LazyInner::Uninit(_, _)) {
default
@@ -183,9 +460,30 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
}
}
- /// Consumes the lazy resource, calling the initializer first if not yet initialized.
+ /// Unwraps the internal resource value, returning `T::default()` if the resource has not been initialized.
///
- /// If the resource has not been initialized, `T::default()` is used as the fallback.
+ /// Consumes the current `LazyRes`; if the resource has been initialized, returns the internal resource value `T`;
+ /// otherwise returns `T::default()` as a substitute. This method does not force initialization.
+ ///
+ /// This is a convenience wrapper around [`LazyRes::unwrap_or`], using `T::default()` as the
+ /// default value.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `T: Default` — The resource type must implement the [`Default`] trait.
+ ///
+ /// # Returns
+ ///
+ /// The internal resource value if the resource has been initialized; otherwise `T::default()`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let res: LazyRes<i32> = LazyRes::new(|| 42);
+ /// assert_eq!(res.unwrap_or_default(), 0);
+ /// ```
pub fn unwrap_or_default(self) -> T
where
T: Default,
@@ -216,14 +514,12 @@ impl<T: Send + Sync + 'static> Drop for LazyRes<T> {
}
impl<T: Send + Sync + Default + 'static> Default for LazyRes<T> {
- /// Creates an uninitialized `LazyRes<T>` whose initializer returns `T::default()`.
fn default() -> Self {
Self::new(|| T::default())
}
}
impl<T: Send + Sync + 'static> From<T> for LazyRes<T> {
- /// Creates a `LazyRes<T>` from an already-initialized value.
fn from(value: T) -> Self {
Self {
inner: LazyInner::Init(value, None),
@@ -232,7 +528,28 @@ impl<T: Send + Sync + 'static> From<T> for LazyRes<T> {
}
impl<T: Send + Sync + 'static> LazyRes<T> {
- /// Creates a lazily initialized resource using `T::default()` as the initializer.
+ /// Creates a lazily initialized default resource.
+ ///
+ /// This is an alias method for [`LazyRes::default`], using `T::default()` as the factory function's return value.
+ /// Like [`LazyRes::new`], the resource value is created only upon first access.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `T: Default` — The resource type must implement the [`Default`] trait.
+ ///
+ /// # Returns
+ ///
+ /// Returns a not-yet-initialized `LazyRes<T>` instance whose factory function will return `T::default()`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::<i32>::lazy_default();
+ /// assert!(!res.is_initialized());
+ /// assert_eq!(*res.get_ref(), 0);
+ /// ```
#[must_use]
pub fn lazy_default() -> Self
where
@@ -241,18 +558,100 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
Self::default()
}
- /// Creates a lazily initialized resource with a custom initializer.
+ /// Creates a lazily initialized resource using the specified factory function.
+ ///
+ /// This is an alias method for [`LazyRes::new`], providing a name more aligned with "lazy initialization" semantics.
+ /// The factory function `f` runs once when the resource is first accessed, producing the actual resource value.
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: The resource initialization factory function. This function runs once on the first call to `get_ref`, `get_mut`, or
+ /// `get_clone`, and is used to produce the actual resource value.
+ ///
+ /// # Returns
///
- /// Same as `LazyRes::new`.
+ /// Returns a not-yet-initialized `LazyRes<T>` instance.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyRes;
+ ///
+ /// let mut res = LazyRes::lazy_init(|| String::from("lazy"));
+ /// assert!(!res.is_initialized());
+ /// assert_eq!(*res.get_ref(), "lazy");
+ /// ```
pub fn lazy_init(f: impl FnMut() -> T + Send + Sync + 'static) -> Self {
Self::new(f)
}
}
-/// Provides convenience methods for types, allowing them to easily
-/// create a corresponding `LazyRes<T>`.
+/// A trait that provides convenient lazy-initialization capabilities.
+///
+/// The `LazyInit` trait provides a set of convenience methods for all types satisfying `Send + Sync + 'static`
+/// to create [`LazyRes`]-wrapped resources. Any type satisfying those constraints can directly call
+/// [`LazyInit::lazy_default`] or [`LazyInit::lazy_init`] by type name,
+/// without needing to explicitly write the more verbose `LazyRes<T>::new(...)`.
+///
+/// # Implementation
+///
+/// This trait is automatically implemented for all `T: Send + Sync + 'static` types
+/// (via `impl<T: Send + Sync + 'static> LazyInit for T {}`),
+/// so manual implementation is generally unnecessary.
+///
+/// # Method 1: `lazy_default`
+///
+/// Creates a `LazyRes<T>` using `T::default()` as the factory function's return value.
+/// Requires `T` to implement the [`Default`] trait.
+///
+/// ```
+/// use mingling_core::{LazyRes, LazyInit};
+///
+/// let mut res = i32::lazy_default();
+/// assert!(!res.is_initialized());
+/// assert_eq!(*res.get_ref(), 0);
+/// ```
+///
+/// # Method 2: `lazy_init`
+///
+/// Creates a `LazyRes<T>` using a custom factory function. The factory function executes when
+/// the resource is first accessed to produce the actual value.
+///
+/// ```
+/// use mingling_core::{LazyRes, LazyInit};
+///
+/// let mut res = String::lazy_init(|| String::from("hello"));
+/// assert!(!res.is_initialized());
+/// assert_eq!(*res.get_ref(), "hello");
+/// ```
+///
+/// # Generic Constraints
+///
+/// - `Self: Send + Sync + 'static` — The type must be safely shareable between threads and must not hold
+/// non-'static lifetime references.
pub trait LazyInit: Send + Sync + 'static {
- /// Creates a lazily initialized resource for this type using `Default` as the initializer.
+ /// Creates a lazily initialized default resource.
+ ///
+ /// Uses `Self::default()` as the factory function, returning a not-yet-initialized
+ /// [`LazyRes<Self>`](LazyRes). The resource value is created via `Self::default()` upon first access.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `Self: Default` — The type must implement the [`Default`] trait.
+ ///
+ /// # Returns
+ ///
+ /// Returns a not-yet-initialized `LazyRes<Self>` instance.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyInit;
+ ///
+ /// let mut res = i32::lazy_default();
+ /// assert!(!res.is_initialized());
+ /// assert_eq!(*res.get_ref(), 0);
+ /// ```
#[must_use]
fn lazy_default() -> LazyRes<Self>
where
@@ -261,7 +660,32 @@ pub trait LazyInit: Send + Sync + 'static {
LazyRes::default()
}
- /// Creates a lazily initialized resource for this type with a custom initializer.
+ /// Creates a lazily initialized resource using a custom factory function.
+ ///
+ /// The factory function `f` runs once when the resource is first accessed to produce the actual value.
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: The resource initialization factory function. This function runs once on the first call to `get_ref`, `get_mut`, or
+ /// `get_clone`, and is used to produce the actual resource value.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `Self: Sized` — The type must have a known size.
+ ///
+ /// # Returns
+ ///
+ /// Returns a not-yet-initialized `LazyRes<Self>` instance.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::LazyInit;
+ ///
+ /// let mut res = String::lazy_init(|| String::from("hello"));
+ /// assert!(!res.is_initialized());
+ /// assert_eq!(*res.get_ref(), "hello");
+ /// ```
fn lazy_init(f: impl FnMut() -> Self + Send + Sync + 'static) -> LazyRes<Self>
where
Self: Sized,
@@ -273,8 +697,19 @@ pub trait LazyInit: Send + Sync + 'static {
impl<T: Send + Sync + 'static> LazyInit for T {}
impl<T: Send + Sync + 'static + Default + Clone> ResourceMarker for LazyRes<T> {
- /// Clones the lazy resource. The cloned resource retains any initialized value,
- /// but the initializer is reset to `T::default()`.
+ /// Clones a deep copy of the current lazy resource as either initialized or default state.
+ ///
+ /// If the resource has been initialized, clones the internal resource value and returns a new initialized
+ /// `LazyRes<T>`; if the resource has not been initialized, returns a default `LazyRes<T>` using `T::default()`
+ /// as the factory function.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `T: Clone` — The resource type must implement the [`Clone`] trait.
+ ///
+ /// # Returns
+ ///
+ /// A cloned `LazyRes<T>` instance. The cloned instance does not carry the original instance's drop callback.
fn __resource_marker_clone(&self) -> Self {
match &self.inner {
LazyInner::Init(t, _) => Self {
@@ -284,12 +719,34 @@ impl<T: Send + Sync + 'static + Default + Clone> ResourceMarker for LazyRes<T> {
}
}
- /// Returns a default lazy resource (uninitialized, using `T::default()` as the initializer).
+ /// Creates a default lazy resource instance.
+ ///
+ /// Returns a not-yet-initialized `LazyRes<T>` using `T::default()` as the factory function.
+ /// The resource value is created only upon first access.
+ ///
+ /// # Type Constraints
+ ///
+ /// - `T: Default` — The resource type must implement the [`Default`] trait.
+ ///
+ /// # Returns
+ ///
+ /// A not-yet-initialized default `LazyRes<T>` instance.
fn __resource_marker_default() -> Self {
Self::default()
}
- /// Modifies the current lazy resource via the `this` context provided by `C`.
+ /// Modifies the current lazy resource via `ProgramCollect`.
+ ///
+ /// Uses the provided closure `f` to modify the current `LazyRes<T>`. The modification is delegated
+ /// to the `ProgramCollect` resource collector via `this::<C>().modify_res(f)`.
+ ///
+ /// # Parameters
+ ///
+ /// - `f`: The closure used to modify `LazyRes<T>`, receiving a `&mut Self` parameter.
+ ///
+ /// # Generic Constraints
+ ///
+ /// - `C: ProgramCollect<Enum = C> + 'static` — The program collector type; the `Enum` associated type must equal itself.
fn __resource_marker_modify<C>(f: impl FnOnce(&mut Self))
where
C: ProgramCollect<Enum = C> + 'static,
@@ -303,8 +760,6 @@ mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
-
- /// Helper for tracking drops via an `Arc<AtomicBool>`.
struct DropFlag(Arc<AtomicBool>);
impl Drop for DropFlag {
diff --git a/mingling_core/src/asset/metadata.rs b/mingling_core/src/asset/metadata.rs
index 996b34c..4ac758b 100644
--- a/mingling_core/src/asset/metadata.rs
+++ b/mingling_core/src/asset/metadata.rs
@@ -3,12 +3,35 @@
/// Any type can be attached to an Entry as metadata, allowing the program to
/// carry compile-time-typed, arbitrary description data alongside each
/// registered entry. The [`Metadata`] trait bridges an Entry type (`Self`) to
-/// an arbitrary metadata type `B`.
+/// an arbitrary metadata type `DataType`.
+///
+/// # Manual impl
///
/// It is recommended to use the `#[metadata(Entry)]` attribute macro from
/// [mingling_macros](https://crates.io/crates/mingling_macros) to implement this
/// trait and register the entry via `register_metadata!`.
-pub trait Metadata<B> {
- /// Initializes and returns the metadata value of type `B` for this entry.
- fn init_metadata() -> B;
+///
+/// If you need to implement it manually, you can refer to the following
+/// example:
+///
+/// ```
+/// # use mingling_core::MockProgramCollect as ThisProgram;
+/// # use mingling_core::Metadata;
+/// struct EntryGreet;
+/// struct MyInformation {
+/// name: String
+/// }
+///
+/// impl Metadata<MyInformation> for EntryGreet {
+/// fn init_metadata() -> MyInformation {
+/// MyInformation { name: "Greeting".into() }
+/// }
+/// }
+///
+/// // Register the MyInformation metadata for EntryGreet using `register_metadata!`.
+/// // mingling::macros::register_metadata!(EntryGreet, MyInformation);
+/// ```
+pub trait Metadata<DataType> {
+ /// Initializes and returns the metadata value of type `DataType` for this entry.
+ fn init_metadata() -> DataType;
}
diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs
index b9002b6..ee28836 100644
--- a/mingling_core/src/asset/node.rs
+++ b/mingling_core/src/asset/node.rs
@@ -1,16 +1,39 @@
use just_fmt::kebab_case;
-/// Represents a command node, used to match user-input command paths.
+/// Represents a command node used for matching user-input command paths.
///
-/// The node consists of multiple parts, each separated by a dot (`.`), and automatically converted to kebab-case.
-/// For example, the input string `"node.subnode"` will be converted to a node representation of `["node", "subnode"]`.
+/// The node consists of multiple segments, separated by dots (`.`), which are automatically
+/// converted to kebab-case. For example, the input string `"node.subnode"` would be converted
+/// to the node representation `["node", "subnode"]`.
+///
+/// # Examples
+///
+/// ```
+/// use mingling_core::Node;
+///
+/// // Create a node and append child segments
+/// let node = Node::from("base").join("sub").join("leaf");
+/// assert_eq!(node.to_string(), "base.sub.leaf");
+/// ```
#[derive(Debug, Default)]
pub struct Node {
node: Vec<String>,
}
impl Node {
- /// Append a new part to the node path.
+ /// Appends a new segment to the node path.
+ ///
+ /// This method consumes the current node and returns a new node with the new
+ /// segment appended to the end of the path.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::Node;
+ ///
+ /// let node = Node::from("base").join("sub");
+ /// assert_eq!(node.to_string(), "base.sub");
+ /// ```
#[must_use]
pub fn join(self, node: impl Into<String>) -> Self {
let mut new_node = self.node;
diff --git a/mingling_core/src/asset/renderer.rs b/mingling_core/src/asset/renderer.rs
index 732f9b7..f6dcbd0 100644
--- a/mingling_core/src/asset/renderer.rs
+++ b/mingling_core/src/asset/renderer.rs
@@ -1,10 +1,35 @@
use crate::RenderResult;
-/// Takes over a type (`Self::Previous`) and converts it to a [`RenderResult`](./struct.RenderResult.html)
+/// Rendering logic for Mingling programs
+///
+/// Add a rendering type for registered types in the Mingling program. When they are routed to `to_render()`, this renderer will be invoked to render them into the result output.
+///
+/// # Manual impl
+///
+/// Generally speaking, it is recommended to use the [`#[renderer]`](https://docs.rs/mingling/latest/mingling/macros/attr.renderer.html) macro instead.
+/// If you need to implement this manually, please refer to the following example:
+///
+/// ```
+/// # use mingling_core::Renderer;
+/// # use mingling_core::RenderResult;
+/// # struct MyRenderer;
+/// # struct StateMyType;
+///
+/// impl Renderer for MyRenderer {
+/// type Previous = StateMyType;
+///
+/// fn render(prev: Self::Previous) -> RenderResult {
+/// // The specific rendering logic
+/// # return mingling_core::RenderResult::default();
+/// }
+/// }
+/// ```
pub trait Renderer {
- /// The previous type in the chain
+ /// The previous type handled by the renderer, used to convert it into a render result
type Previous;
- /// Process the previous value and write the result into the provided [`RenderResult`](./struct.RenderResult.html)
+ /// The rendering logic, which converts the `Previous` type into the corresponding [`RenderResult`] output
+ ///
+ /// When a program is routed to the type registered for this renderer, this method will be called to convert and render the previous type into the final result.
fn render(p: Self::Previous) -> RenderResult;
}
diff --git a/mingling_core/src/asset/routable.rs b/mingling_core/src/asset/routable.rs
index 24b7bb1..7ed0fb4 100644
--- a/mingling_core/src/asset/routable.rs
+++ b/mingling_core/src/asset/routable.rs
@@ -1,22 +1,112 @@
-use crate::ChainProcess;
+use crate::{AnyOutput, ChainProcess, Grouped, ProgramCollect};
-/// Provides routing capabilities for converting an item into a `ChainProcess`
-/// directed to either the chain or render processing pipeline.
+/// Represents a type that can be routed within a group.
///
-/// This trait enables items to be dispatched to different processing routes
-/// (chain or render) by wrapping them into an `AnyOutput` and routing them
-/// through the appropriate pipeline.
+/// Used to indicate that a group member can be routed into another [`ChainProcess`]
+/// within the execution logic of a [`Chain`].
+///
+/// # Blanket impl
+///
+/// When a type implements [`Grouped`], it automatically gets a corresponding [`Routable`]
+/// implementation, meaning all types deriving [`Grouped`] can flow through the program loop.
+///
+/// # Reference
+///
+/// You can use the [`routeify`](https://docs.rs/mingling/latest/mingling/macros/attr.routeify.html)
+/// macro and the [`route!`](https://docs.rs/mingling/latest/mingling/macros/macro.route.html) macro
+/// to build flexible program execution logic.
+///
+/// # Example
+///
+/// ```
+/// # use mingling_core::Routable;
+/// # use mingling_core::ChainProcess;
+/// # use mingling_core::MockProgramCollect as ThisProgram;
+/// # use mingling_core::Grouped;
+/// # unsafe impl Grouped<ThisProgram> for Foo {
+/// # fn member_id() -> ThisProgram {
+/// # ThisProgram::Foo
+/// # }
+/// # }
+/// struct Foo;
+///
+/// // With `Grouped` implemented, the type automatically implements `Routable`
+/// // and can be converted into a `ChainProcess` via `to_chain` / `to_render`:
+/// fn takes_chain<T: Routable<ThisProgram>>(value: T) -> ChainProcess<ThisProgram> {
+/// value.to_chain()
+/// }
+///
+/// # fn main() {
+/// # takes_chain(Foo);
+/// # }
+/// ```
pub trait Routable<Group>
where
Self: Sized + 'static,
{
- /// Converts the routable item into a `ChainProcess` directed to the chain route.
+ /// Converts the current type into a [`ChainProcess`] that can be used for execution in a program chain (`Chain`).
+ ///
+ /// # Return value
+ ///
+ /// Returns a [`ChainProcess`] wrapping the current value,
+ /// which will be scheduled for execution in the program chain.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{Grouped, ChainProcess};
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for StateMyType {
+ /// # fn member_id() -> ThisProgram {
+ /// # ThisProgram::Foo
+ /// # }
+ /// # }
+ /// use mingling_core::Routable;
///
- /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline.
+ /// struct StateMyType;
+ ///
+ /// let my_type = StateMyType;
+ /// let process: ChainProcess<ThisProgram> = my_type.to_chain();
+ /// ```
fn to_chain(self) -> ChainProcess<Group>;
- /// Converts the routable item into a `ChainProcess` directed to the render route.
+ /// Converts the current type into a [`ChainProcess`] that can be used for the rendering pipeline.
+ ///
+ /// # Return value
+ ///
+ /// Returns a [`ChainProcess`] wrapping the current value,
+ /// which will be scheduled for execution in the rendering pipeline.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use mingling_core::{Grouped, ChainProcess};
+ /// # use mingling_core::MockProgramCollect as ThisProgram;
+ /// # unsafe impl Grouped<ThisProgram> for StateMyType {
+ /// # fn member_id() -> ThisProgram {
+ /// # ThisProgram::Foo
+ /// # }
+ /// # }
+ /// use mingling_core::Routable;
+ ///
+ /// struct StateMyType;
///
- /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline.
+ /// let my_type = StateMyType;
+ /// let process: ChainProcess<ThisProgram> = my_type.to_render();
+ /// ```
fn to_render(self) -> ChainProcess<Group>;
}
+
+impl<T, C> Routable<C> for T
+where
+ C: ProgramCollect<Enum = C>,
+ T: Grouped<C> + Send,
+{
+ fn to_chain(self) -> ChainProcess<C> {
+ AnyOutput::new(self).route_chain()
+ }
+
+ fn to_render(self) -> ChainProcess<C> {
+ AnyOutput::new(self).route_renderer()
+ }
+}