aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core/src')
-rw-r--r--mingling_core/src/asset/lazy_resource.rs165
1 files changed, 145 insertions, 20 deletions
diff --git a/mingling_core/src/asset/lazy_resource.rs b/mingling_core/src/asset/lazy_resource.rs
index 356e348..8cf8d89 100644
--- a/mingling_core/src/asset/lazy_resource.rs
+++ b/mingling_core/src/asset/lazy_resource.rs
@@ -3,9 +3,12 @@ use crate::{ProgramCollect, ResourceMarker, this};
enum LazyInner<T> {
/// Not yet initialized — holds the factory function.
/// After init, the factory is **dropped**, no wasted memory.
- Uninit(Box<dyn FnMut() -> T + Send + Sync>),
+ Uninit(
+ Box<dyn FnMut() -> T + Send + Sync>,
+ Option<Box<dyn FnOnce(T) + Send + Sync>>,
+ ),
/// Initialized and ready to go.
- Init(T),
+ Init(T, Option<Box<dyn FnOnce(T) + Send + Sync>>),
}
/// A lazily initialized resource that only creates its value on first access.
@@ -25,25 +28,80 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
#[must_use]
pub fn new(f: impl FnMut() -> T + Send + Sync + 'static) -> Self {
Self {
- inner: LazyInner::Uninit(Box::new(f)),
+ inner: LazyInner::Uninit(Box::new(f), None),
}
}
+ /// 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.
+ #[must_use]
+ pub fn new_with_drop(
+ f: impl FnMut() -> T + Send + Sync + 'static,
+ on_drop: impl FnOnce(T) + Send + Sync + 'static,
+ ) -> Self {
+ Self {
+ inner: LazyInner::Uninit(Box::new(f), Some(Box::new(on_drop))),
+ }
+ }
+
+ /// Chains an on-drop callback onto the lazy resource, returning ownership.
+ ///
+ /// This method consumes `self` and returns it with the callback attached,
+ /// allowing a builder‑style pattern.
+ ///
+ /// 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.
+ pub fn with_on_drop(mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) -> Self {
+ match &mut self.inner {
+ LazyInner::Uninit(_, existing_opt) => {
+ *existing_opt = Some(Box::new(on_drop));
+ }
+ LazyInner::Init(_, existing_opt) => {
+ *existing_opt = Some(Box::new(on_drop));
+ }
+ }
+ 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.
+ pub fn set_on_drop(&mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) {
+ match &mut self.inner {
+ LazyInner::Uninit(_, existing_opt) => {
+ *existing_opt = Some(Box::new(on_drop));
+ }
+ LazyInner::Init(_, existing_opt) => {
+ *existing_opt = Some(Box::new(on_drop));
+ }
+ }
+ }
+
+ /// Returns `true` if the resource has been initialized.
+ pub fn is_initialized(&self) -> bool {
+ matches!(&self.inner, LazyInner::Init(_, _))
+ }
+
fn force_init(&mut self) -> &mut LazyInner<T> {
- if matches!(&self.inner, LazyInner::Uninit(_)) {
+ if matches!(&self.inner, LazyInner::Uninit(_, _)) {
// Replace with a temporary poison value so we can move the real factory out.
// SAFETY: if the factory panics, the poison value's `unreachable!()` will
// catch any subsequent access.
- let poisoned = LazyInner::Uninit(Box::new(|| {
- unreachable!("LazyRes poisoned during initialization")
- }));
+ let poisoned = LazyInner::Uninit(
+ Box::new(|| unreachable!("LazyRes poisoned during initialization")),
+ None,
+ );
let old = std::mem::replace(&mut self.inner, poisoned);
match old {
- LazyInner::Uninit(mut f) => {
- self.inner = LazyInner::Init((f)());
+ LazyInner::Uninit(mut f, on_drop) => {
+ self.inner = LazyInner::Init((f)(), on_drop);
}
// Already initialized — put it back
- init @ LazyInner::Init(_) => {
+ init @ LazyInner::Init(_, _) => {
self.inner = init;
}
}
@@ -56,7 +114,7 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
pub fn get_ref(&mut self) -> &T {
self.force_init();
match &self.inner {
- LazyInner::Init(t) => t,
+ LazyInner::Init(t, _) => t,
_ => unreachable!(),
}
}
@@ -66,7 +124,7 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
pub fn get_mut(&mut self) -> &mut T {
self.force_init();
match &mut self.inner {
- LazyInner::Init(t) => t,
+ LazyInner::Init(t, _) => t,
_ => unreachable!(),
}
}
@@ -83,10 +141,77 @@ impl<T: Send + Sync + 'static> LazyRes<T> {
///
/// Unlike `reset()`, this **drops** the lazy wrapper entirely.
/// If you need to re-initialize, just construct a new `LazyRes::new(f)`.
- pub fn into_inner(self) -> Option<T> {
- match self.inner {
- LazyInner::Init(t) => Some(t),
- LazyInner::Uninit(_) => 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(
+ &mut self.inner,
+ LazyInner::Uninit(Box::new(|| unreachable!()), None),
+ );
+ match inner {
+ LazyInner::Init(t, _) => Some(t),
+ LazyInner::Uninit(_, _) => None,
+ }
+ }
+
+ /// Consumes the lazy resource and returns the inner value.
+ ///
+ /// If the resource has not been initialized, the initializer is called first.
+ /// This is different from `into_inner()` which returns `None` if uninitialized.
+ pub fn unwrap(mut self) -> T {
+ match std::mem::replace(
+ &mut self.inner,
+ LazyInner::Uninit(Box::new(|| unreachable!()), None),
+ ) {
+ LazyInner::Init(t, _) => t,
+ LazyInner::Uninit(_, _) => {
+ panic!("called `LazyRes::unwrap()` on an uninitialized value")
+ }
+ }
+ }
+
+ /// Consumes the lazy resource, calling the initializer first if not yet initialized.
+ pub fn unwrap_or(mut self, default: T) -> T {
+ if matches!(&self.inner, LazyInner::Uninit(_, _)) {
+ default
+ } else {
+ match std::mem::replace(
+ &mut self.inner,
+ LazyInner::Uninit(Box::new(|| unreachable!()), None),
+ ) {
+ LazyInner::Init(t, _) => t,
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ /// Consumes the lazy resource, calling the initializer first if not yet initialized.
+ ///
+ /// If the resource has not been initialized, `T::default()` is used as the fallback.
+ pub fn unwrap_or_default(self) -> T
+ where
+ T: Default,
+ {
+ self.unwrap_or(T::default())
+ }
+}
+
+impl<T: Send + Sync + 'static> Drop for LazyRes<T> {
+ fn drop(&mut self) {
+ // Take ownership of the inner value and call the on-drop callback if present.
+ let poisoned = LazyInner::Uninit(
+ Box::new(|| unreachable!("LazyRes poisoned during drop")),
+ None,
+ );
+ let old = std::mem::replace(&mut self.inner, poisoned);
+ match old {
+ LazyInner::Init(val, Some(callback)) => {
+ callback(val);
+ }
+ LazyInner::Uninit(_, Some(_callback)) => {
+ // Resource was never initialized but a drop callback was set.
+ // Nothing to pass to the callback — just drop it.
+ }
+ _ => {}
}
}
}
@@ -105,7 +230,7 @@ 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),
+ inner: LazyInner::Init(value, None),
}
}
}
@@ -157,10 +282,10 @@ where
/// but the initializer is reset to `T::default()`.
fn res_clone(&self) -> Self {
match &self.inner {
- LazyInner::Init(t) => Self {
- inner: LazyInner::Init(t.clone()),
+ LazyInner::Init(t, _) => Self {
+ inner: LazyInner::Init(t.clone(), None),
},
- LazyInner::Uninit(_) => Self::default(),
+ LazyInner::Uninit(_, _) => Self::default(),
}
}