use std::future::Future;
use std::pin::Pin;
use crate::fiber::{AsyncCleanupFn, AsyncPendingEffect, PendingEffect};
use crate::fiber_tree::with_current_fiber;
use crate::scheduler::effect_queue::{
queue_async_cleanup, queue_async_effect, queue_cleanup, queue_effect,
};
pub fn use_effect<Deps, F, C>(effect: F, deps: Option<Deps>)
where
Deps: PartialEq + Clone + Send + 'static,
F: FnOnce() -> Option<C> + 'static,
C: FnOnce() + Send + 'static,
{
with_current_fiber(|fiber| {
fiber.track_hook_call("use_effect");
let hook_index = fiber.next_hook_index();
let prev_deps: Option<Option<Deps>> = fiber.get_hook(hook_index);
let should_run = match (&deps, &prev_deps) {
(None, _) => true,
(Some(_), None) => true,
(Some(current_deps), Some(Some(prev_deps))) => current_deps != prev_deps,
(Some(_), Some(None)) => true,
};
if should_run {
if let Some(cleanup) = fiber.cleanup_by_hook.remove(&hook_index) {
queue_cleanup(cleanup);
}
let fiber_id = fiber.id;
let wrapped_effect = Box::new(move || {
let cleanup_opt = effect();
cleanup_opt.map(|c| Box::new(c) as crate::fiber::CleanupFn)
});
queue_effect(
fiber_id,
PendingEffect {
effect: wrapped_effect,
hook_index,
},
);
}
fiber.set_hook(hook_index, deps);
});
}
pub fn use_effect_once<F, C>(effect: F)
where
F: FnOnce() -> Option<C> + 'static,
C: FnOnce() + Send + 'static,
{
use_effect(effect, Some(()));
}
pub fn use_async_effect<Deps, F, Fut, C, CFut>(effect: F, deps: Option<Deps>)
where
Deps: PartialEq + Clone + Send + 'static,
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = Option<C>> + Send + 'static,
C: FnOnce() -> CFut + Send + 'static,
CFut: Future<Output = ()> + Send + 'static,
{
with_current_fiber(|fiber| {
fiber.track_hook_call("use_async_effect");
let hook_index = fiber.next_hook_index();
let prev_deps: Option<Option<Deps>> = fiber.get_hook(hook_index);
let should_run = match (&deps, &prev_deps) {
(None, _) => true,
(Some(_), None) => true,
(Some(current_deps), Some(Some(prev_deps))) => current_deps != prev_deps,
(Some(_), Some(None)) => true,
};
if should_run {
if let Some(async_cleanup) = fiber.async_cleanup_by_hook.remove(&hook_index) {
queue_async_cleanup(async_cleanup);
}
let fiber_id = fiber.id;
let wrapped_effect: crate::fiber::AsyncEffectFn = Box::new(move || {
Box::pin(async move {
let cleanup_opt = effect().await;
cleanup_opt.map(|c| {
Box::new(move || Box::pin(c()) as Pin<Box<dyn Future<Output = ()> + Send>>)
as AsyncCleanupFn
})
})
});
queue_async_effect(
fiber_id,
AsyncPendingEffect {
effect: wrapped_effect,
hook_index,
},
);
}
fiber.set_hook(hook_index, deps);
});
}
pub fn use_async_effect_once<F, Fut, C, CFut>(effect: F)
where
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = Option<C>> + Send + 'static,
C: FnOnce() -> CFut + Send + 'static,
CFut: Future<Output = ()> + Send + 'static,
{
use_async_effect(effect, Some(()));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fiber_tree::{FiberTree, clear_fiber_tree, set_fiber_tree};
use crate::scheduler::effect_queue::{
clear_effect_queue, flush_effects_with_tree, has_pending_effects,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn setup_test_fiber() -> crate::fiber::FiberId {
clear_effect_queue();
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();
clear_effect_queue();
}
#[test]
fn test_effect_queued_not_executed_immediately() {
let _fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some(()),
);
assert_eq!(executed.load(Ordering::SeqCst), 0);
assert!(has_pending_effects());
cleanup_test();
}
#[test]
fn test_effect_runs_on_flush() {
let _fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some(()),
);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
cleanup_test();
}
#[test]
fn test_effect_with_empty_deps_runs_once() {
let fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some(()), );
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some(()), );
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
cleanup_test();
}
#[test]
fn test_effect_with_none_deps_runs_every_render() {
let fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
None::<()>, );
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
None::<()>,
);
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 2);
cleanup_test();
}
#[test]
fn test_effect_with_changing_deps() {
let fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some((1i32,)),
);
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some((1i32,)), );
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_effect(
move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
},
Some((2i32,)), );
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 2);
cleanup_test();
}
#[test]
fn test_use_effect_once() {
let fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
{
let executed_clone = executed.clone();
use_effect_once(move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
});
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_effect_once(move || {
executed_clone.fetch_add(1, Ordering::SeqCst);
Option::<fn()>::None
});
}
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
flush_effects_with_tree(tree);
});
assert_eq!(executed.load(Ordering::SeqCst), 1);
cleanup_test();
}
#[tokio::test]
async fn test_async_effect_queued_not_executed_immediately() {
let _fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
let executed_clone = executed.clone();
use_async_effect(
move || {
let executed = executed_clone.clone();
async move {
executed.fetch_add(1, Ordering::SeqCst);
Option::<fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>>::None
}
},
Some(()),
);
assert_eq!(executed.load(Ordering::SeqCst), 0);
assert!(crate::scheduler::effect_queue::has_pending_async_effects());
cleanup_test();
}
#[tokio::test]
async fn test_async_effect_with_empty_deps_runs_once() {
let fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
{
let executed_clone = executed.clone();
use_async_effect(
move || {
let executed = executed_clone.clone();
async move {
executed.fetch_add(1, Ordering::SeqCst);
Option::<
fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
>::None
}
},
Some(()), );
}
assert!(crate::scheduler::effect_queue::has_pending_async_effects());
let pending =
crate::scheduler::effect_queue::with_effect_queue(|queue| queue.drain_async_effects());
for (_fiber_id, pending_effect) in pending {
let future = (pending_effect.effect)();
future.await;
}
assert_eq!(executed.load(Ordering::SeqCst), 1);
crate::fiber_tree::with_fiber_tree_mut(|tree| {
tree.end_render();
tree.begin_render(fiber_id);
});
{
let executed_clone = executed.clone();
use_async_effect(
move || {
let executed = executed_clone.clone();
async move {
executed.fetch_add(1, Ordering::SeqCst);
Option::<
fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
>::None
}
},
Some(()), );
}
assert!(!crate::scheduler::effect_queue::has_pending_async_effects());
assert_eq!(executed.load(Ordering::SeqCst), 1);
cleanup_test();
}
#[test]
fn test_use_async_effect_once() {
let _fiber_id = setup_test_fiber();
let executed = Arc::new(AtomicUsize::new(0));
let executed_clone = executed.clone();
use_async_effect_once(move || {
let executed = executed_clone.clone();
async move {
executed.fetch_add(1, Ordering::SeqCst);
Option::<fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>>::None
}
});
assert!(crate::scheduler::effect_queue::has_pending_async_effects());
cleanup_test();
}
}