bevy/crates/bevy_state/src/reflect.rs
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](8e3488c880/crates/bevy_reflect/src/reflect.rs (L337-L342))
and make `FromReflect` a requirement to improve [`FromReflect`
ergonomics](https://github.com/bevyengine/rfcs/pull/59). This is
currently not possible because dynamic types cannot sensibly be
`FromReflect`.
  - Since this is an alternative to #5772, #5781 would be made cleaner.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-08-12 17:01:41 +00:00

165 lines
5.5 KiB
Rust

use crate::state::{FreelyMutableState, NextState, State, States};
use bevy_ecs::reflect::from_reflect_with_fallback;
use bevy_ecs::world::World;
use bevy_reflect::{FromType, Reflect, TypePath, TypeRegistry};
/// A struct used to operate on the reflected [`States`] trait of a type.
///
/// A [`ReflectState`] for type `T` can be obtained via
/// [`bevy_reflect::TypeRegistration::data`].
#[derive(Clone)]
pub struct ReflectState(ReflectStateFns);
/// The raw function pointers needed to make up a [`ReflectState`].
#[derive(Clone)]
pub struct ReflectStateFns {
/// Function pointer implementing [`ReflectState::reflect()`].
pub reflect: fn(&World) -> Option<&dyn Reflect>,
}
impl ReflectStateFns {
/// Get the default set of [`ReflectStateFns`] for a specific component type using its
/// [`FromType`] implementation.
///
/// This is useful if you want to start with the default implementation before overriding some
/// of the functions to create a custom implementation.
pub fn new<T: States + Reflect>() -> Self {
<ReflectState as FromType<T>>::from_type().0
}
}
impl ReflectState {
/// Gets the value of this [`States`] type from the world as a reflected reference.
pub fn reflect<'a>(&self, world: &'a World) -> Option<&'a dyn Reflect> {
(self.0.reflect)(world)
}
}
impl<S: States + Reflect> FromType<S> for ReflectState {
fn from_type() -> Self {
ReflectState(ReflectStateFns {
reflect: |world| {
world
.get_resource::<State<S>>()
.map(|res| res.get() as &dyn Reflect)
},
})
}
}
/// A struct used to operate on the reflected [`FreelyMutableState`] trait of a type.
///
/// A [`ReflectFreelyMutableState`] for type `T` can be obtained via
/// [`bevy_reflect::TypeRegistration::data`].
#[derive(Clone)]
pub struct ReflectFreelyMutableState(ReflectFreelyMutableStateFns);
/// The raw function pointers needed to make up a [`ReflectFreelyMutableState`].
#[derive(Clone)]
pub struct ReflectFreelyMutableStateFns {
/// Function pointer implementing [`ReflectFreelyMutableState::set_next_state()`].
pub set_next_state: fn(&mut World, &dyn Reflect, &TypeRegistry),
}
impl ReflectFreelyMutableStateFns {
/// Get the default set of [`ReflectFreelyMutableStateFns`] for a specific component type using its
/// [`FromType`] implementation.
///
/// This is useful if you want to start with the default implementation before overriding some
/// of the functions to create a custom implementation.
pub fn new<T: FreelyMutableState + Reflect + TypePath>() -> Self {
<ReflectFreelyMutableState as FromType<T>>::from_type().0
}
}
impl ReflectFreelyMutableState {
/// Tentatively set a pending state transition to a reflected [`ReflectFreelyMutableState`].
pub fn set_next_state(&self, world: &mut World, state: &dyn Reflect, registry: &TypeRegistry) {
(self.0.set_next_state)(world, state, registry);
}
}
impl<S: FreelyMutableState + Reflect + TypePath> FromType<S> for ReflectFreelyMutableState {
fn from_type() -> Self {
ReflectFreelyMutableState(ReflectFreelyMutableStateFns {
set_next_state: |world, reflected_state, registry| {
let new_state: S = from_reflect_with_fallback(
reflected_state.as_partial_reflect(),
world,
registry,
);
if let Some(mut next_state) = world.get_resource_mut::<NextState<S>>() {
next_state.set(new_state);
}
},
})
}
}
#[cfg(test)]
mod tests {
use crate as bevy_state;
use crate::app::{AppExtStates, StatesPlugin};
use crate::reflect::{ReflectFreelyMutableState, ReflectState};
use crate::state::State;
use bevy_app::App;
use bevy_ecs::prelude::AppTypeRegistry;
use bevy_reflect::Reflect;
use bevy_state_macros::States;
use std::any::TypeId;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, States, Reflect)]
enum StateTest {
A,
B,
}
#[test]
fn test_reflect_state_operations() {
let mut app = App::new();
app.add_plugins(StatesPlugin)
.insert_state(StateTest::A)
.register_type_mutable_state::<StateTest>();
let type_registry = app.world_mut().resource::<AppTypeRegistry>().0.clone();
let type_registry = type_registry.read();
let (reflect_state, reflect_mutable_state) = (
type_registry
.get_type_data::<ReflectState>(TypeId::of::<StateTest>())
.unwrap()
.clone(),
type_registry
.get_type_data::<ReflectFreelyMutableState>(TypeId::of::<StateTest>())
.unwrap()
.clone(),
);
let current_value = reflect_state.reflect(app.world()).unwrap();
assert_eq!(
current_value.downcast_ref::<StateTest>().unwrap(),
&StateTest::A
);
reflect_mutable_state.set_next_state(app.world_mut(), &StateTest::B, &type_registry);
assert_ne!(
app.world().resource::<State<StateTest>>().get(),
&StateTest::B
);
app.update();
assert_eq!(
app.world().resource::<State<StateTest>>().get(),
&StateTest::B
);
let current_value = reflect_state.reflect(app.world()).unwrap();
assert_eq!(
current_value.downcast_ref::<StateTest>().unwrap(),
&StateTest::B
);
}
}