diff options
| -rw-r--r-- | CHANGELOG.md | 44 | ||||
| -rw-r--r-- | mingling_core/src/asset/global_resource.rs | 34 | ||||
| -rw-r--r-- | mingling_macros/src/chain.rs | 65 |
3 files changed, 115 insertions, 28 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d101a6b..ba9c212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,9 +40,33 @@ fn proc(prev: HelloEntry) -> ChainProcess<ThisProgram> { } ``` -3. **\[mingling\]** Added the `dispatch_tree` feature. When enabled, it will automatically generate a prefix tree, improving the command lookup efficiency from O(n) to O(len) +3. **\[macros\]** The `#[chain]` macro now supports mutable resource injection via the `&mut` syntax. When you write: -4. **\[mingling\]** Added `mingling::feature` module for runtime feature detection. You can now check which features are enabled at compile time: +```rust +#[chain] +pub fn handle_some_entry(_prev: SomeEntry, exit: &mut ExitCode) -> NextProcess { + exit.exit_code = 2; + Empty::default() +} +``` + +Will expand: + +```rust +fn proc(_prev: OkEntry) -> ChainProcess<ThisProgram> { + ::mingling::this::<ThisProgram>() + .__modify_res_and_return_any(|exit: &mut ExitCode| { + exit.exit_code = 2; + Empty::default() + }) +} +``` + +This allows directly mutating global resources within chain functions without manually calling `modify_res`. Multiple `&mut` parameters are supported with proper nesting. + +4. **\[mingling\]** Added the `dispatch_tree` feature. When enabled, it will automatically generate a prefix tree, improving the command lookup efficiency from O(n) to O(len) + +5. **\[mingling\]** Added `mingling::feature` module for runtime feature detection. You can now check which features are enabled at compile time: ```rust // Example: Check if a feature is enabled @@ -51,10 +75,10 @@ if mingling::feature::MINGLING_ASYNC { } ``` -5. **\[core\]** Added `with_hook` functions to embed callback events into the program lifecycle -6. **\[core\]** Added `user_context.run_hook` configuration item to control whether the program runs hooks -7. **\[core\]** Added `exec_and_exit`, which will return an `i32` exit code after the program ends -8. **\[core\]** Added `ExitCodeSetup`, you can control the program's exit code by modifying the `mingling::res::ExitCode` resource +6. **\[core\]** Added `with_hook` functions to embed callback events into the program lifecycle +7. **\[core\]** Added `user_context.run_hook` configuration item to control whether the program runs hooks +8. **\[core\]** Added `exec_and_exit`, which will return an `i32` exit code after the program ends +9. **\[core\]** Added `ExitCodeSetup`, you can control the program's exit code by modifying the `mingling::res::ExitCode` resource ```rust #[chain] @@ -68,9 +92,9 @@ fn your_chain(_prev: Prev) -> NextProcess { } ``` -9. **\[core\]** `RenderResult` now carries new data `exit_code` +10. **\[core\]** `RenderResult` now carries new data `exit_code` -10. **\[core\]** Added `modify` function to `ResourceMarker` for modifying a program's global resources +11. **\[core\]** Added `modify` function to `ResourceMarker` for modifying a program's global resources ```rust // Example @@ -84,8 +108,8 @@ this::<ThisProgram>().modify_res::<ExitCode>(|code| { }); ``` -11. **\[core\]** Added panic catch for program execution. -12. **\[core\]** Added the `stdout_setting.silence_panic` option, which is disabled by default. When enabled, `Program`'s `panic!` will not output to the console +12. **\[core\]** Added panic catch for program execution. +13. **\[core\]** Added the `stdout_setting.silence_panic` option, which is disabled by default. When enabled, `Program`'s `panic!` will not output to the console #### **BREAKING CHANGES**: diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs index 8e2ce9f..104367a 100644 --- a/mingling_core/src/asset/global_resource.rs +++ b/mingling_core/src/asset/global_resource.rs @@ -43,6 +43,40 @@ where } } + /// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly + #[doc(hidden)] + pub fn __modify_res_and_return_any<Res, Return>( + &self, + f: impl FnOnce(&mut Res) -> Return, + ) -> impl Into<ChainProcess<C>> + where + Res: 'static + Default + ResourceMarker + Send + Sync, + Return: Into<ChainProcess<C>>, + { + let mut guard = match self.resources.lock() { + Ok(guard) => guard, + Err(_) => { + let mut default_res = Res::res_default(); + return f(&mut default_res); + } + }; + if let Some(arc_res) = guard + .get_mut(&TypeId::of::<Res>()) + .and_then(|a| a.downcast_mut::<Arc<Res>>()) + { + let mut new_res = match Arc::try_unwrap(std::mem::take(arc_res)) { + Ok(val) => val, + Err(arc) => (*arc).res_clone(), + }; + let r = f(&mut new_res); + *arc_res = Arc::new(new_res); + r + } else { + let mut default_res = Res::res_default(); + f(&mut default_res) + } + } + /// Get an resources by type, returning `Res` if present pub fn res<Res: 'static + Send + Sync>(&self) -> Option<GlobalResource<Res>> { let guard = self.resources.lock().ok()?; diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index f9291e0..496a0a4 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -11,6 +11,7 @@ struct ResourceInjection { full_type: Type, inner_type: TypePath, is_ref: bool, + is_mut: bool, } /// Extracts the previous type and parameter name from function arguments, @@ -64,9 +65,13 @@ fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<Resourc let full_type = *(*ty).clone(); // Try to extract inner type for reference patterns like `&Age` -> `Age` - let (inner_type, is_ref) = match &full_type { + // and `&mut Age` -> `Age` + let (inner_type, is_ref, is_mut) = match &full_type { Type::Reference(ref_type) => match &*ref_type.elem { - Type::Path(type_path) => (type_path.clone(), true), + Type::Path(type_path) => { + let is_mut = ref_type.mutability.is_some(); + (type_path.clone(), true, is_mut) + } _ => { return Err(syn::Error::new( ty.span(), @@ -74,7 +79,7 @@ fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<Resourc )); } }, - Type::Path(type_path) => (type_path.clone(), false), + Type::Path(type_path) => (type_path.clone(), false, false), _ => { return Err(syn::Error::new( ty.span(), @@ -88,6 +93,7 @@ fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<Resourc full_type, inner_type, is_ref, + is_mut, }); } FnArg::Receiver(_) => { @@ -207,8 +213,12 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { quote! { #group_name } }; - // Build resource injection statements (let ... = ...) - let resource_stmts: Vec<_> = resources + // Separate resources into immutable refs and mutable refs + let immut_resources: Vec<_> = resources.iter().filter(|r| !r.is_mut).collect(); + let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); + + // Build resource injection statements for immutable references (let ... = ...) + let immut_resource_stmts: Vec<_> = immut_resources .iter() .map(|res| { let var_binding_name = syn::Ident::new( @@ -233,16 +243,37 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { }) .collect(); - let has_resources = !resources.is_empty(); + // Build nested __modify_res_and_return_any wrappers for mutable references + // The innermost layer is the original function body, wrapping outward for each mutable resource. + // The function returns a value, so the return values need to be properly chained. + let body_stmts = &fn_body.stmts; + let mut wrapped_body = quote! { + #(#body_stmts)* + }; + + // Wrap from inside to outside: the first mutable parameter becomes the outermost wrapper, + // and the last mutable parameter becomes the innermost wrapper. + // Therefore iterate mut_resources and wrap outward. + for res in mut_resources.iter() { + let var_name = &res.var_name; + let inner_type = &res.inner_type; + wrapped_body = quote! { + ::mingling::this::<#program_type>().__modify_res_and_return_any(|#var_name: &mut #inner_type| { + #wrapped_body + }).into() + }; + } + + let has_immut_resources = !immut_resources.is_empty(); + let has_mut_resources = !mut_resources.is_empty(); #[cfg(feature = "async")] let proc_fn = if is_async_fn { - if has_resources { - let body_stmts = &fn_body.stmts; + if has_immut_resources || has_mut_resources { quote! { async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { - #(#resource_stmts)* - #(#body_stmts)* + #(#immut_resource_stmts)* + #wrapped_body } } } else { @@ -253,12 +284,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } } } else { - if has_resources { - let body_stmts = &fn_body.stmts; + if has_immut_resources || has_mut_resources { quote! { async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { - #(#resource_stmts)* - #(#body_stmts)* + #(#immut_resource_stmts)* + #wrapped_body } } } else { @@ -288,12 +318,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { }; #[cfg(not(feature = "async"))] - let proc_fn = if has_resources { - let body_stmts = &fn_body.stmts; + let proc_fn = if has_immut_resources || has_mut_resources { quote! { fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { - #(#resource_stmts)* - #(#body_stmts)* + #(#immut_resource_stmts)* + #wrapped_body } } } else { |
