use std::sync::Arc;
use crate::fiber_tree::with_current_fiber;
struct CallbackStorage<F, Deps> {
callback: Arc<F>,
prev_deps: Option<Deps>,
}
impl<F: Clone, Deps: Clone> Clone for CallbackStorage<F, Deps> {
fn clone(&self) -> Self {
Self {
callback: self.callback.clone(),
prev_deps: self.prev_deps.clone(),
}
}
}
struct MemoStorage<T, Deps> {
value: T,
prev_deps: Option<Deps>,
}
impl<T: Clone, Deps: Clone> Clone for MemoStorage<T, Deps> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
prev_deps: self.prev_deps.clone(),
}
}
}
pub fn use_callback<F, Deps>(callback: F, deps: Option<Deps>) -> Arc<F>
where
F: Clone + Send + Sync + 'static,
Deps: Clone + PartialEq + Send + 'static,
{
with_current_fiber(|fiber| {
let hook_index = fiber.next_hook_index();
let existing_storage: Option<CallbackStorage<F, Deps>> = fiber.get_hook(hook_index);
let storage = if let Some(mut storage) = existing_storage {
let deps_changed = match (&storage.prev_deps, &deps) {
(None, None) => false, (Some(_), None) | (None, Some(_)) => true, (Some(prev), Some(curr)) => prev != curr, };
if deps_changed {
storage.callback = Arc::new(callback);
storage.prev_deps = deps;
fiber.set_hook(hook_index, storage.clone());
}
storage
} else {
let new_storage = CallbackStorage {
callback: Arc::new(callback),
prev_deps: deps,
};
fiber.set_hook(hook_index, new_storage.clone());
new_storage
};
storage.callback
})
.expect("use_callback must be called within a component render context")
}
pub fn use_memo<T, Deps, F>(compute: F, deps: Option<Deps>) -> T
where
T: Clone + Send + 'static,
Deps: Clone + PartialEq + Send + 'static,
F: FnOnce() -> T,
{
with_current_fiber(|fiber| {
let hook_index = fiber.next_hook_index();
let existing_storage: Option<MemoStorage<T, Deps>> = fiber.get_hook(hook_index);
let storage = if let Some(mut storage) = existing_storage {
let deps_changed = match (&storage.prev_deps, &deps) {
(None, None) => false, (Some(_), None) | (None, Some(_)) => true, (Some(prev), Some(curr)) => prev != curr, };
if deps_changed {
storage.value = compute();
storage.prev_deps = deps;
fiber.set_hook(hook_index, storage.clone());
}
storage
} else {
let new_storage = MemoStorage {
value: compute(),
prev_deps: deps,
};
fiber.set_hook(hook_index, new_storage.clone());
new_storage
};
storage.value
})
.expect("use_memo must be called within a component render context")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fiber::FiberId;
use crate::fiber_tree::{FiberTree, clear_fiber_tree, set_fiber_tree};
fn setup_test_fiber() -> FiberId {
let mut tree = FiberTree::new();
let fiber_id = tree.mount(None, None);
tree.begin_render(fiber_id);
set_fiber_tree(tree);
fiber_id
}
fn cleanup_test() {
clear_fiber_tree();
}
#[test]
fn test_use_callback_basic() {
let _fiber_id = setup_test_fiber();
let callback = use_callback(|x: i32| x * 2, None::<()>);
assert_eq!(callback(5), 10);
cleanup_test();
}
#[test]
fn test_use_callback_stable_with_none_deps() {
let fiber_id = setup_test_fiber();
fn add_one(x: i32) -> i32 {
x + 1
}
let callback1 = use_callback(add_one, None::<()>);
let ptr1 = Arc::as_ptr(&callback1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let callback2 = use_callback(add_one, None::<()>);
let ptr2 = Arc::as_ptr(&callback2);
assert_eq!(ptr1, ptr2, "Callback with None deps should be stable");
assert_eq!(callback2(5), 6);
cleanup_test();
}
#[test]
fn test_use_callback_recreated_on_dep_change() {
let fiber_id = setup_test_fiber();
fn multiply(x: i32) -> i32 {
x * 2
}
let callback1 = use_callback(multiply, Some(1));
let ptr1 = Arc::as_ptr(&callback1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let callback2 = use_callback(multiply, Some(2));
let ptr2 = Arc::as_ptr(&callback2);
assert_ne!(ptr1, ptr2, "Callback should be recreated when deps change");
cleanup_test();
}
#[test]
fn test_use_callback_stable_with_same_deps() {
let fiber_id = setup_test_fiber();
fn double(x: i32) -> i32 {
x * 2
}
let callback1 = use_callback(double, Some(42));
let ptr1 = Arc::as_ptr(&callback1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let callback2 = use_callback(double, Some(42));
let ptr2 = Arc::as_ptr(&callback2);
assert_eq!(ptr1, ptr2, "Callback should be stable when deps unchanged");
assert_eq!(callback2(5), 10);
cleanup_test();
}
#[test]
fn test_use_callback_with_tuple_deps() {
let fiber_id = setup_test_fiber();
fn noop(_: ()) -> i32 {
1
}
let callback1 = use_callback(noop, Some((1, "hello")));
let ptr1 = Arc::as_ptr(&callback1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let callback2 = use_callback(noop, Some((1, "hello")));
let ptr2 = Arc::as_ptr(&callback2);
assert_eq!(ptr1, ptr2, "Same tuple deps should be stable");
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let callback3 = use_callback(noop, Some((2, "hello")));
let ptr3 = Arc::as_ptr(&callback3);
assert_ne!(ptr2, ptr3, "Different tuple deps should recreate");
cleanup_test();
}
#[test]
fn test_multiple_callbacks() {
let _fiber_id = setup_test_fiber();
let cb1 = use_callback(|x: i32| x + 1, None::<()>);
let cb2 = use_callback(|x: i32| x * 2, None::<()>);
let cb3 = use_callback(|s: &str| s.len(), None::<()>);
assert_eq!(cb1(5), 6);
assert_eq!(cb2(5), 10);
assert_eq!(cb3("hello"), 5);
cleanup_test();
}
#[test]
#[should_panic(expected = "use_callback must be called within a component render context")]
fn test_use_callback_panics_outside_render() {
clear_fiber_tree();
let _ = use_callback(|_: ()| {}, None::<()>);
}
#[test]
fn test_use_memo_basic() {
let _fiber_id = setup_test_fiber();
let value = use_memo(|| 42, None::<()>);
assert_eq!(value, 42);
cleanup_test();
}
#[test]
fn test_use_memo_stable_with_none_deps() {
let fiber_id = setup_test_fiber();
use std::sync::atomic::{AtomicUsize, Ordering};
let compute_count = Arc::new(AtomicUsize::new(0));
let cc1 = compute_count.clone();
let value1 = use_memo(
move || {
cc1.fetch_add(1, Ordering::SeqCst);
100
},
None::<()>,
);
assert_eq!(value1, 100);
assert_eq!(compute_count.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let cc2 = compute_count.clone();
let value2 = use_memo(
move || {
cc2.fetch_add(1, Ordering::SeqCst);
200
},
None::<()>,
);
assert_eq!(value2, 100); assert_eq!(compute_count.load(Ordering::SeqCst), 1);
cleanup_test();
}
#[test]
fn test_use_memo_recomputes_on_dep_change() {
let fiber_id = setup_test_fiber();
let value1 = use_memo(|| "first", Some(1));
assert_eq!(value1, "first");
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let value2 = use_memo(|| "second", Some(2));
assert_eq!(value2, "second");
cleanup_test();
}
#[test]
fn test_use_memo_stable_with_same_deps() {
let fiber_id = setup_test_fiber();
let _value1 = use_memo(|| vec![1, 2, 3], Some("key"));
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
let value2 = use_memo(|| vec![4, 5, 6], Some("key"));
assert_eq!(value2, vec![1, 2, 3]);
cleanup_test();
}
#[test]
#[should_panic(expected = "use_memo must be called within a component render context")]
fn test_use_memo_panics_outside_render() {
clear_fiber_tree();
let _ = use_memo(|| 42, None::<()>);
}
}