use super::{AsCompositionObject, CompositionObject, CompositionPropertySet, Compositor};
use crate::reactive::{Disposable, IDisposable};
use crate::threading::{Dispatcher, DispatcherPriority};
use crate::utilities::HandlerList;
use bitflags::bitflags;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct CompositionBatchTypes: u32 {
const ANIMATION = 1;
const EFFECT = 2;
const INFINITE_ANIMATION = 4;
const ALL_ANIMATIONS = 5;
}
}
impl CompositionBatchTypes {
pub const NONE: CompositionBatchTypes = CompositionBatchTypes::empty();
}
#[derive(Clone, Copy, Debug)]
pub struct CompositionBatchCompletedEventArgs {
_private: (),
}
type CompletedHandlers = HandlerList<dyn Fn(&CompositionBatchCompletedEventArgs)>;
static NEXT_BATCH_ID: AtomicU64 = AtomicU64::new(1);
crate::ferro_thread_local! {
static PENDING_COMPLETIONS: RefCell<HashMap<u64, Rc<CompletedHandlers>>> = RefCell::new(HashMap::new());
}
fn raise_completed(id: u64) {
let handlers = PENDING_COMPLETIONS
.try_with(|pending| pending.try_borrow_mut().ok().and_then(|mut pending| pending.remove(&id)))
.ok()
.flatten();
let Some(handlers) = handlers else { return };
let args = CompositionBatchCompletedEventArgs { _private: () };
for (_, handler) in handlers.snapshot().iter() {
handler(&args);
}
}
fn register_pending_completion(id: u64, handlers: Rc<CompletedHandlers>) {
PENDING_COMPLETIONS.with(|pending| {
pending.borrow_mut().insert(id, handlers);
});
}
fn subscribe(
handlers: &Rc<CompletedHandlers>,
handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static,
) -> Rc<dyn IDisposable> {
let token = handlers.add(Rc::new(handler));
let weak = Rc::downgrade(handlers);
Disposable::create(move || {
if let Some(handlers) = weak.upgrade() {
handlers.remove(token);
}
})
}
struct BatchState {
types: CompositionBatchTypes,
finite: usize,
infinite: usize,
ended: bool,
completed: bool,
observed: bool,
}
pub(crate) struct BatchShared {
id: u64,
dispatcher: Arc<Dispatcher>,
state: Mutex<BatchState>,
}
impl BatchShared {
fn new(dispatcher: Arc<Dispatcher>, types: CompositionBatchTypes, observed: bool) -> Arc<BatchShared> {
Arc::new(BatchShared {
id: NEXT_BATCH_ID.fetch_add(1, Ordering::Relaxed),
dispatcher,
state: Mutex::new(BatchState {
types,
finite: 0,
infinite: 0,
ended: false,
completed: false,
observed,
}),
})
}
fn lock(&self) -> std::sync::MutexGuard<'_, BatchState> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
fn acquire(self: &Arc<Self>, infinite: bool) -> BatchCompletionGuard {
{
let mut state = self.lock();
if infinite {
state.infinite += 1;
} else {
state.finite += 1;
}
}
BatchCompletionGuard { shared: self.clone(), infinite }
}
fn release(&self, infinite: bool) {
let mut state = self.lock();
if infinite {
state.infinite = state.infinite.saturating_sub(1);
} else {
state.finite = state.finite.saturating_sub(1);
}
self.check_completed(state);
}
fn end(&self) {
let mut state = self.lock();
state.ended = true;
self.check_completed(state);
}
fn is_ended(&self) -> bool {
self.lock().ended
}
fn observe(&self, types: CompositionBatchTypes) {
let mut state = self.lock();
state.observed = true;
state.types |= types;
}
fn check_completed(&self, mut state: std::sync::MutexGuard<'_, BatchState>) {
if state.completed || !state.ended {
return;
}
let mut running = 0;
if state.types.contains(CompositionBatchTypes::ANIMATION) {
running += state.finite;
}
if state.types.contains(CompositionBatchTypes::INFINITE_ANIMATION) {
running += state.infinite;
}
if running != 0 {
return;
}
state.completed = true;
let observed = state.observed;
drop(state);
if observed {
let id = self.id;
self.dispatcher.post(move || raise_completed(id), DispatcherPriority::NORMAL);
}
}
}
pub struct BatchCompletionGuard {
shared: Arc<BatchShared>,
infinite: bool,
}
impl Drop for BatchCompletionGuard {
fn drop(&mut self) {
self.shared.release(self.infinite);
}
}
#[derive(Default)]
pub struct AnimationCompletion {
guards: Vec<BatchCompletionGuard>,
}
impl AnimationCompletion {
pub fn none() -> AnimationCompletion {
AnimationCompletion::default()
}
pub fn is_empty(&self) -> bool {
self.guards.is_empty()
}
pub fn release(&mut self) {
self.guards.clear();
}
}
#[derive(Default)]
pub(crate) struct CompositorBatches {
scoped: RefCell<Vec<Weak<CompositionScopedBatch>>>,
frame: RefCell<Option<Arc<BatchShared>>>,
commit_batch: RefCell<Option<Rc<CompositionCommitBatch>>>,
used: Cell<bool>,
}
impl CompositorBatches {
pub(crate) fn new() -> CompositorBatches {
CompositorBatches::default()
}
fn frame(&self, dispatcher: &Arc<Dispatcher>) -> Arc<BatchShared> {
self.frame
.borrow_mut()
.get_or_insert_with(|| BatchShared::new(dispatcher.clone(), CompositionBatchTypes::NONE, false))
.clone()
}
pub(crate) fn used(&self) -> bool {
self.used.get()
}
pub(crate) fn on_commit(&self) {
let frame = self.frame.borrow_mut().take();
let commit_batch = self.commit_batch.borrow_mut().take();
if let Some(frame) = frame {
frame.end();
}
drop(commit_batch);
}
}
pub struct CompositionScopedBatch {
object: CompositionObject,
shared: Arc<BatchShared>,
is_suspended: Cell<bool>,
is_ended: Cell<bool>,
completed: Rc<CompletedHandlers>,
}
impl CompositionScopedBatch {
pub fn compositor(&self) -> &Rc<Compositor> {
self.object.compositor()
}
pub fn is_active(&self) -> bool {
!self.is_suspended.get() && !self.is_ended.get()
}
pub fn is_ended(&self) -> bool {
self.is_ended.get()
}
pub fn end(&self) {
if self.is_ended.get() {
return;
}
self.is_ended.set(true);
let this: *const CompositionScopedBatch = self;
self.compositor()
.animation_batches()
.scoped
.borrow_mut()
.retain(|batch| batch.strong_count() > 0 && !std::ptr::eq(batch.as_ptr(), this));
register_pending_completion(self.shared.id, self.completed.clone());
self.shared.end();
}
pub fn suspend(&self) {
if self.is_ended.get() {
panic!("The batch has been ended and cannot be suspended");
}
self.is_suspended.set(true);
}
pub fn resume(&self) {
if self.is_ended.get() {
panic!("The batch has been ended and cannot be resumed");
}
self.is_suspended.set(false);
}
pub fn completed(&self, handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static) -> Rc<dyn IDisposable> {
subscribe(&self.completed, handler)
}
}
impl AsCompositionObject for CompositionScopedBatch {
fn as_composition_object(&self) -> &CompositionObject {
&self.object
}
fn composition_type_name(&self) -> &'static str {
"CompositionScopedBatch"
}
}
pub struct CompositionCommitBatch {
object: CompositionObject,
shared: Arc<BatchShared>,
completed: Rc<CompletedHandlers>,
}
impl CompositionCommitBatch {
pub fn compositor(&self) -> &Rc<Compositor> {
self.object.compositor()
}
pub fn is_active(&self) -> bool {
!self.shared.is_ended()
}
pub fn is_ended(&self) -> bool {
self.shared.is_ended()
}
pub fn completed(&self, handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static) -> Rc<dyn IDisposable> {
subscribe(&self.completed, handler)
}
}
impl AsCompositionObject for CompositionCommitBatch {
fn as_composition_object(&self) -> &CompositionObject {
&self.object
}
fn composition_type_name(&self) -> &'static str {
"CompositionCommitBatch"
}
}
impl Compositor {
pub fn create_scoped_batch(&self, batch_type: CompositionBatchTypes) -> Rc<CompositionScopedBatch> {
let batch = Rc::new(CompositionScopedBatch {
object: CompositionObject::new(&self.this_handle(), None),
shared: BatchShared::new(self.dispatcher().clone(), batch_type, true),
is_suspended: Cell::new(false),
is_ended: Cell::new(false),
completed: Rc::new(HandlerList::new()),
});
self.animation_batches().used.set(true);
let mut scoped = self.animation_batches().scoped.borrow_mut();
scoped.retain(|batch| batch.strong_count() > 0);
scoped.push(Rc::downgrade(&batch));
batch
}
pub fn get_commit_batch(&self, batch_type: CompositionBatchTypes) -> Rc<CompositionCommitBatch> {
let batches = self.animation_batches();
batches.used.set(true);
let existing = batches.commit_batch.borrow().clone();
if let Some(existing) = existing {
existing.shared.observe(batch_type);
return existing;
}
let shared = batches.frame(self.dispatcher());
shared.observe(batch_type);
let batch = Rc::new(CompositionCommitBatch {
object: CompositionObject::new(&self.this_handle(), None),
shared: shared.clone(),
completed: Rc::new(HandlerList::new()),
});
register_pending_completion(shared.id, batch.completed.clone());
*batches.commit_batch.borrow_mut() = Some(batch.clone());
self.request_commit_async();
batch
}
pub fn create_property_set(&self) -> Rc<CompositionPropertySet> {
CompositionPropertySet::new(&self.this_handle())
}
pub(crate) fn acquire_animation_completion(&self, infinite: bool) -> AnimationCompletion {
let batches = self.animation_batches();
let mut guards = Vec::new();
let scoped: Vec<Rc<CompositionScopedBatch>> =
batches.scoped.borrow().iter().filter_map(Weak::upgrade).collect();
for batch in scoped {
if batch.is_active() {
guards.push(batch.shared.acquire(infinite));
}
}
guards.push(batches.frame(self.dispatcher()).acquire(infinite));
AnimationCompletion { guards }
}
}