Skip to main content

bevy_mod_scripting_bindings/path/
mod.rs

1//! BMS native reflection paths
2
3use std::{borrow::Cow, fmt::Display};
4
5use bevy_mod_scripting_asset::Language;
6use bevy_mod_scripting_derive::DebugWithTypeInfo;
7use bevy_mod_scripting_display::{DisplayWithTypeInfo, GetTypeInfo};
8use bevy_reflect::{PartialReflect, ReflectMut, ReflectRef, TypeInfo, TypeRegistry};
9
10use crate::{ScriptValue, WorldGuard, convert};
11
12/// A key referencing into a `Reflect` supporting trait object.
13#[derive(DebugWithTypeInfo)]
14#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
15pub enum ReferencePart {
16    /// A string labelled reference
17    StringAccess(Cow<'static, str>),
18    /// An integer labelled reference, with optional indexing correction
19    IntegerAccess(i64, bool),
20    /// A key to a map or set,
21    MapAccess(Box<dyn PartialReflect>),
22}
23
24impl Clone for ReferencePart {
25    fn clone(&self) -> Self {
26        match self {
27            Self::StringAccess(arg0) => Self::StringAccess(arg0.clone()),
28            Self::IntegerAccess(arg0, arg1) => Self::IntegerAccess(*arg0, *arg1),
29            Self::MapAccess(arg0) => Self::MapAccess(match arg0.reflect_clone() {
30                Ok(c) => c,
31                Err(_) => arg0.to_dynamic(), // this is okay, because we need to call FromReflect on the map side anyway
32            }),
33        }
34    }
35}
36
37impl PartialEq for ReferencePart {
38    fn eq(&self, other: &Self) -> bool {
39        match (self, other) {
40            (Self::StringAccess(l0), Self::StringAccess(r0)) => l0 == r0,
41            (Self::IntegerAccess(l0, l1), Self::IntegerAccess(r0, r1)) => l0 == r0 && l1 == r1,
42            (Self::MapAccess(l0), Self::MapAccess(r0)) => {
43                l0.reflect_partial_eq(r0.as_ref()).unwrap_or(false)
44            }
45            _ => false,
46        }
47    }
48}
49
50impl Display for ReferencePart {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            ReferencePart::StringAccess(cow) => {
54                f.write_str("[\"")?;
55                f.write_str(cow)?;
56                f.write_str("\"]")
57            }
58            ReferencePart::IntegerAccess(int, correction) => {
59                f.write_str("[")?;
60                f.write_str(&if *correction { *int - 1 } else { *int }.to_string())?;
61                f.write_str("]")
62            }
63            ReferencePart::MapAccess(partial_reflect) => {
64                f.write_str("[")?;
65                partial_reflect.debug(f)?;
66                f.write_str("]")
67            }
68        }
69    }
70}
71
72#[allow(clippy::result_unit_err, reason = "it works better")]
73impl ReferencePart {
74    /// Expect this reference to be a string, and return it if it is
75    pub fn expect_string(&self) -> Result<&str, ()> {
76        match self {
77            ReferencePart::StringAccess(cow) => Ok(cow.as_ref()),
78            _ => Err(()),
79        }
80    }
81
82    /// Expect this reference to be an integer, and return it if it is
83    pub fn expect_integer(&self, correct_indexing: bool) -> Result<i64, ()> {
84        match self {
85            ReferencePart::IntegerAccess(index, correction) => {
86                Ok(if *correction && correct_indexing {
87                    *index - 1
88                } else {
89                    *index
90                })
91            }
92            _ => Err(()),
93        }
94    }
95
96    /// Casts the keys into a partial reflect value regardless of type of access
97    pub fn with_any<O, F: FnOnce(&dyn PartialReflect) -> O>(
98        &self,
99        correct_indexing: bool,
100        f: F,
101    ) -> O {
102        match self {
103            ReferencePart::StringAccess(cow) => f(cow),
104            ReferencePart::IntegerAccess(index, correction) => {
105                f(&(if *correction && correct_indexing {
106                    *index - 1
107                } else {
108                    *index
109                }))
110            }
111            ReferencePart::MapAccess(partial_reflect) => f(partial_reflect.as_ref()),
112        }
113    }
114
115    /// Converts a [`ScriptValue`] to a indexing corrected reference part.
116    ///
117    /// If given a world guard also supports arbitrary references as keys
118    pub fn new_from_script_val(
119        value: ScriptValue,
120        language: Language,
121        world: Option<WorldGuard>,
122    ) -> Result<Self, ScriptValue> {
123        Ok(match value {
124            ScriptValue::Integer(v) => Self::IntegerAccess(v, language.one_indexed()),
125            ScriptValue::Float(v) => Self::IntegerAccess(
126                v.max(i64::MIN as f64).min(i64::MAX as f64).round() as i64,
127                language.one_indexed(),
128            ),
129            ScriptValue::String(cow) => Self::StringAccess(cow),
130            ScriptValue::Reference(_ref) => world
131                .and_then(|world| Some(Self::MapAccess(_ref.to_owned_value(world).ok()?)))
132                .ok_or_else(|| ScriptValue::Reference(_ref))?,
133            _ => return Err(value),
134        })
135    }
136
137    /// Tries to reference into the given root object with the current reference part
138    pub fn reflect_element<'a>(
139        &self,
140        elem: &'a dyn PartialReflect,
141        _type_registry: &TypeRegistry,
142        one_indexed: bool,
143    ) -> Result<Option<&'a dyn PartialReflect>, ()> {
144        Ok(match elem.reflect_ref() {
145            ReflectRef::Struct(x) => x.field(self.expect_string()?),
146            ReflectRef::TupleStruct(x) => x.field(self.expect_integer(one_indexed)? as usize),
147            ReflectRef::Tuple(x) => x.field(self.expect_integer(one_indexed)? as usize),
148            ReflectRef::List(x) => x.get(self.expect_integer(one_indexed)? as usize),
149            ReflectRef::Array(x) => x.get(self.expect_integer(one_indexed)? as usize),
150            ReflectRef::Enum(x) => match x.variant_type() {
151                bevy_reflect::VariantType::Struct => x.field(self.expect_string()?),
152                bevy_reflect::VariantType::Tuple => {
153                    x.field_at(self.expect_integer(one_indexed)? as usize)
154                }
155                bevy_reflect::VariantType::Unit => return Err(()),
156            },
157            ReflectRef::Map(x) => {
158                let id = x.get_represented_map_info().ok_or(())?.key_ty().id();
159                self.with_any(one_indexed, |key| {
160                    let coerced = convert(key, id).ok_or(())?;
161                    Ok(x.get(coerced.as_ref()))
162                })?
163            }
164            ReflectRef::Set(x) => {
165                let id = match x.get_represented_type_info().ok_or(())? {
166                    TypeInfo::Set(set_info) => set_info.value_ty().id(),
167                    _ => unreachable!("impossible"),
168                };
169                self.with_any(one_indexed, |key| {
170                    let coerced = convert(key, id).ok_or(())?;
171                    Ok(x.get(coerced.as_ref()))
172                })?
173            }
174            _ => return Err(()),
175        })
176    }
177
178    /// Tries to reference into the given root object with the current reference part
179    pub fn reflect_element_mut<'a>(
180        &self,
181        elem: &'a mut dyn PartialReflect,
182        _type_registry: &TypeRegistry,
183        one_indexed: bool,
184    ) -> Result<Option<&'a mut dyn PartialReflect>, ()> {
185        Ok(match elem.reflect_mut() {
186            ReflectMut::Struct(x) => x.field_mut(self.expect_string()?),
187            ReflectMut::TupleStruct(x) => x.field_mut(self.expect_integer(one_indexed)? as usize),
188            ReflectMut::Tuple(x) => x.field_mut(self.expect_integer(one_indexed)? as usize),
189            ReflectMut::List(x) => x.get_mut(self.expect_integer(one_indexed)? as usize),
190            ReflectMut::Array(x) => x.get_mut(self.expect_integer(one_indexed)? as usize),
191            ReflectMut::Enum(x) => match x.variant_type() {
192                bevy_reflect::VariantType::Struct => x.field_mut(self.expect_string()?),
193                bevy_reflect::VariantType::Tuple => {
194                    x.field_at_mut(self.expect_integer(one_indexed)? as usize)
195                }
196                bevy_reflect::VariantType::Unit => return Err(()),
197            },
198            ReflectMut::Map(x) => {
199                let id = x.get_represented_map_info().ok_or(())?.key_ty().id();
200                self.with_any(one_indexed, |key| {
201                    let coerced = convert(key, id).ok_or(())?;
202                    Ok(x.get_mut(coerced.as_ref()))
203                })?
204            }
205            // ReflectMut::Set(x) => {} // no get_mut is available
206            _ => return Err(()),
207        })
208    }
209}
210
211/// A collection of references into a `Reflect` supporting trait object in series.
212#[derive(DebugWithTypeInfo, Clone, PartialEq, Default)]
213#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
214pub struct ReferencePath {
215    one_indexed: bool,
216    path: Vec<ReferencePart>,
217}
218
219impl ReferencePath {
220    /// Sets the indexing mode on the path
221    pub fn set_is_one_indexed(&mut self, is_one_indexed: bool) {
222        self.one_indexed = is_one_indexed;
223    }
224
225    /// Traverses the reference path from the given root object.
226    pub fn reflect_element<'a>(
227        &self,
228        val: &'a dyn PartialReflect,
229        type_registry: &TypeRegistry,
230    ) -> Result<Option<&'a dyn PartialReflect>, ReferencePathError> {
231        let mut next: &'a dyn PartialReflect = val;
232        for i in &self.path {
233            next = match i.reflect_element(next, type_registry, self.one_indexed) {
234                Ok(None) => return Ok(None),
235                Ok(Some(v)) => v,
236                Err(_) => {
237                    return Err(ReferencePathError {
238                        val: next.get_represented_type_info(),
239                        part: i.clone(),
240                    });
241                }
242            };
243        }
244        Ok(Some(next))
245    }
246
247    /// Traverses the reference path from the given root object.
248    pub fn reflect_element_mut<'a>(
249        &self,
250        val: &'a mut dyn PartialReflect,
251        type_registry: &TypeRegistry,
252    ) -> Result<Option<&'a mut dyn PartialReflect>, ReferencePathError> {
253        let mut next: &'a mut dyn PartialReflect = val;
254        for i in &self.path {
255            let type_info_current = next.get_represented_type_info();
256            next = match i.reflect_element_mut(next, type_registry, self.one_indexed) {
257                Ok(None) => return Ok(None),
258                Ok(Some(v)) => v,
259                Err(_) => {
260                    return Err(ReferencePathError {
261                        val: type_info_current,
262                        part: i.clone(),
263                    });
264                }
265            };
266        }
267        Ok(Some(next))
268    }
269
270    /// Returns true if the underlying path has no elements
271    pub fn is_empty(&self) -> bool {
272        self.path.is_empty()
273    }
274
275    /// Pushes a new reference at the end of the current reflection path.
276    pub fn push(&mut self, part: ReferencePart) {
277        self.path.push(part)
278    }
279
280    /// Pushes a new reference at the end of the current reflection path.
281    pub fn extend(&mut self, part: impl Iterator<Item = ReferencePart>) {
282        self.path.extend(part)
283    }
284}
285
286impl Display for ReferencePath {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        for i in &self.path {
289            std::fmt::Display::fmt(i, f)?
290        }
291        Ok(())
292    }
293}
294
295impl DisplayWithTypeInfo for ReferencePath {
296    fn display_with_type_info(
297        &self,
298        f: &mut std::fmt::Formatter<'_>,
299        _type_info_provider: Option<&dyn GetTypeInfo>,
300    ) -> std::fmt::Result {
301        std::fmt::Display::fmt(self, f)
302    }
303}
304
305/// Errors to do with custom BMS reference path resolution.
306pub struct ReferencePathError {
307    val: Option<&'static TypeInfo>,
308    part: ReferencePart,
309}
310
311impl Display for ReferencePathError {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        f.write_str("Cannot reflect into ")?;
314        f.write_str(match self.val {
315            Some(TypeInfo::Struct(_)) => "struct",
316            Some(TypeInfo::TupleStruct(_)) => "tuple truct",
317            Some(TypeInfo::Tuple(_)) => "tuple",
318            Some(TypeInfo::List(_)) => "list",
319            Some(TypeInfo::Array(_)) => "array",
320            Some(TypeInfo::Map(_)) => "map",
321            Some(TypeInfo::Set(_)) => "set",
322            Some(TypeInfo::Enum(_)) => "enum",
323            Some(TypeInfo::Opaque(_)) => "opaque type",
324            None => "unknown type",
325        })?;
326
327        f.write_str(" of type: ")?;
328        f.write_str(
329            self.val
330                .map(|t| t.type_path_table().path())
331                .unwrap_or("unknown type"),
332        )?;
333
334        f.write_str(" with ")?;
335
336        f.write_str(match &self.part {
337            ReferencePart::StringAccess(_) => "string key",
338            ReferencePart::IntegerAccess(_, _) => "integer key",
339            ReferencePart::MapAccess(_) => "map key",
340        })?;
341
342        f.write_str(": `")?;
343        std::fmt::Display::fmt(&self.part, f)?;
344        f.write_str("`")?;
345
346        Ok(())
347    }
348}