use std::{
cell::{Cell, RefCell},
time::Duration,
};
use cranpose_core::NodeId;
use crate::render_state::{current_app_context, require_current_app_context};
#[derive(Default)]
pub(crate) struct LazyPrefetchState {
requests: RefCell<Vec<NodeId>>,
idle_pass: Cell<bool>,
item_cost_nanos: Cell<u64>,
prefetched: RefCell<Vec<NodeId>>,
}
impl LazyPrefetchState {
pub(crate) fn new() -> Self {
Self::default()
}
}
fn with_state<R>(f: impl FnOnce(&LazyPrefetchState) -> R) -> R {
let context = require_current_app_context("lazy prefetch access");
f(context.lazy_prefetch())
}
pub(crate) fn request_lazy_prefetch(node_id: NodeId) {
with_state(|state| {
let mut requests = state.requests.borrow_mut();
if !requests.contains(&node_id) {
requests.push(node_id);
}
});
}
pub(crate) fn in_lazy_prefetch_pass() -> bool {
current_app_context().is_some_and(|context| context.lazy_prefetch().idle_pass.get())
}
pub(crate) fn record_lazy_item_cost(cost: Duration) {
let sample = u64::try_from(cost.as_nanos()).unwrap_or(u64::MAX);
with_state(|state| {
let previous = state.item_cost_nanos.get();
let blended = if previous == 0 {
sample
} else {
previous.saturating_mul(3).saturating_add(sample) / 4
};
state.item_cost_nanos.set(blended);
});
}
pub(crate) fn note_prefetched_item(nodes: &[u64]) {
with_state(|state| {
state
.prefetched
.borrow_mut()
.extend(nodes.iter().filter_map(|&node| NodeId::try_from(node).ok()));
});
}
pub fn warm_prefetched_slices(applier: &mut cranpose_core::MemoryApplier) {
let mut stack = with_state(|state| std::mem::take(&mut *state.prefetched.borrow_mut()));
while let Some(id) = stack.pop() {
let layout = applier.with_node::<crate::widgets::nodes::LayoutNode, _>(id, |node| {
let _ = node.modifier_slices_snapshot();
stack.extend_from_slice(&node.children);
});
if layout.is_err() {
let _ = applier.with_node::<crate::subcompose_layout::SubcomposeLayoutNode, _>(
id,
|node| {
let _ = node.modifier_slices_snapshot();
node.with_active_children(|children| stack.extend_from_slice(children));
},
);
}
}
with_state(|state| {
let mut prefetched = state.prefetched.borrow_mut();
if prefetched.is_empty() {
*prefetched = stack;
}
});
}
pub fn drain_lazy_prefetch_requests(schedule: impl FnMut(NodeId)) {
with_state(|state| state.requests.borrow_mut().drain(..).for_each(schedule));
}
pub fn has_lazy_prefetch_requests() -> bool {
with_state(|state| !state.requests.borrow().is_empty())
}
pub fn lazy_prefetch_item_cost() -> Duration {
with_state(|state| Duration::from_nanos(state.item_cost_nanos.get()))
}
pub fn with_lazy_prefetch_pass<R>(pass: impl FnOnce() -> R) -> R {
struct Reset;
impl Drop for Reset {
fn drop(&mut self) {
with_state(|state| state.idle_pass.set(false));
}
}
with_state(|state| state.idle_pass.set(true));
let _reset = Reset;
pass()
}
#[cfg(test)]
#[path = "tests/lazy_prefetch_tests.rs"]
mod tests;