use nightshade::ecs::animation::components::AnimationPlayer;
use nightshade::ecs::audio::components::AudioSource;
use nightshade::ecs::camera::components::Camera;
use nightshade::ecs::decal::components::Decal;
use nightshade::ecs::material::components::Material;
use nightshade::ecs::morph::components::MorphWeights;
use nightshade::ecs::particles::components::ParticleEmitter;
use nightshade::ecs::prefab::components::PrefabSource;
use nightshade::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
use nightshade::ecs::script::Script;
use nightshade::ecs::text::components::{Text, TextProperties};
use nightshade::ecs::transform::components::IgnoreParentScale;
use nightshade::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator};
use nightshade::ecs::water::components::Water;
use nightshade::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, enum2schema::Schema)]
#[schema(string_enum)]
pub enum ComponentKind {
ParticleEmitter,
Decal,
Water,
AudioSource,
RigidBody,
Collider,
CharacterController,
NavmeshAgent,
Camera,
Text,
AnimationPlayer,
Visibility,
CastsShadow,
Light,
RenderLayer,
CullingMask,
CameraCullingMask,
IgnoreParentScale,
AudioListener,
CollisionListener,
PhysicsInterpolation,
MorphWeights,
MaterialVariants,
PanOrbitCamera,
ThirdPersonCamera,
CameraEnvironment,
CameraPostProcess,
ConstrainedAspect,
ViewportUpdateMode,
Script,
Beam,
LightningBolt,
Trail,
VfxAnimator,
}
pub const ALL_COMPONENT_KINDS: [ComponentKind; 34] = [
ComponentKind::ParticleEmitter,
ComponentKind::Decal,
ComponentKind::Water,
ComponentKind::AudioSource,
ComponentKind::RigidBody,
ComponentKind::Collider,
ComponentKind::CharacterController,
ComponentKind::NavmeshAgent,
ComponentKind::Camera,
ComponentKind::Text,
ComponentKind::AnimationPlayer,
ComponentKind::Visibility,
ComponentKind::CastsShadow,
ComponentKind::Light,
ComponentKind::RenderLayer,
ComponentKind::CullingMask,
ComponentKind::CameraCullingMask,
ComponentKind::IgnoreParentScale,
ComponentKind::AudioListener,
ComponentKind::CollisionListener,
ComponentKind::PhysicsInterpolation,
ComponentKind::MorphWeights,
ComponentKind::MaterialVariants,
ComponentKind::PanOrbitCamera,
ComponentKind::ThirdPersonCamera,
ComponentKind::CameraEnvironment,
ComponentKind::CameraPostProcess,
ComponentKind::ConstrainedAspect,
ComponentKind::ViewportUpdateMode,
ComponentKind::Script,
ComponentKind::Beam,
ComponentKind::LightningBolt,
ComponentKind::Trail,
ComponentKind::VfxAnimator,
];
pub fn kind_mask(kind: ComponentKind) -> u64 {
use nightshade::ecs::world as masks;
match kind {
ComponentKind::ParticleEmitter => masks::PARTICLE_EMITTER,
ComponentKind::Decal => masks::DECAL,
ComponentKind::Water => masks::WATER,
ComponentKind::AudioSource => masks::AUDIO_SOURCE,
ComponentKind::RigidBody => masks::RIGID_BODY,
ComponentKind::Collider => masks::COLLIDER,
ComponentKind::CharacterController => masks::CHARACTER_CONTROLLER,
ComponentKind::NavmeshAgent => masks::NAVMESH_AGENT,
ComponentKind::Camera => masks::CAMERA,
ComponentKind::Text => masks::TEXT,
ComponentKind::AnimationPlayer => masks::ANIMATION_PLAYER,
ComponentKind::Visibility => masks::VISIBILITY,
ComponentKind::CastsShadow => masks::CASTS_SHADOW,
ComponentKind::Light => masks::LIGHT,
ComponentKind::RenderLayer => masks::RENDER_LAYER,
ComponentKind::CullingMask => masks::CULLING_MASK,
ComponentKind::CameraCullingMask => masks::CAMERA_CULLING_MASK,
ComponentKind::IgnoreParentScale => masks::IGNORE_PARENT_SCALE,
ComponentKind::AudioListener => masks::AUDIO_LISTENER,
ComponentKind::CollisionListener => masks::COLLISION_LISTENER,
ComponentKind::PhysicsInterpolation => masks::PHYSICS_INTERPOLATION,
ComponentKind::MorphWeights => masks::MORPH_WEIGHTS,
ComponentKind::MaterialVariants => masks::MATERIAL_VARIANTS,
ComponentKind::PanOrbitCamera => masks::PAN_ORBIT_CAMERA,
ComponentKind::ThirdPersonCamera => masks::THIRD_PERSON_CAMERA,
ComponentKind::CameraEnvironment => masks::CAMERA_ENVIRONMENT,
ComponentKind::CameraPostProcess => masks::CAMERA_POST_PROCESS,
ComponentKind::ConstrainedAspect => masks::CONSTRAINED_ASPECT,
ComponentKind::ViewportUpdateMode => masks::VIEWPORT_UPDATE_MODE,
ComponentKind::Script => masks::SCRIPT,
ComponentKind::Beam => masks::BEAM,
ComponentKind::LightningBolt => masks::LIGHTNING_BOLT,
ComponentKind::Trail => masks::TRAIL,
ComponentKind::VfxAnimator => masks::VFX_ANIMATOR,
}
}
pub fn has_component(world: &World, entity: Entity, kind: ComponentKind) -> bool {
world.core.entity_has_components(entity, kind_mask(kind))
}
pub fn remove_component(world: &mut World, entity: Entity, kind: ComponentKind) {
world.core.remove_components(entity, kind_mask(kind));
}
pub fn present_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
ALL_COMPONENT_KINDS
.into_iter()
.filter(|kind| has_component(world, entity, *kind))
.collect()
}
pub fn addable_components(world: &World, entity: Entity) -> Vec<ComponentKind> {
ALL_COMPONENT_KINDS
.into_iter()
.filter(|kind| !has_component(world, entity, *kind))
.collect()
}
pub fn add_component_default(world: &mut World, entity: Entity, kind: ComponentKind) {
use nightshade::ecs::world as masks;
match kind {
ComponentKind::ParticleEmitter => {
world.core.add_components(entity, masks::PARTICLE_EMITTER);
world
.core
.set_particle_emitter(entity, ParticleEmitter::default());
}
ComponentKind::Decal => {
world.core.add_components(entity, masks::DECAL);
world.core.set_decal(entity, Decal::default());
}
ComponentKind::Water => {
world.core.add_components(entity, masks::WATER);
world.core.set_water(entity, Water::default());
}
ComponentKind::AudioSource => {
world.core.add_components(entity, masks::AUDIO_SOURCE);
world.core.set_audio_source(entity, AudioSource::default());
}
ComponentKind::RigidBody => {
world.core.add_components(entity, masks::RIGID_BODY);
world
.core
.set_rigid_body(entity, RigidBodyComponent::new_dynamic());
}
ComponentKind::Collider => {
world.core.add_components(entity, masks::COLLIDER);
world
.core
.set_collider(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5));
}
ComponentKind::CharacterController => {
world
.core
.add_components(entity, masks::CHARACTER_CONTROLLER);
world.core.set_character_controller(
entity,
CharacterControllerComponent::new_capsule(0.9, 0.3),
);
}
ComponentKind::NavmeshAgent => {
world.core.add_components(entity, masks::NAVMESH_AGENT);
world.core.set_navmesh_agent(entity, NavMeshAgent::new());
}
ComponentKind::Camera => {
world.core.add_components(entity, masks::CAMERA);
world.core.set_camera(entity, Camera::default());
}
ComponentKind::Text => {
let text_index = world.resources.text.cache.add_text("Text");
world.core.add_components(entity, masks::TEXT);
world.core.set_text(entity, Text::new(text_index));
}
ComponentKind::AnimationPlayer => {
world.core.add_components(entity, masks::ANIMATION_PLAYER);
world
.core
.set_animation_player(entity, AnimationPlayer::default());
}
ComponentKind::Visibility => {
world.core.add_components(entity, masks::VISIBILITY);
world
.core
.set_visibility(entity, Visibility { visible: true });
}
ComponentKind::CastsShadow => {
world.core.add_components(entity, masks::CASTS_SHADOW);
world.core.set_casts_shadow(entity, CastsShadow);
}
ComponentKind::Light => {
world.core.add_components(entity, masks::LIGHT);
world.core.set_light(entity, Light::default());
}
ComponentKind::RenderLayer => {
world.core.add_components(entity, masks::RENDER_LAYER);
world.core.set_render_layer(entity, RenderLayer::default());
}
ComponentKind::CullingMask => {
world.core.add_components(entity, masks::CULLING_MASK);
world.core.set_culling_mask(entity, CullingMask::default());
}
ComponentKind::CameraCullingMask => {
world
.core
.add_components(entity, masks::CAMERA_CULLING_MASK);
world
.core
.set_camera_culling_mask(entity, CameraCullingMask::default());
}
ComponentKind::IgnoreParentScale => {
world
.core
.add_components(entity, masks::IGNORE_PARENT_SCALE);
world
.core
.set_ignore_parent_scale(entity, IgnoreParentScale);
}
ComponentKind::AudioListener => {
world.core.add_components(entity, masks::AUDIO_LISTENER);
world
.core
.set_audio_listener(entity, nightshade::ecs::audio::components::AudioListener);
}
ComponentKind::CollisionListener => {
world.core.add_components(entity, masks::COLLISION_LISTENER);
world.core.set_collision_listener(
entity,
nightshade::ecs::physics::components::CollisionListener,
);
}
ComponentKind::PhysicsInterpolation => {
world
.core
.add_components(entity, masks::PHYSICS_INTERPOLATION);
world.core.set_physics_interpolation(
entity,
nightshade::ecs::physics::components::PhysicsInterpolation::default(),
);
}
ComponentKind::MorphWeights => {
world.core.add_components(entity, masks::MORPH_WEIGHTS);
world
.core
.set_morph_weights(entity, MorphWeights::default());
}
ComponentKind::MaterialVariants => {
world.core.add_components(entity, masks::MATERIAL_VARIANTS);
world.core.set_material_variants(
entity,
nightshade::ecs::material::components::MaterialVariants::default(),
);
}
ComponentKind::PanOrbitCamera => {
world.core.add_components(entity, masks::PAN_ORBIT_CAMERA);
world.core.set_pan_orbit_camera(
entity,
nightshade::ecs::camera::components::PanOrbitCamera::default(),
);
}
ComponentKind::ThirdPersonCamera => {
world
.core
.add_components(entity, masks::THIRD_PERSON_CAMERA);
world.core.set_third_person_camera(
entity,
nightshade::ecs::camera::components::ThirdPersonCamera::default(),
);
}
ComponentKind::CameraEnvironment => {
world.core.add_components(entity, masks::CAMERA_ENVIRONMENT);
world.core.set_camera_environment(
entity,
nightshade::ecs::camera::components::CameraEnvironment::default(),
);
}
ComponentKind::CameraPostProcess => {
world
.core
.add_components(entity, masks::CAMERA_POST_PROCESS);
world.core.set_camera_post_process(
entity,
nightshade::ecs::camera::components::CameraPostProcess::default(),
);
}
ComponentKind::ConstrainedAspect => {
world.core.add_components(entity, masks::CONSTRAINED_ASPECT);
world.core.set_constrained_aspect(
entity,
nightshade::ecs::camera::components::ConstrainedAspect::default(),
);
}
ComponentKind::ViewportUpdateMode => {
world
.core
.add_components(entity, masks::VIEWPORT_UPDATE_MODE);
world.core.set_viewport_update_mode(
entity,
nightshade::ecs::camera::components::ViewportUpdateMode::default(),
);
}
ComponentKind::Script => {
world.core.add_components(entity, masks::SCRIPT);
world.core.set_script(entity, Script::default());
}
ComponentKind::Beam => {
world.core.add_components(entity, masks::BEAM);
world.core.set_beam(entity, Beam::default());
}
ComponentKind::LightningBolt => {
world.core.add_components(entity, masks::LIGHTNING_BOLT);
world
.core
.set_lightning_bolt(entity, LightningBolt::default());
}
ComponentKind::Trail => {
world.core.add_components(entity, masks::TRAIL);
world.core.set_trail(entity, Trail::default());
}
ComponentKind::VfxAnimator => {
world.core.add_components(entity, masks::VFX_ANIMATOR);
world.core.set_vfx_animator(entity, VfxAnimator::default());
}
}
}
#[derive(Clone, Serialize, Deserialize, enum2schema::Schema)]
pub enum ComponentPatch {
Light(Light),
Visibility(bool),
CastsShadow(bool),
Camera(Camera),
RigidBody(RigidBodyComponent),
Collider(ColliderComponent),
CharacterController(CharacterControllerComponent),
NavmeshAgent(NavMeshAgent),
ParticleEmitter(Box<ParticleEmitter>),
Decal(Decal),
Water(Water),
AudioSource(AudioSource),
RenderLayer(RenderLayer),
CullingMask(CullingMask),
CameraCullingMask(CameraCullingMask),
IgnoreParentScale(bool),
MorphWeights(Vec<f32>),
Text {
content: String,
properties: TextProperties,
},
Script(String),
Beam(Box<Beam>),
LightningBolt(Box<LightningBolt>),
Trail(Box<Trail>),
VfxAnimator(Box<VfxAnimator>),
}
impl ComponentPatch {
pub fn kind(&self) -> ComponentKind {
match self {
Self::Light(_) => ComponentKind::Light,
Self::Visibility(_) => ComponentKind::Visibility,
Self::CastsShadow(_) => ComponentKind::CastsShadow,
Self::Camera(_) => ComponentKind::Camera,
Self::RigidBody(_) => ComponentKind::RigidBody,
Self::Collider(_) => ComponentKind::Collider,
Self::CharacterController(_) => ComponentKind::CharacterController,
Self::NavmeshAgent(_) => ComponentKind::NavmeshAgent,
Self::ParticleEmitter(_) => ComponentKind::ParticleEmitter,
Self::Decal(_) => ComponentKind::Decal,
Self::Water(_) => ComponentKind::Water,
Self::AudioSource(_) => ComponentKind::AudioSource,
Self::RenderLayer(_) => ComponentKind::RenderLayer,
Self::CullingMask(_) => ComponentKind::CullingMask,
Self::CameraCullingMask(_) => ComponentKind::CameraCullingMask,
Self::IgnoreParentScale(_) => ComponentKind::IgnoreParentScale,
Self::MorphWeights(_) => ComponentKind::MorphWeights,
Self::Text { .. } => ComponentKind::Text,
Self::Script(_) => ComponentKind::Script,
Self::Beam(_) => ComponentKind::Beam,
Self::LightningBolt(_) => ComponentKind::LightningBolt,
Self::Trail(_) => ComponentKind::Trail,
Self::VfxAnimator(_) => ComponentKind::VfxAnimator,
}
}
}
pub fn apply_patch(world: &mut World, entity: Entity, patch: ComponentPatch) {
use nightshade::ecs::world as masks;
match patch {
ComponentPatch::Light(light) => {
if let Some(slot) = world.core.get_light_mut(entity) {
*slot = light;
}
}
ComponentPatch::Visibility(visible) => {
world.core.add_components(entity, masks::VISIBILITY);
world.core.set_visibility(entity, Visibility { visible });
}
ComponentPatch::CastsShadow(value) => {
if value {
world.core.add_components(entity, masks::CASTS_SHADOW);
world.core.set_casts_shadow(entity, CastsShadow);
} else {
world.core.remove_components(entity, masks::CASTS_SHADOW);
}
}
ComponentPatch::Camera(camera) => {
if let Some(slot) = world.core.get_camera_mut(entity) {
*slot = camera;
}
}
ComponentPatch::RigidBody(mut body) => {
if let Some(slot) = world.core.get_rigid_body_mut(entity) {
body.handle = slot.handle;
*slot = body;
}
}
ComponentPatch::Collider(mut collider) => {
if let Some(slot) = world.core.get_collider_mut(entity) {
collider.handle = slot.handle;
*slot = collider;
}
}
ComponentPatch::CharacterController(controller) => {
if let Some(slot) = world.core.get_character_controller_mut(entity) {
*slot = controller;
}
}
ComponentPatch::NavmeshAgent(agent) => {
if let Some(slot) = world.core.get_navmesh_agent_mut(entity) {
*slot = agent;
}
}
ComponentPatch::ParticleEmitter(emitter) => {
if let Some(slot) = world.core.get_particle_emitter_mut(entity) {
*slot = *emitter;
}
}
ComponentPatch::Decal(decal) => {
if let Some(slot) = world.core.get_decal_mut(entity) {
*slot = decal;
}
}
ComponentPatch::Water(water) => {
if let Some(slot) = world.core.get_water_mut(entity) {
*slot = water;
}
}
ComponentPatch::AudioSource(source) => {
if let Some(slot) = world.core.get_audio_source_mut(entity) {
*slot = source;
}
}
ComponentPatch::RenderLayer(layer) => {
if let Some(slot) = world.core.get_render_layer_mut(entity) {
*slot = layer;
}
}
ComponentPatch::CullingMask(mask) => {
if let Some(slot) = world.core.get_culling_mask_mut(entity) {
*slot = mask;
}
}
ComponentPatch::CameraCullingMask(mask) => {
if let Some(slot) = world.core.get_camera_culling_mask_mut(entity) {
*slot = mask;
}
}
ComponentPatch::IgnoreParentScale(present) => {
if present {
world
.core
.add_components(entity, masks::IGNORE_PARENT_SCALE);
world
.core
.set_ignore_parent_scale(entity, IgnoreParentScale);
} else {
world
.core
.remove_components(entity, masks::IGNORE_PARENT_SCALE);
}
}
ComponentPatch::MorphWeights(weights) => {
if let Some(slot) = world.core.get_morph_weights_mut(entity) {
for (index, weight) in weights.into_iter().enumerate() {
slot.set_weight(index, weight);
}
}
}
ComponentPatch::Text {
content,
properties,
} => {
let text_index = world.core.get_text(entity).map(|text| text.text_index);
if let Some(text_index) = text_index {
world.resources.text.cache.set_text(text_index, &content);
if let Some(text) = world.core.get_text_mut(entity) {
text.properties = properties;
text.dirty = true;
}
}
}
ComponentPatch::Script(source) => {
world.core.add_components(entity, masks::SCRIPT);
world.core.set_script(entity, Script::from_source(source));
}
ComponentPatch::Beam(beam) => {
if let Some(slot) = world.core.get_beam_mut(entity) {
*slot = *beam;
}
}
ComponentPatch::LightningBolt(bolt) => {
if let Some(slot) = world.core.get_lightning_bolt_mut(entity) {
*slot = *bolt;
}
}
ComponentPatch::Trail(trail) => {
if let Some(slot) = world.core.get_trail_mut(entity) {
*slot = *trail;
}
}
ComponentPatch::VfxAnimator(animator) => {
if let Some(slot) = world.core.get_vfx_animator_mut(entity) {
*slot = *animator;
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum SnapshotKind {
Light,
Visibility,
CastsShadow,
Name,
ParticleEmitter,
Decal,
Water,
AudioSource,
RigidBody,
Collider,
CharacterController,
NavmeshAgent,
Camera,
AnimationPlayer,
Text,
RenderLayer,
CullingMask,
CameraCullingMask,
IgnoreParentScale,
PrefabSource,
Beam,
LightningBolt,
Trail,
VfxAnimator,
}
pub fn snapshot_kind_for(kind: ComponentKind) -> Option<SnapshotKind> {
Some(match kind {
ComponentKind::ParticleEmitter => SnapshotKind::ParticleEmitter,
ComponentKind::Decal => SnapshotKind::Decal,
ComponentKind::Water => SnapshotKind::Water,
ComponentKind::AudioSource => SnapshotKind::AudioSource,
ComponentKind::RigidBody => SnapshotKind::RigidBody,
ComponentKind::Collider => SnapshotKind::Collider,
ComponentKind::CharacterController => SnapshotKind::CharacterController,
ComponentKind::NavmeshAgent => SnapshotKind::NavmeshAgent,
ComponentKind::Camera => SnapshotKind::Camera,
ComponentKind::Text => SnapshotKind::Text,
ComponentKind::AnimationPlayer => SnapshotKind::AnimationPlayer,
ComponentKind::Visibility => SnapshotKind::Visibility,
ComponentKind::CastsShadow => SnapshotKind::CastsShadow,
ComponentKind::Light => SnapshotKind::Light,
ComponentKind::RenderLayer => SnapshotKind::RenderLayer,
ComponentKind::CullingMask => SnapshotKind::CullingMask,
ComponentKind::CameraCullingMask => SnapshotKind::CameraCullingMask,
ComponentKind::IgnoreParentScale => SnapshotKind::IgnoreParentScale,
ComponentKind::Beam => SnapshotKind::Beam,
ComponentKind::LightningBolt => SnapshotKind::LightningBolt,
ComponentKind::Trail => SnapshotKind::Trail,
ComponentKind::VfxAnimator => SnapshotKind::VfxAnimator,
_ => return None,
})
}
#[derive(Clone)]
pub enum ComponentSnapshot {
Light(Light),
Visibility(bool),
CastsShadow(bool),
Name(String),
ParticleEmitter(Option<Box<ParticleEmitter>>),
Decal(Option<Box<Decal>>),
Water(Option<Box<Water>>),
AudioSource(Option<Box<AudioSource>>),
RigidBody(Option<Box<RigidBodyComponent>>),
Collider(Option<Box<ColliderComponent>>),
CharacterController(Option<Box<CharacterControllerComponent>>),
NavmeshAgent(Option<Box<NavMeshAgent>>),
Camera(Option<Camera>),
AnimationPlayer(Option<Box<AnimationPlayer>>),
Material {
name: String,
material: Option<Box<Material>>,
},
Text {
component: Option<Box<Text>>,
content: Option<String>,
},
RenderLayer(Option<RenderLayer>),
CullingMask(Option<CullingMask>),
CameraCullingMask(Option<CameraCullingMask>),
IgnoreParentScale(bool),
PrefabSource(Option<Box<PrefabSource>>),
Beam(Option<Box<Beam>>),
LightningBolt(Option<Box<LightningBolt>>),
Trail(Option<Box<Trail>>),
VfxAnimator(Option<Box<VfxAnimator>>),
}
impl PartialEq for ComponentSnapshot {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Light(a), Self::Light(b)) => {
a.light_type == b.light_type
&& a.color == b.color
&& a.intensity == b.intensity
&& a.range == b.range
&& a.inner_cone_angle == b.inner_cone_angle
&& a.outer_cone_angle == b.outer_cone_angle
&& a.cast_shadows == b.cast_shadows
&& a.shadow_bias == b.shadow_bias
}
(Self::Visibility(a), Self::Visibility(b)) => a == b,
(Self::CastsShadow(a), Self::CastsShadow(b)) => a == b,
(Self::Name(a), Self::Name(b)) => a == b,
(Self::ParticleEmitter(a), Self::ParticleEmitter(b)) => {
snapshot_bytes(a) == snapshot_bytes(b)
}
(Self::Decal(a), Self::Decal(b)) => a == b,
(Self::Water(a), Self::Water(b)) => a == b,
(Self::AudioSource(a), Self::AudioSource(b)) => snapshot_bytes(a) == snapshot_bytes(b),
(Self::RigidBody(a), Self::RigidBody(b)) => snapshot_bytes(a) == snapshot_bytes(b),
(Self::Collider(a), Self::Collider(b)) => snapshot_bytes(a) == snapshot_bytes(b),
(Self::CharacterController(a), Self::CharacterController(b)) => {
snapshot_bytes(a) == snapshot_bytes(b)
}
(Self::NavmeshAgent(a), Self::NavmeshAgent(b)) => {
snapshot_bytes(a) == snapshot_bytes(b)
}
(Self::Camera(a), Self::Camera(b)) => a == b,
(Self::AnimationPlayer(a), Self::AnimationPlayer(b)) => a == b,
(
Self::Material {
name: a_name,
material: a_mat,
},
Self::Material {
name: b_name,
material: b_mat,
},
) => a_name == b_name && a_mat == b_mat,
(
Self::Text {
component: a_component,
content: a_content,
},
Self::Text {
component: b_component,
content: b_content,
},
) => {
snapshot_bytes(a_component) == snapshot_bytes(b_component) && a_content == b_content
}
(Self::RenderLayer(a), Self::RenderLayer(b)) => a == b,
(Self::CullingMask(a), Self::CullingMask(b)) => a == b,
(Self::CameraCullingMask(a), Self::CameraCullingMask(b)) => a == b,
(Self::IgnoreParentScale(a), Self::IgnoreParentScale(b)) => a == b,
(Self::PrefabSource(a), Self::PrefabSource(b)) => {
snapshot_bytes(a) == snapshot_bytes(b)
}
(Self::Beam(a), Self::Beam(b)) => snapshot_bytes(a) == snapshot_bytes(b),
(Self::LightningBolt(a), Self::LightningBolt(b)) => {
snapshot_bytes(a) == snapshot_bytes(b)
}
(Self::Trail(a), Self::Trail(b)) => snapshot_bytes(a) == snapshot_bytes(b),
(Self::VfxAnimator(a), Self::VfxAnimator(b)) => snapshot_bytes(a) == snapshot_bytes(b),
_ => false,
}
}
}
fn snapshot_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
bincode::serialize(value).unwrap_or_default()
}
pub fn snapshot_component(
world: &World,
entity: Entity,
kind: SnapshotKind,
) -> Option<ComponentSnapshot> {
match kind {
SnapshotKind::Light => world
.core
.get_light(entity)
.cloned()
.map(ComponentSnapshot::Light),
SnapshotKind::Visibility => world
.core
.get_visibility(entity)
.map(|visibility| ComponentSnapshot::Visibility(visibility.visible)),
SnapshotKind::CastsShadow => Some(ComponentSnapshot::CastsShadow(
world.core.entity_has_casts_shadow(entity),
)),
SnapshotKind::Name => Some(ComponentSnapshot::Name(
world
.core
.get_name(entity)
.map(|name| name.0.clone())
.unwrap_or_default(),
)),
SnapshotKind::ParticleEmitter => Some(ComponentSnapshot::ParticleEmitter(
world
.core
.get_particle_emitter(entity)
.cloned()
.map(Box::new),
)),
SnapshotKind::Decal => Some(ComponentSnapshot::Decal(
world.core.get_decal(entity).cloned().map(Box::new),
)),
SnapshotKind::Water => Some(ComponentSnapshot::Water(
world.core.get_water(entity).cloned().map(Box::new),
)),
SnapshotKind::AudioSource => Some(ComponentSnapshot::AudioSource(
world.core.get_audio_source(entity).cloned().map(Box::new),
)),
SnapshotKind::RigidBody => Some(ComponentSnapshot::RigidBody(
world.core.get_rigid_body(entity).cloned().map(Box::new),
)),
SnapshotKind::Collider => Some(ComponentSnapshot::Collider(
world.core.get_collider(entity).cloned().map(Box::new),
)),
SnapshotKind::CharacterController => Some(ComponentSnapshot::CharacterController(
world
.core
.get_character_controller(entity)
.cloned()
.map(Box::new),
)),
SnapshotKind::NavmeshAgent => Some(ComponentSnapshot::NavmeshAgent(
world.core.get_navmesh_agent(entity).cloned().map(Box::new),
)),
SnapshotKind::Camera => Some(ComponentSnapshot::Camera(
world.core.get_camera(entity).copied(),
)),
SnapshotKind::AnimationPlayer => Some(ComponentSnapshot::AnimationPlayer(
world
.core
.get_animation_player(entity)
.cloned()
.map(Box::new),
)),
SnapshotKind::Text => {
let component = world.core.get_text(entity).cloned().map(Box::new);
let content = component
.as_ref()
.and_then(|text| world.resources.text.cache.get_text(text.text_index))
.map(str::to_string);
Some(ComponentSnapshot::Text { component, content })
}
SnapshotKind::RenderLayer => Some(ComponentSnapshot::RenderLayer(
world.core.get_render_layer(entity).copied(),
)),
SnapshotKind::CullingMask => Some(ComponentSnapshot::CullingMask(
world.core.get_culling_mask(entity).copied(),
)),
SnapshotKind::CameraCullingMask => Some(ComponentSnapshot::CameraCullingMask(
world.core.get_camera_culling_mask(entity).copied(),
)),
SnapshotKind::IgnoreParentScale => Some(ComponentSnapshot::IgnoreParentScale(
world.core.entity_has_ignore_parent_scale(entity),
)),
SnapshotKind::PrefabSource => Some(ComponentSnapshot::PrefabSource(
world.core.get_prefab_source(entity).cloned().map(Box::new),
)),
SnapshotKind::Beam => Some(ComponentSnapshot::Beam(
world.core.get_beam(entity).cloned().map(Box::new),
)),
SnapshotKind::LightningBolt => Some(ComponentSnapshot::LightningBolt(
world.core.get_lightning_bolt(entity).cloned().map(Box::new),
)),
SnapshotKind::Trail => Some(ComponentSnapshot::Trail(
world.core.get_trail(entity).cloned().map(Box::new),
)),
SnapshotKind::VfxAnimator => Some(ComponentSnapshot::VfxAnimator(
world.core.get_vfx_animator(entity).cloned().map(Box::new),
)),
}
}
pub fn restore_component(snapshot: &ComponentSnapshot, world: &mut World, entity: Entity) {
use nightshade::ecs::world as masks;
match snapshot {
ComponentSnapshot::Light(light) => {
if let Some(target) = world.core.get_light_mut(entity) {
*target = light.clone();
}
}
ComponentSnapshot::Visibility(visible) => {
world.core.add_components(entity, masks::VISIBILITY);
world
.core
.set_visibility(entity, Visibility { visible: *visible });
}
ComponentSnapshot::CastsShadow(value) => {
if *value {
world.core.add_components(entity, masks::CASTS_SHADOW);
world.core.set_casts_shadow(entity, CastsShadow);
} else {
world.core.remove_components(entity, masks::CASTS_SHADOW);
}
}
ComponentSnapshot::Name(text) => {
world.core.set_name(entity, Name(text.clone()));
}
ComponentSnapshot::ParticleEmitter(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::PARTICLE_EMITTER,
|world, entity, value| world.core.set_particle_emitter(entity, value),
);
}
ComponentSnapshot::Decal(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::DECAL,
|world, entity, value| world.core.set_decal(entity, value),
);
}
ComponentSnapshot::Water(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::WATER,
|world, entity, value| world.core.set_water(entity, value),
);
}
ComponentSnapshot::AudioSource(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::AUDIO_SOURCE,
|world, entity, value| world.core.set_audio_source(entity, value),
);
}
ComponentSnapshot::RigidBody(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::RIGID_BODY,
|world, entity, value| world.core.set_rigid_body(entity, value),
);
}
ComponentSnapshot::Collider(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::COLLIDER,
|world, entity, value| world.core.set_collider(entity, value),
);
}
ComponentSnapshot::CharacterController(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::CHARACTER_CONTROLLER,
|world, entity, value| world.core.set_character_controller(entity, value),
);
}
ComponentSnapshot::NavmeshAgent(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::NAVMESH_AGENT,
|world, entity, value| world.core.set_navmesh_agent(entity, value),
);
}
ComponentSnapshot::Camera(value) => {
restore_optional(
world,
entity,
*value,
masks::CAMERA,
|world, entity, value| world.core.set_camera(entity, value),
);
}
ComponentSnapshot::AnimationPlayer(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::ANIMATION_PLAYER,
|world, entity, value| world.core.set_animation_player(entity, value),
);
}
ComponentSnapshot::Material { name, material } => {
if let Some(material) = material.as_deref() {
queue_ecs_command(
world,
nightshade::ecs::world::commands::EcsCommand::ReloadMaterial {
name: name.clone(),
material: Box::new(material.clone()),
},
);
}
}
ComponentSnapshot::Text { component, content } => {
restore_optional(
world,
entity,
component.as_deref().cloned(),
masks::TEXT,
|world, entity, value| world.core.set_text(entity, value),
);
if let (Some(component), Some(content)) = (component.as_deref(), content.as_deref()) {
world
.resources
.text
.cache
.set_text(component.text_index, content);
}
}
ComponentSnapshot::RenderLayer(value) => {
restore_optional(
world,
entity,
*value,
masks::RENDER_LAYER,
|world, entity, value| world.core.set_render_layer(entity, value),
);
}
ComponentSnapshot::CullingMask(value) => {
restore_optional(
world,
entity,
*value,
masks::CULLING_MASK,
|world, entity, value| world.core.set_culling_mask(entity, value),
);
}
ComponentSnapshot::CameraCullingMask(value) => {
restore_optional(
world,
entity,
*value,
masks::CAMERA_CULLING_MASK,
|world, entity, value| world.core.set_camera_culling_mask(entity, value),
);
}
ComponentSnapshot::IgnoreParentScale(present) => {
if *present {
world
.core
.add_components(entity, masks::IGNORE_PARENT_SCALE);
world
.core
.set_ignore_parent_scale(entity, IgnoreParentScale);
} else {
world
.core
.remove_components(entity, masks::IGNORE_PARENT_SCALE);
}
}
ComponentSnapshot::PrefabSource(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::PREFAB_SOURCE,
|world, entity, value| world.core.set_prefab_source(entity, value),
);
}
ComponentSnapshot::Beam(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::BEAM,
|world, entity, value| world.core.set_beam(entity, value),
);
}
ComponentSnapshot::LightningBolt(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::LIGHTNING_BOLT,
|world, entity, value| world.core.set_lightning_bolt(entity, value),
);
}
ComponentSnapshot::Trail(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::TRAIL,
|world, entity, value| world.core.set_trail(entity, value),
);
}
ComponentSnapshot::VfxAnimator(value) => {
restore_optional(
world,
entity,
value.as_deref().cloned(),
masks::VFX_ANIMATOR,
|world, entity, value| world.core.set_vfx_animator(entity, value),
);
}
}
}
fn restore_optional<T, F>(world: &mut World, entity: Entity, value: Option<T>, mask: u64, set: F)
where
F: FnOnce(&mut World, Entity, T),
{
if let Some(value) = value {
world.core.add_components(entity, mask);
set(world, entity, value);
} else {
world.core.remove_components(entity, mask);
}
}