aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-16 15:43:34 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-16 15:43:34 +0800
commitf7964129a5b5e41fd6da0f191a7e37e93bd36814 (patch)
treecab11413bbb109239e01bc4384a8dfa85757a348
parentae78d9211c879516779dde9dd470d998ba3644a8 (diff)
Add mutable resource injection to `#[chain]` macro
-rw-r--r--CHANGELOG.md44
-rw-r--r--mingling_core/src/asset/global_resource.rs34
-rw-r--r--mingling_macros/src/chain.rs65
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 {