mirror of
https://github.com/bevyengine/bevy
synced 2025-01-12 05:09:00 +00:00
6cc01c1449
# Objective Enums are now reflectable, but are not accessible via reflection paths. This would allow us to do things like: ```rust #[derive(Reflect)] struct MyStruct { data: MyEnum } #[derive(Reflect)] struct MyEnum { Foo(u32, u32), Bar(bool) } let x = MyStruct { data: MyEnum::Foo(123), }; assert_eq!(*x.get_path::<u32>("data.1").unwrap(), 123); ``` ## Solution Added support for enums in reflection paths. ##### Note This uses a simple approach of just getting the field with the given accessor. It does not do matching or anything else to ensure the enum is the intended variant. This means that the variant must be known ahead of time or matched outside the reflection path (i.e. path to variant, perform manual match, and continue pathing). --- ## Changelog - Added support for enums in reflection paths
561 lines
18 KiB
Rust
561 lines
18 KiB
Rust
use std::num::ParseIntError;
|
|
|
|
use crate::{Array, Reflect, ReflectMut, ReflectRef, VariantType};
|
|
use thiserror::Error;
|
|
|
|
/// An error returned from a failed path string query.
|
|
#[derive(Debug, PartialEq, Eq, Error)]
|
|
pub enum ReflectPathError<'a> {
|
|
#[error("expected an identifier at index {index}")]
|
|
ExpectedIdent { index: usize },
|
|
#[error("the current struct doesn't have a field with the name `{field}`")]
|
|
InvalidField { index: usize, field: &'a str },
|
|
#[error("the current tuple struct doesn't have a field with the index {tuple_struct_index}")]
|
|
InvalidTupleStructIndex {
|
|
index: usize,
|
|
tuple_struct_index: usize,
|
|
},
|
|
#[error("the current list doesn't have a value at the index {list_index}")]
|
|
InvalidListIndex { index: usize, list_index: usize },
|
|
#[error("encountered an unexpected token `{token}`")]
|
|
UnexpectedToken { index: usize, token: &'a str },
|
|
#[error("expected token `{token}`, but it wasn't there.")]
|
|
ExpectedToken { index: usize, token: &'a str },
|
|
#[error("expected a struct, but found a different reflect value")]
|
|
ExpectedStruct { index: usize },
|
|
#[error("expected a list, but found a different reflect value")]
|
|
ExpectedList { index: usize },
|
|
#[error("failed to parse a usize")]
|
|
IndexParseError(#[from] ParseIntError),
|
|
#[error("failed to downcast to the path result to the given type")]
|
|
InvalidDowncast,
|
|
#[error("expected either a struct variant or tuple variant, but found a unit variant")]
|
|
InvalidVariantAccess { index: usize, accessor: &'a str },
|
|
}
|
|
|
|
/// A trait which allows nested values to be retrieved with path strings.
|
|
///
|
|
/// Path strings use Rust syntax:
|
|
/// - [`Struct`] items are accessed with a dot and a field name: `.field_name`
|
|
/// - [`TupleStruct`] and [`Tuple`] items are accessed with a dot and a number: `.0`
|
|
/// - [`List`] items are accessed with brackets: `[0]`
|
|
///
|
|
/// If the initial path element is a field of a struct, tuple struct, or tuple,
|
|
/// the initial '.' may be omitted.
|
|
///
|
|
/// For example, given a struct with a field `foo` which is a reflected list of
|
|
/// 2-tuples (like a `Vec<(T, U)>`), the path string `foo[3].0` would access tuple
|
|
/// element 0 of element 3 of `foo`.
|
|
///
|
|
/// [`Struct`]: crate::Struct
|
|
/// [`TupleStruct`]: crate::TupleStruct
|
|
/// [`Tuple`]: crate::Tuple
|
|
/// [`List`]: crate::List
|
|
pub trait GetPath {
|
|
/// Returns a reference to the value specified by `path`.
|
|
///
|
|
/// To retrieve a statically typed reference, use
|
|
/// [`get_path`][GetPath::get_path].
|
|
fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>;
|
|
|
|
/// Returns a mutable reference to the value specified by `path`.
|
|
///
|
|
/// To retrieve a statically typed mutable reference, use
|
|
/// [`get_path_mut`][GetPath::get_path_mut].
|
|
fn path_mut<'r, 'p>(
|
|
&'r mut self,
|
|
path: &'p str,
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>;
|
|
|
|
/// Returns a statically typed reference to the value specified by `path`.
|
|
fn get_path<'r, 'p, T: Reflect>(
|
|
&'r self,
|
|
path: &'p str,
|
|
) -> Result<&'r T, ReflectPathError<'p>> {
|
|
self.path(path).and_then(|p| {
|
|
p.downcast_ref::<T>()
|
|
.ok_or(ReflectPathError::InvalidDowncast)
|
|
})
|
|
}
|
|
|
|
/// Returns a statically typed mutable reference to the value specified by
|
|
/// `path`.
|
|
fn get_path_mut<'r, 'p, T: Reflect>(
|
|
&'r mut self,
|
|
path: &'p str,
|
|
) -> Result<&'r mut T, ReflectPathError<'p>> {
|
|
self.path_mut(path).and_then(|p| {
|
|
p.downcast_mut::<T>()
|
|
.ok_or(ReflectPathError::InvalidDowncast)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: Reflect> GetPath for T {
|
|
fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> {
|
|
(self as &dyn Reflect).path(path)
|
|
}
|
|
|
|
fn path_mut<'r, 'p>(
|
|
&'r mut self,
|
|
path: &'p str,
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> {
|
|
(self as &mut dyn Reflect).path_mut(path)
|
|
}
|
|
}
|
|
|
|
impl GetPath for dyn Reflect {
|
|
fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> {
|
|
let mut index = 0;
|
|
let mut current: &dyn Reflect = self;
|
|
while let Some(token) = next_token(path, &mut index) {
|
|
let current_index = index;
|
|
match token {
|
|
Token::Dot => {
|
|
if let Some(Token::Ident(value)) = next_token(path, &mut index) {
|
|
current = read_field(current, value, current_index)?;
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedIdent {
|
|
index: current_index,
|
|
});
|
|
}
|
|
}
|
|
Token::OpenBracket => {
|
|
if let Some(Token::Ident(value)) = next_token(path, &mut index) {
|
|
match current.reflect_ref() {
|
|
ReflectRef::List(reflect_list) => {
|
|
current = read_array_entry(reflect_list, value, current_index)?;
|
|
}
|
|
ReflectRef::Array(reflect_arr) => {
|
|
current = read_array_entry(reflect_arr, value, current_index)?;
|
|
}
|
|
_ => {
|
|
return Err(ReflectPathError::ExpectedList {
|
|
index: current_index,
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedIdent {
|
|
index: current_index,
|
|
});
|
|
}
|
|
|
|
if let Some(Token::CloseBracket) = next_token(path, &mut index) {
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedToken {
|
|
index: current_index,
|
|
token: "]",
|
|
});
|
|
}
|
|
}
|
|
Token::CloseBracket => {
|
|
return Err(ReflectPathError::UnexpectedToken {
|
|
index: current_index,
|
|
token: "]",
|
|
})
|
|
}
|
|
Token::Ident(value) => {
|
|
current = read_field(current, value, current_index)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(current)
|
|
}
|
|
|
|
fn path_mut<'r, 'p>(
|
|
&'r mut self,
|
|
path: &'p str,
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> {
|
|
let mut index = 0;
|
|
let mut current: &mut dyn Reflect = self;
|
|
while let Some(token) = next_token(path, &mut index) {
|
|
let current_index = index;
|
|
match token {
|
|
Token::Dot => {
|
|
if let Some(Token::Ident(value)) = next_token(path, &mut index) {
|
|
current = read_field_mut(current, value, current_index)?;
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedIdent {
|
|
index: current_index,
|
|
});
|
|
}
|
|
}
|
|
Token::OpenBracket => {
|
|
if let Some(Token::Ident(value)) = next_token(path, &mut index) {
|
|
match current.reflect_mut() {
|
|
ReflectMut::List(reflect_list) => {
|
|
current = read_array_entry_mut(reflect_list, value, current_index)?;
|
|
}
|
|
ReflectMut::Array(reflect_arr) => {
|
|
current = read_array_entry_mut(reflect_arr, value, current_index)?;
|
|
}
|
|
_ => {
|
|
return Err(ReflectPathError::ExpectedStruct {
|
|
index: current_index,
|
|
})
|
|
}
|
|
}
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedIdent {
|
|
index: current_index,
|
|
});
|
|
}
|
|
|
|
if let Some(Token::CloseBracket) = next_token(path, &mut index) {
|
|
} else {
|
|
return Err(ReflectPathError::ExpectedToken {
|
|
index: current_index,
|
|
token: "]",
|
|
});
|
|
}
|
|
}
|
|
Token::CloseBracket => {
|
|
return Err(ReflectPathError::UnexpectedToken {
|
|
index: current_index,
|
|
token: "]",
|
|
})
|
|
}
|
|
Token::Ident(value) => {
|
|
current = read_field_mut(current, value, current_index)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(current)
|
|
}
|
|
}
|
|
|
|
fn read_array_entry<'r, 'p, T>(
|
|
list: &'r T,
|
|
value: &'p str,
|
|
current_index: usize,
|
|
) -> Result<&'r dyn Reflect, ReflectPathError<'p>>
|
|
where
|
|
T: Array + ?Sized,
|
|
{
|
|
let list_index = value.parse::<usize>()?;
|
|
list.get(list_index)
|
|
.ok_or(ReflectPathError::InvalidListIndex {
|
|
index: current_index,
|
|
list_index,
|
|
})
|
|
}
|
|
|
|
fn read_array_entry_mut<'r, 'p, T>(
|
|
list: &'r mut T,
|
|
value: &'p str,
|
|
current_index: usize,
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>
|
|
where
|
|
T: Array + ?Sized,
|
|
{
|
|
let list_index = value.parse::<usize>()?;
|
|
list.get_mut(list_index)
|
|
.ok_or(ReflectPathError::InvalidListIndex {
|
|
index: current_index,
|
|
list_index,
|
|
})
|
|
}
|
|
|
|
fn read_field<'r, 'p>(
|
|
current: &'r dyn Reflect,
|
|
field: &'p str,
|
|
current_index: usize,
|
|
) -> Result<&'r dyn Reflect, ReflectPathError<'p>> {
|
|
match current.reflect_ref() {
|
|
ReflectRef::Struct(reflect_struct) => {
|
|
Ok(reflect_struct
|
|
.field(field)
|
|
.ok_or(ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
})?)
|
|
}
|
|
ReflectRef::TupleStruct(reflect_struct) => {
|
|
let tuple_index = field.parse::<usize>()?;
|
|
Ok(reflect_struct.field(tuple_index).ok_or(
|
|
ReflectPathError::InvalidTupleStructIndex {
|
|
index: current_index,
|
|
tuple_struct_index: tuple_index,
|
|
},
|
|
)?)
|
|
}
|
|
ReflectRef::Enum(reflect_enum) => match reflect_enum.variant_type() {
|
|
VariantType::Struct => {
|
|
Ok(reflect_enum
|
|
.field(field)
|
|
.ok_or(ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
})?)
|
|
}
|
|
VariantType::Tuple => {
|
|
let tuple_index = field.parse::<usize>()?;
|
|
Ok(reflect_enum
|
|
.field_at(tuple_index)
|
|
.ok_or(ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
})?)
|
|
}
|
|
_ => Err(ReflectPathError::InvalidVariantAccess {
|
|
index: current_index,
|
|
accessor: field,
|
|
}),
|
|
},
|
|
_ => Err(ReflectPathError::ExpectedStruct {
|
|
index: current_index,
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn read_field_mut<'r, 'p>(
|
|
current: &'r mut dyn Reflect,
|
|
field: &'p str,
|
|
current_index: usize,
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> {
|
|
match current.reflect_mut() {
|
|
ReflectMut::Struct(reflect_struct) => {
|
|
Ok(reflect_struct
|
|
.field_mut(field)
|
|
.ok_or(ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
})?)
|
|
}
|
|
ReflectMut::TupleStruct(reflect_struct) => {
|
|
let tuple_index = field.parse::<usize>()?;
|
|
Ok(reflect_struct.field_mut(tuple_index).ok_or(
|
|
ReflectPathError::InvalidTupleStructIndex {
|
|
index: current_index,
|
|
tuple_struct_index: tuple_index,
|
|
},
|
|
)?)
|
|
}
|
|
ReflectMut::Enum(reflect_enum) => match reflect_enum.variant_type() {
|
|
VariantType::Struct => {
|
|
Ok(reflect_enum
|
|
.field_mut(field)
|
|
.ok_or(ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
})?)
|
|
}
|
|
VariantType::Tuple => {
|
|
let tuple_index = field.parse::<usize>()?;
|
|
Ok(reflect_enum.field_at_mut(tuple_index).ok_or(
|
|
ReflectPathError::InvalidField {
|
|
index: current_index,
|
|
field,
|
|
},
|
|
)?)
|
|
}
|
|
_ => Err(ReflectPathError::InvalidVariantAccess {
|
|
index: current_index,
|
|
accessor: field,
|
|
}),
|
|
},
|
|
_ => Err(ReflectPathError::ExpectedStruct {
|
|
index: current_index,
|
|
}),
|
|
}
|
|
}
|
|
|
|
enum Token<'a> {
|
|
Dot,
|
|
OpenBracket,
|
|
CloseBracket,
|
|
Ident(&'a str),
|
|
}
|
|
|
|
fn next_token<'a>(path: &'a str, index: &mut usize) -> Option<Token<'a>> {
|
|
if *index >= path.len() {
|
|
return None;
|
|
}
|
|
|
|
match path[*index..].chars().next().unwrap() {
|
|
'.' => {
|
|
*index += 1;
|
|
return Some(Token::Dot);
|
|
}
|
|
'[' => {
|
|
*index += 1;
|
|
return Some(Token::OpenBracket);
|
|
}
|
|
']' => {
|
|
*index += 1;
|
|
return Some(Token::CloseBracket);
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// we can assume we are parsing an ident now
|
|
for (char_index, character) in path[*index..].chars().enumerate() {
|
|
match character {
|
|
'.' | '[' | ']' => {
|
|
let ident = Token::Ident(&path[*index..*index + char_index]);
|
|
*index += char_index;
|
|
return Some(ident);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
let ident = Token::Ident(&path[*index..]);
|
|
*index = path.len();
|
|
Some(ident)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
|
mod tests {
|
|
use super::GetPath;
|
|
use crate as bevy_reflect;
|
|
use crate::*;
|
|
|
|
#[test]
|
|
fn reflect_array_behaves_like_list() {
|
|
#[derive(Reflect)]
|
|
struct A {
|
|
list: Vec<u8>,
|
|
array: [u8; 10],
|
|
}
|
|
|
|
let a = A {
|
|
list: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
};
|
|
|
|
assert_eq!(*a.get_path::<u8>("list[5]").unwrap(), 5);
|
|
assert_eq!(*a.get_path::<u8>("array[5]").unwrap(), 5);
|
|
assert_eq!(*a.get_path::<u8>("list[0]").unwrap(), 0);
|
|
assert_eq!(*a.get_path::<u8>("array[0]").unwrap(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn reflect_array_behaves_like_list_mut() {
|
|
#[derive(Reflect)]
|
|
struct A {
|
|
list: Vec<u8>,
|
|
array: [u8; 10],
|
|
}
|
|
|
|
let mut a = A {
|
|
list: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
};
|
|
|
|
assert_eq!(*a.get_path_mut::<u8>("list[5]").unwrap(), 5);
|
|
assert_eq!(*a.get_path_mut::<u8>("array[5]").unwrap(), 5);
|
|
|
|
*a.get_path_mut::<u8>("list[5]").unwrap() = 10;
|
|
*a.get_path_mut::<u8>("array[5]").unwrap() = 10;
|
|
|
|
assert_eq!(*a.get_path_mut::<u8>("list[5]").unwrap(), 10);
|
|
assert_eq!(*a.get_path_mut::<u8>("array[5]").unwrap(), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn reflect_path() {
|
|
#[derive(Reflect)]
|
|
struct A {
|
|
w: usize,
|
|
x: B,
|
|
y: Vec<C>,
|
|
z: D,
|
|
unit_variant: F,
|
|
tuple_variant: F,
|
|
struct_variant: F,
|
|
}
|
|
|
|
#[derive(Reflect)]
|
|
struct B {
|
|
foo: usize,
|
|
bar: C,
|
|
}
|
|
|
|
#[derive(Reflect, FromReflect)]
|
|
struct C {
|
|
baz: f32,
|
|
}
|
|
|
|
#[derive(Reflect)]
|
|
struct D(E);
|
|
|
|
#[derive(Reflect)]
|
|
struct E(f32, usize);
|
|
|
|
#[derive(Reflect, FromReflect, PartialEq, Debug)]
|
|
enum F {
|
|
Unit,
|
|
Tuple(u32, u32),
|
|
Struct { value: char },
|
|
}
|
|
|
|
let mut a = A {
|
|
w: 1,
|
|
x: B {
|
|
foo: 10,
|
|
bar: C { baz: 3.14 },
|
|
},
|
|
y: vec![C { baz: 1.0 }, C { baz: 2.0 }],
|
|
z: D(E(10.0, 42)),
|
|
unit_variant: F::Unit,
|
|
tuple_variant: F::Tuple(123, 321),
|
|
struct_variant: F::Struct { value: 'm' },
|
|
};
|
|
|
|
assert_eq!(*a.get_path::<usize>("w").unwrap(), 1);
|
|
assert_eq!(*a.get_path::<usize>("x.foo").unwrap(), 10);
|
|
assert_eq!(*a.get_path::<f32>("x.bar.baz").unwrap(), 3.14);
|
|
assert_eq!(*a.get_path::<f32>("y[1].baz").unwrap(), 2.0);
|
|
assert_eq!(*a.get_path::<usize>("z.0.1").unwrap(), 42);
|
|
|
|
assert_eq!(*a.get_path::<F>("unit_variant").unwrap(), F::Unit);
|
|
assert_eq!(*a.get_path::<u32>("tuple_variant.1").unwrap(), 321);
|
|
assert_eq!(*a.get_path::<char>("struct_variant.value").unwrap(), 'm');
|
|
|
|
*a.get_path_mut::<f32>("y[1].baz").unwrap() = 3.0;
|
|
assert_eq!(a.y[1].baz, 3.0);
|
|
|
|
*a.get_path_mut::<u32>("tuple_variant.0").unwrap() = 1337;
|
|
assert_eq!(a.tuple_variant, F::Tuple(1337, 321));
|
|
|
|
assert_eq!(
|
|
a.path("x.notreal").err().unwrap(),
|
|
ReflectPathError::InvalidField {
|
|
index: 2,
|
|
field: "notreal"
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
a.path("unit_variant.0").err().unwrap(),
|
|
ReflectPathError::InvalidVariantAccess {
|
|
index: 13,
|
|
accessor: "0"
|
|
}
|
|
);
|
|
|
|
assert_eq!(
|
|
a.path("x..").err().unwrap(),
|
|
ReflectPathError::ExpectedIdent { index: 2 }
|
|
);
|
|
|
|
assert_eq!(
|
|
a.path("x[0]").err().unwrap(),
|
|
ReflectPathError::ExpectedList { index: 2 }
|
|
);
|
|
|
|
assert_eq!(
|
|
a.path("y.x").err().unwrap(),
|
|
ReflectPathError::ExpectedStruct { index: 2 }
|
|
);
|
|
|
|
assert!(matches!(
|
|
a.path("y[badindex]"),
|
|
Err(ReflectPathError::IndexParseError(_))
|
|
));
|
|
}
|
|
}
|