4cdcc8d9f1
9 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
7645ce91ed
|
Add newlines before impl blocks (#19746)
# Objective Fix https://github.com/bevyengine/bevy/issues/19617 # Solution Add newlines before all impl blocks. I suspect that at least some of these will be objectionable! If there's a desired Bevy style for this then I'll update the PR. If not then we can just close it - it's the work of a single find and replace. |
||
![]() |
ddee5cca85
|
Improve Bevy's double-precision story for third-party crates (#19194)
# Objective Certain classes of games, usually those with enormous worlds, require some amount of support for double-precision. Libraries like `big_space` exist to allow for large worlds while integrating cleanly with Bevy's primarily single-precision ecosystem, but even then, games will often still work directly in double-precision throughout the part of the pipeline that feeds into the Bevy interface. Currently, working with double-precision types in Bevy is a pain. `glam` provides types like `DVec3`, but Bevy doesn't provide double-precision analogs for `glam` wrappers like `Dir3`. This is mostly because doing so involves one of: - code duplication - generics - templates (like `glam` uses) - macros Each of these has issues that are enough to be deal-breakers as far as maintainability, usability or readability. To work around this, I'm putting together `bevy_dmath`, a crate that duplicates `bevy_math` types and functionality to allow downstream users to enjoy the ergonomics and power of `bevy_math` in double-precision. For the most part, it's a smooth process, but in order to fully integrate, there are some necessary changes that can only be made in `bevy_math`. ## Solution This PR addresses the first and easiest issue with downstream double-precision math support: `VectorSpace` currently can only represent vector spaces over `f32`. This automatically closes the door to double-precision curves, among other things. This restriction can be easily lifted by allowing vector spaces to specify the underlying scalar field. This PR adds a new trait `ScalarField` that satisfies the properties of a scalar field (the ones that can be upheld statically) and adds a new associated type `type Scalar: ScalarField` to `VectorSpace`. It's mostly an unintrusive change. The biggest annoyances are: - it touches a lot of curve code - `bevy_math::ops` doesn't support `f64`, so there are some annoying workarounds As far as curves code, I wanted to make this change unintrusive and bite-sized, so I'm trying to touch as little code as possible. To prove to myself it can be done, I went ahead and (*not* in this PR) migrated most of the curves API to support different `ScalarField`s and it went really smoothly! The ugliest thing was adding `P::Scalar: From<usize>` in several places. There's an argument to be made here that we should be using `num-traits`, but that's not immediately relevant. The point is that for now, the smallest change I could make was to go into every curve impl and make them generic over `VectorSpace<Scalar = f32>`. Curves work exactly like before and don't change the user API at all. # Follow-up - **Extend `bevy_math::ops` to work with `f64`.** `bevy_math::ops` is used all over, and if curves are ever going to support different `ScalarField` types, we'll need to be able to use the correct `std` or `libm` ops for `f64` types as well. Adding an `ops64` mod turned out to be really ugly, but I'll point out the maintenance burden is low because we're not going to be adding new floating-point ops anytime soon. Another solution is to build a floating-point trait that calls the right op variant and impl it for `f32` and `f64`. This reduces maintenance burden because on the off chance we ever *do* want to go modify it, it's all tied together: you can't change the interface on one without changing the trait, which forces you to update the other. A third option is to use `num-traits`, which is basically option 2 but someone else did the work for us. They already support `no_std` using `libm`, so it would be more or less a drop-in replacement. They're missing a couple floating-point ops like `floor` and `ceil`, but we could make our own floating-point traits for those (there's even the potential for upstreaming them into `num-traits`). - **Tweak curves to accept vector spaces over any `ScalarField`.** Curves are ready to support custom scalar types as soon as the bullet above is addressed. I will admit that the code is not as fun to look at: `P::Scalar` instead of `f32` everywhere. We could consider an alternate design where we use `f32` even to interpolate something like a `DVec3`, but personally I think that's a worse solution than parameterizing curves over the vector space's scalar type. At the end of the day, it's not really bad to deal with in my opinion... `ScalarType` supports enough operations that working with them is almost like working with raw float types, and it unlocks a whole ecosystem for games that want to use double-precision. |
||
![]() |
a266e7e642
|
More uninlined_format_args fixes (#19396)
# Objective There are several uninlined format args (seems to be in more formatting macros and in more crates) that are not detected on stable, but are on nightly. ## Solution Fix them. |
||
![]() |
9b32e09551
|
bevy_reflect: Add clone registrations project-wide (#18307)
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ``` |
||
![]() |
c7531074bc
|
Improve cubic segment bezier functionality (#17645)
# Objective - Fixes #17642 ## Solution - Implemented method `new_bezier(points: [P; 4]) -> Self` for `CubicSegment<P>` - Old implementation of `new_bezier` is now `new_bezier_easing(p1: impl Into<Vec2>, p2: impl Into<Vec2>) -> Self` (**breaking change**) - ~~added method `new_bezier_with_anchor`, which can make a bezier curve between two points with one control anchor~~ - added methods `iter_positions`, `iter_velocities`, `iter_accelerations`, the same as in `CubicCurve` (**copied code, potentially can be reduced)** - bezier creation logic is moved from `CubicCurve` to `CubicSegment`, removing the unneeded allocation ## Testing - Did you test these changes? If so, how? - Run tests inside `crates/bevy_math/` - Tested the functionality in my project - Are there any parts that need more testing? - Did not run `cargo test` on the whole bevy directory because of OOM - Performance improvements are expected when creating `CubicCurve` with `new_bezier` and `new_bezier_easing`, but not tested - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Use in any code that works created `CubicCurve::new_bezier` - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - I don't think relevant --- ## Showcase ```rust // Imagine a car goes towards a local target // Create a simple `CubicSegment`, without using heap let planned_path = CubicSegment::new_bezier([ car_pos, car_pos + car_dir * turn_radius, target_point - target_dir * turn_radius, target_point, ]); // Check if the planned path itersect other entities for pos in planned_path.iter_positions(8) { // do some collision checks } ``` ## Migration Guide > This section is optional. If there are no breaking changes, you can delete this section. - Replace `CubicCurve::new_bezier` with `CubicCurve::new_bezier_easing` |
||
![]() |
5241e09671
|
Upgrade to Rust Edition 2024 (#17967)
# Objective - Fixes #17960 ## Solution - Followed the [edition upgrade guide](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html) ## Testing - CI --- ## Summary of Changes ### Documentation Indentation When using lists in documentation, proper indentation is now linted for. This means subsequent lines within the same list item must start at the same indentation level as the item. ```rust /* Valid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; /* Invalid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; ``` ### Implicit `!` to `()` Conversion `!` (the never return type, returned by `panic!`, etc.) no longer implicitly converts to `()`. This is particularly painful for systems with `todo!` or `panic!` statements, as they will no longer be functions returning `()` (or `Result<()>`), making them invalid systems for functions like `add_systems`. The ideal fix would be to accept functions returning `!` (or rather, _not_ returning), but this is blocked on the [stabilisation of the `!` type itself](https://doc.rust-lang.org/std/primitive.never.html), which is not done. The "simple" fix would be to add an explicit `-> ()` to system signatures (e.g., `|| { todo!() }` becomes `|| -> () { todo!() }`). However, this is _also_ banned, as there is an existing lint which (IMO, incorrectly) marks this as an unnecessary annotation. So, the "fix" (read: workaround) is to put these kinds of `|| -> ! { ... }` closuers into variables and give the variable an explicit type (e.g., `fn()`). ```rust // Valid let system: fn() = || todo!("Not implemented yet!"); app.add_systems(..., system); // Invalid app.add_systems(..., || todo!("Not implemented yet!")); ``` ### Temporary Variable Lifetimes The order in which temporary variables are dropped has changed. The simple fix here is _usually_ to just assign temporaries to a named variable before use. ### `gen` is a keyword We can no longer use the name `gen` as it is reserved for a future generator syntax. This involved replacing uses of the name `gen` with `r#gen` (the raw-identifier syntax). ### Formatting has changed Use statements have had the order of imports changed, causing a substantial +/-3,000 diff when applied. For now, I have opted-out of this change by amending `rustfmt.toml` ```toml style_edition = "2021" ``` This preserves the original formatting for now, reducing the size of this PR. It would be a simple followup to update this to 2024 and run `cargo fmt`. ### New `use<>` Opt-Out Syntax Lifetimes are now implicitly included in RPIT types. There was a handful of instances where it needed to be added to satisfy the borrow checker, but there may be more cases where it _should_ be added to avoid breakages in user code. ### `MyUnitStruct { .. }` is an invalid pattern Previously, you could match against unit structs (and unit enum variants) with a `{ .. }` destructuring. This is no longer valid. ### Pretty much every use of `ref` and `mut` are gone Pattern binding has changed to the point where these terms are largely unused now. They still serve a purpose, but it is far more niche now. ### `iter::repeat(...).take(...)` is bad New lint recommends using the more explicit `iter::repeat_n(..., ...)` instead. ## Migration Guide The lifetimes of functions using return-position impl-trait (RPIT) are likely _more_ conservative than they had been previously. If you encounter lifetime issues with such a function, please create an issue to investigate the addition of `+ use<...>`. ## Notes - Check the individual commits for a clearer breakdown for what _actually_ changed. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com> |
||
![]() |
0403948aa2
|
Remove Implicit std Prelude from no_std Crates (#17086)
# Background In `no_std` compatible crates, there is often an `std` feature which will allow access to the standard library. Currently, with the `std` feature _enabled_, the [`std::prelude`](https://doc.rust-lang.org/std/prelude/index.html) is implicitly imported in all modules. With the feature _disabled_, instead the [`core::prelude`](https://doc.rust-lang.org/core/prelude/index.html) is implicitly imported. This creates a subtle and pervasive issue where `alloc` items _may_ be implicitly included (if `std` is enabled), or must be explicitly included (if `std` is not enabled). # Objective - Make the implicit imports for `no_std` crates consistent regardless of what features are/not enabled. ## Solution - Replace the `cfg_attr` "double negative" `no_std` attribute with conditional compilation to _include_ `std` as an external crate. ```rust // Before #![cfg_attr(not(feature = "std"), no_std)] // After #![no_std] #[cfg(feature = "std")] extern crate std; ``` - Fix imports that are currently broken but are only now visible with the above fix. ## Testing - CI ## Notes I had previously used the "double negative" version of `no_std` based on general consensus that it was "cleaner" within the Rust embedded community. However, this implicit prelude issue likely was considered when forming this consensus. I believe the reason why is the items most affected by this issue are provided by the `alloc` crate, which is rarely used within embedded but extensively used within Bevy. |
||
![]() |
e2248afb3e
|
bevy_math: Apply #[deny(clippy::allow_attributes, clippy::allow_attributes_without_reason)] (#17091)
# Objective We want to deny the following lints: * `clippy::allow_attributes` - Because there's no reason to `#[allow(...)]` an attribute if it wouldn't lint against anything; you should always use `#[expect(...)]` * `clippy::allow_attributes_without_reason` - Because documenting the reason for allowing/expecting a lint is always good ## Solution Set the `clippy::allow_attributes` and `clippy::allow_attributes_without_reason` lints to `deny`, and bring `bevy_math` in line with the new restrictions. No code changes have been made - except if a lint that was previously `allow(...)`'d could be removed via small code changes. For example, `unused_variables` can be handled by adding a `_` to the beginning of a field's name. ## Testing I ran `cargo clippy`, and received no errors. --------- Co-authored-by: IQuick 143 <IQuick143cz@gmail.com> |
||
![]() |
c60dcea231
|
Derivative access patterns for curves (#16503)
# Objective - For curves that also include derivatives, make accessing derivative information via the `Curve` API ergonomic: that is, provide access to a curve that also samples derivative information. - Implement this functionality for cubic spline curves provided by `bevy_math`. Ultimately, this is to serve the purpose of doing more geometric operations on curves, like reparametrization by arclength and the construction of moving frames. ## Solution This has several parts, some of which may seem redundant. However, care has been put into this to satisfy the following constraints: - Accessing a `Curve` that samples derivative information should be not just possible but easy and non-error-prone. For example, given a differentiable `Curve<Vec2>`, one should be able to access something like a `Curve<(Vec2, Vec2)>` ergonomically, and not just sample the derivatives piecemeal from point to point. - Derivative access should not step on the toes of ordinary curve usage. In particular, in the above scenario, we want to avoid simply making the same curve both a `Curve<Vec2>` and a `Curve<(Vec2, Vec2)>` because this requires manual disambiguation when the API is used. - Derivative access must work gracefully in both owned and borrowed contexts. ### `HasTangent` We introduce a trait `HasTangent` that provides an associated `Tangent` type for types that have tangent spaces: ```rust pub trait HasTangent { /// The tangent type. type Tangent: VectorSpace; } ``` (Mathematically speaking, it would be more precise to say that these are types that represent spaces which are canonically [parallelized](https://en.wikipedia.org/wiki/Parallelizable_manifold). ) The idea here is that a point moving through a `HasTangent` type may have a derivative valued in the associated `Tangent` type at each time in its journey. We reify this with a `WithDerivative<T>` type that uses `HasTangent` to include derivative information: ```rust pub struct WithDerivative<T> where T: HasTangent, { /// The underlying value. pub value: T, /// The derivative at `value`. pub derivative: T::Tangent, } ``` And we can play the same game with second derivatives as well, since every `VectorSpace` type is `HasTangent` where `Tangent` is itself (we may want to be more restrictive with this in practice, but this holds mathematically). ```rust pub struct WithTwoDerivatives<T> where T: HasTangent, { /// The underlying value. pub value: T, /// The derivative at `value`. pub derivative: T::Tangent, /// The second derivative at `value`. pub second_derivative: <T::Tangent as HasTangent>::Tangent, } ``` In this PR, `HasTangent` is only implemented for `VectorSpace` types, but it would be valuable to have this implementation for types like `Rot2` and `Quat` as well. We could also do it for the isometry types and, potentially, transforms as well. (This is in decreasing order of value in my opinion.) ### `CurveWithDerivative` This is a trait for a `Curve<T>` which allows the construction of a `Curve<WithDerivative<T>>` when derivative information is known intrinsically. It looks like this: ```rust /// Trait for curves that have a well-defined notion of derivative, allowing for /// derivatives to be extracted along with values. pub trait CurveWithDerivative<T> where T: HasTangent, { /// This curve, but with its first derivative included in sampling. fn with_derivative(self) -> impl Curve<WithDerivative<T>>; } ``` The idea here is to provide patterns like this: ```rust let value_and_derivative = my_curve.with_derivative().sample_clamped(t); ``` One of the main points here is that `Curve<WithDerivative<T>>` is useful as an output because it can be used durably. For example, in a dynamic context, something that needs curves with derivatives can store something like a `Box<dyn Curve<WithDerivative<T>>>`. Note that `CurveWithDerivative` is not dyn-compatible. ### `SampleDerivative` Many curves "know" how to sample their derivatives instrinsically, but implementing `CurveWithDerivative` as given would be onerous or require an annoying amount of boilerplate. There are also hurdles to overcome that involve references to curves: for the `Curve` API, the expectation is that curve transformations like `with_derivative` take things by value, with the contract that they can still be used by reference through deref-magic by including `by_ref` in a method chain. These problems are solved simultaneously by a trait `SampleDerivative` which, when implemented, automatically derives `CurveWithDerivative` for a type and all types that dereference to it. It just looks like this: ```rust pub trait SampleDerivative<T>: Curve<T> where T: HasTangent, { fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T>; // ... other sampling variants as default methods } ``` The point is that the output of `with_derivative` is a `Curve<WithDerivative<T>>` that uses the `SampleDerivative` implementation. On a `SampleDerivative` type, you can also just call `my_curve.sample_with_derivative(t)` instead of something like `my_curve.by_ref().with_derivative().sample(t)`, which is more verbose and less accessible. In practice, `CurveWithDerivative<T>` is actually a "sealed" extension trait of `SampleDerivative<T>`. ## Adaptors `SampleDerivative` has automatic implementations on all curve adaptors except for `FunctionCurve`, `MapCurve`, and `ReparamCurve` (because we do not have a notion of differentiable Rust functions). For example, `CurveReparamCurve` (the reparametrization of a curve by another curve) can compute derivatives using the chain rule in the case both its constituents have them. ## Testing Tests for derivatives on the curve adaptors are included. --- ## Showcase This development allows derivative information to be included with and extracted from curves using the `Curve` API. ```rust let points = [ vec2(-1.0, -20.0), vec2(3.0, 2.0), vec2(5.0, 3.0), vec2(9.0, 8.0), ]; // A cubic spline curve that goes through `points`. let curve = CubicCardinalSpline::new(0.3, points).to_curve().unwrap(); // Calling `with_derivative` causes derivative output to be included in the output of the curve API. let curve_with_derivative = curve.with_derivative(); // A `Curve<f32>` that outputs the speed of the original. let speed_curve = curve_with_derivative.map(|x| x.derivative.norm()); ``` --- ## Questions - ~~Maybe we should seal `WithDerivative` or make it require `SampleDerivative` (i.e. make it unimplementable except through `SampleDerivative`).~~ I decided this is a good idea. - ~~Unclear whether `VectorSpace: HasTangent` blanket implementation is really appropriate. For colors, for example, I'm not sure that the derivative values can really be interpreted as a color. In any case, it should still remain the case that `VectorSpace` types are `HasTangent` and that `HasTangent::Tangent: HasTangent`.~~ I think this is fine. - Infinity bikeshed on names of traits and things. ## Future - Faster implementations of `SampleDerivative` for cubic spline curves. - Improve ergonomics for accessing only derivatives (and other kinds of transformations on derivative curves). - Implement `HasTangent` for: - `Rot2`/`Quat` - `Isometry` types - `Transform`, maybe - Implement derivatives for easing curves. - Marker traits for continuous/differentiable curves. (It's actually unclear to me how much value this has in practice, but we have discussed it in the past.) --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |