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#[derive(Clone, Debug, PartialEq, Reflect, Deserialize)]
17#[reflect(Deserialize)]
18pub enum ProtoEntity {
19 EntityPath(PathBuf),
21 Child(ChildAccess),
23 Sibling(SiblingAccess),
25 Parent,
27 Ancestor(usize),
29 Root,
31}
32
33#[derive(Debug, Clone, Eq, PartialEq, Reflect, Deserialize)]
35#[reflect(Deserialize)]
36pub enum ChildAccess {
37 Id(
54 String,
55 #[serde(default = "get_one")]
56 #[reflect(default = "get_one")]
57 NonZeroIsize,
58 ),
59 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#[derive(Debug, Clone, Eq, PartialEq, Reflect, Deserialize)]
85pub enum SiblingAccess {
86 Id(
104 String,
105 #[serde(default = "get_one")]
106 #[reflect(default = "get_one")]
107 NonZeroIsize,
108 ),
109 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 Root,
139 Parent,
141 Child(ChildAccess),
143 Sibling(SiblingAccess),
145}
146
147#[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 pub fn root() -> Self {
180 Self {
181 ops: vec![AccessOp::Root],
182 }
183 }
184
185 pub fn parent(mut self) -> Self {
187 self.ops.push(AccessOp::Parent);
188 self
189 }
190
191 pub fn child<C: Into<ChildAccess>>(mut self, child: C) -> Self {
195 self.ops.push(AccessOp::Child(child.into()));
196 self
197 }
198
199 pub fn sibling<S: Into<SiblingAccess>>(mut self, sibling: S) -> Self {
203 self.ops.push(AccessOp::Sibling(sibling.into()));
204 self
205 }
206
207 pub fn to_path(&self) -> PathBuf {
209 let mut path = if matches!(self.ops.first(), Some(AccessOp::Root)) {
210 PathBuf::from("/")
211 } else {
212 PathBuf::from(".")
214 };
215
216 for op in self.ops() {
217 match op {
218 AccessOp::Root => {
219 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 continue;
291 }
292 Component::CurDir => {
293 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 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 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 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 let offset = NonZeroIsize::from_str(index.trim()).unwrap();
326 access
327 .ops
328 .push(AccessOp::Sibling(SiblingAccess::At(offset)));
329 }
330 } else {
331 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#[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}