use dioxus_core::{ReactiveContext, SubscriberList, Subscribers};
use dioxus_signals::{CopyValue, ReadableExt, SyncStorage, Writable, WritableExt};
use std::fmt::Debug;
use std::hash::BuildHasher;
use std::{
collections::{HashMap, HashSet},
hash::Hash,
ops::Deref,
sync::Arc,
};
#[derive(Clone, Default)]
pub(crate) struct SelectorNode {
subscribers: HashSet<ReactiveContext>,
root: HashMap<PathKey, SelectorNode>,
}
impl SelectorNode {
fn get(&self, path: &[PathKey]) -> Option<&SelectorNode> {
let [first, rest @ ..] = path else {
return Some(self);
};
self.root.get(first).and_then(|child| child.get(rest))
}
fn get_mut(&mut self, path: &[PathKey]) -> Option<&mut SelectorNode> {
let [first, rest @ ..] = path else {
return Some(self);
};
self.root
.get_mut(first)
.and_then(|child| child.get_mut(rest))
}
fn get_mut_or_default(&mut self, path: &[PathKey]) -> &mut SelectorNode {
let [first, rest @ ..] = path else {
return self;
};
self.root
.entry(*first)
.or_default()
.get_mut_or_default(rest)
}
fn paths_under(&self, current_path: &[PathKey], paths: &mut Vec<Box<[PathKey]>>) {
paths.push(current_path.into());
for (i, child) in self.root.iter() {
let mut child_path: Vec<PathKey> = current_path.into();
child_path.push(*i);
child.paths_under(&child_path, paths);
}
}
fn paths_at_and_after_index(
&self,
path: &[PathKey],
index: usize,
paths: &mut Vec<Box<[PathKey]>>,
) {
let Some(node) = self.get(path) else {
return;
};
for (i, child) in node.root.iter() {
if *i as usize >= index {
let mut child_path: Vec<PathKey> = path.into();
child_path.push(*i);
child.paths_under(&child_path, paths);
}
}
}
fn remove(&mut self, path: &[PathKey]) {
let [first, rest @ ..] = path else {
return;
};
if let Some(node) = self.root.get_mut(first) {
if rest.is_empty() {
self.root.remove(first);
} else {
node.remove(rest);
}
}
}
}
pub(crate) type PathKey = u16;
#[cfg(feature = "large-path")]
const PATH_LENGTH: usize = 32;
#[cfg(not(feature = "large-path"))]
const PATH_LENGTH: usize = 16;
#[derive(Copy, Clone, PartialEq)]
pub(crate) struct TinyVec {
length: usize,
path: [PathKey; PATH_LENGTH],
}
impl Default for TinyVec {
fn default() -> Self {
Self::new()
}
}
impl Debug for TinyVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TinyVec")
.field("path", &&self.path[..self.length])
.finish()
}
}
impl TinyVec {
pub(crate) const fn new() -> Self {
Self {
length: 0,
path: [0; PATH_LENGTH],
}
}
pub(crate) const fn push(&mut self, index: u16) {
if self.length < self.path.len() {
self.path[self.length] = index;
self.length += 1;
} else {
panic!("SelectorPath is full");
}
}
}
impl Deref for TinyVec {
type Target = [u16];
fn deref(&self) -> &Self::Target {
&self.path[..self.length]
}
}
#[derive(Default)]
pub(crate) struct StoreSubscriptionsInner {
root: SelectorNode,
hasher: std::collections::hash_map::RandomState,
}
#[derive(Default)]
pub(crate) struct StoreSubscriptions {
inner: CopyValue<StoreSubscriptionsInner, SyncStorage>,
}
impl Clone for StoreSubscriptions {
fn clone(&self) -> Self {
*self
}
}
impl Copy for StoreSubscriptions {}
impl PartialEq for StoreSubscriptions {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl StoreSubscriptions {
pub(crate) fn new() -> Self {
Self {
inner: CopyValue::new_maybe_sync(StoreSubscriptionsInner {
root: SelectorNode::default(),
hasher: std::collections::hash_map::RandomState::new(),
}),
}
}
pub(crate) fn hash(&self, index: &impl Hash) -> PathKey {
(self.inner.write_unchecked().hasher.hash_one(index) % PathKey::MAX as u64) as PathKey
}
pub(crate) fn track(&self, key: &[PathKey]) {
if let Some(rc) = ReactiveContext::current() {
let subscribers = self.subscribers(key);
rc.subscribe(subscribers);
}
}
pub(crate) fn track_recursive(&self, key: &[PathKey]) {
if let Some(rc) = ReactiveContext::current() {
let mut paths = Vec::new();
{
let mut write = self.inner.write_unchecked();
let root = write.root.get_mut_or_default(key);
let mut nodes = vec![(key.to_vec(), &*root)];
while let Some((path, node)) = nodes.pop() {
for (child_key, child_node) in &node.root {
let mut new_path = path.clone();
new_path.push(*child_key);
nodes.push((new_path, child_node));
}
paths.push(path);
}
}
for path in paths {
let subscribers = self.subscribers(&path);
rc.subscribe(subscribers);
}
}
}
pub(crate) fn mark_dirty(&self, key: &[PathKey]) {
let paths = {
let read = &self.inner.read_unchecked();
let Some(node) = read.root.get(key) else {
return;
};
let mut paths = Vec::new();
node.paths_under(key, &mut paths);
paths
};
for path in paths {
self.mark_dirty_shallow(&path);
}
}
pub(crate) fn mark_dirty_shallow(&self, key: &[PathKey]) {
#[allow(clippy::mutable_key_type)]
let mut subscribers = {
let mut write = self.inner.write_unchecked();
let Some(node) = write.root.get_mut(key) else {
return;
};
std::mem::take(&mut node.subscribers)
};
subscribers.retain(|reactive_context| reactive_context.mark_dirty());
let mut write = self.inner.write_unchecked();
let Some(node) = write.root.get_mut(key) else {
return;
};
node.subscribers.extend(subscribers);
}
pub(crate) fn mark_dirty_at_and_after_index(&self, key: &[PathKey], index: usize) {
let paths = {
let read = self.inner.read_unchecked();
let mut paths = Vec::new();
read.root.paths_at_and_after_index(key, index, &mut paths);
paths
};
for path in paths {
self.mark_dirty_shallow(&path);
}
}
pub(crate) fn subscribers(&self, key: &[PathKey]) -> Subscribers {
Arc::new(StoreSubscribers {
subscriptions: *self,
path: key.to_vec().into_boxed_slice(),
})
.into()
}
}
struct StoreSubscribers {
subscriptions: StoreSubscriptions,
path: Box<[PathKey]>,
}
impl SubscriberList for StoreSubscribers {
fn add(&self, subscriber: ReactiveContext) {
let Ok(mut write) = self.subscriptions.inner.try_write_unchecked() else {
return;
};
let node = write.root.get_mut_or_default(&self.path);
node.subscribers.insert(subscriber);
}
fn remove(&self, subscriber: &ReactiveContext) {
let Ok(mut write) = self.subscriptions.inner.try_write_unchecked() else {
return;
};
let Some(node) = write.root.get_mut(&self.path) else {
return;
};
node.subscribers.remove(subscriber);
if node.subscribers.is_empty() && node.root.is_empty() {
write.root.remove(&self.path);
}
}
fn visit(&self, f: &mut dyn FnMut(&ReactiveContext)) {
let Ok(read) = self.subscriptions.inner.try_read() else {
return;
};
let Some(node) = read.root.get(&self.path) else {
return;
};
node.subscribers.iter().for_each(f);
}
}