2020-11-28 00:39:59 +00:00
|
|
|
use std::num::ParseIntError;
|
|
|
|
|
|
|
|
use crate::{Reflect, ReflectMut, ReflectRef};
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2022-01-14 19:09:44 +00:00
|
|
|
/// An error returned from a failed path string query.
|
2020-11-28 00:39:59 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Error)]
|
|
|
|
pub enum ReflectPathError<'a> {
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("expected an identifier at the given index")]
|
2020-11-28 00:39:59 +00:00
|
|
|
ExpectedIdent { index: usize },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("the current struct doesn't have a field with the given name")]
|
2020-11-28 00:39:59 +00:00
|
|
|
InvalidField { index: usize, field: &'a str },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("the current tuple struct doesn't have a field with the given index")]
|
2020-11-28 00:39:59 +00:00
|
|
|
InvalidTupleStructIndex {
|
|
|
|
index: usize,
|
|
|
|
tuple_struct_index: usize,
|
|
|
|
},
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("the current list doesn't have a value at the given index")]
|
2020-11-28 00:39:59 +00:00
|
|
|
InvalidListIndex { index: usize, list_index: usize },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("encountered an unexpected token")]
|
2020-11-28 00:39:59 +00:00
|
|
|
UnexpectedToken { index: usize, token: &'a str },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("expected a token, but it wasn't there.")]
|
2020-11-28 00:39:59 +00:00
|
|
|
ExpectedToken { index: usize, token: &'a str },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("expected a struct, but found a different reflect value")]
|
2020-11-28 00:39:59 +00:00
|
|
|
ExpectedStruct { index: usize },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("expected a list, but found a different reflect value")]
|
2020-11-28 00:39:59 +00:00
|
|
|
ExpectedList { index: usize },
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("failed to parse a usize")]
|
2020-11-28 00:39:59 +00:00
|
|
|
IndexParseError(#[from] ParseIntError),
|
2020-12-02 19:31:16 +00:00
|
|
|
#[error("failed to downcast to the path result to the given type")]
|
2020-11-28 00:39:59 +00:00
|
|
|
InvalidDowncast,
|
|
|
|
}
|
|
|
|
|
2022-01-14 19:09:44 +00:00
|
|
|
/// 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
|
2020-11-28 00:39:59 +00:00
|
|
|
pub trait GetPath {
|
2022-01-14 19:09:44 +00:00
|
|
|
/// Returns a reference to the value specified by `path`.
|
|
|
|
///
|
|
|
|
/// To retrieve a statically typed reference, use
|
|
|
|
/// [`get_path`][GetPath::get_path].
|
2020-11-28 00:39:59 +00:00
|
|
|
fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>;
|
2022-01-14 19:09:44 +00:00
|
|
|
|
|
|
|
/// 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].
|
2020-11-28 00:39:59 +00:00
|
|
|
fn path_mut<'r, 'p>(
|
|
|
|
&'r mut self,
|
|
|
|
path: &'p str,
|
|
|
|
) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>;
|
|
|
|
|
2022-01-14 19:09:44 +00:00
|
|
|
/// Returns a statically typed reference to the value specified by `path`.
|
2020-11-28 00:39:59 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-01-14 19:09:44 +00:00
|
|
|
/// Returns a statically typed mutable reference to the value specified by
|
|
|
|
/// `path`.
|
2020-11-28 00:39:59 +00:00
|
|
|
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) => {
|
|
|
|
let list_index = value.parse::<usize>()?;
|
|
|
|
let list_item = reflect_list.get(list_index).ok_or(
|
|
|
|
ReflectPathError::InvalidListIndex {
|
|
|
|
index: current_index,
|
|
|
|
list_index,
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
current = list_item;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
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) => {
|
|
|
|
let list_index = value.parse::<usize>()?;
|
|
|
|
let list_item = reflect_list.get_mut(list_index).ok_or(
|
|
|
|
ReflectPathError::InvalidListIndex {
|
|
|
|
index: current_index,
|
|
|
|
list_index,
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
current = list_item;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
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_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,
|
|
|
|
},
|
|
|
|
)?)
|
|
|
|
}
|
|
|
|
_ => 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,
|
|
|
|
},
|
|
|
|
)?)
|
|
|
|
}
|
|
|
|
_ => 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)]
|
2021-02-22 08:42:19 +00:00
|
|
|
#[allow(clippy::float_cmp, clippy::approx_constant)]
|
2020-11-28 00:39:59 +00:00
|
|
|
mod tests {
|
|
|
|
use super::GetPath;
|
2021-05-19 19:03:36 +00:00
|
|
|
use crate as bevy_reflect;
|
2020-11-28 00:39:59 +00:00
|
|
|
use crate::*;
|
|
|
|
#[test]
|
|
|
|
fn reflect_path() {
|
|
|
|
#[derive(Reflect)]
|
|
|
|
struct A {
|
|
|
|
w: usize,
|
|
|
|
x: B,
|
|
|
|
y: Vec<C>,
|
|
|
|
z: D,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Reflect)]
|
|
|
|
struct B {
|
|
|
|
foo: usize,
|
|
|
|
bar: C,
|
|
|
|
}
|
|
|
|
|
Add FromReflect trait to convert dynamic types to concrete types (#1395)
Dynamic types (`DynamicStruct`, `DynamicTupleStruct`, `DynamicTuple`, `DynamicList` and `DynamicMap`) are used when deserializing scenes, but currently they can only be applied to existing concrete types. This leads to issues when trying to spawn non trivial deserialized scene.
For components, the issue is avoided by requiring that reflected components implement ~~`FromResources`~~ `FromWorld` (or `Default`). When spawning, a new concrete type is created that way, and the dynamic type is applied to it. Unfortunately, some components don't have any valid implementation of these traits.
In addition, any `Vec` or `HashMap` inside a component will panic when a dynamic type is pushed into it (for instance, `Text` panics when adding a text section).
To solve this issue, this PR adds the `FromReflect` trait that creates a concrete type from a dynamic type that represent it, derives the trait alongside the `Reflect` trait, drops the ~~`FromResources`~~ `FromWorld` requirement on reflected components, ~~and enables reflection for UI and Text bundles~~. It also adds the requirement that fields ignored with `#[reflect(ignore)]` implement `Default`, since we need to initialize them somehow.
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2021-12-26 18:49:01 +00:00
|
|
|
#[derive(Reflect, FromReflect)]
|
2020-11-28 00:39:59 +00:00
|
|
|
struct C {
|
|
|
|
baz: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Reflect)]
|
|
|
|
struct D(E);
|
|
|
|
|
|
|
|
#[derive(Reflect)]
|
|
|
|
struct E(f32, usize);
|
|
|
|
|
|
|
|
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)),
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
*a.get_path_mut::<f32>("y[1].baz").unwrap() = 3.0;
|
|
|
|
assert_eq!(a.y[1].baz, 3.0);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
a.path("x.notreal").err().unwrap(),
|
|
|
|
ReflectPathError::InvalidField {
|
|
|
|
index: 2,
|
|
|
|
field: "notreal"
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
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(_))
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|