Skip to main content

ferroui_base/rendering/composition/server/
server_render_resource.rs

1use super::{IAnimatedServerObject, ServerCompositor, ServerObject, ServerValueChange};
2use std::cell::{Cell, RefCell};
3use std::rc::{Rc, Weak};
4
5/// Something that is notified when a render resource it depends on is
6/// invalidated.
7pub trait IServerRenderResourceObserver {
8    fn dependency_queued_invalidate(&self, sender: &dyn IServerRenderResource);
9}
10
11/// A server-side render resource: a brush, pen, geometry, transform or
12/// render data whose changes invalidate the objects that use it.
13pub trait IServerRenderResource: IServerRenderResourceObserver {
14    fn add_observer(&self, observer: &Rc<dyn IServerRenderResourceObserver>);
15    fn remove_observer(&self, observer: &Rc<dyn IServerRenderResourceObserver>);
16    fn queued_invalidate(&self);
17}
18
19/// A render resource that owns a [`ServerRenderResourceCore`].
20pub trait IServerRenderResourceHost: IServerRenderResource {
21    /// The compositor of the resource, while it is alive.
22    fn compositor(&self) -> Option<Rc<ServerCompositor>>;
23
24    /// The resource as a shared handle, for the invalidation queue.
25    fn as_render_resource_rc(&self) -> Option<Rc<dyn IServerRenderResource>>;
26
27    /// Called once per group of property changes, before the queued
28    /// invalidation.
29    fn resource_property_changed(&self) {}
30}
31
32struct ObserverEntry {
33    observer: Weak<dyn IServerRenderResourceObserver>,
34    count: u32,
35}
36
37/// The shared implementation of render resources: change coalescing and
38/// the reference-counted observer list.
39///
40/// Observers are held weakly; a dropped observer is pruned the next time
41/// the list is walked.
42#[derive(Default)]
43pub struct ServerRenderResourceCore {
44    pending_invalidation: Cell<bool>,
45    disposed: Cell<bool>,
46    observers: RefCell<Vec<ObserverEntry>>,
47}
48
49fn same_observer(a: &Weak<dyn IServerRenderResourceObserver>, b: &Rc<dyn IServerRenderResourceObserver>) -> bool {
50    std::ptr::addr_eq(a.as_ptr(), Rc::as_ptr(b))
51}
52
53impl ServerRenderResourceCore {
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    pub fn is_disposed(&self) -> bool {
59        self.disposed.get()
60    }
61
62    /// Assigns a property field of the resource: moves the observation of
63    /// the resource from the old value to the new one and records the
64    /// change. Nothing happens when the value is unchanged.
65    pub fn set_value(&self, host: &dyn IServerRenderResourceHost, change: ServerValueChange<'_>) {
66        if change.equal {
67            return;
68        }
69
70        if self.disposed.get() {
71            (change.assign)();
72            return;
73        }
74
75        let observer: Option<Rc<dyn IServerRenderResourceObserver>> =
76            host.as_render_resource_rc().map(|this| this as Rc<dyn IServerRenderResourceObserver>);
77
78        if let (Some(old_child), Some(observer)) = (&change.old_resource, &observer) {
79            old_child.remove_observer(observer);
80        }
81
82        (change.assign)();
83
84        if let (Some(new_child), Some(observer)) = (&change.new_resource, &observer) {
85            new_child.add_observer(observer);
86        }
87
88        self.invalidated(host);
89    }
90
91    /// Assigns a property field that holds a list of values (the
92    /// `IEnumerable` branch of `SetValue`): moves the observation of the
93    /// resource from the render resources of the old list to those of the
94    /// new one and records the change. The lists differ by reference, which
95    /// is the caller's equality test.
96    pub fn set_list_value(
97        &self,
98        host: &dyn IServerRenderResourceHost,
99        old_children: Vec<Rc<dyn IServerRenderResource>>,
100        new_children: Vec<Rc<dyn IServerRenderResource>>,
101        assign: &mut dyn FnMut(),
102    ) {
103        if self.disposed.get() {
104            assign();
105            return;
106        }
107
108        let observer: Option<Rc<dyn IServerRenderResourceObserver>> =
109            host.as_render_resource_rc().map(|this| this as Rc<dyn IServerRenderResourceObserver>);
110
111        if let Some(observer) = &observer {
112            for child in &old_children {
113                child.remove_observer(observer);
114            }
115        }
116
117        assign();
118
119        if let Some(observer) = &observer {
120            for child in &new_children {
121                child.add_observer(observer);
122            }
123        }
124
125        self.invalidated(host);
126    }
127
128    /// Stops observing the render resource behind a property value.
129    pub fn remove_observers_from_property(
130        &self,
131        host: &dyn IServerRenderResourceHost,
132        resource: Option<&Rc<dyn IServerRenderResource>>,
133    ) {
134        if let (Some(resource), Some(this)) = (resource, host.as_render_resource_rc()) {
135            let observer: Rc<dyn IServerRenderResourceObserver> = this;
136            resource.remove_observer(&observer);
137        }
138    }
139
140    /// Records that a property of the resource changed. The first change
141    /// since the last queued invalidation enqueues the resource on its
142    /// compositor.
143    pub fn invalidated(&self, host: &dyn IServerRenderResourceHost) {
144        // This is needed to avoid triggering on multiple property changes.
145        if !self.pending_invalidation.get() {
146            self.pending_invalidation.set(true);
147            if let (Some(compositor), Some(this)) = (host.compositor(), host.as_render_resource_rc()) {
148                compositor.enqueue_render_resource_for_invalidation(this);
149            }
150            host.resource_property_changed();
151        }
152    }
153
154    pub fn dispose(&self) {
155        self.disposed.set(true);
156        self.observers.borrow_mut().clear();
157    }
158
159    pub fn dependency_queued_invalidate(&self, host: &dyn IServerRenderResourceHost) {
160        if let (Some(compositor), Some(this)) = (host.compositor(), host.as_render_resource_rc()) {
161            compositor.enqueue_render_resource_for_invalidation(this);
162        }
163    }
164
165    pub fn add_observer(&self, observer: &Rc<dyn IServerRenderResourceObserver>) {
166        debug_assert!(!self.disposed.get());
167        if self.disposed.get() {
168            return;
169        }
170        let mut observers = self.observers.borrow_mut();
171        match observers.iter_mut().find(|e| same_observer(&e.observer, observer)) {
172            Some(entry) => entry.count += 1,
173            None => observers.push(ObserverEntry { observer: Rc::downgrade(observer), count: 1 }),
174        }
175    }
176
177    pub fn remove_observer(&self, observer: &Rc<dyn IServerRenderResourceObserver>) {
178        if self.disposed.get() {
179            return;
180        }
181        let mut observers = self.observers.borrow_mut();
182        if let Some(index) = observers.iter().position(|e| same_observer(&e.observer, observer)) {
183            observers[index].count -= 1;
184            if observers[index].count == 0 {
185                observers.swap_remove(index);
186            }
187        }
188    }
189
190    pub fn queued_invalidate(&self, this: &dyn IServerRenderResource) {
191        self.pending_invalidation.set(false);
192        let observers: Vec<Rc<dyn IServerRenderResourceObserver>> = {
193            let mut observers = self.observers.borrow_mut();
194            observers.retain(|e| e.observer.strong_count() > 0);
195            observers.iter().filter_map(|e| e.observer.upgrade()).collect()
196        };
197        for observer in observers {
198            observer.dependency_queued_invalidate(this);
199        }
200    }
201
202    /// The number of distinct live observers.
203    pub fn observer_count(&self) -> usize {
204        self.observers.borrow().iter().filter(|e| e.observer.strong_count() > 0).count()
205    }
206}
207
208/// The base of the server-side render resources that are not animatable
209/// (upstream `SimpleServerRenderResource`), as a part the resource classes
210/// embed: the compositor, the resource core and the handle of the object as
211/// a render resource.
212pub struct SimpleServerRenderResource {
213    compositor: Weak<ServerCompositor>,
214    this: Weak<dyn IServerRenderResource>,
215    core: ServerRenderResourceCore,
216}
217
218impl SimpleServerRenderResource {
219    /// `this` is the object that embeds the part.
220    pub fn new(compositor: &Rc<ServerCompositor>, this: Weak<dyn IServerRenderResource>) -> Self {
221        Self { compositor: Rc::downgrade(compositor), this, core: ServerRenderResourceCore::new() }
222    }
223
224    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
225        self.compositor.upgrade()
226    }
227
228    pub fn core(&self) -> &ServerRenderResourceCore {
229        &self.core
230    }
231
232    pub fn is_disposed(&self) -> bool {
233        self.core.is_disposed()
234    }
235
236    pub fn as_render_resource_rc(&self) -> Option<Rc<dyn IServerRenderResource>> {
237        self.this.upgrade()
238    }
239}
240
241/// Implements the render resource contracts of a class that embeds a
242/// [`SimpleServerRenderResource`] in the field `$base`: the members
243/// `SimpleServerRenderResource` gives its subclasses upstream.
244///
245/// `SetValue` goes through the resource core, so a property that holds a
246/// render resource is observed and every change invalidates the resource.
247#[macro_export]
248#[doc(hidden)]
249macro_rules! __impl_simple_server_render_resource {
250    ($ty:ty, $base:ident) => {
251        impl $crate::rendering::composition::server::IServerRenderResourceObserver for $ty {
252            fn dependency_queued_invalidate(
253                &self,
254                _sender: &dyn $crate::rendering::composition::server::IServerRenderResource,
255            ) {
256                self.$base.core().dependency_queued_invalidate(self);
257            }
258        }
259
260        impl $crate::rendering::composition::server::IServerRenderResource for $ty {
261            fn add_observer(
262                &self,
263                observer: &::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResourceObserver>,
264            ) {
265                self.$base.core().add_observer(observer);
266            }
267
268            fn remove_observer(
269                &self,
270                observer: &::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResourceObserver>,
271            ) {
272                self.$base.core().remove_observer(observer);
273            }
274
275            fn queued_invalidate(&self) {
276                self.$base.core().queued_invalidate(self);
277            }
278        }
279
280        impl $crate::rendering::composition::server::IServerRenderResourceHost for $ty {
281            fn compositor(&self) -> Option<::std::rc::Rc<$crate::rendering::composition::server::ServerCompositor>> {
282                self.$base.compositor()
283            }
284
285            fn as_render_resource_rc(
286                &self,
287            ) -> Option<::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResource>> {
288                self.$base.as_render_resource_rc()
289            }
290        }
291
292        impl $crate::rendering::composition::server::IServerPropertyHost for $ty {
293            fn server_compositor(
294                &self,
295            ) -> Option<::std::rc::Rc<$crate::rendering::composition::server::ServerCompositor>> {
296                self.$base.compositor()
297            }
298
299            fn set_value(
300                &self,
301                _property: &'static $crate::rendering::composition::server::CompositionProperty,
302                change: $crate::rendering::composition::server::ServerValueChange<'_>,
303            ) {
304                self.$base.core().set_value(self, change);
305            }
306        }
307    };
308}
309
310pub use crate::__impl_simple_server_render_resource as impl_simple_server_render_resource;
311
312/// The base of the animatable server-side render resources (upstream
313/// `ServerRenderResource`, a `ServerObject`), as a part the resource classes
314/// embed: the animation support of the server object, the resource core and
315/// the handle of the object as a render resource.
316pub struct ServerRenderResource {
317    object: ServerObject,
318    this: Weak<dyn IServerRenderResource>,
319    core: ServerRenderResourceCore,
320}
321
322impl ServerRenderResource {
323    /// `owner` and `this` are the object that embeds the part.
324    pub fn new(
325        compositor: &Rc<ServerCompositor>,
326        owner: Weak<dyn IAnimatedServerObject>,
327        this: Weak<dyn IServerRenderResource>,
328    ) -> Self {
329        Self { object: ServerObject::new(compositor, owner), this, core: ServerRenderResourceCore::new() }
330    }
331
332    /// The embedded `ServerObject`.
333    pub fn object(&self) -> &ServerObject {
334        &self.object
335    }
336
337    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
338        self.object.compositor()
339    }
340
341    pub fn core(&self) -> &ServerRenderResourceCore {
342        &self.core
343    }
344
345    pub fn is_disposed(&self) -> bool {
346        self.core.is_disposed()
347    }
348
349    pub fn as_render_resource_rc(&self) -> Option<Rc<dyn IServerRenderResource>> {
350        self.this.upgrade()
351    }
352}
353
354/// Implements the render resource and animation contracts of a class that
355/// embeds a [`ServerRenderResource`] in the field `$base`: the members
356/// `ServerRenderResource` gives its subclasses upstream.
357///
358/// As upstream, `SetValue` goes through the resource core only: the
359/// `SetValue` of the render resource hides the one of `ServerObject`, so a
360/// direct set does not invalidate the animations that depend on the
361/// property. `$get` resolves a composition property by name.
362///
363/// The class implements `IServerObject` itself, with `values_invalidated`
364/// invalidating the resource (`self.$base.core().invalidated(self)`).
365#[macro_export]
366#[doc(hidden)]
367macro_rules! __impl_server_render_resource {
368    ($ty:ty, $base:ident, composition_property: $get:expr) => {
369        $crate::rendering::composition::server::impl_server_render_resource!(@resource $ty, $base);
370
371        impl $crate::rendering::composition::server::IServerAnimatedPropertyHost for $ty {
372            fn set_animated_value(
373                &self,
374                property: &'static $crate::rendering::composition::server::CompositionProperty,
375                current_value: $crate::rendering::composition::expressions::ExpressionVariant,
376                committed_at: ::std::time::Duration,
377                animation: ::std::rc::Rc<dyn $crate::rendering::composition::animations::IAnimationInstance>,
378            ) {
379                self.$base.object().set_animated_value(property, current_value, committed_at, animation);
380            }
381
382            fn remove_animation_for_property(
383                &self,
384                property: &'static $crate::rendering::composition::server::CompositionProperty,
385            ) {
386                self.$base.object().remove_animation_for_property(property);
387            }
388
389            fn notify_animated_value_changed(
390                &self,
391                _property: &'static $crate::rendering::composition::server::CompositionProperty,
392            ) {
393                $crate::rendering::composition::server::IServerObject::values_invalidated(self);
394            }
395        }
396
397        impl $crate::rendering::composition::server::IAnimatedServerObject for $ty {
398            fn server_object(&self) -> &$crate::rendering::composition::server::ServerObject {
399                self.$base.object()
400            }
401
402            fn get_composition_property(
403                &self,
404                field_name: &str,
405            ) -> Option<&'static $crate::rendering::composition::server::CompositionProperty> {
406                ($get)(field_name)
407            }
408
409            fn as_server_object_dyn(&self) -> &dyn $crate::rendering::composition::server::IServerObject {
410                self
411            }
412        }
413    };
414    (@resource $ty:ty, $base:ident) => {
415        impl $crate::rendering::composition::server::IServerRenderResourceObserver for $ty {
416            fn dependency_queued_invalidate(
417                &self,
418                _sender: &dyn $crate::rendering::composition::server::IServerRenderResource,
419            ) {
420                self.$base.core().dependency_queued_invalidate(self);
421            }
422        }
423
424        impl $crate::rendering::composition::server::IServerRenderResource for $ty {
425            fn add_observer(
426                &self,
427                observer: &::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResourceObserver>,
428            ) {
429                self.$base.core().add_observer(observer);
430            }
431
432            fn remove_observer(
433                &self,
434                observer: &::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResourceObserver>,
435            ) {
436                self.$base.core().remove_observer(observer);
437            }
438
439            fn queued_invalidate(&self) {
440                self.$base.core().queued_invalidate(self);
441            }
442        }
443
444        impl $crate::rendering::composition::server::IServerRenderResourceHost for $ty {
445            fn compositor(&self) -> Option<::std::rc::Rc<$crate::rendering::composition::server::ServerCompositor>> {
446                self.$base.compositor()
447            }
448
449            fn as_render_resource_rc(
450                &self,
451            ) -> Option<::std::rc::Rc<dyn $crate::rendering::composition::server::IServerRenderResource>> {
452                self.$base.as_render_resource_rc()
453            }
454        }
455
456        impl $crate::rendering::composition::server::IServerPropertyHost for $ty {
457            fn server_compositor(
458                &self,
459            ) -> Option<::std::rc::Rc<$crate::rendering::composition::server::ServerCompositor>> {
460                self.$base.compositor()
461            }
462
463            fn set_value(
464                &self,
465                _property: &'static $crate::rendering::composition::server::CompositionProperty,
466                change: $crate::rendering::composition::server::ServerValueChange<'_>,
467            ) {
468                self.$base.core().set_value(self, change);
469            }
470        }
471    };
472}
473
474pub use crate::__impl_server_render_resource as impl_server_render_resource;