use super::IStreamPlugin;
use crate::data::core::WeakValue;
use crate::data::{BindingError, BindingErrorType, BindingNotification};
use crate::reactive::{IObservable, LightweightSubject, Observable};
use crate::threading::{Dispatcher, DispatcherPriority, FerroSynchronizationContext};
use crate::{BoxedValue, PropertyValue};
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
#[derive(Clone)]
enum TaskStatus {
Running,
RanToCompletion(Option<BoxedValue>),
Faulted(BindingError),
}
struct TaskShared {
status: RefCell<TaskStatus>,
continuations: RefCell<Vec<Box<dyn FnOnce()>>>,
}
impl TaskShared {
fn new(status: TaskStatus) -> Rc<Self> {
Rc::new(Self { status: RefCell::new(status), continuations: RefCell::new(Vec::new()) })
}
fn try_complete(&self, status: TaskStatus) -> bool {
if !matches!(*self.status.borrow(), TaskStatus::Running) {
return false;
}
self.status.replace(status);
let continuations = std::mem::take(&mut *self.continuations.borrow_mut());
for continuation in continuations {
continuation();
}
true
}
}
#[derive(Clone)]
pub struct TaskValue(Rc<TaskShared>);
impl TaskValue {
pub fn from_result<T: PropertyValue>(value: T) -> Self {
Self::from_untyped_result(Some(Rc::new(value)))
}
pub fn from_untyped_result(value: Option<BoxedValue>) -> Self {
Self(TaskShared::new(TaskStatus::RanToCompletion(value)))
}
pub fn from_exception(error: BindingError) -> Self {
Self(TaskShared::new(TaskStatus::Faulted(error)))
}
pub fn run<F>(future: F) -> Self
where
F: Future<Output = Result<Option<BoxedValue>, BindingError>> + 'static,
{
let task = Self(TaskShared::new(TaskStatus::Running));
let shared = task.0.clone();
drop(Dispatcher::current_dispatcher().invoke_async_task_local(move || async move {
let status = match future.await {
Ok(value) => TaskStatus::RanToCompletion(value),
Err(error) => TaskStatus::Faulted(error),
};
shared.try_complete(status);
}));
task
}
pub fn is_completed(&self) -> bool {
!matches!(*self.0.status.borrow(), TaskStatus::Running)
}
pub fn is_completed_successfully(&self) -> bool {
matches!(*self.0.status.borrow(), TaskStatus::RanToCompletion(_))
}
pub fn is_faulted(&self) -> bool {
matches!(*self.0.status.borrow(), TaskStatus::Faulted(_))
}
pub fn result(&self) -> Option<Result<Option<BoxedValue>, BindingError>> {
match self.0.status.borrow().clone() {
TaskStatus::Running => None,
TaskStatus::RanToCompletion(value) => Some(Ok(value)),
TaskStatus::Faulted(error) => Some(Err(error)),
}
}
pub fn continue_with(&self, continuation: impl FnOnce() + 'static) {
if self.is_completed() {
continuation();
} else {
self.0.continuations.borrow_mut().push(Box::new(continuation));
}
}
}
impl PartialEq for TaskValue {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
pub struct TaskValueSource {
task: TaskValue,
}
impl Default for TaskValueSource {
fn default() -> Self {
Self::new()
}
}
impl TaskValueSource {
pub fn new() -> Self {
Self { task: TaskValue(TaskShared::new(TaskStatus::Running)) }
}
pub fn task(&self) -> TaskValue {
self.task.clone()
}
pub fn set_result<T: PropertyValue>(&self, value: T) {
self.set_untyped_result(Some(Rc::new(value)));
}
pub fn set_untyped_result(&self, value: Option<BoxedValue>) {
if !self.try_set_untyped_result(value) {
panic!("An attempt was made to transition a task to a final state when it had already completed.");
}
}
pub fn set_exception(&self, error: BindingError) {
if !self.try_set_exception(error) {
panic!("An attempt was made to transition a task to a final state when it had already completed.");
}
}
pub fn try_set_untyped_result(&self, value: Option<BoxedValue>) -> bool {
self.task.0.try_complete(TaskStatus::RanToCompletion(value))
}
pub fn try_set_exception(&self, error: BindingError) -> bool {
self.task.0.try_complete(TaskStatus::Faulted(error))
}
}
pub struct TaskStreamPlugin;
impl TaskStreamPlugin {
pub fn handle_completed(task: &TaskValue) -> Rc<dyn IObservable<Option<BoxedValue>>> {
match task.result() {
Some(Ok(value)) => Observable::return_(value),
Some(Err(error)) => Observable::return_(Some(
Rc::new(BindingNotification::with_error(error, BindingErrorType::Error)) as BoxedValue,
)),
None => panic!("handle_completed called for non-completed task."),
}
}
}
impl IStreamPlugin for TaskStreamPlugin {
fn match_(&self, reference: &WeakValue) -> bool {
reference.upgrade().is_some_and(|t| t.is::<TaskValue>())
}
fn start(&self, reference: &WeakValue) -> Option<Rc<dyn IObservable<Option<BoxedValue>>>> {
let target = reference.upgrade();
let Some(task) = target.as_ref().and_then(|t| t.downcast_ref::<TaskValue>()) else {
return Some(Observable::empty());
};
Some(Self::observe(task))
}
}
impl TaskStreamPlugin {
pub fn observe(task: &TaskValue) -> Rc<dyn IObservable<Option<BoxedValue>>> {
if task.is_completed() {
return Self::handle_completed(task);
}
let subject = LightweightSubject::<Option<BoxedValue>>::new();
let dispatcher = Dispatcher::current_dispatcher();
let priority = FerroSynchronizationContext::current().map_or(DispatcherPriority::NORMAL, |c| c.priority());
let completed = task.clone();
let observer = subject.clone();
task.continue_with(move || {
dispatcher.clone().post_local(
move || {
Self::handle_completed(&completed).subscribe(Rc::new(observer));
},
priority,
);
});
Rc::new(subject)
}
}