#![allow(dead_code)]
use ferroui_system::{IDisposable, IObserver, ObservableError};
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
enum Notification<T> {
Next(T),
Error(ObservableError),
Completed,
}
impl<T> Notification<T> {
fn is_error(&self) -> bool {
matches!(self, Notification::Error(_))
}
fn accept(self, observer: &dyn IObserver<T>) {
match self {
Notification::Next(value) => observer.on_next(value),
Notification::Error(error) => observer.on_error(error),
Notification::Completed => observer.on_completed(),
}
}
}
trait Drainable {
fn is_staged_error(&self) -> bool;
fn inline_deliverable_count(&self) -> usize;
fn try_stage_next(&self) -> bool;
fn try_stage_inline(&self) -> bool;
fn deliver_staged(&self);
}
pub(crate) struct SharedDeliveryQueue {
order: RefCell<VecDeque<Rc<dyn Drainable>>>,
draining: Cell<bool>,
terminated: Cell<bool>,
}
struct ReleaseDrain<'a>(&'a Cell<bool>);
impl Drop for ReleaseDrain<'_> {
fn drop(&mut self) {
self.0.set(false);
}
}
impl SharedDeliveryQueue {
pub(crate) fn new() -> Rc<Self> {
Rc::new(Self { order: RefCell::new(VecDeque::new()), draining: Cell::new(false), terminated: Cell::new(false) })
}
pub(crate) fn is_terminated(&self) -> bool {
self.terminated.get()
}
pub(crate) fn has_pending(&self) -> bool {
self.draining.get() || !self.order.borrow().is_empty()
}
pub(crate) fn create_queue<T: 'static>(self: &Rc<Self>, observer: Rc<dyn IObserver<T>>) -> Rc<DeliverySubQueue<T>> {
Rc::new_cyclic(|this| DeliverySubQueue {
this: this.clone(),
items: RefCell::new(VecDeque::with_capacity(1)),
parent: self.clone(),
observer,
staged: RefCell::new(None),
inline_delivered_count: Cell::new(0),
is_delivering: Cell::new(false),
is_removed: Cell::new(false),
})
}
pub(crate) fn dispose(&self) {
self.terminated.set(true);
let pending = std::mem::take(&mut *self.order.borrow_mut());
drop(pending);
}
fn enqueue_order(&self, source: Rc<dyn Drainable>) {
self.order.borrow_mut().push_back(source);
}
fn exit_lock_and_drain(&self, source: Rc<dyn Drainable>) {
if self.draining.get() {
let inline_count = if self.terminated.get() { 0 } else { source.inline_deliverable_count() };
self.deliver_inline(&*source, inline_count);
return;
}
if !self.terminated.get() && !self.order.borrow().is_empty() {
self.draining.set(true);
self.drain_all();
}
}
fn drain_all(&self) {
let _release = ReleaseDrain(&self.draining);
loop {
if !self.drain_pending() {
return;
}
if !self.order.borrow().is_empty() && !self.terminated.get() {
continue;
}
return;
}
}
fn drain_pending(&self) -> bool {
loop {
if self.terminated.get() {
return false;
}
let source = match self.order.borrow_mut().pop_front() {
Some(source) => source,
None => return true,
};
if !source.try_stage_next() {
continue;
}
if !self.deliver_staged(&*source) {
return false;
}
}
}
fn deliver_inline(&self, source: &dyn Drainable, count: usize) {
for _ in 0..count {
if self.terminated.get() || !source.try_stage_inline() {
return;
}
if !self.deliver_staged(source) {
return;
}
}
}
fn deliver_staged(&self, source: &dyn Drainable) -> bool {
let is_error = source.is_staged_error();
source.deliver_staged();
if !is_error {
return true;
}
self.terminated.set(true);
let pending = std::mem::take(&mut *self.order.borrow_mut());
drop(pending);
false
}
}
impl IDisposable for SharedDeliveryQueue {
fn dispose(&self) {
SharedDeliveryQueue::dispose(self);
}
}
pub(crate) struct DeliverySubQueue<T> {
this: Weak<DeliverySubQueue<T>>,
items: RefCell<VecDeque<Notification<T>>>,
parent: Rc<SharedDeliveryQueue>,
observer: Rc<dyn IObserver<T>>,
staged: RefCell<Option<Notification<T>>>,
inline_delivered_count: Cell<usize>,
is_delivering: Cell<bool>,
is_removed: Cell<bool>,
}
impl<T: 'static> DeliverySubQueue<T> {
pub(crate) fn acquire(&self) -> SubQueueScopedAccess<T> {
SubQueueScopedAccess { owner: self.this.upgrade() }
}
pub(crate) fn dispose(&self) {
if self.is_removed.replace(true) {
return;
}
let pending = std::mem::take(&mut *self.items.borrow_mut());
drop(pending);
}
fn try_stage(&self) -> bool {
if self.is_removed.get() {
return false;
}
let next = self.items.borrow_mut().pop_front();
match next {
Some(notification) => {
*self.staged.borrow_mut() = Some(notification);
true
}
None => false,
}
}
fn enqueue_item(&self, item: Notification<T>) {
if self.parent.is_terminated() || self.is_removed.get() {
return;
}
self.items.borrow_mut().push_back(item);
if let Some(this) = self.this.upgrade() {
self.parent.enqueue_order(this);
}
}
}
struct EndDelivery<'a>(&'a Cell<bool>);
impl Drop for EndDelivery<'_> {
fn drop(&mut self) {
self.0.set(false);
}
}
impl<T: 'static> Drainable for DeliverySubQueue<T> {
fn is_staged_error(&self) -> bool {
self.staged.borrow().as_ref().is_some_and(Notification::is_error)
}
fn inline_deliverable_count(&self) -> usize {
if self.is_removed.get() || self.is_delivering.get() {
0
} else {
self.items.borrow().len()
}
}
fn try_stage_next(&self) -> bool {
let inline_delivered = self.inline_delivered_count.get();
if inline_delivered != 0 {
self.inline_delivered_count.set(inline_delivered - 1);
return false;
}
self.try_stage()
}
fn try_stage_inline(&self) -> bool {
if !self.try_stage() {
return false;
}
self.inline_delivered_count.set(self.inline_delivered_count.get() + 1);
true
}
fn deliver_staged(&self) {
let staged = self.staged.borrow_mut().take();
self.is_delivering.set(true);
let _end = EndDelivery(&self.is_delivering);
if let Some(notification) = staged {
notification.accept(&*self.observer);
}
}
}
impl<T: 'static> IObserver<T> for DeliverySubQueue<T> {
fn on_next(&self, value: T) {
let scope = self.acquire();
scope.enqueue_next(value);
}
fn on_error(&self, error: ObservableError) {
let scope = self.acquire();
scope.enqueue_error(error);
}
fn on_completed(&self) {
let scope = self.acquire();
scope.enqueue_completed();
}
}
impl<T: 'static> IDisposable for DeliverySubQueue<T> {
fn dispose(&self) {
DeliverySubQueue::dispose(self);
}
}
pub(crate) struct SubQueueScopedAccess<T: 'static> {
owner: Option<Rc<DeliverySubQueue<T>>>,
}
impl<T: 'static> SubQueueScopedAccess<T> {
pub(crate) fn enqueue_next(&self, value: T) {
if let Some(owner) = &self.owner {
owner.enqueue_item(Notification::Next(value));
}
}
pub(crate) fn enqueue_error(&self, error: ObservableError) {
if let Some(owner) = &self.owner {
owner.enqueue_item(Notification::Error(error));
}
}
pub(crate) fn enqueue_completed(&self) {
if let Some(owner) = &self.owner {
owner.enqueue_item(Notification::Completed);
}
}
}
impl<T: 'static> Drop for SubQueueScopedAccess<T> {
fn drop(&mut self) {
if let Some(owner) = self.owner.take() {
if !std::thread::panicking() {
let parent = owner.parent.clone();
parent.exit_lock_and_drain(owner);
}
}
}
}