Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bdf4b17
Rename `WorldPipeline` -> `BevyWorldPipeline`
nixonyh Apr 29, 2026
c34a425
Add `SubjectSource` trait to unify bake and sample world access
nixonyh Apr 29, 2026
f928cd5
Make `Pipeline` `W` agnostic
nixonyh Apr 30, 2026
55ecdfe
Change `AccessorRegistry::register` to take `FieldAccessor`, use `pat…
nixonyh Apr 30, 2026
968ae82
Add lazy registration support!
nixonyh Apr 30, 2026
5583b20
Fix `bevy_motiongfx` README
nixonyh May 1, 2026
da5dca5
Remove old field/accessor exports from prelude, add TODO in timeline
nixonyh May 1, 2026
aaad04e
Rewrite motiongfx README for beginner friendliness
nixonyh May 1, 2026
f5a0e5f
Do not compile if track is empty in `try_compile`
nixonyh May 1, 2026
b93f3a3
Rename pipeline.rs -> world.rs, world.rs -> manager.rs, MotionGfxWorl…
nixonyh May 1, 2026
56311c9
Fix remaining MotionGfxWorld references in examples
nixonyh May 1, 2026
ccb7a5b
Fix fmt
nixonyh May 1, 2026
973cc1a
Improve safeness of pipeline baking & sampling
nixonyh May 1, 2026
faa72cd
`debug_assert` when pipeline key is missing
nixonyh May 1, 2026
1ea5af9
rename `create_bevy_builder` to `create_builder`
nixonyh May 1, 2026
5770804
Fix doc test
nixonyh May 1, 2026
26de1c0
Better variable naming in examples
nixonyh May 2, 2026
f5fb8ed
Update doc to defer users to safer options
nixonyh May 2, 2026
dc64596
Improve registry docs
nixonyh May 2, 2026
746ef95
Remove the need of `BevyWorld`
nixonyh May 3, 2026
8b83c77
Add doc to marker type
nixonyh May 3, 2026
a8254c6
Fix doc
nixonyh May 3, 2026
93e1da9
Reduce README bloat
nixonyh May 3, 2026
f95e9a0
Simplify README
nixonyh May 3, 2026
533e023
Remove SubjectSource marker
nixonyh May 4, 2026
949bbc8
Update README.md
nixonyh May 4, 2026
5d75d17
Auto play `custom_interp` example
nixonyh May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ bevy_sprite = { version = "0.18.1", default-features = false }
bevy_pbr = { version = "0.18.1", default-features = false }

# other
field_path = "0.3"
field_path = "0.4.1"
nonempty = { version = "0.12", default-features = false }

[workspace.lints.clippy]
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_motiongfx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ fn build_timeline(
.id();

// Build the timeline.
let mut b = TimelineBuilder::new();
let mut b = motiongfx.create_bevy_builder();
let track = b
.act_interp(entity, field!(<Transform>::translation::x), |x| {
.act_interp(entity, path!(<Transform>::translation::x), |x| {
x + 6.0
})
.play(1.0)
Expand Down Expand Up @@ -82,12 +82,12 @@ fn build_timeline(
commands.spawn(MeshMaterial3d(material.clone()));

// Build the timeline.
let mut b = TimelineBuilder::new();
let mut b = motiongfx.create_bevy_builder();
let track = b
.act_interp(
// AssetIds must be type-erased.
material.untyped().id(),
field!(<StandardMaterial>::base_color),
path!(<StandardMaterial>::base_color),
|_| Srgba::RED.into(),
)
.play(1.0)
Expand Down Expand Up @@ -115,7 +115,7 @@ fn build_timeline(
mut motiongfx: ResMut<MotionGfxWorld>,
) {
// Build the timeline.
let mut b = TimelineBuilder::new();
let mut b = motiongfx.create_bevy_builder();
// Add tracks here...
let timeline = b.compile();

Expand Down
18 changes: 11 additions & 7 deletions crates/bevy_motiongfx/src/interpolation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@ use bevy_math::*;
use motiongfx::prelude::*;
use motiongfx::subject::SubjectId;

pub trait ActionInterpTimelineExt {
pub trait ActionInterpTimelineExt<W> {
fn act_interp<I, S, T>(
&mut self,
target: I,
field: Field<S, T>,
field_acc: FieldAccessor<S, T>,
action: impl Action<T>,
) -> InterpActionBuilder<'_, T>
where
W: SubjectSource<I, S>,
I: SubjectId,
S: 'static,
T: Interpolation + ThreadSafe;
T: Interpolation + Clone + ThreadSafe;
}

impl ActionInterpTimelineExt for TimelineBuilder {
impl<W: 'static> ActionInterpTimelineExt<W>
for TimelineBuilder<'_, W>
{
/// Add an [`Action`] with interpolation using
/// [`Interpolation::interp`].
fn act_interp<I, S, T>(
&mut self,
target: I,
field: Field<S, T>,
field_acc: FieldAccessor<S, T>,
action: impl Action<T>,
) -> InterpActionBuilder<'_, T>
where
W: SubjectSource<I, S>,
I: SubjectId,
S: 'static,
T: Interpolation + ThreadSafe,
T: Interpolation + Clone + ThreadSafe,
{
self.act(target, field, action).with_interp(T::interp)
self.act(target, field_acc, action).with_interp(T::interp)
}
}

Expand Down
66 changes: 1 addition & 65 deletions crates/bevy_motiongfx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::world::MotionGfxWorldPlugin;
pub mod controller;
pub mod interpolation;
pub mod pipeline;
pub mod registry;
pub mod world;

pub mod prelude {
Expand All @@ -20,11 +19,7 @@ pub mod prelude {
pub use crate::interpolation::{
ActionInterpTimelineExt, Interpolation,
};
pub use crate::pipeline::{
PipelineRegistryExt, WorldPipeline, WorldPipelineRegistry,
};
pub use crate::register_fields;
pub use crate::registry::FieldPathRegisterAppExt;
pub use crate::pipeline::{BevyTimeline, BevyTimelineBuilder};
pub use crate::world::{MotionGfxWorld, TimelineId};
}

Expand All @@ -48,65 +43,6 @@ impl Plugin for BevyMotionGfxPlugin {
.chain(),
);
app.add_plugins((MotionGfxWorldPlugin, ControllerPlugin));

#[cfg(feature = "transform")]
{
use bevy_transform::components::Transform;

register_fields!(
app.register_component_field(),
Transform,
(
translation(x, y, z),
scale(x, y, z),
rotation(x, y, z, w),
)
);
}

#[cfg(feature = "sprite")]
{
use bevy_sprite::prelude::*;

register_fields!(
app.register_component_field(),
Sprite,
(
image,
texture_atlas,
color,
flip_x,
flip_y,
custom_size,
rect,
image_mode,
)
);
}

#[cfg(feature = "pbr")]
{
use bevy_pbr::prelude::*;

register_fields!(
app.register_asset_field(),
StandardMaterial,
(
base_color,
emissive,
perceptual_roughness,
metallic,
reflectance,
specular_tint,
diffuse_transmission,
specular_transmission,
thickness,
ior,
attenuation_distance,
attenuation_color,
)
);
}
}
}

Expand Down
156 changes: 48 additions & 108 deletions crates/bevy_motiongfx/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,124 +2,64 @@ use bevy_ecs::component::Mutable;
use bevy_ecs::prelude::*;
use motiongfx::prelude::*;

pub type WorldPipelineRegistry = PipelineRegistry<World>;
pub type WorldPipeline = Pipeline<World>;

pub fn bake_component_actions<S, T>(world: &World, ctx: BakeCtx)
where
S: Component,
T: Clone + ThreadSafe,
{
ctx.bake::<Entity, S, T>(|entity| world.get::<S>(entity));
}
/// Newtype wrapper around [`World`] that is local to this crate,
/// allowing [`SubjectSource`] impls without violating the orphan rule.
#[repr(transparent)]
pub struct BevyWorld(pub World);

impl BevyWorld {
pub fn from_ref(world: &World) -> &Self {
// SAFETY: `BevyWorld` is repr(transparent) over `World`.
unsafe { &*(world as *const World as *const Self) }
}

pub fn sample_component_actions<S, T>(
world: &mut World,
ctx: SampleCtx,
) where
S: Component<Mutability = Mutable>,
T: Clone + ThreadSafe,
{
ctx.sample::<Entity, S, T>(|entity, target, accessor| {
if let Some(mut source) = world.get_mut::<S>(entity) {
*accessor.get_mut(&mut source) = target;
}
});
pub fn from_mut(world: &mut World) -> &mut Self {
// SAFETY: `BevyWorld` is repr(transparent) over `World`.
unsafe { &mut *(world as *mut World as *mut Self) }
}
}

#[cfg(feature = "asset")]
pub fn bake_asset_actions<S, T>(world: &World, ctx: BakeCtx)
where
S: bevy_asset::Asset,
T: Clone + ThreadSafe,
impl<S: Component<Mutability = Mutable>> SubjectSource<Entity, S>
for BevyWorld
{
use bevy_asset::Assets;
use bevy_asset::UntypedAssetId;

let Some(assets) = world.get_resource::<Assets<S>>() else {
return;
};
fn get_source(&self, id: Entity) -> Option<&S> {
self.0.get::<S>(id)
}

ctx.bake::<UntypedAssetId, S, T>(|asset_id| {
assets.get(asset_id.typed::<S>())
});
fn apply_source<R>(
&mut self,
id: Entity,
f: impl FnOnce(&mut S) -> R,
) -> Option<R> {
self.0.get_mut::<S>(id).map(|mut m| f(m.as_mut()))
}
}

#[cfg(feature = "asset")]
pub fn sample_asset_actions<S, T>(world: &mut World, ctx: SampleCtx)
where
S: bevy_asset::Asset,
T: Clone + ThreadSafe,
impl<S: bevy_asset::Asset>
SubjectSource<bevy_asset::UntypedAssetId, S> for BevyWorld
{
use bevy_asset::Assets;
use bevy_asset::UntypedAssetId;

let Some(mut assets) = world.get_resource_mut::<Assets<S>>()
else {
return;
};

ctx.sample::<UntypedAssetId, S, T>(
|asset_id, target, accessor| {
if let Some(source) =
assets.get_mut(asset_id.typed::<S>())
{
*accessor.get_mut(source) = target;
}
},
);
}

pub trait PipelineRegistryExt {
fn register_component<S, T>(&mut self) -> PipelineKey
where
S: Component<Mutability = Mutable>,
T: Clone + ThreadSafe;

#[cfg(feature = "asset")]
fn register_asset<S, T>(&mut self) -> PipelineKey
where
S: bevy_asset::Asset,
T: Clone + ThreadSafe;
}

impl PipelineRegistryExt for WorldPipelineRegistry {
fn register_component<S, T>(&mut self) -> PipelineKey
where
S: Component<Mutability = Mutable>,
T: Clone + ThreadSafe,
{
let key = PipelineKey::new::<Entity, S, T>();

self.register_unchecked(
key,
WorldPipeline::new(
bake_component_actions::<S, T>,
sample_component_actions::<S, T>,
),
);

key
fn get_source(
&self,
id: bevy_asset::UntypedAssetId,
) -> Option<&S> {
self.0
.get_resource::<bevy_asset::Assets<S>>()?
.get(id.typed::<S>())
}

#[cfg(feature = "asset")]
fn register_asset<S, T>(&mut self) -> PipelineKey
where
S: bevy_asset::Asset,
T: Clone + ThreadSafe,
{
use bevy_asset::UntypedAssetId;

let key = PipelineKey::new::<UntypedAssetId, S, T>();

self.register_unchecked(
key,
WorldPipeline::new(
bake_asset_actions::<S, T>,
sample_asset_actions::<S, T>,
),
);

key
fn apply_source<R>(
&mut self,
id: bevy_asset::UntypedAssetId,
f: impl FnOnce(&mut S) -> R,
) -> Option<R> {
self.0
.get_resource_mut::<bevy_asset::Assets<S>>()?
.into_inner()
.get_mut(id.typed::<S>())
.map(f)
}
}

pub type BevyTimeline = Timeline<BevyWorld>;
pub type BevyTimelineBuilder<'a> = TimelineBuilder<'a, BevyWorld>;
Loading
Loading