ferroui_base/rendering/composition/
composition_batch.rs1use super::{AsCompositionObject, CompositionObject, CompositionPropertySet, Compositor};
31use crate::reactive::{Disposable, IDisposable};
32use crate::threading::{Dispatcher, DispatcherPriority};
33use crate::utilities::HandlerList;
34use bitflags::bitflags;
35use std::cell::{Cell, RefCell};
36use std::collections::HashMap;
37use std::rc::{Rc, Weak};
38use std::sync::atomic::{AtomicU64, Ordering};
39use std::sync::{Arc, Mutex};
40
41bitflags! {
42 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
46 pub struct CompositionBatchTypes: u32 {
47 const ANIMATION = 1;
49 const EFFECT = 2;
51 const INFINITE_ANIMATION = 4;
53 const ALL_ANIMATIONS = 5;
55 }
56}
57
58impl CompositionBatchTypes {
59 pub const NONE: CompositionBatchTypes = CompositionBatchTypes::empty();
61}
62
63#[derive(Clone, Copy, Debug)]
66pub struct CompositionBatchCompletedEventArgs {
67 _private: (),
68}
69
70type CompletedHandlers = HandlerList<dyn Fn(&CompositionBatchCompletedEventArgs)>;
72
73static NEXT_BATCH_ID: AtomicU64 = AtomicU64::new(1);
74
75crate::ferro_thread_local! {
76 static PENDING_COMPLETIONS: RefCell<HashMap<u64, Rc<CompletedHandlers>>> = RefCell::new(HashMap::new());
80}
81
82fn raise_completed(id: u64) {
85 let handlers = PENDING_COMPLETIONS
86 .try_with(|pending| pending.try_borrow_mut().ok().and_then(|mut pending| pending.remove(&id)))
87 .ok()
88 .flatten();
89 let Some(handlers) = handlers else { return };
90 let args = CompositionBatchCompletedEventArgs { _private: () };
91 for (_, handler) in handlers.snapshot().iter() {
92 handler(&args);
93 }
94}
95
96fn register_pending_completion(id: u64, handlers: Rc<CompletedHandlers>) {
97 PENDING_COMPLETIONS.with(|pending| {
98 pending.borrow_mut().insert(id, handlers);
99 });
100}
101
102fn subscribe(
104 handlers: &Rc<CompletedHandlers>,
105 handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static,
106) -> Rc<dyn IDisposable> {
107 let token = handlers.add(Rc::new(handler));
108 let weak = Rc::downgrade(handlers);
109 Disposable::create(move || {
110 if let Some(handlers) = weak.upgrade() {
111 handlers.remove(token);
112 }
113 })
114}
115
116struct BatchState {
117 types: CompositionBatchTypes,
119 finite: usize,
121 infinite: usize,
123 ended: bool,
124 completed: bool,
125 observed: bool,
128}
129
130pub(crate) struct BatchShared {
133 id: u64,
134 dispatcher: Arc<Dispatcher>,
135 state: Mutex<BatchState>,
136}
137
138impl BatchShared {
139 fn new(dispatcher: Arc<Dispatcher>, types: CompositionBatchTypes, observed: bool) -> Arc<BatchShared> {
140 Arc::new(BatchShared {
141 id: NEXT_BATCH_ID.fetch_add(1, Ordering::Relaxed),
142 dispatcher,
143 state: Mutex::new(BatchState {
144 types,
145 finite: 0,
146 infinite: 0,
147 ended: false,
148 completed: false,
149 observed,
150 }),
151 })
152 }
153
154 fn lock(&self) -> std::sync::MutexGuard<'_, BatchState> {
155 self.state.lock().unwrap_or_else(|e| e.into_inner())
156 }
157
158 fn acquire(self: &Arc<Self>, infinite: bool) -> BatchCompletionGuard {
159 {
160 let mut state = self.lock();
161 if infinite {
162 state.infinite += 1;
163 } else {
164 state.finite += 1;
165 }
166 }
167 BatchCompletionGuard { shared: self.clone(), infinite }
168 }
169
170 fn release(&self, infinite: bool) {
171 let mut state = self.lock();
172 if infinite {
173 state.infinite = state.infinite.saturating_sub(1);
174 } else {
175 state.finite = state.finite.saturating_sub(1);
176 }
177 self.check_completed(state);
178 }
179
180 fn end(&self) {
181 let mut state = self.lock();
182 state.ended = true;
183 self.check_completed(state);
184 }
185
186 fn is_ended(&self) -> bool {
187 self.lock().ended
188 }
189
190 fn observe(&self, types: CompositionBatchTypes) {
193 let mut state = self.lock();
194 state.observed = true;
195 state.types |= types;
196 }
197
198 fn check_completed(&self, mut state: std::sync::MutexGuard<'_, BatchState>) {
201 if state.completed || !state.ended {
202 return;
203 }
204 let mut running = 0;
205 if state.types.contains(CompositionBatchTypes::ANIMATION) {
206 running += state.finite;
207 }
208 if state.types.contains(CompositionBatchTypes::INFINITE_ANIMATION) {
209 running += state.infinite;
210 }
211 if running != 0 {
212 return;
213 }
214 state.completed = true;
215 let observed = state.observed;
216 drop(state);
217 if observed {
218 let id = self.id;
219 self.dispatcher.post(move || raise_completed(id), DispatcherPriority::NORMAL);
220 }
221 }
222}
223
224pub struct BatchCompletionGuard {
227 shared: Arc<BatchShared>,
228 infinite: bool,
229}
230
231impl Drop for BatchCompletionGuard {
232 fn drop(&mut self) {
233 self.shared.release(self.infinite);
234 }
235}
236
237#[derive(Default)]
242pub struct AnimationCompletion {
243 guards: Vec<BatchCompletionGuard>,
244}
245
246impl AnimationCompletion {
247 pub fn none() -> AnimationCompletion {
249 AnimationCompletion::default()
250 }
251
252 pub fn is_empty(&self) -> bool {
254 self.guards.is_empty()
255 }
256
257 pub fn release(&mut self) {
260 self.guards.clear();
261 }
262}
263
264#[derive(Default)]
267pub(crate) struct CompositorBatches {
268 scoped: RefCell<Vec<Weak<CompositionScopedBatch>>>,
269 frame: RefCell<Option<Arc<BatchShared>>>,
272 commit_batch: RefCell<Option<Rc<CompositionCommitBatch>>>,
275 used: Cell<bool>,
278}
279
280impl CompositorBatches {
281 pub(crate) fn new() -> CompositorBatches {
282 CompositorBatches::default()
283 }
284
285 fn frame(&self, dispatcher: &Arc<Dispatcher>) -> Arc<BatchShared> {
286 self.frame
287 .borrow_mut()
288 .get_or_insert_with(|| BatchShared::new(dispatcher.clone(), CompositionBatchTypes::NONE, false))
289 .clone()
290 }
291
292 pub(crate) fn used(&self) -> bool {
294 self.used.get()
295 }
296
297 pub(crate) fn on_commit(&self) {
300 let frame = self.frame.borrow_mut().take();
301 let commit_batch = self.commit_batch.borrow_mut().take();
302 if let Some(frame) = frame {
303 frame.end();
304 }
305 drop(commit_batch);
306 }
307}
308
309pub struct CompositionScopedBatch {
321 object: CompositionObject,
322 shared: Arc<BatchShared>,
323 is_suspended: Cell<bool>,
324 is_ended: Cell<bool>,
325 completed: Rc<CompletedHandlers>,
326}
327
328impl CompositionScopedBatch {
329 pub fn compositor(&self) -> &Rc<Compositor> {
331 self.object.compositor()
332 }
333
334 pub fn is_active(&self) -> bool {
337 !self.is_suspended.get() && !self.is_ended.get()
338 }
339
340 pub fn is_ended(&self) -> bool {
342 self.is_ended.get()
343 }
344
345 pub fn end(&self) {
350 if self.is_ended.get() {
351 return;
352 }
353 self.is_ended.set(true);
354 let this: *const CompositionScopedBatch = self;
355 self.compositor()
356 .animation_batches()
357 .scoped
358 .borrow_mut()
359 .retain(|batch| batch.strong_count() > 0 && !std::ptr::eq(batch.as_ptr(), this));
360 register_pending_completion(self.shared.id, self.completed.clone());
361 self.shared.end();
362 }
363
364 pub fn suspend(&self) {
368 if self.is_ended.get() {
369 panic!("The batch has been ended and cannot be suspended");
370 }
371 self.is_suspended.set(true);
372 }
373
374 pub fn resume(&self) {
377 if self.is_ended.get() {
378 panic!("The batch has been ended and cannot be resumed");
379 }
380 self.is_suspended.set(false);
381 }
382
383 pub fn completed(&self, handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static) -> Rc<dyn IDisposable> {
389 subscribe(&self.completed, handler)
390 }
391}
392
393impl AsCompositionObject for CompositionScopedBatch {
394 fn as_composition_object(&self) -> &CompositionObject {
395 &self.object
396 }
397
398 fn composition_type_name(&self) -> &'static str {
399 "CompositionScopedBatch"
400 }
401}
402
403pub struct CompositionCommitBatch {
411 object: CompositionObject,
412 shared: Arc<BatchShared>,
413 completed: Rc<CompletedHandlers>,
414}
415
416impl CompositionCommitBatch {
417 pub fn compositor(&self) -> &Rc<Compositor> {
419 self.object.compositor()
420 }
421
422 pub fn is_active(&self) -> bool {
425 !self.shared.is_ended()
426 }
427
428 pub fn is_ended(&self) -> bool {
430 self.shared.is_ended()
431 }
432
433 pub fn completed(&self, handler: impl Fn(&CompositionBatchCompletedEventArgs) + 'static) -> Rc<dyn IDisposable> {
436 subscribe(&self.completed, handler)
437 }
438}
439
440impl AsCompositionObject for CompositionCommitBatch {
441 fn as_composition_object(&self) -> &CompositionObject {
442 &self.object
443 }
444
445 fn composition_type_name(&self) -> &'static str {
446 "CompositionCommitBatch"
447 }
448}
449
450impl Compositor {
451 pub fn create_scoped_batch(&self, batch_type: CompositionBatchTypes) -> Rc<CompositionScopedBatch> {
454 let batch = Rc::new(CompositionScopedBatch {
455 object: CompositionObject::new(&self.this_handle(), None),
456 shared: BatchShared::new(self.dispatcher().clone(), batch_type, true),
457 is_suspended: Cell::new(false),
458 is_ended: Cell::new(false),
459 completed: Rc::new(HandlerList::new()),
460 });
461 self.animation_batches().used.set(true);
462 let mut scoped = self.animation_batches().scoped.borrow_mut();
463 scoped.retain(|batch| batch.strong_count() > 0);
464 scoped.push(Rc::downgrade(&batch));
465 batch
466 }
467
468 pub fn get_commit_batch(&self, batch_type: CompositionBatchTypes) -> Rc<CompositionCommitBatch> {
473 let batches = self.animation_batches();
474 batches.used.set(true);
475 let existing = batches.commit_batch.borrow().clone();
476 if let Some(existing) = existing {
477 existing.shared.observe(batch_type);
478 return existing;
479 }
480 let shared = batches.frame(self.dispatcher());
481 shared.observe(batch_type);
482 let batch = Rc::new(CompositionCommitBatch {
483 object: CompositionObject::new(&self.this_handle(), None),
484 shared: shared.clone(),
485 completed: Rc::new(HandlerList::new()),
486 });
487 register_pending_completion(shared.id, batch.completed.clone());
488 *batches.commit_batch.borrow_mut() = Some(batch.clone());
489 self.request_commit_async();
490 batch
491 }
492
493 pub fn create_property_set(&self) -> Rc<CompositionPropertySet> {
495 CompositionPropertySet::new(&self.this_handle())
496 }
497
498 pub(crate) fn acquire_animation_completion(&self, infinite: bool) -> AnimationCompletion {
506 let batches = self.animation_batches();
507 let mut guards = Vec::new();
508 let scoped: Vec<Rc<CompositionScopedBatch>> =
509 batches.scoped.borrow().iter().filter_map(Weak::upgrade).collect();
510 for batch in scoped {
511 if batch.is_active() {
512 guards.push(batch.shared.acquire(infinite));
513 }
514 }
515 guards.push(batches.frame(self.dispatcher()).acquire(infinite));
516 AnimationCompletion { guards }
517 }
518}