use crate::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
enum ReuseBindingKey {
Global(CowArc<str>),
LocalNumber(usize),
LocalString(CowArc<str>),
}
mod sealed {
pub trait Sealed {}
impl Sealed for usize {}
impl Sealed for &str {}
impl Sealed for String {}
impl Sealed for crate::prelude::CowArc<str> {}
}
#[doc(hidden)]
pub trait ReuseLocalKey: sealed::Sealed {
#[doc(hidden)]
fn into_local_reuse_key(self) -> Result<usize, CowArc<str>>;
}
impl ReuseLocalKey for usize {
fn into_local_reuse_key(self) -> Result<usize, CowArc<str>> { Ok(self) }
}
impl ReuseLocalKey for &str {
fn into_local_reuse_key(self) -> Result<usize, CowArc<str>> { Err(self.to_owned().into()) }
}
impl ReuseLocalKey for String {
fn into_local_reuse_key(self) -> Result<usize, CowArc<str>> { Err(self.into()) }
}
impl ReuseLocalKey for CowArc<str> {
fn into_local_reuse_key(self) -> Result<usize, CowArc<str>> { Err(self) }
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ReuseKey {
binding: ReuseBindingKey,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReuseLeaveResult {
NotFound,
LiveLeft,
CachedLeft,
}
#[derive(Clone)]
pub struct Reuse {
pub reuse: ReuseKey,
}
impl ReuseKey {
pub fn global(key: impl Into<CowArc<str>>) -> Self {
Self { binding: ReuseBindingKey::Global(key.into()) }
}
pub fn local(key: impl ReuseLocalKey) -> Self {
let binding = match key.into_local_reuse_key() {
Ok(key) => ReuseBindingKey::LocalNumber(key),
Err(key) => ReuseBindingKey::LocalString(key),
};
Self { binding }
}
pub fn is_local(&self) -> bool {
matches!(self.binding, ReuseBindingKey::LocalNumber(_) | ReuseBindingKey::LocalString(_))
}
pub fn is_global(&self) -> bool { matches!(self.binding, ReuseBindingKey::Global(_)) }
pub fn resolve(&self) -> Reuse { Reuse { reuse: self.clone() } }
pub fn leave(&self, ctx: &impl AsRef<ProviderCtx>) -> ReuseLeaveResult {
ReuseScope::leave(ctx, self)
}
pub(crate) fn is_bound_locally(&self) -> bool { self.is_local() }
}
impl Declare for Reuse {
type Builder = FatObj<()>;
#[inline]
fn declarer() -> Self::Builder { FatObj::new(()) }
}
impl<'a> ComposeChild<'a> for Reuse {
type Child = Widget<'a>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'a> {
let this = this
.try_into_value()
.unwrap_or_else(|_| panic!("Reuse should be a stateless widget"));
fn_widget! {
let (scope, w) = ReuseScope::resolve_or_build(BuildCtx::get(), &this.reuse, child);
wrap_reuse_lifecycle(this.reuse.clone(), scope, w, this.reuse.is_local())
}
.into_widget()
}
}
impl Compose for Reuse {
fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
let this = this
.try_into_value()
.unwrap_or_else(|_| panic!("Reuse should be a stateless widget"));
fn_widget! {
let (scope, w) = ReuseScope::resolve(BuildCtx::get(), &this.reuse)
.expect("{this.reuse:?} is not find");
wrap_reuse_lifecycle(this.reuse.clone(), scope, w, this.reuse.is_local())
}
.into_widget()
}
}
fn wrap_reuse_lifecycle<'a>(
key: ReuseKey, scope: ReuseScope, w: Widget<'a>, prune_when_detached: bool,
) -> Widget<'a> {
let mut w = FatObj::new(w);
if prune_when_detached {
let scope = scope.clone();
let key = key.clone();
w.on_disposing(move |_| {
AppCtx::spawn_local(async move {
scope.prune_detached(&key);
});
});
}
w.on_disposed(move |_| {
AppCtx::spawn_local(async move {
scope.finalize_disposed(&key);
});
});
w.into_widget()
}
#[cfg(test)]
mod tests {
use std::{cell::RefCell, rc::Rc};
use super::*;
use crate::test_helper::*;
#[test]
fn local_unused_instance_is_removed_after_recycle() {
reset_test_env!();
let (item_cnt, item_w) = split_value(1);
let local_scope = Rc::new(RefCell::new(None));
let local_scope2 = local_scope.clone();
let wnd = TestWindow::from_widget(fn_widget! {
@ReuseScope {
on_mounted: {
let local_scope2 = local_scope2.clone();
move |e| {
*local_scope2.borrow_mut() = Some(Provider::of::<ReuseScope>(e).unwrap().clone());
}
},
@MockMulti {
@pipe! {
(0..*$read(item_cnt)).map(move |i| {
@Reuse {
reuse: ReuseKey::local(i),
@ { Void::default() }
}
})
}
}
}
});
wnd.draw_frame();
let local_scope = local_scope.borrow_mut().take().unwrap();
assert_eq!(local_scope.binding_count(), 1);
*item_w.write() = 4;
wnd.draw_frame();
assert_eq!(local_scope.binding_count(), 4);
*item_w.write() = 2;
wnd.draw_frame();
wnd.draw_frame();
assert_eq!(local_scope.binding_count(), 2);
}
}