bevy_proto_backend/tree/
access.rs

1use std::fmt::{Debug, Formatter};
2use std::num::NonZeroIsize;
3use std::path::{Component, Path, PathBuf};
4use std::slice::Iter;
5use std::str::FromStr;
6
7use bevy::prelude::Entity;
8use bevy::reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize};
9use serde::Deserialize;
10
11use crate::schematics::{FromSchematicInput, SchematicContext};
12
13/// A deserializable prototype entity reference.
14///
15/// [prototype]: crate::proto::Prototypical
16#[derive(Clone, Debug, PartialEq, Reflect, Deserialize)]
17#[reflect(Deserialize)]
18pub enum ProtoEntity {
19    /// Access the entity from the given access path.
20    EntityPath(PathBuf),
21    /// Access a child entity.
22    Child(ChildAccess),
23    /// Access a sibling entity.
24    Sibling(SiblingAccess),
25    /// Access the parent entity.
26    Parent,
27    /// Access an ancestor entity the given number of levels up.
28    Ancestor(usize),
29    /// Access the root entity.
30    Root,
31}
32
33/// Determines how a child entity is accessed.
34#[derive(Debug, Clone, Eq, PartialEq, Reflect, Deserialize)]
35#[reflect(Deserialize)]
36pub enum ChildAccess {
37    /// Access the child with the given ID.
38    ///
39    /// The second argument denotes the occurrence.
40    /// A negative occurrence begins the search from the last child.
41    ///
42    /// # Example
43    ///
44    /// ```
45    /// # use std::num::NonZeroIsize;
46    /// # use bevy_proto_backend::tree::ChildAccess;
47    /// // Access the first occurrence of "Foo" within this entity's children
48    /// ChildAccess::Id(String::from("Foo"), NonZeroIsize::new(1).unwrap());
49    ///
50    /// // Access the second-to-last occurrence of "Foo" within this entity's children
51    /// ChildAccess::Id(String::from("Foo"), NonZeroIsize::new(-2).unwrap());
52    /// ```
53    Id(
54        String,
55        #[serde(default = "get_one")]
56        #[reflect(default = "get_one")]
57        NonZeroIsize,
58    ),
59    /// Access the child at the given index.
60    ///
61    /// Negative values are offset from the last child.
62    At(isize),
63}
64
65impl From<String> for ChildAccess {
66    fn from(value: String) -> Self {
67        Self::Id(value, get_one())
68    }
69}
70
71impl From<isize> for ChildAccess {
72    fn from(value: isize) -> Self {
73        Self::At(value)
74    }
75}
76
77impl From<(String, NonZeroIsize)> for ChildAccess {
78    fn from(value: (String, NonZeroIsize)) -> Self {
79        Self::Id(value.0, value.1)
80    }
81}
82
83/// Determines how a sibling entity is accessed.
84#[derive(Debug, Clone, Eq, PartialEq, Reflect, Deserialize)]
85pub enum SiblingAccess {
86    /// Access the sibling with the given ID.
87    ///
88    /// The second argument denotes the occurrence, offset from the current entity.
89    /// A negative occurrence searches from the current entity to the first sibling,
90    /// while a positive occurrence searches from the current entity to the last sibling.
91    ///
92    /// # Example
93    ///
94    /// ```
95    /// # use std::num::NonZeroIsize;
96    /// # use bevy_proto_backend::tree::SiblingAccess;
97    /// // Access the second occurrence of "Foo" after this entity
98    /// SiblingAccess::Id(String::from("Foo"), NonZeroIsize::new(2).unwrap());
99    ///
100    /// // Access the occurrence of "Foo" right before this entity
101    /// SiblingAccess::Id(String::from("Foo"), NonZeroIsize::new(-1).unwrap());
102    /// ```
103    Id(
104        String,
105        #[serde(default = "get_one")]
106        #[reflect(default = "get_one")]
107        NonZeroIsize,
108    ),
109    /// Access the sibling at the given offset.
110    At(NonZeroIsize),
111}
112
113fn get_one() -> NonZeroIsize {
114    NonZeroIsize::new(1).unwrap()
115}
116
117impl From<String> for SiblingAccess {
118    fn from(value: String) -> Self {
119        Self::Id(value, get_one())
120    }
121}
122
123impl From<NonZeroIsize> for SiblingAccess {
124    fn from(value: NonZeroIsize) -> Self {
125        Self::At(value)
126    }
127}
128
129impl From<(String, NonZeroIsize)> for SiblingAccess {
130    fn from(value: (String, NonZeroIsize)) -> Self {
131        Self::Id(value.0, value.1)
132    }
133}
134
135#[derive(Debug, Clone, Eq, PartialEq, Reflect)]
136pub(crate) enum AccessOp {
137    /// Access the root entity.
138    Root,
139    /// Access a parent entity.
140    Parent,
141    /// Access a child entity.
142    Child(ChildAccess),
143    /// Access a sibling entity.
144    Sibling(SiblingAccess),
145}
146
147/// An accessor used to retrieve an [`Entity`] within an [`EntityTree`].
148///
149/// # Path Format
150///
151/// This struct can also be created from a [`Path`] (or any type that satisfies `AsRef<Path>`).
152///
153/// These paths use a custom format to create the different access operations.
154/// Here is a list of the various operations:
155///
156/// | Examples             | Access Type                            |
157/// | :------------------- | :------------------------------------- |
158/// | `./`                 | Self                                   |
159/// | `../`                | [`ProtoEntity::Parent`]                |
160/// | `/`                  | [`ProtoEntity::Root`]                  |
161/// | `./@2`, `@2`         | [`ChildAccess::At`]                    |
162/// | `./foo`, `foo`       | [`ChildAccess::Id`] (1st occurrence)   |
163/// | `./@2:foo`, `@2:foo` | [`ChildAccess::Id`] (nth occurrence)   |
164/// | `./~2`, `~2`         | [`SiblingAccess::At`]                  |
165/// | `./~foo`, `~foo`     | [`SiblingAccess::Id`] (1st occurrence) |
166/// | `./~2:foo`, `~2:foo` | [`SiblingAccess::Id`] (nth occurrence) |
167///
168///
169/// [`EntityTree`]: crate::tree::EntityTree
170#[derive(Default, Clone, Eq, PartialEq, Reflect, Deserialize)]
171#[reflect(Default, Deserialize)]
172#[serde(from = "ProtoEntity")]
173pub struct EntityAccess {
174    ops: Vec<AccessOp>,
175}
176
177impl EntityAccess {
178    /// Create an [`EntityAccess`] that starts from the root.
179    pub fn root() -> Self {
180        Self {
181            ops: vec![AccessOp::Root],
182        }
183    }
184
185    /// Add a parent access.
186    pub fn parent(mut self) -> Self {
187        self.ops.push(AccessOp::Parent);
188        self
189    }
190
191    /// Add a [child access].
192    ///
193    /// [child access]: ChildAccess
194    pub fn child<C: Into<ChildAccess>>(mut self, child: C) -> Self {
195        self.ops.push(AccessOp::Child(child.into()));
196        self
197    }
198
199    /// Add a [sibling access].
200    ///
201    /// [sibling access]: SiblingAccess
202    pub fn sibling<S: Into<SiblingAccess>>(mut self, sibling: S) -> Self {
203        self.ops.push(AccessOp::Sibling(sibling.into()));
204        self
205    }
206
207    /// Create an access path using the operations in this struct.
208    pub fn to_path(&self) -> PathBuf {
209        let mut path = if matches!(self.ops.first(), Some(AccessOp::Root)) {
210            PathBuf::from("/")
211        } else {
212            // This is added purely to improve the readability of the path
213            PathBuf::from(".")
214        };
215
216        for op in self.ops() {
217            match op {
218                AccessOp::Root => {
219                    // Handled in constructor
220                    continue;
221                }
222                AccessOp::Parent => path.push(".."),
223                AccessOp::Child(ChildAccess::At(index)) => path.push(format!("@{}", index)),
224                AccessOp::Child(ChildAccess::Id(id, occurrence)) => {
225                    path.push(if occurrence.get() != 1 {
226                        format!("@{}:{}", occurrence, id)
227                    } else {
228                        id.to_string()
229                    })
230                }
231                AccessOp::Sibling(SiblingAccess::At(index)) => path.push(format!("~{}", index)),
232                AccessOp::Sibling(SiblingAccess::Id(id, occurrence)) => {
233                    path.push(if occurrence.get() != 1 {
234                        format!("~{}:{}", occurrence, id)
235                    } else {
236                        format!("~{}", id)
237                    })
238                }
239            }
240        }
241
242        path
243    }
244
245    pub(crate) fn ops(&self) -> Iter<'_, AccessOp> {
246        self.ops.iter()
247    }
248}
249
250impl From<ProtoEntity> for EntityAccess {
251    fn from(value: ProtoEntity) -> Self {
252        match value {
253            ProtoEntity::EntityPath(path) => EntityAccess::from(path),
254            ProtoEntity::Child(ChildAccess::Id(id, index)) => {
255                EntityAccess::default().child((id, index))
256            }
257            ProtoEntity::Child(ChildAccess::At(index)) => EntityAccess::default().child(index),
258            ProtoEntity::Sibling(SiblingAccess::Id(id, index)) => {
259                EntityAccess::default().sibling((id, index))
260            }
261            ProtoEntity::Sibling(SiblingAccess::At(index)) => {
262                EntityAccess::default().sibling(index)
263            }
264            ProtoEntity::Parent => EntityAccess::default().parent(),
265            ProtoEntity::Ancestor(depth) => {
266                let mut access = EntityAccess::default();
267                access.ops.extend((0..depth).map(|_| AccessOp::Parent));
268                access
269            }
270            ProtoEntity::Root => EntityAccess::root(),
271        }
272    }
273}
274
275impl<T: AsRef<Path>> From<T> for EntityAccess {
276    fn from(value: T) -> Self {
277        let path = value.as_ref();
278
279        let mut access = if path.is_absolute() {
280            Self::root()
281        } else {
282            Self::default()
283        };
284
285        for component in path.components() {
286            match component {
287                Component::Prefix(_) => panic!("prefix path operation not supported"),
288                Component::RootDir => {
289                    // Handled in constructor
290                    continue;
291                }
292                Component::CurDir => {
293                    // Do nothing
294                    continue;
295                }
296                Component::ParentDir => {
297                    access.ops.push(AccessOp::Parent);
298                }
299                Component::Normal(path) => {
300                    let path = path.to_string_lossy();
301
302                    if let Some(index) = path.strip_prefix('@') {
303                        if let Some((index, id)) = index.split_once(':') {
304                            // "foo/bar/@3:baz"
305                            let occurrence = NonZeroIsize::from_str(index.trim()).unwrap();
306                            access.ops.push(AccessOp::Child(ChildAccess::Id(
307                                id.trim().to_string(),
308                                occurrence,
309                            )));
310                        } else {
311                            // "foo/bar/@3"
312                            let index = isize::from_str(index.trim()).unwrap();
313                            access.ops.push(AccessOp::Child(ChildAccess::At(index)));
314                        }
315                    } else if let Some(index) = path.strip_prefix('~') {
316                        if let Some((index, id)) = index.split_once(':') {
317                            // "foo/bar/~3:baz"
318                            let occurrence = NonZeroIsize::from_str(index.trim()).unwrap();
319                            access.ops.push(AccessOp::Sibling(SiblingAccess::Id(
320                                id.trim().to_string(),
321                                occurrence,
322                            )));
323                        } else {
324                            // "foo/bar/~3"
325                            let offset = NonZeroIsize::from_str(index.trim()).unwrap();
326                            access
327                                .ops
328                                .push(AccessOp::Sibling(SiblingAccess::At(offset)));
329                        }
330                    } else {
331                        // "foo/bar/baz"
332                        access.ops.push(AccessOp::Child(ChildAccess::Id(
333                            path.to_string(),
334                            get_one(),
335                        )))
336                    }
337                }
338            }
339        }
340
341        access
342    }
343}
344
345impl FromSchematicInput<EntityAccess> for Entity {
346    fn from_input(input: EntityAccess, context: &mut SchematicContext) -> Self {
347        context
348            .find_entity(&input)
349            .unwrap_or_else(|| panic!("entity should exist at path {:?}", input.to_path()))
350    }
351}
352
353impl FromSchematicInput<ProtoEntity> for Entity {
354    fn from_input(input: ProtoEntity, context: &mut SchematicContext) -> Self {
355        let access: EntityAccess = input.into();
356        context
357            .find_entity(&access)
358            .unwrap_or_else(|| panic!("entity should exist at path {:?}", access.to_path()))
359    }
360}
361
362impl FromSchematicInput<EntityAccess> for Option<Entity> {
363    fn from_input(input: EntityAccess, context: &mut SchematicContext) -> Self {
364        context.find_entity(&input)
365    }
366}
367
368impl FromSchematicInput<ProtoEntity> for Option<Entity> {
369    fn from_input(input: ProtoEntity, context: &mut SchematicContext) -> Self {
370        context.find_entity(&input.into())
371    }
372}
373
374/// A helper struct to deserialize `Vec<Entity>`.
375///
376/// # Example
377///
378/// ```ignore
379/// # use bevy::prelude::{Entity, Reflect};
380/// # use bevy_proto_backend::tree::ProtoEntityList;
381/// # use bevy_proto_backend::schematics::{Schematic, ReflectSchematic};
382/// #[derive(Reflect, Schematic)]
383/// #[reflect(Schematic)]
384/// struct EntityGroup {
385///   #[from = ProtoEntityList]
386///   entities: Vec<Entity>
387/// }
388/// ```
389///
390#[derive(Default, Clone, PartialEq, Reflect, Deserialize)]
391#[reflect(Default, Deserialize)]
392#[serde(transparent)]
393pub struct ProtoEntityList(pub Vec<ProtoEntity>);
394
395impl FromSchematicInput<ProtoEntityList> for Vec<Entity> {
396    fn from_input(input: ProtoEntityList, context: &mut SchematicContext) -> Self {
397        input
398            .0
399            .into_iter()
400            .map(|entity| {
401                let access: EntityAccess = entity.into();
402                context
403                    .find_entity(&access)
404                    .unwrap_or_else(|| panic!("entity should exist at path {:?}", access.to_path()))
405            })
406            .collect()
407    }
408}
409
410impl Debug for EntityAccess {
411    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
412        write!(f, "{:?}", self.to_path())
413    }
414}