Commit graph

14 commits

Author SHA1 Message Date
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
```
2024-08-19 21:52:36 +00:00
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)
});
```
2024-08-17 00:20:47 +00:00
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`.
2024-08-07 03:11:08 +00:00
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.
2024-08-06 01:09:48 +00:00
Sou1gh0st
f51a306b30
feat(bevy_app): expose an API to perform updates for a specific sub-app. (#14009)
# Objective

- Fixes https://github.com/bevyengine/bevy/issues/14003

## Solution

- Expose an API to perform updates for a specific sub-app, so we can
avoid mutable borrow the app twice.

## Testing

- I have tested the API by modifying the code in the `many_lights`
example with the following changes:
```rust
impl Plugin for LogVisibleLights {
    fn build(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.add_systems(Render, print_visible_light_count.in_set(RenderSet::Prepare));
    }

    fn finish(&self, app: &mut App) {
        app.update_sub_app_by_label(RenderApp);
    }
}
```

---

## Changelog
- add the `update_sub_app_by_label` API to `App` and `SubApps`.

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2024-06-25 14:04:31 +00:00
Chris Juchem
49661b99fe
Remove extra call to clear_trackers (#13762)
Fixes #13758.

# Objective

Calling `update` on the main app already calls `clear_trackers`. Calling
it again in `SubApps::update` caused RemovedCompenet Events to be
cleared earlier than they should be.

## Solution

- Don't call clear_trackers an extra time.

## Testing

I manually tested the fix with this unit test: 
```
#[cfg(test)]
mod test {
    use crate::core::{FrameCount, FrameCountPlugin};
    use crate::prelude::*;

    #[test]
    fn test_next_frame_removal() {
        #[derive(Component)]
        struct Foo;

        #[derive(Resource)]
        struct RemovedCount(usize);

        let mut app = App::new();
        app.add_plugins(FrameCountPlugin);
        app.add_systems(Startup, |mut commands: Commands| {
            for _ in 0..100 {
                commands.spawn(Foo);
            }
            commands.insert_resource(RemovedCount(0));
        });

        app.add_systems(First, |counter: Res<FrameCount>| {
            println!("Frame {}:", counter.0)
        });

        fn detector_system(
            mut removals: RemovedComponents<Foo>,
            foos: Query<Entity, With<Foo>>,
            mut removed_c: ResMut<RemovedCount>,
        ) {
            for e in removals.read() {
                println!("  Detected removed Foo component for {e:?}");
                removed_c.0 += 1;
            }
            let c = foos.iter().count();
            println!("  Total Foos: {}", c);
            assert_eq!(c + removed_c.0, 100);
        }
        fn deleter_system(foos: Query<Entity, With<Foo>>, mut commands: Commands) {
            foos.iter().next().map(|e| {
                commands.entity(e).remove::<Foo>();
            });
        }
        app.add_systems(Update, (detector_system, deleter_system).chain());

        app.update();
        app.update();
        app.update();
        app.update();
    }
}
```
2024-06-10 18:06:05 +00:00
MiniaczQ
25f7a29a2f
Move state installation methods from bevy_app to bevy_state (#13637)
# Objective

After separating `bevy_states`, state installation methods like
`init_state` were kept in `bevy_app` under the `bevy_state` feature
flag.
This is problematic, because `bevy_state` is not a core module,
`bevy_app` is, yet it depends on `bevy_state`.
This causes practical problems like the inability to use
`bevy_hierarchy` inside `bevy_state`, because of circular dependencies.

## Solution

- `bevy_state` now has a `bevy_app` feature flag, which gates the new
`AppStateExt` trait.
All previous state installation methods were moved to this trait.
It's implemented for both `SubApp` and `App`.

## Changelog

- All state related app methods are now in `AppExtStates` trait in
`bevy_state`.
- Added `StatesPlugin` which is in `DefaultPlugins` when `bevy_state` is
enabled.

## Migration Guide

`App::init_state` is now provided by the
`bevy_state::app::AppExtStates;` trait: import it if you need this
method and are not blob-importing the `bevy` prelude.
2024-06-03 13:47:08 +00:00
MiniaczQ
5cb4808026
Simplify state transitions (#13616)
# Objective

Prerequisite to #13579.
Make state transition schedule running simpler.

## Solution

- Remove `should_run_transition` which read the latest event and
fake-fire an event for the startup transitions (e.g. startup
`OnEnter()`).
- Account for startup event, by actually emitting an event when adding
states to `App`.
- Replace `should_run_transition` with `last_transition`, which is a
light wrapper over `EventReader::read().last()`.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-06-01 21:00:38 +00:00
Lee-Orr
42ba9dfaea
Separate state crate (#13216)
# Objective

Extracts the state mechanisms into a new crate called "bevy_state".

This comes with a few goals:

- state wasn't really an inherent machinery of the ecs system, and so
keeping it within bevy_ecs felt forced
- by mixing it in with bevy_ecs, the maintainability of our more robust
state system was significantly compromised

moving state into a new crate makes it easier to encapsulate as it's own
feature, and easier to read and understand since it's no longer a
single, massive file.

## Solution

move the state-related elements from bevy_ecs to a new crate

## Testing

- Did you test these changes? If so, how? all the automated tests
migrated and passed, ran the pre-existing examples without changes to
validate.

---

## Migration Guide

Since bevy_state is now gated behind the `bevy_state` feature, projects
that use state but don't use the `default-features` will need to add
that feature flag.

Since it is no longer part of bevy_ecs, projects that use bevy_ecs
directly will need to manually pull in `bevy_state`, trigger the
StateTransition schedule, and handle any of the elements that bevy_app
currently sets up.

---------

Co-authored-by: Kristoffer Søholm <k.soeholm@gmail.com>
2024-05-09 18:06:05 +00:00
Lee-Orr
b9455afd0c
Schedule resource mutation (#13193)
# Objective

Resolves #13185 

## Solution

Move the following methods from `sub_app` to the `Schedules` resource,
and use them in the sub app:

- `add_systems`
- `configure_sets`
- `ignore_ambiguity`

Add an `entry(&mut self, label: impl ScheduleLabel) -> &mut Schedule`
method to the `Schedules` resource, which returns a mutable reference to
the schedule associated with the label, and creates one if it doesn't
already exist. (build on top of the `entry(..).or_insert_with(...)`
pattern in `HashMap`.

## Testing

- Did you test these changes? If so, how? Added 4 unit tests to the
`schedule.rs` - one that validates adding a system to an existing
schedule, one that validates adding a system to a new one, one that
validates configuring sets on an existing schedule, and one that
validates configuring sets on a new schedule.
- I didn't add tests for `entry` since the previous 4 tests use
functions that rely on it.
- I didn't test `ignore_ambiguity` since I didn't see examples of it's
use, and am not familiar enough with it to know how to set up a good
test for it. However, it relies on the `entry` method as well, so it
should work just like the other 2 methods.
2024-05-03 12:40:32 +00:00
Lee-Orr
b8832dc862
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.

# Objective

This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.

As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.

### Addition - Sub States
closes #9942 
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316

## Solution
closes #11358 

The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.

In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.

---

## Changelog

- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist

## Migration Guide

If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above

If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.

---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.

- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5

- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.

- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.

- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.

- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087

As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
Charles Bournhonesque
f73950767b
Update App:is_plugin_added to work inside Plugin::finish and Plugin::clean (#12761)
# Objective

I have been trying to check for the existing of some plugins via
`App::is_plugin_added` to conditionally run some behaviour in the
`Plugin::finish` part of my plugin, before realizing that the plugin
registry is actually not available during this step.
This is because the `App::is_plugin_added` using the plugin registry to
check for previous registration.

## Solution

- Switch the `App::is_plugin_added` to use the list of plugin names to
check for previous registrations
- Add a unit test showcasing that `App::is_plugin_added` works during
`Plugin::finish`
2024-04-28 21:32:16 +00:00
James Liu
ae9775c83b
Optimize Event Updates (#12936)
# Objective
Improve performance scalability when adding new event types to a Bevy
app. Currently, just using Bevy in the default configuration, all apps
spend upwards of 100+us in the `First` schedule, every app tick,
evaluating if it should update events or not, even if events are not
being used for that particular frame, and this scales with the number of
Events registered in the app.

## Solution
As `Events::update` is guaranteed `O(1)` by just checking if a
resource's value, swapping two Vecs, and then clearing one of them, the
actual cost of running `event_update_system` is *very* cheap. The
overhead of doing system dependency injection, task scheduling ,and the
multithreaded executor outweighs the cost of running the system by a
large margin.

Create an `EventRegistry` resource that keeps a number of function
pointers that update each event. Replace the per-event type
`event_update_system` with a singular exclusive system uses the
`EventRegistry` to update all events instead. Update `SubApp::add_event`
to use `EventRegistry` instead.

## Performance
This speeds reduces the cost of the `First` schedule in both many_foxes
and many_cubes by over 80%. Note this is with system spans on. The
majority of this is now context-switching costs from launching
`time_system`, which should be mostly eliminated with #12869.

![image](https://github.com/bevyengine/bevy/assets/3137680/037624be-21a2-4dc2-a42f-9d0bfa3e9b4a)

The actual `event_update_system` is usually *very* short, using only a
few microseconds on average.

![image](https://github.com/bevyengine/bevy/assets/3137680/01ff1689-3595-49b6-8f09-5c44bcf903e8)

---

## Changelog
TODO

## Migration Guide
TODO

---------

Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-04-13 14:11:28 +00:00
Cameron
01649f13e2
Refactor App and SubApp internals for better separation (#9202)
# Objective

This is a necessary precursor to #9122 (this was split from that PR to
reduce the amount of code to review all at once).

Moving `!Send` resource ownership to `App` will make it unambiguously
`!Send`. `SubApp` must be `Send`, so it can't wrap `App`.

## Solution

Refactor `App` and `SubApp` to not have a recursive relationship. Since
`SubApp` no longer wraps `App`, once `!Send` resources are moved out of
`World` and into `App`, `SubApp` will become unambiguously `Send`.

There could be less code duplication between `App` and `SubApp`, but
that would break `App` method chaining.

## Changelog

- `SubApp` no longer wraps `App`.
- `App` fields are no longer publicly accessible.
- `App` can no longer be converted into a `SubApp`.
- Various methods now return references to a `SubApp` instead of an
`App`.
## Migration Guide

- To construct a sub-app, use `SubApp::new()`. `App` can no longer
convert into `SubApp`.
- If you implemented a trait for `App`, you may want to implement it for
`SubApp` as well.
- If you're accessing `app.world` directly, you now have to use
`app.world()` and `app.world_mut()`.
- `App::sub_app` now returns `&SubApp`.
- `App::sub_app_mut`  now returns `&mut SubApp`.
- `App::get_sub_app` now returns `Option<&SubApp>.`
- `App::get_sub_app_mut` now returns `Option<&mut SubApp>.`
2024-03-31 03:16:10 +00:00