use std::cell::RefCell;
use crate::graph::{LayerNode, PrimitiveEntry, PrimitiveNode, RenderNode, TextPrimitiveNode};
#[derive(Default)]
#[expect(
clippy::vec_box,
reason = "the boxes are the allocations the pool keeps for reuse"
)]
struct Pool {
layers: Vec<Box<LayerNode>>,
texts: Vec<Box<TextPrimitiveNode>>,
lists: Vec<Vec<RenderNode>>,
}
thread_local! {
static POOL: RefCell<Pool> = RefCell::new(Pool::default());
}
impl Pool {
fn recycle_layer(&mut self, mut layer: Box<LayerNode>) {
self.recycle_children(&mut layer);
layer.hit_test = None;
self.layers.push(layer);
}
fn recycle_children(&mut self, layer: &mut LayerNode) {
while let Some(child) = layer.children.pop() {
match child {
RenderNode::Layer(layer) => self.recycle_layer(layer),
RenderNode::Primitive(PrimitiveEntry {
node: PrimitiveNode::Text(text),
..
}) => self.texts.push(text),
RenderNode::Primitive(_) | RenderNode::DrawRun(_) => {}
}
}
}
}
pub(crate) fn recycle_children(layer: &mut LayerNode) {
POOL.with(|pool| pool.borrow_mut().recycle_children(layer));
}
pub(crate) fn recycle_primitives(layer: &mut LayerNode) {
let children = &mut layer.children;
let is_layer = |child: &RenderNode| matches!(child, RenderNode::Layer(_));
let first_layer = children.iter().position(is_layer).unwrap_or(children.len());
let end = children
.iter()
.rposition(is_layer)
.map_or(first_layer, |last| last + 1);
debug_assert!(
children[first_layer..end].iter().all(is_layer),
"a layer's own primitives never sit between its child layers"
);
POOL.with(|pool| {
let texts = &mut pool.borrow_mut().texts;
let mut keep_text = |child| {
if let RenderNode::Primitive(PrimitiveEntry {
node: PrimitiveNode::Text(text),
..
}) = child
{
texts.push(text);
}
};
children.drain(end..).for_each(&mut keep_text);
children.drain(..first_layer).for_each(keep_text);
});
}
pub(crate) fn recycle(layer: Box<LayerNode>) {
POOL.with(|pool| pool.borrow_mut().recycle_layer(layer));
}
pub(crate) fn recycle_list(list: Vec<RenderNode>) {
debug_assert!(list.is_empty(), "only an emptied list is recycled");
if list.capacity() > 0 {
POOL.with(|pool| pool.borrow_mut().lists.push(list));
}
}
pub(crate) fn child_list(capacity: usize) -> Vec<RenderNode> {
let mut list = POOL
.with(|pool| pool.borrow_mut().lists.pop())
.unwrap_or_default();
reserve_nodes(&mut list, capacity);
list
}
pub(crate) fn reserve_nodes(list: &mut Vec<RenderNode>, additional: usize) {
if list.capacity() == 0 {
list.reserve_exact(additional);
} else {
list.reserve(additional);
}
}
pub(crate) fn layer_box() -> Box<LayerNode> {
POOL.with(|pool| pool.borrow_mut().layers.pop())
.unwrap_or_default()
}
pub(crate) fn boxed_text(text: TextPrimitiveNode) -> Box<TextPrimitiveNode> {
match POOL.with(|pool| pool.borrow_mut().texts.pop()) {
Some(mut recycled) => {
*recycled = text;
recycled
}
None => Box::new(text),
}
}
pub(crate) fn release() {
POOL.with(|pool| {
let mut pool = pool.borrow_mut();
pool.layers.clear();
pool.texts.clear();
pool.lists.clear();
});
}