diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-08-12 00:42:07 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-08-12 00:56:52 +0800 |
| commit | d5a039e9d882f8f50b3cc9cdc10bc7bd84a5fa4d (patch) | |
| tree | c0ae87e58690685183e28925a4bc7b42188f2162 | |
| parent | 5f8bd86c982dbfb3d8ed47f84432a3347752157f (diff) | |
refactor(core): extract global resource store into standalone container
Refactor `GlobalResources` from a type-erased `Arc<Mutex<HashMap<...>>>`
alias into a `GlobalResContainer` struct with per-resource mutexes.
This prevents nested `modify_res` calls from deadlocking, allows
independent containers to coexist, and keeps `Program`'s public
resource API unchanged.
| -rw-r--r-- | CHANGELOG.md | 14 | ||||
| -rw-r--r-- | mingling_core/src/asset/global_resource.rs | 495 | ||||
| -rw-r--r-- | mingling_core/src/program.rs | 10 |
3 files changed, 446 insertions, 73 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbe748..c7f0250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,20 @@ None These changes fix incorrect word-index computation and avoid sending empty option values to the underlying completion engine from PowerShell completions. +10. **[`core:resources`]** Refactored the global resource storage from a single type-erased `Arc<Mutex<HashMap<...>>>` container into a standalone `GlobalResContainer` struct with per-resource mutexes. + + Previously, `GlobalResources` was a type alias for `Arc<Mutex<HashMap<TypeId, Box<dyn Any + Sync + Send>>>>`, and the resource methods (`with_resource`, `modify_res`, `__modify_res_and_return_route`, `__extract_res_mut`, `__store_res`, `res`, `res_or_route`, `res_or_default`) were implemented directly on `Program`. Now: + + - **`GlobalResContainer`** — A new public struct owning the resource map (`map: Mutex<HashMap<TypeId, Box<dyn Any + Send + Sync>>>`), with `new()` and `Default` impls. Each resource entry is stored as `Arc<Mutex<Arc<Res>>>` — the outer `Mutex` guards the entry itself (so a resource can be locked without holding the container lock), and the inner `Arc<Res>` is the shared immutable snapshot returned by `res()`. + - **Per-resource mutex** — The container lock is only held for the brief lookup/clone of the entry. This means two nested `modify_res` calls (e.g., two `&mut` resource parameters generated by `#[chain]`) lock _different_ mutexes and cannot deadlock against each other. + - **`Program` delegation** — All resource methods on `Program` now delegate to its internal `resources: GlobalResContainer` member, keeping the public `Program` resource API unchanged. + - **Generic helper methods** — `__modify_res_and_return_route<Res, C>` and `res_or_route<Res, C>` now take an explicit `C: ProgramCollect<Enum = C>` generic parameter (added separately on the container), with `Program`'s versions keeping the single-`Res` signature. + - **`__store_res` semantics** — If an entry already exists for the type and is a matching `Arc<Mutex<Arc<Res>>>`, the value is updated in place under the entry lock; otherwise (missing entry, type mismatch, or poisoned lock) the entry is replaced wholesale. + + The container is standalone and not coupled to any `Program` instance or the global `this::<C>()` context, so any number of independent `GlobalResContainer`s can be created and used simultaneously. + + _No behavioral changes for downstream code — `Program`'s public resource API (`with_resource`, `modify_res`, `res`, `res_or_route`, `res_or_default`) is unchanged, and the `&mut` resource injection syntax for `#[chain]` (sync and async) continues to work as before. A side benefit: nested resource modifications no longer risk deadlock._ + #### Optimizations: 1. **[`pathf`]** Added `is_module` field to `AnalyzeItem` and a new constructor `AnalyzeItem::local_module(module, item_name)` which sets `is_module: true`. The `type_mapping_builder` now tracks whether an item is a module: when generating `type_using.rs`, module items produce `use path::to::module::*;` (glob import) instead of the standard `use path::to::TypeName;` direct import. Non-module items continue to use direct imports as before. The internal data structure changed from `Vec<(String, String)>` to `Vec<(String, String, bool)>` to carry the `is_module` flag through the pipeline. diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs index f37edd0..07d0b5e 100644 --- a/mingling_core/src/asset/global_resource.rs +++ b/mingling_core/src/asset/global_resource.rs @@ -6,22 +6,64 @@ use std::{ use crate::{ChainProcess, Program, ProgramCollect, this}; -/// A thread-safe, type-erased container for storing global resources keyed by their type. +/// A standalone, thread-safe container for storing global resources keyed by their type. /// -/// Use `Program::with_resource` to insert resources and `Program::res` to retrieve them. -pub type GlobalResources = Arc<Mutex<HashMap<TypeId, Box<dyn Any + Sync + Send>>>>; +/// This is the resource store behind [`Program`]'s resource API: every `Program` +/// owns one of these containers and all of its resource operations delegate to it. +/// +/// Unlike the resource API on [`Program`], this container is **not** coupled to a +/// program instance nor to the global `this::<C>()` context, so any number of +/// containers can be created and used at the same time — each with fully +/// independent storage. +/// +/// Each resource is stored behind its **own** [`Mutex`]. The container lock is +/// only held for the brief lookup/clone of the entry, so two nested +/// `modify_res` calls (e.g. two `&mut` resource parameters generated by +/// `#[chain]`) lock **different** mutexes and cannot deadlock against each other. +pub struct GlobalResContainer { + /// Thread-safe storage for resources, keyed by their `TypeId` and protected by a `Mutex`. + /// + /// Each entry is a `Box<dyn Any>` holding an `Arc<Mutex<Arc<Res>>>`: the outer + /// `Mutex` guards the entry itself (so a resource can be locked without + /// holding the container lock), and the inner `Arc<Res>` is the shared + /// immutable snapshot returned by `res()`. + map: Mutex<HashMap<TypeId, Box<dyn Any + Send + Sync>>>, +} + +impl GlobalResContainer { + /// Creates an empty resource container. + #[must_use] + pub fn new() -> Self { + Self { + map: Mutex::new(HashMap::new()), + } + } + + /// Clones the per-resource entry out from under the container lock. + /// + /// The container lock is released as soon as the entry `Arc` is cloned, + /// so all subsequent operations lock only the resource's own mutex. + fn res_entry<Res: 'static>(&self) -> Option<Arc<Mutex<Arc<Res>>>> { + let guard = self.map.lock().ok()?; + let entry = guard + .get(&TypeId::of::<Res>())? + .as_ref() + .downcast_ref::<Arc<Mutex<Arc<Res>>>>() + .map(Arc::clone); + drop(guard); + entry + } -impl<C> Program<C> -where - C: ProgramCollect<Enum = C>, -{ /// Insert a resource of the given type, cloning the provided value into the store pub fn with_resource<Res: 'static + Send + Sync + ResourceMarker>( &mut self, res: Res, ) -> &mut Self { - if let Ok(mut guard) = self.resources.lock() { - guard.insert(TypeId::of::<Res>(), Box::new(Arc::new(res))); + if let Ok(mut guard) = self.map.lock() { + guard.insert( + TypeId::of::<Res>(), + Box::new(Arc::new(Mutex::new(Arc::new(res)))), + ); } self } @@ -32,95 +74,102 @@ where Res: 'static + Default + ResourceMarker + Send + Sync, Return: Default, { - let Ok(mut guard) = self.resources.lock() else { + let Some(entry) = self.res_entry::<Res>() else { return Return::default(); }; - 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).__resource_marker_clone(), - }; - let r = f(&mut new_res); - *arc_res = Arc::new(new_res); - return r; - } - Return::default() + let Ok(mut guard) = entry.lock() else { + return Return::default(); + }; + let mut new_res = match Arc::try_unwrap(std::mem::take(&mut *guard)) { + Ok(val) => val, + Err(arc) => (*arc).__resource_marker_clone(), + }; + let r = f(&mut new_res); + *guard = Arc::new(new_res); + r } /// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly #[doc(hidden)] - pub fn __modify_res_and_return_route<Res>( + pub fn __modify_res_and_return_route<Res, C>( &self, f: impl FnOnce(&mut Res) -> ChainProcess<C>, ) -> ChainProcess<C> where Res: 'static + Default + ResourceMarker + Send + Sync, + C: ProgramCollect<Enum = C>, { - let Ok(mut guard) = self.resources.lock() else { + let Some(entry) = self.res_entry::<Res>() else { let mut default_res = Res::__resource_marker_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).__resource_marker_clone(), - }; - let r = f(&mut new_res); - *arc_res = Arc::new(new_res); - r - } else { + let Ok(mut guard) = entry.lock() else { let mut default_res = Res::__resource_marker_default(); - f(&mut default_res) - } + return f(&mut default_res); + }; + let mut new_res = match Arc::try_unwrap(std::mem::take(&mut *guard)) { + Ok(val) => val, + Err(arc) => (*arc).__resource_marker_clone(), + }; + let r = f(&mut new_res); + *guard = Arc::new(new_res); + r } /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly. /// - /// Extracts a mutable resource from the global store (clone-out), returning an + /// Extracts a mutable resource from the store (clone-out), returning an /// owned value. The caller must call [`__store_res`] to write back modifications. #[doc(hidden)] #[must_use] pub fn __extract_res_mut<Res: 'static + Default + ResourceMarker + Send + Sync>(&self) -> Res { - let Ok(mut guard) = self.resources.lock() else { + let Some(entry) = self.res_entry::<Res>() else { return Res::__resource_marker_default(); }; - guard - .get_mut(&TypeId::of::<Res>()) - .and_then(|a| a.downcast_mut::<Arc<Res>>()) - .map_or_else( - Res::__resource_marker_default, - |arc_res| match Arc::try_unwrap(std::mem::take(arc_res)) { - Ok(val) => val, - Err(arc) => (*arc).__resource_marker_clone(), - }, - ) + let Ok(mut guard) = entry.lock() else { + return Res::__resource_marker_default(); + }; + match Arc::try_unwrap(std::mem::take(&mut *guard)) { + Ok(val) => val, + Err(arc) => (*arc).__resource_marker_clone(), + } } /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly. /// - /// Stores a modified resource value back into the global store. + /// Stores a modified resource value back into the store. #[doc(hidden)] pub fn __store_res<Res: 'static + Send + Sync + ResourceMarker>(&self, val: Res) { - if let Ok(mut guard) = self.resources.lock() { - guard.insert(TypeId::of::<Res>(), Box::new(Arc::new(val))); + let Ok(mut guard) = self.map.lock() else { + return; + }; + let Some(boxed_any) = guard.get_mut(&TypeId::of::<Res>()) else { + guard.insert( + TypeId::of::<Res>(), + Box::new(Arc::new(Mutex::new(Arc::new(val)))), + ); + return; + }; + if let Some(entry) = boxed_any.downcast_mut::<Arc<Mutex<Arc<Res>>>>() + && let Ok(mut entry_guard) = entry.lock() + { + *entry_guard = Arc::new(val); + return; } + // The entry exists but cannot be updated (type mismatch or poisoned + // lock): replace it wholesale. + guard.insert( + TypeId::of::<Res>(), + Box::new(Arc::new(Mutex::new(Arc::new(val)))), + ); } /// Get an resources by type, returning `Res` if present #[must_use] pub fn res<Res: 'static + Send + Sync>(&self) -> Option<GlobalResource<Res>> { - let guard = self.resources.lock().ok()?; - let boxed_any = guard.get(&TypeId::of::<Res>())?; - let arc_res = boxed_any.as_ref().downcast_ref::<Arc<Res>>()?; - let result = GlobalResource::from(Arc::clone(arc_res)); - drop(guard); - Some(result) + let entry = self.res_entry::<Res>()?; + let guard = entry.lock().ok()?; + Some(GlobalResource::from(Arc::clone(&*guard))) } /// Get a resource by type, returning `GlobalResource<Res>` if present. @@ -130,10 +179,14 @@ where /// # Errors /// /// Returns `Err(route)` when the resource of type `Res` is not present in the store. - pub fn res_or_route<Res: 'static + Send + Sync>( + pub fn res_or_route<Res, C>( &self, route: ChainProcess<C>, - ) -> Result<GlobalResource<Res>, ChainProcess<C>> { + ) -> Result<GlobalResource<Res>, ChainProcess<C>> + where + Res: 'static + Send + Sync, + C: ProgramCollect<Enum = C>, + { self.res().map_or_else(|| Err(route), Ok) } @@ -147,6 +200,93 @@ where } } +impl Default for GlobalResContainer { + fn default() -> Self { + Self::new() + } +} + +impl<C> Program<C> +where + C: ProgramCollect<Enum = C>, +{ + /// Insert a resource of the given type, cloning the provided value into the store + pub fn with_resource<Res: 'static + Send + Sync + ResourceMarker>( + &mut self, + res: Res, + ) -> &mut Self { + self.resources.with_resource(res); + self + } + + /// Modify a resource by type, applying a closure to the resource if present + pub fn modify_res<Res, Return>(&self, f: impl FnOnce(&mut Res) -> Return) -> Return + where + Res: 'static + Default + ResourceMarker + Send + Sync, + Return: Default, + { + self.resources.modify_res(f) + } + + /// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly + #[doc(hidden)] + pub fn __modify_res_and_return_route<Res>( + &self, + f: impl FnOnce(&mut Res) -> ChainProcess<C>, + ) -> ChainProcess<C> + where + Res: 'static + Default + ResourceMarker + Send + Sync, + { + self.resources.__modify_res_and_return_route(f) + } + + /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly. + /// + /// Extracts a mutable resource from the global store (clone-out), returning an + /// owned value. The caller must call [`__store_res`] to write back modifications. + #[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. + /// + /// Stores a modified resource value back into the global store. + #[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 + #[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. + /// + /// If the resource is not present, returns the provided [`ChainProcess`] as an `Err`. + /// + /// # Errors + /// + /// Returns `Err(route)` when the resource of type `Res` is not present in the store. + pub fn res_or_route<Res: 'static + Send + Sync>( + &self, + route: ChainProcess<C>, + ) -> Result<GlobalResource<Res>, ChainProcess<C>> { + self.resources.res_or_route(route) + } + + /// Get a resource by type, returning `GlobalResource<Res>` or inserting a default + #[must_use] + pub fn res_or_default<Res: 'static + Send + Sync + ResourceMarker>( + &self, + ) -> GlobalResource<Res> { + self.resources.res_or_default() + } +} + /// Global assets for storing Program global state information pub struct GlobalResource<ResType: 'static + Send + Sync> { res_arc: Arc<ResType>, @@ -225,6 +365,8 @@ where #[cfg(test)] mod tests { use super::*; + use crate::MockProgramCollect; + use crate::error::ChainProcessError; #[test] fn global_resource_new_and_deref() { @@ -285,8 +427,229 @@ mod tests { ); } - // Note: Tests for Program::with_resource, res(), res_or_route(), res_or_default(), - // and modify_res() require a concrete ProgramCollect implementation, which is - // complex and outside the scope of these unit tests. - // Those are better covered by integration tests. + #[test] + fn container_new_creates_empty_store() { + let container = GlobalResContainer::new(); + assert!(container.res::<i32>().is_none()); + } + + #[test] + fn container_insert_then_res() { + let mut container = GlobalResContainer::new(); + container.with_resource(42i32); + let res = container.res::<i32>(); + assert_eq!(*res.unwrap(), 42); + } + + #[test] + fn container_missing_res_returns_none() { + let container = GlobalResContainer::new(); + assert!(container.res::<String>().is_none()); + } + + #[test] + fn container_res_or_default_creates_default() { + let container = GlobalResContainer::new(); + assert_eq!(*container.res_or_default::<i32>(), 0); + } + + #[test] + fn container_res_or_default_returns_existing() { + let mut container = GlobalResContainer::new(); + container.with_resource(7i32); + assert_eq!(*container.res_or_default::<i32>(), 7); + } + + #[test] + fn container_modify_res_updates_value() { + let mut container = GlobalResContainer::new(); + container.with_resource(1i32); + let doubled = container.modify_res(|v: &mut i32| { + *v *= 2; + *v + }); + assert_eq!(doubled, 2); + assert_eq!(*container.res::<i32>().unwrap(), 2); + } + + #[test] + fn container_modify_res_missing_returns_default() { + let container = GlobalResContainer::new(); + let value: i32 = container.modify_res(|v: &mut i32| *v); + assert_eq!(value, 0); + } + + #[test] + fn container_modify_res_through_shared_reference() { + let mut container = GlobalResContainer::new(); + container.with_resource(10i32); + let shared = &container; + shared.modify_res(|v: &mut i32| *v += 5); + assert_eq!(*container.res::<i32>().unwrap(), 15); + } + + #[test] + fn container_modify_res_clones_when_shared() { + let mut container = GlobalResContainer::new(); + container.with_resource(10i32); + // Hold a shared handle so `Arc::try_unwrap` fails and the resource is cloned out + let handle = container.res::<i32>().unwrap(); + container.modify_res(|v: &mut i32| *v += 5); + assert_eq!(*container.res::<i32>().unwrap(), 15); + assert_eq!(*handle, 10); + } + + #[test] + fn container_extract_res_mut_takes_value_out() { + let mut container = GlobalResContainer::new(); + container.with_resource(3i32); + let extracted: i32 = container.__extract_res_mut(); + assert_eq!(extracted, 3); + // The slot is reset to a default value after the extraction + assert_eq!(*container.res::<i32>().unwrap(), 0); + } + + #[test] + fn container_extract_res_mut_clones_when_shared() { + let mut container = GlobalResContainer::new(); + container.with_resource(3i32); + // Hold a shared handle so `Arc::try_unwrap` fails and the resource is cloned out + let _handle = container.res::<i32>().unwrap(); + let extracted: i32 = container.__extract_res_mut(); + assert_eq!(extracted, 3); + // The slot is reset to a default value after the extraction + assert_eq!(*container.res::<i32>().unwrap(), 0); + } + + #[test] + fn container_store_res_inserts_value() { + let container = GlobalResContainer::new(); + container.__store_res(8i32); + assert_eq!(*container.res::<i32>().unwrap(), 8); + } + + #[test] + fn container_extract_store_roundtrip() { + let mut container = GlobalResContainer::new(); + container.with_resource(7i32); + let value: i32 = container.__extract_res_mut(); + container.__store_res(value + 1); + assert_eq!(*container.res::<i32>().unwrap(), 8); + } + + #[test] + fn container_res_shared_handles_share_the_arc() { + let mut container = GlobalResContainer::new(); + container.with_resource(String::from("hello")); + let handle_a = container.res::<String>().unwrap(); + let handle_b = container.res::<String>().unwrap(); + assert_eq!(*handle_a, "hello"); + assert_eq!(*handle_b, "hello"); + assert!(Arc::ptr_eq(&handle_a.res_arc, &handle_b.res_arc)); + } + + #[test] + fn container_res_or_route_missing_returns_route() { + let container = GlobalResContainer::new(); + let route: ChainProcess<MockProgramCollect> = + ChainProcess::Err(ChainProcessError::Other("missing".into())); + let result = container.res_or_route::<i32, MockProgramCollect>(route); + assert!(result.is_err()); + } + + #[test] + fn container_res_or_route_present_returns_resource() { + let mut container = GlobalResContainer::new(); + container.with_resource(5i32); + let route: ChainProcess<MockProgramCollect> = + ChainProcess::Err(ChainProcessError::Other("missing".into())); + let Ok(resource) = container.res_or_route::<i32, MockProgramCollect>(route) else { + panic!("expected the resource to be present"); + }; + assert_eq!(*resource, 5); + } + + #[test] + fn container_modify_res_and_return_route_works() { + let mut container = GlobalResContainer::new(); + container.with_resource(1i32); + 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); + } + + #[test] + fn container_nested_modify_res_different_types_do_not_deadlock() { + let mut container = GlobalResContainer::new(); + container.with_resource(1i32).with_resource("a".to_string()); + // Two `&mut` injections nest `modify_res` calls; each resource has its + // own mutex, so the inner call must not deadlock against the outer one. + container.modify_res(|count: &mut i32| { + *count += 10; + container.modify_res(|text: &mut String| { + text.push('b'); + }); + }); + assert_eq!(*container.res::<i32>().unwrap(), 11); + assert_eq!(*container.res::<String>().unwrap(), "ab"); + } + + #[test] + fn container_nested_modify_res_and_return_route_no_deadlock() { + let mut container = GlobalResContainer::new(); + container.with_resource(1i32).with_resource("a".to_string()); + let route: ChainProcess<MockProgramCollect> = + container.__modify_res_and_return_route(|count: &mut i32| { + *count += 10; + container.__modify_res_and_return_route(|text: &mut String| { + text.push('b'); + ChainProcess::Err(ChainProcessError::Other("done".into())) + }) + }); + assert!(matches!(route, ChainProcess::Err(_))); + assert_eq!(*container.res::<i32>().unwrap(), 11); + assert_eq!(*container.res::<String>().unwrap(), "ab"); + } + + #[test] + fn container_res_inside_modify_of_another_resource() { + let mut container = GlobalResContainer::new(); + container.with_resource(1i32).with_resource("a".to_string()); + container.modify_res(|count: &mut i32| { + // Reading a different resource while holding this one's lock must + // not deadlock either. + assert_eq!(*container.res::<String>().unwrap(), "a"); + *count += 10; + }); + assert_eq!(*container.res::<i32>().unwrap(), 11); + } + + #[test] + fn container_multiple_instances_are_independent() { + let mut first = GlobalResContainer::new(); + let mut second = GlobalResContainer::new(); + first.with_resource(1i32); + second.with_resource(2i32); + first.modify_res(|v: &mut i32| *v += 10); + assert_eq!(*first.res::<i32>().unwrap(), 11); + assert_eq!(*second.res::<i32>().unwrap(), 2); + // A resource present in one container is invisible to the other + assert!(first.res::<String>().is_none()); + assert!(second.res::<String>().is_none()); + } + + #[test] + fn program_resource_methods_delegate_to_container() { + let mut program = crate::Program::<MockProgramCollect>::new_with_args(Vec::<String>::new()); + program.with_resource(1i32); + assert_eq!(*program.res::<i32>().unwrap(), 1); + program.modify_res(|v: &mut i32| *v += 1); + assert_eq!(*program.res::<i32>().unwrap(), 2); + assert_eq!(*program.res_or_default::<i32>(), 2); + assert_eq!(*program.res_or_default::<String>(), ""); + } } diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 3c43cee..be8d85c 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -2,13 +2,9 @@ use std::env; use crate::{ - AnyOutput, GlobalResources, asset::dispatcher::Dispatcher, error::ChainProcessError, + AnyOutput, GlobalResContainer, asset::dispatcher::Dispatcher, error::ChainProcessError, hook::ProgramHook, }; -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; #[doc(hidden)] pub mod error; @@ -81,7 +77,7 @@ where pub(crate) hooks: Vec<ProgramHook<C>>, - pub(crate) resources: GlobalResources, + pub(crate) resources: GlobalResContainer, } impl<C> Program<C> @@ -124,7 +120,7 @@ where hooks: Vec::new(), - resources: Arc::new(Mutex::new(HashMap::new())), + resources: GlobalResContainer::new(), } } |
