1use 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 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
41 pub struct DistantLightChangedFields: u8 {
42 const COLOR = 1;
43 const COLOR_ANIMATED = 2;
44 const COORDINATE_SPACE = 4;
45 const DIRECTION = 8;
46 const DIRECTION_ANIMATED = 16;
47 const INTENSITY = 32;
48 const INTENSITY_ANIMATED = 64;
49 }
50}
51
52impl BatchValue for DistantLightChangedFields {
53 #[inline]
54 fn write_to(&self, out: &mut Vec<u8>) {
55 self.bits().write_to(out);
56 }
57
58 #[inline]
59 fn read_from(input: &mut BatchValueReader<'_>) -> Self {
60 DistantLightChangedFields::from_bits_retain(u8::read_from(input))
61 }
62}
63
64pub trait DistantLightHooks: CompositionLightHooks {
67 fn initialize_defaults_extra(&self) {}
68
69 fn validate_color_change(&self, _old_value: Color, _new_value: Color) {}
70 fn on_color_changed(&self) {}
71 fn on_color_changing(&self) {}
72
73 fn validate_coordinate_space_change(&self, _old_value: &Option<Rc<dyn ICompositionObject>>, _new_value: &Option<Rc<dyn ICompositionObject>>) {}
74 fn on_coordinate_space_changed(&self) {}
75 fn on_coordinate_space_changing(&self) {}
76
77 fn validate_direction_change(&self, _old_value: Vector3D, _new_value: Vector3D) {}
78 fn on_direction_changed(&self) {}
79 fn on_direction_changing(&self) {}
80
81 fn validate_intensity_change(&self, _old_value: f32, _new_value: f32) {}
82 fn on_intensity_changed(&self) {}
83 fn on_intensity_changing(&self) {}
84}
85
86pub struct DistantLightProps {
88 base: CompositionLightProps,
89 changed_fields_of_distant_light: Cell<DistantLightChangedFields>,
90 color: Cell<Color>,
91 coordinate_space: RefCell<Option<Rc<dyn ICompositionObject>>>,
92 direction: Cell<Vector3D>,
93 intensity: Cell<f32>,
94}
95
96impl Default for DistantLightProps {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl DistantLightProps {
103 pub fn new() -> Self {
107 Self {
108 base: CompositionLightProps::new(),
109 changed_fields_of_distant_light: Cell::new(DistantLightChangedFields::empty()),
110 color: Cell::new(Default::default()),
111 coordinate_space: RefCell::new(None),
112 direction: Cell::new(Default::default()),
113 intensity: Cell::new(Default::default()),
114 }
115 }
116
117 pub fn base(&self) -> &CompositionLightProps {
119 &self.base
120 }
121
122 pub fn initialize_defaults(&self, host: &dyn DistantLightHooks) {
125 self.base.initialize_defaults(host);
126 self.initialize_own_defaults(host);
127 }
128
129 pub fn initialize_own_defaults(&self, host: &dyn DistantLightHooks) {
131 DistantLightHooks::initialize_defaults_extra(host);
132 self.set_color(host, crate::media::Colors::WHITE);
133 self.set_direction(host, Vector3D::new(0.0, 0.0, -1.0));
134 self.set_intensity(host, 1.0);
135 }
136
137 pub fn color(&self) -> Color {
139 self.color.get()
140 }
141
142 pub fn set_color(&self, host: &dyn DistantLightHooks, value: Color) {
144 let mut changed = false;
145 let old_value = self.color.get();
146 if old_value.differs(&value) {
147 DistantLightHooks::validate_color_change(host, old_value, value);
148 DistantLightHooks::on_color_changing(host);
149 changed = true;
150 self.color.set(value);
152 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::COLOR);
154 host.register_for_serialization();
155 host.pending_animations().remove(ServerDistantLightProps::id_of_color_property().base());
157 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() & !DistantLightChangedFields::COLOR_ANIMATED);
158 if let Some(animation) = host.implicit_animation("Color") {
160 if let Some(a) = animation.as_composition_animation() {
162 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::COLOR_ANIMATED);
163 host.pending_animations().set(
164 ServerDistantLightProps::id_of_color_property().base(),
165 a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
166 );
167 }
168 host.start_animation_group(&animation, "Color", ExpressionVariant::create(value));
170 }
171 }
172 self.color.set(value);
173 if changed {
174 DistantLightHooks::on_color_changed(host);
175 }
176 }
177
178 pub fn coordinate_space(&self) -> Option<Rc<dyn ICompositionObject>> {
180 self.coordinate_space.borrow().clone()
181 }
182
183 pub fn set_coordinate_space(&self, host: &dyn DistantLightHooks, value: Option<Rc<dyn ICompositionObject>>) {
185 let mut changed = false;
186 let old_value = self.coordinate_space.borrow().clone();
187 if old_value.differs(&value) {
188 DistantLightHooks::validate_coordinate_space_change(host, &old_value, &value);
189 DistantLightHooks::on_coordinate_space_changing(host);
190 changed = true;
191 *self.coordinate_space.borrow_mut() = value.clone();
193 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::COORDINATE_SPACE);
195 host.register_for_serialization();
196 }
197 *self.coordinate_space.borrow_mut() = value;
198 if changed {
199 DistantLightHooks::on_coordinate_space_changed(host);
200 }
201 }
202
203 pub fn direction(&self) -> Vector3D {
205 self.direction.get()
206 }
207
208 pub fn set_direction(&self, host: &dyn DistantLightHooks, value: Vector3D) {
210 let mut changed = false;
211 let old_value = self.direction.get();
212 if old_value.differs(&value) {
213 DistantLightHooks::validate_direction_change(host, old_value, value);
214 DistantLightHooks::on_direction_changing(host);
215 changed = true;
216 self.direction.set(value);
218 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::DIRECTION);
220 host.register_for_serialization();
221 host.pending_animations().remove(ServerDistantLightProps::id_of_direction_property().base());
223 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() & !DistantLightChangedFields::DIRECTION_ANIMATED);
224 if let Some(animation) = host.implicit_animation("Direction") {
226 if let Some(a) = animation.as_composition_animation() {
228 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::DIRECTION_ANIMATED);
229 host.pending_animations().set(
230 ServerDistantLightProps::id_of_direction_property().base(),
231 a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
232 );
233 }
234 host.start_animation_group(&animation, "Direction", ExpressionVariant::create(value));
236 }
237 }
238 self.direction.set(value);
239 if changed {
240 DistantLightHooks::on_direction_changed(host);
241 }
242 }
243
244 pub fn intensity(&self) -> f32 {
246 self.intensity.get()
247 }
248
249 pub fn set_intensity(&self, host: &dyn DistantLightHooks, value: f32) {
251 let mut changed = false;
252 let old_value = self.intensity.get();
253 if old_value.differs(&value) {
254 DistantLightHooks::validate_intensity_change(host, old_value, value);
255 DistantLightHooks::on_intensity_changing(host);
256 changed = true;
257 self.intensity.set(value);
259 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::INTENSITY);
261 host.register_for_serialization();
262 host.pending_animations().remove(ServerDistantLightProps::id_of_intensity_property().base());
264 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() & !DistantLightChangedFields::INTENSITY_ANIMATED);
265 if let Some(animation) = host.implicit_animation("Intensity") {
267 if let Some(a) = animation.as_composition_animation() {
269 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::INTENSITY_ANIMATED);
270 host.pending_animations().set(
271 ServerDistantLightProps::id_of_intensity_property().base(),
272 a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
273 );
274 }
275 host.start_animation_group(&animation, "Intensity", ExpressionVariant::create(value));
277 }
278 }
279 self.intensity.set(value);
280 if changed {
281 DistantLightHooks::on_intensity_changed(host);
282 }
283 }
284
285 pub fn start_animation(
289 &self,
290 host: &dyn DistantLightHooks,
291 property_name: &str,
292 animation: &dyn ICompositionAnimation,
293 final_value: Option<ExpressionVariant>,
294 ) -> bool {
295 if property_name == "Color" {
296 let server = animation.create_instance(host.server(), final_value);
297 host.pending_animations().set(ServerDistantLightProps::id_of_color_property().base(), server);
298 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::COLOR_ANIMATED);
299 host.register_for_serialization();
300 return true;
301 }
302 if property_name == "Direction" {
303 let server = animation.create_instance(host.server(), final_value);
304 host.pending_animations().set(ServerDistantLightProps::id_of_direction_property().base(), server);
305 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::DIRECTION_ANIMATED);
306 host.register_for_serialization();
307 return true;
308 }
309 if property_name == "Intensity" {
310 let server = animation.create_instance(host.server(), final_value);
311 host.pending_animations().set(ServerDistantLightProps::id_of_intensity_property().base(), server);
312 self.changed_fields_of_distant_light.set(self.changed_fields_of_distant_light.get() | DistantLightChangedFields::INTENSITY_ANIMATED);
313 host.register_for_serialization();
314 return true;
315 }
316 false
317 }
318
319 pub fn serialize_changes_core(&self, host: &dyn DistantLightHooks, writer: &mut BatchStreamWriter<'_>) {
322 self.base.serialize_changes_core(host, writer);
323 let changed = self.changed_fields_of_distant_light.get();
324 writer.write(changed);
325 if changed.contains(DistantLightChangedFields::COLOR_ANIMATED) {
326 writer.write_value(host.pending_animations().get_and_remove(ServerDistantLightProps::id_of_color_property().base()));
327 } else if changed.contains(DistantLightChangedFields::COLOR) {
328 writer.write(self.color.get());
329 }
330 if changed.contains(DistantLightChangedFields::COORDINATE_SPACE) {
331 writer.write_server_object(self.coordinate_space.borrow().clone().map(|o| o.server()));
332 }
333 if changed.contains(DistantLightChangedFields::DIRECTION_ANIMATED) {
334 writer.write_value(host.pending_animations().get_and_remove(ServerDistantLightProps::id_of_direction_property().base()));
335 } else if changed.contains(DistantLightChangedFields::DIRECTION) {
336 writer.write(self.direction.get());
337 }
338 if changed.contains(DistantLightChangedFields::INTENSITY_ANIMATED) {
339 writer.write_value(host.pending_animations().get_and_remove(ServerDistantLightProps::id_of_intensity_property().base()));
340 } else if changed.contains(DistantLightChangedFields::INTENSITY) {
341 writer.write(self.intensity.get());
342 }
343 self.changed_fields_of_distant_light.set(DistantLightChangedFields::empty());
344 }
345}
346
347pub trait ServerDistantLightHooks: ServerCompositionLightHooks {
350 fn deserialize_changes_extra(&self, _reader: &mut BatchStreamReader<'_>) {}
351 fn on_fields_deserialized(&self, _changed: DistantLightChangedFields) {}
352
353 fn on_coordinate_space_changed(&self) {}
354 fn on_coordinate_space_changing(&self) {}
355}
356
357pub struct ServerDistantLightProps {
359 base: ServerCompositionLightProps,
360 color: Cell<Color>,
361 coordinate_space: RefCell<Option<Rc<dyn IServerObject>>>,
362 direction: Cell<Vector3D>,
363 intensity: Cell<f32>,
364}
365
366impl Default for ServerDistantLightProps {
367 fn default() -> Self {
368 Self::new()
369 }
370}
371
372impl ServerDistantLightProps {
373 pub fn new() -> Self {
376 Self {
377 base: ServerCompositionLightProps::new(),
378 color: Cell::new(Default::default()),
379 coordinate_space: RefCell::new(None),
380 direction: Cell::new(Default::default()),
381 intensity: Cell::new(Default::default()),
382 }
383 }
384
385 pub fn base(&self) -> &ServerCompositionLightProps {
387 &self.base
388 }
389
390 pub fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
393 if type_id == TypeId::of::<Self>() {
394 return Some(self);
395 }
396 self.base.find_props(type_id)
397 }
398
399 pub fn id_of_color_property() -> &'static CompositionPropertyOf<Color> {
401 static PROPERTY: OnceLock<CompositionPropertyOf<Color>> = OnceLock::new();
402 PROPERTY.get_or_init(|| {
403 CompositionProperty::register::<ServerDistantLightProps, Color>(
404 "Color",
405 "ServerDistantLight",
406 |obj| props_of::<ServerDistantLightProps>(obj).color.get(),
407 |obj, v| props_of::<ServerDistantLightProps>(obj).color.set(v),
408 Some(|obj| ExpressionVariant::create(props_of::<ServerDistantLightProps>(obj).color.get())),
409 Some(|obj, v: ExpressionVariant| props_of::<ServerDistantLightProps>(obj).color.set(v.cast_or_default::<Color>())),
410 )
411 })
412 }
413
414 pub fn color(&self) -> Color {
416 self.color.get()
417 }
418
419 pub fn set_color(&self, host: &dyn ServerDistantLightHooks, value: Color) {
420 self.color.set(value);
421 host.remove_animation_for_property(Self::id_of_color_property().base());
422 }
423
424 pub fn id_of_coordinate_space_property() -> &'static CompositionPropertyOf<Option<Rc<dyn IServerObject>>> {
426 static PROPERTY: OnceLock<CompositionPropertyOf<Option<Rc<dyn IServerObject>>>> = OnceLock::new();
427 PROPERTY.get_or_init(|| {
428 CompositionProperty::register::<ServerDistantLightProps, Option<Rc<dyn IServerObject>>>(
429 "CoordinateSpace",
430 "ServerDistantLight",
431 |obj| props_of::<ServerDistantLightProps>(obj).coordinate_space.borrow().clone(),
432 |obj, v| *props_of::<ServerDistantLightProps>(obj).coordinate_space.borrow_mut() = v,
433 None,
434 None,
435 )
436 })
437 }
438
439 pub fn coordinate_space(&self) -> Option<Rc<dyn IServerObject>> {
441 self.coordinate_space.borrow().clone()
442 }
443
444 pub fn set_coordinate_space(&self, host: &dyn ServerDistantLightHooks, value: Option<Rc<dyn IServerObject>>) {
445 let old_value = self.coordinate_space.borrow().clone();
446 let mut changed = false;
447 if old_value.differs(&value) {
448 ServerDistantLightHooks::on_coordinate_space_changing(host);
449 changed = true;
450 }
451 host.set_value(
452 Self::id_of_coordinate_space_property().base(),
453 ServerValueChange {
454 equal: old_value.default_equals(&value),
455 old_resource: old_value.as_server_render_resource(),
456 new_resource: value.as_server_render_resource(),
457 assign: &mut || *self.coordinate_space.borrow_mut() = value.clone(),
458 },
459 );
460 if changed {
461 ServerDistantLightHooks::on_coordinate_space_changed(host);
462 }
463 }
464
465 pub fn id_of_direction_property() -> &'static CompositionPropertyOf<Vector3D> {
467 static PROPERTY: OnceLock<CompositionPropertyOf<Vector3D>> = OnceLock::new();
468 PROPERTY.get_or_init(|| {
469 CompositionProperty::register::<ServerDistantLightProps, Vector3D>(
470 "Direction",
471 "ServerDistantLight",
472 |obj| props_of::<ServerDistantLightProps>(obj).direction.get(),
473 |obj, v| props_of::<ServerDistantLightProps>(obj).direction.set(v),
474 Some(|obj| ExpressionVariant::create(props_of::<ServerDistantLightProps>(obj).direction.get())),
475 Some(|obj, v: ExpressionVariant| props_of::<ServerDistantLightProps>(obj).direction.set(v.cast_or_default::<Vector3D>())),
476 )
477 })
478 }
479
480 pub fn direction(&self) -> Vector3D {
482 self.direction.get()
483 }
484
485 pub fn set_direction(&self, host: &dyn ServerDistantLightHooks, value: Vector3D) {
486 self.direction.set(value);
487 host.remove_animation_for_property(Self::id_of_direction_property().base());
488 }
489
490 pub fn id_of_intensity_property() -> &'static CompositionPropertyOf<f32> {
492 static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
493 PROPERTY.get_or_init(|| {
494 CompositionProperty::register::<ServerDistantLightProps, f32>(
495 "Intensity",
496 "ServerDistantLight",
497 |obj| props_of::<ServerDistantLightProps>(obj).intensity.get(),
498 |obj, v| props_of::<ServerDistantLightProps>(obj).intensity.set(v),
499 Some(|obj| ExpressionVariant::create(props_of::<ServerDistantLightProps>(obj).intensity.get())),
500 Some(|obj, v: ExpressionVariant| props_of::<ServerDistantLightProps>(obj).intensity.set(v.cast_or_default::<f32>())),
501 )
502 })
503 }
504
505 pub fn intensity(&self) -> f32 {
507 self.intensity.get()
508 }
509
510 pub fn set_intensity(&self, host: &dyn ServerDistantLightHooks, value: f32) {
511 self.intensity.set(value);
512 host.remove_animation_for_property(Self::id_of_intensity_property().base());
513 }
514
515 pub fn deserialize_changes_core(
518 &self,
519 host: &dyn ServerDistantLightHooks,
520 reader: &mut BatchStreamReader<'_>,
521 committed_at: Duration,
522 ) {
523 let compositor = host.server_compositor();
524 self.base.deserialize_changes_core(host, reader, committed_at);
525 ServerDistantLightHooks::deserialize_changes_extra(host, reader);
526 let changed = reader.read::<DistantLightChangedFields>();
527 if changed.contains(DistantLightChangedFields::COLOR_ANIMATED) {
528 let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
529 panic!("a batch starts an animation of `Color` without an animation instance");
530 };
531 host.set_animated_value(
532 Self::id_of_color_property().base(),
533 ExpressionVariant::create(self.color.get()),
534 committed_at,
535 animation,
536 );
537 } else if changed.contains(DistantLightChangedFields::COLOR) {
538 let value = reader.read::<Color>();
539 self.set_color(host, value);
540 }
541 if changed.contains(DistantLightChangedFields::COORDINATE_SPACE) {
542 let value = read_server_object(reader, compositor.as_ref());
543 self.set_coordinate_space(host, value);
544 }
545 if changed.contains(DistantLightChangedFields::DIRECTION_ANIMATED) {
546 let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
547 panic!("a batch starts an animation of `Direction` without an animation instance");
548 };
549 host.set_animated_value(
550 Self::id_of_direction_property().base(),
551 ExpressionVariant::create(self.direction.get()),
552 committed_at,
553 animation,
554 );
555 } else if changed.contains(DistantLightChangedFields::DIRECTION) {
556 let value = reader.read::<Vector3D>();
557 self.set_direction(host, value);
558 }
559 if changed.contains(DistantLightChangedFields::INTENSITY_ANIMATED) {
560 let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
561 panic!("a batch starts an animation of `Intensity` without an animation instance");
562 };
563 host.set_animated_value(
564 Self::id_of_intensity_property().base(),
565 ExpressionVariant::create(self.intensity.get()),
566 committed_at,
567 animation,
568 );
569 } else if changed.contains(DistantLightChangedFields::INTENSITY) {
570 let value = reader.read::<f32>();
571 self.set_intensity(host, value);
572 }
573 ServerDistantLightHooks::on_fields_deserialized(host, changed);
574 }
575
576 pub fn get_composition_property(name: &str) -> Option<&'static CompositionProperty> {
579 if name == "Color" {
580 return Some(Self::id_of_color_property().base());
581 }
582 if name == "Direction" {
583 return Some(Self::id_of_direction_property().base());
584 }
585 if name == "Intensity" {
586 return Some(Self::id_of_intensity_property().base());
587 }
588 ServerCompositionLightProps::get_composition_property(name)
589 }
590}