Commit graph

7 commits

Author SHA1 Message Date
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>
2024-07-01 15:54:40 +00:00
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:
ba727898f2/examples/reflection/function_reflection.rs (L1-L157)

#### Details

Added the following items:

- `ArgError` enum
- `ArgId` enum
- `ArgInfo` struct
- `ArgList` struct
- `Arg` enum
- `DynamicFunction` struct
- `FromArg` trait (derived with `derive(Reflect)`)
- `FunctionError` enum
- `FunctionInfo` struct
- `FunctionResult` alias
- `GetOwnership` trait (derived with `derive(Reflect)`)
- `IntoFunction` trait (with blanket implementation)
- `IntoReturn` trait (derived with `derive(Reflect)`)
- `Ownership` enum
- `ReturnInfo` struct
- `Return` enum

---------

Co-authored-by: Periwink <charlesbour@gmail.com>
2024-07-01 13:49:08 +00:00
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.
2024-05-22 21:18:57 +00:00
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")]`)
2024-05-20 19:30:21 +00:00
Gino Valente
705c144259
bevy_reflect: Remove ContainerAttributes::merge (#13303)
# Objective

Unblocks #11659.

Currently the `Reflect` derive macro has to go through a merge process
for each `#[reflect]`/`#[reflet_value]` attribute encountered on a
container type.

Not only is this a bit inefficient, but it also has a soft requirement
that we can compare attributes such that an error can be thrown on
duplicates, invalid states, etc.

While working on #11659 this proved to be challenging due to the fact
that `syn` types don't implement `PartialEq` or `Hash` without enabling
the `extra-traits` feature.

Ideally, we wouldn't have to enable another feature just to accommodate
this one use case.

## Solution

Removed `ContainerAttributes::merge`.

This was a fairly simple change as we could just have the parsing
functions take `&mut self` instead of returning `Self`.

## Testing

CI should build as there should be no user-facing change.
2024-05-09 18:17:54 +00:00
Brezak
9c4ac7c297
Finish the work on try_apply (#12646)
# Objective

Finish the `try_apply` implementation started in #6770 by @feyokorenhof.
Supersedes and closes #6770. Closes #6182

## Solution

Add `try_apply` to `Reflect` and implement it in all the places that
implement `Reflect`.

---

## Changelog

Added `try_apply` to `Reflect`.

---------

Co-authored-by: Feyo Korenhof <feyokorenhof@gmail.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-05-08 14:26:01 +00:00
BD103
22305acf66
Rename bevy_reflect_derive folder to derive (#13269)
# Objective

- Some of the "large" crates have sub-crates, usually for things such as
macros.
- For an example, see [`bevy_ecs_macros` at
`bevy_ecs/macros`](4f9f987099/crates/bevy_ecs/macros).
- The one crate that does not follow this convention is
[`bevy_reflect_derive`](4f9f987099/crates/bevy_reflect/bevy_reflect_derive),
which is in the `bevy_reflect/bevy_reflect_derive` folder and not
`bevy_reflect/derive` or `bevy_reflect/macros`.

## Solution

- Rename folder `bevy_reflect_derive` to `derive`.
- I chose to use `derive` instead of `macros` because the crate name
itself ends in `_derive`. (One of only two crates to actually use this
convention, funnily enough.)

## Testing

- Build and test `bevy_reflect` and `bevy_reflect_derive`.
- Apply the following patch to `publish.sh` to run it in `--dry-run`
mode, to test that the path has been successfully updated:
- If you have any security concerns about applying random diffs, feel
free to skip this step. Worst case scenario it fails and Cart has to
manually publish a few crates.

```bash
# Apply patch to make `publish.sh` *not* actually publish anything.
git apply path/to/foo.patch
# Make `publish.sh` executable.
chmod +x tools/publish.sh
# Execute `publish.sh`.
./tools/publish.sh
```

```patch
diff --git a/tools/publish.sh b/tools/publish.sh
index b020bad28..fbcc09281 100644
--- a/tools/publish.sh
+++ b/tools/publish.sh
@@ -49,7 +49,7 @@ crates=(
 
 if [ -n "$(git status --porcelain)" ]; then
     echo "You have local changes!"
-    exit 1
+    # exit 1
 fi
 
 pushd crates
@@ -61,15 +61,15 @@ do
   cp ../LICENSE-APACHE "$crate"
   pushd "$crate"
   git add LICENSE-MIT LICENSE-APACHE
-  cargo publish --no-verify --allow-dirty
+  cargo publish --no-verify --allow-dirty --dry-run
   popd
-  sleep 20
+  # sleep 20
 done
 
 popd
 
 echo "Publishing root crate"
-cargo publish --allow-dirty
+cargo publish --allow-dirty --dry-run
 
 echo "Cleaning local state"
 git reset HEAD --hard
```

---

## Changelog

- Moved `bevy_reflect_derive` from
`crates/bevy_reflect/bevy_reflect_derive` to
`crates/bevy_reflect/derive`.
2024-05-07 07:55:32 +00:00