1use crate::concurrency::{IScheduler, ImmediateScheduler, SchedulerExt};
13use crate::disposables::{CancellationDisposable, SingleAssignmentDisposableValue, StableCompositeDisposable};
14use crate::linq::{Observable, SequenceError};
15use crate::ObservableBase;
16use ferroui_system::threading::tasks::{Task, TaskCompletionSource};
17use ferroui_system::threading::{CancellationToken, LocalCancellationTokenRegistration};
18use ferroui_system::{IDisposable, IObservable, IObserver, ObservableError};
19use std::cell::{Cell, RefCell};
20use std::future::Future;
21use std::rc::Rc;
22
23fn emit_task_result<T: Clone + 'static>(task: &Task<T>, observer: &Rc<dyn IObserver<T>>) {
25 match task.result() {
26 Ok(value) => {
27 observer.on_next(value);
28 observer.on_completed();
29 }
30 Err(error) => observer.on_error(error),
31 }
32}
33
34struct SlowTaskObservable<T> {
37 task: Task<T>,
38 scheduler: Option<Rc<dyn IScheduler>>,
39}
40
41struct RemoveContinuation<T> {
44 task: Task<T>,
45 id: Cell<Option<ferroui_system::threading::tasks::ContinuationId>>,
46}
47
48impl<T: 'static> IDisposable for RemoveContinuation<T> {
49 fn dispose(&self) {
50 if let Some(id) = self.id.take() {
51 self.task.remove_continuation(id);
52 }
53 }
54}
55
56impl<T: Clone + 'static> IObservable<T> for SlowTaskObservable<T> {
57 fn subscribe(&self, observer: Rc<dyn IObserver<T>>) -> Rc<dyn IDisposable> {
58 let id = match &self.scheduler {
59 None => self.task.continue_with(move |task| emit_task_result(task, &observer)),
60 Some(scheduler) => {
61 let scheduler = scheduler.clone();
62 self.task.continue_with(move |task| {
63 let task = task.clone();
64 scheduler.schedule_action(move || emit_task_result(&task, &observer));
65 })
66 }
67 };
68 Rc::new(RemoveContinuation { task: self.task.clone(), id: Cell::new(id) })
69 }
70}
71
72pub(crate) fn task_to_observable<T: Clone + 'static>(task: Task<T>, scheduler: Option<Rc<dyn IScheduler>>) -> Rc<dyn IObservable<T>> {
75 if task.is_completed() {
76 let scheduler = scheduler.unwrap_or_else(ImmediateScheduler::instance);
77 return match task.result() {
78 Ok(value) => Observable::return_on(value, scheduler),
79 Err(error) => Observable::throw_on(error, scheduler),
80 };
81 }
82 Rc::new(SlowTaskObservable { task, scheduler })
83}
84
85pub trait TaskObservableExt<T> {
87 fn to_observable(&self) -> Rc<dyn IObservable<T>>;
90
91 fn to_observable_on(&self, scheduler: Rc<dyn IScheduler>) -> Rc<dyn IObservable<T>>;
94}
95
96impl<T: Clone + 'static> TaskObservableExt<T> for Task<T> {
97 fn to_observable(&self) -> Rc<dyn IObservable<T>> {
98 task_to_observable(self.clone(), None)
99 }
100
101 fn to_observable_on(&self, scheduler: Rc<dyn IScheduler>) -> Rc<dyn IObservable<T>> {
102 task_to_observable(self.clone(), Some(scheduler))
103 }
104}
105
106struct ToTaskObserver<T> {
109 source: TaskCompletionSource<T>,
110 registration: RefCell<Option<LocalCancellationTokenRegistration>>,
111 last_value: RefCell<Option<T>>,
112 resource: SingleAssignmentDisposableValue,
113 is_stopped: Cell<bool>,
114}
115
116impl<T: 'static> ToTaskObserver<T> {
117 fn dispose_registration(&self) {
118 let registration = self.registration.borrow_mut().take();
119 if let Some(registration) = registration {
120 registration.dispose();
121 }
122 }
123
124 fn cancel(&self) {
126 self.resource.dispose();
127 self.source.try_set_canceled();
128 }
129}
130
131impl<T: 'static> IObserver<T> for ToTaskObserver<T> {
132 fn on_next(&self, value: T) {
133 if self.is_stopped.get() {
134 return;
135 }
136 *self.last_value.borrow_mut() = Some(value);
137 }
138
139 fn on_error(&self, error: ObservableError) {
140 if self.is_stopped.replace(true) {
141 return;
142 }
143 self.source.try_set_exception(error);
144 self.dispose_registration();
145 self.resource.dispose();
146 }
147
148 fn on_completed(&self) {
149 if self.is_stopped.replace(true) {
150 return;
151 }
152 let last = self.last_value.borrow_mut().take();
153 match last {
154 Some(value) => {
155 self.source.try_set_result(value);
156 }
157 None => {
158 self.source.try_set_exception(Rc::new(SequenceError::NoElements));
159 }
160 }
161 self.dispose_registration();
162 self.resource.dispose();
163 }
164}
165
166pub(crate) fn to_task<T: 'static>(source: Rc<dyn IObservable<T>>, token: CancellationToken) -> Task<T> {
168 let completion = TaskCompletionSource::new();
169 let observer = Rc::new(ToTaskObserver {
170 source: completion.clone(),
171 registration: RefCell::new(None),
172 last_value: RefCell::new(None),
173 resource: SingleAssignmentDisposableValue::new(),
174 is_stopped: Cell::new(false),
175 });
176 if token.can_be_canceled() {
177 let target = observer.clone();
181 let registration = token.register_local(move || target.cancel());
182 *observer.registration.borrow_mut() = Some(registration);
183 }
184 let subscription = source.subscribe(observer.clone());
185 observer.resource.set_disposable(Some(subscription));
186 completion.task()
187}
188
189pub(crate) fn continue_on_scheduler<T: Clone + 'static>(task: Task<T>, scheduler: Rc<dyn IScheduler>) -> Task<T> {
191 let completion = TaskCompletionSource::new();
192 let result = completion.task();
193 task.continue_with(move |task| {
194 let task = task.clone();
195 scheduler.schedule_action(move || {
196 if task.is_canceled() {
197 completion.try_set_canceled();
198 } else {
199 match task.result() {
200 Ok(value) => {
201 completion.try_set_result(value);
202 }
203 Err(error) => {
204 completion.try_set_exception(error);
205 }
206 }
207 }
208 });
209 });
210 result
211}
212
213pub(crate) fn start_async<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
215 future: Fut,
216 scheduler: Option<Rc<dyn IScheduler>>,
217) -> Rc<dyn IObservable<T>> {
218 task_to_observable(Task::spawn_local(future), scheduler)
219}
220
221pub(crate) fn start_async_with_token<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
224 function_async: impl FnOnce(CancellationToken) -> Fut,
225 scheduler: Option<Rc<dyn IScheduler>>,
226) -> Rc<dyn IObservable<T>> {
227 let cancellable = CancellationDisposable::new();
228 let result = task_to_observable(Task::spawn_local(function_async(cancellable.token())), scheduler);
229 Rc::new(ObservableBase::from_fn(move |observer: Rc<dyn IObserver<T>>| {
230 let subscription = result.subscribe(observer);
231 let cancellable: Rc<dyn IDisposable> = cancellable.clone();
232 let composite: Rc<dyn IDisposable> = StableCompositeDisposable::create(cancellable, subscription);
233 composite
234 }))
235}
236
237impl Observable {
238 pub fn start_async<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
242 function_async: impl FnOnce() -> Fut,
243 ) -> Rc<dyn IObservable<T>> {
244 start_async(function_async(), None)
245 }
246
247 pub fn start_async_on<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
250 function_async: impl FnOnce() -> Fut,
251 scheduler: Rc<dyn IScheduler>,
252 ) -> Rc<dyn IObservable<T>> {
253 start_async(function_async(), Some(scheduler))
254 }
255
256 pub fn start_async_with_token<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
259 function_async: impl FnOnce(CancellationToken) -> Fut,
260 ) -> Rc<dyn IObservable<T>> {
261 start_async_with_token(function_async, None)
262 }
263
264 pub fn start_async_with_token_on<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
267 function_async: impl FnOnce(CancellationToken) -> Fut,
268 scheduler: Rc<dyn IScheduler>,
269 ) -> Rc<dyn IObservable<T>> {
270 start_async_with_token(function_async, Some(scheduler))
271 }
272
273 pub fn from_async<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
277 function_async: impl Fn() -> Fut + 'static,
278 ) -> Rc<dyn IObservable<T>> {
279 Observable::defer(move || start_async(function_async(), None))
280 }
281
282 pub fn from_async_on<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
285 function_async: impl Fn() -> Fut + 'static,
286 scheduler: Rc<dyn IScheduler>,
287 ) -> Rc<dyn IObservable<T>> {
288 Observable::defer(move || start_async(function_async(), Some(scheduler.clone())))
289 }
290
291 pub fn from_async_with_token<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
294 function_async: impl Fn(CancellationToken) -> Fut + 'static,
295 ) -> Rc<dyn IObservable<T>> {
296 Observable::defer(move || start_async_with_token(&function_async, None))
297 }
298
299 pub fn from_async_with_token_on<T: Clone + 'static, Fut: Future<Output = Result<T, ObservableError>> + 'static>(
302 function_async: impl Fn(CancellationToken) -> Fut + 'static,
303 scheduler: Rc<dyn IScheduler>,
304 ) -> Rc<dyn IObservable<T>> {
305 Observable::defer(move || start_async_with_token(&function_async, Some(scheduler.clone())))
306 }
307}
308
309macro_rules! task_observable_methods {
310 () => {
311 fn to_task(&self) -> ferroui_system::threading::tasks::Task<T> {
314 crate::linq::task_observable::to_task(self.observable(), ferroui_system::threading::CancellationToken::none())
315 }
316
317 fn to_task_with_token(&self, token: ferroui_system::threading::CancellationToken) -> ferroui_system::threading::tasks::Task<T> {
320 crate::linq::task_observable::to_task(self.observable(), token)
321 }
322
323 fn to_task_on(&self, scheduler: std::rc::Rc<dyn crate::concurrency::IScheduler>) -> ferroui_system::threading::tasks::Task<T>
326 where
327 T: Clone,
328 {
329 let task = crate::linq::task_observable::to_task(self.observable(), ferroui_system::threading::CancellationToken::none());
330 crate::linq::task_observable::continue_on_scheduler(task, scheduler)
331 }
332
333 fn to_task_with_token_on(
336 &self,
337 token: ferroui_system::threading::CancellationToken,
338 scheduler: std::rc::Rc<dyn crate::concurrency::IScheduler>,
339 ) -> ferroui_system::threading::tasks::Task<T>
340 where
341 T: Clone,
342 {
343 let task = crate::linq::task_observable::to_task(self.observable(), token);
344 crate::linq::task_observable::continue_on_scheduler(task, scheduler)
345 }
346 };
347}
348pub(crate) use task_observable_methods;