Skip to content

Conversation

viridia
Copy link
Contributor

@viridia viridia commented Oct 23, 2025

Fixes: #20579

Testing

  • Doc test
  • Also tested extensively in a separate repo, however that code was not carried over (yet).

Note: Because docs.rs was down, I could not validate the doc link urls.

@viridia viridia requested a review from NthTensor October 23, 2025 03:32
@viridia
Copy link
Contributor Author

viridia commented Oct 23, 2025

@LikeLakers2 Tried to add you as a reviewer but could not for some reason.

@viridia viridia requested a review from mockersf October 23, 2025 03:35
Copy link
Contributor

@LikeLakers2 LikeLakers2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. I hope I didn't frustrate you too much in the issue you made!

Comment on lines +589 to +593
impl<T: StableInterpolate> TryStableInterpolate for T {
fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, InterpolationError> {
Ok(self.interpolate_stable(other, t))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, so this ended up working? I'm curious what was different about how you initially tested it, that caused the error you were reporting.

Copy link
Contributor Author

@viridia viridia Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it has to do with the fact that the impls are in the same module as the target types instead of being in the same module as the trait definition. But to be honest, I'm not really sure why it works.

Comment on lines +583 to +587
pub trait TryStableInterpolate: Clone {
/// Attempt to interpolate the value. This may fail if the two interpolation values have
/// different units, or if the type is not interpolable.
fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, InterpolationError>;
}
Copy link
Contributor

@LikeLakers2 LikeLakers2 Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me thinks it's best to let the user specify an error type, since interpolation can fail for reasons specific to the type being interpolated.

Suggested change
pub trait TryStableInterpolate: Clone {
/// Attempt to interpolate the value. This may fail if the two interpolation values have
/// different units, or if the type is not interpolable.
fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, InterpolationError>;
}
pub trait TryStableInterpolate: Clone {
/// The type returned in the event a stable interpolation cannot be performed.
type Error;
/// Attempt to interpolate the value.
fn try_interpolate_stable(&self, other: &Self, t: f32) -> Result<Self, Self::Error>;
}

This is similar to what TryFrom does, since conversion can fail for a variety of reasons, usually reasons specific to the types being converted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the error type depend on the value type would defeat one of my purposes: to have a single uniform animation clip system that can mix both interpolable and non-interpolable types. Basically something like AnimatableProperty which can work for Val.

To be perfectly honest, we don't actually need an error at all, we could instead just make it an Option and return None. We never actually look at the error code, it's only there for documentation purposes and because the try_ name prefix suggest a Result return type. The animation only cares that the interpolation succeeded, it's not like it actually logs an error or anything.

Copy link
Contributor

@LikeLakers2 LikeLakers2 Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate? I'm not sure how an error type that can be specified by each impl would defeat that purpose.

Also, I would recommend keeping it as a Result, even if our error type is (). This is because Option implies that trying will always succeed but may sometimes return nothing, whereas Result tells the user that the operation may fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that it could work, since the error value is ignored, but it seems a bit dodgy. Here's the way the animation code looks now:

match self.start.try_interpolate(&self.end, t) {
    Ok(value) => value,
    Err(_) => self.end, // If we can't interpolate, then just skip to the end
}

Where start and end are generics of type TransitionProperty::ValueType:

/// Represents an animatable property such as `BackgroundColor` or `Width`.
pub trait TransitionProperty {
    /// The data type of the animated property.
    type ValueType: Copy + Send + Sync + PartialEq + 'static + TryStableInterpolate;

    /// The type of component that contains the animated property.
    type ComponentType: Component<Mutability = Mutable>;

    /// Read the value of the animatable property from the component.
    fn get(component: &Self::ComponentType) -> Self::ValueType;

    /// Update the value of the animatable property in the component.
    fn set(component: &mut Mut<Self::ComponentType>, value: Self::ValueType);
}

Adding an extra generic parameter for error type would mean that I wouldn't be able to treat different value types quite as uniformly as before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an extra generic parameter for error type would mean that I wouldn't be able to treat different value types quite as uniformly as before.

I'm not sure what's meant by this. Can you give me an example of what is meant here?

Comment on lines +595 to +601
/// Boolean values can never be interpolated, but they can be animatable parameters (for things
/// like enabling and disabling lighting).
impl TryStableInterpolate for bool {
fn try_interpolate_stable(&self, _other: &Self, _t: f32) -> Result<Self, InterpolationError> {
Err(InterpolationError::NonContiguous)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this will always return an error, then I don't think it's an interpolate-able type.

Suggested change
/// Boolean values can never be interpolated, but they can be animatable parameters (for things
/// like enabling and disabling lighting).
impl TryStableInterpolate for bool {
fn try_interpolate_stable(&self, _other: &Self, _t: f32) -> Result<Self, InterpolationError> {
Err(InterpolationError::NonContiguous)
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this was a bit of a social experiment to see what other people thought.

The use case here is to be able to have an AnimatableProperty that is a boolean rather than a continuous value - so for example you could use this to enable or disable an entity or component as part of an animation clip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fallible interpolation

2 participants