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::morph::components::MorphWeights;
17use nightshade::ecs::prefab::components::PrefabSource;
18use nightshade::ecs::primitives::{CameraCullingMask, CullingMask};
19use nightshade::ecs::script::Script;
20use nightshade::ecs::text::components::{Text, TextProperties};
21use nightshade::ecs::transform::components::IgnoreParentScale;
22use nightshade::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator};
23use nightshade::ecs::water::components::Water;
24use nightshade::prelude::*;
25use nightshade::render::material::Material;
26use nightshade::render::particles::ParticleEmitter;
27use nightshade::render::render_layer::RenderLayer;
28use serde::{Deserialize, Serialize};
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, enum2schema::Schema)]
33#[schema(string_enum)]
34pub enum ComponentKind {
35 ParticleEmitter,
36 Decal,
37 Water,
38 AudioSource,
39 RigidBody,
40 Collider,
41 CharacterController,
42 NavmeshAgent,
43 Camera,
44 Text,
45 AnimationPlayer,
46 Visibility,
47 CastsShadow,
48 Light,
49 RenderLayer,
50 CullingMask,
51 CameraCullingMask,
52 IgnoreParentScale,
53 AudioListener,
54 CollisionListener,
55 PhysicsInterpolation,
56 MorphWeights,
57 MaterialVariants,
58 PanOrbitCamera,
59 ThirdPersonCamera,
60 CameraEnvironment,
61 CameraPostProcess,
62 ConstrainedAspect,
63 ViewportUpdateMode,
64 Script,
65 Beam,
66 LightningBolt,
67 Trail,
68 VfxAnimator,
69}
70
71pub const ALL_COMPONENT_KINDS: [ComponentKind; 34] = [
73 ComponentKind::ParticleEmitter,
74 ComponentKind::Decal,
75 ComponentKind::Water,
76 ComponentKind::AudioSource,
77 ComponentKind::RigidBody,
78 ComponentKind::Collider,
79 ComponentKind::CharacterController,
80 ComponentKind::NavmeshAgent,
81 ComponentKind::Camera,
82 ComponentKind::Text,
83 ComponentKind::AnimationPlayer,
84 ComponentKind::Visibility,
85 ComponentKind::CastsShadow,
86 ComponentKind::Light,
87 ComponentKind::RenderLayer,
88 ComponentKind::CullingMask,
89 ComponentKind::CameraCullingMask,
90 ComponentKind::IgnoreParentScale,
91 ComponentKind::AudioListener,
92 ComponentKind::CollisionListener,
93 ComponentKind::PhysicsInterpolation,
94 ComponentKind::MorphWeights,
95 ComponentKind::MaterialVariants,
96 ComponentKind::PanOrbitCamera,
97 ComponentKind::ThirdPersonCamera,
98 ComponentKind::CameraEnvironment,
99 ComponentKind::CameraPostProcess,
100 ComponentKind::ConstrainedAspect,
101 ComponentKind::ViewportUpdateMode,
102 ComponentKind::Script,
103 ComponentKind::Beam,
104 ComponentKind::LightningBolt,
105 ComponentKind::Trail,
106 ComponentKind::VfxAnimator,
107];
108
109pub fn kind_mask(kind: ComponentKind) -> u64 {
112 use nightshade::ecs::world as masks;
113 match kind {
114 ComponentKind::ParticleEmitter => masks::PARTICLE_EMITTER,
115 ComponentKind::Decal => masks::DECAL,
116 ComponentKind::Water => masks::WATER,
117 ComponentKind::AudioSource => masks::AUDIO_SOURCE,
118 ComponentKind::RigidBody => masks::RIGID_BODY,
119 ComponentKind::Collider => masks::COLLIDER,
120 ComponentKind::CharacterController => masks::CHARACTER_CONTROLLER,
121 ComponentKind::NavmeshAgent => masks::NAVMESH_AGENT,
122 ComponentKind::Camera => masks::CAMERA,
123 ComponentKind::Text => masks::TEXT,
124 ComponentKind::AnimationPlayer => masks::ANIMATION_PLAYER,
125 ComponentKind::Visibility => masks::VISIBILITY,
126 ComponentKind::CastsShadow => masks::CASTS_SHADOW,
127 ComponentKind::Light => masks::LIGHT,
128 ComponentKind::RenderLayer => masks::RENDER_LAYER,
129 ComponentKind::CullingMask => masks::CULLING_MASK,
130 ComponentKind::CameraCullingMask => masks::CAMERA_CULLING_MASK,
131 ComponentKind::IgnoreParentScale => masks::IGNORE_PARENT_SCALE,
132 ComponentKind::AudioListener => masks::AUDIO_LISTENER,
133 ComponentKind::CollisionListener => masks::COLLISION_LISTENER,
134 ComponentKind::PhysicsInterpolation => masks::PHYSICS_INTERPOLATION,
135 ComponentKind::MorphWeights => masks::MORPH_WEIGHTS,
136 ComponentKind::MaterialVariants => masks::MATERIAL_VARIANTS,
137 ComponentKind::PanOrbitCamera => masks::PAN_ORBIT_CAMERA,
138 ComponentKind::ThirdPersonCamera => masks::THIRD_PERSON_CAMERA,
139 ComponentKind::CameraEnvironment => masks::CAMERA_ENVIRONMENT,
140 ComponentKind::CameraPostProcess => masks::CAMERA_POST_PROCESS,
141 ComponentKind::ConstrainedAspect => masks::CONSTRAINED_ASPECT,
142 ComponentKind::ViewportUpdateMode => masks::VIEWPORT_UPDATE_MODE,
143 ComponentKind::Script => masks::SCRIPT,
144 ComponentKind::Beam => masks::BEAM,
145 ComponentKind::LightningBolt => masks::LIGHTNING_BOLT,
146 ComponentKind::Trail => masks::TRAIL,
147 ComponentKind::VfxAnimator => masks::VFX_ANIMATOR,
148 }
149}
150
151pub fn has_component(world: &World, entity: Entity, kind: ComponentKind) -> bool {
153 world.ecs.worlds[CORE].entity_has_components(entity, kind_mask(kind))
154}
155
156pub fn remove_component(world: &mut World, entity: Entity, kind: ComponentKind) {
158 world.ecs.worlds[CORE].remove_components(entity, kind_mask(kind));
159}
160
161pub fn present_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
163 ALL_COMPONENT_KINDS
164 .into_iter()
165 .filter(|kind| has_component(world, entity, *kind))
166 .collect()
167}
168
169pub fn addable_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
171 ALL_COMPONENT_KINDS
172 .into_iter()
173 .filter(|kind| !has_component(world, entity, *kind))
174 .collect()
175}
176
177pub fn add_component_default(world: &mut World, entity: Entity, kind: ComponentKind) {
181 use nightshade::ecs::world as masks;
182 match kind {
183 ComponentKind::ParticleEmitter => {
184 world.ecs.worlds[CORE].add_components(entity, masks::PARTICLE_EMITTER);
185 world.set(entity, ParticleEmitter::default());
186 }
187 ComponentKind::Decal => {
188 world.ecs.worlds[CORE].add_components(entity, masks::DECAL);
189 world.set(entity, Decal::default());
190 }
191 ComponentKind::Water => {
192 world.ecs.worlds[CORE].add_components(entity, masks::WATER);
193 world.set(entity, Water::default());
194 }
195 ComponentKind::AudioSource => {
196 world.ecs.worlds[CORE].add_components(entity, masks::AUDIO_SOURCE);
197 world.set(entity, AudioSource::default());
198 }
199 ComponentKind::RigidBody => {
200 world.ecs.worlds[CORE].add_components(entity, masks::RIGID_BODY);
201 world.set(entity, RigidBodyComponent::new_dynamic());
202 }
203 ComponentKind::Collider => {
204 world.ecs.worlds[CORE].add_components(entity, masks::COLLIDER);
205 world.set(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5));
206 }
207 ComponentKind::CharacterController => {
208 world.ecs.worlds[CORE].add_components(entity, masks::CHARACTER_CONTROLLER);
209 world.set(entity, CharacterControllerComponent::new_capsule(0.9, 0.3));
210 }
211 ComponentKind::NavmeshAgent => {
212 world.ecs.worlds[CORE].add_components(entity, masks::NAVMESH_AGENT);
213 world.set(entity, NavMeshAgent::new());
214 }
215 ComponentKind::Camera => {
216 world.ecs.worlds[CORE].add_components(entity, masks::CAMERA);
217 world.set(entity, Camera::default());
218 }
219 ComponentKind::Text => {
220 let text_index = world.resources.text.cache.add_text("Text");
221 world.ecs.worlds[CORE].add_components(entity, masks::TEXT);
222 world.set(entity, Text::new(text_index));
223 }
224 ComponentKind::AnimationPlayer => {
225 world.ecs.worlds[CORE].add_components(entity, masks::ANIMATION_PLAYER);
226 world.set(entity, AnimationPlayer::default());
227 }
228 ComponentKind::Visibility => {
229 world.ecs.worlds[CORE].add_components(entity, masks::VISIBILITY);
230 world.set(entity, Visibility { visible: true });
231 }
232 ComponentKind::CastsShadow => {
233 world.ecs.worlds[CORE].add_components(entity, masks::CASTS_SHADOW);
234 world.set(entity, CastsShadow);
235 }
236 ComponentKind::Light => {
237 world.ecs.worlds[CORE].add_components(entity, masks::LIGHT);
238 world.set(entity, Light::default());
239 }
240 ComponentKind::RenderLayer => {
241 world.ecs.worlds[CORE].add_components(entity, masks::RENDER_LAYER);
242 world.set(entity, RenderLayer::default());
243 }
244 ComponentKind::CullingMask => {
245 world.ecs.worlds[CORE].add_components(entity, masks::CULLING_MASK);
246 world.set(entity, CullingMask::default());
247 }
248 ComponentKind::CameraCullingMask => {
249 world.ecs.worlds[CORE].add_components(entity, masks::CAMERA_CULLING_MASK);
250 world.set(entity, CameraCullingMask::default());
251 }
252 ComponentKind::IgnoreParentScale => {
253 world.ecs.worlds[CORE].add_components(entity, masks::IGNORE_PARENT_SCALE);
254 world.set(entity, IgnoreParentScale);
255 }
256 ComponentKind::AudioListener => {
257 world.ecs.worlds[CORE].add_components(entity, masks::AUDIO_LISTENER);
258 world.set(entity, nightshade::ecs::audio::components::AudioListener);
259 }
260 ComponentKind::CollisionListener => {
261 world.ecs.worlds[CORE].add_components(entity, masks::COLLISION_LISTENER);
262 world.set(
263 entity,
264 nightshade::ecs::physics::components::CollisionListener,
265 );
266 }
267 ComponentKind::PhysicsInterpolation => {
268 world.ecs.worlds[CORE].add_components(entity, masks::PHYSICS_INTERPOLATION);
269 world.set(
270 entity,
271 nightshade::ecs::physics::components::PhysicsInterpolation::default(),
272 );
273 }
274 ComponentKind::MorphWeights => {
275 world.ecs.worlds[CORE].add_components(entity, masks::MORPH_WEIGHTS);
276 world.set(entity, MorphWeights::default());
277 }
278 ComponentKind::MaterialVariants => {
279 world.ecs.worlds[CORE].add_components(entity, masks::MATERIAL_VARIANTS);
280 world.set(
281 entity,
282 nightshade::ecs::material::components::MaterialVariants::default(),
283 );
284 }
285 ComponentKind::PanOrbitCamera => {
286 world.ecs.worlds[CORE].add_components(entity, masks::PAN_ORBIT_CAMERA);
287 world.set(
288 entity,
289 nightshade::ecs::camera::components::PanOrbitCamera::default(),
290 );
291 }
292 ComponentKind::ThirdPersonCamera => {
293 world.ecs.worlds[CORE].add_components(entity, masks::THIRD_PERSON_CAMERA);
294 world.set(
295 entity,
296 nightshade::ecs::camera::components::ThirdPersonCamera::default(),
297 );
298 }
299 ComponentKind::CameraEnvironment => {
300 world.ecs.worlds[CORE].add_components(entity, masks::CAMERA_ENVIRONMENT);
301 world.set(
302 entity,
303 nightshade::ecs::camera::components::CameraEnvironment::default(),
304 );
305 }
306 ComponentKind::CameraPostProcess => {
307 world.ecs.worlds[CORE].add_components(entity, masks::CAMERA_POST_PROCESS);
308 world.set(
309 entity,
310 nightshade::ecs::camera::components::CameraPostProcess::default(),
311 );
312 }
313 ComponentKind::ConstrainedAspect => {
314 world.ecs.worlds[CORE].add_components(entity, masks::CONSTRAINED_ASPECT);
315 world.set(
316 entity,
317 nightshade::ecs::camera::components::ConstrainedAspect::default(),
318 );
319 }
320 ComponentKind::ViewportUpdateMode => {
321 world.ecs.worlds[CORE].add_components(entity, masks::VIEWPORT_UPDATE_MODE);
322 world.set(
323 entity,
324 nightshade::render::config::ViewportUpdateMode::default(),
325 );
326 }
327 ComponentKind::Script => {
328 world.ecs.worlds[CORE].add_components(entity, masks::SCRIPT);
329 world.set(entity, Script::default());
330 }
331 ComponentKind::Beam => {
332 world.ecs.worlds[CORE].add_components(entity, masks::BEAM);
333 world.set(entity, Beam::default());
334 }
335 ComponentKind::LightningBolt => {
336 world.ecs.worlds[CORE].add_components(entity, masks::LIGHTNING_BOLT);
337 world.set(entity, LightningBolt::default());
338 }
339 ComponentKind::Trail => {
340 world.ecs.worlds[CORE].add_components(entity, masks::TRAIL);
341 world.set(entity, Trail::default());
342 }
343 ComponentKind::VfxAnimator => {
344 world.ecs.worlds[CORE].add_components(entity, masks::VFX_ANIMATOR);
345 world.set(entity, VfxAnimator::default());
346 }
347 }
348}
349
350#[derive(Clone, Serialize, Deserialize, enum2schema::Schema)]
354pub enum ComponentPatch {
355 Light(Light),
356 Visibility(bool),
357 CastsShadow(bool),
358 Camera(Camera),
359 RigidBody(RigidBodyComponent),
360 Collider(ColliderComponent),
361 CharacterController(CharacterControllerComponent),
362 NavmeshAgent(NavMeshAgent),
363 ParticleEmitter(Box<ParticleEmitter>),
364 Decal(Decal),
365 Water(Water),
366 AudioSource(AudioSource),
367 RenderLayer(RenderLayer),
368 CullingMask(CullingMask),
369 CameraCullingMask(CameraCullingMask),
370 IgnoreParentScale(bool),
371 MorphWeights(Vec<f32>),
372 Text {
373 content: String,
374 properties: TextProperties,
375 },
376 Script(String),
377 Beam(Box<Beam>),
378 LightningBolt(Box<LightningBolt>),
379 Trail(Box<Trail>),
380 VfxAnimator(Box<VfxAnimator>),
381}
382
383impl ComponentPatch {
384 pub fn kind(&self) -> ComponentKind {
386 match self {
387 Self::Light(_) => ComponentKind::Light,
388 Self::Visibility(_) => ComponentKind::Visibility,
389 Self::CastsShadow(_) => ComponentKind::CastsShadow,
390 Self::Camera(_) => ComponentKind::Camera,
391 Self::RigidBody(_) => ComponentKind::RigidBody,
392 Self::Collider(_) => ComponentKind::Collider,
393 Self::CharacterController(_) => ComponentKind::CharacterController,
394 Self::NavmeshAgent(_) => ComponentKind::NavmeshAgent,
395 Self::ParticleEmitter(_) => ComponentKind::ParticleEmitter,
396 Self::Decal(_) => ComponentKind::Decal,
397 Self::Water(_) => ComponentKind::Water,
398 Self::AudioSource(_) => ComponentKind::AudioSource,
399 Self::RenderLayer(_) => ComponentKind::RenderLayer,
400 Self::CullingMask(_) => ComponentKind::CullingMask,
401 Self::CameraCullingMask(_) => ComponentKind::CameraCullingMask,
402 Self::IgnoreParentScale(_) => ComponentKind::IgnoreParentScale,
403 Self::MorphWeights(_) => ComponentKind::MorphWeights,
404 Self::Text { .. } => ComponentKind::Text,
405 Self::Script(_) => ComponentKind::Script,
406 Self::Beam(_) => ComponentKind::Beam,
407 Self::LightningBolt(_) => ComponentKind::LightningBolt,
408 Self::Trail(_) => ComponentKind::Trail,
409 Self::VfxAnimator(_) => ComponentKind::VfxAnimator,
410 }
411 }
412}
413
414pub fn apply_patch(world: &mut World, entity: Entity, patch: ComponentPatch) {
418 use nightshade::ecs::world as masks;
419 match patch {
420 ComponentPatch::Light(light) => {
421 if let Some(slot) = world.get_mut::<nightshade::ecs::light::components::Light>(entity) {
422 *slot = light;
423 }
424 }
425 ComponentPatch::Visibility(visible) => {
426 world.ecs.worlds[CORE].add_components(entity, masks::VISIBILITY);
427 world.set(entity, Visibility { visible });
428 }
429 ComponentPatch::CastsShadow(value) => {
430 if value {
431 world.ecs.worlds[CORE].add_components(entity, masks::CASTS_SHADOW);
432 world.set(entity, CastsShadow);
433 } else {
434 world.ecs.worlds[CORE].remove_components(entity, masks::CASTS_SHADOW);
435 }
436 }
437 ComponentPatch::Camera(camera) => {
438 if let Some(slot) = world.get_mut::<nightshade::ecs::camera::components::Camera>(entity)
439 {
440 *slot = camera;
441 }
442 }
443 ComponentPatch::RigidBody(mut body) => {
444 if let Some(slot) =
445 world.get_mut::<nightshade::ecs::physics::components::RigidBodyComponent>(entity)
446 {
447 body.handle = slot.handle;
448 *slot = body;
449 }
450 }
451 ComponentPatch::Collider(mut collider) => {
452 if let Some(slot) =
453 world.get_mut::<nightshade::ecs::physics::components::ColliderComponent>(entity)
454 {
455 collider.handle = slot.handle;
456 *slot = collider;
457 }
458 }
459 ComponentPatch::CharacterController(controller) => {
460 if let Some(slot) = world
461 .get_mut::<nightshade::ecs::physics::components::CharacterControllerComponent>(
462 entity,
463 ) {
464 *slot = controller;
465 }
466 }
467 ComponentPatch::NavmeshAgent(agent) => {
468 if let Some(slot) =
469 world.get_mut::<nightshade::ecs::navmesh::components::NavMeshAgent>(entity)
470 {
471 *slot = agent;
472 }
473 }
474 ComponentPatch::ParticleEmitter(emitter) => {
475 if let Some(slot) =
476 world.get_mut::<nightshade::render::particles::ParticleEmitter>(entity)
477 {
478 *slot = *emitter;
479 }
480 }
481 ComponentPatch::Decal(decal) => {
482 if let Some(slot) = world.get_mut::<nightshade::ecs::decal::components::Decal>(entity) {
483 *slot = decal;
484 }
485 }
486 ComponentPatch::Water(water) => {
487 if let Some(slot) = world.get_mut::<nightshade::ecs::water::components::Water>(entity) {
488 *slot = water;
489 }
490 }
491 ComponentPatch::AudioSource(source) => {
492 if let Some(slot) =
493 world.get_mut::<nightshade::ecs::audio::components::AudioSource>(entity)
494 {
495 *slot = source;
496 }
497 }
498 ComponentPatch::RenderLayer(layer) => {
499 if let Some(slot) =
500 world.get_mut::<nightshade::render::render_layer::RenderLayer>(entity)
501 {
502 *slot = layer;
503 }
504 }
505 ComponentPatch::CullingMask(mask) => {
506 if let Some(slot) = world.get_mut::<nightshade::ecs::primitives::CullingMask>(entity) {
507 *slot = mask;
508 }
509 }
510 ComponentPatch::CameraCullingMask(mask) => {
511 if let Some(slot) =
512 world.get_mut::<nightshade::ecs::primitives::CameraCullingMask>(entity)
513 {
514 *slot = mask;
515 }
516 }
517 ComponentPatch::IgnoreParentScale(present) => {
518 if present {
519 world.ecs.worlds[CORE].add_components(entity, masks::IGNORE_PARENT_SCALE);
520 world.set(entity, IgnoreParentScale);
521 } else {
522 world.ecs.worlds[CORE].remove_components(entity, masks::IGNORE_PARENT_SCALE);
523 }
524 }
525 ComponentPatch::MorphWeights(weights) => {
526 if let Some(slot) =
527 world.get_mut::<nightshade::ecs::morph::components::MorphWeights>(entity)
528 {
529 for (index, weight) in weights.into_iter().enumerate() {
530 slot.set_weight(index, weight);
531 }
532 }
533 }
534 ComponentPatch::Text {
535 content,
536 properties,
537 } => {
538 let text_index = world
539 .get::<nightshade::ecs::text::components::Text>(entity)
540 .map(|text| text.text_index);
541 if let Some(text_index) = text_index {
542 world.resources.text.cache.set_text(text_index, &content);
543 if let Some(text) = world.get_mut::<nightshade::ecs::text::components::Text>(entity)
544 {
545 text.properties = properties;
546 text.dirty = true;
547 }
548 }
549 }
550 ComponentPatch::Script(source) => {
551 world.ecs.worlds[CORE].add_components(entity, masks::SCRIPT);
552 world.set(entity, Script::from_source(source));
553 }
554 ComponentPatch::Beam(beam) => {
555 if let Some(slot) = world.get_mut::<nightshade::ecs::vfx::components::Beam>(entity) {
556 *slot = *beam;
557 }
558 }
559 ComponentPatch::LightningBolt(bolt) => {
560 if let Some(slot) =
561 world.get_mut::<nightshade::ecs::vfx::components::LightningBolt>(entity)
562 {
563 *slot = *bolt;
564 }
565 }
566 ComponentPatch::Trail(trail) => {
567 if let Some(slot) = world.get_mut::<nightshade::ecs::vfx::components::Trail>(entity) {
568 *slot = *trail;
569 }
570 }
571 ComponentPatch::VfxAnimator(animator) => {
572 if let Some(slot) =
573 world.get_mut::<nightshade::ecs::vfx::components::VfxAnimator>(entity)
574 {
575 *slot = *animator;
576 }
577 }
578 }
579}
580
581#[derive(Clone, Copy, PartialEq, Eq, Hash)]
584pub enum SnapshotKind {
585 Light,
586 Visibility,
587 CastsShadow,
588 Name,
589 ParticleEmitter,
590 Decal,
591 Water,
592 AudioSource,
593 RigidBody,
594 Collider,
595 CharacterController,
596 NavmeshAgent,
597 Camera,
598 AnimationPlayer,
599 Text,
600 RenderLayer,
601 CullingMask,
602 CameraCullingMask,
603 IgnoreParentScale,
604 PrefabSource,
605 Beam,
606 LightningBolt,
607 Trail,
608 VfxAnimator,
609}
610
611pub fn snapshot_kind_for(kind: ComponentKind) -> Option<SnapshotKind> {
615 Some(match kind {
616 ComponentKind::ParticleEmitter => SnapshotKind::ParticleEmitter,
617 ComponentKind::Decal => SnapshotKind::Decal,
618 ComponentKind::Water => SnapshotKind::Water,
619 ComponentKind::AudioSource => SnapshotKind::AudioSource,
620 ComponentKind::RigidBody => SnapshotKind::RigidBody,
621 ComponentKind::Collider => SnapshotKind::Collider,
622 ComponentKind::CharacterController => SnapshotKind::CharacterController,
623 ComponentKind::NavmeshAgent => SnapshotKind::NavmeshAgent,
624 ComponentKind::Camera => SnapshotKind::Camera,
625 ComponentKind::Text => SnapshotKind::Text,
626 ComponentKind::AnimationPlayer => SnapshotKind::AnimationPlayer,
627 ComponentKind::Visibility => SnapshotKind::Visibility,
628 ComponentKind::CastsShadow => SnapshotKind::CastsShadow,
629 ComponentKind::Light => SnapshotKind::Light,
630 ComponentKind::RenderLayer => SnapshotKind::RenderLayer,
631 ComponentKind::CullingMask => SnapshotKind::CullingMask,
632 ComponentKind::CameraCullingMask => SnapshotKind::CameraCullingMask,
633 ComponentKind::IgnoreParentScale => SnapshotKind::IgnoreParentScale,
634 ComponentKind::Beam => SnapshotKind::Beam,
635 ComponentKind::LightningBolt => SnapshotKind::LightningBolt,
636 ComponentKind::Trail => SnapshotKind::Trail,
637 ComponentKind::VfxAnimator => SnapshotKind::VfxAnimator,
638 _ => return None,
639 })
640}
641
642#[derive(Clone)]
646pub enum ComponentSnapshot {
647 Light(Light),
648 Visibility(bool),
649 CastsShadow(bool),
650 Name(String),
651 ParticleEmitter(Option<Box<ParticleEmitter>>),
652 Decal(Option<Box<Decal>>),
653 Water(Option<Box<Water>>),
654 AudioSource(Option<Box<AudioSource>>),
655 RigidBody(Option<Box<RigidBodyComponent>>),
656 Collider(Option<Box<ColliderComponent>>),
657 CharacterController(Option<Box<CharacterControllerComponent>>),
658 NavmeshAgent(Option<Box<NavMeshAgent>>),
659 Camera(Option<Camera>),
660 AnimationPlayer(Option<Box<AnimationPlayer>>),
661 Material {
662 name: String,
663 material: Option<Box<Material>>,
664 },
665 Text {
666 component: Option<Box<Text>>,
667 content: Option<String>,
668 },
669 RenderLayer(Option<RenderLayer>),
670 CullingMask(Option<CullingMask>),
671 CameraCullingMask(Option<CameraCullingMask>),
672 IgnoreParentScale(bool),
673 PrefabSource(Option<Box<PrefabSource>>),
674 Beam(Option<Box<Beam>>),
675 LightningBolt(Option<Box<LightningBolt>>),
676 Trail(Option<Box<Trail>>),
677 VfxAnimator(Option<Box<VfxAnimator>>),
678}
679
680impl PartialEq for ComponentSnapshot {
681 fn eq(&self, other: &Self) -> bool {
682 match (self, other) {
683 (Self::Light(a), Self::Light(b)) => {
684 a.light_type == b.light_type
685 && a.color == b.color
686 && a.intensity == b.intensity
687 && a.range == b.range
688 && a.inner_cone_angle == b.inner_cone_angle
689 && a.outer_cone_angle == b.outer_cone_angle
690 && a.cast_shadows == b.cast_shadows
691 && a.shadow_bias == b.shadow_bias
692 }
693 (Self::Visibility(a), Self::Visibility(b)) => a == b,
694 (Self::CastsShadow(a), Self::CastsShadow(b)) => a == b,
695 (Self::Name(a), Self::Name(b)) => a == b,
696 (Self::ParticleEmitter(a), Self::ParticleEmitter(b)) => {
697 snapshot_bytes(a) == snapshot_bytes(b)
698 }
699 (Self::Decal(a), Self::Decal(b)) => a == b,
700 (Self::Water(a), Self::Water(b)) => a == b,
701 (Self::AudioSource(a), Self::AudioSource(b)) => snapshot_bytes(a) == snapshot_bytes(b),
702 (Self::RigidBody(a), Self::RigidBody(b)) => snapshot_bytes(a) == snapshot_bytes(b),
703 (Self::Collider(a), Self::Collider(b)) => snapshot_bytes(a) == snapshot_bytes(b),
704 (Self::CharacterController(a), Self::CharacterController(b)) => {
705 snapshot_bytes(a) == snapshot_bytes(b)
706 }
707 (Self::NavmeshAgent(a), Self::NavmeshAgent(b)) => {
708 snapshot_bytes(a) == snapshot_bytes(b)
709 }
710 (Self::Camera(a), Self::Camera(b)) => a == b,
711 (Self::AnimationPlayer(a), Self::AnimationPlayer(b)) => a == b,
712 (
713 Self::Material {
714 name: a_name,
715 material: a_mat,
716 },
717 Self::Material {
718 name: b_name,
719 material: b_mat,
720 },
721 ) => a_name == b_name && a_mat == b_mat,
722 (
723 Self::Text {
724 component: a_component,
725 content: a_content,
726 },
727 Self::Text {
728 component: b_component,
729 content: b_content,
730 },
731 ) => {
732 snapshot_bytes(a_component) == snapshot_bytes(b_component) && a_content == b_content
733 }
734 (Self::RenderLayer(a), Self::RenderLayer(b)) => a == b,
735 (Self::CullingMask(a), Self::CullingMask(b)) => a == b,
736 (Self::CameraCullingMask(a), Self::CameraCullingMask(b)) => a == b,
737 (Self::IgnoreParentScale(a), Self::IgnoreParentScale(b)) => a == b,
738 (Self::PrefabSource(a), Self::PrefabSource(b)) => {
739 snapshot_bytes(a) == snapshot_bytes(b)
740 }
741 (Self::Beam(a), Self::Beam(b)) => snapshot_bytes(a) == snapshot_bytes(b),
742 (Self::LightningBolt(a), Self::LightningBolt(b)) => {
743 snapshot_bytes(a) == snapshot_bytes(b)
744 }
745 (Self::Trail(a), Self::Trail(b)) => snapshot_bytes(a) == snapshot_bytes(b),
746 (Self::VfxAnimator(a), Self::VfxAnimator(b)) => snapshot_bytes(a) == snapshot_bytes(b),
747 _ => false,
748 }
749 }
750}
751
752fn snapshot_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
753 bincode::serialize(value).unwrap_or_default()
754}
755
756pub fn snapshot_component(
759 world: &World,
760 entity: Entity,
761 kind: SnapshotKind,
762) -> Option<ComponentSnapshot> {
763 match kind {
764 SnapshotKind::Light => world
765 .get::<nightshade::ecs::light::components::Light>(entity)
766 .cloned()
767 .map(ComponentSnapshot::Light),
768 SnapshotKind::Visibility => world
769 .get::<nightshade::ecs::primitives::Visibility>(entity)
770 .map(|visibility| ComponentSnapshot::Visibility(visibility.visible)),
771 SnapshotKind::CastsShadow => Some(ComponentSnapshot::CastsShadow(
772 world.has::<nightshade::ecs::primitives::CastsShadow>(entity),
773 )),
774 SnapshotKind::Name => Some(ComponentSnapshot::Name(
775 world
776 .get::<nightshade::ecs::primitives::Name>(entity)
777 .map(|name| name.0.clone())
778 .unwrap_or_default(),
779 )),
780 SnapshotKind::ParticleEmitter => Some(ComponentSnapshot::ParticleEmitter(
781 world
782 .get::<nightshade::render::particles::ParticleEmitter>(entity)
783 .cloned()
784 .map(Box::new),
785 )),
786 SnapshotKind::Decal => Some(ComponentSnapshot::Decal(
787 world
788 .get::<nightshade::ecs::decal::components::Decal>(entity)
789 .cloned()
790 .map(Box::new),
791 )),
792 SnapshotKind::Water => Some(ComponentSnapshot::Water(
793 world
794 .get::<nightshade::ecs::water::components::Water>(entity)
795 .cloned()
796 .map(Box::new),
797 )),
798 SnapshotKind::AudioSource => Some(ComponentSnapshot::AudioSource(
799 world
800 .get::<nightshade::ecs::audio::components::AudioSource>(entity)
801 .cloned()
802 .map(Box::new),
803 )),
804 SnapshotKind::RigidBody => Some(ComponentSnapshot::RigidBody(
805 world
806 .get::<nightshade::ecs::physics::components::RigidBodyComponent>(entity)
807 .cloned()
808 .map(Box::new),
809 )),
810 SnapshotKind::Collider => Some(ComponentSnapshot::Collider(
811 world
812 .get::<nightshade::ecs::physics::components::ColliderComponent>(entity)
813 .cloned()
814 .map(Box::new),
815 )),
816 SnapshotKind::CharacterController => Some(ComponentSnapshot::CharacterController(
817 world
818 .get::<nightshade::ecs::physics::components::CharacterControllerComponent>(entity)
819 .cloned()
820 .map(Box::new),
821 )),
822 SnapshotKind::NavmeshAgent => Some(ComponentSnapshot::NavmeshAgent(
823 world
824 .get::<nightshade::ecs::navmesh::components::NavMeshAgent>(entity)
825 .cloned()
826 .map(Box::new),
827 )),
828 SnapshotKind::Camera => Some(ComponentSnapshot::Camera(
829 world
830 .get::<nightshade::ecs::camera::components::Camera>(entity)
831 .copied(),
832 )),
833 SnapshotKind::AnimationPlayer => Some(ComponentSnapshot::AnimationPlayer(
834 world
835 .get::<nightshade::ecs::animation::components::AnimationPlayer>(entity)
836 .cloned()
837 .map(Box::new),
838 )),
839 SnapshotKind::Text => {
840 let component = world
841 .get::<nightshade::ecs::text::components::Text>(entity)
842 .cloned()
843 .map(Box::new);
844 let content = component
845 .as_ref()
846 .and_then(|text| world.resources.text.cache.get_text(text.text_index))
847 .map(str::to_string);
848 Some(ComponentSnapshot::Text { component, content })
849 }
850 SnapshotKind::RenderLayer => Some(ComponentSnapshot::RenderLayer(
851 world
852 .get::<nightshade::render::render_layer::RenderLayer>(entity)
853 .copied(),
854 )),
855 SnapshotKind::CullingMask => Some(ComponentSnapshot::CullingMask(
856 world
857 .get::<nightshade::ecs::primitives::CullingMask>(entity)
858 .copied(),
859 )),
860 SnapshotKind::CameraCullingMask => Some(ComponentSnapshot::CameraCullingMask(
861 world
862 .get::<nightshade::ecs::primitives::CameraCullingMask>(entity)
863 .copied(),
864 )),
865 SnapshotKind::IgnoreParentScale => Some(ComponentSnapshot::IgnoreParentScale(
866 world.has::<nightshade::ecs::transform::components::IgnoreParentScale>(entity),
867 )),
868 SnapshotKind::PrefabSource => Some(ComponentSnapshot::PrefabSource(
869 world
870 .get::<nightshade::ecs::prefab::components::PrefabSource>(entity)
871 .cloned()
872 .map(Box::new),
873 )),
874 SnapshotKind::Beam => Some(ComponentSnapshot::Beam(
875 world
876 .get::<nightshade::ecs::vfx::components::Beam>(entity)
877 .cloned()
878 .map(Box::new),
879 )),
880 SnapshotKind::LightningBolt => Some(ComponentSnapshot::LightningBolt(
881 world
882 .get::<nightshade::ecs::vfx::components::LightningBolt>(entity)
883 .cloned()
884 .map(Box::new),
885 )),
886 SnapshotKind::Trail => Some(ComponentSnapshot::Trail(
887 world
888 .get::<nightshade::ecs::vfx::components::Trail>(entity)
889 .cloned()
890 .map(Box::new),
891 )),
892 SnapshotKind::VfxAnimator => Some(ComponentSnapshot::VfxAnimator(
893 world
894 .get::<nightshade::ecs::vfx::components::VfxAnimator>(entity)
895 .cloned()
896 .map(Box::new),
897 )),
898 }
899}
900
901pub fn restore_component(snapshot: &ComponentSnapshot, world: &mut World, entity: Entity) {
904 use nightshade::ecs::world as masks;
905 match snapshot {
906 ComponentSnapshot::Light(light) => {
907 if let Some(target) = world.get_mut::<nightshade::ecs::light::components::Light>(entity)
908 {
909 *target = light.clone();
910 }
911 }
912 ComponentSnapshot::Visibility(visible) => {
913 world.ecs.worlds[CORE].add_components(entity, masks::VISIBILITY);
914 world.set(entity, Visibility { visible: *visible });
915 }
916 ComponentSnapshot::CastsShadow(value) => {
917 if *value {
918 world.ecs.worlds[CORE].add_components(entity, masks::CASTS_SHADOW);
919 world.set(entity, CastsShadow);
920 } else {
921 world.ecs.worlds[CORE].remove_components(entity, masks::CASTS_SHADOW);
922 }
923 }
924 ComponentSnapshot::Name(text) => {
925 world.set(entity, Name(text.clone()));
926 }
927 ComponentSnapshot::ParticleEmitter(value) => {
928 restore_optional(
929 world,
930 entity,
931 value.as_deref().cloned(),
932 masks::PARTICLE_EMITTER,
933 |world, entity, value| world.set(entity, value),
934 );
935 }
936 ComponentSnapshot::Decal(value) => {
937 restore_optional(
938 world,
939 entity,
940 value.as_deref().cloned(),
941 masks::DECAL,
942 |world, entity, value| world.set(entity, value),
943 );
944 }
945 ComponentSnapshot::Water(value) => {
946 restore_optional(
947 world,
948 entity,
949 value.as_deref().cloned(),
950 masks::WATER,
951 |world, entity, value| world.set(entity, value),
952 );
953 }
954 ComponentSnapshot::AudioSource(value) => {
955 restore_optional(
956 world,
957 entity,
958 value.as_deref().cloned(),
959 masks::AUDIO_SOURCE,
960 |world, entity, value| world.set(entity, value),
961 );
962 }
963 ComponentSnapshot::RigidBody(value) => {
964 restore_optional(
965 world,
966 entity,
967 value.as_deref().cloned(),
968 masks::RIGID_BODY,
969 |world, entity, value| world.set(entity, value),
970 );
971 }
972 ComponentSnapshot::Collider(value) => {
973 restore_optional(
974 world,
975 entity,
976 value.as_deref().cloned(),
977 masks::COLLIDER,
978 |world, entity, value| world.set(entity, value),
979 );
980 }
981 ComponentSnapshot::CharacterController(value) => {
982 restore_optional(
983 world,
984 entity,
985 value.as_deref().cloned(),
986 masks::CHARACTER_CONTROLLER,
987 |world, entity, value| world.set(entity, value),
988 );
989 }
990 ComponentSnapshot::NavmeshAgent(value) => {
991 restore_optional(
992 world,
993 entity,
994 value.as_deref().cloned(),
995 masks::NAVMESH_AGENT,
996 |world, entity, value| world.set(entity, value),
997 );
998 }
999 ComponentSnapshot::Camera(value) => {
1000 restore_optional(
1001 world,
1002 entity,
1003 *value,
1004 masks::CAMERA,
1005 |world, entity, value| world.set(entity, value),
1006 );
1007 }
1008 ComponentSnapshot::AnimationPlayer(value) => {
1009 restore_optional(
1010 world,
1011 entity,
1012 value.as_deref().cloned(),
1013 masks::ANIMATION_PLAYER,
1014 |world, entity, value| world.set(entity, value),
1015 );
1016 }
1017 ComponentSnapshot::Material { name, material } => {
1018 if let Some(material) = material.as_deref() {
1019 queue_ecs_command(
1020 world,
1021 nightshade::ecs::world::commands::EcsCommand::ReloadMaterial {
1022 name: name.clone(),
1023 material: Box::new(material.clone()),
1024 },
1025 );
1026 }
1027 }
1028 ComponentSnapshot::Text { component, content } => {
1029 restore_optional(
1030 world,
1031 entity,
1032 component.as_deref().cloned(),
1033 masks::TEXT,
1034 |world, entity, value| world.set(entity, value),
1035 );
1036 if let (Some(component), Some(content)) = (component.as_deref(), content.as_deref()) {
1037 world
1038 .resources
1039 .text
1040 .cache
1041 .set_text(component.text_index, content);
1042 }
1043 }
1044 ComponentSnapshot::RenderLayer(value) => {
1045 restore_optional(
1046 world,
1047 entity,
1048 *value,
1049 masks::RENDER_LAYER,
1050 |world, entity, value| world.set(entity, value),
1051 );
1052 }
1053 ComponentSnapshot::CullingMask(value) => {
1054 restore_optional(
1055 world,
1056 entity,
1057 *value,
1058 masks::CULLING_MASK,
1059 |world, entity, value| world.set(entity, value),
1060 );
1061 }
1062 ComponentSnapshot::CameraCullingMask(value) => {
1063 restore_optional(
1064 world,
1065 entity,
1066 *value,
1067 masks::CAMERA_CULLING_MASK,
1068 |world, entity, value| world.set(entity, value),
1069 );
1070 }
1071 ComponentSnapshot::IgnoreParentScale(present) => {
1072 if *present {
1073 world.ecs.worlds[CORE].add_components(entity, masks::IGNORE_PARENT_SCALE);
1074 world.set(entity, IgnoreParentScale);
1075 } else {
1076 world.ecs.worlds[CORE].remove_components(entity, masks::IGNORE_PARENT_SCALE);
1077 }
1078 }
1079 ComponentSnapshot::PrefabSource(value) => {
1080 restore_optional(
1081 world,
1082 entity,
1083 value.as_deref().cloned(),
1084 masks::PREFAB_SOURCE,
1085 |world, entity, value| world.set(entity, value),
1086 );
1087 }
1088 ComponentSnapshot::Beam(value) => {
1089 restore_optional(
1090 world,
1091 entity,
1092 value.as_deref().cloned(),
1093 masks::BEAM,
1094 |world, entity, value| world.set(entity, value),
1095 );
1096 }
1097 ComponentSnapshot::LightningBolt(value) => {
1098 restore_optional(
1099 world,
1100 entity,
1101 value.as_deref().cloned(),
1102 masks::LIGHTNING_BOLT,
1103 |world, entity, value| world.set(entity, value),
1104 );
1105 }
1106 ComponentSnapshot::Trail(value) => {
1107 restore_optional(
1108 world,
1109 entity,
1110 value.as_deref().cloned(),
1111 masks::TRAIL,
1112 |world, entity, value| world.set(entity, value),
1113 );
1114 }
1115 ComponentSnapshot::VfxAnimator(value) => {
1116 restore_optional(
1117 world,
1118 entity,
1119 value.as_deref().cloned(),
1120 masks::VFX_ANIMATOR,
1121 |world, entity, value| world.set(entity, value),
1122 );
1123 }
1124 }
1125}
1126
1127fn restore_optional<T, F>(world: &mut World, entity: Entity, value: Option<T>, mask: u64, set: F)
1130where
1131 F: FnOnce(&mut World, Entity, T),
1132{
1133 if let Some(value) = value {
1134 world.ecs.worlds[CORE].add_components(entity, mask);
1135 set(world, entity, value);
1136 } else {
1137 world.ecs.worlds[CORE].remove_components(entity, mask);
1138 }
1139}