use std::collections::HashMap;
use std::hash::Hash;
use std::mem;
use crate::*;
pub fn map_keyed<T, K, U>(
list: impl Into<MaybeDyn<Vec<T>>> + 'static,
mut map_fn: impl FnMut(T) -> U + 'static,
key_fn: impl Fn(&T) -> K + 'static,
) -> ReadSignal<Vec<U>>
where
T: PartialEq + Clone + 'static,
K: Eq + Hash,
U: Clone,
{
let list = list.into();
let mut items = Vec::new();
let mut mapped: Vec<U> = Vec::new();
let mut mapped_tmp: Vec<Option<U>> = Vec::new();
let mut disposers: Vec<Option<NodeHandle>> = Vec::new();
let mut disposers_tmp: Vec<Option<NodeHandle>> = Vec::new();
let _list = list.clone();
let mut update = move || {
let new_items = _list.get_clone();
if new_items.is_empty() {
for dis in mem::take(&mut disposers) {
dis.unwrap().dispose();
}
mapped = Vec::new();
} else if items.is_empty() {
mapped.reserve(new_items.len());
disposers.reserve(new_items.len());
for new_item in new_items.iter().cloned() {
let map_fn = &mut map_fn;
let mapped = &mut mapped;
let new_disposer = create_child_scope(move || mapped.push(map_fn(new_item)));
disposers.push(Some(new_disposer));
}
} else {
mapped_tmp.clear();
mapped_tmp.resize(new_items.len(), None);
disposers_tmp.clear();
disposers_tmp.resize_with(new_items.len(), || None);
let min_len = usize::min(items.len(), new_items.len());
let start = items
.iter()
.zip(new_items.iter())
.position(|(a, b)| a != b)
.unwrap_or(min_len);
debug_assert!(
(items.get(start).is_none() && new_items.get(start).is_none())
|| (items.get(start) != new_items.get(start)),
"start is the first index where items[start] != new_items[start]"
);
let mut end = items.len();
let mut new_end = new_items.len();
while end > start && new_end > start && items[end - 1] == new_items[new_end - 1] {
end -= 1;
new_end -= 1;
mapped_tmp[new_end] = Some(mapped[end].clone());
disposers_tmp[new_end] = disposers[end].take();
}
debug_assert!(
if end != 0 && new_end != 0 {
(end == items.len() && new_end == new_items.len())
|| (items[end - 1] != new_items[new_end - 1])
} else {
true
},
"end and new_end are the last indexes where items[end - 1] != new_items[new_end - 1]"
);
let mut new_indices = HashMap::with_capacity(new_end - start);
let mut new_indices_next = vec![None; new_end - start];
for j in (start..new_end).rev() {
let item = &new_items[j];
let key = key_fn(item);
let i = new_indices.get(&key);
new_indices_next[j - start] = i.copied();
new_indices.insert(key, j);
}
for i in start..end {
let item = &items[i];
let key = key_fn(item);
if let Some(j) = new_indices.get(&key).copied() {
mapped_tmp[j] = Some(mapped[i].clone());
disposers_tmp[j] = disposers[i].take();
new_indices_next[j - start].and_then(|j| new_indices.insert(key, j));
} else {
disposers[i].take().unwrap().dispose();
}
}
for j in start..new_items.len() {
if matches!(mapped_tmp.get(j), Some(Some(_))) {
if j >= mapped.len() {
debug_assert_eq!(mapped.len(), j);
mapped.push(mapped_tmp[j].clone().unwrap());
disposers.push(disposers_tmp[j].take());
} else {
mapped[j] = mapped_tmp[j].clone().unwrap();
disposers[j] = disposers_tmp[j].take();
}
} else {
let mut tmp = None;
let new_item = new_items[j].clone();
let new_disposer = create_child_scope(|| tmp = Some(map_fn(new_item)));
if mapped.len() > j {
mapped[j] = tmp.unwrap();
disposers[j] = Some(new_disposer);
} else {
mapped.push(tmp.unwrap());
disposers.push(Some(new_disposer));
}
}
}
}
mapped.truncate(new_items.len());
disposers.truncate(new_items.len());
debug_assert!([mapped.len(), disposers.len()]
.iter()
.all(|l| *l == new_items.len()));
items = new_items;
mapped.clone()
};
let scope = use_current_scope();
create_memo(on(list, move || scope.run_in(&mut update)))
}
pub fn map_indexed<T, U>(
list: impl Into<MaybeDyn<Vec<T>>> + 'static,
mut map_fn: impl FnMut(T) -> U + 'static,
) -> ReadSignal<Vec<U>>
where
T: PartialEq + Clone + 'static,
U: Clone,
{
let list = list.into();
let mut items = Vec::new();
let mut mapped = Vec::new();
let mut disposers: Vec<NodeHandle> = Vec::new();
let _list = list.clone();
let mut update = move || {
let new_items = _list.get_clone();
if new_items.is_empty() {
for dis in mem::take(&mut disposers) {
dis.dispose();
}
items = Vec::new();
mapped = Vec::new();
} else {
if new_items.len() > items.len() {
let new_count = new_items.len() - items.len();
mapped.reserve(new_count);
disposers.reserve(new_count);
}
for (i, new_item) in new_items.iter().cloned().enumerate() {
let item = items.get(i);
let eqs = item != Some(&new_item);
if item.is_none() || eqs {
let mut tmp = None;
let new_disposer = create_child_scope(|| tmp = Some(map_fn(new_item)));
if item.is_none() {
mapped.push(tmp.unwrap());
disposers.push(new_disposer);
} else if eqs {
mapped[i] = tmp.unwrap();
let prev = mem::replace(&mut disposers[i], new_disposer);
prev.dispose();
}
}
}
if new_items.len() < items.len() {
for _i in new_items.len()..items.len() {
disposers.pop().unwrap().dispose();
}
}
mapped.truncate(new_items.len());
debug_assert!([mapped.len(), disposers.len()]
.iter()
.all(|l| *l == new_items.len()));
items = new_items;
}
mapped.clone()
};
let scope = use_current_scope();
create_memo(on(list, move || scope.run_in(&mut update)))
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::rc::Rc;
use super::*;
#[test]
fn keyed() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_keyed(a, |x| x * 2, |x| *x);
assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
a.set(vec![1, 2, 3, 4]);
assert_eq!(mapped.get_clone(), vec![2, 4, 6, 8]);
a.set(vec![2, 2, 3, 4]);
assert_eq!(mapped.get_clone(), vec![4, 4, 6, 8]);
});
}
#[test]
fn keyed_recompute_everything() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_keyed(a, |x| x * 2, |x| *x);
assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
a.set(vec![4, 5, 6]);
assert_eq!(mapped.get_clone(), vec![8, 10, 12]);
});
}
#[test]
fn keyed_clear() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_keyed(a, |x| x * 2, |x| *x);
a.set(Vec::new());
assert_eq!(mapped.get_clone(), Vec::<i32>::new());
});
}
#[test]
fn keyed_use_previous_computation() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let mapped = map_keyed(
a,
{
let counter = Rc::clone(&counter);
move |_| {
counter.set(counter.get() + 1);
counter.get()
}
},
|x| *x,
);
assert_eq!(mapped.get_clone(), vec![1, 2, 3]);
a.set(vec![1, 2]);
assert_eq!(mapped.get_clone(), vec![1, 2]);
a.set(vec![1, 2, 4]);
assert_eq!(mapped.get_clone(), vec![1, 2, 4]);
a.set(vec![1, 2, 3, 4]);
assert_eq!(mapped.get_clone(), vec![1, 2, 5, 4]);
});
}
#[test]
fn keyed_call_cleanup_on_remove() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let _mapped = map_keyed(
a,
{
let counter = Rc::clone(&counter);
move |_| {
let counter = Rc::clone(&counter);
on_cleanup(move || {
counter.set(counter.get() + 1);
});
}
},
|x| *x,
);
assert_eq!(counter.get(), 0, "no cleanup yet");
a.set(vec![1, 2]);
assert_eq!(counter.get(), 1);
a.set(vec![1, 2, 3]);
assert_eq!(counter.get(), 1);
a.set(vec![1, 3]);
assert_eq!(counter.get(), 2);
});
}
#[test]
fn keyed_call_cleanup_on_remove_all() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let _mapped = map_keyed(
a,
{
let counter = Rc::clone(&counter);
move |_| {
let counter = Rc::clone(&counter);
on_cleanup(move || {
counter.set(counter.get() + 1);
})
}
},
|x| *x,
);
assert_eq!(counter.get(), 0, "no cleanup yet");
a.set(vec![]);
assert_eq!(counter.get(), 3);
});
}
#[test]
fn indexed() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_indexed(a, |x| x * 2);
assert_eq!(mapped.get_clone(), vec![2, 4, 6]);
a.set(vec![1, 2, 3, 4]);
assert_eq!(mapped.get_clone(), vec![2, 4, 6, 8]);
a.set(vec![2, 2, 3, 4]);
assert_eq!(mapped.get_clone(), vec![4, 4, 6, 8]);
});
}
#[test]
fn indexed_clear() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_indexed(a, |x| x * 2);
a.set(Vec::new());
assert_eq!(mapped.get_clone(), Vec::<i32>::new());
});
}
#[test]
fn indexed_react() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let mapped = map_indexed(a, |x| x * 2);
let counter = create_signal(0);
create_effect(move || {
counter.set(counter.get_untracked() + 1);
mapped.track();
});
assert_eq!(counter.get(), 1);
a.set(vec![1, 2, 3, 4]);
assert_eq!(counter.get(), 2);
});
}
#[test]
fn indexed_use_previous_computation() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let mapped = map_indexed(a, {
let counter = Rc::clone(&counter);
move |_| {
counter.set(counter.get() + 1);
counter.get()
}
});
assert_eq!(mapped.get_clone(), vec![1, 2, 3]);
a.set(vec![1, 2]);
assert_eq!(mapped.get_clone(), vec![1, 2]);
a.set(vec![1, 2, 4]);
assert_eq!(mapped.get_clone(), vec![1, 2, 4]);
a.set(vec![1, 3, 4]);
assert_eq!(mapped.get_clone(), vec![1, 5, 4]);
});
}
#[test]
fn indexed_call_cleanup_on_remove() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let _mapped = map_indexed(a, {
let counter = Rc::clone(&counter);
move |_| {
let counter = Rc::clone(&counter);
on_cleanup(move || {
counter.set(counter.get() + 1);
});
}
});
assert_eq!(counter.get(), 0, "no cleanup yet");
a.set(vec![1, 2]);
assert_eq!(counter.get(), 1);
a.set(vec![1, 2, 3]);
assert_eq!(counter.get(), 1);
a.set(vec![1, 3]);
assert_eq!(counter.get(), 3);
});
}
#[test]
fn indexed_call_cleanup_on_remove_all() {
let _ = create_root(|| {
let a = create_signal(vec![1, 2, 3]);
let counter = Rc::new(Cell::new(0));
let _mapped = map_indexed(a, {
let counter = Rc::clone(&counter);
move |_| {
let counter = Rc::clone(&counter);
on_cleanup(move || {
counter.set(counter.get() + 1);
})
}
});
assert_eq!(counter.get(), 0, "no cleanup yet");
a.set(vec![]);
assert_eq!(counter.get(), 3);
});
}
#[test]
fn issue_739_keyed_should_not_track_nested_signals() {
let _ = create_root(|| {
let items = create_signal(vec![create_signal(1), create_signal(2)]);
map_keyed(items, |x| on_cleanup(move || x.dispose()), |x| x.get());
items.set(items.get_clone()[1..].to_vec());
});
}
#[test]
fn issue_739_indexed_should_not_track_nested_signals() {
let _ = create_root(|| {
let items = create_signal(vec![create_signal(()), create_signal(())]);
map_indexed(items, |x| on_cleanup(move || x.dispose()));
items.set(items.get_clone()[1..].to_vec());
});
}
}