ferroui_base/data/core/plugins/
task_stream_plugin.rs1use super::IStreamPlugin;
2use crate::data::core::WeakValue;
3use crate::data::{BindingError, BindingErrorType, BindingNotification};
4use crate::reactive::{IObservable, LightweightSubject, Observable};
5use crate::threading::{Dispatcher, DispatcherPriority, FerroSynchronizationContext};
6use crate::{BoxedValue, PropertyValue};
7use std::cell::RefCell;
8use std::future::Future;
9use std::rc::Rc;
10
11#[derive(Clone)]
12enum TaskStatus {
13 Running,
14 RanToCompletion(Option<BoxedValue>),
15 Faulted(BindingError),
16}
17
18struct TaskShared {
19 status: RefCell<TaskStatus>,
20 continuations: RefCell<Vec<Box<dyn FnOnce()>>>,
21}
22
23impl TaskShared {
24 fn new(status: TaskStatus) -> Rc<Self> {
25 Rc::new(Self { status: RefCell::new(status), continuations: RefCell::new(Vec::new()) })
26 }
27
28 fn try_complete(&self, status: TaskStatus) -> bool {
29 if !matches!(*self.status.borrow(), TaskStatus::Running) {
30 return false;
31 }
32 self.status.replace(status);
33 let continuations = std::mem::take(&mut *self.continuations.borrow_mut());
34 for continuation in continuations {
35 continuation();
36 }
37 true
38 }
39}
40
41#[derive(Clone)]
50pub struct TaskValue(Rc<TaskShared>);
51
52impl TaskValue {
53 pub fn from_result<T: PropertyValue>(value: T) -> Self {
55 Self::from_untyped_result(Some(Rc::new(value)))
56 }
57
58 pub fn from_untyped_result(value: Option<BoxedValue>) -> Self {
61 Self(TaskShared::new(TaskStatus::RanToCompletion(value)))
62 }
63
64 pub fn from_exception(error: BindingError) -> Self {
66 Self(TaskShared::new(TaskStatus::Faulted(error)))
67 }
68
69 pub fn run<F>(future: F) -> Self
74 where
75 F: Future<Output = Result<Option<BoxedValue>, BindingError>> + 'static,
76 {
77 let task = Self(TaskShared::new(TaskStatus::Running));
78 let shared = task.0.clone();
79 drop(Dispatcher::current_dispatcher().invoke_async_task_local(move || async move {
80 let status = match future.await {
81 Ok(value) => TaskStatus::RanToCompletion(value),
82 Err(error) => TaskStatus::Faulted(error),
83 };
84 shared.try_complete(status);
85 }));
86 task
87 }
88
89 pub fn is_completed(&self) -> bool {
91 !matches!(*self.0.status.borrow(), TaskStatus::Running)
92 }
93
94 pub fn is_completed_successfully(&self) -> bool {
96 matches!(*self.0.status.borrow(), TaskStatus::RanToCompletion(_))
97 }
98
99 pub fn is_faulted(&self) -> bool {
101 matches!(*self.0.status.borrow(), TaskStatus::Faulted(_))
102 }
103
104 pub fn result(&self) -> Option<Result<Option<BoxedValue>, BindingError>> {
106 match self.0.status.borrow().clone() {
107 TaskStatus::Running => None,
108 TaskStatus::RanToCompletion(value) => Some(Ok(value)),
109 TaskStatus::Faulted(error) => Some(Err(error)),
110 }
111 }
112
113 pub fn continue_with(&self, continuation: impl FnOnce() + 'static) {
117 if self.is_completed() {
118 continuation();
119 } else {
120 self.0.continuations.borrow_mut().push(Box::new(continuation));
121 }
122 }
123}
124
125impl PartialEq for TaskValue {
126 fn eq(&self, other: &Self) -> bool {
127 Rc::ptr_eq(&self.0, &other.0)
128 }
129}
130
131pub struct TaskValueSource {
134 task: TaskValue,
135}
136
137impl Default for TaskValueSource {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl TaskValueSource {
144 pub fn new() -> Self {
145 Self { task: TaskValue(TaskShared::new(TaskStatus::Running)) }
146 }
147
148 pub fn task(&self) -> TaskValue {
150 self.task.clone()
151 }
152
153 pub fn set_result<T: PropertyValue>(&self, value: T) {
156 self.set_untyped_result(Some(Rc::new(value)));
157 }
158
159 pub fn set_untyped_result(&self, value: Option<BoxedValue>) {
162 if !self.try_set_untyped_result(value) {
163 panic!("An attempt was made to transition a task to a final state when it had already completed.");
164 }
165 }
166
167 pub fn set_exception(&self, error: BindingError) {
169 if !self.try_set_exception(error) {
170 panic!("An attempt was made to transition a task to a final state when it had already completed.");
171 }
172 }
173
174 pub fn try_set_untyped_result(&self, value: Option<BoxedValue>) -> bool {
177 self.task.0.try_complete(TaskStatus::RanToCompletion(value))
178 }
179
180 pub fn try_set_exception(&self, error: BindingError) -> bool {
182 self.task.0.try_complete(TaskStatus::Faulted(error))
183 }
184}
185
186pub struct TaskStreamPlugin;
192
193impl TaskStreamPlugin {
194 pub fn handle_completed(task: &TaskValue) -> Rc<dyn IObservable<Option<BoxedValue>>> {
197 match task.result() {
198 Some(Ok(value)) => Observable::return_(value),
199 Some(Err(error)) => Observable::return_(Some(
200 Rc::new(BindingNotification::with_error(error, BindingErrorType::Error)) as BoxedValue,
201 )),
202 None => panic!("handle_completed called for non-completed task."),
203 }
204 }
205}
206
207impl IStreamPlugin for TaskStreamPlugin {
208 fn match_(&self, reference: &WeakValue) -> bool {
209 reference.upgrade().is_some_and(|t| t.is::<TaskValue>())
210 }
211
212 fn start(&self, reference: &WeakValue) -> Option<Rc<dyn IObservable<Option<BoxedValue>>>> {
213 let target = reference.upgrade();
214 let Some(task) = target.as_ref().and_then(|t| t.downcast_ref::<TaskValue>()) else {
215 return Some(Observable::empty());
216 };
217 Some(Self::observe(task))
218 }
219}
220
221impl TaskStreamPlugin {
222 pub fn observe(task: &TaskValue) -> Rc<dyn IObservable<Option<BoxedValue>>> {
226 if task.is_completed() {
227 return Self::handle_completed(task);
228 }
229
230 let subject = LightweightSubject::<Option<BoxedValue>>::new();
233 let dispatcher = Dispatcher::current_dispatcher();
234 let priority = FerroSynchronizationContext::current().map_or(DispatcherPriority::NORMAL, |c| c.priority());
235 let completed = task.clone();
236 let observer = subject.clone();
237 task.continue_with(move || {
238 dispatcher.clone().post_local(
239 move || {
240 Self::handle_completed(&completed).subscribe(Rc::new(observer));
241 },
242 priority,
243 );
244 });
245 Rc::new(subject)
246 }
247}