diff options
Diffstat (limited to 'mingling_core')
| -rw-r--r-- | mingling_core/src/any/group.rs | 81 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain.rs | 44 | ||||
| -rw-r--r-- | mingling_core/src/asset/chain/error.rs | 11 | ||||
| -rw-r--r-- | mingling_core/src/asset/dispatcher.rs | 254 | ||||
| -rw-r--r-- | mingling_core/src/asset/enum_tag.rs | 49 | ||||
| -rw-r--r-- | mingling_core/src/asset/global_resource.rs | 1138 | ||||
| -rw-r--r-- | mingling_core/src/asset/help.rs | 45 | ||||
| -rw-r--r-- | mingling_core/src/asset/lazy_resource.rs | 563 | ||||
| -rw-r--r-- | mingling_core/src/asset/metadata.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/asset/node.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/asset/renderer.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/asset/routable.rs | 110 |
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() + } +} |
