Skip to main content

ferroui_base/rendering/composition/server/
server_object.rs

1use super::{
2    CompositionProperty, IServerAnimatedPropertyHost, IServerRenderResource, ServerCompositor, ServerObjectAnimations,
3    ServerValueChange,
4};
5use crate::rendering::composition::animations::IAnimationInstance;
6use crate::rendering::composition::expressions::{ExpressionObjectKey, ExpressionVariant, IExpressionObject};
7use crate::media::{IBrush, IGradientStop, IPen, ITransform};
8use crate::rendering::composition::drawing::IRenderDataGeometry;
9use crate::rendering::composition::transport::BatchStreamReader;
10use std::any::{Any, TypeId};
11use std::cell::{Cell, RefCell};
12use std::rc::{Rc, Weak};
13use std::time::Duration;
14
15/// Identifies a server-side object within its server compositor.
16///
17/// UI-thread objects never hold a server object; they hold its id, and the
18/// batch that creates, changes or disposes the object refers to it by id.
19/// The id is a plain value that can cross threads.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub struct ServerObjectId(pub(crate) u32);
22
23impl ServerObjectId {
24    /// The slot index of the object.
25    #[inline]
26    pub fn index(self) -> usize {
27        self.0 as usize
28    }
29}
30
31/// The base contract of every server-side object (upstream
32/// `SimpleServerObject`).
33pub trait IServerObject: 'static {
34    /// Reads the changes of the object from a batch.
35    fn deserialize_changes_core(&self, _reader: &mut BatchStreamReader<'_>, _committed_at: Duration) {}
36
37    /// Called after a group of values of the object changed.
38    fn values_invalidated(&self) {}
39
40    /// Applies the changes of the object from a batch.
41    fn deserialize_changes(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
42        self.deserialize_changes_core(reader, committed_at);
43        self.values_invalidated();
44    }
45
46    /// Releases the object: it has been disposed on the UI thread.
47    fn dispose(&self) {}
48
49    /// The object as a render resource, if it is one.
50    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
51        None
52    }
53
54    /// The object as a brush, if it is a server-side brush.
55    fn as_brush(self: Rc<Self>) -> Option<Rc<dyn IBrush>> {
56        None
57    }
58
59    /// The object as a pen, if it is a server-side pen.
60    fn as_pen(self: Rc<Self>) -> Option<Rc<dyn IPen>> {
61        None
62    }
63
64    /// The object as a geometry usable by render data, if it is a
65    /// server-side geometry.
66    fn as_render_data_geometry(self: Rc<Self>) -> Option<Rc<dyn IRenderDataGeometry>> {
67        None
68    }
69
70    /// The object as a transform, if it is a server-side transform.
71    fn as_transform(self: Rc<Self>) -> Option<Rc<dyn ITransform>> {
72        None
73    }
74
75    /// The object as a gradient stop, if it is a server-side gradient stop.
76    fn as_gradient_stop(self: Rc<Self>) -> Option<Rc<dyn IGradientStop>> {
77        None
78    }
79
80    /// The object as a composition surface, if it is one.
81    fn as_surface(self: Rc<Self>) -> Option<Rc<dyn super::IServerCompositionSurface>> {
82        None
83    }
84
85    /// The object as an animatable object, if it is one.
86    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
87        None
88    }
89
90    /// The generated property block of the given type embedded in the
91    /// object, if it has one. A class that embeds generated properties
92    /// forwards to their `find_props`; composition properties locate their
93    /// fields through it.
94    fn get_props(&self, _type_id: TypeId) -> Option<&dyn Any> {
95        None
96    }
97
98    fn as_any(&self) -> &dyn Any;
99
100    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any>;
101}
102
103/// A server object that supports animated properties: what upstream's
104/// abstract `ServerObject` is to its subclasses.
105///
106/// A class "derives" from `ServerObject` by embedding a [`ServerObject`]
107/// and implementing this trait; the embedded part reaches the class through
108/// it.
109pub trait IAnimatedServerObject: IServerObject + IServerAnimatedPropertyHost {
110    /// The embedded `ServerObject` part.
111    fn server_object(&self) -> &ServerObject;
112
113    /// The composition property with the given name, if the class has one.
114    fn get_composition_property(&self, _field_name: &str) -> Option<&'static CompositionProperty> {
115        None
116    }
117
118    /// The object as the base contract (an explicit upcast).
119    fn as_server_object_dyn(&self) -> &dyn IServerObject;
120
121    /// Reads a property of the object as a variant. The default reads the field through
122    /// [`CompositionProperty::get_variant`]; an object with dynamic properties (a property set)
123    /// reads its own storage.
124    fn read_property_variant(&self, property: &'static CompositionProperty) -> ExpressionVariant {
125        property.get_variant().map(|get| get(self.as_server_object_dyn())).unwrap_or_default()
126    }
127
128    /// Writes a variant into a property of the object without notification (what an animation
129    /// does with the value it evaluated). The default writes the field through
130    /// [`CompositionProperty::set_variant`]; an object with dynamic properties writes its own
131    /// storage.
132    fn write_property_variant(&self, property: &'static CompositionProperty, value: ExpressionVariant) {
133        if let Some(set) = property.set_variant() {
134            set(self.as_server_object_dyn(), value);
135        }
136    }
137}
138
139/// An animatable server object as an expression reads it: the target of an
140/// animation, or an object an animation refers to through a reference
141/// parameter (upstream passes the `ServerObject` itself, which implements
142/// `IExpressionObject`).
143#[derive(Clone)]
144pub struct ServerExpressionObject(pub Rc<dyn IAnimatedServerObject>);
145
146impl IExpressionObject for ServerExpressionObject {
147    fn get_property(&self, name: &str) -> ExpressionVariant {
148        self.0.server_object().get_property(name)
149    }
150
151    fn key(&self) -> ExpressionObjectKey {
152        ExpressionObjectKey(Rc::as_ptr(&self.0) as *const () as usize)
153    }
154}
155
156/// Server-side `CompositionObject` counterpart. Is responsible for
157/// animation activation and invalidation.
158pub struct ServerObject {
159    compositor: Weak<ServerCompositor>,
160    owner: Weak<dyn IAnimatedServerObject>,
161    activation_count: Cell<u32>,
162    animations: RefCell<Option<Rc<ServerObjectAnimations>>>,
163}
164
165impl ServerObject {
166    /// `owner` is the object that embeds this part.
167    pub fn new(compositor: &Rc<ServerCompositor>, owner: Weak<dyn IAnimatedServerObject>) -> Self {
168        Self {
169            compositor: Rc::downgrade(compositor),
170            owner,
171            activation_count: Cell::new(0),
172            animations: RefCell::new(None),
173        }
174    }
175
176    /// The compositor of the object, while it is alive.
177    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
178        self.compositor.upgrade()
179    }
180
181    pub fn animations(&self) -> Option<Rc<ServerObjectAnimations>> {
182        self.animations.borrow().clone()
183    }
184
185    pub fn get_or_create_animations(&self) -> Rc<ServerObjectAnimations> {
186        self.animations
187            .borrow_mut()
188            .get_or_insert_with(|| Rc::new(ServerObjectAnimations::new(self.owner.clone(), self.compositor.clone())))
189            .clone()
190    }
191
192    pub fn is_active(&self) -> bool {
193        self.activation_count.get() != 0
194    }
195
196    pub fn activate(&self) {
197        self.activation_count.set(self.activation_count.get() + 1);
198        if self.activation_count.get() == 1 {
199            self.activated();
200        }
201    }
202
203    pub fn deactivate(&self) {
204        if cfg!(debug_assertions) && self.activation_count.get() == 0 {
205            panic!("the server object is not active");
206        }
207        self.activation_count.set(self.activation_count.get().saturating_sub(1));
208        if self.activation_count.get() == 0 {
209            self.deactivated();
210        }
211    }
212
213    fn activated(&self) {
214        if let Some(animations) = self.animations() {
215            animations.activated();
216        }
217    }
218
219    fn deactivated(&self) {
220        if let Some(animations) = self.animations() {
221            animations.deactivated();
222        }
223    }
224
225    /// `SetValue(prop, ref field, value)` of an animatable object: assigns
226    /// and invalidates the subscriptions of the property.
227    pub fn set_value(&self, property: &'static CompositionProperty, change: ServerValueChange<'_>) {
228        (change.assign)();
229        if let Some(animations) = self.animations() {
230            animations.on_set_direct_value(property);
231        }
232    }
233
234    /// `SetAnimatedValue(prop, ref field, committedAt, animation)`.
235    pub fn set_animated_value(
236        &self,
237        property: &'static CompositionProperty,
238        current_value: ExpressionVariant,
239        committed_at: Duration,
240        animation: Rc<dyn IAnimationInstance>,
241    ) {
242        self.get_or_create_animations().on_set_animated_value(property, current_value, committed_at, animation);
243    }
244
245    /// The tail of `SetAnimatedValue(prop, out field, value)`.
246    pub fn remove_animation_for_property(&self, property: &'static CompositionProperty) {
247        if let Some(animations) = self.animations() {
248            animations.remove_animation_for_property(property);
249        }
250    }
251
252    /// `IExpressionObject.GetProperty` of the owner.
253    pub fn get_property(&self, name: &str) -> ExpressionVariant {
254        match self.animations() {
255            None => {
256                let Some(owner) = self.owner.upgrade() else { return ExpressionVariant::default() };
257                owner
258                    .get_composition_property(name)
259                    .map(|property| owner.read_property_variant(property))
260                    .unwrap_or_default()
261            }
262            Some(animations) => animations.get_property_for_animation(name),
263        }
264    }
265}
266
267/// Implements the property-host and animation contracts of a class that
268/// embeds a [`ServerObject`] in the field `$object` and a
269/// `Weak<ServerCompositor>` in the field `compositor`: what deriving from
270/// upstream's `ServerObject` provides.
271#[doc(hidden)]
272#[macro_export]
273macro_rules! __impl_animated_server_object {
274    ($ty:ty, $object:ident $(, composition_property: $get:expr)?) => {
275        impl $crate::rendering::composition::server::IServerPropertyHost for $ty {
276            fn server_compositor(
277                &self,
278            ) -> Option<::std::rc::Rc<$crate::rendering::composition::server::ServerCompositor>> {
279                self.$object.compositor()
280            }
281
282            fn set_value(
283                &self,
284                property: &'static $crate::rendering::composition::server::CompositionProperty,
285                change: $crate::rendering::composition::server::ServerValueChange<'_>,
286            ) {
287                self.$object.set_value(property, change);
288            }
289        }
290
291        impl $crate::rendering::composition::server::IServerAnimatedPropertyHost for $ty {
292            fn set_animated_value(
293                &self,
294                property: &'static $crate::rendering::composition::server::CompositionProperty,
295                current_value: $crate::rendering::composition::expressions::ExpressionVariant,
296                committed_at: ::std::time::Duration,
297                animation: ::std::rc::Rc<dyn $crate::rendering::composition::animations::IAnimationInstance>,
298            ) {
299                self.$object.set_animated_value(property, current_value, committed_at, animation);
300            }
301
302            fn remove_animation_for_property(
303                &self,
304                property: &'static $crate::rendering::composition::server::CompositionProperty,
305            ) {
306                self.$object.remove_animation_for_property(property);
307            }
308
309            fn notify_animated_value_changed(
310                &self,
311                _property: &'static $crate::rendering::composition::server::CompositionProperty,
312            ) {
313                $crate::rendering::composition::server::IServerObject::values_invalidated(self);
314            }
315        }
316
317        impl $crate::rendering::composition::server::IAnimatedServerObject for $ty {
318            fn server_object(&self) -> &$crate::rendering::composition::server::ServerObject {
319                &self.$object
320            }
321
322            $(fn get_composition_property(
323                &self,
324                field_name: &str,
325            ) -> Option<&'static $crate::rendering::composition::server::CompositionProperty> {
326                ($get)(field_name)
327            })?
328
329            fn as_server_object_dyn(&self) -> &dyn $crate::rendering::composition::server::IServerObject {
330                self
331            }
332        }
333    };
334}
335
336pub use crate::__impl_animated_server_object as impl_animated_server_object;