bones_scripting/lua/bindings/
ecsref.rs

1use super::*;
2
3/// A type data that can be used to specify a custom metatable to use for the type when it is
4/// used in an [`EcsRef`] in the lua API.
5#[derive(HasSchema)]
6#[schema(no_clone, no_default)]
7pub struct SchemaLuaEcsRefMetatable(pub fn(piccolo::Context) -> piccolo::Table);
8
9/// A reference to an ECS-compatible value.
10#[derive(Clone)]
11pub struct EcsRef {
12    /// The kind of reference.
13    pub data: EcsRefData,
14    /// The path to the desired field.
15    pub path: Ustr,
16}
17impl Default for EcsRef {
18    fn default() -> Self {
19        #[derive(HasSchema, Clone, Default)]
20        struct Void;
21        Self {
22            data: EcsRefData::Free(Rc::new(AtomicCell::new(SchemaBox::new(Void)))),
23            path: default(),
24        }
25    }
26}
27impl<'gc> FromValue<'gc> for &'gc EcsRef {
28    fn from_value(_ctx: Context<'gc>, value: Value<'gc>) -> Result<Self, piccolo::TypeError> {
29        value.as_static_user_data::<EcsRef>()
30    }
31}
32
33impl EcsRef {
34    /// Borrow the value pointed to by the [`EcsRef`]
35    pub fn borrow(&self) -> EcsRefBorrow<'_> {
36        EcsRefBorrow {
37            borrow: self.data.borrow(),
38            path: self.path,
39        }
40    }
41
42    /// Mutably borrow the value pointed to by the [`EcsRef`]
43    pub fn borrow_mut(&self) -> EcsRefBorrowMut<'_> {
44        EcsRefBorrowMut {
45            borrow: self.data.borrow_mut(),
46            path: self.path,
47        }
48    }
49
50    /// Convert into a lua value.
51    pub fn into_value(self, ctx: Context) -> Value {
52        let metatable = ctx.singletons().get(ctx, self.metatable_fn());
53        let ecsref = UserData::new_static(&ctx, self);
54        ecsref.set_metatable(&ctx, Some(metatable));
55        ecsref.into()
56    }
57}
58
59/// A borrow of an [`EcsRef`].
60pub struct EcsRefBorrow<'a> {
61    borrow: EcsRefBorrowKind<'a>,
62    path: Ustr,
63}
64
65impl EcsRefBorrow<'_> {
66    /// Get the [`SchemaRef`].
67    pub fn schema_ref(&self) -> Result<SchemaRef<'_>, EcsRefBorrowError> {
68        let b = self.borrow.schema_ref()?;
69        let b = b
70            .field_path(FieldPath(self.path))
71            .ok_or(EcsRefBorrowError::FieldNotFound(self.path))?;
72        Ok(b)
73    }
74}
75
76/// A mutable borrow of an [`EcsRef`].
77pub struct EcsRefBorrowMut<'a> {
78    borrow: EcsRefBorrowMutKind<'a>,
79    path: Ustr,
80}
81
82impl EcsRefBorrowMut<'_> {
83    /// Get the [`SchemaRef`].
84    pub fn schema_ref_mut(&mut self) -> Result<SchemaRefMut<'_>, EcsRefBorrowError> {
85        let b = self.borrow.schema_ref_mut()?;
86        let b = b
87            .into_field_path(FieldPath(self.path))
88            .ok_or(EcsRefBorrowError::FieldNotFound(self.path))?;
89        Ok(b)
90    }
91}
92
93/// The kind of value reference for [`EcsRef`].
94#[derive(Clone)]
95pub enum EcsRefData {
96    /// A resource ref.
97    Resource(AtomicUntypedResource),
98    /// A component ref.
99    Component(ComponentRef),
100    /// An asset ref.
101    Asset(AssetRef),
102    /// A free-standing ref, not stored in the ECS.
103    // TODO: use a `Gc` pointer instead of an Rc maybe.
104    Free(Rc<AtomicCell<SchemaBox>>),
105}
106
107/// A kind of borrow into an [`EcsRef`].
108pub enum EcsRefBorrowKind<'a> {
109    Resource(Ref<'a, Option<SchemaBox>>),
110    Component(ComponentBorrow<'a>),
111    Free(Ref<'a, SchemaBox>),
112    Asset(Option<MappedRef<'a, Cid, LoadedAsset, SchemaBox>>),
113}
114
115/// An error that occurs when borrowing an [`EcsRef`].
116#[derive(Debug)]
117pub enum EcsRefBorrowError {
118    MissingResource,
119    MissingComponent {
120        entity: Entity,
121        component_name: &'static str,
122    },
123    AssetNotLoaded,
124    FieldNotFound(Ustr),
125}
126impl std::error::Error for EcsRefBorrowError {}
127impl std::fmt::Display for EcsRefBorrowError {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        match self {
130            EcsRefBorrowError::MissingComponent {
131                entity,
132                component_name,
133            } => write!(
134                f,
135                "Cannot access variable because entity {entity:?} no longer has the \
136                component `{component_name}`. This may happen if you remove the cmponent \
137                while you still have a variable referencing the component data, and you \
138                then try to access the component data through the variable."
139            ),
140            EcsRefBorrowError::AssetNotLoaded => write!(f, "Asset not loaded"),
141            EcsRefBorrowError::FieldNotFound(field) => write!(f, "Field not found: {field}"),
142            EcsRefBorrowError::MissingResource => {
143                write!(f, "Resource not in world.")
144            }
145        }
146    }
147}
148
149impl EcsRefBorrowKind<'_> {
150    /// Get the borrow as a [`SchemaRef`].
151    ///
152    /// Will return none if the value does not exist, such as an unloaded asset or a component
153    /// that is not set for a given entity.
154    pub fn schema_ref(&self) -> Result<SchemaRef<'_>, EcsRefBorrowError> {
155        match self {
156            EcsRefBorrowKind::Resource(r) => Ok(r
157                .as_ref()
158                .ok_or(EcsRefBorrowError::MissingResource)?
159                .as_ref()),
160            EcsRefBorrowKind::Component(c) => {
161                c.borrow
162                    .get_ref(c.entity)
163                    .ok_or(EcsRefBorrowError::MissingComponent {
164                        entity: c.entity,
165                        component_name: &c.borrow.schema().full_name,
166                    })
167            }
168            EcsRefBorrowKind::Free(f) => Ok(f.as_ref()),
169            EcsRefBorrowKind::Asset(a) => a
170                .as_ref()
171                .map(|x| x.as_ref())
172                .ok_or(EcsRefBorrowError::AssetNotLoaded),
173        }
174    }
175}
176
177/// A component borrow into an [`EcsRef`].
178pub struct ComponentBorrow<'a> {
179    pub borrow: Ref<'a, UntypedComponentStore>,
180    pub entity: Entity,
181}
182
183/// A mutable component borrow into an [`EcsRef`].
184pub struct ComponentBorrowMut<'a> {
185    pub borrow: RefMut<'a, UntypedComponentStore>,
186    pub entity: Entity,
187}
188
189/// A kind of mutable borrow of an [`EcsRef`].
190pub enum EcsRefBorrowMutKind<'a> {
191    Resource(RefMut<'a, Option<SchemaBox>>),
192    Component(ComponentBorrowMut<'a>),
193    Free(RefMut<'a, SchemaBox>),
194    Asset(Option<MappedRefMut<'a, Cid, LoadedAsset, SchemaBox>>),
195}
196
197impl EcsRefBorrowMutKind<'_> {
198    /// Get the borrow as a [`SchemaRefMut`].
199    ///
200    /// Will return none if the value does not exist, such as an unloaded asset or a component
201    /// that is not set for a given entity.
202    pub fn schema_ref_mut(&mut self) -> Result<SchemaRefMut<'_>, EcsRefBorrowError> {
203        match self {
204            EcsRefBorrowMutKind::Resource(r) => Ok(r
205                .as_mut()
206                .ok_or(EcsRefBorrowError::MissingResource)?
207                .as_mut()),
208            EcsRefBorrowMutKind::Component(c) => {
209                c.borrow
210                    .get_ref_mut(c.entity)
211                    .ok_or(EcsRefBorrowError::MissingComponent {
212                        entity: c.entity,
213                        component_name: &c.borrow.schema().full_name,
214                    })
215            }
216            EcsRefBorrowMutKind::Free(f) => Ok(f.as_mut()),
217            EcsRefBorrowMutKind::Asset(a) => a
218                .as_mut()
219                .map(|x| x.as_mut())
220                .ok_or(EcsRefBorrowError::AssetNotLoaded),
221        }
222    }
223}
224
225impl EcsRefData {
226    /// Immutably borrow the data.
227    pub fn borrow(&self) -> EcsRefBorrowKind<'_> {
228        match self {
229            EcsRefData::Resource(resource) => {
230                let b = resource.as_ref().borrow();
231                EcsRefBorrowKind::Resource(b)
232            }
233            EcsRefData::Component(componentref) => {
234                let b = componentref.store.as_ref();
235                EcsRefBorrowKind::Component(ComponentBorrow {
236                    borrow: b.borrow(),
237                    entity: componentref.entity,
238                })
239            }
240            EcsRefData::Asset(assetref) => {
241                let b = assetref.server.try_get_untyped(assetref.handle);
242                EcsRefBorrowKind::Asset(b)
243            }
244            EcsRefData::Free(rc) => {
245                let b = rc.as_ref();
246                EcsRefBorrowKind::Free(b.borrow())
247            }
248        }
249    }
250
251    /// Mutably borrow the data.
252    pub fn borrow_mut(&self) -> EcsRefBorrowMutKind<'_> {
253        match self {
254            EcsRefData::Resource(resource) => {
255                let b = resource.borrow_mut();
256                EcsRefBorrowMutKind::Resource(b)
257            }
258            EcsRefData::Component(componentref) => {
259                let b = componentref.store.borrow_mut();
260                EcsRefBorrowMutKind::Component(ComponentBorrowMut {
261                    borrow: b,
262                    entity: componentref.entity,
263                })
264            }
265            EcsRefData::Asset(assetref) => {
266                let b = assetref.server.try_get_untyped_mut(assetref.handle);
267                EcsRefBorrowMutKind::Asset(b)
268            }
269            EcsRefData::Free(rc) => {
270                let b = rc.borrow_mut();
271                EcsRefBorrowMutKind::Free(b)
272            }
273        }
274    }
275}
276
277/// A reference to component in an [`EcsRef`].
278#[derive(Clone)]
279pub struct ComponentRef {
280    /// The component store.
281    pub store: UntypedAtomicComponentStore,
282    /// The entity to get the component data for.
283    pub entity: Entity,
284}
285
286/// A reference to an asset in an [`EcsRef`]
287#[derive(Clone)]
288pub struct AssetRef {
289    /// The asset server handle.
290    pub server: AssetServer,
291    /// The kind of asset we are referencing.
292    pub handle: UntypedHandle,
293}
294
295pub fn metatable(ctx: Context) -> Table {
296    let metatable = Table::new(&ctx);
297
298    metatable
299        .set(
300            ctx,
301            "__tostring",
302            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
303                let this: &EcsRef = stack.consume(ctx)?;
304
305                let b = this.borrow();
306                if let Ok(value) = b.schema_ref() {
307                    let access = value.access();
308                    stack.push_front(Value::String(piccolo::String::from_slice(
309                        &ctx,
310                        format!("{access:?}"),
311                    )));
312                }
313                Ok(CallbackReturn::Return)
314            }),
315        )
316        .unwrap();
317    metatable
318        .set(
319            ctx,
320            "__index",
321            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
322                let (this, key): (&EcsRef, lua::Value) = stack.consume(ctx)?;
323
324                let mut newref = this.clone();
325                newref.path = ustr(&format!("{}.{key}", this.path));
326                let b = newref.borrow();
327
328                match b.schema_ref()?.access() {
329                    SchemaRefAccess::Primitive(p) if !matches!(p, PrimitiveRef::Opaque { .. }) => {
330                        match p {
331                            PrimitiveRef::Bool(b) => stack.push_front(Value::Boolean(*b)),
332                            PrimitiveRef::U8(n) => stack.push_front(Value::Integer(*n as i64)),
333                            PrimitiveRef::U16(n) => stack.push_front(Value::Integer(*n as i64)),
334                            PrimitiveRef::U32(n) => stack.push_front(Value::Integer(*n as i64)),
335                            PrimitiveRef::U64(n) => stack.push_front(Value::Integer(*n as i64)),
336                            PrimitiveRef::U128(n) => stack.push_front(Value::Integer(*n as i64)),
337                            PrimitiveRef::I8(n) => stack.push_front(Value::Integer(*n as i64)),
338                            PrimitiveRef::I16(n) => stack.push_front(Value::Integer(*n as i64)),
339                            PrimitiveRef::I32(n) => stack.push_front(Value::Integer(*n as i64)),
340                            PrimitiveRef::I64(n) => stack.push_front(Value::Integer(*n)),
341                            PrimitiveRef::I128(n) => stack.push_front(Value::Integer(*n as i64)),
342                            PrimitiveRef::F32(n) => stack.push_front(Value::Number(*n as f64)),
343                            PrimitiveRef::F64(n) => stack.push_front(Value::Number(*n)),
344                            PrimitiveRef::String(s) => stack
345                                .push_front(Value::String(piccolo::String::from_slice(&ctx, s))),
346                            PrimitiveRef::Opaque { .. } => unreachable!(),
347                        }
348                    }
349                    _ => {
350                        stack.push_front(newref.clone().into_value(ctx));
351                    }
352                }
353
354                Ok(CallbackReturn::Return)
355            }),
356        )
357        .unwrap();
358    metatable
359        .set(
360            ctx,
361            "__newindex",
362            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
363                let (this, key, newvalue): (&EcsRef, lua::Value, lua::Value) =
364                    stack.consume(ctx)?;
365
366                let mut this = this.clone();
367                this.path = ustr(&format!("{}.{key}", this.path));
368                let mut b = this.borrow_mut();
369                let mut this_ref = b.schema_ref_mut()?;
370
371                match this_ref.access_mut() {
372                    SchemaRefMutAccess::Struct(_)
373                    | SchemaRefMutAccess::Vec(_)
374                    | SchemaRefMutAccess::Enum(_)
375                    | SchemaRefMutAccess::Map(_) => {
376                        let newvalue = newvalue.as_static_user_data::<EcsRef>()?;
377                        let newvalue_b = newvalue.borrow();
378                        let newvalue_ref = newvalue_b.schema_ref()?;
379
380                        // If the current and new ref are asset handles
381                        if this_ref
382                            .schema()
383                            .type_data
384                            .get::<SchemaAssetHandle>()
385                            .is_some()
386                            && newvalue_ref
387                                .schema()
388                                .type_data
389                                .get::<SchemaAssetHandle>()
390                                .is_some()
391                        {
392                            // SOUND: the `SchemaAssetHandle` type data asserts that these types
393                            // are represented by `UntypedHandle`. Additionally, `SchemaAssetHandle`
394                            // cannot be constructed outside the crate due to private fields, so it
395                            // cannot be added to non-conforming types.
396                            unsafe {
397                                *this_ref.cast_mut_unchecked::<UntypedHandle>() =
398                                    *newvalue_ref.cast_unchecked::<UntypedHandle>()
399                            }
400                        } else {
401                            // If we are not dealing with asset handles
402                            // Attempt to write the new value
403                            this_ref.write(newvalue_ref)?;
404                        }
405                    }
406                    SchemaRefMutAccess::Primitive(p) => match (p, newvalue) {
407                        (PrimitiveRefMut::Bool(b), Value::Boolean(newb)) => *b = newb,
408                        (PrimitiveRefMut::U8(n), Value::Integer(newi)) => {
409                            *n = newi.try_into().unwrap()
410                        }
411                        (PrimitiveRefMut::U16(n), Value::Integer(newi)) => {
412                            *n = newi.try_into().unwrap()
413                        }
414                        (PrimitiveRefMut::U32(n), Value::Integer(newi)) => {
415                            *n = newi.try_into().unwrap()
416                        }
417                        (PrimitiveRefMut::U64(n), Value::Integer(newi)) => {
418                            *n = newi.try_into().unwrap()
419                        }
420                        (PrimitiveRefMut::U128(n), Value::Integer(newi)) => {
421                            *n = newi.try_into().unwrap()
422                        }
423                        (PrimitiveRefMut::I8(n), Value::Integer(newi)) => {
424                            *n = newi.try_into().unwrap()
425                        }
426                        (PrimitiveRefMut::I16(n), Value::Integer(newi)) => {
427                            *n = newi.try_into().unwrap()
428                        }
429                        (PrimitiveRefMut::I32(n), Value::Integer(newi)) => {
430                            *n = newi.try_into().unwrap()
431                        }
432                        (PrimitiveRefMut::I64(n), Value::Integer(newi)) => *n = newi,
433                        (PrimitiveRefMut::I128(n), Value::Integer(newi)) => *n = newi.into(),
434                        (PrimitiveRefMut::F32(n), Value::Number(newf)) => *n = newf as f32,
435                        (PrimitiveRefMut::F64(n), Value::Number(newf)) => *n = newf,
436                        (PrimitiveRefMut::F32(n), Value::Integer(newi)) => *n = newi as f32,
437                        (PrimitiveRefMut::F64(n), Value::Integer(newi)) => *n = newi as f64,
438                        (PrimitiveRefMut::String(s), Value::String(news)) => {
439                            if let Ok(news) = news.to_str() {
440                                s.clear();
441                                s.push_str(news);
442                            } else {
443                                return Err(
444                                    anyhow::format_err!("Non UTF-8 string assignment.").into()
445                                );
446                            }
447                        }
448                        (PrimitiveRefMut::Opaque { mut schema_ref, .. }, value) => {
449                            // Special handling for `Ustr`
450                            if let Ok(ustr) = schema_ref.reborrow().try_cast_mut::<Ustr>() {
451                                if let Value::String(s) = value {
452                                    *ustr = s.to_str()?.into()
453                                } else if let Value::UserData(data) = value {
454                                    let ecsref = data.downcast_static::<EcsRef>()?;
455                                    let b = ecsref.borrow();
456                                    let value_ref = b.schema_ref()?;
457
458                                    if let Ok(value) = value_ref.try_cast::<Ustr>() {
459                                        *ustr = *value;
460                                    } else if let Ok(value) = value_ref.try_cast::<String>() {
461                                        *ustr = value.as_str().into();
462                                    }
463                                }
464                            } else {
465                                todo!("Opaque type assignment")
466                            }
467                        }
468                        _ => return Err(anyhow::format_err!("Invalid type").into()),
469                    },
470                }
471
472                Ok(CallbackReturn::Return)
473            }),
474        )
475        .unwrap();
476
477    metatable
478}