From 17e715cde4a17d0af8189f7d669854dcc69a3da3 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 2 May 2025 16:52:04 +0200 Subject: [PATCH] criteria: add infrastructure --- Cargo.lock | 1 + Cargo.toml | 1 + src/criteria.rs | 96 ++++++++++ src/criteria/crit_graph.rs | 16 ++ src/criteria/crit_graph/crit_downstream.rs | 52 ++++++ src/criteria/crit_graph/crit_middle.rs | 111 +++++++++++ src/criteria/crit_graph/crit_root.rs | 173 +++++++++++++++++ src/criteria/crit_graph/crit_target.rs | 48 +++++ src/criteria/crit_graph/crit_upstream.rs | 176 ++++++++++++++++++ src/criteria/crit_leaf.rs | 156 ++++++++++++++++ src/criteria/crit_matchers.rs | 4 + .../crit_matchers/critm_any_or_all.rs | 73 ++++++++ src/criteria/crit_matchers/critm_constant.rs | 59 ++++++ src/criteria/crit_matchers/critm_exactly.rs | 61 ++++++ src/criteria/crit_matchers/critm_string.rs | 46 +++++ src/criteria/crit_per_target_data.rs | 136 ++++++++++++++ src/macros.rs | 7 +- src/main.rs | 1 + 18 files changed, 1214 insertions(+), 3 deletions(-) create mode 100644 src/criteria.rs create mode 100644 src/criteria/crit_graph.rs create mode 100644 src/criteria/crit_graph/crit_downstream.rs create mode 100644 src/criteria/crit_graph/crit_middle.rs create mode 100644 src/criteria/crit_graph/crit_root.rs create mode 100644 src/criteria/crit_graph/crit_target.rs create mode 100644 src/criteria/crit_graph/crit_upstream.rs create mode 100644 src/criteria/crit_leaf.rs create mode 100644 src/criteria/crit_matchers.rs create mode 100644 src/criteria/crit_matchers/critm_any_or_all.rs create mode 100644 src/criteria/crit_matchers/critm_constant.rs create mode 100644 src/criteria/crit_matchers/critm_exactly.rs create mode 100644 src/criteria/crit_matchers/critm_string.rs create mode 100644 src/criteria/crit_per_target_data.rs diff --git a/Cargo.lock b/Cargo.lock index da6f0291..bc8963a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,6 +602,7 @@ dependencies = [ "pin-project", "png", "rand", + "regex", "repc", "rustc-demangle", "serde", diff --git a/Cargo.toml b/Cargo.toml index dd43f8d5..36752338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ rustc-demangle = { version = "0.1.24", optional = true } tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true } kbvm = "0.1.4" tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] } +regex = "1.11.1" [build-dependencies] repc = "0.1.1" diff --git a/src/criteria.rs b/src/criteria.rs new file mode 100644 index 00000000..5e6f21f2 --- /dev/null +++ b/src/criteria.rs @@ -0,0 +1,96 @@ +mod crit_graph; +pub mod crit_leaf; +mod crit_matchers; +mod crit_per_target_data; + +use { + crate::{ + criteria::{ + crit_graph::{CritMgr, CritMiddle, CritRoot, CritRootCriterion, CritRootFixed}, + crit_leaf::CritLeafMatcher, + crit_matchers::{critm_any_or_all::CritMatchAnyOrAll, critm_exactly::CritMatchExactly}, + }, + utils::copyhashmap::CopyHashMap, + }, + linearize::StaticMap, + regex::Regex, + std::rc::{Rc, Weak}, +}; +pub use { + crit_graph::{CritTarget, CritUpstreamNode}, + crit_per_target_data::CritDestroyListener, +}; + +linear_ids!(CritMatcherIds, CritMatcherId, u64); + +type RootMatcherMap = CopyHashMap>>; +type FixedRootMatcher = StaticMap>>>; + +#[derive(Clone)] +#[expect(dead_code)] +pub enum CritLiteralOrRegex { + Literal(String), + Regex(Regex), +} + +impl CritLiteralOrRegex { + fn matches(&self, string: &str) -> bool { + match self { + CritLiteralOrRegex::Literal(p) => string == p, + CritLiteralOrRegex::Regex(r) => r.is_match(string), + } + } +} + +#[expect(dead_code)] +pub trait CritMgrExt: CritMgr { + fn list( + &self, + upstream: &[Rc>], + all: bool, + ) -> Rc> { + if upstream.is_empty() { + return self.match_constant()[all].clone(); + } + CritMiddle::new(self, upstream, CritMatchAnyOrAll::new(upstream, all)) + } + + fn exactly( + &self, + upstream: &[Rc>], + num: usize, + ) -> Rc> { + if num > upstream.len() { + return self.match_constant()[false].clone(); + } + if num == 0 { + let upstream: Vec<_> = upstream.iter().map(|u| u.not(self)).collect(); + return self.list(&upstream, true); + } + CritMiddle::new(self, upstream, CritMatchExactly::new(upstream, num)) + } + + fn leaf( + &self, + upstream: &Rc>, + on_match: impl Fn(::LeafData) -> Box + 'static, + ) -> Rc> { + CritLeafMatcher::new(self, upstream, on_match) + } + + fn not( + &self, + upstream: &Rc>, + ) -> Rc> { + upstream.not(self) + } + + fn root(&self, criterion: T) -> Rc> + where + T: CritRootCriterion, + { + CritRoot::new(self.roots(), self.id(), criterion) + } +} + +impl CritMgrExt for T where T: CritMgr {} diff --git a/src/criteria/crit_graph.rs b/src/criteria/crit_graph.rs new file mode 100644 index 00000000..e7ed8be4 --- /dev/null +++ b/src/criteria/crit_graph.rs @@ -0,0 +1,16 @@ +mod crit_downstream; +mod crit_middle; +mod crit_root; +mod crit_target; +mod crit_upstream; + +pub use { + crit_downstream::{CritDownstream, CritDownstreamData}, + crit_middle::{CritMiddle, CritMiddleCriterion}, + crit_root::{ + CritFixedRootCriterion, CritFixedRootCriterionBase, CritRoot, CritRootCriterion, + CritRootFixed, + }, + crit_target::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner}, + crit_upstream::{CritUpstreamData, CritUpstreamNode}, +}; diff --git a/src/criteria/crit_graph/crit_downstream.rs b/src/criteria/crit_graph/crit_downstream.rs new file mode 100644 index 00000000..cf3f50be --- /dev/null +++ b/src/criteria/crit_graph/crit_downstream.rs @@ -0,0 +1,52 @@ +use { + crate::criteria::{ + CritMatcherId, + crit_graph::{CritTarget, crit_upstream::CritUpstreamNode}, + }, + std::rc::Rc, +}; + +pub struct CritDownstreamData +where + Target: CritTarget, +{ + id: CritMatcherId, + pub(super) upstream: Vec>>, +} + +pub trait CritDownstream: 'static { + fn update_matched(self: Rc, target: &Target, matched: bool); +} + +impl CritDownstreamData +where + Target: CritTarget, +{ + pub fn new(id: CritMatcherId, upstream: &[Rc>]) -> Self { + Self { + id, + upstream: upstream.to_vec(), + } + } + + pub fn attach(&self, slf: &Rc>) { + for upstream in &self.upstream { + upstream.attach(self.id, slf.clone() as _); + } + } + + pub fn not(&self, mgr: &Target::Mgr) -> Vec>> { + self.upstream.iter().map(|n| n.not(mgr)).collect() + } +} + +impl Drop for CritDownstreamData +where + Target: CritTarget, +{ + fn drop(&mut self) { + for el in &self.upstream { + el.detach(self.id); + } + } +} diff --git a/src/criteria/crit_graph/crit_middle.rs b/src/criteria/crit_graph/crit_middle.rs new file mode 100644 index 00000000..f8e76041 --- /dev/null +++ b/src/criteria/crit_graph/crit_middle.rs @@ -0,0 +1,111 @@ +use { + crate::criteria::{ + CritUpstreamNode, + crit_graph::{ + CritDownstream, CritDownstreamData, CritTarget, CritUpstreamData, + crit_target::CritMgr, + crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData}, + }, + crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, + }, + std::rc::Rc, +}; + +pub struct CritMiddle +where + Target: CritTarget, + T: CritMiddleCriterion, +{ + upstream: CritDownstreamData, + downstream: CritUpstreamData>, + criterion: T, +} + +#[derive(Default)] +pub struct CritMiddleData { + matches: usize, + data: T, +} + +pub trait CritMiddleCriterion: Sized + 'static { + type Data: Default; + type Not: CritMiddleCriterion; + + fn update_matched(&self, target: &Target, node: &mut Self::Data, matched: bool) -> bool; + fn pull(&self, upstream: &[Rc>], target: &Target) -> bool; + fn not(&self) -> Self::Not; +} + +impl CritMiddle +where + Target: CritTarget, + T: CritMiddleCriterion, +{ + pub fn new( + mgr: &Target::Mgr, + upstream: &[Rc>], + criterion: T, + ) -> Rc { + let id = mgr.id(); + let slf = Rc::new_cyclic(|slf| Self { + upstream: CritDownstreamData::new(id, upstream), + downstream: CritUpstreamData::new(slf, id), + criterion, + }); + slf.upstream.attach(&slf); + slf + } +} + +impl CritDownstream for CritMiddle +where + Target: CritTarget, + T: CritMiddleCriterion, +{ + fn update_matched(self: Rc, target: &Target, matched: bool) { + let mut node = self.downstream.get_or_create(target); + let change = self + .criterion + .update_matched(target, &mut node.data, matched); + let matches = match matched { + true => node.matches + 1, + false => node.matches - 1, + }; + node.matches = matches; + self.downstream + .update_matched(target, node, change, matches == 0); + } +} + +impl CritUpstreamNodeBase for CritMiddle +where + Target: CritTarget, + T: CritMiddleCriterion, +{ + type Data = CritMiddleData; + + fn data(&self) -> &CritUpstreamData { + &self.downstream + } + + fn not(&self, mgr: &Target::Mgr) -> Rc> { + let upstream = self.upstream.not(mgr); + CritMiddle::new(mgr, &upstream, self.criterion.not()) + } + + fn pull(&self, target: &Target) -> bool { + self.criterion.pull(&self.upstream.upstream, target) + } +} + +impl CritDestroyListenerBase for CritMiddle +where + Target: CritTarget, + T: CritMiddleCriterion, +{ + type Data = CritUpstreamNodeData>; + + fn data(&self) -> &CritPerTargetData { + &self.downstream.nodes + } +} diff --git a/src/criteria/crit_graph/crit_root.rs b/src/criteria/crit_graph/crit_root.rs new file mode 100644 index 00000000..20828765 --- /dev/null +++ b/src/criteria/crit_graph/crit_root.rs @@ -0,0 +1,173 @@ +use { + crate::criteria::{ + CritMatcherId, CritUpstreamNode, FixedRootMatcher, RootMatcherMap, + crit_graph::{ + CritTarget, CritUpstreamData, + crit_target::CritMgr, + crit_upstream::{CritUpstreamNodeBase, CritUpstreamNodeData}, + }, + crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, + }, + std::{marker::PhantomData, rc::Rc}, +}; + +pub struct CritRoot +where + Target: CritTarget, + T: CritRootCriterion, +{ + id: CritMatcherId, + downstream: CritUpstreamData, + not: bool, + criterion: Rc, + roots: Rc, +} + +pub struct CritRootFixed(pub Crit, pub PhantomData); + +pub trait CritRootCriterion: Sized + 'static +where + Target: CritTarget, +{ + fn matches(&self, data: &Target) -> bool; + fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap> { + let _ = roots; + None + } + fn not(&self, mgr: &Target::Mgr) -> Option>> { + let _ = mgr; + None + } +} + +pub trait CritFixedRootCriterionBase: Sized + 'static +where + Target: CritTarget, +{ + fn constant(&self) -> bool; + fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher + where + Self: CritFixedRootCriterion; +} + +pub trait CritFixedRootCriterion: CritFixedRootCriterionBase +where + Target: CritTarget, +{ + const COMPARE: bool = true; + + fn matches(&self, data: &Target) -> bool; +} + +impl CritRootCriterion for CritRootFixed +where + Target: CritTarget, + T: CritFixedRootCriterion, +{ + fn matches(&self, data: &Target) -> bool { + let mut res = self.0.matches(data); + if T::COMPARE { + res = res == self.0.constant(); + } + res + } + + fn not(&self, mgr: &Target::Mgr) -> Option>> { + Some(self.0.not(mgr)[!self.0.constant()].clone()) + } +} + +impl CritUpstreamNodeBase for CritRoot +where + Target: CritTarget, + T: CritRootCriterion, +{ + type Data = (); + + fn data(&self) -> &CritUpstreamData { + &self.downstream + } + + fn not(&self, mgr: &Target::Mgr) -> Rc> { + if let Some(node) = self.criterion.not(mgr) { + return node; + } + let id = mgr.id(); + Self::new_(&self.roots, id, self.criterion.clone(), !self.not) + } + + fn pull(&self, target: &Target) -> bool { + self.criterion.matches(target) ^ self.not + } +} + +impl CritRoot +where + Target: CritTarget, + T: CritRootCriterion, +{ + pub fn new(roots: &Rc, id: CritMatcherId, criterion: T) -> Rc { + Self::new_(roots, id, Rc::new(criterion), false) + } + + fn new_( + roots: &Rc, + id: CritMatcherId, + criterion: Rc, + not: bool, + ) -> Rc { + let slf = Rc::new_cyclic(|slf| Self { + id, + downstream: CritUpstreamData::new(slf, id), + not, + criterion, + roots: roots.clone(), + }); + if let Some(nodes) = T::nodes(roots) { + nodes.set(id, Rc::downgrade(&slf)); + } + slf + } + + #[expect(dead_code)] + pub fn handle(&self, target: &Target) { + let new = self.criterion.matches(target) ^ self.not; + let node = match new { + true => self.downstream.get_or_create(target), + false => match self.downstream.get(target) { + Some(n) => n, + None => return, + }, + }; + self.downstream.update_matched(target, node, new, !new); + } + + #[expect(dead_code)] + pub fn has_downstream(&self) -> bool { + self.downstream.has_downstream() + } +} + +impl CritDestroyListenerBase for CritRoot +where + Target: CritTarget, + T: CritRootCriterion, +{ + type Data = CritUpstreamNodeData; + + fn data(&self) -> &CritPerTargetData { + &self.downstream.nodes + } +} + +impl Drop for CritRoot +where + Target: CritTarget, + T: CritRootCriterion, +{ + fn drop(&mut self) { + if let Some(nodes) = T::nodes(&self.roots) { + nodes.remove(&self.id); + } + } +} diff --git a/src/criteria/crit_graph/crit_target.rs b/src/criteria/crit_graph/crit_target.rs new file mode 100644 index 00000000..18974748 --- /dev/null +++ b/src/criteria/crit_graph/crit_target.rs @@ -0,0 +1,48 @@ +use { + crate::{ + criteria::{ + CritDestroyListener, CritMatcherId, FixedRootMatcher, crit_leaf::CritLeafEvent, + crit_matchers::critm_constant::CritMatchConstant, + }, + utils::{copyhashmap::CopyHashMap, queue::AsyncQueue}, + }, + std::{ + hash::Hash, + rc::{Rc, Weak}, + }, +}; + +pub trait CritMgr: 'static { + type Target: CritTarget; + + fn id(&self) -> CritMatcherId; + fn leaf_events(&self) -> &Rc>>; + fn match_constant(&self) -> &FixedRootMatcher>; + fn roots(&self) -> &Rc<::RootMatchers>; +} + +pub trait CritTarget: 'static { + type Id: Copy + Hash + Eq; + type Mgr: CritMgr; + type RootMatchers; + type LeafData: Copy + Eq; + type Owner: WeakCritTargetOwner; + + fn owner(&self) -> Self::Owner; + fn id(&self) -> Self::Id; + fn destroyed(&self) -> &CopyHashMap>>; + fn leaf_data(&self) -> Self::LeafData; +} + +pub trait CritTargetOwner: 'static { + type Target: CritTarget; + + fn data(&self) -> &Self::Target; +} + +pub trait WeakCritTargetOwner: 'static { + type Target: CritTarget; + type Owner: CritTargetOwner; + + fn upgrade(&self) -> Option; +} diff --git a/src/criteria/crit_graph/crit_upstream.rs b/src/criteria/crit_graph/crit_upstream.rs new file mode 100644 index 00000000..5042e9a5 --- /dev/null +++ b/src/criteria/crit_graph/crit_upstream.rs @@ -0,0 +1,176 @@ +use { + crate::{ + criteria::{ + CritDestroyListener, CritMatcherId, + crit_graph::{ + WeakCritTargetOwner, + crit_downstream::CritDownstream, + crit_target::{CritTarget, CritTargetOwner}, + }, + crit_per_target_data::CritPerTargetData, + }, + utils::copyhashmap::CopyHashMap, + }, + std::{ + cell::RefMut, + mem, + ops::{Deref, DerefMut}, + rc::{Rc, Weak}, + }, +}; + +pub struct CritUpstreamData +where + Target: CritTarget, +{ + downstream: CopyHashMap>>, + pub nodes: CritPerTargetData>, +} + +pub struct CritUpstreamNodeData +where + Target: CritTarget, +{ + matched: bool, + tl: Target::Owner, + data: T, +} + +pub trait CritUpstreamNodeBase: 'static +where + Target: CritTarget, +{ + type Data; + + fn data(&self) -> &CritUpstreamData; + fn not(&self, mgr: &Target::Mgr) -> Rc>; + fn pull(&self, target: &Target) -> bool; +} + +pub trait CritUpstreamNode: 'static +where + Target: CritTarget, +{ + fn attach(&self, id: CritMatcherId, downstream: Rc>); + fn detach(&self, id: CritMatcherId); + fn not(&self, mgr: &Target::Mgr) -> Rc>; + fn pull(&self, target: &Target) -> bool; + #[expect(dead_code)] + fn get(&self, target: &Target) -> bool; +} + +impl CritUpstreamNode for T +where + Target: CritTarget, + T: CritUpstreamNodeBase, +{ + fn attach(&self, id: CritMatcherId, downstream: Rc>) { + let data = self.data(); + for n in data.nodes.borrow_mut().values_mut() { + if !n.matched { + continue; + } + let Some(target) = n.tl.upgrade() else { + continue; + }; + downstream.clone().update_matched(target.data(), true); + } + data.downstream.set(id, Rc::downgrade(&downstream)); + } + + fn detach(&self, id: CritMatcherId) { + self.data().downstream.remove(&id); + } + + fn not(&self, mgr: &Target::Mgr) -> Rc> { + >::not(self, mgr) + } + + fn pull(&self, target: &Target) -> bool { + >::pull(self, target) + } + + fn get(&self, target: &Target) -> bool { + >::data(self).matched(target) + } +} + +impl Deref for CritUpstreamNodeData +where + Target: CritTarget, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for CritUpstreamNodeData +where + Target: CritTarget, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl CritUpstreamData +where + Target: CritTarget, +{ + pub fn new(slf: &Weak>, id: CritMatcherId) -> Self { + Self { + downstream: Default::default(), + nodes: CritPerTargetData::new(slf, id), + } + } + + pub fn update_matched( + &self, + target: &Target, + mut node: RefMut>, + matched: bool, + remove: bool, + ) { + let unchanged = mem::replace(&mut node.matched, matched) == matched; + drop(node); + if remove { + self.nodes.remove(target.id()); + } + if unchanged { + return; + } + for el in self.downstream.lock().values() { + if let Some(el) = el.upgrade() { + el.update_matched(target, matched); + } + } + } + + pub fn get_or_create(&self, target: &Target) -> RefMut> + where + T: Default, + { + self.nodes.get_or_create(target, || CritUpstreamNodeData { + matched: false, + tl: target.owner(), + data: Default::default(), + }) + } + + pub fn get(&self, target: &Target) -> Option>> { + self.nodes.get(target) + } + + pub fn has_downstream(&self) -> bool { + self.downstream.is_not_empty() + } + + pub fn matched(&self, target: &Target) -> bool { + let Some(node) = self.nodes.get(target) else { + return false; + }; + node.matched + } +} diff --git a/src/criteria/crit_leaf.rs b/src/criteria/crit_leaf.rs new file mode 100644 index 00000000..5bc3ddb4 --- /dev/null +++ b/src/criteria/crit_leaf.rs @@ -0,0 +1,156 @@ +use { + crate::{ + criteria::{ + CritUpstreamNode, + crit_graph::{CritDownstream, CritDownstreamData, CritMgr, CritTarget}, + crit_per_target_data::{CritDestroyListenerBase, CritPerTargetData}, + }, + utils::{cell_ext::CellExt, queue::AsyncQueue}, + }, + std::{ + cell::Cell, + rc::{Rc, Weak}, + }, +}; + +pub struct CritLeafMatcher +where + Target: CritTarget, +{ + upstream: CritDownstreamData, + on_match: Box Box>, + targets: CritPerTargetData>, + events: Rc>>, +} + +pub(in crate::criteria) struct NodeHolder +where + Target: CritTarget, +{ + node: Rc>, +} + +struct Node +where + Target: CritTarget, +{ + leaf: Weak>, + target_id: Target::Id, + needs_event: Cell, + new_data: Cell>, + data: Cell>, + on_unmatch: Cell>>, +} + +pub struct CritLeafEvent +where + Target: CritTarget, +{ + node: Rc>, +} + +impl CritDownstream for CritLeafMatcher +where + Target: CritTarget, +{ + fn update_matched(self: Rc, data: &Target, matched: bool) { + let node = &self + .targets + .get_or_create(data, || { + let node = Rc::new(Node { + leaf: Rc::downgrade(&self), + target_id: data.id(), + needs_event: Cell::new(true), + new_data: Cell::new(None), + data: Cell::new(None), + on_unmatch: Cell::new(None), + }); + NodeHolder { node: node.clone() } + }) + .node; + self.push_event(node, matched.then_some(data.leaf_data())); + } +} + +impl CritLeafMatcher +where + Target: CritTarget, +{ + pub(in crate::criteria) fn new( + mgr: &Target::Mgr, + upstream: &Rc>, + on_match: impl Fn(Target::LeafData) -> Box + 'static, + ) -> Rc { + let id = mgr.id(); + let slf = Rc::new_cyclic(|slf| Self { + targets: CritPerTargetData::new(slf, id), + on_match: Box::new(on_match), + events: mgr.leaf_events().clone(), + upstream: CritDownstreamData::new(id, &[upstream.clone()]), + }); + slf.upstream.attach(&slf); + slf + } + + fn push_event(&self, node: &Rc>, new_data: Option) { + node.new_data.set(new_data); + if node.needs_event.replace(false) { + self.events.push(CritLeafEvent { node: node.clone() }); + } + } +} + +impl CritLeafEvent +where + Target: CritTarget, +{ + #[expect(dead_code)] + pub fn run(self) { + let n = self.node; + n.needs_event.set(true); + if n.new_data != n.data { + if let Some(on_unmatch) = n.on_unmatch.take() { + if n.leaf.strong_count() == 0 { + return; + } + on_unmatch(); + } + } + n.data.set(n.new_data.get()); + if n.data.is_some() != n.on_unmatch.is_some() { + let Some(leaf) = n.leaf.upgrade() else { + return; + }; + if let Some(id) = n.data.get() { + n.on_unmatch.set(Some((leaf.on_match)(id))); + } else { + if let Some(on_unmatch) = n.on_unmatch.take() { + on_unmatch(); + } + leaf.targets.remove(n.target_id); + } + } + } +} + +impl Drop for NodeHolder +where + Target: CritTarget, +{ + fn drop(&mut self) { + if let Some(leaf) = self.node.leaf.upgrade() { + leaf.push_event(&self.node, None); + } + } +} + +impl CritDestroyListenerBase for CritLeafMatcher +where + Target: CritTarget, +{ + type Data = NodeHolder; + + fn data(&self) -> &CritPerTargetData { + &self.targets + } +} diff --git a/src/criteria/crit_matchers.rs b/src/criteria/crit_matchers.rs new file mode 100644 index 00000000..df042f5a --- /dev/null +++ b/src/criteria/crit_matchers.rs @@ -0,0 +1,4 @@ +pub mod critm_any_or_all; +pub mod critm_constant; +pub mod critm_exactly; +pub mod critm_string; diff --git a/src/criteria/crit_matchers/critm_any_or_all.rs b/src/criteria/crit_matchers/critm_any_or_all.rs new file mode 100644 index 00000000..38c3eaaa --- /dev/null +++ b/src/criteria/crit_matchers/critm_any_or_all.rs @@ -0,0 +1,73 @@ +use { + crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, + std::{marker::PhantomData, rc::Rc}, +}; + +pub struct CritMatchAnyOrAll +where + Target: CritTarget, +{ + all: bool, + total: usize, + _phantom: PhantomData, +} + +impl CritMatchAnyOrAll +where + Target: CritTarget, +{ + pub fn new(upstream: &[Rc>], all: bool) -> Self { + Self { + all, + total: upstream.len(), + _phantom: Default::default(), + } + } +} + +impl CritMiddleCriterion for CritMatchAnyOrAll +where + Target: CritTarget, +{ + type Data = usize; + type Not = Self; + + fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool { + if matched { + *node += 1; + } else { + *node -= 1; + } + if self.all { + *node == self.total + } else { + *node > 0 + } + } + + fn pull(&self, upstream: &[Rc>], node: &Target) -> bool { + for upstream in upstream { + if upstream.pull(node) { + if !self.all { + return true; + } + } else { + if self.all { + return false; + } + } + } + self.all + } + + fn not(&self) -> Self + where + Self: Sized, + { + Self { + all: !self.all, + total: self.total, + _phantom: Default::default(), + } + } +} diff --git a/src/criteria/crit_matchers/critm_constant.rs b/src/criteria/crit_matchers/critm_constant.rs new file mode 100644 index 00000000..c96404bc --- /dev/null +++ b/src/criteria/crit_matchers/critm_constant.rs @@ -0,0 +1,59 @@ +use { + crate::criteria::{ + CritMatcherIds, FixedRootMatcher, + crit_graph::{ + CritFixedRootCriterion, CritFixedRootCriterionBase, CritMgr, CritRoot, CritRootFixed, + CritTarget, + }, + }, + linearize::static_map, + std::{marker::PhantomData, rc::Rc}, +}; + +pub struct CritMatchConstant(pub bool, pub PhantomData); + +impl CritMatchConstant +where + Target: CritTarget, +{ + #[expect(dead_code)] + pub fn create( + roots: &Rc, + ids: &CritMatcherIds, + ) -> FixedRootMatcher> { + static_map! { + v => CritRoot::new( + roots, + ids.next(), + CritRootFixed(Self(v, PhantomData), PhantomData), + ), + } + } +} + +impl CritFixedRootCriterionBase for CritMatchConstant +where + Target: CritTarget, +{ + fn constant(&self) -> bool { + self.0 + } + + fn not<'a>(&self, mgr: &'a Target::Mgr) -> &'a FixedRootMatcher + where + Self: CritFixedRootCriterion, + { + mgr.match_constant() + } +} + +impl CritFixedRootCriterion for CritMatchConstant +where + Target: CritTarget, +{ + const COMPARE: bool = false; + + fn matches(&self, _data: &Target) -> bool { + self.0 + } +} diff --git a/src/criteria/crit_matchers/critm_exactly.rs b/src/criteria/crit_matchers/critm_exactly.rs new file mode 100644 index 00000000..fe4c3e0a --- /dev/null +++ b/src/criteria/crit_matchers/critm_exactly.rs @@ -0,0 +1,61 @@ +use { + crate::criteria::crit_graph::{CritMiddleCriterion, CritTarget, CritUpstreamNode}, + std::{marker::PhantomData, rc::Rc}, +}; + +pub struct CritMatchExactly { + total: usize, + num: usize, + not: bool, + _phantom: PhantomData, +} + +impl CritMatchExactly { + pub fn new(upstream: &[Rc>], num: usize) -> Self { + Self { + total: upstream.len(), + num, + not: false, + _phantom: Default::default(), + } + } +} + +impl CritMiddleCriterion for CritMatchExactly +where + Target: CritTarget, +{ + type Data = usize; + type Not = Self; + + fn update_matched(&self, _data: &Target, node: &mut usize, matched: bool) -> bool { + if matched { + *node += 1; + } else { + *node -= 1; + } + (*node == self.num) ^ self.not + } + + fn pull(&self, upstream: &[Rc>], node: &Target) -> bool { + let mut n = 0; + for upstream in upstream { + if upstream.pull(node) { + n += 1; + } + } + (n == self.num) ^ self.not + } + + fn not(&self) -> Self + where + Self: Sized, + { + Self { + total: self.total, + num: self.total - self.num, + not: !self.not, + _phantom: Default::default(), + } + } +} diff --git a/src/criteria/crit_matchers/critm_string.rs b/src/criteria/crit_matchers/critm_string.rs new file mode 100644 index 00000000..b486ea0f --- /dev/null +++ b/src/criteria/crit_matchers/critm_string.rs @@ -0,0 +1,46 @@ +use { + crate::criteria::{ + CritLiteralOrRegex, RootMatcherMap, + crit_graph::{CritRootCriterion, CritTarget}, + }, + std::marker::PhantomData, +}; + +pub struct CritMatchString { + string: CritLiteralOrRegex, + _phantom: PhantomData<(fn(&Target), A)>, +} + +pub trait StringAccess: Sized + 'static +where + Target: CritTarget, +{ + fn with_string(data: &Target, f: impl FnOnce(&str) -> bool) -> bool; + fn nodes( + roots: &Target::RootMatchers, + ) -> &RootMatcherMap>; +} + +impl CritMatchString { + #[expect(dead_code)] + pub fn new(string: CritLiteralOrRegex) -> Self { + Self { + string, + _phantom: Default::default(), + } + } +} + +impl CritRootCriterion for CritMatchString +where + Target: CritTarget, + A: StringAccess, +{ + fn matches(&self, data: &Target) -> bool { + A::with_string(data, |s| self.string.matches(s)) + } + + fn nodes(roots: &Target::RootMatchers) -> Option<&RootMatcherMap> { + Some(A::nodes(roots)) + } +} diff --git a/src/criteria/crit_per_target_data.rs b/src/criteria/crit_per_target_data.rs new file mode 100644 index 00000000..ea9dd86f --- /dev/null +++ b/src/criteria/crit_per_target_data.rs @@ -0,0 +1,136 @@ +use { + crate::criteria::{ + CritMatcherId, + crit_graph::{CritTarget, CritTargetOwner, WeakCritTargetOwner}, + }, + ahash::AHashMap, + std::{ + cell::{RefCell, RefMut}, + ops::{Deref, DerefMut}, + rc::Weak, + }, +}; + +pub struct CritPerTargetData +where + Target: CritTarget, +{ + id: CritMatcherId, + slf: Weak>, + data: RefCell>>, +} + +pub struct PerTreeNodeData +where + Target: CritTarget, +{ + node: Target::Owner, + data: T, +} + +pub(super) trait CritDestroyListenerBase: 'static +where + Target: CritTarget, +{ + type Data; + + fn data(&self) -> &CritPerTargetData; +} + +pub trait CritDestroyListener: 'static +where + Target: CritTarget, +{ + #[expect(dead_code)] + fn destroyed(&self, target_id: Target::Id); +} + +impl CritPerTargetData +where + Target: CritTarget, +{ + pub fn new(slf: &Weak>, id: CritMatcherId) -> Self { + Self { + id, + slf: slf.clone() as _, + data: Default::default(), + } + } + + pub fn get_or_create(&self, target: &Target, default: impl FnOnce() -> T) -> RefMut { + RefMut::map(self.data.borrow_mut(), |d| { + &mut d + .entry(target.id()) + .or_insert_with(|| { + target.destroyed().set(self.id, self.slf.clone()); + PerTreeNodeData { + node: target.owner(), + data: default(), + } + }) + .data + }) + } + + pub fn get(&self, target: &Target) -> Option> { + RefMut::filter_map(self.data.borrow_mut(), |d| { + d.get_mut(&target.id()).map(|d| &mut d.data) + }) + .ok() + } + + pub fn remove(&self, target_id: Target::Id) { + if let Some(node) = self.data.borrow_mut().remove(&target_id) { + if let Some(node) = node.node.upgrade() { + node.data().destroyed().remove(&self.id); + } + } + } + + pub fn borrow_mut(&self) -> RefMut<'_, AHashMap>> { + self.data.borrow_mut() + } +} + +impl Drop for CritPerTargetData +where + Target: CritTarget, +{ + fn drop(&mut self) { + for d in self.data.borrow().values() { + if let Some(n) = d.node.upgrade() { + n.data().destroyed().remove(&self.id); + } + } + } +} + +impl CritDestroyListener for T +where + Target: CritTarget, + T: CritDestroyListenerBase, +{ + fn destroyed(&self, target_id: Target::Id) { + let _v = self.data().data.borrow_mut().remove(&target_id); + } +} + +impl Deref for PerTreeNodeData +where + Target: CritTarget, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for PerTreeNodeData +where + Target: CritTarget, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/macros.rs b/src/macros.rs index c16639b9..65bd71c7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -181,10 +181,10 @@ macro_rules! shared_ids { } macro_rules! linear_ids { - ($ids:ident, $id:ident $(,)?) => { - linear_ids!($ids, $id, u32); + ($(#[$attr1:meta])* $ids:ident, $id:ident $(,)?) => { + linear_ids!($(#[$attr1])* $ids, $id, u32); }; - ($ids:ident, $id:ident, $ty:ty $(,)?) => { + ($(#[$attr1:meta])* $ids:ident, $id:ident, $ty:ty $(,)?) => { pub struct $ids { next: crate::utils::numcell::NumCell<$ty>, } @@ -197,6 +197,7 @@ macro_rules! linear_ids { } } + $(#[$attr1])* impl $ids { pub fn next(&self) -> $id { $id(self.next.fetch_add(1)) diff --git a/src/main.rs b/src/main.rs index 974d0441..4fa74368 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,7 @@ mod cmm; mod compositor; mod config; mod cpu_worker; +mod criteria; mod cursor; mod cursor_user; mod damage;