1use nightshade::ecs::animation::components::AnimationPlayer;
13use nightshade::ecs::audio::components::AudioSource;
14use nightshade::ecs::camera::components::Camera;
15use nightshade::ecs::decal::components::Decal;
16use nightshade::ecs::material::components::Material;
17use nightshade::ecs::morph::components::MorphWeights;
18use nightshade::ecs::particles::components::ParticleEmitter;
19use nightshade::ecs::prefab::components::PrefabSource;
20use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
21use nightshade::ecs::script::Script;
22use nightshade::ecs::text::components::{Text, TextProperties};
23use nightshade::ecs::transform::components::IgnoreParentScale;
24use nightshade::prelude::*;
25use serde::{Deserialize, Serialize};
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, enum2schema::Schema)]
30#[schema(string_enum)]
31pub enum ComponentKind {
32 ParticleEmitter,
33 Decal,
34 AudioSource,
35 RigidBody,
36 Collider,
37 CharacterController,
38 NavmeshAgent,
39 Camera,
40 Text,
41 AnimationPlayer,
42 Visibility,
43 CastsShadow,
44 Light,
45 RenderLayer,
46 CullingMask,
47 CameraCullingMask,
48 IgnoreParentScale,
49 AudioListener,
50 CollisionListener,
51 PhysicsInterpolation,
52 MorphWeights,
53 MaterialVariants,
54 PanOrbitCamera,
55 ThirdPersonCamera,
56 CameraEnvironment,
57 CameraPostProcess,
58 ConstrainedAspect,
59 ViewportUpdateMode,
60 Script,
61}
62
63pub const ALL_COMPONENT_KINDS: [ComponentKind; 29] = [
65 ComponentKind::ParticleEmitter,
66 ComponentKind::Decal,
67 ComponentKind::AudioSource,
68 ComponentKind::RigidBody,
69 ComponentKind::Collider,
70 ComponentKind::CharacterController,
71 ComponentKind::NavmeshAgent,
72 ComponentKind::Camera,
73 ComponentKind::Text,
74 ComponentKind::AnimationPlayer,
75 ComponentKind::Visibility,
76 ComponentKind::CastsShadow,
77 ComponentKind::Light,
78 ComponentKind::RenderLayer,
79 ComponentKind::CullingMask,
80 ComponentKind::CameraCullingMask,
81 ComponentKind::IgnoreParentScale,
82 ComponentKind::AudioListener,
83 ComponentKind::CollisionListener,
84 ComponentKind::PhysicsInterpolation,
85 ComponentKind::MorphWeights,
86 ComponentKind::MaterialVariants,
87 ComponentKind::PanOrbitCamera,
88 ComponentKind::ThirdPersonCamera,
89 ComponentKind::CameraEnvironment,
90 ComponentKind::CameraPostProcess,
91 ComponentKind::ConstrainedAspect,
92 ComponentKind::ViewportUpdateMode,
93 ComponentKind::Script,
94];
95
96pub fn kind_mask(kind: ComponentKind) -> u64 {
99 use nightshade::ecs::world as masks;
100 match kind {
101 ComponentKind::ParticleEmitter => masks::PARTICLE_EMITTER,
102 ComponentKind::Decal => masks::DECAL,
103 ComponentKind::AudioSource => masks::AUDIO_SOURCE,
104 ComponentKind::RigidBody => masks::RIGID_BODY,
105 ComponentKind::Collider => masks::COLLIDER,
106 ComponentKind::CharacterController => masks::CHARACTER_CONTROLLER,
107 ComponentKind::NavmeshAgent => masks::NAVMESH_AGENT,
108 ComponentKind::Camera => masks::CAMERA,
109 ComponentKind::Text => masks::TEXT,
110 ComponentKind::AnimationPlayer => masks::ANIMATION_PLAYER,
111 ComponentKind::Visibility => masks::VISIBILITY,
112 ComponentKind::CastsShadow => masks::CASTS_SHADOW,
113 ComponentKind::Light => masks::LIGHT,
114 ComponentKind::RenderLayer => masks::RENDER_LAYER,
115 ComponentKind::CullingMask => masks::CULLING_MASK,
116 ComponentKind::CameraCullingMask => masks::CAMERA_CULLING_MASK,
117 ComponentKind::IgnoreParentScale => masks::IGNORE_PARENT_SCALE,
118 ComponentKind::AudioListener => masks::AUDIO_LISTENER,
119 ComponentKind::CollisionListener => masks::COLLISION_LISTENER,
120 ComponentKind::PhysicsInterpolation => masks::PHYSICS_INTERPOLATION,
121 ComponentKind::MorphWeights => masks::MORPH_WEIGHTS,
122 ComponentKind::MaterialVariants => masks::MATERIAL_VARIANTS,
123 ComponentKind::PanOrbitCamera => masks::PAN_ORBIT_CAMERA,
124 ComponentKind::ThirdPersonCamera => masks::THIRD_PERSON_CAMERA,
125 ComponentKind::CameraEnvironment => masks::CAMERA_ENVIRONMENT,
126 ComponentKind::CameraPostProcess => masks::CAMERA_POST_PROCESS,
127 ComponentKind::ConstrainedAspect => masks::CONSTRAINED_ASPECT,
128 ComponentKind::ViewportUpdateMode => masks::VIEWPORT_UPDATE_MODE,
129 ComponentKind::Script => masks::SCRIPT,
130 }
131}
132
133pub fn has_component(world: &World, entity: Entity, kind: ComponentKind) -> bool {
135 world.core.entity_has_components(entity, kind_mask(kind))
136}
137
138pub fn remove_component(world: &mut World, entity: Entity, kind: ComponentKind) {
140 world.core.remove_components(entity, kind_mask(kind));
141}
142
143pub fn present_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
145 ALL_COMPONENT_KINDS
146 .into_iter()
147 .filter(|kind| has_component(world, entity, *kind))
148 .collect()
149}
150
151pub fn addable_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
153 ALL_COMPONENT_KINDS
154 .into_iter()
155 .filter(|kind| !has_component(world, entity, *kind))
156 .collect()
157}
158
159pub fn add_component_default(world: &mut World, entity: Entity, kind: ComponentKind) {
163 use nightshade::ecs::world as masks;
164 match kind {
165 ComponentKind::ParticleEmitter => {
166 world.core.add_components(entity, masks::PARTICLE_EMITTER);
167 world
168 .core
169 .set_particle_emitter(entity, ParticleEmitter::default());
170 }
171 ComponentKind::Decal => {
172 world.core.add_components(entity, masks::DECAL);
173 world.core.set_decal(entity, Decal::default());
174 }
175 ComponentKind::AudioSource => {
176 world.core.add_components(entity, masks::AUDIO_SOURCE);
177 world.core.set_audio_source(entity, AudioSource::default());
178 }
179 ComponentKind::RigidBody => {
180 world.core.add_components(entity, masks::RIGID_BODY);
181 world
182 .core
183 .set_rigid_body(entity, RigidBodyComponent::new_dynamic());
184 }
185 ComponentKind::Collider => {
186 world.core.add_components(entity, masks::COLLIDER);
187 world
188 .core
189 .set_collider(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5));
190 }
191 ComponentKind::CharacterController => {
192 world
193 .core
194 .add_components(entity, masks::CHARACTER_CONTROLLER);
195 world.core.set_character_controller(
196 entity,
197 CharacterControllerComponent::new_capsule(0.9, 0.3),
198 );
199 }
200 ComponentKind::NavmeshAgent => {
201 world.core.add_components(entity, masks::NAVMESH_AGENT);
202 world.core.set_navmesh_agent(entity, NavMeshAgent::new());
203 }
204 ComponentKind::Camera => {
205 world.core.add_components(entity, masks::CAMERA);
206 world.core.set_camera(entity, Camera::default());
207 }
208 ComponentKind::Text => {
209 let text_index = world.resources.text.cache.add_text("Text");
210 world.core.add_components(entity, masks::TEXT);
211 world.core.set_text(entity, Text::new(text_index));
212 }
213 ComponentKind::AnimationPlayer => {
214 world.core.add_components(entity, masks::ANIMATION_PLAYER);
215 world
216 .core
217 .set_animation_player(entity, AnimationPlayer::default());
218 }
219 ComponentKind::Visibility => {
220 world.core.add_components(entity, masks::VISIBILITY);
221 world
222 .core
223 .set_visibility(entity, Visibility { visible: true });
224 }
225 ComponentKind::CastsShadow => {
226 world.core.add_components(entity, masks::CASTS_SHADOW);
227 world.core.set_casts_shadow(entity, CastsShadow);
228 }
229 ComponentKind::Light => {
230 world.core.add_components(entity, masks::LIGHT);
231 world.core.set_light(entity, Light::default());
232 }
233 ComponentKind::RenderLayer => {
234 world.core.add_components(entity, masks::RENDER_LAYER);
235 world.core.set_render_layer(entity, RenderLayer::default());
236 }
237 ComponentKind::CullingMask => {
238 world.core.add_components(entity, masks::CULLING_MASK);
239 world.core.set_culling_mask(entity, CullingMask::default());
240 }
241 ComponentKind::CameraCullingMask => {
242 world
243 .core
244 .add_components(entity, masks::CAMERA_CULLING_MASK);
245 world
246 .core
247 .set_camera_culling_mask(entity, CameraCullingMask::default());
248 }
249 ComponentKind::IgnoreParentScale => {
250 world
251 .core
252 .add_components(entity, masks::IGNORE_PARENT_SCALE);
253 world
254 .core
255 .set_ignore_parent_scale(entity, IgnoreParentScale);
256 }
257 ComponentKind::AudioListener => {
258 world.core.add_components(entity, masks::AUDIO_LISTENER);
259 world
260 .core
261 .set_audio_listener(entity, nightshade::ecs::audio::components::AudioListener);
262 }
263 ComponentKind::CollisionListener => {
264 world.core.add_components(entity, masks::COLLISION_LISTENER);
265 world.core.set_collision_listener(
266 entity,
267 nightshade::ecs::physics::components::CollisionListener,
268 );
269 }
270 ComponentKind::PhysicsInterpolation => {
271 world
272 .core
273 .add_components(entity, masks::PHYSICS_INTERPOLATION);
274 world.core.set_physics_interpolation(
275 entity,
276 nightshade::ecs::physics::components::PhysicsInterpolation::default(),
277 );
278 }
279 ComponentKind::MorphWeights => {
280 world.core.add_components(entity, masks::MORPH_WEIGHTS);
281 world
282 .core
283 .set_morph_weights(entity, MorphWeights::default());
284 }
285 ComponentKind::MaterialVariants => {
286 world.core.add_components(entity, masks::MATERIAL_VARIANTS);
287 world.core.set_material_variants(
288 entity,
289 nightshade::ecs::material::components::MaterialVariants::default(),
290 );
291 }
292 ComponentKind::PanOrbitCamera => {
293 world.core.add_components(entity, masks::PAN_ORBIT_CAMERA);
294 world.core.set_pan_orbit_camera(
295 entity,
296 nightshade::ecs::camera::components::PanOrbitCamera::default(),
297 );
298 }
299 ComponentKind::ThirdPersonCamera => {
300 world
301 .core
302 .add_components(entity, masks::THIRD_PERSON_CAMERA);
303 world.core.set_third_person_camera(
304 entity,
305 nightshade::ecs::camera::components::ThirdPersonCamera::default(),
306 );
307 }
308 ComponentKind::CameraEnvironment => {
309 world.core.add_components(entity, masks::CAMERA_ENVIRONMENT);
310 world.core.set_camera_environment(
311 entity,
312 nightshade::ecs::camera::components::CameraEnvironment::default(),
313 );
314 }
315 ComponentKind::CameraPostProcess => {
316 world
317 .core
318 .add_components(entity, masks::CAMERA_POST_PROCESS);
319 world.core.set_camera_post_process(
320 entity,
321 nightshade::ecs::camera::components::CameraPostProcess::default(),
322 );
323 }
324 ComponentKind::ConstrainedAspect => {
325 world.core.add_components(entity, masks::CONSTRAINED_ASPECT);
326 world.core.set_constrained_aspect(
327 entity,
328 nightshade::ecs::camera::components::ConstrainedAspect::default(),
329 );
330 }
331 ComponentKind::ViewportUpdateMode => {
332 world
333 .core
334 .add_components(entity, masks::VIEWPORT_UPDATE_MODE);
335 world.core.set_viewport_update_mode(
336 entity,
337 nightshade::ecs::camera::components::ViewportUpdateMode::default(),
338 );
339 }
340 ComponentKind::Script => {
341 world.core.add_components(entity, masks::SCRIPT);
342 world.core.set_script(entity, Script::default());
343 }
344 }
345}
346
347#[derive(Clone, Serialize, Deserialize, enum2schema::Schema)]
351pub enum ComponentPatch {
352 Light(Light),
353 Visibility(bool),
354 CastsShadow(bool),
355 Camera(Camera),
356 RigidBody(RigidBodyComponent),
357 Collider(ColliderComponent),
358 CharacterController(CharacterControllerComponent),
359 NavmeshAgent(NavMeshAgent),
360 ParticleEmitter(Box<ParticleEmitter>),
361 Decal(Decal),
362 AudioSource(AudioSource),
363 RenderLayer(RenderLayer),
364 CullingMask(CullingMask),
365 CameraCullingMask(CameraCullingMask),
366 IgnoreParentScale(bool),
367 MorphWeights(Vec<f32>),
368 Text {
369 content: String,
370 properties: TextProperties,
371 },
372 Script(String),
373}
374
375impl ComponentPatch {
376 pub fn kind(&self) -> ComponentKind {
378 match self {
379 Self::Light(_) => ComponentKind::Light,
380 Self::Visibility(_) => ComponentKind::Visibility,
381 Self::CastsShadow(_) => ComponentKind::CastsShadow,
382 Self::Camera(_) => ComponentKind::Camera,
383 Self::RigidBody(_) => ComponentKind::RigidBody,
384 Self::Collider(_) => ComponentKind::Collider,
385 Self::CharacterController(_) => ComponentKind::CharacterController,
386 Self::NavmeshAgent(_) => ComponentKind::NavmeshAgent,
387 Self::ParticleEmitter(_) => ComponentKind::ParticleEmitter,
388 Self::Decal(_) => ComponentKind::Decal,
389 Self::AudioSource(_) => ComponentKind::AudioSource,
390 Self::RenderLayer(_) => ComponentKind::RenderLayer,
391 Self::CullingMask(_) => ComponentKind::CullingMask,
392 Self::CameraCullingMask(_) => ComponentKind::CameraCullingMask,
393 Self::IgnoreParentScale(_) => ComponentKind::IgnoreParentScale,
394 Self::MorphWeights(_) => ComponentKind::MorphWeights,
395 Self::Text { .. } => ComponentKind::Text,
396 Self::Script(_) => ComponentKind::Script,
397 }
398 }
399}
400
401pub fn apply_patch(world: &mut World, entity: Entity, patch: ComponentPatch) {
405 use nightshade::ecs::world as masks;
406 match patch {
407 ComponentPatch::Light(light) => {
408 if let Some(slot) = world.core.get_light_mut(entity) {
409 *slot = light;
410 }
411 }
412 ComponentPatch::Visibility(visible) => {
413 world.core.add_components(entity, masks::VISIBILITY);
414 world.core.set_visibility(entity, Visibility { visible });
415 }
416 ComponentPatch::CastsShadow(value) => {
417 if value {
418 world.core.add_components(entity, masks::CASTS_SHADOW);
419 world.core.set_casts_shadow(entity, CastsShadow);
420 } else {
421 world.core.remove_components(entity, masks::CASTS_SHADOW);
422 }
423 }
424 ComponentPatch::Camera(camera) => {
425 if let Some(slot) = world.core.get_camera_mut(entity) {
426 *slot = camera;
427 }
428 }
429 ComponentPatch::RigidBody(mut body) => {
430 if let Some(slot) = world.core.get_rigid_body_mut(entity) {
431 body.handle = slot.handle;
432 *slot = body;
433 }
434 }
435 ComponentPatch::Collider(mut collider) => {
436 if let Some(slot) = world.core.get_collider_mut(entity) {
437 collider.handle = slot.handle;
438 *slot = collider;
439 }
440 }
441 ComponentPatch::CharacterController(controller) => {
442 if let Some(slot) = world.core.get_character_controller_mut(entity) {
443 *slot = controller;
444 }
445 }
446 ComponentPatch::NavmeshAgent(agent) => {
447 if let Some(slot) = world.core.get_navmesh_agent_mut(entity) {
448 *slot = agent;
449 }
450 }
451 ComponentPatch::ParticleEmitter(emitter) => {
452 if let Some(slot) = world.core.get_particle_emitter_mut(entity) {
453 *slot = *emitter;
454 }
455 }
456 ComponentPatch::Decal(decal) => {
457 if let Some(slot) = world.core.get_decal_mut(entity) {
458 *slot = decal;
459 }
460 }
461 ComponentPatch::AudioSource(source) => {
462 if let Some(slot) = world.core.get_audio_source_mut(entity) {
463 *slot = source;
464 }
465 }
466 ComponentPatch::RenderLayer(layer) => {
467 if let Some(slot) = world.core.get_render_layer_mut(entity) {
468 *slot = layer;
469 }
470 }
471 ComponentPatch::CullingMask(mask) => {
472 if let Some(slot) = world.core.get_culling_mask_mut(entity) {
473 *slot = mask;
474 }
475 }
476 ComponentPatch::CameraCullingMask(mask) => {
477 if let Some(slot) = world.core.get_camera_culling_mask_mut(entity) {
478 *slot = mask;
479 }
480 }
481 ComponentPatch::IgnoreParentScale(present) => {
482 if present {
483 world
484 .core
485 .add_components(entity, masks::IGNORE_PARENT_SCALE);
486 world
487 .core
488 .set_ignore_parent_scale(entity, IgnoreParentScale);
489 } else {
490 world
491 .core
492 .remove_components(entity, masks::IGNORE_PARENT_SCALE);
493 }
494 }
495 ComponentPatch::MorphWeights(weights) => {
496 if let Some(slot) = world.core.get_morph_weights_mut(entity) {
497 for (index, weight) in weights.into_iter().enumerate() {
498 slot.set_weight(index, weight);
499 }
500 }
501 }
502 ComponentPatch::Text {
503 content,
504 properties,
505 } => {
506 let text_index = world.core.get_text(entity).map(|text| text.text_index);
507 if let Some(text_index) = text_index {
508 world.resources.text.cache.set_text(text_index, &content);
509 if let Some(text) = world.core.get_text_mut(entity) {
510 text.properties = properties;
511 text.dirty = true;
512 }
513 }
514 }
515 ComponentPatch::Script(source) => {
516 world.core.add_components(entity, masks::SCRIPT);
517 world.core.set_script(entity, Script::from_source(source));
518 }
519 }
520}
521
522#[derive(Clone, Copy, PartialEq, Eq, Hash)]
525pub enum SnapshotKind {
526 Light,
527 Visibility,
528 CastsShadow,
529 Name,
530 ParticleEmitter,
531 Decal,
532 AudioSource,
533 RigidBody,
534 Collider,
535 CharacterController,
536 NavmeshAgent,
537 Camera,
538 AnimationPlayer,
539 Text,
540 RenderLayer,
541 CullingMask,
542 CameraCullingMask,
543 IgnoreParentScale,
544 PrefabSource,
545}
546
547pub fn snapshot_kind_for(kind: ComponentKind) -> Option<SnapshotKind> {
551 Some(match kind {
552 ComponentKind::ParticleEmitter => SnapshotKind::ParticleEmitter,
553 ComponentKind::Decal => SnapshotKind::Decal,
554 ComponentKind::AudioSource => SnapshotKind::AudioSource,
555 ComponentKind::RigidBody => SnapshotKind::RigidBody,
556 ComponentKind::Collider => SnapshotKind::Collider,
557 ComponentKind::CharacterController => SnapshotKind::CharacterController,
558 ComponentKind::NavmeshAgent => SnapshotKind::NavmeshAgent,
559 ComponentKind::Camera => SnapshotKind::Camera,
560 ComponentKind::Text => SnapshotKind::Text,
561 ComponentKind::AnimationPlayer => SnapshotKind::AnimationPlayer,
562 ComponentKind::Visibility => SnapshotKind::Visibility,
563 ComponentKind::CastsShadow => SnapshotKind::CastsShadow,
564 ComponentKind::Light => SnapshotKind::Light,
565 ComponentKind::RenderLayer => SnapshotKind::RenderLayer,
566 ComponentKind::CullingMask => SnapshotKind::CullingMask,
567 ComponentKind::CameraCullingMask => SnapshotKind::CameraCullingMask,
568 ComponentKind::IgnoreParentScale => SnapshotKind::IgnoreParentScale,
569 _ => return None,
570 })
571}
572
573#[derive(Clone)]
577pub enum ComponentSnapshot {
578 Light(Light),
579 Visibility(bool),
580 CastsShadow(bool),
581 Name(String),
582 ParticleEmitter(Option<Box<ParticleEmitter>>),
583 Decal(Option<Box<Decal>>),
584 AudioSource(Option<Box<AudioSource>>),
585 RigidBody(Option<Box<RigidBodyComponent>>),
586 Collider(Option<Box<ColliderComponent>>),
587 CharacterController(Option<Box<CharacterControllerComponent>>),
588 NavmeshAgent(Option<Box<NavMeshAgent>>),
589 Camera(Option<Camera>),
590 AnimationPlayer(Option<Box<AnimationPlayer>>),
591 Material {
592 name: String,
593 material: Option<Box<Material>>,
594 },
595 Text {
596 component: Option<Box<Text>>,
597 content: Option<String>,
598 },
599 RenderLayer(Option<RenderLayer>),
600 CullingMask(Option<CullingMask>),
601 CameraCullingMask(Option<CameraCullingMask>),
602 IgnoreParentScale(bool),
603 PrefabSource(Option<Box<PrefabSource>>),
604}
605
606impl PartialEq for ComponentSnapshot {
607 fn eq(&self, other: &Self) -> bool {
608 match (self, other) {
609 (Self::Light(a), Self::Light(b)) => {
610 a.light_type == b.light_type
611 && a.color == b.color
612 && a.intensity == b.intensity
613 && a.range == b.range
614 && a.inner_cone_angle == b.inner_cone_angle
615 && a.outer_cone_angle == b.outer_cone_angle
616 && a.cast_shadows == b.cast_shadows
617 && a.shadow_bias == b.shadow_bias
618 }
619 (Self::Visibility(a), Self::Visibility(b)) => a == b,
620 (Self::CastsShadow(a), Self::CastsShadow(b)) => a == b,
621 (Self::Name(a), Self::Name(b)) => a == b,
622 (Self::ParticleEmitter(a), Self::ParticleEmitter(b)) => {
623 snapshot_bytes(a) == snapshot_bytes(b)
624 }
625 (Self::Decal(a), Self::Decal(b)) => a == b,
626 (Self::AudioSource(a), Self::AudioSource(b)) => snapshot_bytes(a) == snapshot_bytes(b),
627 (Self::RigidBody(a), Self::RigidBody(b)) => snapshot_bytes(a) == snapshot_bytes(b),
628 (Self::Collider(a), Self::Collider(b)) => snapshot_bytes(a) == snapshot_bytes(b),
629 (Self::CharacterController(a), Self::CharacterController(b)) => {
630 snapshot_bytes(a) == snapshot_bytes(b)
631 }
632 (Self::NavmeshAgent(a), Self::NavmeshAgent(b)) => {
633 snapshot_bytes(a) == snapshot_bytes(b)
634 }
635 (Self::Camera(a), Self::Camera(b)) => a == b,
636 (Self::AnimationPlayer(a), Self::AnimationPlayer(b)) => a == b,
637 (
638 Self::Material {
639 name: a_name,
640 material: a_mat,
641 },
642 Self::Material {
643 name: b_name,
644 material: b_mat,
645 },
646 ) => a_name == b_name && a_mat == b_mat,
647 (
648 Self::Text {
649 component: a_component,
650 content: a_content,
651 },
652 Self::Text {
653 component: b_component,
654 content: b_content,
655 },
656 ) => {
657 snapshot_bytes(a_component) == snapshot_bytes(b_component) && a_content == b_content
658 }
659 (Self::RenderLayer(a), Self::RenderLayer(b)) => a == b,
660 (Self::CullingMask(a), Self::CullingMask(b)) => a == b,
661 (Self::CameraCullingMask(a), Self::CameraCullingMask(b)) => a == b,
662 (Self::IgnoreParentScale(a), Self::IgnoreParentScale(b)) => a == b,
663 (Self::PrefabSource(a), Self::PrefabSource(b)) => {
664 snapshot_bytes(a) == snapshot_bytes(b)
665 }
666 _ => false,
667 }
668 }
669}
670
671fn snapshot_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
672 bincode::serialize(value).unwrap_or_default()
673}
674
675pub fn snapshot_component(
678 world: &World,
679 entity: Entity,
680 kind: SnapshotKind,
681) -> Option<ComponentSnapshot> {
682 match kind {
683 SnapshotKind::Light => world
684 .core
685 .get_light(entity)
686 .cloned()
687 .map(ComponentSnapshot::Light),
688 SnapshotKind::Visibility => world
689 .core
690 .get_visibility(entity)
691 .map(|visibility| ComponentSnapshot::Visibility(visibility.visible)),
692 SnapshotKind::CastsShadow => Some(ComponentSnapshot::CastsShadow(
693 world.core.entity_has_casts_shadow(entity),
694 )),
695 SnapshotKind::Name => Some(ComponentSnapshot::Name(
696 world
697 .core
698 .get_name(entity)
699 .map(|name| name.0.clone())
700 .unwrap_or_default(),
701 )),
702 SnapshotKind::ParticleEmitter => Some(ComponentSnapshot::ParticleEmitter(
703 world
704 .core
705 .get_particle_emitter(entity)
706 .cloned()
707 .map(Box::new),
708 )),
709 SnapshotKind::Decal => Some(ComponentSnapshot::Decal(
710 world.core.get_decal(entity).cloned().map(Box::new),
711 )),
712 SnapshotKind::AudioSource => Some(ComponentSnapshot::AudioSource(
713 world.core.get_audio_source(entity).cloned().map(Box::new),
714 )),
715 SnapshotKind::RigidBody => Some(ComponentSnapshot::RigidBody(
716 world.core.get_rigid_body(entity).cloned().map(Box::new),
717 )),
718 SnapshotKind::Collider => Some(ComponentSnapshot::Collider(
719 world.core.get_collider(entity).cloned().map(Box::new),
720 )),
721 SnapshotKind::CharacterController => Some(ComponentSnapshot::CharacterController(
722 world
723 .core
724 .get_character_controller(entity)
725 .cloned()
726 .map(Box::new),
727 )),
728 SnapshotKind::NavmeshAgent => Some(ComponentSnapshot::NavmeshAgent(
729 world.core.get_navmesh_agent(entity).cloned().map(Box::new),
730 )),
731 SnapshotKind::Camera => Some(ComponentSnapshot::Camera(
732 world.core.get_camera(entity).copied(),
733 )),
734 SnapshotKind::AnimationPlayer => Some(ComponentSnapshot::AnimationPlayer(
735 world
736 .core
737 .get_animation_player(entity)
738 .cloned()
739 .map(Box::new),
740 )),
741 SnapshotKind::Text => {
742 let component = world.core.get_text(entity).cloned().map(Box::new);
743 let content = component
744 .as_ref()
745 .and_then(|text| world.resources.text.cache.get_text(text.text_index))
746 .map(str::to_string);
747 Some(ComponentSnapshot::Text { component, content })
748 }
749 SnapshotKind::RenderLayer => Some(ComponentSnapshot::RenderLayer(
750 world.core.get_render_layer(entity).copied(),
751 )),
752 SnapshotKind::CullingMask => Some(ComponentSnapshot::CullingMask(
753 world.core.get_culling_mask(entity).copied(),
754 )),
755 SnapshotKind::CameraCullingMask => Some(ComponentSnapshot::CameraCullingMask(
756 world.core.get_camera_culling_mask(entity).copied(),
757 )),
758 SnapshotKind::IgnoreParentScale => Some(ComponentSnapshot::IgnoreParentScale(
759 world.core.entity_has_ignore_parent_scale(entity),
760 )),
761 SnapshotKind::PrefabSource => Some(ComponentSnapshot::PrefabSource(
762 world.core.get_prefab_source(entity).cloned().map(Box::new),
763 )),
764 }
765}
766
767pub fn restore_component(snapshot: &ComponentSnapshot, world: &mut World, entity: Entity) {
770 use nightshade::ecs::world as masks;
771 match snapshot {
772 ComponentSnapshot::Light(light) => {
773 if let Some(target) = world.core.get_light_mut(entity) {
774 *target = light.clone();
775 }
776 }
777 ComponentSnapshot::Visibility(visible) => {
778 world.core.add_components(entity, masks::VISIBILITY);
779 world
780 .core
781 .set_visibility(entity, Visibility { visible: *visible });
782 }
783 ComponentSnapshot::CastsShadow(value) => {
784 if *value {
785 world.core.add_components(entity, masks::CASTS_SHADOW);
786 world.core.set_casts_shadow(entity, CastsShadow);
787 } else {
788 world.core.remove_components(entity, masks::CASTS_SHADOW);
789 }
790 }
791 ComponentSnapshot::Name(text) => {
792 world.core.set_name(entity, Name(text.clone()));
793 }
794 ComponentSnapshot::ParticleEmitter(value) => {
795 restore_optional(
796 world,
797 entity,
798 value.as_deref().cloned(),
799 masks::PARTICLE_EMITTER,
800 |world, entity, value| world.core.set_particle_emitter(entity, value),
801 );
802 }
803 ComponentSnapshot::Decal(value) => {
804 restore_optional(
805 world,
806 entity,
807 value.as_deref().cloned(),
808 masks::DECAL,
809 |world, entity, value| world.core.set_decal(entity, value),
810 );
811 }
812 ComponentSnapshot::AudioSource(value) => {
813 restore_optional(
814 world,
815 entity,
816 value.as_deref().cloned(),
817 masks::AUDIO_SOURCE,
818 |world, entity, value| world.core.set_audio_source(entity, value),
819 );
820 }
821 ComponentSnapshot::RigidBody(value) => {
822 restore_optional(
823 world,
824 entity,
825 value.as_deref().cloned(),
826 masks::RIGID_BODY,
827 |world, entity, value| world.core.set_rigid_body(entity, value),
828 );
829 }
830 ComponentSnapshot::Collider(value) => {
831 restore_optional(
832 world,
833 entity,
834 value.as_deref().cloned(),
835 masks::COLLIDER,
836 |world, entity, value| world.core.set_collider(entity, value),
837 );
838 }
839 ComponentSnapshot::CharacterController(value) => {
840 restore_optional(
841 world,
842 entity,
843 value.as_deref().cloned(),
844 masks::CHARACTER_CONTROLLER,
845 |world, entity, value| world.core.set_character_controller(entity, value),
846 );
847 }
848 ComponentSnapshot::NavmeshAgent(value) => {
849 restore_optional(
850 world,
851 entity,
852 value.as_deref().cloned(),
853 masks::NAVMESH_AGENT,
854 |world, entity, value| world.core.set_navmesh_agent(entity, value),
855 );
856 }
857 ComponentSnapshot::Camera(value) => {
858 restore_optional(
859 world,
860 entity,
861 *value,
862 masks::CAMERA,
863 |world, entity, value| world.core.set_camera(entity, value),
864 );
865 }
866 ComponentSnapshot::AnimationPlayer(value) => {
867 restore_optional(
868 world,
869 entity,
870 value.as_deref().cloned(),
871 masks::ANIMATION_PLAYER,
872 |world, entity, value| world.core.set_animation_player(entity, value),
873 );
874 }
875 ComponentSnapshot::Material { name, material } => {
876 if let Some(material) = material.as_deref() {
877 queue_ecs_command(
878 world,
879 nightshade::ecs::world::commands::EcsCommand::ReloadMaterial {
880 name: name.clone(),
881 material: Box::new(material.clone()),
882 },
883 );
884 }
885 }
886 ComponentSnapshot::Text { component, content } => {
887 restore_optional(
888 world,
889 entity,
890 component.as_deref().cloned(),
891 masks::TEXT,
892 |world, entity, value| world.core.set_text(entity, value),
893 );
894 if let (Some(component), Some(content)) = (component.as_deref(), content.as_deref()) {
895 world
896 .resources
897 .text
898 .cache
899 .set_text(component.text_index, content);
900 }
901 }
902 ComponentSnapshot::RenderLayer(value) => {
903 restore_optional(
904 world,
905 entity,
906 *value,
907 masks::RENDER_LAYER,
908 |world, entity, value| world.core.set_render_layer(entity, value),
909 );
910 }
911 ComponentSnapshot::CullingMask(value) => {
912 restore_optional(
913 world,
914 entity,
915 *value,
916 masks::CULLING_MASK,
917 |world, entity, value| world.core.set_culling_mask(entity, value),
918 );
919 }
920 ComponentSnapshot::CameraCullingMask(value) => {
921 restore_optional(
922 world,
923 entity,
924 *value,
925 masks::CAMERA_CULLING_MASK,
926 |world, entity, value| world.core.set_camera_culling_mask(entity, value),
927 );
928 }
929 ComponentSnapshot::IgnoreParentScale(present) => {
930 if *present {
931 world
932 .core
933 .add_components(entity, masks::IGNORE_PARENT_SCALE);
934 world
935 .core
936 .set_ignore_parent_scale(entity, IgnoreParentScale);
937 } else {
938 world
939 .core
940 .remove_components(entity, masks::IGNORE_PARENT_SCALE);
941 }
942 }
943 ComponentSnapshot::PrefabSource(value) => {
944 restore_optional(
945 world,
946 entity,
947 value.as_deref().cloned(),
948 masks::PREFAB_SOURCE,
949 |world, entity, value| world.core.set_prefab_source(entity, value),
950 );
951 }
952 }
953}
954
955fn restore_optional<T, F>(world: &mut World, entity: Entity, value: Option<T>, mask: u64, set: F)
958where
959 F: FnOnce(&mut World, Entity, T),
960{
961 if let Some(value) = value {
962 world.core.add_components(entity, mask);
963 set(world, entity, value);
964 } else {
965 world.core.remove_components(entity, mask);
966 }
967}