Add peniko (2d graphics) animation support#103
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR refactors animation interpolation into a marker-pattern abstraction in ChangesCore Interpolation Abstraction and Timeline API
Vello/Winit Demo Framework
Workspace, Configuration, and Example Migrations
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/motiongfx/src/timeline.rs (1)
438-472:⚠️ Potential issue | 🔴 CriticalAdd the missing
'staticbound to bothactandact_stepmethods.Both methods call
ActionBuilder::with_interp, which requiresT: 'static(defined atcrates/motiongfx/src/action.rs:380). The current signatures omit this bound and will fail to compile.🔧 Suggested fix
pub fn act<I, S, T, M>( &mut self, target: I, field_acc: FieldAccessor<S, T>, action: impl Action<T>, ) -> InterpActionBuilder<'_, T> where W: SubjectSource<M, I, S> + 'static, I: SubjectId, S: 'static, - T: Interpolation<M> + Clone + ThreadSafe, + T: Interpolation<M> + Clone + ThreadSafe + 'static, @@ action: impl Action<T>, ) -> InterpActionBuilder<'_, T> where W: SubjectSource<M, I, S> + 'static, I: SubjectId, S: 'static, - T: Clone + ThreadSafe, + T: Clone + ThreadSafe + 'static,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/motiongfx/src/timeline.rs` around lines 438 - 472, The act and act_step methods call ActionBuilder::with_interp which requires the interpolated type T to be 'static, so add the missing T: 'static bound to both function where-clauses (the act(...) and act_step(...) methods) so their generics include T: 'static alongside the existing bounds; ensure the signatures for act and act_step mirror the required trait bounds used by ActionBuilder::with_interp.
🧹 Nitpick comments (1)
crates/motiongfx/src/interpolation.rs (1)
1-8: ⚡ Quick winClarify the
tcontract in docs.At Line 7, please document whether
tis expected in[0.0, 1.0]or whether extrapolation is intentionally supported. That will prevent divergent downstream implementations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/motiongfx/src/interpolation.rs` around lines 1 - 8, Document the contract for the interpolation parameter `t` on the Interpolation trait: update the doc comment for trait Interpolation and the interp method (Interpolation::interp) to state explicitly whether `t` is clamped to [0.0, 1.0] or if extrapolation outside that range is allowed, and if clamped describe who is responsible for clamping (caller or implementor); include expected behavior for edge cases (e.g., t == 0.0 returns a, t == 1.0 returns b) so downstream implementors have a single authoritative spec to follow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/kurbo_motiongfx/src/trace.rs`:
- Around line 24-26: The public function trace_bez_path currently treats
multi-subpath BezPath as a single stroke; fix by either preserving subpaths or
enforcing single-subpath input: implement detection of MoveTo elements and split
the input BezPath into subpaths, call the existing single-subpath tracing logic
for each subpath, and append results to a new output BezPath preserving MoveTo
boundaries (ensuring you handle closing/First/ClosePath logic), or change
trace_bez_path to return a Result/Option or panic when more than one subpath is
detected and document the behavior; locate and update trace_bez_path and any
helpers that operate on path elements (see comments around lines 44–55) to
perform the split-or-validate step and then aggregate per-subpath traces into
the final BezPath (or return an error) accordingly.
- Around line 7-17: The trace_cubic_bez and trace_quad_bez helpers currently
take an unbounded t and forward it to curve.subsegment, causing inconsistent
behavior with trace_bez_path which clamps t; update both trace_cubic_bez and
trace_quad_bez to clamp t into the [0.0, 1.0] range (e.g. min(max(t, 0.0), 1.0))
before converting to f64 and calling curve.subsegment so their prefix semantics
match trace_bez_path.
---
Outside diff comments:
In `@crates/motiongfx/src/timeline.rs`:
- Around line 438-472: The act and act_step methods call
ActionBuilder::with_interp which requires the interpolated type T to be 'static,
so add the missing T: 'static bound to both function where-clauses (the act(...)
and act_step(...) methods) so their generics include T: 'static alongside the
existing bounds; ensure the signatures for act and act_step mirror the required
trait bounds used by ActionBuilder::with_interp.
---
Nitpick comments:
In `@crates/motiongfx/src/interpolation.rs`:
- Around line 1-8: Document the contract for the interpolation parameter `t` on
the Interpolation trait: update the doc comment for trait Interpolation and the
interp method (Interpolation::interp) to state explicitly whether `t` is clamped
to [0.0, 1.0] or if extrapolation outside that range is allowed, and if clamped
describe who is responsible for clamping (caller or implementor); include
expected behavior for edge cases (e.g., t == 0.0 returns a, t == 1.0 returns b)
so downstream implementors have a single authoritative spec to follow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2bd8cfa6-0cff-45be-9f85-eca05403b7c1
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
Cargo.tomlcrates/bevy_motiongfx/Cargo.tomlcrates/bevy_motiongfx/src/interpolation.rscrates/bevy_motiongfx/src/lib.rscrates/kurbo_motiongfx/Cargo.tomlcrates/kurbo_motiongfx/src/interpolation.rscrates/kurbo_motiongfx/src/lib.rscrates/kurbo_motiongfx/src/trace.rscrates/motiongfx/Cargo.tomlcrates/motiongfx/docs/world.rscrates/motiongfx/examples/custom_world.rscrates/motiongfx/src/interpolation.rscrates/motiongfx/src/lib.rscrates/motiongfx/src/timeline.rsexamples/bevy_examples/examples/custom_ease.rsexamples/bevy_examples/examples/custom_interp.rsexamples/bevy_examples/examples/easings.rsexamples/bevy_examples/examples/hello_world.rsexamples/bevy_examples/examples/minimal.rsexamples/bevy_examples/examples/recording.rsexamples/bevy_examples/examples/slide_basic.rs
💤 Files with no reviewable changes (1)
- crates/bevy_motiongfx/src/lib.rs
535f251 to
3eacd70
Compare
peniko (2d graphics) animation support
- Rename interpolation module to interp - Add CubicBez and QuadBez interpolation - Add trace module with trace_bez_path, trace_cubic_bez, trace_quad_bez - Remove prelude in favour of direct module imports
Make `act` first class for creating animation and use `act_builder` for custom interpolation method.
2e46e80 to
f089dd9
Compare
468c0e8 to
4013fb5
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (4)
crates/motiongfx/src/interpolation.rs (2)
10-25: 💤 Low valueUse the monotonic lerp form to guarantee endpoint exactness.
(*a) * (1.0 - t) + (*b) * tis the imprecise lerp form: due to floating-point rounding, the result att == 1.0is not guaranteed to equalb(and att == 0.0not guaranteed to equala). For animation, this can leave residual error at keyframe endpoints. The standard "monotonic" forma + (b - a) * tguaranteest == 0 → aandt == 1 → bexactly. See alsof32::lerp(currently nightly) which uses this form.Note: switching changes results for
Vec*/DVec*/Colorimpls inbevy_motiongfxtoo, since they reuse this macro.♻️ Proposed change
($ty:ty, $base:ty, $marker:ty) => { impl $crate::interpolation::Interpolation<$marker> for $ty { #[inline] fn interp(a: &Self, b: &Self, t: f32) -> Self { let t = <$base>::from(t); - (*a) * (1.0 - t) + (*b) * t + *a + (*b - *a) * t } } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/motiongfx/src/interpolation.rs` around lines 10 - 25, The interpolation macro impl_float_interpolation emits a non-monotonic lerp (*a) * (1.0 - t) + (*b) * t which can produce endpoint rounding errors; update the Interpolation implementation inside impl_float_interpolation (the fn interp for types produced by the macro) to compute the monotonic form a + (b - a) * t using the same base conversion for t (i.e. let t = <$base>::from(t)), ensuring all operations use the concrete type variables ($ty and $base) so t is applied to (b - a) then added to a to guarantee exact endpoints for t == 0.0 and t == 1.0.
6-8: 💤 Low valueConsider idiomatic
&selfreceiver forinterp.The associated function takes
a: &Self, b: &Selfinstead of&self, other: &Self. Using a&selfreceiver would let callers writea.interp(&b, t)and aligns with idiomatic Rust trait design. The current static-style call siteInterpolation::interp(&a, &b, t)is more verbose at every use (as visible in theTransformimpl inbevy_motiongfx). Since this is a brand new public trait, now is the cheap time to lock in the shape.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/motiongfx/src/interpolation.rs` around lines 6 - 8, Change the trait method signature from a static associated function to an instance method: update pub trait Interpolation<M> { fn interp(a: &Self, b: &Self, t: f32) -> Self; } so it becomes fn interp(&self, other: &Self, t: f32) -> Self; and then update all implementations and call sites (e.g., usages in Transform impls in bevy_motiongfx that currently call Interpolation::interp(&a,&b,t)) to use the instance form a.interp(&b, t). Keep the generic parameter M as-is and ensure lifetimes/borrows still compile when converting impl blocks and tests.crates/bevy_motiongfx/src/interpolation.rs (2)
5-5: 💤 Low valueAdd standard derives to the
Bevymarker.For a public marker type that downstream code may pass as a generic argument or store in
PhantomData-bearing structs, the conventional derives are helpful. Without them, downstream users get worse error messages and can't, e.g., debug-print or copy the marker.♻️ Proposed change
-pub struct Bevy; +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Bevy;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/bevy_motiongfx/src/interpolation.rs` at line 5, The public marker type `Bevy` lacks standard derives which makes it harder for downstream users to print, copy, or compare it; add a derive attribute to the `Bevy` struct such as #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] so it can be cheaply copied, debug-printed, and used in PhantomData-bearing generics—apply this derive to the `pub struct Bevy` declaration.
85-101: ⚡ Quick winInference may become ambiguous if foreign
Interpolation<_>impls land onVec3/Quat.The calls
Interpolation::interp(&a.translation, ...)rely on the compiler inferringM = Bevyfrom there being a single matching impl. If any future impl addsInterpolation<OtherMarker>forVec3/Quat/Vec3, these calls become ambiguous and fail to compile. Since the orphan-rule workaround in this PR (peniko_motiongfx::Peniko) is explicitly designed to enable exactly that pattern downstream, consider disambiguating now.♻️ Proposed change
- impl Interpolation<Bevy> for Transform { - fn interp(a: &Self, b: &Self, t: f32) -> Self { - Self { - translation: Interpolation::interp( - &a.translation, - &b.translation, - t, - ), - rotation: Interpolation::interp( - &a.rotation, - &b.rotation, - t, - ), - scale: Interpolation::interp(&a.scale, &b.scale, t), - } - } - } + impl Interpolation<Bevy> for Transform { + fn interp(a: &Self, b: &Self, t: f32) -> Self { + Self { + translation: <_ as Interpolation<Bevy>>::interp( + &a.translation, + &b.translation, + t, + ), + rotation: <_ as Interpolation<Bevy>>::interp( + &a.rotation, + &b.rotation, + t, + ), + scale: <_ as Interpolation<Bevy>>::interp(&a.scale, &b.scale, t), + } + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/bevy_motiongfx/src/interpolation.rs` around lines 85 - 101, The calls inside the Interpolation<Bevy> for Transform impl are relying on type inference (Interpolation::interp(&a.translation, ...)) which can become ambiguous if other Interpolation<...> impls for Vec3/Quat appear; disambiguate by calling the trait impls directly on the concrete field types with the Bevy marker, e.g. replace Interpolation::interp(&a.translation, &b.translation, t) with <Vec3 as Interpolation<Bevy>>::interp(&a.translation, &b.translation, t), do the same for rotation using <Quat as Interpolation<Bevy>>::interp(...) and for scale using <Vec3 as Interpolation<Bevy>>::interp(...), keeping this change inside the impl Interpolation<Bevy> for Transform block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/bevy_motiongfx/src/interpolation.rs`:
- Line 5: The public marker type `Bevy` lacks standard derives which makes it
harder for downstream users to print, copy, or compare it; add a derive
attribute to the `Bevy` struct such as #[derive(Clone, Copy, Debug, Default,
PartialEq, Eq, Hash)] so it can be cheaply copied, debug-printed, and used in
PhantomData-bearing generics—apply this derive to the `pub struct Bevy`
declaration.
- Around line 85-101: The calls inside the Interpolation<Bevy> for Transform
impl are relying on type inference (Interpolation::interp(&a.translation, ...))
which can become ambiguous if other Interpolation<...> impls for Vec3/Quat
appear; disambiguate by calling the trait impls directly on the concrete field
types with the Bevy marker, e.g. replace Interpolation::interp(&a.translation,
&b.translation, t) with <Vec3 as Interpolation<Bevy>>::interp(&a.translation,
&b.translation, t), do the same for rotation using <Quat as
Interpolation<Bevy>>::interp(...) and for scale using <Vec3 as
Interpolation<Bevy>>::interp(...), keeping this change inside the impl
Interpolation<Bevy> for Transform block.
In `@crates/motiongfx/src/interpolation.rs`:
- Around line 10-25: The interpolation macro impl_float_interpolation emits a
non-monotonic lerp (*a) * (1.0 - t) + (*b) * t which can produce endpoint
rounding errors; update the Interpolation implementation inside
impl_float_interpolation (the fn interp for types produced by the macro) to
compute the monotonic form a + (b - a) * t using the same base conversion for t
(i.e. let t = <$base>::from(t)), ensuring all operations use the concrete type
variables ($ty and $base) so t is applied to (b - a) then added to a to
guarantee exact endpoints for t == 0.0 and t == 1.0.
- Around line 6-8: Change the trait method signature from a static associated
function to an instance method: update pub trait Interpolation<M> { fn interp(a:
&Self, b: &Self, t: f32) -> Self; } so it becomes fn interp(&self, other: &Self,
t: f32) -> Self; and then update all implementations and call sites (e.g.,
usages in Transform impls in bevy_motiongfx that currently call
Interpolation::interp(&a,&b,t)) to use the instance form a.interp(&b, t). Keep
the generic parameter M as-is and ensure lifetimes/borrows still compile when
converting impl blocks and tests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 85ce7ed0-2b51-48bc-99e1-f63286d0f6de
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (29)
.github/workflows/rust.ymlCargo.tomlcrates/bevy_motiongfx/Cargo.tomlcrates/bevy_motiongfx/README.mdcrates/bevy_motiongfx/src/interpolation.rscrates/bevy_motiongfx/src/lib.rscrates/bevy_motiongfx/src/manager.rscrates/motiongfx/Cargo.tomlcrates/motiongfx/README.mdcrates/motiongfx/docs/world.rscrates/motiongfx/examples/custom_world.rscrates/motiongfx/src/interpolation.rscrates/motiongfx/src/lib.rscrates/motiongfx/src/timeline.rscrates/peniko_motiongfx/Cargo.tomlcrates/peniko_motiongfx/src/interpolation.rscrates/peniko_motiongfx/src/lib.rscrates/peniko_motiongfx/src/trace.rsexamples/bevy_examples/Cargo.tomlexamples/bevy_examples/examples/custom_ease.rsexamples/bevy_examples/examples/custom_interp.rsexamples/bevy_examples/examples/easings.rsexamples/bevy_examples/examples/hello_world.rsexamples/bevy_examples/examples/minimal.rsexamples/bevy_examples/examples/recording.rsexamples/bevy_examples/examples/slide_basic.rsexamples/vello_winit_example/Cargo.tomlexamples/vello_winit_example/examples/lissajous.rsexamples/vello_winit_example/src/lib.rs
💤 Files with no reviewable changes (2)
- crates/bevy_motiongfx/src/lib.rs
- examples/bevy_examples/Cargo.toml
✅ Files skipped from review due to trivial changes (1)
- crates/motiongfx/README.md
🚧 Files skipped from review as they are similar to previous changes (11)
- crates/motiongfx/src/lib.rs
- examples/bevy_examples/examples/custom_interp.rs
- examples/bevy_examples/examples/recording.rs
- examples/bevy_examples/examples/custom_ease.rs
- crates/motiongfx/Cargo.toml
- examples/bevy_examples/examples/easings.rs
- examples/bevy_examples/examples/hello_world.rs
- crates/motiongfx/src/timeline.rs
- crates/motiongfx/docs/world.rs
- examples/bevy_examples/examples/slide_basic.rs
- crates/motiongfx/examples/custom_world.rs
- Added `peniko_motiongfx` crate that covers common interpolation & tracing implementations - Move `Interpolation<M>` trait into `motiongfx` and add a marker type to workaround the orphan rule. - Add `Tracer<T>` type in `peniko_motiongfx` to handle tracing operations. - Add examples to showcase animations using `peniko`, rendered via `vello`. https://github.com/user-attachments/assets/69354a82-53d3-48e1-b3a3-8e86ea8b54fa ---------
Stack on top of #100peniko_motiongfxcrate that covers common interpolation & tracing implementationsInterpolation<M>trait intomotiongfxand add a marker type to workaround the orphan rule.Tracer<T>type inpeniko_motiongfxto handle tracing operations.peniko, rendered viavello.2026-05-15_23-41-46.mp4