Skip to main content

forge/mt/
property_list.rs

1use core::{ffi::c_void, marker::PhantomData};
2
3use macros::Object;
4
5use crate::{mem, mt::property::MtProperty};
6
7/// The list of [`MtProperty`] entries exposed by an [`MtObject`].
8///
9/// MT Framework reports an object's reflected properties by populating one of
10/// these lists (see [`MtObject::get_properties`]). The list itself only holds
11/// the head of the chain; the properties live in the game and are linked
12/// together through their [`next`](MtProperty::next) pointers. Iterate it (via
13/// [`iter`](Self::iter) or `for p in &list`) to walk those properties in order.
14///
15/// # Layout
16///
17/// `#[repr(C)]` so the fields match the game's in-memory layout exactly, since
18/// the game writes directly into a list created with [`new`](Self::new).
19///
20/// [`MtObject`]: crate::mt::object::MtObject
21/// [`MtObject::get_properties`]: crate::mt::object::MtObject::get_properties
22#[repr(C)]
23#[derive(Object)]
24pub struct MtPropertyList {
25    _vft: *const c_void,
26    first: *const MtProperty,
27}
28
29/// Iterator over an [`MtPropertyList`], yielding each [`MtProperty`] from the
30/// head of the chain onward.
31///
32/// Created by [`MtPropertyList::iter`] or by iterating a `&MtPropertyList`.
33pub struct MtPropertyListIter<'a> {
34    current: *const MtProperty,
35    _phantom: PhantomData<&'a MtProperty>,
36}
37
38impl MtPropertyList {
39    /// Creates a new, empty property list.
40    ///
41    /// The list is initialised with the game's `MtPropertyList` vtable so it can
42    /// be handed to the game to be filled in (see
43    /// [`MtObject::get_properties`](crate::mt::object::MtObject::get_properties));
44    /// on its own it contains no properties.
45    pub fn new() -> Self {
46        Self {
47            _vft: (mem::text_addr() + 0x177E9A0) as *const c_void,
48            first: core::ptr::null(),
49        }
50    }
51
52    /// Returns the first property in the list, or `None` if the list is empty.
53    pub fn first(&self) -> Option<&MtProperty> {
54        if self.is_empty() {
55            None
56        } else {
57            Some(unsafe { &*self.first })
58        }
59    }
60
61    /// Returns `true` if the list contains no properties.
62    pub fn is_empty(&self) -> bool {
63        self.first == core::ptr::null()
64    }
65
66    /// Returns the number of properties in the list.
67    ///
68    /// This walks the whole chain, so it is `O(n)`.
69    pub fn len(&self) -> usize {
70        if self.is_empty() {
71            return 0;
72        }
73
74        let mut len = 0;
75        let mut p = self.first;
76        while p != core::ptr::null() {
77            len += 1;
78            p = unsafe { (*p).next };
79        }
80
81        len
82    }
83
84    /// Returns an iterator over the properties in the list, from first to last.
85    pub fn iter(&self) -> MtPropertyListIter<'_> {
86        MtPropertyListIter {
87            current: self.first,
88            _phantom: PhantomData,
89        }
90    }
91}
92
93impl<'a> Iterator for MtPropertyListIter<'a> {
94    type Item = &'a MtProperty;
95
96    fn next(&mut self) -> Option<Self::Item> {
97        if self.current.is_null() {
98            return None;
99        }
100        let current = unsafe { &*self.current };
101        self.current = current.next;
102        Some(current)
103    }
104}
105
106impl<'a> IntoIterator for &'a MtPropertyList {
107    type Item = &'a MtProperty;
108    type IntoIter = MtPropertyListIter<'a>;
109
110    fn into_iter(self) -> Self::IntoIter {
111        self.iter()
112    }
113}