Skip to main content

ferroui_base/rendering/composition/generated/
point_light.rs

1// @generated by scripts/generate_composition.py — do not edit
2
3//! `PointLight`.
4//!
5//! Defined by the FerroUI overlay (scripts/composition-schema-winui.xml).
6
7use bitflags::bitflags;
8use crate::Vector3D;
9use crate::media::Color;
10use crate::rendering::composition::ICompositionObject;
11use crate::rendering::composition::animations::AnimationInstanceFactory;
12use crate::rendering::composition::animations::ICompositionAnimation;
13use crate::rendering::composition::expressions::ExpressionVariant;
14use crate::rendering::composition::server::AsServerRenderResource;
15use crate::rendering::composition::server::CompositionProperty;
16use crate::rendering::composition::server::CompositionPropertyOf;
17use crate::rendering::composition::server::IServerObject;
18use crate::rendering::composition::server::ServerPropertyValue;
19use crate::rendering::composition::server::ServerValueChange;
20use crate::rendering::composition::server::props_of;
21use crate::rendering::composition::server::read_server_object;
22use crate::rendering::composition::transport::BatchStreamReader;
23use crate::rendering::composition::transport::BatchStreamWriter;
24use crate::rendering::composition::transport::BatchValue;
25use crate::rendering::composition::transport::BatchValueReader;
26use std::any::Any;
27use std::any::TypeId;
28use std::cell::Cell;
29use std::cell::RefCell;
30use std::rc::Rc;
31use std::sync::OnceLock;
32use std::time::Duration;
33use super::CompositionLightHooks;
34use super::CompositionLightProps;
35use super::ServerCompositionLightHooks;
36use super::ServerCompositionLightProps;
37
38bitflags! {
39    /// The fields of `PointLight` that changed since its last serialization.
40    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
41    pub struct PointLightChangedFields: u32 {
42        const COLOR = 1;
43        const COLOR_ANIMATED = 2;
44        const CONSTANT_ATTENUATION = 4;
45        const CONSTANT_ATTENUATION_ANIMATED = 8;
46        const LINEAR_ATTENUATION = 16;
47        const LINEAR_ATTENUATION_ANIMATED = 32;
48        const QUADRATIC_ATTENUATION = 64;
49        const QUADRATIC_ATTENUATION_ANIMATED = 128;
50        const COORDINATE_SPACE = 256;
51        const OFFSET = 512;
52        const OFFSET_ANIMATED = 1024;
53        const INTENSITY = 2048;
54        const INTENSITY_ANIMATED = 4096;
55        const MIN_ATTENUATION_CUTOFF = 8192;
56        const MIN_ATTENUATION_CUTOFF_ANIMATED = 16384;
57        const MAX_ATTENUATION_CUTOFF = 32768;
58        const MAX_ATTENUATION_CUTOFF_ANIMATED = 65536;
59    }
60}
61
62impl BatchValue for PointLightChangedFields {
63    #[inline]
64    fn write_to(&self, out: &mut Vec<u8>) {
65        self.bits().write_to(out);
66    }
67
68    #[inline]
69    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
70        PointLightChangedFields::from_bits_retain(u32::read_from(input))
71    }
72}
73
74/// The partial methods of `PointLight`: implemented by the class that embeds
75/// [`PointLightProps`]. Every hook defaults to doing nothing.
76pub trait PointLightHooks: CompositionLightHooks {
77    fn initialize_defaults_extra(&self) {}
78
79    fn validate_color_change(&self, _old_value: Color, _new_value: Color) {}
80    fn on_color_changed(&self) {}
81    fn on_color_changing(&self) {}
82
83    fn validate_constant_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
84    fn on_constant_attenuation_changed(&self) {}
85    fn on_constant_attenuation_changing(&self) {}
86
87    fn validate_linear_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
88    fn on_linear_attenuation_changed(&self) {}
89    fn on_linear_attenuation_changing(&self) {}
90
91    fn validate_quadratic_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
92    fn on_quadratic_attenuation_changed(&self) {}
93    fn on_quadratic_attenuation_changing(&self) {}
94
95    fn validate_coordinate_space_change(&self, _old_value: &Option<Rc<dyn ICompositionObject>>, _new_value: &Option<Rc<dyn ICompositionObject>>) {}
96    fn on_coordinate_space_changed(&self) {}
97    fn on_coordinate_space_changing(&self) {}
98
99    fn validate_offset_change(&self, _old_value: Vector3D, _new_value: Vector3D) {}
100    fn on_offset_changed(&self) {}
101    fn on_offset_changing(&self) {}
102
103    fn validate_intensity_change(&self, _old_value: f32, _new_value: f32) {}
104    fn on_intensity_changed(&self) {}
105    fn on_intensity_changing(&self) {}
106
107    fn validate_min_attenuation_cutoff_change(&self, _old_value: f32, _new_value: f32) {}
108    fn on_min_attenuation_cutoff_changed(&self) {}
109    fn on_min_attenuation_cutoff_changing(&self) {}
110
111    fn validate_max_attenuation_cutoff_change(&self, _old_value: f32, _new_value: f32) {}
112    fn on_max_attenuation_cutoff_changed(&self) {}
113    fn on_max_attenuation_cutoff_changing(&self) {}
114}
115
116/// The UI-thread properties of `PointLight`.
117pub struct PointLightProps {
118    base: CompositionLightProps,
119    changed_fields_of_point_light: Cell<PointLightChangedFields>,
120    color: Cell<Color>,
121    constant_attenuation: Cell<f32>,
122    linear_attenuation: Cell<f32>,
123    quadratic_attenuation: Cell<f32>,
124    coordinate_space: RefCell<Option<Rc<dyn ICompositionObject>>>,
125    offset: Cell<Vector3D>,
126    intensity: Cell<f32>,
127    min_attenuation_cutoff: Cell<f32>,
128    max_attenuation_cutoff: Cell<f32>,
129}
130
131impl Default for PointLightProps {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl PointLightProps {
138    /// Creates the properties with every field at the default of its type.
139    /// The defaults of the schema are applied by
140    /// [`initialize_defaults`](Self::initialize_defaults).
141    pub fn new() -> Self {
142        Self {
143            base: CompositionLightProps::new(),
144            changed_fields_of_point_light: Cell::new(PointLightChangedFields::empty()),
145            color: Cell::new(Default::default()),
146            constant_attenuation: Cell::new(Default::default()),
147            linear_attenuation: Cell::new(Default::default()),
148            quadratic_attenuation: Cell::new(Default::default()),
149            coordinate_space: RefCell::new(None),
150            offset: Cell::new(Default::default()),
151            intensity: Cell::new(Default::default()),
152            min_attenuation_cutoff: Cell::new(Default::default()),
153            max_attenuation_cutoff: Cell::new(Default::default()),
154        }
155    }
156
157    /// The properties of the base class.
158    pub fn base(&self) -> &CompositionLightProps {
159        &self.base
160    }
161
162    /// The `InitializeDefaults` calls of the constructor chain: the base
163    /// classes first, then this class.
164    pub fn initialize_defaults(&self, host: &dyn PointLightHooks) {
165        self.base.initialize_defaults(host);
166        self.initialize_own_defaults(host);
167    }
168
169    /// `InitializeDefaults` of this class alone.
170    pub fn initialize_own_defaults(&self, host: &dyn PointLightHooks) {
171        PointLightHooks::initialize_defaults_extra(host);
172        self.set_color(host, crate::media::Colors::WHITE);
173        self.set_constant_attenuation(host, 1.0);
174        self.set_intensity(host, 1.0);
175        self.set_max_attenuation_cutoff(host, f32::MAX);
176    }
177
178    /// `Color` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
179    pub fn color(&self) -> Color {
180        self.color.get()
181    }
182
183    /// Sets `Color` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
184    pub fn set_color(&self, host: &dyn PointLightHooks, value: Color) {
185        let mut changed = false;
186        let old_value = self.color.get();
187        if old_value.differs(&value) {
188            PointLightHooks::validate_color_change(host, old_value, value);
189            PointLightHooks::on_color_changing(host);
190            changed = true;
191            // Update the backing value
192            self.color.set(value);
193            // Register object for serialization in the next batch
194            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::COLOR);
195            host.register_for_serialization();
196            // Reset previous animation if any
197            host.pending_animations().remove(ServerPointLightProps::id_of_color_property().base());
198            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::COLOR_ANIMATED);
199            // Check for implicit animations
200            if let Some(animation) = host.implicit_animation("Color") {
201                // Animation affects only current property
202                if let Some(a) = animation.as_composition_animation() {
203                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::COLOR_ANIMATED);
204                    host.pending_animations().set(
205                        ServerPointLightProps::id_of_color_property().base(),
206                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
207                    );
208                }
209                // Animation is triggered by the current field, but does not necessary affects it
210                host.start_animation_group(&animation, "Color", ExpressionVariant::create(value));
211            }
212        }
213        self.color.set(value);
214        if changed {
215            PointLightHooks::on_color_changed(host);
216        }
217    }
218
219    /// `ConstantAttenuation` (animated, default `1.0`, FerroUI overlay).
220    pub fn constant_attenuation(&self) -> f32 {
221        self.constant_attenuation.get()
222    }
223
224    /// Sets `ConstantAttenuation` (animated, default `1.0`, FerroUI overlay).
225    pub fn set_constant_attenuation(&self, host: &dyn PointLightHooks, value: f32) {
226        let mut changed = false;
227        let old_value = self.constant_attenuation.get();
228        if old_value.differs(&value) {
229            PointLightHooks::validate_constant_attenuation_change(host, old_value, value);
230            PointLightHooks::on_constant_attenuation_changing(host);
231            changed = true;
232            // Update the backing value
233            self.constant_attenuation.set(value);
234            // Register object for serialization in the next batch
235            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::CONSTANT_ATTENUATION);
236            host.register_for_serialization();
237            // Reset previous animation if any
238            host.pending_animations().remove(ServerPointLightProps::id_of_constant_attenuation_property().base());
239            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
240            // Check for implicit animations
241            if let Some(animation) = host.implicit_animation("ConstantAttenuation") {
242                // Animation affects only current property
243                if let Some(a) = animation.as_composition_animation() {
244                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
245                    host.pending_animations().set(
246                        ServerPointLightProps::id_of_constant_attenuation_property().base(),
247                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
248                    );
249                }
250                // Animation is triggered by the current field, but does not necessary affects it
251                host.start_animation_group(&animation, "ConstantAttenuation", ExpressionVariant::create(value));
252            }
253        }
254        self.constant_attenuation.set(value);
255        if changed {
256            PointLightHooks::on_constant_attenuation_changed(host);
257        }
258    }
259
260    /// `LinearAttenuation` (animated, FerroUI overlay).
261    pub fn linear_attenuation(&self) -> f32 {
262        self.linear_attenuation.get()
263    }
264
265    /// Sets `LinearAttenuation` (animated, FerroUI overlay).
266    pub fn set_linear_attenuation(&self, host: &dyn PointLightHooks, value: f32) {
267        let mut changed = false;
268        let old_value = self.linear_attenuation.get();
269        if old_value.differs(&value) {
270            PointLightHooks::validate_linear_attenuation_change(host, old_value, value);
271            PointLightHooks::on_linear_attenuation_changing(host);
272            changed = true;
273            // Update the backing value
274            self.linear_attenuation.set(value);
275            // Register object for serialization in the next batch
276            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::LINEAR_ATTENUATION);
277            host.register_for_serialization();
278            // Reset previous animation if any
279            host.pending_animations().remove(ServerPointLightProps::id_of_linear_attenuation_property().base());
280            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
281            // Check for implicit animations
282            if let Some(animation) = host.implicit_animation("LinearAttenuation") {
283                // Animation affects only current property
284                if let Some(a) = animation.as_composition_animation() {
285                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
286                    host.pending_animations().set(
287                        ServerPointLightProps::id_of_linear_attenuation_property().base(),
288                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
289                    );
290                }
291                // Animation is triggered by the current field, but does not necessary affects it
292                host.start_animation_group(&animation, "LinearAttenuation", ExpressionVariant::create(value));
293            }
294        }
295        self.linear_attenuation.set(value);
296        if changed {
297            PointLightHooks::on_linear_attenuation_changed(host);
298        }
299    }
300
301    /// `QuadraticAttenuation` (animated, FerroUI overlay).
302    pub fn quadratic_attenuation(&self) -> f32 {
303        self.quadratic_attenuation.get()
304    }
305
306    /// Sets `QuadraticAttenuation` (animated, FerroUI overlay).
307    pub fn set_quadratic_attenuation(&self, host: &dyn PointLightHooks, value: f32) {
308        let mut changed = false;
309        let old_value = self.quadratic_attenuation.get();
310        if old_value.differs(&value) {
311            PointLightHooks::validate_quadratic_attenuation_change(host, old_value, value);
312            PointLightHooks::on_quadratic_attenuation_changing(host);
313            changed = true;
314            // Update the backing value
315            self.quadratic_attenuation.set(value);
316            // Register object for serialization in the next batch
317            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::QUADRATIC_ATTENUATION);
318            host.register_for_serialization();
319            // Reset previous animation if any
320            host.pending_animations().remove(ServerPointLightProps::id_of_quadratic_attenuation_property().base());
321            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
322            // Check for implicit animations
323            if let Some(animation) = host.implicit_animation("QuadraticAttenuation") {
324                // Animation affects only current property
325                if let Some(a) = animation.as_composition_animation() {
326                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
327                    host.pending_animations().set(
328                        ServerPointLightProps::id_of_quadratic_attenuation_property().base(),
329                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
330                    );
331                }
332                // Animation is triggered by the current field, but does not necessary affects it
333                host.start_animation_group(&animation, "QuadraticAttenuation", ExpressionVariant::create(value));
334            }
335        }
336        self.quadratic_attenuation.set(value);
337        if changed {
338            PointLightHooks::on_quadratic_attenuation_changed(host);
339        }
340    }
341
342    /// `CoordinateSpace` (FerroUI overlay).
343    pub fn coordinate_space(&self) -> Option<Rc<dyn ICompositionObject>> {
344        self.coordinate_space.borrow().clone()
345    }
346
347    /// Sets `CoordinateSpace` (FerroUI overlay).
348    pub fn set_coordinate_space(&self, host: &dyn PointLightHooks, value: Option<Rc<dyn ICompositionObject>>) {
349        let mut changed = false;
350        let old_value = self.coordinate_space.borrow().clone();
351        if old_value.differs(&value) {
352            PointLightHooks::validate_coordinate_space_change(host, &old_value, &value);
353            PointLightHooks::on_coordinate_space_changing(host);
354            changed = true;
355            // Update the backing value
356            *self.coordinate_space.borrow_mut() = value.clone();
357            // Register object for serialization in the next batch
358            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::COORDINATE_SPACE);
359            host.register_for_serialization();
360        }
361        *self.coordinate_space.borrow_mut() = value;
362        if changed {
363            PointLightHooks::on_coordinate_space_changed(host);
364        }
365    }
366
367    /// `Offset` (animated, FerroUI overlay).
368    pub fn offset(&self) -> Vector3D {
369        self.offset.get()
370    }
371
372    /// Sets `Offset` (animated, FerroUI overlay).
373    pub fn set_offset(&self, host: &dyn PointLightHooks, value: Vector3D) {
374        let mut changed = false;
375        let old_value = self.offset.get();
376        if old_value.differs(&value) {
377            PointLightHooks::validate_offset_change(host, old_value, value);
378            PointLightHooks::on_offset_changing(host);
379            changed = true;
380            // Update the backing value
381            self.offset.set(value);
382            // Register object for serialization in the next batch
383            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::OFFSET);
384            host.register_for_serialization();
385            // Reset previous animation if any
386            host.pending_animations().remove(ServerPointLightProps::id_of_offset_property().base());
387            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::OFFSET_ANIMATED);
388            // Check for implicit animations
389            if let Some(animation) = host.implicit_animation("Offset") {
390                // Animation affects only current property
391                if let Some(a) = animation.as_composition_animation() {
392                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::OFFSET_ANIMATED);
393                    host.pending_animations().set(
394                        ServerPointLightProps::id_of_offset_property().base(),
395                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
396                    );
397                }
398                // Animation is triggered by the current field, but does not necessary affects it
399                host.start_animation_group(&animation, "Offset", ExpressionVariant::create(value));
400            }
401        }
402        self.offset.set(value);
403        if changed {
404            PointLightHooks::on_offset_changed(host);
405        }
406    }
407
408    /// `Intensity` (animated, default `1.0`, FerroUI overlay).
409    pub fn intensity(&self) -> f32 {
410        self.intensity.get()
411    }
412
413    /// Sets `Intensity` (animated, default `1.0`, FerroUI overlay).
414    pub fn set_intensity(&self, host: &dyn PointLightHooks, value: f32) {
415        let mut changed = false;
416        let old_value = self.intensity.get();
417        if old_value.differs(&value) {
418            PointLightHooks::validate_intensity_change(host, old_value, value);
419            PointLightHooks::on_intensity_changing(host);
420            changed = true;
421            // Update the backing value
422            self.intensity.set(value);
423            // Register object for serialization in the next batch
424            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::INTENSITY);
425            host.register_for_serialization();
426            // Reset previous animation if any
427            host.pending_animations().remove(ServerPointLightProps::id_of_intensity_property().base());
428            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::INTENSITY_ANIMATED);
429            // Check for implicit animations
430            if let Some(animation) = host.implicit_animation("Intensity") {
431                // Animation affects only current property
432                if let Some(a) = animation.as_composition_animation() {
433                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::INTENSITY_ANIMATED);
434                    host.pending_animations().set(
435                        ServerPointLightProps::id_of_intensity_property().base(),
436                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
437                    );
438                }
439                // Animation is triggered by the current field, but does not necessary affects it
440                host.start_animation_group(&animation, "Intensity", ExpressionVariant::create(value));
441            }
442        }
443        self.intensity.set(value);
444        if changed {
445            PointLightHooks::on_intensity_changed(host);
446        }
447    }
448
449    /// `MinAttenuationCutoff` (animated, FerroUI overlay).
450    pub fn min_attenuation_cutoff(&self) -> f32 {
451        self.min_attenuation_cutoff.get()
452    }
453
454    /// Sets `MinAttenuationCutoff` (animated, FerroUI overlay).
455    pub fn set_min_attenuation_cutoff(&self, host: &dyn PointLightHooks, value: f32) {
456        let mut changed = false;
457        let old_value = self.min_attenuation_cutoff.get();
458        if old_value.differs(&value) {
459            PointLightHooks::validate_min_attenuation_cutoff_change(host, old_value, value);
460            PointLightHooks::on_min_attenuation_cutoff_changing(host);
461            changed = true;
462            // Update the backing value
463            self.min_attenuation_cutoff.set(value);
464            // Register object for serialization in the next batch
465            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MIN_ATTENUATION_CUTOFF);
466            host.register_for_serialization();
467            // Reset previous animation if any
468            host.pending_animations().remove(ServerPointLightProps::id_of_min_attenuation_cutoff_property().base());
469            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
470            // Check for implicit animations
471            if let Some(animation) = host.implicit_animation("MinAttenuationCutoff") {
472                // Animation affects only current property
473                if let Some(a) = animation.as_composition_animation() {
474                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
475                    host.pending_animations().set(
476                        ServerPointLightProps::id_of_min_attenuation_cutoff_property().base(),
477                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
478                    );
479                }
480                // Animation is triggered by the current field, but does not necessary affects it
481                host.start_animation_group(&animation, "MinAttenuationCutoff", ExpressionVariant::create(value));
482            }
483        }
484        self.min_attenuation_cutoff.set(value);
485        if changed {
486            PointLightHooks::on_min_attenuation_cutoff_changed(host);
487        }
488    }
489
490    /// `MaxAttenuationCutoff` (animated, default `f32::MAX`, FerroUI overlay).
491    pub fn max_attenuation_cutoff(&self) -> f32 {
492        self.max_attenuation_cutoff.get()
493    }
494
495    /// Sets `MaxAttenuationCutoff` (animated, default `f32::MAX`, FerroUI overlay).
496    pub fn set_max_attenuation_cutoff(&self, host: &dyn PointLightHooks, value: f32) {
497        let mut changed = false;
498        let old_value = self.max_attenuation_cutoff.get();
499        if old_value.differs(&value) {
500            PointLightHooks::validate_max_attenuation_cutoff_change(host, old_value, value);
501            PointLightHooks::on_max_attenuation_cutoff_changing(host);
502            changed = true;
503            // Update the backing value
504            self.max_attenuation_cutoff.set(value);
505            // Register object for serialization in the next batch
506            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MAX_ATTENUATION_CUTOFF);
507            host.register_for_serialization();
508            // Reset previous animation if any
509            host.pending_animations().remove(ServerPointLightProps::id_of_max_attenuation_cutoff_property().base());
510            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() & !PointLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
511            // Check for implicit animations
512            if let Some(animation) = host.implicit_animation("MaxAttenuationCutoff") {
513                // Animation affects only current property
514                if let Some(a) = animation.as_composition_animation() {
515                    self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
516                    host.pending_animations().set(
517                        ServerPointLightProps::id_of_max_attenuation_cutoff_property().base(),
518                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
519                    );
520                }
521                // Animation is triggered by the current field, but does not necessary affects it
522                host.start_animation_group(&animation, "MaxAttenuationCutoff", ExpressionVariant::create(value));
523            }
524        }
525        self.max_attenuation_cutoff.set(value);
526        if changed {
527            PointLightHooks::on_max_attenuation_cutoff_changed(host);
528        }
529    }
530
531    /// Connects an animation with the named property. Returns `false` when
532    /// neither this class nor its generated bases have an animated property
533    /// of that name: the caller continues with its hand-written base.
534    pub fn start_animation(
535        &self,
536        host: &dyn PointLightHooks,
537        property_name: &str,
538        animation: &dyn ICompositionAnimation,
539        final_value: Option<ExpressionVariant>,
540    ) -> bool {
541        if property_name == "Color" {
542            let server = animation.create_instance(host.server(), final_value);
543            host.pending_animations().set(ServerPointLightProps::id_of_color_property().base(), server);
544            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::COLOR_ANIMATED);
545            host.register_for_serialization();
546            return true;
547        }
548        if property_name == "ConstantAttenuation" {
549            let server = animation.create_instance(host.server(), final_value);
550            host.pending_animations().set(ServerPointLightProps::id_of_constant_attenuation_property().base(), server);
551            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
552            host.register_for_serialization();
553            return true;
554        }
555        if property_name == "LinearAttenuation" {
556            let server = animation.create_instance(host.server(), final_value);
557            host.pending_animations().set(ServerPointLightProps::id_of_linear_attenuation_property().base(), server);
558            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
559            host.register_for_serialization();
560            return true;
561        }
562        if property_name == "QuadraticAttenuation" {
563            let server = animation.create_instance(host.server(), final_value);
564            host.pending_animations().set(ServerPointLightProps::id_of_quadratic_attenuation_property().base(), server);
565            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
566            host.register_for_serialization();
567            return true;
568        }
569        if property_name == "Offset" {
570            let server = animation.create_instance(host.server(), final_value);
571            host.pending_animations().set(ServerPointLightProps::id_of_offset_property().base(), server);
572            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::OFFSET_ANIMATED);
573            host.register_for_serialization();
574            return true;
575        }
576        if property_name == "Intensity" {
577            let server = animation.create_instance(host.server(), final_value);
578            host.pending_animations().set(ServerPointLightProps::id_of_intensity_property().base(), server);
579            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::INTENSITY_ANIMATED);
580            host.register_for_serialization();
581            return true;
582        }
583        if property_name == "MinAttenuationCutoff" {
584            let server = animation.create_instance(host.server(), final_value);
585            host.pending_animations().set(ServerPointLightProps::id_of_min_attenuation_cutoff_property().base(), server);
586            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
587            host.register_for_serialization();
588            return true;
589        }
590        if property_name == "MaxAttenuationCutoff" {
591            let server = animation.create_instance(host.server(), final_value);
592            host.pending_animations().set(ServerPointLightProps::id_of_max_attenuation_cutoff_property().base(), server);
593            self.changed_fields_of_point_light.set(self.changed_fields_of_point_light.get() | PointLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
594            host.register_for_serialization();
595            return true;
596        }
597        false
598    }
599
600    /// Writes the changes of the object: the base class first, then the
601    /// changed-fields mask and the changed members of this class.
602    pub fn serialize_changes_core(&self, host: &dyn PointLightHooks, writer: &mut BatchStreamWriter<'_>) {
603        self.base.serialize_changes_core(host, writer);
604        let changed = self.changed_fields_of_point_light.get();
605        writer.write(changed);
606        if changed.contains(PointLightChangedFields::COLOR_ANIMATED) {
607            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_color_property().base()));
608        } else if changed.contains(PointLightChangedFields::COLOR) {
609            writer.write(self.color.get());
610        }
611        if changed.contains(PointLightChangedFields::CONSTANT_ATTENUATION_ANIMATED) {
612            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_constant_attenuation_property().base()));
613        } else if changed.contains(PointLightChangedFields::CONSTANT_ATTENUATION) {
614            writer.write(self.constant_attenuation.get());
615        }
616        if changed.contains(PointLightChangedFields::LINEAR_ATTENUATION_ANIMATED) {
617            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_linear_attenuation_property().base()));
618        } else if changed.contains(PointLightChangedFields::LINEAR_ATTENUATION) {
619            writer.write(self.linear_attenuation.get());
620        }
621        if changed.contains(PointLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED) {
622            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_quadratic_attenuation_property().base()));
623        } else if changed.contains(PointLightChangedFields::QUADRATIC_ATTENUATION) {
624            writer.write(self.quadratic_attenuation.get());
625        }
626        if changed.contains(PointLightChangedFields::COORDINATE_SPACE) {
627            writer.write_server_object(self.coordinate_space.borrow().clone().map(|o| o.server()));
628        }
629        if changed.contains(PointLightChangedFields::OFFSET_ANIMATED) {
630            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_offset_property().base()));
631        } else if changed.contains(PointLightChangedFields::OFFSET) {
632            writer.write(self.offset.get());
633        }
634        if changed.contains(PointLightChangedFields::INTENSITY_ANIMATED) {
635            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_intensity_property().base()));
636        } else if changed.contains(PointLightChangedFields::INTENSITY) {
637            writer.write(self.intensity.get());
638        }
639        if changed.contains(PointLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED) {
640            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_min_attenuation_cutoff_property().base()));
641        } else if changed.contains(PointLightChangedFields::MIN_ATTENUATION_CUTOFF) {
642            writer.write(self.min_attenuation_cutoff.get());
643        }
644        if changed.contains(PointLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED) {
645            writer.write_value(host.pending_animations().get_and_remove(ServerPointLightProps::id_of_max_attenuation_cutoff_property().base()));
646        } else if changed.contains(PointLightChangedFields::MAX_ATTENUATION_CUTOFF) {
647            writer.write(self.max_attenuation_cutoff.get());
648        }
649        self.changed_fields_of_point_light.set(PointLightChangedFields::empty());
650    }
651}
652
653/// The partial methods of `ServerPointLight`: implemented by the class that embeds
654/// [`ServerPointLightProps`]. Every hook defaults to doing nothing.
655pub trait ServerPointLightHooks: ServerCompositionLightHooks {
656    fn deserialize_changes_extra(&self, _reader: &mut BatchStreamReader<'_>) {}
657    fn on_fields_deserialized(&self, _changed: PointLightChangedFields) {}
658
659    fn on_coordinate_space_changed(&self) {}
660    fn on_coordinate_space_changing(&self) {}
661}
662
663/// The server-side properties of `ServerPointLight`.
664pub struct ServerPointLightProps {
665    base: ServerCompositionLightProps,
666    color: Cell<Color>,
667    constant_attenuation: Cell<f32>,
668    linear_attenuation: Cell<f32>,
669    quadratic_attenuation: Cell<f32>,
670    coordinate_space: RefCell<Option<Rc<dyn IServerObject>>>,
671    offset: Cell<Vector3D>,
672    intensity: Cell<f32>,
673    min_attenuation_cutoff: Cell<f32>,
674    max_attenuation_cutoff: Cell<f32>,
675}
676
677impl Default for ServerPointLightProps {
678    fn default() -> Self {
679        Self::new()
680    }
681}
682
683impl ServerPointLightProps {
684    /// Creates the properties with every field at the default of its type; the
685    /// values of the UI thread arrive with the first batch.
686    pub fn new() -> Self {
687        Self {
688            base: ServerCompositionLightProps::new(),
689            color: Cell::new(Default::default()),
690            constant_attenuation: Cell::new(Default::default()),
691            linear_attenuation: Cell::new(Default::default()),
692            quadratic_attenuation: Cell::new(Default::default()),
693            coordinate_space: RefCell::new(None),
694            offset: Cell::new(Default::default()),
695            intensity: Cell::new(Default::default()),
696            min_attenuation_cutoff: Cell::new(Default::default()),
697            max_attenuation_cutoff: Cell::new(Default::default()),
698        }
699    }
700
701    /// The properties of the base class.
702    pub fn base(&self) -> &ServerCompositionLightProps {
703        &self.base
704    }
705
706    /// This block or the block of a base class, by type: what
707    /// `IServerObject::get_props` of the embedding class returns.
708    pub fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
709        if type_id == TypeId::of::<Self>() {
710            return Some(self);
711        }
712        self.base.find_props(type_id)
713    }
714
715    /// The composition property of `Color`.
716    pub fn id_of_color_property() -> &'static CompositionPropertyOf<Color> {
717        static PROPERTY: OnceLock<CompositionPropertyOf<Color>> = OnceLock::new();
718        PROPERTY.get_or_init(|| {
719            CompositionProperty::register::<ServerPointLightProps, Color>(
720                "Color",
721                "ServerPointLight",
722                |obj| props_of::<ServerPointLightProps>(obj).color.get(),
723                |obj, v| props_of::<ServerPointLightProps>(obj).color.set(v),
724                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).color.get())),
725                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).color.set(v.cast_or_default::<Color>())),
726            )
727        })
728    }
729
730    /// `Color` (animated).
731    pub fn color(&self) -> Color {
732        self.color.get()
733    }
734
735    pub fn set_color(&self, host: &dyn ServerPointLightHooks, value: Color) {
736        self.color.set(value);
737        host.remove_animation_for_property(Self::id_of_color_property().base());
738    }
739
740    /// The composition property of `ConstantAttenuation`.
741    pub fn id_of_constant_attenuation_property() -> &'static CompositionPropertyOf<f32> {
742        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
743        PROPERTY.get_or_init(|| {
744            CompositionProperty::register::<ServerPointLightProps, f32>(
745                "ConstantAttenuation",
746                "ServerPointLight",
747                |obj| props_of::<ServerPointLightProps>(obj).constant_attenuation.get(),
748                |obj, v| props_of::<ServerPointLightProps>(obj).constant_attenuation.set(v),
749                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).constant_attenuation.get())),
750                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).constant_attenuation.set(v.cast_or_default::<f32>())),
751            )
752        })
753    }
754
755    /// `ConstantAttenuation` (animated).
756    pub fn constant_attenuation(&self) -> f32 {
757        self.constant_attenuation.get()
758    }
759
760    pub fn set_constant_attenuation(&self, host: &dyn ServerPointLightHooks, value: f32) {
761        self.constant_attenuation.set(value);
762        host.remove_animation_for_property(Self::id_of_constant_attenuation_property().base());
763    }
764
765    /// The composition property of `LinearAttenuation`.
766    pub fn id_of_linear_attenuation_property() -> &'static CompositionPropertyOf<f32> {
767        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
768        PROPERTY.get_or_init(|| {
769            CompositionProperty::register::<ServerPointLightProps, f32>(
770                "LinearAttenuation",
771                "ServerPointLight",
772                |obj| props_of::<ServerPointLightProps>(obj).linear_attenuation.get(),
773                |obj, v| props_of::<ServerPointLightProps>(obj).linear_attenuation.set(v),
774                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).linear_attenuation.get())),
775                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).linear_attenuation.set(v.cast_or_default::<f32>())),
776            )
777        })
778    }
779
780    /// `LinearAttenuation` (animated).
781    pub fn linear_attenuation(&self) -> f32 {
782        self.linear_attenuation.get()
783    }
784
785    pub fn set_linear_attenuation(&self, host: &dyn ServerPointLightHooks, value: f32) {
786        self.linear_attenuation.set(value);
787        host.remove_animation_for_property(Self::id_of_linear_attenuation_property().base());
788    }
789
790    /// The composition property of `QuadraticAttenuation`.
791    pub fn id_of_quadratic_attenuation_property() -> &'static CompositionPropertyOf<f32> {
792        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
793        PROPERTY.get_or_init(|| {
794            CompositionProperty::register::<ServerPointLightProps, f32>(
795                "QuadraticAttenuation",
796                "ServerPointLight",
797                |obj| props_of::<ServerPointLightProps>(obj).quadratic_attenuation.get(),
798                |obj, v| props_of::<ServerPointLightProps>(obj).quadratic_attenuation.set(v),
799                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).quadratic_attenuation.get())),
800                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).quadratic_attenuation.set(v.cast_or_default::<f32>())),
801            )
802        })
803    }
804
805    /// `QuadraticAttenuation` (animated).
806    pub fn quadratic_attenuation(&self) -> f32 {
807        self.quadratic_attenuation.get()
808    }
809
810    pub fn set_quadratic_attenuation(&self, host: &dyn ServerPointLightHooks, value: f32) {
811        self.quadratic_attenuation.set(value);
812        host.remove_animation_for_property(Self::id_of_quadratic_attenuation_property().base());
813    }
814
815    /// The composition property of `CoordinateSpace`.
816    pub fn id_of_coordinate_space_property() -> &'static CompositionPropertyOf<Option<Rc<dyn IServerObject>>> {
817        static PROPERTY: OnceLock<CompositionPropertyOf<Option<Rc<dyn IServerObject>>>> = OnceLock::new();
818        PROPERTY.get_or_init(|| {
819            CompositionProperty::register::<ServerPointLightProps, Option<Rc<dyn IServerObject>>>(
820                "CoordinateSpace",
821                "ServerPointLight",
822                |obj| props_of::<ServerPointLightProps>(obj).coordinate_space.borrow().clone(),
823                |obj, v| *props_of::<ServerPointLightProps>(obj).coordinate_space.borrow_mut() = v,
824                None,
825                None,
826            )
827        })
828    }
829
830    /// `CoordinateSpace`.
831    pub fn coordinate_space(&self) -> Option<Rc<dyn IServerObject>> {
832        self.coordinate_space.borrow().clone()
833    }
834
835    pub fn set_coordinate_space(&self, host: &dyn ServerPointLightHooks, value: Option<Rc<dyn IServerObject>>) {
836        let old_value = self.coordinate_space.borrow().clone();
837        let mut changed = false;
838        if old_value.differs(&value) {
839            ServerPointLightHooks::on_coordinate_space_changing(host);
840            changed = true;
841        }
842        host.set_value(
843            Self::id_of_coordinate_space_property().base(),
844            ServerValueChange {
845                equal: old_value.default_equals(&value),
846                old_resource: old_value.as_server_render_resource(),
847                new_resource: value.as_server_render_resource(),
848                assign: &mut || *self.coordinate_space.borrow_mut() = value.clone(),
849            },
850        );
851        if changed {
852            ServerPointLightHooks::on_coordinate_space_changed(host);
853        }
854    }
855
856    /// The composition property of `Offset`.
857    pub fn id_of_offset_property() -> &'static CompositionPropertyOf<Vector3D> {
858        static PROPERTY: OnceLock<CompositionPropertyOf<Vector3D>> = OnceLock::new();
859        PROPERTY.get_or_init(|| {
860            CompositionProperty::register::<ServerPointLightProps, Vector3D>(
861                "Offset",
862                "ServerPointLight",
863                |obj| props_of::<ServerPointLightProps>(obj).offset.get(),
864                |obj, v| props_of::<ServerPointLightProps>(obj).offset.set(v),
865                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).offset.get())),
866                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).offset.set(v.cast_or_default::<Vector3D>())),
867            )
868        })
869    }
870
871    /// `Offset` (animated).
872    pub fn offset(&self) -> Vector3D {
873        self.offset.get()
874    }
875
876    pub fn set_offset(&self, host: &dyn ServerPointLightHooks, value: Vector3D) {
877        self.offset.set(value);
878        host.remove_animation_for_property(Self::id_of_offset_property().base());
879    }
880
881    /// The composition property of `Intensity`.
882    pub fn id_of_intensity_property() -> &'static CompositionPropertyOf<f32> {
883        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
884        PROPERTY.get_or_init(|| {
885            CompositionProperty::register::<ServerPointLightProps, f32>(
886                "Intensity",
887                "ServerPointLight",
888                |obj| props_of::<ServerPointLightProps>(obj).intensity.get(),
889                |obj, v| props_of::<ServerPointLightProps>(obj).intensity.set(v),
890                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).intensity.get())),
891                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).intensity.set(v.cast_or_default::<f32>())),
892            )
893        })
894    }
895
896    /// `Intensity` (animated).
897    pub fn intensity(&self) -> f32 {
898        self.intensity.get()
899    }
900
901    pub fn set_intensity(&self, host: &dyn ServerPointLightHooks, value: f32) {
902        self.intensity.set(value);
903        host.remove_animation_for_property(Self::id_of_intensity_property().base());
904    }
905
906    /// The composition property of `MinAttenuationCutoff`.
907    pub fn id_of_min_attenuation_cutoff_property() -> &'static CompositionPropertyOf<f32> {
908        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
909        PROPERTY.get_or_init(|| {
910            CompositionProperty::register::<ServerPointLightProps, f32>(
911                "MinAttenuationCutoff",
912                "ServerPointLight",
913                |obj| props_of::<ServerPointLightProps>(obj).min_attenuation_cutoff.get(),
914                |obj, v| props_of::<ServerPointLightProps>(obj).min_attenuation_cutoff.set(v),
915                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).min_attenuation_cutoff.get())),
916                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).min_attenuation_cutoff.set(v.cast_or_default::<f32>())),
917            )
918        })
919    }
920
921    /// `MinAttenuationCutoff` (animated).
922    pub fn min_attenuation_cutoff(&self) -> f32 {
923        self.min_attenuation_cutoff.get()
924    }
925
926    pub fn set_min_attenuation_cutoff(&self, host: &dyn ServerPointLightHooks, value: f32) {
927        self.min_attenuation_cutoff.set(value);
928        host.remove_animation_for_property(Self::id_of_min_attenuation_cutoff_property().base());
929    }
930
931    /// The composition property of `MaxAttenuationCutoff`.
932    pub fn id_of_max_attenuation_cutoff_property() -> &'static CompositionPropertyOf<f32> {
933        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
934        PROPERTY.get_or_init(|| {
935            CompositionProperty::register::<ServerPointLightProps, f32>(
936                "MaxAttenuationCutoff",
937                "ServerPointLight",
938                |obj| props_of::<ServerPointLightProps>(obj).max_attenuation_cutoff.get(),
939                |obj, v| props_of::<ServerPointLightProps>(obj).max_attenuation_cutoff.set(v),
940                Some(|obj| ExpressionVariant::create(props_of::<ServerPointLightProps>(obj).max_attenuation_cutoff.get())),
941                Some(|obj, v: ExpressionVariant| props_of::<ServerPointLightProps>(obj).max_attenuation_cutoff.set(v.cast_or_default::<f32>())),
942            )
943        })
944    }
945
946    /// `MaxAttenuationCutoff` (animated).
947    pub fn max_attenuation_cutoff(&self) -> f32 {
948        self.max_attenuation_cutoff.get()
949    }
950
951    pub fn set_max_attenuation_cutoff(&self, host: &dyn ServerPointLightHooks, value: f32) {
952        self.max_attenuation_cutoff.set(value);
953        host.remove_animation_for_property(Self::id_of_max_attenuation_cutoff_property().base());
954    }
955
956    /// Reads the changes of the object: the base class first, then the
957    /// changed-fields mask and the changed members of this class.
958    pub fn deserialize_changes_core(
959        &self,
960        host: &dyn ServerPointLightHooks,
961        reader: &mut BatchStreamReader<'_>,
962        committed_at: Duration,
963    ) {
964        let compositor = host.server_compositor();
965        self.base.deserialize_changes_core(host, reader, committed_at);
966        ServerPointLightHooks::deserialize_changes_extra(host, reader);
967        let changed = reader.read::<PointLightChangedFields>();
968        if changed.contains(PointLightChangedFields::COLOR_ANIMATED) {
969            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
970                panic!("a batch starts an animation of `Color` without an animation instance");
971            };
972            host.set_animated_value(
973                Self::id_of_color_property().base(),
974                ExpressionVariant::create(self.color.get()),
975                committed_at,
976                animation,
977            );
978        } else if changed.contains(PointLightChangedFields::COLOR) {
979            let value = reader.read::<Color>();
980            self.set_color(host, value);
981        }
982        if changed.contains(PointLightChangedFields::CONSTANT_ATTENUATION_ANIMATED) {
983            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
984                panic!("a batch starts an animation of `ConstantAttenuation` without an animation instance");
985            };
986            host.set_animated_value(
987                Self::id_of_constant_attenuation_property().base(),
988                ExpressionVariant::create(self.constant_attenuation.get()),
989                committed_at,
990                animation,
991            );
992        } else if changed.contains(PointLightChangedFields::CONSTANT_ATTENUATION) {
993            let value = reader.read::<f32>();
994            self.set_constant_attenuation(host, value);
995        }
996        if changed.contains(PointLightChangedFields::LINEAR_ATTENUATION_ANIMATED) {
997            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
998                panic!("a batch starts an animation of `LinearAttenuation` without an animation instance");
999            };
1000            host.set_animated_value(
1001                Self::id_of_linear_attenuation_property().base(),
1002                ExpressionVariant::create(self.linear_attenuation.get()),
1003                committed_at,
1004                animation,
1005            );
1006        } else if changed.contains(PointLightChangedFields::LINEAR_ATTENUATION) {
1007            let value = reader.read::<f32>();
1008            self.set_linear_attenuation(host, value);
1009        }
1010        if changed.contains(PointLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED) {
1011            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1012                panic!("a batch starts an animation of `QuadraticAttenuation` without an animation instance");
1013            };
1014            host.set_animated_value(
1015                Self::id_of_quadratic_attenuation_property().base(),
1016                ExpressionVariant::create(self.quadratic_attenuation.get()),
1017                committed_at,
1018                animation,
1019            );
1020        } else if changed.contains(PointLightChangedFields::QUADRATIC_ATTENUATION) {
1021            let value = reader.read::<f32>();
1022            self.set_quadratic_attenuation(host, value);
1023        }
1024        if changed.contains(PointLightChangedFields::COORDINATE_SPACE) {
1025            let value = read_server_object(reader, compositor.as_ref());
1026            self.set_coordinate_space(host, value);
1027        }
1028        if changed.contains(PointLightChangedFields::OFFSET_ANIMATED) {
1029            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1030                panic!("a batch starts an animation of `Offset` without an animation instance");
1031            };
1032            host.set_animated_value(
1033                Self::id_of_offset_property().base(),
1034                ExpressionVariant::create(self.offset.get()),
1035                committed_at,
1036                animation,
1037            );
1038        } else if changed.contains(PointLightChangedFields::OFFSET) {
1039            let value = reader.read::<Vector3D>();
1040            self.set_offset(host, value);
1041        }
1042        if changed.contains(PointLightChangedFields::INTENSITY_ANIMATED) {
1043            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1044                panic!("a batch starts an animation of `Intensity` without an animation instance");
1045            };
1046            host.set_animated_value(
1047                Self::id_of_intensity_property().base(),
1048                ExpressionVariant::create(self.intensity.get()),
1049                committed_at,
1050                animation,
1051            );
1052        } else if changed.contains(PointLightChangedFields::INTENSITY) {
1053            let value = reader.read::<f32>();
1054            self.set_intensity(host, value);
1055        }
1056        if changed.contains(PointLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED) {
1057            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1058                panic!("a batch starts an animation of `MinAttenuationCutoff` without an animation instance");
1059            };
1060            host.set_animated_value(
1061                Self::id_of_min_attenuation_cutoff_property().base(),
1062                ExpressionVariant::create(self.min_attenuation_cutoff.get()),
1063                committed_at,
1064                animation,
1065            );
1066        } else if changed.contains(PointLightChangedFields::MIN_ATTENUATION_CUTOFF) {
1067            let value = reader.read::<f32>();
1068            self.set_min_attenuation_cutoff(host, value);
1069        }
1070        if changed.contains(PointLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED) {
1071            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1072                panic!("a batch starts an animation of `MaxAttenuationCutoff` without an animation instance");
1073            };
1074            host.set_animated_value(
1075                Self::id_of_max_attenuation_cutoff_property().base(),
1076                ExpressionVariant::create(self.max_attenuation_cutoff.get()),
1077                committed_at,
1078                animation,
1079            );
1080        } else if changed.contains(PointLightChangedFields::MAX_ATTENUATION_CUTOFF) {
1081            let value = reader.read::<f32>();
1082            self.set_max_attenuation_cutoff(host, value);
1083        }
1084        ServerPointLightHooks::on_fields_deserialized(host, changed);
1085    }
1086
1087    /// The composition property with the given name, among the properties
1088    /// of this class and of its generated bases that have a variant form.
1089    pub fn get_composition_property(name: &str) -> Option<&'static CompositionProperty> {
1090        if name == "Color" {
1091            return Some(Self::id_of_color_property().base());
1092        }
1093        if name == "ConstantAttenuation" {
1094            return Some(Self::id_of_constant_attenuation_property().base());
1095        }
1096        if name == "LinearAttenuation" {
1097            return Some(Self::id_of_linear_attenuation_property().base());
1098        }
1099        if name == "QuadraticAttenuation" {
1100            return Some(Self::id_of_quadratic_attenuation_property().base());
1101        }
1102        if name == "Offset" {
1103            return Some(Self::id_of_offset_property().base());
1104        }
1105        if name == "Intensity" {
1106            return Some(Self::id_of_intensity_property().base());
1107        }
1108        if name == "MinAttenuationCutoff" {
1109            return Some(Self::id_of_min_attenuation_cutoff_property().base());
1110        }
1111        if name == "MaxAttenuationCutoff" {
1112            return Some(Self::id_of_max_attenuation_cutoff_property().base());
1113        }
1114        ServerCompositionLightProps::get_composition_property(name)
1115    }
1116}