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()
}
pub fn component_tween_system_with_time_context<I, TimeCtx>()
-> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Component<Mutability = Mutable>,
TimeCtx: Default + Send + Sync + 'static,
{
apply_component_tween_system::<I, TimeCtx>.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::NotSpawned(entity_not_spawned_error) => {
EH::EntityDoesNotExist(entity_not_spawned_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, TimeCtx>(
q_animation_target: Query<(Option<&ChildOf>, Has<AnimationTarget>)>,
mut q_tween: Query<
(
Entity,
&Tween<TargetComponent, I>,
&TweenInterpolationValue,
&mut TweenPreviousValue,
),
(Without<SkipTween>, With<TimeContext<TimeCtx>>),
>,
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>,
TimeCtx: Default + Send + Sync + 'static,
{
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 component_dyn_tween_system_with_time_context<C, TimeCtx>()
-> ScheduleConfigs<ScheduleSystem>
where
C: Component<Mutability = Mutable>,
TimeCtx: Default + Send + Sync + 'static,
{
apply_component_tween_system::<Box<dyn Interpolator<Item = C>>, TimeCtx>
.into_configs()
}
pub fn resource_tween_system<I>() -> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Resource<Mutability = Mutable>,
{
apply_resource_tween_system::<I, ()>.into_configs()
}
pub fn resource_tween_system_with_time_context<I, TimeCtx>()
-> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Resource<Mutability = Mutable>,
TimeCtx: Default + Send + Sync + 'static,
{
apply_resource_tween_system::<I, TimeCtx>.into_configs()
}
#[allow(clippy::type_complexity)]
pub fn apply_resource_tween_system<I, TimeCtx>(
mut q_tween: Query<
(
&Tween<TargetResource, I>,
&TweenInterpolationValue,
&mut TweenPreviousValue,
),
(Without<SkipTween>, With<TimeContext<TimeCtx>>),
>,
resource: Option<ResMut<I::Item>>,
mut last_error: Local<bool>,
) where
I: Interpolator,
I::Item: Resource<Mutability = Mutable>,
TimeCtx: Default + Send + Sync + 'static,
{
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, TimeCtx>() -> ScheduleConfigs<ScheduleSystem>
where
R: Resource<Mutability = Mutable>,
TimeCtx: Default + Send + Sync + 'static,
{
apply_resource_tween_system::<Box<dyn Interpolator<Item = R>>, TimeCtx>
.into_configs()
}
#[cfg(feature = "bevy_asset")]
pub fn asset_tween_system<I, TimeCtx>() -> ScheduleConfigs<ScheduleSystem>
where
I: Interpolator + Send + Sync + 'static,
I::Item: Asset,
TimeCtx: Default + Send + Sync + 'static,
{
apply_asset_tween_system::<I, TimeCtx>.into_configs()
}
#[cfg(feature = "bevy_asset")]
#[allow(clippy::type_complexity)]
pub fn apply_asset_tween_system<I, TimeCtx>(
mut q_tween: Query<
(
&Tween<TargetAsset<I::Item>, I>,
&TweenInterpolationValue,
&mut TweenPreviousValue,
),
(Without<SkipTween>, With<TimeContext<TimeCtx>>),
>,
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,
TimeCtx: Default + Send + Sync + 'static,
{
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(mut 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(&mut *asset, ease_value.0, previous_value.0);
previous_value.0 = ease_value.0;
}
TargetAsset::Assets(assets) => {
for a in assets {
let Some(mut 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(&mut *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, TimeCtx>() -> ScheduleConfigs<ScheduleSystem>
where
A: Asset,
TimeCtx: Default + Send + Sync + 'static,
{
apply_asset_tween_system::<Box<dyn Interpolator<Item = A>>, TimeCtx>
.into_configs()
}