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}