mirror of
https://github.com/bevyengine/bevy
synced 2024-12-01 17:09:12 +00:00
382 commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
Gino Valente
|
245d03a78a
|
bevy_reflect: Update on_unimplemented attributes (#15110)
# Objective Some of the new compile error messages are a little unclear (at least to me). For example: ``` error[E0277]: `tests::foo::Bar` can not be created through reflection --> crates/bevy_reflect/src/lib.rs:679:18 | 679 | #[derive(Reflect)] | ^^^^^^^ the trait `from_reflect::FromReflect` is not implemented for `tests::foo::Bar` | = note: consider annotating `tests::foo::Bar` with `#[derive(Reflect)]` or `#[derive(FromReflect)]` ``` While the annotation makes it clear that `FromReflect` is missing, it's not very clear from the main error message. My IDE lists errors with only their message immediately present: <p align="center"> <img width="700" alt="Image of said IDE listing errors with only their message immediately present. These errors are as follows: \"`tests::foo::Bar` can not be created through reflection\", \"The trait bound `tests::foo::Bar: RegisterForReflection` is not satisfied\", and \"The trait bound `tests::foo::Bar: type_info::MaybeTyped` is not satisfied\"" src="https://github.com/user-attachments/assets/42c24051-9e8e-4555-8477-51a9407446aa"> </p> This makes it hard to tell at a glance why my code isn't compiling. ## Solution Updated all `on_unimplemented` attributes in `bevy_reflect` to mention the relevant trait—either the actual trait or the one users actually need to implement—as well as a small snippet of what not implementing them means. For example, failing to implement `TypePath` now mentions missing a `TypePath` implementation. And failing to implement `DynamicTypePath` now also mentions missing a `TypePath` implementation, since that's the actual trait users need to implement (i.e. they shouldn't implement `DynamicTypePath` directly). Lastly, I also added some missing `on_unimplemented` attributes for `MaybeTyped` and `RegisterForReflection` (which you can see in the image above). Here's how this looks in my IDE now: <p align="center"> <img width="700" alt="Similar image as before showing the errors listed by the IDE. This time the errors read as follows: \"`tests::foo::Bar` does not implement `FromReflect` so cannot be reified through reflection\", \"`tests::foo::Bar` does not implement `GetTypeRegistration` so cannot be registered for reflection\", and \"`tests::foo::Bar` does not implement `Typed` so cannot provide static type information\"" src="https://github.com/user-attachments/assets/f6f8501f-0450-4f78-b84f-00e7a18d0533"> </p> ## Testing You can test by adding the following code and verifying the compile errors are correct: ```rust #[derive(Reflect)] struct Foo(Bar); struct Bar; ``` |
||
Christian Hughes
|
e939d6c33f
|
Remove remnant EntityHash and related types from bevy_utils (#15039)
# Objective `EntityHash` and related types were moved from `bevy_utils` to `bevy_ecs` in #11498, but seemed to have been accidentally reintroduced a week later in #11707. ## Solution Remove the old leftover code. --- ## Migration Guide - Uses of `bevy::utils::{EntityHash, EntityHasher, EntityHashMap, EntityHashSet}` now have to be imported from `bevy::ecs::entity`. |
||
Gino Valente
|
ba3d9b3fb6
|
bevy_reflect: Refactor serde module (#15107)
# Objective The `ser` and `de` modules in `bevy_reflect/serde` are very long and difficult to navigate. ## Solution Refactor both modules into many smaller modules that each have a single primary focus (i.e. a `structs` module that only handles struct serialization/deserialization). I chose to keep the `ser` and `de` modules separate. We could have instead broken it up kind (e.g. lists, maps, etc.), but I think this is a little cleaner. Serialization and deserialization, while related, can be very different. So keeping them separated makes sense for organizational purposes. That being said, if people disagree and think we should structure this a different way, I am open to changing it. Note that this PR's changes are mainly structural. There are a few places I refactored code to reduce duplication and to make things a bit cleaner, but these are largely cosmetic and shouldn't have any impact on behavior. ### Other Details This PR also hides a lot of the internal logic from being exported. These were originally public, but it's unlikely they really saw any use outside of these modules. In fact, you don't really gain anything by using them outside of this module either. By privatizing these fields and items, we also set ourselves up for more easily changing internal logic around without involving a breaking change. I also chose not to mess around with tests since that would really blow up the diff haha. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect --all-features ``` --- ## Migration Guide The fields on `ReflectSerializer` and `TypedReflectSerializer` are now private. To instantiate, the corresponding constructor must be used: ```rust // BEFORE let serializer = ReflectSerializer { value: &my_value, registry: &type_registry, }; // AFTER let serializer = ReflectSerializer::new(&my_value, &type_registry); ``` Additionally, the following types are no longer public: - `ArraySerializer` - `EnumSerializer` - `ListSerializer` - `MapSerializer` - `ReflectValueSerializer` (fully removed) - `StructSerializer` - `TupleSerializer` - `TupleStructSerializer` As well as the following traits: - `DeserializeValue` (fully removed) |
||
BD103
|
6ec6a55645
|
Unify crate-level preludes (#15080)
# Objective
- Crate-level prelude modules, such as `bevy_ecs::prelude`, are plagued
with inconsistency! Let's fix it!
## Solution
Format all preludes based on the following rules:
1. All preludes should have brief documentation in the format of:
> The _name_ prelude.
>
> This includes the most common types in this crate, re-exported for
your convenience.
2. All documentation should be outer, not inner. (`///` instead of
`//!`.)
3. No prelude modules should be annotated with `#[doc(hidden)]`. (Items
within them may, though I'm not sure why this was done.)
## Testing
- I manually searched for the term `mod prelude` and updated all
occurrences by hand. 🫠
---------
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
|
||
Zachary Harrold
|
547b1c7a7a
|
Reflect SmolStr 's De/Serialize implementation (#14982)
# Objective - Fixes #14969 ## Solution - Added `Deserialize` to the list of reflected traits for `SmolStr` ## Testing - CI passed locally. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
Zachary Harrold
|
bc13161416
|
Migrated NonZero* to NonZero<*> (#14978)
# Objective - Fixes #14974 ## Solution - Replace all* instances of `NonZero*` with `NonZero<*>` ## Testing - CI passed locally. --- ## Notes Within the `bevy_reflect` implementations for `std` types, `impl_reflect_value!()` will continue to use the type aliases instead, as it inappropriately parses the concrete type parameter as a generic argument. If the `ZeroablePrimitive` trait was stable, or the macro could be modified to accept a finite list of types, then we could fully migrate. |
||
Zachary Harrold
|
44620dd6ae
|
Split GenericTypeCell::get_or_insert into smaller pieces (#14865)
# Objective Based on the discussion in #14864, I wanted to experiment with the core `GenericTypeCell` type, whose `get_or_insert` method accounted for 2% of the final binary size of the `3d_scene` example. The reason for this large percentage is likely because the type is fundamental to the rest of Bevy while having 3 generic parameters (the type stored `T`, the type to retrieve `G`, and the function used to insert a new value `F`). - Acts on #14864 ## Solution - Split `get_or_insert` into smaller functions with minimised parameterisation. These new functions are private as to preserve the public facing API, but could be exposed if desired. ## Testing - Ran CI locally. - Used `cargo bloat --release --example 3d_scene -n 100000 --message-format json > out.json` and @cart's [bloat analyzer](https://gist.github.com/cart/722756ba3da0e983d207633e0a48a8ab) to measure a 428KiB reduction in binary size when compiling on Windows 10. - ~I have _not_ benchmarked to determine if this improves/hurts performance.~ See [below](https://github.com/bevyengine/bevy/pull/14865#issuecomment-2306083606). ## Notes In my opinion this seems like a good test-case for the concept of debloating generics within the Bevy codebase. I believe the performance impact here is negligible in either direction (at runtime and compile time), but the binary reduction is measurable and quite significant for a relatively minor change in code. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> |
||
Gino Valente
|
3892adcb47
|
bevy_reflect: Add Type type (#14838)
# Objective Closes #7622. I was working on adding support for reflecting generic functions and found that I wanted to use an argument's `TypeId` for hashing and comparison, but its `TypePath` for debugging and error messaging. While I could just keep them separate, place them in a tuple or a local struct or something, I think I see an opportunity to make a dedicate type for this. Additionally, we can use this type to clean up some duplication amongst the type info structs in a manner similar to #7622. ## Solution Added the `Type` type. This should be seen as the most basic representation of a type apart from `TypeId`. It stores both the `TypeId` of the type as well as its `TypePathTable`. The `Hash` and `PartialEq` implementations rely on the `TypeId`, while the `Debug` implementation relies on the `TypePath`. This makes it especially useful as a key in a `HashMap` since we get the speed of the `TypeId` hashing/comparisons with the readability of `TypePath`. With this type, we're able to reduce the duplication across the type info structs by removing individual fields for `TypeId` and `TypePathTable`, replacing them with a single `Type` field. Similarly, we can remove many duplicate methods and replace it with a macro that delegates to the stored `Type`. ### Caveats It should be noted that this type is currently 3x larger than `TypeId`. On my machine, it's 48 bytes compared to `TypeId`'s 16. While this doesn't matter for `TypeInfo` since it would contain that data regardless, it is something to keep in mind when using elsewhere. ## Testing All tests should pass as normal: ``` cargo test --package bevy_reflect ``` --- ## Showcase `bevy_reflect` now exports a `Type` struct. This type contains both the `TypeId` and the `TypePathTable` of the given type, allowing it to be used like `TypeId` but have the debuggability of `TypePath`. ```rust // We can create this for any type implementing `TypePath`: let ty = Type::of::<String>(); // It has `Hash` and `Eq` impls powered by `TypeId`, making it useful for maps: let mut map = HashMap::<Type, i32>::new(); map.insert(ty, 25); // And it has a human-readable `Debug` representation: let debug = format!("{:?}", map); assert_eq!(debug, "{alloc::string::String: 25}"); ``` ## Migration Guide Certain type info structs now only return their item types as `Type` instead of exposing direct methods on them. The following methods have been removed: - `ArrayInfo::item_type_path_table` - `ArrayInfo::item_type_id` - `ArrayInfo::item_is` - `ListInfo::item_type_path_table` - `ListInfo::item_type_id` - `ListInfo::item_is` - `SetInfo::value_type_path_table` - `SetInfo::value_type_id` - `SetInfo::value_is` - `MapInfo::key_type_path_table` - `MapInfo::key_type_id` - `MapInfo::key_is` - `MapInfo::value_type_path_table` - `MapInfo::value_type_id` - `MapInfo::value_is` Instead, access the `Type` directly using one of the new methods: - `ArrayInfo::item_ty` - `ListInfo::item_ty` - `SetInfo::value_ty` - `MapInfo::key_ty` - `MapInfo::value_ty` For example: ```rust // BEFORE let type_id = array_info.item_type_id(); // AFTER let type_id = array_info.item_ty().id(); ``` |
||
Lubba64
|
f9fbd08f9f
|
Implement Reflect for std::ops::Bound (#14861)
# Objective - Fixes #14844 ## Solution - implement reflect using the `impl_reflect_value` macro ## Testing - I wrote a test locally to understand and learn how reflection worked on a basic level and to confirm that yes indeed the bound struct could use the reflection traits that have been implemented for it. note: I did remove a line that asked for bound to not have reflect implemented in a test, since that's the point of this PR and the test worked without the line so I am not sure what that was about, not sure if that uncovers a deeper issue or not. |
||
EdJoPaTo
|
938d810766
|
Apply unused_qualifications lint (#14828)
# Objective Fixes #14782 ## Solution Enable the lint and fix all upcoming hints (`--fix`). Also tried to figure out the false-positive (see review comment). Maybe split this PR up into multiple parts where only the last one enables the lint, so some can already be merged resulting in less many files touched / less potential for merge conflicts? Currently, there are some cases where it might be easier to read the code with the qualifier, so perhaps remove the import of it and adapt its cases? In the current stage it's just a plain adoption of the suggestions in order to have a base to discuss. ## Testing `cargo clippy` and `cargo run -p ci` are happy. |
||
Gino Valente
|
2b4180ca8f
|
bevy_reflect: Function reflection terminology refactor (#14813)
# Objective One of the changes in #14704 made `DynamicFunction` effectively the same as `DynamicClosure<'static>`. This change meant that the de facto function type would likely be `DynamicClosure<'static>` instead of the intended `DynamicFunction`, since the former is much more flexible. We _could_ explore ways of making `DynamicFunction` implement `Copy` using some unsafe code, but it likely wouldn't be worth it. And users would likely still reach for the convenience of `DynamicClosure<'static>` over the copy-ability of `DynamicFunction`. The goal of this PR is to fix this confusion between the two types. ## Solution Firstly, the `DynamicFunction` type was removed. Again, it was no different than `DynamicClosure<'static>` so it wasn't a huge deal to remove. Secondly, `DynamicClosure<'env>` and `DynamicClosureMut<'env>` were renamed to `DynamicFunction<'env>` and `DynamicFunctionMut<'env>`, respectively. Yes, we still ultimately kept the naming of `DynamicFunction`, but changed its behavior to that of `DynamicClosure<'env>`. We need a term to refer to both functions and closures, and "function" was the best option. [Originally](https://discord.com/channels/691052431525675048/1002362493634629796/1274091992162242710), I was going to go with "callable" as the replacement term to encompass both functions and closures (e.g. `DynamciCallable<'env>`). However, it was [suggested](https://discord.com/channels/691052431525675048/1002362493634629796/1274653581777047625) by @SkiFire13 that the simpler "function" term could be used instead. While "callable" is perhaps the better umbrella term—being truly ambiguous over functions and closures— "function" is more familiar, used more often, easier to discover, and is subjectively just "better-sounding". ## Testing Most changes are purely swapping type names or updating documentation, but you can verify everything still works by running the following command: ``` cargo test --package bevy_reflect ``` |
||
Gino Valente
|
423285cf1c
|
bevy_reflect: Store functions as DynamicClosure<'static> in FunctionRegistry (#14704)
# Objective #14098 added the `FunctionRegistry` for registering functions such that they can be retrieved by name and used dynamically. One thing we chose to leave out in that initial PR is support for closures. Why support closures? Mainly, we don't want to prohibit users from injecting environmental data into their registered functions. This allows these functions to not leak their internals to the public API. For example, let's say we're writing a library crate that allows users to register callbacks for certain actions. We want to perform some actions before invoking the user's callback so we can't just call it directly. We need a closure for this: ```rust registry.register("my_lib::onclick", move |event: ClickEvent| { // ...other work... user_onclick.call(event); // <-- Captured variable }); ``` We could have made our callback take a reference to the user's callback. This would remove the need for the closure, but it would change our desired API to place the burden of fetching the correct callback on the caller. ## Solution Modify the `FunctionRegistry` to store registered functions as `DynamicClosure<'static>` instead of `DynamicFunction` (now using `IntoClosure` instead of `IntoFunction`). Due to limitations in Rust and how function reflection works, `DynamicClosure<'static>` is functionally equivalent to `DynamicFunction`. And a normal function is considered a subset of closures (it's a closure that doesn't capture anything), so there shouldn't be any difference in usage: all functions that satisfy `IntoFunction` should satisfy `IntoClosure`. This means that the registration API introduced in #14098 should require little-to-no changes on anyone following `main`. ### Closures vs Functions One consideration here is whether we should keep closures and functions separate. This PR unifies them into `DynamicClosure<'static>`, but we can consider splitting them up. The reasons we might want to do so are: - Simplifies mental model and terminology (users don't have to understand that functions turn into closures) - If Rust ever improves its function model, we may be able to add additional guarantees to `DynamicFunction` that make it useful to separate the two - Adding support for generic functions may be less confusing for users since closures in Rust technically can't be generic The reasons behind this PR's unification approach are: - Reduces the number of methods needed on `FunctionRegistry` - Reduces the number of lookups a user may have to perform (i.e. "`get_function` or else `get_closure`") - Establishes `DynamicClosure<'static>` as the de facto dynamic callable (similar to how most APIs in Rust code tend to prefer `impl Fn() -> String` over `fn() -> String`) I'd love to hear feedback on this matter, and whether we should continue with this PR's approach or switch to a split model. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Showcase Closures can now be registered into the `FunctionRegistry`: ```rust let punct = String::from("!!!"); registry.register_with_name("my_crate::punctuate", move |text: String| { format!("{}{}", text, punct) }); ``` |
||
eckz
|
a44278aee6
|
Making DynamicEnum::is_dynamic() return true (#14732)
# Objective - Right now `DynamicEnum::is_dynamic()` is returning `false`. I don't think this was expected, since the rest of `Dynamic*` types return `true`. ## Solution - Making `DynamicEnum::is_dynamic()` return true ## Testing - Added an extra unit test to verify that `.is_dynamic()` returns `true`. |
||
Gino Valente
|
6183b56b5d
|
bevy_reflect: Reflect remote types (#6042)
# Objective The goal with this PR is to allow the use of types that don't implement `Reflect` within the reflection API. Rust's [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type) prevents implementing a trait on an external type when neither type nor trait are owned by the implementor. This means that if a crate, `cool_rust_lib`, defines a type, `Foo`, then a user cannot use it with reflection. What this means is that we have to ignore it most of the time: ```rust #[derive(Reflect)] struct SomeStruct { #[reflect(ignore)] data: cool_rust_lib::Foo } ``` Obviously, it's impossible to implement `Reflect` on `Foo`. But does it *have* to be? Most of reflection doesn't deal with concrete types— it's almost all using `dyn Reflect`. And being very metadata-driven, it should theoretically be possible. I mean, [`serde`](https://serde.rs/remote-derive.html) does it. ## Solution > Special thanks to @danielhenrymantilla for their help reviewing this PR and offering wisdom wrt safety. Taking a page out of `serde`'s book, this PR adds the ability to easily use "remote types" with reflection. In this context, a "remote type" is the external type for which we have no ability to implement `Reflect`. This adds the `#[reflect_remote(...)]` attribute macro, which is used to generate "remote type wrappers". All you have to do is define the wrapper exactly the same as the remote type's definition: ```rust // Pretend this is our external crate mod cool_rust_lib { #[derive(Default)] struct Foo { pub value: String } } #[reflect_remote(cool_rust_lib::Foo)] struct FooWrapper { pub value: String } ``` > **Note:** All fields in the external type *must* be public. This could be addressed with a separate getter/setter attribute either in this PR or in another one. The macro takes this user-defined item and transforms it into a newtype wrapper around the external type, marking it as `#[repr(transparent)]`. The fields/variants defined by the user are simply used to build out the reflection impls. Additionally, it generates an implementation of the new trait, `ReflectRemote`, which helps prevent accidental misuses of this API. Therefore, the output generated by the macro would look something like: ```rust #[repr(transparent)] struct FooWrapper(pub cool_rust_lib::Foo); impl ReflectRemote for FooWrapper { type Remote = cool_rust_lib::Foo; // transmutation methods... } // reflection impls... // these will acknowledge and make use of the `value` field ``` Internally, the reflection API will pass around the `FooWrapper` and [transmute](https://doc.rust-lang.org/std/mem/fn.transmute.html) it where necessary. All we have to do is then tell `Reflect` to do that. So rather than ignoring the field, we tell `Reflect` to use our wrapper using the `#[reflect(remote = ...)]` field attribute: ```rust #[derive(Reflect)] struct SomeStruct { #[reflect(remote = FooWrapper)] data: cool_rust_lib::Foo } ``` #### Other Macros & Type Data Because this macro consumes the defined item and generates a new one, we can't just put our macros anywhere. All macros that should be passed to the generated struct need to come *below* this macro. For example, to derive `Default` and register its associated type data: ```rust // ✅ GOOD #[reflect_remote(cool_rust_lib::Foo)] #[derive(Default)] #[reflect(Default)] struct FooWrapper { pub value: String } // ❌ BAD #[derive(Default)] #[reflect_remote(cool_rust_lib::Foo)] #[reflect(Default)] struct FooWrapper { pub value: String } ``` #### Generics Generics are forwarded to the generated struct as well. They should also be defined in the same order: ```rust #[reflect_remote(RemoteGeneric<'a, T1, T2>)] struct GenericWrapper<'a, T1, T2> { pub foo: &'a T1, pub bar: &'a T2, } ``` > Naming does *not* need to match the original definition's. Only order matters here. > Also note that the code above is just a demonstration and doesn't actually compile since we'd need to enforce certain bounds (e.g. `T1: Reflect`, `'a: 'static`, etc.) #### Nesting And, yes, you can nest remote types: ```rust #[reflect_remote(RemoteOuter)] struct OuterWrapper { #[reflect(remote = InnerWrapper)] pub inner: RemoteInner } #[reflect_remote(RemoteInner)] struct InnerWrapper(usize); ``` #### Assertions This macro will also generate some compile-time assertions to ensure that the correct types are used. It's important we catch this early so users don't have to wait for something to panic. And it also helps keep our `unsafe` a little safer. For example, a wrapper definition that does not match its corresponding remote type will result in an error: ```rust mod external_crate { pub struct TheirStruct(pub u32); } #[reflect_remote(external_crate::TheirStruct)] struct MyStruct(pub String); // ERROR: expected type `u32` but found `String` ``` <details> <summary>Generated Assertion</summary> ```rust const _: () = { #[allow(non_snake_case)] #[allow(unused_variables)] #[allow(unused_assignments)] #[allow(unreachable_patterns)] #[allow(clippy::multiple_bound_locations)] fn assert_wrapper_definition_matches_remote_type( mut __remote__: external_crate::TheirStruct, ) { __remote__.0 = (|| -> ::core::option::Option<String> { None })().unwrap(); } }; ``` </details> Additionally, using the incorrect type in a `#[reflect(remote = ...)]` attribute should result in an error: ```rust mod external_crate { pub struct TheirFoo(pub u32); pub struct TheirBar(pub i32); } #[reflect_remote(external_crate::TheirFoo)] struct MyFoo(pub u32); #[reflect_remote(external_crate::TheirBar)] struct MyBar(pub i32); #[derive(Reflect)] struct MyStruct { #[reflect(remote = MyBar)] // ERROR: expected type `TheirFoo` but found struct `TheirBar` foo: external_crate::TheirFoo } ``` <details> <summary>Generated Assertion</summary> ```rust const _: () = { struct RemoteFieldAssertions; impl RemoteFieldAssertions { #[allow(non_snake_case)] #[allow(clippy::multiple_bound_locations)] fn assert__foo__is_valid_remote() { let _: <MyBar as bevy_reflect::ReflectRemote>::Remote = (|| -> ::core::option::Option<external_crate::TheirFoo> { None })().unwrap(); } } }; ``` </details> ### Discussion There are a couple points that I think still need discussion or validation. - [x] 1. `Any` shenanigans ~~If we wanted to downcast our remote type from a `dyn Reflect`, we'd have to first downcast to the wrapper then extract the inner type. This PR has a [commit](b840db9f74cb6d357f951cb11b150d46bac89ee2) that addresses this by making all the `Reflect::*any` methods return the inner type rather than the wrapper type. This allows us to downcast directly to our remote type.~~ ~~However, I'm not sure if this is something we want to do. For unknowing users, it could be confusing and seemingly inconsistent. Is it worth keeping? Or should this behavior be removed?~~ I think this should be fine. The remote wrapper is an implementation detail and users should not need to downcast to the wrapper type. Feel free to let me know if there are other opinions on this though! - [x] 2. Implementing `Deref/DerefMut` and `From` ~~We don't currently do this, but should we implement other traits on the generated transparent struct? We could implement `Deref`/`DerefMut` to easily access the inner type. And we could implement `From` for easier conversion between the two types (e.g. `T: Into<Foo>`).~~ As mentioned in the comments, we probably don't need to do this. Again, the remote wrapper is an implementation detail, and should generally not be used directly. - [x] 3. ~~Should we define a getter/setter field attribute in this PR as well or leave it for a future one?~~ I think this should be saved for a future PR - [ ] 4. Any foreseeable issues with this implementation? #### Alternatives One alternative to defining our own `ReflectRemote` would be to use [bytemuck's `TransparentWrapper`](https://docs.rs/bytemuck/1.13.1/bytemuck/trait.TransparentWrapper.html) (as suggested by @danielhenrymantilla). This is definitely a viable option, as `ReflectRemote` is pretty much the same thing as `TransparentWrapper`. However, the cost would be bringing in a new crate— though, it is already in use in a few other sub-crates like bevy_render. I think we're okay just defining `ReflectRemote` ourselves, but we can go the bytemuck route if we'd prefer offloading that work to another crate. --- ## Changelog * Added the `#[reflect_remote(...)]` attribute macro to allow `Reflect` to be used on remote types * Added `ReflectRemote` trait for ensuring proper remote wrapper usage |
||
Tau Gärtli
|
aab1f8e435
|
Use #[doc(fake_variadic)] to improve docs readability (#14703)
# Objective - Fixes #14697 ## Solution This PR modifies the existing `all_tuples!` macro to optionally accept a `#[doc(fake_variadic)]` attribute in its input. If the attribute is present, each invocation of the impl macro gets the correct attributes (i.e. the first impl receives `#[doc(fake_variadic)]` while the other impls are hidden using `#[doc(hidden)]`. Impls for the empty tuple (unit type) are left untouched (that's what the [standard library](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html#impl-PartialEq-for-()) and [serde](https://docs.rs/serde/latest/serde/trait.Serialize.html#impl-Serialize-for-()) do). To work around https://github.com/rust-lang/cargo/issues/8811 and to get impls on re-exports to correctly show up as variadic, `--cfg docsrs_dep` is passed when building the docs for the toplevel `bevy` crate. `#[doc(fake_variadic)]` only works on tuples and fn pointers, so impls for structs like `AnyOf<(T1, T2, ..., Tn)>` are unchanged. ## Testing I built the docs locally using `RUSTDOCFLAGS='--cfg docsrs' RUSTFLAGS='--cfg docsrs_dep' cargo +nightly doc --no-deps --workspace` and checked the documentation page of a trait both in its original crate and the re-exported version in `bevy`. The description should correctly mention for how many tuple items the trait is implemented. I added `rustc-args` for docs.rs to the `bevy` crate, I hope there aren't any other notable crates that re-export `#[doc(fake_variadic)]` traits. --- ## Showcase `bevy_ecs::query::QueryData`: <img width="1015" alt="Screenshot 2024-08-12 at 16 41 28" src="https://github.com/user-attachments/assets/d40136ed-6731-475f-91a0-9df255cd24e3"> `bevy::ecs::query::QueryData` (re-export): <img width="1005" alt="Screenshot 2024-08-12 at 16 42 57" src="https://github.com/user-attachments/assets/71d44cf0-0ab0-48b0-9a51-5ce332594e12"> ## Original Description <details> Resolves #14697 Submitting as a draft for now, very WIP. Unfortunately, the docs don't show the variadics nicely when looking at reexported items. For example: `bevy_ecs::bundle::Bundle` correctly shows the variadic impl: ![image](https://github.com/user-attachments/assets/90bf8af1-1d1f-4714-9143-cdd3d0199998) while `bevy::ecs::bundle::Bundle` (the reexport) shows all the impls (not good): ![image](https://github.com/user-attachments/assets/439c428e-f712-465b-bec2-481f7bf5870b) Built using `RUSTDOCFLAGS='--cfg docsrs' cargo +nightly doc --workspace --no-deps` (`--no-deps` because of wgpu-core). Maybe I missed something or this is a limitation in the *totally not private* `#[doc(fake_variadic)]` thingy. In any case I desperately need some sleep now :)) </details> |
||
radiish
|
6ab8767d3b
|
reflect: implement the unique reflect rfc (#7207)
# Objective
- Implements the [Unique Reflect
RFC](https://github.com/nicopap/rfcs/blob/bevy-reflect-api/rfcs/56-better-reflect.md).
## Solution
- Implements the RFC.
- This implementation differs in some ways from the RFC:
- In the RFC, it was suggested `Reflect: Any` but `PartialReflect:
?Any`. During initial implementation I tried this, but we assume the
`PartialReflect: 'static` in a lot of places and the changes required
crept out of the scope of this PR.
- `PartialReflect::try_into_reflect` originally returned `Option<Box<dyn
Reflect>>` but i changed this to `Result<Box<dyn Reflect>, Box<dyn
PartialReflect>>` since the method takes by value and otherwise there
would be no way to recover the type. `as_full` and `as_full_mut` both
still return `Option<&(mut) dyn Reflect>`.
---
## Changelog
- Added `PartialReflect`.
- `Reflect` is now a subtrait of `PartialReflect`.
- Moved most methods on `Reflect` to the new `PartialReflect`.
- Added `PartialReflect::{as_partial_reflect, as_partial_reflect_mut,
into_partial_reflect}`.
- Added `PartialReflect::{try_as_reflect, try_as_reflect_mut,
try_into_reflect}`.
- Added `<dyn PartialReflect>::{try_downcast_ref, try_downcast_mut,
try_downcast, try_take}` supplementing the methods on `dyn Reflect`.
## Migration Guide
- Most instances of `dyn Reflect` should be changed to `dyn
PartialReflect` which is less restrictive, however trait bounds should
generally stay as `T: Reflect`.
- The new `PartialReflect::{as_partial_reflect, as_partial_reflect_mut,
into_partial_reflect, try_as_reflect, try_as_reflect_mut,
try_into_reflect}` methods as well as `Reflect::{as_reflect,
as_reflect_mut, into_reflect}` will need to be implemented for manual
implementors of `Reflect`.
## Future Work
- This PR is designed to be followed up by another "Unique Reflect Phase
2" that addresses the following points:
- Investigate making serialization revolve around `Reflect` instead of
`PartialReflect`.
- [Remove the `try_*` methods on `dyn PartialReflect` since they are
stop
gaps](https://github.com/bevyengine/bevy/pull/7207#discussion_r1083476050).
- Investigate usages like `ReflectComponent`. In the places they
currently use `PartialReflect`, should they be changed to use `Reflect`?
- Merging this opens the door to lots of reflection features we haven't
been able to implement.
- We could re-add [the `Reflectable`
trait](
|
||
Gino Valente
|
297c0a3954
|
bevy_reflect: Add DynamicSet to dynamic_types example (#14665)
# Objective The `dynamic_types` example was missing a reference to the newly added `DynamicSet` type. ## Solution Add `DynamicSet` to the `dynamic_types` example. For parity with the other dynamic types, I also implemented `FromIterator<T: Reflect>`, `FromIterator<Box<dyn Reflect>>`, and `IntoIterator for &DynamicSet`. ## Testing You can run the example locally: ``` cargo run --example dynamic_types ``` |
||
Gino Valente
|
aeef1c0f20
|
bevy_reflect: Update internal docs regarding anonymous function type names (#14666)
# Objective As pointed out by @SkiFire13 on [Discord](https://discord.com/channels/691052431525675048/1002362493634629796/1270624366119485441), I was incorrect in #14641 regarding the type name of anonymous functions. I had stated that they will return something like `fn(i32, i32) -> i32`, but this is wrong. They actually behave like closures (despite not technically being closures) and return something more like `foo::bar::{{closure}}`. This isn't a major issue because the reasoning behind #14641 still stands. However, the internal documentation should probably be updated so future contributors don't believe the lies I left behind. ## Solution Updated the internal documentation for `create_info` to reflect the actual type name of an anonymous function. In that same module, I also added a test for function pointers and updated all tests to include sanity checks for the `std::any::type_name` of each category of callable. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` |
||
Gino Valente
|
a0cc636ea3
|
bevy_reflect: Anonymous function parsing (#14641)
# Objective ### TL;DR #14098 added the `FunctionRegistry` but had some last minute complications due to anonymous functions. It ended up going with a "required name" approach to ensure anonymous functions would always have a name. However, this approach isn't ideal for named functions since, by definition, they will always have a name. Therefore, this PR aims to modify function reflection such that we can make function registration easier for named functions, while still allowing anonymous functions to be registered as well. ### Context Function registration (#14098) ran into a little problem: anonymous functions. Anonymous functions, including function pointers, have very non-unique type names. For example, the anonymous function `|a: i32, b: i32| a + b` has the type name of `fn(i32, i32) -> i32`. This obviously means we'd conflict with another function like `|a: i32, b: i32| a - b`. The solution that #14098 landed on was to always require a name during function registration. The downside with this is that named functions (e.g. `fn add(a: i32, b: i32) -> i32 { a + b }`) had to redundantly provide a name. Additionally, manually constructed `DynamicFunction`s also ran into this ergonomics issue. I don't entirely know how the function registry will be used, but I have a strong suspicion that most of its registrations will either be named functions or manually constructed `DynamicFunction`s, with anonymous functions only being used here and there for quick prototyping or adding small functionality. Why then should the API prioritize the anonymous function use case by always requiring a name during registration? #### Telling Functions Apart Rust doesn't provide a lot of out-of-the-box tools for reflecting functions. One of the biggest hurdles in attempting to solve the problem outlined above would be to somehow tell the different kinds of functions apart. Let's briefly recap on the categories of functions in Rust: | Category | Example | | ------------------ | ----------------------------------------- | | Named function | `fn add(a: i32, b: i32) -> i32 { a + b }` | | Closure | `\|a: i32\| a + captured_variable` | | Anonymous function | `\|a: i32, b: i32\| a + b` | | Function pointer | `fn(i32, i32) -> i32` | My first thought was to try and differentiate these categories based on their size. However, we can see that this doesn't quite work: | Category | `size_of` | | ------------------ | --------- | | Named function | 0 | | Closure | 0+ | | Anonymous function | 0 | | Function pointer | 8 | Not only does this not tell anonymous functions from named ones, but it struggles with pretty much all of them. My second then was to differentiate based on type name: | Category | `type_name` | | ------------------ | ----------------------- | | Named function | `foo::bar::baz` | | Closure | `foo::bar::{{closure}}` | | Anonymous function | `fn() -> String` | | Function pointer | `fn() -> String` | This is much better. While it can't distinguish between function pointers and anonymous functions, this doesn't matter too much since we only care about whether we can _name_ the function. So why didn't we implement this in #14098? #### Relying on `type_name` While this solution was known about while working on #14098, it was left out from that PR due to it being potentially controversial. The [docs](https://doc.rust-lang.org/stable/std/any/fn.type_name.html) for `std::any::type_name` state: > The returned string must not be considered to be a unique identifier of a type as multiple types may map to the same type name. Similarly, there is no guarantee that all parts of a type will appear in the returned string: for example, lifetime specifiers are currently not included. In addition, the output may change between versions of the compiler. So that's it then? We can't use `type_name`? Well, this statement isn't so much a rule as it is a guideline. And Bevy is no stranger to bending the rules to make things work or to improve ergonomics. Remember that before `TypePath`, Bevy's scene system was entirely dependent on `type_name`. Not to mention that `type_name` is being used as a key into both the `TypeRegistry` and the `FunctionRegistry`. Bevy's practices aside, can we reliably use `type_name` for this? My answer would be "yes". Anonymous functions are anonymous. They have no name. There's nothing Rust could do to give them a name apart from generating a random string of characters. But remember that this is a diagnostic tool, it doesn't make sense to obfuscate the type by randomizing the output. So changing it to be anything other than what it is now is very unlikely. The only changes that I could potentially see happening are: 1. Closures replace `{{closure}}` with the name of their variable 2. Lifetimes are included in the output I don't think the first is likely to happen, but if it does then it actually works out in our favor: closures are now named! The second point is probably the likeliest. However, adding lifetimes doesn't mean we can't still rely on `type_name` to determine whether or not a function is named. So we should be okay in this case as well. ## Solution Parse the `type_name` of the function in the `TypedFunction` impl to determine if the function is named or anonymous. This once again makes `FunctionInfo::name` optional. For manual constructions of `DynamicFunction`, `FunctionInfo::named` or ``FunctionInfo::anonymous` can be used. The `FunctionRegistry` API has also been reworked to account for this change. `FunctionRegistry::register` no longer takes a name and instead takes it from the supplied function, returning a `FunctionRegistrationError::MissingName` error if the name is `None`. This also doubles as a replacement for the old `FunctionRegistry::register_dynamic` method, which has been removed. To handle anonymous functions, a `FunctionRegistry::register_with_name` method has been added. This works in the same way `FunctionRegistry::register` used to work before this PR. The overwriting methods have been updated in a similar manner, with modifications to `FunctionRegistry::overwrite_registration`, the removal of `FunctionRegistry::overwrite_registration_dynamic`, and the addition of `FunctionRegistry::overwrite_registration_with_name`. This PR also updates the methods on `App` in a similar way: `App::register_function` no longer requires a name argument and `App::register_function_with_name` has been added to handle anonymous functions (and eventually closures). ## Testing You can run the tests locally by running: ``` cargo test --package bevy_reflect --features functions ``` --- ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. > [!note] > This list is not exhaustive. It only contains some of the most important changes. `FunctionRegistry::register` no longer requires a name string for named functions. Anonymous functions, however, need to be registered using `FunctionRegistry::register_with_name`. ```rust // BEFORE registry .register(std::any::type_name_of_val(&foo), foo)? .register("bar", || println!("Hello world!")); // AFTER registry .register(foo)? .register_with_name("bar", || println!("Hello world!")); ``` `FunctionInfo::name` is now optional. Anonymous functions and closures will now have their name set to `None` by default. Additionally, `FunctionInfo::new` has been renamed to `FunctionInfo::named`. |
||
Gino Valente
|
0caeaa2ca9
|
bevy_reflect: Update serde tests for Set (#14616)
# Objective Support for reflecting set-like types (e.g. `HashSet`) was added in #13014. However, we didn't add any serialization tests to verify that serialization works as expected. ## Solution Update the serde tests. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` |
||
Robert Walter
|
70a18d26e2
|
Glam 0.28 update - adopted (#14613)
Basically it's https://github.com/bevyengine/bevy/pull/13792 with the bumped versions of `encase` and `hexasphere`. --------- Co-authored-by: Robert Swain <robert.swain@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
Gino Valente
|
df61117850
|
bevy_reflect: Function registry (#14098)
# Objective #13152 added support for reflecting functions. Now, we need a way to register those functions such that they may be accessed anywhere within the ECS. ## Solution Added a `FunctionRegistry` type similar to `TypeRegistry`. This allows a function to be registered and retrieved by name. ```rust fn foo() -> i32 { 123 } let mut registry = FunctionRegistry::default(); registry.register("my_function", foo); let function = registry.get_mut("my_function").unwrap(); let value = function.call(ArgList::new()).unwrap().unwrap_owned(); assert_eq!(value.downcast_ref::<i32>(), Some(&123)); ``` Additionally, I added an `AppFunctionRegistry` resource which wraps a `FunctionRegistryArc`. Functions can be registered into this resource using `App::register_function` or by getting a mutable reference to the resource itself. ### Limitations #### `Send + Sync` In order to get this registry to work across threads, it needs to be `Send + Sync`. This means that `DynamicFunction` needs to be `Send + Sync`, which means that its internal function also needs to be `Send + Sync`. In most cases, this won't be an issue because standard Rust functions (the type most likely to be registered) are always `Send + Sync`. Additionally, closures tend to be `Send + Sync` as well, granted they don't capture any `!Send` or `!Sync` variables. This PR adds this `Send + Sync` requirement, but as mentioned above, it hopefully shouldn't be too big of an issue. #### Closures Unfortunately, closures can't be registered yet. This will likely be explored and added in a followup PR. ### Future Work Besides addressing the limitations listed above, another thing we could look into is improving the lookup of registered functions. One aspect is in the performance of hashing strings. The other is in the developer experience of having to call `std::any::type_name_of_val` to get the name of their function (assuming they didn't give it a custom name). ## Testing You can run the tests locally with: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added `FunctionRegistry` - Added `AppFunctionRegistry` (a `Resource` available from `bevy_ecs`) - Added `FunctionRegistryArc` - Added `FunctionRegistrationError` - Added `reflect_functions` feature to `bevy_ecs` and `bevy_app` - `FunctionInfo` is no longer `Default` - `DynamicFunction` now requires its wrapped function be `Send + Sync` ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. `DynamicFunction` (both those created manually and those created with `IntoFunction`), now require `Send + Sync`. All standard Rust functions should meet that requirement. Closures, on the other hand, may not if they capture any `!Send` or `!Sync` variables from its environment. |
||
recatek
|
87b63af864
|
bevy_reflect: Adding support for Atomic values (#14419)
Fixes #14418 Note that this does not add AtomicPtr, which would need its own special casing support, just the regular value types. Also, I was forced to be opinionated about which Ordering to use, so I chose SeqCst as the strictest by default. |
||
Giacomo Stevanato
|
71c5f1e3e4
|
Generate links to definition in source code pages on docs.rs and dev-docs.bevyengine.org (#12965)
# Objective - Fix issue #2611 ## Solution - Add `--generate-link-to-definition` to all the `rustdoc-args` arrays in the `Cargo.toml`s (for docs.rs) - Add `--generate-link-to-definition` to the `RUSTDOCFLAGS` environment variable in the docs workflow (for dev-docs.bevyengine.org) - Document all the workspace crates in the docs workflow (needed because otherwise only the source code of the `bevy` package will be included, making the argument useless) - I think this also fixes #3662, since it fixes the bug on dev-docs.bevyengine.org, while on docs.rs it has been fixed for a while on their side. --- ## Changelog - The source code viewer on docs.rs now includes links to the definitions. |
||
Robert Walter
|
52a2a3b146
|
Dedicated Reflect implementation for Set -like things (#13014)
# Objective I just wanted to inspect `HashSet`s in `bevy-inspector-egui` but I noticed that it didn't work for some reason. A few minutes later I found myself looking into the bevy reflect impls noticing that `HashSet`s have been covered only rudimentary up until now. ## Solution I'm not sure if this is overkill (especially the first bullet), but here's a list of the changes: - created a whole new trait and enum variants for `ReflectRef` and the like called `Set` - mostly oriented myself at the `Map` trait and made the necessary changes until RA was happy - create macro `impl_reflect_for_hashset!` and call it on `std::HashSet` and `hashbrown::HashSet` Extra notes: - no `get_mut` or `get_mut_at` mirroring the `std::HashSet` - `insert[_boxed]` and `remove` return `bool` mirroring `std::HashSet`, additionally that bool is reflect as I thought that would be how we handle things in bevy reflect, but I'm not sure on this - ser/de are handled via `SeqAccess` - I'm not sure about the general deduplication property of this impl of `Set` that is generally expected? I'm also not sure yet if `Map` does provide this. This mainly refers to the `Dynamic[...]` structs - I'm not sure if there are other methods missing from the `trait`, I felt like `contains` or the set-operations (union/diff/...) could've been helpful, but I wanted to get out the bare minimum for feedback first --- ## Changelog ### Added - `Set` trait for `bevy_reflect` ### Changed - `std::collections::HashSet` and `bevy_utils::hashbrown::HashSet` now implement a more complete set of reflect functionalities instead of "just" `reflect_value` - `TypeInfo` contains a new variant `Set` that contains `SetInfo` - `ReflectKind` contains a new variant `Set` - `ReflectRef` contains a new variant `Set` - `ReflectMut` contains a new variant `Set` - `ReflectOwned` contains a new variant `Set` ## Migration Guide - The new `Set` variants on the enums listed in the change section should probably be considered by people working with this level of the lib ### Help wanted! I'm not sure if this change is able to break code. From my understanding it shouldn't since we just add functionality but I'm not sure yet if theres anything missing from my impl that would be normally provided by `impl_reflect_value!` |
||
Coder-Joe458
|
8f5345573c
|
Remove manual --cfg docsrs (#14376)
# Objective - Fixes #14132 ## Solution - Remove the cfg docsrs |
||
Gino Valente
|
af865e76a3
|
bevy_reflect: Improve DynamicFunction ergonomics (#14201)
# Objective Many functions can be converted to `DynamicFunction` using `IntoFunction`. Unfortunately, we are limited by Rust itself and the implementations are far from exhaustive. For example, we can't convert functions with more than 16 arguments. Additionally, we can't handle returns with lifetimes not tied to the lifetime of the first argument. In such cases, users will have to create their `DynamicFunction` manually. Let's take the following function: ```rust fn get(index: usize, list: &Vec<String>) -> &String { &list[index] } ``` This function cannot be converted to a `DynamicFunction` via `IntoFunction` due to the lifetime of the return value being tied to the second argument. Therefore, we need to construct the `DynamicFunction` manually: ```rust DynamicFunction::new( |mut args, info| { let list = args .pop() .unwrap() .take_ref::<Vec<String>>(&info.args()[1])?; let index = args.pop().unwrap().take_owned::<usize>(&info.args()[0])?; Ok(Return::Ref(get(index, list))) }, FunctionInfo::new() .with_name("get") .with_args(vec![ ArgInfo:🆕:<usize>(0).with_name("index"), ArgInfo:🆕:<&Vec<String>>(1).with_name("list"), ]) .with_return_info(ReturnInfo:🆕:<&String>()), ); ``` While still a small and straightforward snippet, there's a decent amount going on here. There's a lot of room for improvements when it comes to ergonomics and readability. The goal of this PR is to address those issues. ## Solution Improve the ergonomics and readability of manually created `DynamicFunction`s. Some of the major changes: 1. Removed the need for `&ArgInfo` when reifying arguments (i.e. the `&info.args()[1]` calls) 2. Added additional `pop` methods on `ArgList` to handle both popping and casting 3. Added `take` methods on `ArgList` for taking the arguments out in order 4. Removed the need for `&FunctionInfo` in the internal closure (Change 1 made it no longer necessary) 5. Added methods to automatically handle generating `ArgInfo` and `ReturnInfo` With all these changes in place, we get something a lot nicer to both write and look at: ```rust DynamicFunction::new( |mut args| { let index = args.take::<usize>()?; let list = args.take::<&Vec<String>>()?; Ok(Return::Ref(get(index, list))) }, FunctionInfo::new() .with_name("get") .with_arg::<usize>("index") .with_arg::<&Vec<String>>("list") .with_return::<&String>(), ); ``` Alternatively, to rely on type inference for taking arguments, you could do: ```rust DynamicFunction::new( |mut args| { let index = args.take_owned()?; let list = args.take_ref()?; Ok(Return::Ref(get(index, list))) }, FunctionInfo::new() .with_name("get") .with_arg::<usize>("index") .with_arg::<&Vec<String>>("list") .with_return::<&String>(), ); ``` ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Removed `&ArgInfo` argument from `FromArg::from_arg` trait method - Removed `&ArgInfo` argument from `Arg::take_***` methods - Added `ArgValue` - `Arg` is now a struct containing an `ArgValue` and an argument `index` - `Arg::take_***` methods now require `T` is also `TypePath` - Added `Arg::new`, `Arg::index`, `Arg::value`, `Arg::take_value`, and `Arg::take` methods - Replaced `ArgId` in `ArgError` with just the argument `index` - Added `ArgError::EmptyArgList` - Renamed `ArgList::push` to `ArgList::push_arg` - Added `ArgList::pop_arg`, `ArgList::pop_owned`, `ArgList::pop_ref`, and `ArgList::pop_mut` - Added `ArgList::take_arg`, `ArgList::take_owned`, `ArgList::take_ref`, `ArgList::take_mut`, and `ArgList::take` - `ArgList::pop` is now generic - Renamed `FunctionError::InvalidArgCount` to `FunctionError::ArgCountMismatch` - The closure given to `DynamicFunction::new` no longer has a `&FunctionInfo` argument - Added `FunctionInfo::with_arg` - Added `FunctionInfo::with_return` ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. * The `FromArg::from_arg` trait method and the `Arg::take_***` methods no longer take a `&ArgInfo` argument. * What used to be `Arg` is now `ArgValue`. `Arg` is now a struct which contains an `ArgValue`. * `Arg::take_***` methods now require `T` is also `TypePath` * Instances of `id: ArgId` in `ArgError` have been replaced with `index: usize` * `ArgList::push` is now `ArgList::push_arg`. It also takes the new `ArgValue` type. * `ArgList::pop` has become `ArgList::pop_arg` and now returns `ArgValue`. `Arg::pop` now takes a generic type and downcasts to that type. It's recommended to use `ArgList::take` and friends instead since they allow removing the arguments from the list in the order they were pushed (rather than reverse order). * `FunctionError::InvalidArgCount` is now `FunctionError::ArgCountMismatch` * The closure given to `DynamicFunction::new` no longer has a `&FunctionInfo` argument. This argument can be removed. |
||
Gino Valente
|
1042f09c2e
|
bevy_reflect: Add DynamicClosure and DynamicClosureMut (#14141)
# Objective As mentioned in [this](https://github.com/bevyengine/bevy/pull/13152#issuecomment-2198387297) comment, creating a function registry (see #14098) is a bit difficult due to the requirements of `DynamicFunction`. Internally, a `DynamicFunction` contains a `Box<dyn FnMut>` (the function that reifies reflected arguments and calls the actual function), which requires `&mut self` in order to be called. This means that users would require a mutable reference to the function registry for it to be useful— which isn't great. And they can't clone the `DynamicFunction` either because cloning an `FnMut` isn't really feasible (wrapping it in an `Arc` would allow it to be cloned but we wouldn't be able to call the clone since we need a mutable reference to the `FnMut`, which we can't get with multiple `Arc`s still alive, requiring us to also slap in a `Mutex`, which adds additional overhead). And we don't want to just replace the `dyn FnMut` with `dyn Fn` as that would prevent reflecting closures that mutate their environment. Instead, we need to introduce a new type to split the requirements of `DynamicFunction`. ## Solution Introduce new types for representing closures. Specifically, this PR introduces `DynamicClosure` and `DynamicClosureMut`. Similar to how `IntoFunction` exists for `DynamicFunction`, two new traits were introduced: `IntoClosure` and `IntoClosureMut`. Now `DynamicFunction` stores a `dyn Fn` with a `'static` lifetime. `DynamicClosure` also uses a `dyn Fn` but has a lifetime, `'env`, tied to its environment. `DynamicClosureMut` is most like the old `DynamicFunction`, keeping the `dyn FnMut` and also typing its lifetime, `'env`, to the environment Here are some comparison tables: | | `DynamicFunction` | `DynamicClosure` | `DynamicClosureMut` | | - | ----------------- | ---------------- | ------------------- | | Callable with `&self` | ✅ | ✅ | ❌ | | Callable with `&mut self` | ✅ | ✅ | ✅ | | Allows for non-`'static` lifetimes | ❌ | ✅ | ✅ | | | `IntoFunction` | `IntoClosure` | `IntoClosureMut` | | - | -------------- | ------------- | ---------------- | | Convert `fn` functions | ✅ | ✅ | ✅ | | Convert `fn` methods | ✅ | ✅ | ✅ | | Convert anonymous functions | ✅ | ✅ | ✅ | | Convert closures that capture immutable references | ❌ | ✅ | ✅ | | Convert closures that capture mutable references | ❌ | ❌ | ✅ | | Convert closures that capture owned values | ❌[^1] | ✅ | ✅ | [^1]: Due to limitations in Rust, `IntoFunction` can't be implemented for just functions (unless we forced users to manually coerce them to function pointers first). So closures that meet the trait requirements _can technically_ be converted into a `DynamicFunction` as well. To both future-proof and reduce confusion, though, we'll just pretend like this isn't a thing. ```rust let mut list: Vec<i32> = vec![1, 2, 3]; // `replace` is a closure that captures a mutable reference to `list` let mut replace = |index: usize, value: i32| -> i32 { let old_value = list[index]; list[index] = value; old_value }; // Convert the closure into a dynamic closure using `IntoClosureMut::into_closure_mut` let mut func: DynamicClosureMut = replace.into_closure_mut(); // Dynamically call the closure: let args = ArgList::default().push_owned(1_usize).push_owned(-2_i32); let value = func.call_once(args).unwrap().unwrap_owned(); // Check the result: assert_eq!(value.take::<i32>().unwrap(), 2); assert_eq!(list, vec![1, -2, 3]); ``` ### `ReflectFn`/`ReflectFnMut` To make extending the function reflection system easier (the blanket impls for `IntoFunction`, `IntoClosure`, and `IntoClosureMut` are all incredibly short), this PR generalizes callables with two new traits: `ReflectFn` and `ReflectFnMut`. These traits mimic `Fn` and `FnMut` but allow for being called via reflection. In fact, their blanket implementations are identical save for `ReflectFn` being implemented over `Fn` types and `ReflectFnMut` being implemented over `FnMut` types. And just as `Fn` is a subtrait of `FnMut`, `ReflectFn` is a subtrait of `ReflectFnMut`. So anywhere that expects a `ReflectFnMut` can also be given a `ReflectFn`. To reiterate, these traits aren't 100% necessary. They were added in purely for extensibility. If we decide to split things up differently or add new traits/types in the future, then those changes should be much simpler to implement. ### `TypedFunction` Because of the split into `ReflectFn` and `ReflectFnMut`, we needed a new way to access the function type information. This PR moves that concept over into `TypedFunction`. Much like `Typed`, this provides a way to access a function's `FunctionInfo`. By splitting this trait out, it helps to ensure the other traits are focused on a single responsibility. ### Internal Macros The original function PR (#13152) implemented `IntoFunction` using a macro which was passed into an `all_tuples!` macro invocation. Because we needed the same functionality for these new traits, this PR has copy+pasted that code for `ReflectFn`, `ReflectFnMut`, and `TypedFunction`— albeit with some differences between them. Originally, I was going to try and macro-ify the impls and where clauses such that we wouldn't have to straight up duplicate a lot of this logic. However, aside from being more complex in general, autocomplete just does not play nice with such heavily nested macros (tried in both RustRover and VSCode). And both of those problems told me that it just wasn't worth it: we need to ensure the crate is easily maintainable, even at the cost of duplicating code. So instead, I made sure to simplify the macro code by removing all fully-qualified syntax and cutting the where clauses down to the bare essentials, which helps to clean up a lot of the visual noise. I also tried my best to document the macro logic in certain areas (I may even add a bit more) to help with maintainability for future devs. ### Documentation Documentation for this module was a bit difficult for me. So many of these traits and types are very interconnected. And each trait/type has subtle differences that make documenting it in a single place, like at the module level, difficult to do cleanly. Describing the valid signatures is also challenging to do well. Hopefully what I have here is okay. I think I did an okay job, but let me know if there any thoughts on ways to improve it. We can also move such a task to a followup PR for more focused discussion. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added `DynamicClosure` struct - Added `DynamicClosureMut` struct - Added `IntoClosure` trait - Added `IntoClosureMut` trait - Added `ReflectFn` trait - Added `ReflectFnMut` trait - Added `TypedFunction` trait - `IntoFunction` now only works for standard Rust functions - `IntoFunction` no longer takes a lifetime parameter - `DynamicFunction::call` now only requires `&self` - Removed `DynamicFunction::call_once` - Changed the `IntoReturn::into_return` signature to include a where clause ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. ### `IntoClosure` `IntoFunction` now only works for standard Rust functions. Calling `IntoFunction::into_function` on a closure that captures references to its environment (either mutable or immutable), will no longer compile. Instead, you will need to use either `IntoClosure::into_closure` to create a `DynamicClosure` or `IntoClosureMut::into_closure_mut` to create a `DynamicClosureMut`, depending on your needs: ```rust let punct = String::from("!"); let print = |value: String| { println!("{value}{punct}"); }; // BEFORE let func: DynamicFunction = print.into_function(); // AFTER let func: DynamicClosure = print.into_closure(); ``` ### `IntoFunction` lifetime Additionally, `IntoFunction` no longer takes a lifetime parameter as it always expects a `'static` lifetime. Usages will need to remove any lifetime parameters: ```rust // BEFORE fn execute<'env, F: IntoFunction<'env, Marker>, Marker>(f: F) {/* ... */} // AFTER fn execute<F: IntoFunction<Marker>, Marker>(f: F) {/* ... */} ``` ### `IntoReturn` `IntoReturn::into_return` now has a where clause. Any manual implementors will need to add this where clause to their implementation. |
||
Brezak
|
6522795889
|
Specify test group names in github summary for compile fail tests (#14330)
# Objective The github action summary titles every compile test group as `compile_fail_utils`. ![image](https://github.com/user-attachments/assets/9d00a113-6772-430c-8da9-bffe6a60a8f8) ## Solution Manually specify group names for compile fail tests. ## Testing - Wait for compile fail tests to run. - Observe the generated summary. |
||
SpecificProtagonist
|
ab255aefc6
|
Implement FromIterator/IntoIterator for dynamic types (#14250)
# Objective Implement FromIterator/IntoIterator for dynamic types where missing Note: - can't impl `IntoIterator` for `&Array` & co because of orphan rules - `into_iter().collect()` is a no-op for `Vec`s because of specialization --- ## Migration Guide - Change `DynamicArray::from_vec` to `DynamicArray::from_iter` |
||
Gino Valente
|
aa241672e1
|
bevy_reflect: Nested TypeInfo getters (#13321)
# Objective Right now, `TypeInfo` can be accessed directly from a type using either `Typed::type_info` or `Reflect::get_represented_type_info`. However, once that `TypeInfo` is accessed, any nested types must be accessed via the `TypeRegistry`. ```rust #[derive(Reflect)] struct Foo { bar: usize } let registry = TypeRegistry::default(); let TypeInfo::Struct(type_info) = Foo::type_info() else { panic!("expected struct info"); }; let field = type_info.field("bar").unwrap(); let field_info = registry.get_type_info(field.type_id()).unwrap(); assert!(field_info.is::<usize>());; ``` ## Solution Enable nested types within a `TypeInfo` to be retrieved directly. ```rust #[derive(Reflect)] struct Foo { bar: usize } let TypeInfo::Struct(type_info) = Foo::type_info() else { panic!("expected struct info"); }; let field = type_info.field("bar").unwrap(); let field_info = field.type_info().unwrap(); assert!(field_info.is::<usize>());; ``` The particular implementation was chosen for two reasons. Firstly, we can't just store `TypeInfo` inside another `TypeInfo` directly. This is because some types are recursive and would result in a deadlock when trying to create the `TypeInfo` (i.e. it has to create the `TypeInfo` before it can use it, but it also needs the `TypeInfo` before it can create it). Therefore, we must instead store the function so it can be retrieved lazily. I had considered also using a `OnceLock` or something to lazily cache the info, but I figured we can look into optimizations later. The API should remain the same with or without the `OnceLock`. Secondly, a new wrapper trait had to be introduced: `MaybeTyped`. Like `RegisterForReflection`, this trait is `#[doc(hidden)]` and only exists so that we can properly handle dynamic type fields without requiring them to implement `Typed`. We don't want dynamic types to implement `Typed` due to the fact that it would make the return type `Option<&'static TypeInfo>` for all types even though only the dynamic types ever need to return `None` (see #6971 for details). Users should never have to interact with this trait as it has a blanket impl for all `Typed` types. And `Typed` is automatically implemented when deriving `Reflect` (as it is required). The one downside is we do need to return `Option<&'static TypeInfo>` from all these new methods so that we can handle the dynamic cases. If we didn't have to, we'd be able to get rid of the `Option` entirely. But I think that's an okay tradeoff for this one part of the API, and keeps the other APIs intact. ## Testing This PR contains tests to verify everything works as expected. You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog ### Public Changes - Added `ArrayInfo::item_info` method - Added `NamedField::type_info` method - Added `UnnamedField::type_info` method - Added `ListInfo::item_info` method - Added `MapInfo::key_info` method - Added `MapInfo::value_info` method - All active fields now have a `Typed` bound (remember that this is automatically satisfied for all types that derive `Reflect`) ### Internal Changes - Added `MaybeTyped` trait ## Migration Guide All active fields for reflected types (including lists, maps, tuples, etc.), must implement `Typed`. For the majority of users this won't have any visible impact. However, users implementing `Reflect` manually may need to update their types to implement `Typed` if they weren't already. Additionally, custom dynamic types will need to implement the new hidden `MaybeTyped` trait. |
||
Gino Valente
|
e512cb602c
|
bevy_reflect: TypeInfo casting methods (#13320)
# Objective There are times when we might know the type of a `TypeInfo` ahead of time. Or we may have already checked it one way or another. In such cases, it's a bit cumbersome to have to pattern match every time we want to access the nested info: ```rust if let TypeInfo::List(info) = <Vec<i32>>::type_info() { // ... } else { panic!("expected list info"); } ``` Ideally, there would be a way to simply perform the cast down to `ListInfo` since we already know it will succeed. Or even if we don't, perhaps we just want a cleaner way of exiting a function early (i.e. with the `?` operator). ## Solution Taking a bit from [`mirror-mirror`](https://docs.rs/mirror-mirror/latest/mirror_mirror/struct.TypeDescriptor.html#implementations), `TypeInfo` now has methods for attempting a cast into the variant's info type. ```rust let info = <Vec<i32>>::type_info().as_list().unwrap(); // ... ``` These new conversion methods return a `Result` where the error type is a new `TypeInfoError` enum. A `Result` was chosen as the return type over `Option` because if we do choose to `unwrap` it, the error message will give us some indication of what went wrong. In other words, it can truly replace those instances where we were panicking in the `else` case. ### Open Questions 1. Should the error types instead be a struct? I chose an enum for future-proofing, but right now it only has one error state. Alternatively, we could make it a reflect-wide casting error so it could be used for similar methods on `ReflectRef` and friends. 2. I was going to do it in a separate PR but should I just go ahead and add similar methods to `ReflectRef`, `ReflectMut`, and `ReflectOwned`? 🤔 3. Should we name these `try_as_***` instead of `as_***` since they return a `Result`? ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog ### Added - `TypeInfoError` enum - `TypeInfo::kind` method - `TypeInfo::as_struct` method - `TypeInfo::as_tuple_struct` method - `TypeInfo::as_tuple` method - `TypeInfo::as_list` method - `TypeInfo::as_array` method - `TypeInfo::as_map` method - `TypeInfo::as_enum` method - `TypeInfo::as_value` method - `VariantInfoError` enum - `VariantInfo::variant_type` method - `VariantInfo::as_unit_variant` method - `VariantInfo::as_tuple_variant` method - `VariantInfo::as_struct_variant` method |
||
Gino Valente
|
99c9218b56
|
bevy_reflect: Feature-gate function reflection (#14174)
# Objective Function reflection requires a lot of macro code generation in the form of several `all_tuples!` invocations, as well as impls generated in the `Reflect` derive macro. Seeing as function reflection is currently a bit more niche, it makes sense to gate it all behind a feature. ## Solution Add a `functions` feature to `bevy_reflect`, which can be enabled in Bevy using the `reflect_functions` feature. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` That should ensure that everything still works with the feature disabled. To test with the feature on, you can run: ``` cargo test --package bevy_reflect --features functions ``` --- ## Changelog - Moved function reflection behind a Cargo feature (`bevy/reflect_functions` and `bevy_reflect/functions`) - Add `IntoFunction` export in `bevy_reflect::prelude` ## Internal Migration Guide > [!important] > Function reflection was introduced as part of the 0.15 dev cycle. This migration guide was written for developers relying on `main` during this cycle, and is not a breaking change coming from 0.14. Function reflection is now gated behind a feature. To use function reflection, enable the feature: - If using `bevy_reflect` directly, enable the `functions` feature - If using `bevy`, enable the `reflect_functions` feature |
||
Giacomo Stevanato
|
d7080369a7
|
Fix intra-doc links and make CI test them (#14076)
# Objective - Bevy currently has lot of invalid intra-doc links, let's fix them! - Also make CI test them, to avoid future regressions. - Helps with #1983 (but doesn't fix it, as there could still be explicit links to docs.rs that are broken) ## Solution - Make `cargo r -p ci -- doc-check` check fail on warnings (could also be changed to just some specific lints) - Manually fix all the warnings (note that in some cases it was unclear to me what the fix should have been, I'll try to highlight them in a self-review) |
||
Rob Parrett
|
3594c4f2f5
|
Fix doc list indentation (#14225)
# Objective Fixes #14221 ## Solution Add indentation as suggested. ## Testing Confirmed that - This makes Clippy happy with rust beta - Built docs visually look the same before/after |
||
github-actions[bot]
|
8df10d2713
|
Bump Version after Release (#14219)
Bump version after release This PR has been auto-generated Co-authored-by: Bevy Auto Releaser <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: François Mockers <mockersf@gmail.com> |
||
Gino Valente
|
09d86bfb96
|
bevy_reflect: Re-enable reflection compile fail tests (#14165)
# Objective Looks like I accidentally disabled the reflection compile fail tests in #13152. These should be re-enabled. ## Solution Re-enable reflection compile fail tests. ## Testing CI should pass. You can also test locally by navigating to `crates/bevy_reflect/compile_fail/` and running: ``` cargo test --target-dir ../../../target ``` |
||
Lura
|
856b39d821
|
Apply Clippy lints regarding lazy evaluation and closures (#14015)
# Objective - Lazily evaluate [default](https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_or_default)~~/[or](https://rust-lang.github.io/rust-clippy/master/index.html#/or_fun_call)~~ values where it makes sense - ~~`unwrap_or(foo())` -> `unwrap_or_else(|| foo())`~~ - `unwrap_or(Default::default())` -> `unwrap_or_default()` - etc. - Avoid creating [redundant closures](https://rust-lang.github.io/rust-clippy/master/index.html#/redundant_closure), even for [method calls](https://rust-lang.github.io/rust-clippy/master/index.html#/redundant_closure_for_method_calls) - `map(|something| something.into())` -> `map(Into:into)` ## Solution - Apply Clippy lints: - ~~[or_fun_call](https://rust-lang.github.io/rust-clippy/master/index.html#/or_fun_call)~~ - [unwrap_or_default](https://rust-lang.github.io/rust-clippy/master/index.html#/unwrap_or_default) - [redundant_closure_for_method_calls](https://rust-lang.github.io/rust-clippy/master/index.html#/redundant_closure_for_method_calls) ([redundant closures](https://rust-lang.github.io/rust-clippy/master/index.html#/redundant_closure) is already enabled) ## Testing - Tested on Windows 11 (`stable-x86_64-pc-windows-gnu`, 1.79.0) - Bevy compiles without errors or warnings and examples seem to work as intended - `cargo clippy` ✅ - `cargo run -p ci -- compile` ✅ --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
Gino Valente
|
276dd04001
|
bevy_reflect: Function reflection (#13152)
# Objective
We're able to reflect types sooooooo... why not functions?
The goal of this PR is to make functions callable within a dynamic
context, where type information is not readily available at compile
time.
For example, if we have a function:
```rust
fn add(left: i32, right: i32) -> i32 {
left + right
}
```
And two `Reflect` values we've already validated are `i32` types:
```rust
let left: Box<dyn Reflect> = Box::new(2_i32);
let right: Box<dyn Reflect> = Box::new(2_i32);
```
We should be able to call `add` with these values:
```rust
// ?????
let result: Box<dyn Reflect> = add.call_dynamic(left, right);
```
And ideally this wouldn't just work for functions, but methods and
closures too!
Right now, users have two options:
1. Manually parse the reflected data and call the function themselves
2. Rely on registered type data to handle the conversions for them
For a small function like `add`, this isn't too bad. But what about for
more complex functions? What about for many functions?
At worst, this process is error-prone. At best, it's simply tedious.
And this is assuming we know the function at compile time. What if we
want to accept a function dynamically and call it with our own
arguments?
It would be much nicer if `bevy_reflect` could alleviate some of the
problems here.
## Solution
Added function reflection!
This adds a `DynamicFunction` type to wrap a function dynamically. This
can be called with an `ArgList`, which is a dynamic list of
`Reflect`-containing `Arg` arguments. It returns a `FunctionResult`
which indicates whether or not the function call succeeded, returning a
`Reflect`-containing `Return` type if it did succeed.
Many functions can be converted into this `DynamicFunction` type thanks
to the `IntoFunction` trait.
Taking our previous `add` example, this might look something like
(explicit types added for readability):
```rust
fn add(left: i32, right: i32) -> i32 {
left + right
}
let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```
And it also works on closures:
```rust
let add = |left: i32, right: i32| left + right;
let mut function: DynamicFunction = add.into_function();
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
let result: Return = function.call(args).unwrap();
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```
As well as methods:
```rust
#[derive(Reflect)]
struct Foo(i32);
impl Foo {
fn add(&mut self, value: i32) {
self.0 += value;
}
}
let mut foo = Foo(2);
let mut function: DynamicFunction = Foo::add.into_function();
let args: ArgList = ArgList::new().push_mut(&mut foo).push_owned(2_i32);
function.call(args).unwrap();
assert_eq!(foo.0, 4);
```
### Limitations
While this does cover many functions, it is far from a perfect system
and has quite a few limitations. Here are a few of the limitations when
using `IntoFunction`:
1. The lifetime of the return value is only tied to the lifetime of the
first argument (useful for methods). This means you can't have a
function like `(a: i32, b: &i32) -> &i32` without creating the
`DynamicFunction` manually.
2. Only 15 arguments are currently supported. If the first argument is a
(mutable) reference, this number increases to 16.
3. Manual implementations of `Reflect` will need to implement the new
`FromArg`, `GetOwnership`, and `IntoReturn` traits in order to be used
as arguments/return types.
And some limitations of `DynamicFunction` itself:
1. All arguments share the same lifetime, or rather, they will shrink to
the shortest lifetime.
2. Closures that capture their environment may need to have their
`DynamicFunction` dropped before accessing those variables again (there
is a `DynamicFunction::call_once` to make this a bit easier)
3. All arguments and return types must implement `Reflect`. While not a
big surprise coming from `bevy_reflect`, this implementation could
actually still work by swapping `Reflect` out with `Any`. Of course,
that makes working with the arguments and return values a bit harder.
4. Generic functions are not supported (unless they have been manually
monomorphized)
And general, reflection gotchas:
1. `&str` does not implement `Reflect`. Rather, `&'static str`
implements `Reflect` (the same is true for `&Path` and similar types).
This means that `&'static str` is considered an "owned" value for the
sake of generating arguments. Additionally, arguments and return types
containing `&str` will assume it's `&'static str`, which is almost never
the desired behavior. In these cases, the only solution (I believe) is
to use `&String` instead.
### Followup Work
This PR is the first of two PRs I intend to work on. The second PR will
aim to integrate this new function reflection system into the existing
reflection traits and `TypeInfo`. The goal would be to register and call
a reflected type's methods dynamically.
I chose not to do that in this PR since the diff is already quite large.
I also want the discussion for both PRs to be focused on their own
implementation.
Another followup I'd like to do is investigate allowing common container
types as a return type, such as `Option<&[mut] T>` and `Result<&[mut] T,
E>`. This would allow even more functions to opt into this system. I
chose to not include it in this one, though, for the same reasoning as
previously mentioned.
### Alternatives
One alternative I had considered was adding a macro to convert any
function into a reflection-based counterpart. The idea would be that a
struct that wraps the function would be created and users could specify
which arguments and return values should be `Reflect`. It could then be
called via a new `Function` trait.
I think that could still work, but it will be a fair bit more involved,
requiring some slightly more complex parsing. And it of course is a bit
more work for the user, since they need to create the type via macro
invocation.
It also makes registering these functions onto a type a bit more
complicated (depending on how it's implemented).
For now, I think this is a fairly simple, yet powerful solution that
provides the least amount of friction for users.
---
## Showcase
Bevy now adds support for storing and calling functions dynamically
using reflection!
```rust
// 1. Take a standard Rust function
fn add(left: i32, right: i32) -> i32 {
left + right
}
// 2. Convert it into a type-erased `DynamicFunction` using the `IntoFunction` trait
let mut function: DynamicFunction = add.into_function();
// 3. Define your arguments from reflected values
let args: ArgList = ArgList::new().push_owned(2_i32).push_owned(2_i32);
// 4. Call the function with your arguments
let result: Return = function.call(args).unwrap();
// 5. Extract the return value
let value: Box<dyn Reflect> = result.unwrap_owned();
assert_eq!(value.take::<i32>().unwrap(), 4);
```
## Changelog
#### TL;DR
- Added support for function reflection
- Added a new `Function Reflection` example:
|
||
Gino Valente
|
53910e07ae
|
bevy_reflect: Improve reflection serialization error messages (#13867)
# Objective The error messages that appear when a value cannot be serialized or deserialized via reflection could be slightly improved. When one of these operations fails, some users are confused about how to resolve the issue. I've spoken with a few who didn't know they could register `ReflectSerialize` themselves. We should try to clarify this to some degree in the error messages. ## Solution Add some more detail to the error messages. For example, replacing this: ``` Type 'core::ops::RangeInclusive<f32>' did not register ReflectSerialize ``` with this: ``` Type `core::ops::RangeInclusive<f32>` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` ``` I also added a separate error message if the type was not registered in the type registry at all: ``` Type `core::ops::RangeInclusive<f32>` is not registered in the type registry ``` ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Changelog - Added error message for missing type registration when serializing reflect data - Changed error message for missing `ReflectSerialize` registration when serializing reflect data - Changed error message for missing `ReflectDeserialize` registration when deserializing reflect data |
||
Jan Hohenheim
|
6273227e09
|
Fix lints introduced in Rust beta 1.80 (#13899)
Resolves #13895 Mostly just involves being more explicit about which parts of the docs belong to a list and which begin a new paragraph. - found a few docs that were malformed because of exactly this, so I fixed that by introducing a paragraph - added indentation to nearly all multiline lists - fixed a few minor typos - added `#[allow(dead_code)]` to types that are needed to test annotations but are never constructed ([here](https://github.com/bevyengine/bevy/pull/13899/files#diff-b02b63604e569c8577c491e7a2030d456886d8f6716eeccd46b11df8aac75dafR1514) and [here](https://github.com/bevyengine/bevy/pull/13899/files#diff-b02b63604e569c8577c491e7a2030d456886d8f6716eeccd46b11df8aac75dafR1523)) - verified that `cargo +beta run -p ci -- lints` passes - verified that `cargo +beta run -p ci -- test` passes |
||
Wuketuke
|
d45bcfd043
|
improved the error message by insert_boxed (issue #13646) (again) (#13706)
previously I worked on fixing issue #13646, back when the error message did not include the type at all. But that error message had room for improvement, so I included the feedback of @alice-i-cecile and @MrGVSV. The error message will now read `the given key (of type bevy_reflect::tests::Foo) does not support hashing` or 'the given key (of type bevy_reflect::DynamicStruct) does not support hashing' in case of a dynamic struct that represents a hashable struct i also added a new unit test for this new behaviour (`reflect_map_no_hash_dynamic`). Fixes #13646 (again) --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> |
||
Wuketuke
|
2eb9d5cc38
|
hashing error in bevy_reflect now includes the type (bevyengine#13646) (#13691)
# Objective If you try to add an object to the hashmap that is not capable of hashing, the program panics. For easier debugging, the type for that object should be included in the error message. Fixes #13646. ## Solution initially i tried calling std::any::type_name_of_val. this had the problem that it would print something like dyn Box<dyn Reflect>, not helpful. But since these objects all implement Reflect, i used Reflect::type_path() instead. Previously, the error message was part of a constant called HASH_ERROR. i changed that to a macro called hash_error to print the type of that object more easily ## Testing i adapted the unit test reflect_map_no_hash to expect the type in that panic aswell since this is my first contribution, please let me know if i have done everything properly |
||
Alice Cecile
|
ec7b3490f6
|
Add on_unimplemented Diagnostics to Most Public Traits (#13347) (#13662)
# Objective - #13414 did not have the intended effect. - #13404 is still blocked ## Solution - Re-adds #13347. Co-authored-by: Zachary Harrold <zac@harrold.com.au> Co-authored-by: Jamie Ridding <Themayu@users.noreply.github.com> Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> |
||
Olle Lukowski
|
8c7f73ab81
|
Move bevy_math Reflect impls (#13520)
# Objective Fixes #13456 ## Solution Moved `bevy_math`'s `Reflect` impls from `bevy_reflect` to `bevy_math`. ### Quick note I accidentally used the same commit message while resolving a merge conflict (first time I had to resolve a conflict). Sorry about that. |
||
Salvador Carvalhinho
|
7d843e0c08
|
Implement Rhombus 2D primitive. (#13501)
# Objective - Create a new 2D primitive, Rhombus, also knows as "Diamond Shape" - Simplify the creation and handling of isometric projections - Extend Bevy's arsenal of 2D primitives ## Testing - New unit tests created in bevy_math/ primitives and bev_math/ bounding - Tested translations, rotations, wireframe, bounding sphere, aabb and creation parameters --------- Co-authored-by: Luís Figueiredo <luispcfigueiredo@tecnico.ulisboa.pt> |
||
Gino Valente
|
faf003fc9d
|
bevy_reflect: enum_utility cleanup (#13424)
# Objective The `enum_utility` module contains the `get_variant_constructors` function, which is used to generate token streams for constructing enums. It's used for the `FromReflect::from_reflect` implementation and the `Reflect::try_apply` implementation. Due to the complexity of enums, this function is understandably a little messy and difficult to extend. ## Solution Clean up the `enum_utility` module. Now "clean" is a bit subjective. I believe my solution is "cleaner" in that the logic to generate the tokens are strictly coupled with the intended usage. Because of this, `try_apply` is also no longer strictly coupled with `from_reflect`. This makes it easier to extend with new functionality, which is something I'm doing in a future unrelated PR that I have based off this one. ## Testing There shouldn't be any testing required other than ensuring that the project still builds and that CI passes. |
||
Gino Valente
|
5db52663b3
|
bevy_reflect: Custom attributes (#11659)
# Objective As work on the editor starts to ramp up, it might be nice to start allowing types to specify custom attributes. These can be used to provide certain functionality to fields, such as ranges or controlling how data is displayed. A good example of this can be seen in [`bevy-inspector-egui`](https://github.com/jakobhellermann/bevy-inspector-egui) with its [`InspectorOptions`](https://docs.rs/bevy-inspector-egui/0.22.1/bevy_inspector_egui/struct.InspectorOptions.html): ```rust #[derive(Reflect, Default, InspectorOptions)] #[reflect(InspectorOptions)] struct Slider { #[inspector(min = 0.0, max = 1.0)] value: f32, } ``` Normally, as demonstrated in the example above, these attributes are handled by a derive macro and stored in a corresponding `TypeData` struct (i.e. `ReflectInspectorOptions`). Ideally, we would have a good way of defining this directly via reflection so that users don't need to create and manage a whole proc macro just to allow these sorts of attributes. And note that this doesn't have to just be for inspectors and editors. It can be used for things done purely on the code side of things. ## Solution Create a new method for storing attributes on fields via the `Reflect` derive. These custom attributes are stored in type info (e.g. `NamedField`, `StructInfo`, etc.). ```rust #[derive(Reflect)] struct Slider { #[reflect(@0.0..=1.0)] value: f64, } let TypeInfo::Struct(info) = Slider::type_info() else { panic!("expected struct info"); }; let field = info.field("value").unwrap(); let range = field.get_attribute::<RangeInclusive<f64>>().unwrap(); assert_eq!(*range, 0.0..=1.0); ``` ## TODO - [x] ~~Bikeshed syntax~~ Went with a type-based approach, prefixed by `@` for ease of parsing and flexibility - [x] Add support for custom struct/tuple struct field attributes - [x] Add support for custom enum variant field attributes - [x] ~~Add support for custom enum variant attributes (maybe?)~~ ~~Will require a larger refactor. Can be saved for a future PR if we really want it.~~ Actually, we apparently still have support for variant attributes despite not using them, so it was pretty easy to add lol. - [x] Add support for custom container attributes - [x] Allow custom attributes to store any reflectable value (not just `Lit`) - [x] ~~Store attributes in registry~~ This PR used to store these in attributes in the registry, however, it has since switched over to storing them in type info - [x] Add example ## Bikeshedding > [!note] > This section was made for the old method of handling custom attributes, which stored them by name (i.e. `some_attribute = 123`). The PR has shifted away from that, to a more type-safe approach. > > This section has been left for reference. There are a number of ways we can syntactically handle custom attributes. Feel free to leave a comment on your preferred one! Ideally we want one that is clear, readable, and concise since these will potentially see _a lot_ of use. Below is a small, non-exhaustive list of them. Note that the `skip_serializing` reflection attribute is added to demonstrate how each case plays with existing reflection attributes. <details> <summary>List</summary> ##### 1. `@(name = value)` > The `@` was chosen to make them stand out from other attributes and because the "at" symbol is a subtle pneumonic for "attribute". Of course, other symbols could be used (e.g. `$`, `#`, etc.). ```rust #[derive(Reflect)] struct Slider { #[reflect(@(min = 0.0, max = 1.0), skip_serializing)] #[[reflect(@(bevy_editor::hint = "Range: 0.0 to 1.0"))] value: f32, } ``` ##### 2. `@name = value` > This is my personal favorite. ```rust #[derive(Reflect)] struct Slider { #[reflect(@min = 0.0, @max = 1.0, skip_serializing)] #[[reflect(@bevy_editor::hint = "Range: 0.0 to 1.0")] value: f32, } ``` ##### 3. `custom_attr(name = value)` > `custom_attr` can be anything. Other possibilities include `with` or `tag`. ```rust #[derive(Reflect)] struct Slider { #[reflect(custom_attr(min = 0.0, max = 1.0), skip_serializing)] #[[reflect(custom_attr(bevy_editor::hint = "Range: 0.0 to 1.0"))] value: f32, } ``` ##### 4. `reflect_attr(name = value)` ```rust #[derive(Reflect)] struct Slider { #[reflect(skip_serializing)] #[reflect_attr(min = 0.0, max = 1.0)] #[[reflect_attr(bevy_editor::hint = "Range: 0.0 to 1.0")] value: f32, } ``` </details> --- ## Changelog - Added support for custom attributes on reflected types (i.e. `#[reflect(@Foo::new("bar")]`) |
||
Alice Cecile
|
ee6dfd35c9
|
Revert "Add on_unimplemented Diagnostics to Most Public Traits" (#13413)
# Objective - Rust 1.78 breaks all Android support, see https://github.com/bevyengine/bevy/issues/13331 - We should not bump the MSRV to 1.78 until that's resolved in #13366. ## Solution - Temporarily revert https://github.com/bevyengine/bevy/pull/13347 Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com> |
||
Zachary Harrold
|
11f0a2dcde
|
Add on_unimplemented Diagnostics to Most Public Traits (#13347)
# Objective - Fixes #12377 ## Solution Added simple `#[diagnostic::on_unimplemented(...)]` attributes to some critical public traits providing a more approachable initial error message. Where appropriate, a `note` is added indicating that a `derive` macro is available. ## Examples <details> <summary>Examples hidden for brevity</summary> Below is a collection of examples showing the new error messages produced by this change. In general, messages will start with a more Bevy-centric error message (e.g., _`MyComponent` is not a `Component`_), and a note directing the user to an available derive macro where appropriate. ### Missing `#[derive(Resource)]` <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; struct MyResource; fn main() { App::new() .insert_resource(MyResource) .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `MyResource` is not a `Resource` --> examples/app/empty.rs:7:26 | 7 | .insert_resource(MyResource) | --------------- ^^^^^^^^^^ invalid `Resource` | | | required by a bound introduced by this call | = help: the trait `Resource` is not implemented for `MyResource` = note: consider annotating `MyResource` with `#[derive(Resource)]` = help: the following other types implement trait `Resource`: AccessibilityRequested ManageAccessibilityUpdates bevy::bevy_a11y::Focus DiagnosticsStore FrameCount bevy::prelude::State<S> SystemInfo bevy::prelude::Axis<T> and 141 others note: required by a bound in `bevy::prelude::App::insert_resource` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_app\src\app.rs:419:31 | 419 | pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self { | ^^^^^^^^ required by this bound in `App::insert_resource` ``` </details> ### Putting A `QueryData` in a `QueryFilter` Slot <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; #[derive(Component)] struct A; #[derive(Component)] struct B; fn my_system(_query: Query<&A, &B>) {} fn main() { App::new() .add_systems(Update, my_system) .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `&B` is not a valid `Query` filter --> examples/app/empty.rs:9:22 | 9 | fn my_system(_query: Query<&A, &B>) {} | ^^^^^^^^^^^^^ invalid `Query` filter | = help: the trait `QueryFilter` is not implemented for `&B` = help: the following other types implement trait `QueryFilter`: With<T> Without<T> bevy::prelude::Or<()> bevy::prelude::Or<(F0,)> bevy::prelude::Or<(F0, F1)> bevy::prelude::Or<(F0, F1, F2)> bevy::prelude::Or<(F0, F1, F2, F3)> bevy::prelude::Or<(F0, F1, F2, F3, F4)> and 28 others note: required by a bound in `bevy::prelude::Query` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_ecs\src\system\query.rs:349:51 | 349 | pub struct Query<'world, 'state, D: QueryData, F: QueryFilter = ()> { | ^^^^^^^^^^^ required by this bound in `Query` ``` </details> ### Missing `#[derive(Component)]` <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; struct A; fn my_system(mut commands: Commands) { commands.spawn(A); } fn main() { App::new() .add_systems(Startup, my_system) .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `A` is not a `Bundle` --> examples/app/empty.rs:6:20 | 6 | commands.spawn(A); | ----- ^ invalid `Bundle` | | | required by a bound introduced by this call | = help: the trait `bevy::prelude::Component` is not implemented for `A`, which is required by `A: Bundle` = note: consider annotating `A` with `#[derive(Component)]` or `#[derive(Bundle)]` = help: the following other types implement trait `Bundle`: TransformBundle SceneBundle DynamicSceneBundle AudioSourceBundle<Source> SpriteBundle SpriteSheetBundle Text2dBundle MaterialMesh2dBundle<M> and 34 others = note: required for `A` to implement `Bundle` note: required by a bound in `bevy::prelude::Commands::<'w, 's>::spawn` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_ecs\src\system\commands\mod.rs:243:21 | 243 | pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands { | ^^^^^^ required by this bound in `Commands::<'w, 's>::spawn` ``` </details> ### Missing `#[derive(Asset)]` <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; struct A; fn main() { App::new() .init_asset::<A>() .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `A` is not an `Asset` --> examples/app/empty.rs:7:23 | 7 | .init_asset::<A>() | ---------- ^ invalid `Asset` | | | required by a bound introduced by this call | = help: the trait `Asset` is not implemented for `A` = note: consider annotating `A` with `#[derive(Asset)]` = help: the following other types implement trait `Asset`: Font AnimationGraph DynamicScene Scene AudioSource Pitch bevy::bevy_gltf::Gltf GltfNode and 17 others note: required by a bound in `init_asset` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_asset\src\lib.rs:307:22 | 307 | fn init_asset<A: Asset>(&mut self) -> &mut Self; | ^^^^^ required by this bound in `AssetApp::init_asset` ``` </details> ### Mismatched Input and Output on System Piping <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; fn producer() -> u32 { 123 } fn consumer(_: In<u16>) {} fn main() { App::new() .add_systems(Update, producer.pipe(consumer)) .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `fn(bevy::prelude::In<u16>) {consumer}` is not a valid system with input `u32` and output `_` --> examples/app/empty.rs:11:44 | 11 | .add_systems(Update, producer.pipe(consumer)) | ---- ^^^^^^^^ invalid system | | | required by a bound introduced by this call | = help: the trait `bevy::prelude::IntoSystem<u32, _, _>` is not implemented for fn item `fn(bevy::prelude::In<u16>) {consumer}` = note: expecting a system which consumes `u32` and produces `_` note: required by a bound in `pipe` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_ecs\src\system\mod.rs:168:12 | 166 | fn pipe<B, Final, MarkerB>(self, system: B) -> PipeSystem<Self::System, B::System> | ---- required by a bound in this associated function 167 | where 168 | B: IntoSystem<Out, Final, MarkerB>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `IntoSystem::pipe` ``` </details> ### Missing Reflection <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; #[derive(Component)] struct MyComponent; fn main() { App::new() .register_type::<MyComponent>() .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `MyComponent` does not provide type registration information --> examples/app/empty.rs:8:26 | 8 | .register_type::<MyComponent>() | ------------- ^^^^^^^^^^^ the trait `GetTypeRegistration` is not implemented for `MyComponent` | | | required by a bound introduced by this call | = note: consider annotating `MyComponent` with `#[derive(Reflect)]` = help: the following other types implement trait `GetTypeRegistration`: bool char isize i8 i16 i32 i64 i128 and 443 others note: required by a bound in `bevy::prelude::App::register_type` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_app\src\app.rs:619:29 | 619 | pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `App::register_type` ``` </details> ### Missing `#[derive(States)]` Implementation <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)] enum AppState { #[default] Menu, InGame { paused: bool, turbo: bool, }, } fn main() { App::new() .init_state::<AppState>() .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: the trait bound `AppState: FreelyMutableState` is not satisfied --> examples/app/empty.rs:15:23 | 15 | .init_state::<AppState>() | ---------- ^^^^^^^^ the trait `FreelyMutableState` is not implemented for `AppState` | | | required by a bound introduced by this call | = note: consider annotating `AppState` with `#[derive(States)]` note: required by a bound in `bevy::prelude::App::init_state` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_app\src\app.rs:282:26 | 282 | pub fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self { | ^^^^^^^^^^^^^^^^^^ required by this bound in `App::init_state` ``` </details> ### Adding a `System` with Unhandled Output <details> <summary>Example Code</summary> ```rust use bevy::prelude::*; fn producer() -> u32 { 123 } fn main() { App::new() .add_systems(Update, consumer) .run(); } ``` </details> <details> <summary>Error Generated</summary> ```error error[E0277]: `fn() -> u32 {producer}` does not describe a valid system configuration --> examples/app/empty.rs:9:30 | 9 | .add_systems(Update, producer) | ----------- ^^^^^^^^ invalid system configuration | | | required by a bound introduced by this call | = help: the trait `IntoSystem<(), (), _>` is not implemented for fn item `fn() -> u32 {producer}`, which is required by `fn() -> u32 {producer}: IntoSystemConfigs<_>` = help: the following other types implement trait `IntoSystemConfigs<Marker>`: <Box<(dyn bevy::prelude::System<In = (), Out = ()> + 'static)> as IntoSystemConfigs<()>> <NodeConfigs<Box<(dyn bevy::prelude::System<In = (), Out = ()> + 'static)>> as IntoSystemConfigs<()>> <(S0,) as IntoSystemConfigs<(SystemConfigTupleMarker, P0)>> <(S0, S1) as IntoSystemConfigs<(SystemConfigTupleMarker, P0, P1)>> <(S0, S1, S2) as IntoSystemConfigs<(SystemConfigTupleMarker, P0, P1, P2)>> <(S0, S1, S2, S3) as IntoSystemConfigs<(SystemConfigTupleMarker, P0, P1, P2, P3)>> <(S0, S1, S2, S3, S4) as IntoSystemConfigs<(SystemConfigTupleMarker, P0, P1, P2, P3, P4)>> <(S0, S1, S2, S3, S4, S5) as IntoSystemConfigs<(SystemConfigTupleMarker, P0, P1, P2, P3, P4, P5)>> and 14 others = note: required for `fn() -> u32 {producer}` to implement `IntoSystemConfigs<_>` note: required by a bound in `bevy::prelude::App::add_systems` --> C:\Users\Zac\Documents\GitHub\bevy\crates\bevy_app\src\app.rs:342:23 | 339 | pub fn add_systems<M>( | ----------- required by a bound in this associated function ... 342 | systems: impl IntoSystemConfigs<M>, | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `App::add_systems` ``` </details> </details> ## Testing CI passed locally. ## Migration Guide Upgrade to version 1.78 (or higher) of Rust. ## Future Work - Currently, hints are not supported in this diagnostic. Ideally, suggestions like _"consider using ..."_ would be in a hint rather than a note, but that is the best option for now. - System chaining and other `all_tuples!(...)`-based traits have bad error messages due to the slightly different error message format. --------- Co-authored-by: Jamie Ridding <Themayu@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> |