use crate::prelude::*;
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
pub enum CachePolicy {
#[default]
ImmediateRelease,
ManualControl,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ReuseId {
Global(GlobalId),
Local(LocalId, CachePolicy),
}
#[derive(PartialEq, Hash, Eq, Clone, Debug)]
pub enum LocalId {
Number(usize),
String(CowArc<str>),
}
#[derive(PartialEq, Hash, Eq, Clone, Debug)]
pub struct GlobalId(CowArc<str>);
#[derive(Clone)]
pub struct Reuse {
pub reuse_id: ReuseId,
}
impl GlobalId {
pub fn new(key: impl Into<CowArc<str>>) -> Self { GlobalId(key.into()) }
}
impl LocalId {
pub fn string(key: impl Into<CowArc<str>>) -> Self { LocalId::String(key.into()) }
pub fn number(key: usize) -> Self { LocalId::Number(key) }
pub fn with_policy(self, policy: CachePolicy) -> ReuseId { ReuseId::Local(self, policy) }
}
impl From<LocalId> for ReuseId {
fn from(value: LocalId) -> Self { ReuseId::Local(value, CachePolicy::default()) }
}
impl From<GlobalId> for ReuseId {
fn from(value: GlobalId) -> Self { ReuseId::Global(value) }
}
impl<T: Into<CowArc<str>>> From<T> for GlobalId {
fn from(value: T) -> Self { GlobalId(value.into()) }
}
impl ReuseId {
pub fn is_local(&self) -> bool { matches!(self, ReuseId::Local(..)) }
pub fn is_global(&self) -> bool { matches!(self, ReuseId::Global(..)) }
}
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 = match this.try_into_value() {
Ok(v) => v,
Err(_) => {
panic!("Reuse should be a stateless widget");
}
};
fn_widget! {
match this.reuse_id {
ReuseId::Global(key) => {
let p = Provider::writer_of::<GlobalWidgets>(BuildCtx::get()).unwrap();
get_or_insert(&p, &key, child).expect("{this.reuse_id:?} is not find")
},
ReuseId::Local(key, policy) => {
let p = Provider::writer_of::<LocalWidgets>(BuildCtx::get()).unwrap();
let w = get_or_insert(&p, &key, child).expect("{this.reuse_id:?} is not find");
if policy == CachePolicy::ImmediateRelease {
wrap_dispose_recycled(&key, &p, w)
} else {
w
}
},
}
}
.into_widget()
}
}
impl Compose for Reuse {
fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
let this = match this.try_into_value() {
Ok(v) => v,
Err(_) => {
panic!("Reuse should be a stateless widget");
}
};
fn_widget! {
match this.reuse_id {
ReuseId::Global(key) => {
let p = Provider::of::<GlobalWidgets>(BuildCtx::get())
.unwrap();
p.get(&key).expect("{this.reuse_id:?} is not find")
},
ReuseId::Local(key, policy) => {
let p = Provider::writer_of::<LocalWidgets>(BuildCtx::get()).unwrap();
let w = $read(p).get(&key).expect("{this.reuse_id:?} is not find");
if policy == CachePolicy::ImmediateRelease {
wrap_dispose_recycled(&key, &p, w)
} else {
w
}
},
}
}
.into_widget()
}
}
fn wrap_dispose_recycled<'a>(
id: &LocalId, scope: &impl StateWriter<Value = LocalWidgets>, w: Widget<'a>,
) -> Widget<'a> {
let mut w = FatObj::new(w);
let p = scope.clone_writer();
let key = id.clone();
w.on_disposed(move |_| {
AppCtx::spawn_local(async move {
if !p.read().is_in_used(&key) {
p.write().remove(&key);
}
});
});
w.into_widget()
}