ferroui_base/rendering/composition/server/
server_compositor.rs1use super::{
2 CompositorPools, IServerObject, IServerRenderResource, ReadbackIndices, ServerCompositionTarget,
3 ServerCompositionVisual, ServerCompositorAnimations, ServerObjectId,
4};
5use crate::platform::surfaces::IPlatformRenderSurface;
6use crate::platform::{IBitmapImpl, IPlatformGraphics, IRenderTarget, LtrbRect};
7use crate::rendering::composition::transport::{
8 BatchMarker, BatchObject, BatchStreamData, BatchStreamReader, CommittedBatch, CompositionBatch, ServerJob,
9};
10use crate::rendering::composition::CompositionOptions;
11use crate::rendering::PlatformRenderInterfaceContextManager;
12use crate::reactive::IDisposable;
13use crate::threading::Dispatcher;
14use crate::{Matrix, PixelSize, Size, Vector};
15use std::any::{Any, TypeId};
16use std::cell::{Cell, RefCell};
17use std::collections::{HashMap, HashSet, VecDeque};
18use std::rc::{Rc, Weak};
19use std::sync::{Arc, Mutex};
20use std::time::Duration;
21
22pub type CompositorClock = Rc<dyn Fn() -> Duration>;
27
28#[derive(Default)]
34pub struct BatchQueue {
35 batches: Mutex<VecDeque<CommittedBatch>>,
36 data_pool: Mutex<Vec<BatchStreamData>>,
38}
39
40impl BatchQueue {
41 pub(crate) fn enqueue(&self, batch: CommittedBatch) {
42 self.batches.lock().unwrap().push_back(batch);
43 }
44
45 fn dequeue(&self) -> Option<CommittedBatch> {
46 self.batches.lock().unwrap().pop_front()
47 }
48
49 pub(crate) fn rent_data(&self) -> BatchStreamData {
51 self.data_pool.lock().unwrap().pop().unwrap_or_default()
52 }
53
54 pub(crate) fn complete_all(&self) {
64 loop {
65 let Some(batch) = self.dequeue() else { break };
68 batch.batch.notify_processed();
69 batch.batch.notify_rendered();
70 }
71 }
72
73 fn return_data(&self, mut data: BatchStreamData) {
74 data.reset();
75 let mut pool = self.data_pool.lock().unwrap();
76 if pool.len() < 16 {
77 pool.push(data);
78 }
79 }
80}
81
82const _: fn() = || {
84 fn shared<T: Send + Sync>() {}
85 shared::<BatchQueue>();
86};
87
88const COMMIT_GRACE_TICKS: i32 = 10;
89
90type RenderThreadJob = Box<dyn FnOnce(&ServerCompositor)>;
94
95pub struct ServerCompositor {
102 this: Weak<ServerCompositor>,
103 batches: Arc<BatchQueue>,
104 objects: RefCell<Vec<Option<Rc<dyn IServerObject>>>>,
105 received_job_queue: RefCell<VecDeque<RenderThreadJob>>,
106 disposed_in_batch: RefCell<Vec<ServerObjectId>>,
107 received_post_target_job_queue: RefCell<VecDeque<RenderThreadJob>>,
108 last_batch_id: Cell<i64>,
109 clock: CompositorClock,
110 server_now: Cell<Duration>,
111 ui_thread_is_inside_render: Cell<bool>,
112 rendering_stopped: Cell<bool>,
114 render_interface: Rc<PlatformRenderInterfaceContextManager>,
115 options: CompositionOptions,
116 readback: Arc<ReadbackIndices>,
117 ticks_since_last_commit: Cell<i32>,
118 to_notify_processed: RefCell<Vec<Arc<CompositionBatch>>>,
119 to_notify_rendered: RefCell<Vec<Arc<CompositionBatch>>>,
120 render_resources_invalidation_queue: RefCell<VecDeque<Rc<dyn IServerRenderResource>>>,
121 render_resources_invalidation_set: RefCell<HashSet<*const ()>>,
122 visual_own_properties_recompute_pass: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
124 visual_readback_update_pass_queue: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
125 adorner_update_queue: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
126 active_targets: RefCell<Vec<Rc<ServerCompositionTarget>>>,
127 pools: CompositorPools,
128 animations: ServerCompositorAnimations,
129 render_interface_feature_cache: RefCell<Option<Rc<HashMap<TypeId, Rc<dyn Any>>>>>,
130 diagnostic_text_renderer: RefCell<Option<Rc<super::DiagnosticTextRenderer>>>,
134 _context_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
135}
136
137impl ServerCompositor {
138 pub(crate) fn new(
142 platform_graphics: Option<Arc<dyn IPlatformGraphics>>,
143 options: CompositionOptions,
144 batches: Arc<BatchQueue>,
145 readback: Arc<ReadbackIndices>,
146 clock: CompositorClock,
147 ) -> Rc<ServerCompositor> {
148 let compositor = Rc::new_cyclic(|this| ServerCompositor {
149 this: this.clone(),
150 batches,
151 objects: RefCell::new(Vec::new()),
152 received_job_queue: RefCell::new(VecDeque::new()),
153 disposed_in_batch: RefCell::new(Vec::new()),
154 received_post_target_job_queue: RefCell::new(VecDeque::new()),
155 last_batch_id: Cell::new(0),
156 clock,
157 server_now: Cell::new(Duration::ZERO),
158 ui_thread_is_inside_render: Cell::new(false),
159 rendering_stopped: Cell::new(false),
160 render_interface: PlatformRenderInterfaceContextManager::new(platform_graphics),
161 options,
162 readback,
163 ticks_since_last_commit: Cell::new(0),
164 to_notify_processed: RefCell::new(Vec::new()),
165 to_notify_rendered: RefCell::new(Vec::new()),
166 render_resources_invalidation_queue: RefCell::new(VecDeque::new()),
167 render_resources_invalidation_set: RefCell::new(HashSet::new()),
168 visual_own_properties_recompute_pass: RefCell::new(VecDeque::new()),
169 visual_readback_update_pass_queue: RefCell::new(VecDeque::new()),
170 adorner_update_queue: RefCell::new(VecDeque::new()),
171 active_targets: RefCell::new(Vec::new()),
172 pools: CompositorPools::default(),
173 animations: ServerCompositorAnimations::new(),
174 render_interface_feature_cache: RefCell::new(None),
175 diagnostic_text_renderer: RefCell::new(None),
176 _context_subscriptions: RefCell::new(Vec::new()),
177 });
178 let weak = Rc::downgrade(&compositor);
179 let disposed = compositor.render_interface.context_disposed(move || {
180 if let Some(compositor) = weak.upgrade() {
181 compositor.rt_on_context_disposed();
182 }
183 });
184 let weak = Rc::downgrade(&compositor);
185 let created = compositor.render_interface.context_created(move |context| {
186 if let Some(compositor) = weak.upgrade() {
187 compositor.rt_on_context_created(&**context);
188 }
189 });
190 *compositor._context_subscriptions.borrow_mut() = vec![disposed, created];
191 compositor
192 }
193
194 pub fn pools(&self) -> &CompositorPools {
196 &self.pools
197 }
198
199 pub fn animations(&self) -> &ServerCompositorAnimations {
201 &self.animations
202 }
203
204 pub fn last_batch_id(&self) -> i64 {
206 self.last_batch_id.get()
207 }
208
209 pub fn clock_elapsed(&self) -> Duration {
211 (self.clock)()
212 }
213
214 pub fn clock(&self) -> &CompositorClock {
216 &self.clock
217 }
218
219 pub fn server_now(&self) -> Duration {
221 self.server_now.get()
222 }
223
224 pub fn render_interface(&self) -> &Rc<PlatformRenderInterfaceContextManager> {
225 &self.render_interface
226 }
227
228 pub fn diagnostic_text_renderer(&self) -> Option<Rc<super::DiagnosticTextRenderer>> {
236 if self.diagnostic_text_renderer.borrow().is_none() {
237 {
240 use crate::LocatorExtensions as _;
241 crate::FerroLocator::current().get_service::<dyn crate::platform::IFontManagerImpl>()?;
242 }
243 let renderer = Rc::new(super::DiagnosticTextRenderer::create_default());
244 for label in super::FpsCounter::LABELS {
248 renderer.shape_label(label);
249 }
250 *self.diagnostic_text_renderer.borrow_mut() = Some(renderer);
251 }
252 self.diagnostic_text_renderer.borrow().clone()
253 }
254
255 pub fn options(&self) -> &CompositionOptions {
256 &self.options
257 }
258
259 pub fn readback(&self) -> &Arc<ReadbackIndices> {
261 &self.readback
262 }
263
264 pub(crate) fn update_server_time(&self) {
265 self.server_now.set(self.clock_elapsed());
266 }
267
268 pub fn get_object(&self, id: ServerObjectId) -> Option<Rc<dyn IServerObject>> {
272 self.objects.borrow().get(id.index()).and_then(Clone::clone)
273 }
274
275 pub fn get<T: IServerObject>(&self, id: ServerObjectId) -> Option<Rc<T>> {
277 self.get_object(id).and_then(|object| object.into_any_rc().downcast::<T>().ok())
278 }
279
280 pub fn get_animated_object(&self, id: ServerObjectId) -> Option<Rc<dyn super::IAnimatedServerObject>> {
282 self.get_object(id).and_then(|object| object.as_animated())
283 }
284
285 pub fn object_count(&self) -> usize {
287 self.objects.borrow().iter().filter(|o| o.is_some()).count()
288 }
289
290 fn insert_object(&self, id: ServerObjectId, object: Rc<dyn IServerObject>) {
291 let mut objects = self.objects.borrow_mut();
292 if objects.len() <= id.index() {
293 objects.resize_with(id.index() + 1, || None);
294 }
295 debug_assert!(objects[id.index()].is_none(), "a server object id was reused while still alive");
296 objects[id.index()] = Some(object);
297 }
298
299 fn remove_object(&self, id: ServerObjectId) -> Option<Rc<dyn IServerObject>> {
300 self.objects.borrow_mut().get_mut(id.index()).and_then(Option::take)
301 }
302
303 fn apply_pending_batches(&self) {
306 let mut had_batches = false;
307 while let Some(mut batch) = self.batches.dequeue() {
308 {
309 let mut stream = BatchStreamReader::new(&mut batch.changes);
310 while !stream.is_object_eof() {
311 match stream.read_object() {
312 BatchObject::Marker(BatchMarker::CreateStart) => self.read_create_jobs(&mut stream),
313 BatchObject::Marker(BatchMarker::RenderThreadJobsStart) => self.read_server_jobs(
314 &mut stream,
315 &self.received_job_queue,
316 BatchMarker::RenderThreadJobsEnd,
317 ),
318 BatchObject::Marker(BatchMarker::RenderThreadPostTargetJobsStart) => self.read_server_jobs(
319 &mut stream,
320 &self.received_post_target_job_queue,
321 BatchMarker::RenderThreadPostTargetJobsEnd,
322 ),
323 BatchObject::Marker(BatchMarker::RenderThreadDisposeStart) => {
324 self.read_dispose_jobs(&mut stream)
325 }
326 BatchObject::ServerObject(id) => {
327 let Some(target) = self.get_object(id) else {
328 panic!("a batch refers to server object {id:?}, which does not exist");
329 };
330 target.deserialize_changes(&mut stream, batch.committed_at);
331 if cfg!(debug_assertions) {
332 match stream.read_object() {
336 BatchObject::Marker(BatchMarker::ObjectEnd) => {}
337 _ => panic!("server object {id:?} failed to deserialize properly on the object stream"),
338 }
339 if stream.read::<u64>() != OBJECT_END_MAGIC {
340 panic!("server object {id:?} failed to deserialize properly on the data stream");
341 }
342 }
343 }
344 _ => panic!("unexpected item at the top level of a batch object stream"),
345 }
346 }
347 }
348 for id in std::mem::take(&mut *self.disposed_in_batch.borrow_mut()) {
352 self.remove_object(id);
353 }
354 self.batches.return_data(std::mem::take(&mut batch.changes));
355 self.last_batch_id.set(batch.batch.sequence_id());
356 self.to_notify_processed.borrow_mut().push(batch.batch);
357 had_batches = true;
358 }
359
360 if had_batches {
361 self.ticks_since_last_commit.set(0);
362 } else if self.ticks_since_last_commit.get() < i32::MAX {
363 self.ticks_since_last_commit.set(self.ticks_since_last_commit.get() + 1);
364 }
365 }
366
367 fn read_create_jobs(&self, reader: &mut BatchStreamReader<'_>) {
368 let Some(this) = self.this.upgrade() else { return };
369 let mut count = reader.read::<i32>();
370 while count > 0 {
371 let Some(id) = reader.read_server_object() else {
372 panic!("a batch creates a server object without an id");
373 };
374 let BatchObject::Create(factory) = reader.read_object() else {
375 panic!("a batch creates a server object without a factory");
376 };
377 self.insert_object(id, factory(&this, id));
378 count -= 1;
379 }
380 }
381
382 fn read_server_jobs(&self, reader: &mut BatchStreamReader<'_>, queue: &RefCell<VecDeque<RenderThreadJob>>, end: BatchMarker) {
383 loop {
384 match reader.read_object() {
385 BatchObject::Marker(marker) if marker == end => break,
386 BatchObject::Job(job) => queue.borrow_mut().push_back(job),
387 BatchObject::ServerObject(id) => {
388 let target = self.get_object(id);
389 match reader.read_object() {
390 BatchObject::ObjectJob(job) => {
391 queue.borrow_mut().push_back(Box::new(move |compositor| job(compositor, target)))
392 }
393 _ => panic!("a server object in the job list of a batch is not followed by its job"),
394 }
395 }
396 _ => panic!("unexpected item in the job list of a batch"),
397 }
398 }
399 }
400
401 fn read_dispose_jobs(&self, reader: &mut BatchStreamReader<'_>) {
402 let mut count = reader.read::<i32>();
403 while count > 0 {
404 let id = reader.read_server_object();
405 let dispose = reader.read::<bool>();
406 let release = reader.read::<bool>();
407 if let Some(id) = id {
408 if let Some(object) = self.get_object(id) {
409 if dispose {
410 object.dispose();
411 }
412 if release {
413 self.disposed_in_batch.borrow_mut().push(id);
414 }
415 }
416 }
417 count -= 1;
418 }
419 }
420
421 fn execute_server_jobs(&self, queue: &RefCell<VecDeque<RenderThreadJob>>) {
422 loop {
423 let Some(job) = queue.borrow_mut().pop_front() else { break };
424 job(self);
425 }
426 }
427
428 fn notify_batches_processed(&self) {
429 let processed = std::mem::take(&mut *self.to_notify_processed.borrow_mut());
430 for batch in &processed {
431 batch.notify_processed();
432 }
433 self.to_notify_rendered.borrow_mut().extend(processed);
434 }
435
436 fn notify_batches_rendered(&self) {
437 let rendered = std::mem::take(&mut *self.to_notify_rendered.borrow_mut());
438 for batch in &rendered {
439 batch.notify_rendered();
440 }
441 }
442
443 fn apply_enqueued_render_resource_changes_pass(&self) {
446 loop {
447 let Some(resource) = self.render_resources_invalidation_queue.borrow_mut().pop_front() else { break };
448 resource.queued_invalidate();
451 }
452 self.render_resources_invalidation_set.borrow_mut().clear();
453 }
454
455 pub fn enqueue_render_resource_for_invalidation(&self, resource: Rc<dyn IServerRenderResource>) {
458 let key = Rc::as_ptr(&resource) as *const ();
459 if self.render_resources_invalidation_set.borrow_mut().insert(key) {
460 self.render_resources_invalidation_queue.borrow_mut().push_back(resource);
461 }
462 }
463
464 fn visual_own_properties_update_pass(&self) {
465 loop {
466 let Some(visual) = self.visual_own_properties_recompute_pass.borrow_mut().pop_front() else { break };
467 visual.recompute_own_properties();
468 }
469 }
470
471 pub fn enqueue_visual_for_own_properties_update_pass(&self, visual: Rc<ServerCompositionVisual>) {
472 self.visual_own_properties_recompute_pass.borrow_mut().push_back(visual);
473 }
474
475 fn visual_readback_update_pass(&self) {
476 if self.visual_readback_update_pass_queue.borrow().is_empty() {
477 return;
478 }
479
480 let scope = self.readback.begin_write();
483 let read = self.readback.read_revision();
484 let write = self.readback.write_revision();
485 loop {
486 let Some(visual) = self.visual_readback_update_pass_queue.borrow_mut().pop_front() else { break };
487 visual.update_readback(write, read);
488 }
489 drop(scope);
490 }
491
492 pub fn enqueue_visual_for_readback_update_pass(&self, visual: Rc<ServerCompositionVisual>) {
493 self.visual_readback_update_pass_queue.borrow_mut().push_back(visual);
494 }
495
496 pub fn enqueue_adorner_update(&self, visual: Rc<ServerCompositionVisual>) {
497 self.adorner_update_queue.borrow_mut().push_back(visual);
498 }
499
500 fn adorner_update_pass(&self) {
501 loop {
502 let Some(adorner) = self.adorner_update_queue.borrow_mut().pop_front() else { break };
503 adorner.update_adorner();
504 }
505 }
506
507 pub fn add_composition_target(&self, target: &Rc<ServerCompositionTarget>) {
510 self.active_targets.borrow_mut().push(target.clone());
511 }
512
513 pub fn remove_composition_target(&self, target: &Rc<ServerCompositionTarget>) {
514 let mut targets = self.active_targets.borrow_mut();
515 if let Some(index) = targets.iter().position(|t| Rc::ptr_eq(t, target)) {
516 targets.remove(index);
517 }
518 }
519
520 pub fn create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> Rc<dyn IRenderTarget> {
521 let current = self.render_interface.ensure_current();
522 let target = self.render_interface.create_render_target(surfaces);
523 current.dispose();
524 target
525 }
526
527 pub fn is_ready_to_create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> bool {
528 self.render_interface.is_ready_to_create_render_target(surfaces)
529 }
530
531 fn rt_on_context_created(&self, context: &dyn crate::platform::IPlatformRenderInterfaceContext) {
534 *self.render_interface_feature_cache.borrow_mut() = Some(Rc::new(context.public_features()));
535 }
536
537 fn rt_on_context_disposed(&self) {
538 *self.render_interface_feature_cache.borrow_mut() = None;
539 }
540
541 pub fn at_try_get_cached_render_interface_features(&self) -> Option<Rc<HashMap<TypeId, Rc<dyn Any>>>> {
544 self.render_interface_feature_cache.borrow().clone()
545 }
546
547 pub fn rt_get_render_interface_features(&self) -> Rc<HashMap<TypeId, Rc<dyn Any>>> {
550 if let Some(features) = self.render_interface_feature_cache.borrow().clone() {
551 return features;
552 }
553 let features = Rc::new(self.render_interface.value().public_features());
554 *self.render_interface_feature_cache.borrow_mut() = Some(features.clone());
555 features
556 }
557
558 pub fn create_composition_visual_snapshot(
561 &self,
562 visual: &Rc<ServerCompositionVisual>,
563 scaling: f64,
564 render_children: bool,
565 ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
566 let current = self.render_interface.ensure_current();
567 let size = visual.size();
568 let pixel_size = PixelSize::from_size(Size::new(size.x, size.y), scaling);
569 let scale_transform = Matrix::create_scale(scaling, scaling);
570
571 let target = self.render_interface.value().create_offscreen_render_target(
572 pixel_size,
573 Vector::new(scaling, scaling),
574 true,
575 );
576 {
577 let mut canvas = target.create_drawing_context();
578 canvas.set_transform(scale_transform);
579 visual.render(&mut *canvas, LtrbRect::INFINITE, None, render_children, false, false);
580 canvas.dispose();
581 }
582
583 let result: std::sync::Arc<crate::platform::SharedBitmapImpl> = match target.as_layer_with_render_context_affinity() {
584 Some(affined) if affined.has_render_context_affinity() => {
585 let snapshot = affined.create_non_affined_snapshot();
586 target.dispose();
587 snapshot
588 }
589 _ => {
592 let snapshot = target.create_shared_snapshot();
593 target.dispose();
594 snapshot
595 }
596 };
597 current.dispose();
598 result
599 }
600
601 pub fn reset_all_gpu_resources(&self) {
602 let targets = self.active_targets.borrow().clone();
603 for target in targets {
604 target.reset_render_target();
605 }
606 self.render_interface.reset();
607 }
608
609 pub fn release_gpu_resources(&self) {
633 if self.render_interface.existing_backend_context().is_none() {
634 return;
635 }
636 let gpu_context = self.render_interface.gpu_context();
637 if gpu_context.as_ref().is_some_and(|context| context.is_lost()) {
638 return;
639 }
640 let current = gpu_context.map(|context| context.ensure_current());
641 let targets = self.active_targets.borrow().clone();
642 for target in targets {
643 target.release_render_target();
644 }
645 self.render_interface.reset();
646 if let Some(current) = current {
647 current.dispose();
648 }
649 }
650
651 pub fn stop_rendering(&self) {
663 self.rendering_stopped.set(true);
664 }
665
666 pub fn is_rendering_stopped(&self) -> bool {
668 self.rendering_stopped.get()
669 }
670
671 pub fn invalidate_all_composition_targets(&self) {
672 let targets = self.active_targets.borrow().clone();
673 for target in targets {
674 target.request_full_redraw();
675 }
676 }
677
678 pub fn check_access(&self) -> bool {
684 self.ui_thread_is_inside_render.get()
685 }
686
687 pub fn verify_access(&self) {
690 if !self.check_access() {
691 panic!("This object can be only accessed under compositor lock");
692 }
693 }
694
695 pub fn verify_access_of(compositor: Option<&Rc<ServerCompositor>>) -> Rc<ServerCompositor> {
699 match compositor {
700 Some(compositor) => {
701 compositor.verify_access();
702 compositor.clone()
703 }
704 None => panic!("This object can be only accessed under compositor lock"),
705 }
706 }
707
708 pub fn render(&self) -> bool {
714 if self.ui_thread_is_inside_render.replace(true) {
715 panic!("Reentrancy is not supported");
716 }
717 let _reset = ResetFlag(&self.ui_thread_is_inside_render);
718
719 let dispatcher = Dispatcher::ui_thread();
723 let processing_disabled = dispatcher.check_access().then(|| dispatcher.disable_processing());
724 let _notify = NotifyRendered(self);
725 let _enable = EnableProcessing(processing_disabled);
728 self.render_core()
729 }
730
731 fn execute_global_passes(&self) -> Duration {
732 let compositor_global_passes_started = self.clock_elapsed();
733 self.apply_pending_batches();
734 self.notify_batches_processed();
735 self.animations.process();
736 self.apply_enqueued_render_resource_changes_pass();
737 self.visual_own_properties_update_pass();
738 self.adorner_update_pass();
741 self.clock_elapsed().saturating_sub(compositor_global_passes_started)
742 }
743
744 fn render_core(&self) -> bool {
745 self.update_server_time();
746 let compositor_global_passes_elapsed = self.execute_global_passes();
747
748 if self.rendering_stopped.get() {
749 let jobs = std::mem::take(&mut *self.received_job_queue.borrow_mut());
751 let post_target_jobs = std::mem::take(&mut *self.received_post_target_job_queue.borrow_mut());
752 drop((jobs, post_target_jobs));
753 return false;
754 }
755
756 if !self.render_interface.is_ready() {
757 return true;
758 }
759 self.render_interface.ensure_valid_backend_context();
760 self.execute_server_jobs(&self.received_job_queue);
761
762 let targets = self.active_targets.borrow().clone();
763 for target in &targets {
764 target.update(compositor_global_passes_elapsed);
765 target.render();
766 }
767
768 self.visual_readback_update_pass();
769
770 self.execute_server_jobs(&self.received_post_target_job_queue);
771
772 if self.animations.need_next_tick() || self.ticks_since_last_commit.get() < COMMIT_GRACE_TICKS {
774 return true;
775 }
776
777 let targets = self.active_targets.borrow().clone();
780 for target in &targets {
781 if target.is_waiting_for_ready_render_target() && !target.is_waiting_for_render_loop_wakeup() {
782 return true;
783 }
784 }
785
786 false
788 }
789}
790
791pub(crate) const OBJECT_END_MAGIC: u64 = 0x4F42_4A45_4E44_2121;
794
795struct ResetFlag<'a>(&'a Cell<bool>);
796
797impl Drop for ResetFlag<'_> {
798 fn drop(&mut self) {
799 self.0.set(false);
800 }
801}
802
803struct NotifyRendered<'a>(&'a ServerCompositor);
804
805impl Drop for NotifyRendered<'_> {
806 fn drop(&mut self) {
807 self.0.notify_batches_rendered();
808 }
809}
810
811struct EnableProcessing(Option<crate::threading::DispatcherProcessingDisabled>);
814
815impl Drop for EnableProcessing {
816 fn drop(&mut self) {
817 if let Some(disabled) = &self.0 {
818 crate::reactive::IDisposable::dispose(disabled);
819 }
820 }
821}