use super::*;
use bevy::{
ecs::{
component::Mutable, query::QueryEntityError, schedule::ScheduleConfigs,
system::ScheduleSystem,
}
};
use bevy::platform::collections::{HashMap, HashSet};
use std::any::type_name;
use tracing::error;
pub fn component_tween_system<I>() -> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Component<Mutability = Mutable>,
{
apply_component_tween_system::<I>.into_configs()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QueryEntityErrorWithoutWorld {
QueryDoesNotMatch(Entity, bevy::ecs::archetype::ArchetypeId),
EntityDoesNotExist(Entity),
AliasedMutability(Entity),
}
impl From<&QueryEntityError> for QueryEntityErrorWithoutWorld {
fn from(x: &QueryEntityError) -> Self {
use QueryEntityError as E;
use QueryEntityErrorWithoutWorld as EH;
match x {
E::QueryDoesNotMatch(entity, archetype_id) => {
EH::QueryDoesNotMatch(*entity, *archetype_id)
}
E::EntityDoesNotExist(entity_does_not_exist_error) => {
EH::EntityDoesNotExist(entity_does_not_exist_error.entity)
}
E::AliasedMutability(entity) => EH::AliasedMutability(*entity),
}
}
}
impl core::error::Error for QueryEntityErrorWithoutWorld {}
impl core::fmt::Display for QueryEntityErrorWithoutWorld {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::QueryDoesNotMatch(entity, _) => {
write!(f, "The query does not match the entity {entity}")
}
Self::EntityDoesNotExist(entity) => {
write!(f, "The entity {entity} does not exist")
}
Self::AliasedMutability(entity) => write!(
f,
"The entity {entity} was requested mutably more than once"
),
}
}
}
#[allow(clippy::type_complexity)]
pub fn apply_component_tween_system<I>(
q_animation_target: Query<(Option<&ChildOf>, Has<AnimationTarget>)>,
mut q_tween: Query<
(Entity, &Tween<TargetComponent, I>, &TweenInterpolationValue, &mut TweenPreviousValue),
Without<SkipTween>,
>,
mut q_component: Query<&mut I::Item>,
mut last_entity_error: Local<HashMap<Entity, QueryEntityErrorWithoutWorld>>,
mut last_search_error: Local<HashSet<Entity>>,
) where
I: Interpolator + Send + Sync + 'static,
I::Item: Component<Mutability = Mutable>,
{
let mut entity_error = HashMap::default();
let mut search_error = HashSet::default();
q_tween
.iter_mut()
.for_each(|(entity, tween, ease_value, mut previous_value)| match &tween.target {
TargetComponent::Entities(e) => {
e.iter().for_each(|target| {
let mut target_component =
match q_component.get_mut(*target) {
Ok(target_component) => target_component,
Err(e) => {
let e_no_world = QueryEntityErrorWithoutWorld::from(&e);
if last_entity_error
.get(target)
.map(|old_e| old_e != &e_no_world)
.unwrap_or(true)
&& entity_error
.get(target)
.map(|old_e| old_e != &e_no_world)
.unwrap_or(true)
{
error!(
"{} attempted to tween {} component but got query error: {e}",
type_name::<I>(),
type_name::<I::Item>()
);
}
entity_error.insert(*target, e_no_world);
return;
}
};
tween
.interpolator
.interpolate(&mut target_component, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
});
}
_ => {
let target = match &tween.target {
TargetComponent::Marker => {
let mut curr = entity;
let found = 'l: loop {
match q_animation_target.get(curr) {
Ok((parent, has_marker)) => {
if has_marker {
break 'l Some(curr);
} else {
match parent {
Some(parent) => curr = parent.parent(),
None => break 'l None,
}
}
}
_ => break 'l None,
}
};
match found {
Some(found) => found,
None => {
if !last_search_error.contains(&entity) && !search_error.contains(&entity) {
error!(
"Tween {:?} {} cannot find AnimationTarget marker",
entity,
type_name::<I>(),
);
}
search_error.insert(entity);
return;
},
}
}
TargetComponent::Entity(e) => *e,
_ => unreachable!(),
};
let mut target_component = match q_component.get_mut(target) {
Ok(target_component) => target_component,
Err(e) => {
let e_no_world = QueryEntityErrorWithoutWorld::from(&e);
if last_entity_error
.get(&target)
.map(|old_e| old_e != &e_no_world)
.unwrap_or(true)
&& entity_error
.get(&target)
.map(|old_e| old_e != &e_no_world)
.unwrap_or(true)
{
error!(
"{} attempted to tween {} component but got query error: {e}",
type_name::<I>(),
type_name::<I::Item>()
);
}
entity_error.insert(target, e_no_world);
return;
}
};
tween
.interpolator
.interpolate(&mut target_component, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
}
});
*last_entity_error = entity_error;
*last_search_error = search_error;
}
pub fn component_dyn_tween_system<C>() -> ScheduleConfigs<ScheduleSystem>
where
C: Component<Mutability = Mutable>,
{
apply_component_tween_system::<Box<dyn Interpolator<Item = C>>>
.into_configs()
}
pub fn resource_tween_system<I>() -> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Resource,
{
apply_resource_tween_system::<I>.into_configs()
}
#[allow(clippy::type_complexity)]
pub fn apply_resource_tween_system<I>(
mut q_tween: Query<
(&Tween<TargetResource, I>, &TweenInterpolationValue, &mut TweenPreviousValue),
Without<SkipTween>,
>,
resource: Option<ResMut<I::Item>>,
mut last_error: Local<bool>,
) where
I: Interpolator,
I::Item: Resource,
{
let Some(mut resource) = resource else {
if !*last_error {
error!(
"{} resource tween system cannot find the resource",
type_name::<I>()
);
*last_error = true;
}
return;
};
*last_error = false;
q_tween.iter_mut().for_each(|(tween, ease_value, mut previous_value)| {
tween.interpolator.interpolate(&mut resource, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
})
}
pub fn resource_dyn_tween_system<R>() -> ScheduleConfigs<ScheduleSystem>
where
R: Resource,
{
apply_resource_tween_system::<Box<dyn Interpolator<Item = R>>>
.into_configs()
}
#[cfg(feature = "bevy_asset")]
pub fn asset_tween_system<I>() -> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Asset,
{
apply_asset_tween_system::<I>.into_configs()
}
#[cfg(feature = "bevy_asset")]
#[allow(clippy::type_complexity)]
pub fn apply_asset_tween_system<I>(
mut q_tween: Query<
(&Tween<TargetAsset<I::Item>, I>, &TweenInterpolationValue, &mut TweenPreviousValue),
Without<SkipTween>,
>,
asset: Option<ResMut<Assets<I::Item>>>,
mut last_resource_error: Local<bool>,
mut last_asset_error: Local<HashSet<AssetId<I::Item>>>,
) where
I: Interpolator,
I::Item: Asset,
{
let mut asset_error = HashSet::default();
let Some(mut asset) = asset else {
if !*last_resource_error {
error!(
"{} asset tween system cannot find the asset resource",
type_name::<I>()
);
*last_resource_error = true;
}
return;
};
*last_resource_error = false;
q_tween
.iter_mut()
.for_each(|(tween, ease_value, mut previous_value)| match &tween.target {
TargetAsset::Asset(a) => {
let Some(asset) = asset.get_mut(a) else {
if !last_asset_error.contains(&a.id())
&& !asset_error.contains(&a.id())
{
error!(
"{} attempted to tween {} asset {} but it does not exists",
type_name::<I>(),
type_name::<I::Item>(),
a.id()
);
}
asset_error.insert(a.id());
return;
};
tween.interpolator.interpolate(asset, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
}
TargetAsset::Assets(assets) => {
for a in assets {
let Some(a) = asset.get_mut(a) else {
if !last_asset_error.contains(&a.id())
&& !asset_error.contains(&a.id())
{
error!(
"{} attempted to tween {} asset {} but it does not exists",
type_name::<I>(),
type_name::<I::Item>(),
a.id()
);
}
asset_error.insert(a.id());
continue;
};
tween.interpolator.interpolate(a, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
}
}
});
*last_asset_error = asset_error;
}
#[cfg(feature = "bevy_asset")]
pub fn asset_dyn_tween_system<A>() -> ScheduleConfigs<ScheduleSystem>
where
A: Asset,
{
apply_asset_tween_system::<Box<dyn Interpolator<Item = A>>>.into_configs()
}