use super::{IInteraction, IInteractionContext, IOutputContext, InteractionContext, UnhandledInteractionException};
use crate::PanicException;
use ferroui_reactive::concurrency::{IScheduler, SchedulerExt};
use ferroui_reactive::disposables::Disposable;
use ferroui_reactive::linq::Observable;
use ferroui_system::threading::tasks::{Task, TaskCompletionSource};
use ferroui_system::{IDisposable, IObservable, IObserver, ObservableError};
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::rc::Rc;
pub type InteractionHandler<I, O> = Rc<dyn Fn(Rc<dyn IInteractionContext<I, O>>) -> Task<()>>;
pub struct Interaction<I, O> {
inner: Rc<InteractionInner<I, O>>,
}
struct InteractionInner<I, O> {
handler_scheduler: Option<Rc<dyn IScheduler>>,
handlers: RefCell<Rc<Vec<InteractionHandler<I, O>>>>,
}
impl<I, O> Clone for Interaction<I, O> {
fn clone(&self) -> Self {
Self { inner: self.inner.clone() }
}
}
impl<I, O> PartialEq for Interaction<I, O> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl<I, O> fmt::Debug for Interaction<I, O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Interaction").field("handlers", &self.inner.handlers.borrow().len()).finish()
}
}
impl<I: Clone + 'static, O: Clone + 'static> Default for Interaction<I, O> {
fn default() -> Self {
Self::new()
}
}
impl<I: Clone + 'static, O: Clone + 'static> Interaction<I, O> {
pub fn new() -> Self {
Self::with_scheduler(None)
}
pub fn with_scheduler(handler_scheduler: Option<Rc<dyn IScheduler>>) -> Self {
Self { inner: Rc::new(InteractionInner { handler_scheduler, handlers: RefCell::new(Rc::new(Vec::new())) }) }
}
pub fn register_handler(&self, handler: impl Fn(Rc<dyn IInteractionContext<I, O>>) + 'static) -> Rc<dyn IDisposable> {
self.add_handler(Rc::new(move |context| {
handler(context);
Task::from_result(())
}))
}
pub fn register_handler_task<Fut: Future<Output = Result<(), ObservableError>> + 'static>(
&self,
handler: impl Fn(Rc<dyn IInteractionContext<I, O>>) -> Fut + 'static,
) -> Rc<dyn IDisposable> {
self.add_handler(Rc::new(move |context| Task::spawn_local(handler(context))))
}
pub fn register_handler_observable<D: 'static>(
&self,
handler: impl Fn(Rc<dyn IInteractionContext<I, O>>) -> Rc<dyn IObservable<D>> + 'static,
) -> Rc<dyn IDisposable> {
self.add_handler(Rc::new(move |context| {
let completion = TaskCompletionSource::new();
let _ = handler(context).subscribe(Rc::new(ObservableToTaskObserver { completion: completion.clone() }));
completion.task()
}))
}
pub fn handle(&self, input: I) -> Task<O> {
let this = self.clone();
Task::spawn_local(async move {
let context = this.generate_context(input.clone());
let handlers = this.inner.handlers.borrow().clone();
for handler in handlers.iter().rev() {
this.invoke_handler(handler, &context).await?;
if context.is_handled() {
return Ok(context.get_output());
}
}
Err(Rc::new(UnhandledInteractionException::new(this.clone(), input)) as ObservableError)
})
}
pub fn when_handled(&self, input: I) -> Rc<dyn IObservable<O>> {
let this = self.clone();
Observable::from_async(move || this.handle(input.clone()))
}
pub fn get_handlers(&self) -> Vec<InteractionHandler<I, O>> {
self.inner.handlers.borrow().iter().cloned().collect()
}
fn generate_context(&self, input: I) -> Rc<dyn IOutputContext<I, O>> {
Rc::new(InteractionContext::new(input))
}
fn invoke_handler(&self, handler: &InteractionHandler<I, O>, context: &Rc<dyn IOutputContext<I, O>>) -> Task<()> {
let handler_context: Rc<dyn IInteractionContext<I, O>> = context.clone();
let Some(scheduler) = &self.inner.handler_scheduler else {
return run_handler(handler, handler_context);
};
let completion = TaskCompletionSource::new();
let started = completion.clone();
let handler = handler.clone();
let _ = scheduler.schedule_action(move || {
let running = run_handler(&handler, handler_context);
let _ = running.continue_with(move |running| {
if running.is_canceled() {
started.try_set_canceled();
return;
}
match running.result() {
Ok(()) => {
started.try_set_result(());
}
Err(error) => {
started.try_set_exception(error);
}
}
});
});
completion.task()
}
fn add_handler(&self, handler: InteractionHandler<I, O>) -> Rc<dyn IDisposable> {
{
let mut handlers = self.inner.handlers.borrow_mut();
let mut updated: Vec<_> = handlers.iter().cloned().collect();
updated.push(handler.clone());
*handlers = Rc::new(updated);
}
let inner = Rc::downgrade(&self.inner);
Disposable::create(move || {
let Some(inner) = inner.upgrade() else { return };
let mut handlers = inner.handlers.borrow_mut();
if let Some(index) = handlers.iter().position(|registered| Rc::ptr_eq(registered, &handler)) {
let mut updated: Vec<_> = handlers.iter().cloned().collect();
updated.remove(index);
*handlers = Rc::new(updated);
}
})
}
}
fn run_handler<I, O>(handler: &InteractionHandler<I, O>, context: Rc<dyn IInteractionContext<I, O>>) -> Task<()> {
match PanicException::catch(|| handler(context)) {
Ok(task) => task,
Err((error, _)) => Task::from_exception(error),
}
}
impl<I: Clone + 'static, O: Clone + 'static> IInteraction<I, O> for Interaction<I, O> {
fn register_handler(&self, handler: Rc<dyn Fn(Rc<dyn IInteractionContext<I, O>>)>) -> Rc<dyn IDisposable> {
Interaction::register_handler(self, move |context| handler(context))
}
fn register_handler_task(&self, handler: Rc<dyn Fn(Rc<dyn IInteractionContext<I, O>>) -> Task<()>>) -> Rc<dyn IDisposable> {
self.add_handler(Rc::new(move |context| handler(context)))
}
fn register_handler_observable(
&self,
handler: Rc<dyn Fn(Rc<dyn IInteractionContext<I, O>>) -> Rc<dyn IObservable<()>>>,
) -> Rc<dyn IDisposable> {
Interaction::register_handler_observable(self, move |context| handler(context))
}
fn handle(&self, input: I) -> Rc<dyn IObservable<O>> {
self.when_handled(input)
}
}
struct ObservableToTaskObserver {
completion: TaskCompletionSource<()>,
}
impl<T> IObserver<T> for ObservableToTaskObserver {
fn on_next(&self, _value: T) {
}
fn on_error(&self, error: ObservableError) {
self.completion.try_set_exception(error);
}
fn on_completed(&self) {
self.completion.try_set_result(());
}
}