use std::{
cell::{
Cell,
RefCell,
},
hash::{
Hash,
Hasher,
},
mem::take,
rc::{
Rc,
Weak,
},
sync::{
Arc,
atomic::Ordering,
},
task::{
Context,
Waker,
},
};
use futures_channel::mpsc::UnboundedSender;
use futures_util::{
future::LocalBoxFuture,
task::{
ArcWake,
waker,
},
};
use rustc_hash::FxHashMap;
use crate::{
current_context::CurrentContext,
lifecycle::global_context::GlobalContexts,
prelude::current_scope_id,
runner::Message,
scope_id::ScopeId,
};
pub fn spawn_global(future: impl Future<Output = ()> + 'static) -> TaskHandle {
if let Some(tasks) = GlobalTasks::try_get() {
tasks.spawn(future)
} else {
spawn_in_window(future)
}
}
pub fn spawn_in_window(future: impl Future<Output = ()> + 'static) -> TaskHandle {
spawn_in_scope(future, ScopeId::ROOT)
}
pub fn spawn(future: impl Future<Output = ()> + 'static) -> TaskHandle {
spawn_in_scope(future, None)
}
pub fn spawn_in_scope(
future: impl Future<Output = ()> + 'static,
scope_id: impl Into<Option<ScopeId>>,
) -> TaskHandle {
let scope_id = scope_id.into().unwrap_or_else(current_scope_id);
CurrentContext::with(|context| {
let task_id = TaskId(context.task_id_counter.fetch_add(1, Ordering::Relaxed));
context.tasks.borrow_mut().insert(
task_id,
Rc::new(RefCell::new(Task {
scope_id,
future: Box::pin(future),
waker: waker(Arc::new(TaskWaker {
task_id,
sender: context.sender.clone(),
})),
})),
);
context
.sender
.unbounded_send(Message::PollTask(task_id))
.unwrap();
TaskHandle {
task_id,
owner: TaskOwner::Window,
global_tasks: Weak::new(),
}
})
}
#[derive(Clone, Debug)]
pub struct TaskHandle {
task_id: TaskId,
owner: TaskOwner,
global_tasks: Weak<InnerGlobalTasks>,
}
impl PartialEq for TaskHandle {
fn eq(&self, other: &Self) -> bool {
self.task_id == other.task_id
&& self.owner == other.owner
&& Weak::ptr_eq(&self.global_tasks, &other.global_tasks)
}
}
impl Eq for TaskHandle {}
impl Hash for TaskHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.task_id.hash(state);
self.owner.hash(state);
self.global_tasks.as_ptr().hash(state);
}
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
enum TaskOwner {
Window,
Global,
}
impl TaskHandle {
pub fn cancel(&self) {
if self.owner == TaskOwner::Window {
CurrentContext::with(|context| context.tasks.borrow_mut().remove(&self.task_id));
} else {
self.try_cancel();
}
}
pub fn try_cancel(&self) {
if self.owner == TaskOwner::Window {
CurrentContext::try_with(|context| context.tasks.borrow_mut().remove(&self.task_id));
} else if let Some(tasks) = self.global_tasks.upgrade() {
tasks.cancel(self.task_id);
}
}
pub fn is_finished(&self) -> bool {
if self.owner == TaskOwner::Window {
CurrentContext::with(|context| !context.tasks.borrow().contains_key(&self.task_id))
} else {
self.global_tasks
.upgrade()
.is_none_or(|tasks| !tasks.tasks.borrow().contains_key(&self.task_id))
}
}
pub fn owned(self) -> OwnedTaskHandle {
OwnedTaskHandle(Rc::new(InnerOwnedTaskHandle(self)))
}
}
struct InnerOwnedTaskHandle(TaskHandle);
impl Drop for InnerOwnedTaskHandle {
fn drop(&mut self) {
self.0.try_cancel();
}
}
#[derive(Clone)]
pub struct OwnedTaskHandle(Rc<InnerOwnedTaskHandle>);
impl PartialEq for OwnedTaskHandle {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl OwnedTaskHandle {
pub fn cancel(&self) {
self.0.0.cancel();
}
pub fn try_cancel(&self) {
self.0.0.try_cancel();
}
pub fn is_finished(&self) -> bool {
self.0.0.is_finished()
}
pub fn downgrade(&self) -> TaskHandle {
self.0.0.clone()
}
}
pub struct TaskWaker {
task_id: TaskId,
sender: UnboundedSender<Message>,
}
impl ArcWake for TaskWaker {
fn wake_by_ref(arc_self: &Arc<Self>) {
_ = arc_self
.sender
.unbounded_send(Message::PollTask(arc_self.task_id));
}
}
pub struct Task {
pub scope_id: ScopeId,
pub future: LocalBoxFuture<'static, ()>,
pub waker: Waker,
}
#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
pub struct TaskId(u64);
#[derive(Clone)]
pub struct GlobalTasks {
inner: Rc<InnerGlobalTasks>,
}
struct InnerGlobalTasks {
tasks: RefCell<FxHashMap<TaskId, Rc<RefCell<LocalBoxFuture<'static, ()>>>>>,
next_id: Cell<u64>,
waker: Waker,
}
impl InnerGlobalTasks {
fn cancel(&self, task_id: TaskId) {
let _removed_task = self.tasks.borrow_mut().remove(&task_id);
}
}
impl GlobalTasks {
#[track_caller]
pub fn get() -> Self {
Self::try_get().expect("Global tasks are unavailable outside the event loop.")
}
pub fn try_get() -> Option<Self> {
GlobalContexts::try_get().and_then(|contexts| contexts.try_get_context::<Self>())
}
pub fn new(waker: Waker) -> Self {
Self {
inner: Rc::new(InnerGlobalTasks {
tasks: RefCell::default(),
next_id: Cell::default(),
waker,
}),
}
}
pub fn spawn(&self, future: impl Future<Output = ()> + 'static) -> TaskHandle {
let task_id = TaskId(self.inner.next_id.get());
self.inner.next_id.set(self.inner.next_id.get() + 1);
self.inner
.tasks
.borrow_mut()
.insert(task_id, Rc::new(RefCell::new(Box::pin(future))));
self.inner.waker.wake_by_ref();
TaskHandle {
task_id,
owner: TaskOwner::Global,
global_tasks: Rc::downgrade(&self.inner),
}
}
pub fn clear(&self) {
let _removed_tasks = take(&mut *self.inner.tasks.borrow_mut());
}
pub fn poll(&self) {
let task_ids: Vec<_> = self.inner.tasks.borrow().keys().copied().collect();
let mut context = Context::from_waker(&self.inner.waker);
for task_id in task_ids {
let Some(future) = self.inner.tasks.borrow().get(&task_id).cloned() else {
continue;
};
let poll_result = future.borrow_mut().as_mut().poll(&mut context);
if poll_result.is_ready() {
self.inner.cancel(task_id);
}
}
}
}