Skip to main content

intuicio_core/
object.rs

1//! Values of types the host was never compiled against.
2//!
3//! An [`Object`] is one heap allocation plus the [`TypeHandle`] describing
4//! it. That is enough to construct, read, write and destroy a value whose
5//! type a script invented at runtime.
6//!
7//! [`DynamicObject`] and [`TypedDynamicObject`] are a different idea: bags of
8//! named or type-keyed objects, for values whose shape is not fixed at all.
9use crate::types::{StructFieldQuery, Type, TypeHandle, TypeQuery};
10use intuicio_data::{Initialize, non_zero_alloc, non_zero_dealloc, type_hash::TypeHash};
11use std::collections::HashMap;
12
13/// Placeholder standing in for "a type with no Rust counterpart".
14///
15/// Runtime types carry this type's hash, which is how
16/// [`crate::types::Type::is_runtime`] tells them apart from native ones.
17pub struct RuntimeObject;
18
19impl Initialize for RuntimeObject {
20    fn initialize() -> Self {
21        Self
22    }
23}
24
25/// A value of any registered type, held in its own allocation.
26///
27/// Construction and destruction go through the [`TypeHandle`]: a native type
28/// runs the Rust initializer and destructor, a runtime type walks its fields
29/// and does each one in turn.
30///
31/// Typed access is checked, so [`Object::read`] returns [`None`] unless the
32/// object really holds that Rust type.
33pub struct Object {
34    handle: TypeHandle,
35    memory: *mut u8,
36    drop: bool,
37}
38
39impl Drop for Object {
40    fn drop(&mut self) {
41        if self.drop {
42            unsafe {
43                if self.memory.is_null() {
44                    return;
45                }
46                self.handle.finalize(self.memory.cast::<()>());
47                non_zero_dealloc(self.memory, *self.handle.layout());
48                self.memory = std::ptr::null_mut();
49            }
50        }
51    }
52}
53
54impl Object {
55    /// Allocates a default value of the given type.
56    ///
57    /// # Panics
58    ///
59    /// Panics when the type has no way to create a default value. Use
60    /// [`Object::try_new`] to get [`None`] instead.
61    pub fn new(handle: TypeHandle) -> Self {
62        if !handle.can_initialize() {
63            panic!(
64                "Objects of type `{}::{}` cannot be initialized!",
65                handle.module_name().unwrap_or(""),
66                handle.name()
67            );
68        }
69        let memory = unsafe { non_zero_alloc(*handle.layout()) };
70        let mut result = Self {
71            memory,
72            handle,
73            drop: true,
74        };
75        unsafe { result.initialize() };
76        result
77    }
78
79    /// [`Object::new`] that returns [`None`] instead of panicking.
80    pub fn try_new(handle: TypeHandle) -> Option<Self> {
81        if handle.can_initialize() {
82            let memory = unsafe { non_zero_alloc(*handle.layout()) };
83            if memory.is_null() {
84                None
85            } else {
86                let mut result = Self {
87                    memory,
88                    handle,
89                    drop: true,
90                };
91                unsafe { result.initialize() };
92                Some(result)
93            }
94        } else {
95            None
96        }
97    }
98
99    /// Allocates room for a value without creating one.
100    ///
101    /// # Safety
102    ///
103    /// The memory holds garbage. It must be filled before the object is read or
104    /// dropped, since dropping runs the type's destructor over whatever is
105    /// there.
106    pub unsafe fn new_uninitialized(handle: TypeHandle) -> Option<Self> {
107        let memory = unsafe { non_zero_alloc(*handle.layout()) };
108        if memory.is_null() {
109            None
110        } else {
111            Some(Self {
112                memory,
113                handle,
114                drop: true,
115            })
116        }
117    }
118
119    /// Takes ownership of an existing allocation.
120    ///
121    /// # Safety
122    ///
123    /// `memory` must hold an initialized value of the type, allocated so that
124    /// it can be freed with the type's layout. The object frees it on drop.
125    pub unsafe fn new_raw(handle: TypeHandle, memory: *mut u8) -> Self {
126        Self {
127            memory,
128            handle,
129            drop: true,
130        }
131    }
132
133    /// Allocates a value by copying a byte image of one.
134    ///
135    /// Returns [`None`] when `bytes` is not exactly the size of the type.
136    ///
137    /// # Safety
138    ///
139    /// `bytes` must be a valid image of a value of this type, and it is moved,
140    /// so the caller must not drop the source afterwards.
141    pub unsafe fn from_bytes(handle: TypeHandle, bytes: &[u8]) -> Option<Self> {
142        if handle.layout().size() == bytes.len() {
143            let memory = unsafe { non_zero_alloc(*handle.layout()) };
144            if memory.is_null() {
145                None
146            } else {
147                unsafe { memory.copy_from(bytes.as_ptr(), bytes.len()) };
148                Some(Self {
149                    memory,
150                    handle,
151                    drop: true,
152                })
153            }
154        } else {
155            None
156        }
157    }
158
159    /// Moves a Rust value into an object, or returns [`None`] when `handle` does
160    /// not describe `T`.
161    pub fn with_value<T: 'static>(handle: TypeHandle, value: T) -> Option<Self> {
162        if handle.type_hash() == TypeHash::of::<T>() {
163            unsafe {
164                let mut result = Self::new_uninitialized(handle)?;
165                result.as_mut_ptr().cast::<T>().write(value);
166                Some(result)
167            }
168        } else {
169            None
170        }
171    }
172
173    /// Writes a default value into this object's memory.
174    ///
175    /// # Safety
176    ///
177    /// The memory must not already hold a value, since the old one is not
178    /// dropped first.
179    pub unsafe fn initialize(&mut self) {
180        if self.handle.is_native() {
181            unsafe { self.handle.initialize(self.memory.cast::<()>()) };
182        } else {
183            match &*self.handle {
184                Type::Struct(type_) => {
185                    for field in type_.fields() {
186                        unsafe {
187                            field
188                                .type_handle()
189                                .initialize(self.memory.add(field.address_offset()).cast::<()>())
190                        };
191                    }
192                }
193                Type::Enum(type_) => {
194                    if let Some(variant) = type_.default_variant() {
195                        unsafe { self.memory.write(variant.discriminant()) };
196                        for field in &variant.fields {
197                            unsafe {
198                                field.type_handle().initialize(
199                                    self.memory.add(field.address_offset()).cast::<()>(),
200                                )
201                            };
202                        }
203                    }
204                }
205            }
206        }
207    }
208
209    /// Moves the value out as a Rust value, or gives the object back on a type
210    /// mismatch.
211    pub fn consume<T: 'static>(mut self) -> Result<T, Self> {
212        if self.handle.type_hash() == TypeHash::of::<T>() {
213            self.drop = false;
214            unsafe { Ok(self.memory.cast::<T>().read()) }
215        } else {
216            Err(self)
217        }
218    }
219
220    /// Splits into the type handle and the allocation, and stops the object from
221    /// freeing it.
222    ///
223    /// # Safety
224    ///
225    /// The caller becomes responsible for destroying the value and freeing the
226    /// memory with the type's layout.
227    pub unsafe fn into_inner(mut self) -> (TypeHandle, *mut u8) {
228        self.drop = false;
229        (self.handle.clone(), self.memory)
230    }
231
232    /// Returns the type of the stored value.
233    pub fn type_handle(&self) -> &TypeHandle {
234        &self.handle
235    }
236
237    /// Returns the value as raw bytes.
238    ///
239    /// # Safety
240    ///
241    /// Reading the bytes of a type that owns resources, or keeping them past
242    /// the object's life, is on the caller.
243    pub unsafe fn memory(&self) -> &[u8] {
244        unsafe { std::slice::from_raw_parts(self.memory, self.type_handle().layout().size()) }
245    }
246
247    /// Returns the value as mutable raw bytes.
248    ///
249    /// # Safety
250    ///
251    /// Writing bytes that are not a valid value of the stored type makes every
252    /// later read, and the eventual drop, undefined.
253    pub unsafe fn memory_mut(&mut self) -> &mut [u8] {
254        unsafe { std::slice::from_raw_parts_mut(self.memory, self.type_handle().layout().size()) }
255    }
256
257    /// Returns the bytes of one field, chosen by query.
258    ///
259    /// For an enum, the field is looked up in the variant the value currently
260    /// holds.
261    ///
262    /// # Safety
263    ///
264    /// Same conditions as [`Object::memory`].
265    pub unsafe fn field_memory<'a>(&'a self, query: StructFieldQuery<'a>) -> Option<&'a [u8]> {
266        match &*self.handle {
267            Type::Struct(type_) => {
268                let field = type_.find_field(query)?;
269                Some(unsafe {
270                    std::slice::from_raw_parts(
271                        self.memory.add(field.address_offset()),
272                        field.type_handle().layout().size(),
273                    )
274                })
275            }
276            Type::Enum(type_) => {
277                let discriminant = unsafe { self.memory.read() };
278                let variant = type_.find_variant_by_discriminant(discriminant)?;
279                let field = variant.find_field(query)?;
280                Some(unsafe {
281                    std::slice::from_raw_parts(
282                        self.memory.add(field.address_offset()),
283                        field.type_handle().layout().size(),
284                    )
285                })
286            }
287        }
288    }
289
290    /// Returns the mutable bytes of one field, chosen by query.
291    ///
292    /// For an enum, the field is looked up in the variant the value currently
293    /// holds.
294    ///
295    /// # Safety
296    ///
297    /// Same conditions as [`Object::memory_mut`].
298    pub unsafe fn field_memory_mut<'a>(
299        &'a mut self,
300        query: StructFieldQuery<'a>,
301    ) -> Option<&'a mut [u8]> {
302        match &*self.handle {
303            Type::Struct(type_) => {
304                let field = type_.find_field(query)?;
305                Some(unsafe {
306                    std::slice::from_raw_parts_mut(
307                        self.memory.add(field.address_offset()),
308                        field.type_handle().layout().size(),
309                    )
310                })
311            }
312            Type::Enum(type_) => {
313                let discriminant = unsafe { self.memory.read() };
314                let variant = type_.find_variant_by_discriminant(discriminant)?;
315                let field = variant.find_field(query)?;
316                Some(unsafe {
317                    std::slice::from_raw_parts_mut(
318                        self.memory.add(field.address_offset()),
319                        field.type_handle().layout().size(),
320                    )
321                })
322            }
323        }
324    }
325
326    /// Borrows the value as a `T`, or returns [`None`] on a type mismatch.
327    pub fn read<T: 'static>(&self) -> Option<&T> {
328        if self.handle.type_hash() == TypeHash::of::<T>() {
329            unsafe { self.memory.cast::<T>().as_ref() }
330        } else {
331            None
332        }
333    }
334
335    /// Borrows the value mutably as a `T`, or returns [`None`] on a type
336    /// mismatch.
337    pub fn write<T: 'static>(&mut self) -> Option<&mut T> {
338        if self.handle.type_hash() == TypeHash::of::<T>() {
339            unsafe { self.memory.cast::<T>().as_mut() }
340        } else {
341            None
342        }
343    }
344
345    /// Borrows one field by name, or returns [`None`] when there is no such
346    /// field of that type.
347    ///
348    /// This is how a runtime type's fields are reached, since there is no Rust
349    /// struct to go through.
350    pub fn read_field<'a, T: 'static>(&'a self, field: &str) -> Option<&'a T> {
351        let query = StructFieldQuery {
352            name: Some(field.into()),
353            type_query: Some(TypeQuery::of::<T>()),
354            ..Default::default()
355        };
356        let field = match &*self.handle {
357            Type::Struct(type_) => type_.find_field(query),
358            Type::Enum(type_) => {
359                let discriminant = unsafe { self.memory.read() };
360                let variant = type_.find_variant_by_discriminant(discriminant)?;
361                variant.find_field(query)
362            }
363        }?;
364        unsafe { self.memory.add(field.address_offset()).cast::<T>().as_ref() }
365    }
366
367    /// Mutable [`Object::read_field`].
368    pub fn write_field<'a, T: 'static>(&'a mut self, field: &str) -> Option<&'a mut T> {
369        let query = StructFieldQuery {
370            name: Some(field.into()),
371            type_query: Some(TypeQuery::of::<T>()),
372            ..Default::default()
373        };
374        let field = match &*self.handle {
375            Type::Struct(type_) => type_.find_field(query),
376            Type::Enum(type_) => {
377                let discriminant = unsafe { self.memory.read() };
378                let variant = type_.find_variant_by_discriminant(discriminant)?;
379                variant.find_field(query)
380            }
381        }?;
382        unsafe { self.memory.add(field.address_offset()).cast::<T>().as_mut() }
383    }
384
385    /// Returns the allocation pointer.
386    ///
387    /// # Safety
388    ///
389    /// Nothing is checked. The caller takes over both typing and aliasing.
390    pub unsafe fn as_ptr(&self) -> *const u8 {
391        self.memory
392    }
393
394    /// Returns the mutable allocation pointer.
395    ///
396    /// # Safety
397    ///
398    /// Nothing is checked. The caller takes over both typing and aliasing.
399    pub unsafe fn as_mut_ptr(&mut self) -> *mut u8 {
400        self.memory
401    }
402
403    /// Stops this object from destroying its value when it is dropped.
404    ///
405    /// # Safety
406    ///
407    /// The value leaks unless its ownership was already handed to someone else.
408    pub unsafe fn prevent_drop(&mut self) {
409        self.drop = false;
410    }
411}
412
413impl std::fmt::Debug for Object {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        unsafe {
416            f.debug_struct("Object")
417                .field("address", &(self.as_ptr() as usize))
418                .field(
419                    "type",
420                    &format!(
421                        "{}::{}",
422                        self.handle.module_name().unwrap_or_default(),
423                        self.handle.name()
424                    ),
425                )
426                .finish()
427        }
428    }
429}
430
431/// A bag of named [`Object`] values.
432///
433/// For values whose shape is decided entirely at runtime, such as objects in
434/// a dynamically typed language.
435#[derive(Default)]
436pub struct DynamicObject {
437    properties: HashMap<String, Object>,
438}
439
440impl DynamicObject {
441    /// Borrows a property by name.
442    pub fn get(&self, name: &str) -> Option<&Object> {
443        self.properties.get(name)
444    }
445
446    /// Borrows a property mutably by name.
447    pub fn get_mut(&mut self, name: &str) -> Option<&mut Object> {
448        self.properties.get_mut(name)
449    }
450
451    /// Sets a property, replacing anything already there.
452    pub fn set(&mut self, name: impl ToString, value: Object) {
453        self.properties.insert(name.to_string(), value);
454    }
455
456    /// Removes a property and returns it.
457    pub fn delete(&mut self, name: &str) -> Option<Object> {
458        self.properties.remove(name)
459    }
460
461    /// Removes and yields every property.
462    pub fn drain(&mut self) -> impl Iterator<Item = (String, Object)> + '_ {
463        self.properties.drain()
464    }
465
466    /// Iterates names and values.
467    pub fn properties(&self) -> impl Iterator<Item = (&str, &Object)> + '_ {
468        self.properties
469            .iter()
470            .map(|(key, value)| (key.as_str(), value))
471    }
472
473    /// Iterates names and values mutably.
474    pub fn properties_mut(&mut self) -> impl Iterator<Item = (&str, &mut Object)> + '_ {
475        self.properties
476            .iter_mut()
477            .map(|(key, value)| (key.as_str(), value))
478    }
479
480    /// Iterates property names.
481    pub fn property_names(&self) -> impl Iterator<Item = &str> + '_ {
482        self.properties.keys().map(|key| key.as_str())
483    }
484
485    /// Iterates property values.
486    pub fn property_values(&self) -> impl Iterator<Item = &Object> + '_ {
487        self.properties.values()
488    }
489
490    /// Iterates property values mutably.
491    pub fn property_values_mut(&mut self) -> impl Iterator<Item = &mut Object> + '_ {
492        self.properties.values_mut()
493    }
494}
495
496/// A bag of [`Object`] values keyed by type, holding at most one of each.
497///
498/// Useful for attaching optional data to something, the way a component map
499/// does.
500#[derive(Default)]
501pub struct TypedDynamicObject {
502    properties: HashMap<TypeHash, Object>,
503}
504
505impl TypedDynamicObject {
506    /// Borrows the value stored for type `T`.
507    pub fn get<T: 'static>(&self) -> Option<&Object> {
508        self.properties.get(&TypeHash::of::<T>())
509    }
510
511    /// Borrows the value stored for type `T` mutably.
512    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut Object> {
513        self.properties.get_mut(&TypeHash::of::<T>())
514    }
515
516    /// Stores a value under type `T`, replacing anything already there.
517    pub fn set<T: 'static>(&mut self, value: Object) {
518        self.properties.insert(TypeHash::of::<T>(), value);
519    }
520
521    /// Removes the value stored for type `T` and returns it.
522    pub fn delete<T: 'static>(&mut self) -> Option<Object> {
523        self.properties.remove(&TypeHash::of::<T>())
524    }
525
526    /// Removes and yields every value.
527    pub fn drain(&mut self) -> impl Iterator<Item = (TypeHash, Object)> + '_ {
528        self.properties.drain()
529    }
530
531    /// Iterates types and values.
532    pub fn properties(&self) -> impl Iterator<Item = (&TypeHash, &Object)> + '_ {
533        self.properties.iter()
534    }
535
536    /// Iterates types and values mutably.
537    pub fn properties_mut(&mut self) -> impl Iterator<Item = (&TypeHash, &mut Object)> + '_ {
538        self.properties.iter_mut()
539    }
540
541    /// Iterates the stored types.
542    pub fn property_types(&self) -> impl Iterator<Item = &TypeHash> + '_ {
543        self.properties.keys()
544    }
545
546    /// Iterates the stored values.
547    pub fn property_values(&self) -> impl Iterator<Item = &Object> + '_ {
548        self.properties.values()
549    }
550
551    /// Iterates the stored values mutably.
552    pub fn property_values_mut(&mut self) -> impl Iterator<Item = &mut Object> + '_ {
553        self.properties.values_mut()
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use crate::{
560        object::*,
561        registry::Registry,
562        types::struct_type::*,
563        utils::{object_pop_from_stack, object_push_to_stack},
564    };
565    use intuicio_data::{
566        data_stack::{DataStack, DataStackMode},
567        lifetime::{Lifetime, LifetimeRefMut},
568    };
569    use std::{
570        alloc::Layout,
571        rc::{Rc, Weak},
572    };
573
574    #[test]
575    fn test_object() {
576        struct Droppable(Option<Weak<()>>);
577
578        impl Default for Droppable {
579            fn default() -> Self {
580                println!("Wrapper created!");
581                Self(None)
582            }
583        }
584
585        impl Drop for Droppable {
586            fn drop(&mut self) {
587                println!("Wrapper dropped!");
588            }
589        }
590
591        struct Pass;
592
593        impl Default for Pass {
594            fn default() -> Self {
595                println!("Pass created!");
596                Self
597            }
598        }
599
600        impl Drop for Pass {
601            fn drop(&mut self) {
602                println!("Pass dropped!");
603            }
604        }
605
606        let bool_handle = NativeStructBuilder::new::<bool>()
607            .build()
608            .into_type()
609            .into_handle();
610        let f32_handle = NativeStructBuilder::new::<f32>()
611            .build()
612            .into_type()
613            .into_handle();
614        let usize_handle = NativeStructBuilder::new::<usize>()
615            .build()
616            .into_type()
617            .into_handle();
618        let pass_handle = NativeStructBuilder::new::<Pass>()
619            .build()
620            .into_type()
621            .into_handle();
622        let droppable_handle = NativeStructBuilder::new::<Droppable>()
623            .build()
624            .into_type()
625            .into_handle();
626        let handle = RuntimeStructBuilder::new("Foo")
627            .field(StructField::new("a", bool_handle))
628            .field(StructField::new("b", f32_handle))
629            .field(StructField::new("c", usize_handle))
630            .field(StructField::new("d", pass_handle))
631            .field(StructField::new("e", droppable_handle))
632            .build()
633            .into_type()
634            .into_handle();
635        assert_eq!(handle.layout().size(), 24);
636        assert_eq!(handle.layout().align(), 8);
637        assert_eq!(handle.as_struct().unwrap().fields().len(), 5);
638        assert_eq!(
639            handle.as_struct().unwrap().fields()[0]
640                .type_handle()
641                .layout()
642                .size(),
643            1
644        );
645        assert_eq!(
646            handle.as_struct().unwrap().fields()[0]
647                .type_handle()
648                .layout()
649                .align(),
650            1
651        );
652        assert_eq!(handle.as_struct().unwrap().fields()[0].address_offset(), 0);
653        assert_eq!(
654            handle.as_struct().unwrap().fields()[1]
655                .type_handle()
656                .layout()
657                .size(),
658            4
659        );
660        assert_eq!(
661            handle.as_struct().unwrap().fields()[1]
662                .type_handle()
663                .layout()
664                .align(),
665            4
666        );
667        assert_eq!(handle.as_struct().unwrap().fields()[1].address_offset(), 4);
668        assert_eq!(
669            handle.as_struct().unwrap().fields()[2]
670                .type_handle()
671                .layout()
672                .size(),
673            8
674        );
675        assert_eq!(
676            handle.as_struct().unwrap().fields()[2]
677                .type_handle()
678                .layout()
679                .align(),
680            8
681        );
682        assert_eq!(handle.as_struct().unwrap().fields()[2].address_offset(), 8);
683        assert_eq!(
684            handle.as_struct().unwrap().fields()[3]
685                .type_handle()
686                .layout()
687                .size(),
688            0
689        );
690        assert_eq!(
691            handle.as_struct().unwrap().fields()[3]
692                .type_handle()
693                .layout()
694                .align(),
695            1
696        );
697        assert_eq!(handle.as_struct().unwrap().fields()[3].address_offset(), 16);
698        assert_eq!(
699            handle.as_struct().unwrap().fields()[4]
700                .type_handle()
701                .layout()
702                .size(),
703            8
704        );
705        assert_eq!(
706            handle.as_struct().unwrap().fields()[4]
707                .type_handle()
708                .layout()
709                .align(),
710            8
711        );
712        assert_eq!(handle.as_struct().unwrap().fields()[4].address_offset(), 16);
713        let mut object = Object::new(handle);
714        *object.write_field::<bool>("a").unwrap() = true;
715        *object.write_field::<f32>("b").unwrap() = 4.2;
716        *object.write_field::<usize>("c").unwrap() = 42;
717        let dropped = Rc::new(());
718        let dropped_weak = Rc::downgrade(&dropped);
719        object.write_field::<Droppable>("e").unwrap().0 = Some(dropped_weak);
720        assert!(*object.read_field::<bool>("a").unwrap());
721        assert_eq!(*object.read_field::<f32>("b").unwrap(), 4.2);
722        assert_eq!(*object.read_field::<usize>("c").unwrap(), 42);
723        assert_eq!(Rc::weak_count(&dropped), 1);
724        assert!(object.read_field::<()>("e").is_none());
725        drop(object);
726        assert_eq!(Rc::weak_count(&dropped), 0);
727    }
728
729    #[test]
730    fn test_drop() {
731        type Wrapper = LifetimeRefMut;
732
733        let lifetime = Lifetime::default();
734        assert!(lifetime.state().can_write(0));
735        let handle = NativeStructBuilder::new_uninitialized::<Wrapper>()
736            .build()
737            .into_type()
738            .into_handle();
739        let object = Object::with_value(handle, lifetime.borrow_mut().unwrap()).unwrap();
740        assert!(!lifetime.state().can_write(0));
741        drop(object);
742        assert!(lifetime.state().can_write(0));
743    }
744
745    #[test]
746    fn test_inner() {
747        let mut stack = DataStack::new(10240, DataStackMode::Values);
748        assert_eq!(stack.position(), 0);
749        let registry = Registry::default().with_basic_types();
750        let handle = registry.find_type(TypeQuery::of::<usize>()).unwrap();
751        let mut object = Object::new(handle);
752        *object.write::<usize>().unwrap() = 42;
753        let (handle, data) = unsafe { object.into_inner() };
754        assert_eq!(handle.type_hash(), TypeHash::of::<usize>());
755        assert_eq!(*handle.layout(), Layout::new::<usize>().pad_to_align());
756        let object = unsafe { Object::new_raw(handle, data) };
757        assert!(object_push_to_stack(object, &mut stack));
758        assert_eq!(
759            stack.position(),
760            if cfg!(feature = "typehash_debug_name") {
761                32
762            } else {
763                16
764            }
765        );
766        let object = object_pop_from_stack(&mut stack, &registry).unwrap();
767        assert_eq!(*object.read::<usize>().unwrap(), 42);
768        assert_eq!(stack.position(), 0);
769    }
770}