use crate::collections::map::HashMap;
use crate::collections::map::HashSet;
use std::collections::VecDeque;
use std::fmt;
use std::rc::Rc;
use crate::{CallbackHolder, NodeId, RecomposeScope, SlotTable, SlotsHost};
pub type DebugSlotGroup = (usize, crate::Key, Option<usize>, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SlotId(pub u64);
impl SlotId {
#[inline]
pub fn new(raw: u64) -> Self {
Self(raw)
}
#[inline]
pub fn raw(self) -> u64 {
self.0
}
}
pub trait SlotReusePolicy: 'static {
fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId>;
fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool;
fn register_content_type(&self, _slot_id: SlotId, _content_type: u64) {
}
fn remove_content_type(&self, _slot_id: SlotId) {
}
fn prune_slots(&self, _keep_slots: &HashSet<SlotId>) {
}
}
#[derive(Debug, Default)]
pub struct DefaultSlotReusePolicy;
impl SlotReusePolicy for DefaultSlotReusePolicy {
fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
let _ = active;
HashSet::default()
}
fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
existing == requested
}
}
pub struct ContentTypeReusePolicy {
slot_types: std::cell::RefCell<HashMap<SlotId, u64>>,
}
impl std::fmt::Debug for ContentTypeReusePolicy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let types = self.slot_types.borrow();
f.debug_struct("ContentTypeReusePolicy")
.field("slot_types", &*types)
.finish()
}
}
impl Default for ContentTypeReusePolicy {
fn default() -> Self {
Self::new()
}
}
impl ContentTypeReusePolicy {
pub fn new() -> Self {
Self {
slot_types: std::cell::RefCell::new(HashMap::default()),
}
}
pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
self.slot_types.borrow_mut().insert(slot, content_type);
}
pub fn remove_content_type(&self, slot: SlotId) {
self.slot_types.borrow_mut().remove(&slot);
}
pub fn clear(&self) {
self.slot_types.borrow_mut().clear();
}
pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
self.slot_types.borrow().get(&slot).copied()
}
}
impl SlotReusePolicy for ContentTypeReusePolicy {
fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
let _ = active;
HashSet::default()
}
fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
if existing == requested {
return true;
}
let types = self.slot_types.borrow();
match (types.get(&existing), types.get(&requested)) {
(Some(existing_type), Some(requested_type)) => existing_type == requested_type,
_ => false,
}
}
fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
self.set_content_type(slot_id, content_type);
}
fn remove_content_type(&self, slot_id: SlotId) {
ContentTypeReusePolicy::remove_content_type(self, slot_id);
}
fn prune_slots(&self, keep_slots: &HashSet<SlotId>) {
self.slot_types
.borrow_mut()
.retain(|slot, _| keep_slots.contains(slot));
}
}
#[derive(Default, Clone)]
struct NodeSlotMapping {
slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
node_to_slot: HashMap<NodeId, SlotId>,
slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
}
impl fmt::Debug for NodeSlotMapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NodeSlotMapping")
.field("slot_to_nodes", &self.slot_to_nodes)
.field("node_to_slot", &self.node_to_slot)
.finish()
}
}
impl NodeSlotMapping {
fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
self.slot_to_nodes.insert(slot, nodes.to_vec());
for node in nodes {
self.node_to_slot.insert(*node, slot);
}
}
fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
self.slot_to_scopes.insert(slot, scopes.to_vec());
}
fn add_node(&mut self, slot: SlotId, node: NodeId) {
self.slot_to_nodes.entry(slot).or_default().push(node);
self.node_to_slot.insert(node, slot);
}
fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
if let Some(slot) = self.node_to_slot.remove(node) {
if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
nodes.remove(index);
}
if nodes.is_empty() {
self.slot_to_nodes.remove(&slot);
self.slot_to_scopes.remove(&slot);
}
}
Some(slot)
} else {
None
}
}
fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
self.slot_to_nodes.get(slot).map(|nodes| nodes.as_slice())
}
fn get_slot(&self, node: &NodeId) -> Option<SlotId> {
self.node_to_slot.get(node).copied()
}
fn deactivate_slot(&self, slot: SlotId) {
if let Some(scopes) = self.slot_to_scopes.get(&slot) {
for scope in scopes {
scope.deactivate();
}
}
}
fn invalidate_scopes(&self) {
for scopes in self.slot_to_scopes.values() {
for scope in scopes {
scope.invalidate();
}
}
}
fn retain_slots(&mut self, active: &HashSet<SlotId>) -> Vec<NodeId> {
let mut removed_nodes = Vec::new();
self.slot_to_nodes.retain(|slot, nodes| {
if active.contains(slot) {
true
} else {
removed_nodes.extend(nodes.iter().copied());
false
}
});
self.slot_to_scopes.retain(|slot, _| active.contains(slot));
for node in &removed_nodes {
self.node_to_slot.remove(node);
}
removed_nodes
}
}
pub struct SubcomposeState {
mapping: NodeSlotMapping,
active_order: Vec<SlotId>,
reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
slot_content_types: HashMap<SlotId, u64>,
precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
policy: Box<dyn SlotReusePolicy>,
pub(crate) current_index: usize,
pub(crate) reusable_count: usize,
pub(crate) precomposed_count: usize,
slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
slot_callbacks: HashMap<SlotId, CallbackHolder>,
max_reusable_per_type: usize,
max_reusable_untyped: usize,
last_slot_reused: Option<bool>,
}
impl fmt::Debug for SubcomposeState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubcomposeState")
.field("mapping", &self.mapping)
.field("active_order", &self.active_order)
.field("reusable_by_type_count", &self.reusable_by_type.len())
.field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
.field("precomposed_nodes", &self.precomposed_nodes)
.field("current_index", &self.current_index)
.field("reusable_count", &self.reusable_count)
.field("precomposed_count", &self.precomposed_count)
.field("slot_compositions_count", &self.slot_compositions.len())
.finish()
}
}
impl Default for SubcomposeState {
fn default() -> Self {
Self::new(Box::new(DefaultSlotReusePolicy))
}
}
const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
impl SubcomposeState {
pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
Self {
mapping: NodeSlotMapping::default(),
active_order: Vec::new(),
reusable_by_type: HashMap::default(),
reusable_nodes_untyped: VecDeque::new(),
slot_content_types: HashMap::default(),
precomposed_nodes: HashMap::default(),
policy,
current_index: 0,
reusable_count: 0,
precomposed_count: 0,
slot_compositions: HashMap::default(),
slot_callbacks: HashMap::default(),
max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
last_slot_reused: None,
}
}
pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
self.policy = policy;
}
pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
self.slot_content_types.insert(slot_id, content_type);
self.policy.register_content_type(slot_id, content_type);
}
pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
match content_type {
Some(ct) => self.register_content_type(slot_id, ct),
None => {
self.slot_content_types.remove(&slot_id);
self.policy.remove_content_type(slot_id);
}
}
}
pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
self.slot_content_types.get(&slot_id).copied()
}
pub fn begin_pass(&mut self) {
self.current_index = 0;
}
pub fn finish_pass(&mut self) -> Vec<NodeId> {
let disposed = self.dispose_or_reuse_starting_from_index(self.current_index);
self.prune_inactive_slots();
disposed
}
pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
Rc::clone(
self.slot_compositions
.entry(slot_id)
.or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
)
}
pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
self.slot_callbacks.entry(slot_id).or_default().clone()
}
pub fn register_active(
&mut self,
slot_id: SlotId,
node_ids: &[NodeId],
scopes: &[RecomposeScope],
) {
let was_reused =
self.mapping.get_nodes(&slot_id).is_some() || self.active_order.contains(&slot_id);
self.last_slot_reused = Some(was_reused);
if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
if position < self.current_index {
for scope in scopes {
scope.reactivate();
}
self.mapping.set_nodes(slot_id, node_ids);
self.mapping.set_scopes(slot_id, scopes);
if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
let before_len = nodes.len();
nodes.retain(|node| !node_ids.contains(node));
let removed = before_len - nodes.len();
self.precomposed_count = self.precomposed_count.saturating_sub(removed);
if nodes.is_empty() {
self.precomposed_nodes.remove(&slot_id);
}
}
return;
}
self.active_order.remove(position);
}
for scope in scopes {
scope.reactivate();
}
self.mapping.set_nodes(slot_id, node_ids);
self.mapping.set_scopes(slot_id, scopes);
if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
let before_len = nodes.len();
nodes.retain(|node| !node_ids.contains(node));
let removed = before_len - nodes.len();
self.precomposed_count = self.precomposed_count.saturating_sub(removed);
if nodes.is_empty() {
self.precomposed_nodes.remove(&slot_id);
}
}
let insert_at = self.current_index.min(self.active_order.len());
self.active_order.insert(insert_at, slot_id);
self.current_index += 1;
}
pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
self.precomposed_nodes
.entry(slot_id)
.or_default()
.push(node_id);
self.precomposed_count += 1;
}
pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
let first_node = nodes.first().copied();
if let Some(node_id) = first_node {
let _ = self.remove_from_reusable_pools(node_id);
self.update_reusable_count();
return Some(node_id);
}
}
let content_type = self.slot_content_types.get(&slot_id).copied();
if let Some(ct) = content_type {
if let Some(pool) = self.reusable_by_type.get_mut(&ct) {
if let Some((old_slot, node_id)) = pool.pop_front() {
self.migrate_node_to_slot(node_id, old_slot, slot_id);
self.update_reusable_count();
return Some(node_id);
}
}
}
let position = self
.reusable_nodes_untyped
.iter()
.position(|(existing_slot, _)| self.policy.are_compatible(*existing_slot, slot_id));
if let Some(index) = position {
if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
self.migrate_node_to_slot(node_id, old_slot, slot_id);
self.update_reusable_count();
return Some(node_id);
}
}
None
}
fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> bool {
for pool in self.reusable_by_type.values_mut() {
if let Some(pos) = pool.iter().position(|(_, n)| *n == node_id) {
pool.remove(pos);
return true;
}
}
if let Some(pos) = self
.reusable_nodes_untyped
.iter()
.position(|(_, n)| *n == node_id)
{
self.reusable_nodes_untyped.remove(pos);
return true;
}
false
}
fn migrate_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
self.mapping.remove_by_node(&node_id);
self.mapping.add_node(new_slot, node_id);
if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
nodes.retain(|candidate| *candidate != node_id);
if nodes.is_empty() {
self.precomposed_nodes.remove(&old_slot);
}
}
}
fn update_reusable_count(&mut self) {
self.reusable_count = self
.reusable_by_type
.values()
.map(|p| p.len())
.sum::<usize>()
+ self.reusable_nodes_untyped.len();
}
pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
if start_index >= self.active_order.len() {
return Vec::new();
}
let retain = self
.policy
.get_slots_to_retain(&self.active_order[start_index..]);
let mut retained = Vec::new();
while self.active_order.len() > start_index {
let slot = self.active_order.pop().expect("active_order not empty");
if retain.contains(&slot) {
retained.push(slot);
continue;
}
self.mapping.deactivate_slot(slot);
let content_type = self.slot_content_types.get(&slot).copied();
if let Some(nodes) = self.mapping.get_nodes(&slot) {
for node in nodes {
if let Some(ct) = content_type {
self.reusable_by_type
.entry(ct)
.or_default()
.push_back((slot, *node));
} else {
self.reusable_nodes_untyped.push_back((slot, *node));
}
}
}
}
retained.reverse();
self.active_order.extend(retained);
let mut disposed = Vec::new();
for pool in self.reusable_by_type.values_mut() {
while pool.len() > self.max_reusable_per_type {
if let Some((_, node_id)) = pool.pop_front() {
self.mapping.remove_by_node(&node_id);
disposed.push(node_id);
}
}
}
while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
if let Some((_, node_id)) = self.reusable_nodes_untyped.pop_front() {
self.mapping.remove_by_node(&node_id);
disposed.push(node_id);
}
}
self.update_reusable_count();
disposed
}
fn prune_inactive_slots(&mut self) {
let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
let mut reusable_slots: HashSet<SlotId> = HashSet::default();
for pool in self.reusable_by_type.values() {
for (slot, _) in pool {
reusable_slots.insert(*slot);
}
}
for (slot, _) in &self.reusable_nodes_untyped {
reusable_slots.insert(*slot);
}
let mut keep_slots = active.clone();
keep_slots.extend(reusable_slots);
self.mapping.retain_slots(&keep_slots);
self.slot_compositions
.retain(|slot, _| keep_slots.contains(slot));
self.slot_callbacks
.retain(|slot, _| keep_slots.contains(slot));
self.slot_content_types
.retain(|slot, _| keep_slots.contains(slot));
self.policy.prune_slots(&keep_slots);
let before_count = self.precomposed_count;
let mut removed_from_precomposed = 0usize;
self.precomposed_nodes.retain(|slot, nodes| {
if active.contains(slot) {
true
} else {
removed_from_precomposed += nodes.len();
false
}
});
for pool in self.reusable_by_type.values_mut() {
pool.retain(|(_, node)| self.mapping.get_slot(node).is_some());
}
self.reusable_by_type.retain(|_, pool| !pool.is_empty());
self.reusable_nodes_untyped
.retain(|(_, node)| self.mapping.get_slot(node).is_some());
self.update_reusable_count();
self.precomposed_count = before_count.saturating_sub(removed_from_precomposed);
}
pub fn reusable(&self) -> Vec<NodeId> {
let mut nodes: Vec<NodeId> = self
.reusable_by_type
.values()
.flat_map(|pool| pool.iter().map(|(_, n)| *n))
.collect();
nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
nodes
}
pub fn active_slots_count(&self) -> usize {
self.active_order.len()
}
pub fn reusable_slots_count(&self) -> usize {
self.reusable_count
}
pub fn invalidate_scopes(&self) {
self.mapping.invalidate_scopes();
}
pub fn was_last_slot_reused(&self) -> Option<bool> {
self.last_slot_reused
}
#[doc(hidden)]
pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
self.mapping
.slot_to_scopes
.iter()
.map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
.collect()
}
#[doc(hidden)]
pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<(usize, String)>> {
let slots = self.slot_compositions.get(&slot_id)?;
Some(slots.borrow().debug_dump_all_slots())
}
#[doc(hidden)]
pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
let slots = self.slot_compositions.get(&slot_id)?;
Some(slots.borrow().debug_dump_groups())
}
pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
&self.precomposed_nodes
}
pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
let mut disposed = Vec::new();
let mut empty_slots = Vec::new();
for (slot, nodes) in self.precomposed_nodes.iter_mut() {
if !active.contains(slot) {
disposed.extend(nodes.iter().copied());
empty_slots.push(*slot);
}
}
for slot in empty_slots {
self.precomposed_nodes.remove(&slot);
}
self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
disposed
}
}
#[cfg(test)]
#[path = "tests/subcompose_tests.rs"]
mod tests;