Skip to main content

rusteal_core/
containers.rs

1// Container wrappers for UE TArray/TMap/TSet.
2// These are lightweight handles that operate on container data living
3// inside UObject memory. All element access goes through the ContainerApi
4// FFI sub-table, which inspects FProperty to dispatch type-correct operations.
5
6use std::collections::{HashMap, HashSet};
7use std::hash::Hash;
8use std::marker::PhantomData;
9
10use rusteal_ffi::{FPropertyHandle, UObjectHandle, RustealErrorCode};
11
12/// Maximum buffer size across all ContainerElement implementations.
13/// Primitives use 1–8 bytes; String and OwnedStruct use 4096.
14/// Used for stack-allocated FFI transport buffers to avoid heap allocation.
15const MAX_ELEM_BUF: usize = 4096;
16
17use crate::error::{check_ffi, ffi_infallible, RustealError, RustealResult};
18use crate::ffi_dispatch;
19use crate::object_ref::UObjectRef;
20use crate::struct_ref::UStructRef;
21use crate::traits::{UeClass, UeStruct};
22
23// ---------------------------------------------------------------------------
24// ContainerElement trait
25// ---------------------------------------------------------------------------
26
27/// Trait for types that can be stored in UE containers.
28///
29/// # Safety
30/// `BUF_SIZE` must match what the C++ side expects for this element type.
31/// `read_from_buf` must correctly interpret the bytes written by C++'s
32/// `ReadElement`, and `write_to_buf` must produce bytes that C++'s
33/// `WriteElement` can interpret.
34pub unsafe trait ContainerElement: Sized {
35    /// Buffer size for FFI transport. Must be large enough for the C++ side
36    /// to write the element value.
37    const BUF_SIZE: u32;
38
39    /// Whether this type can be bulk-copied as raw bytes (no per-element framing).
40    /// True for fixed-size primitives (bool, integers, floats) and FName.
41    const RAW_COPYABLE: bool = false;
42
43    /// Interpret bytes from the C++ side into a Rust value.
44    ///
45    /// # Safety
46    /// `buf` must point to at least `written` valid bytes produced by C++
47    /// `ReadElement`.
48    unsafe fn read_from_buf(buf: *const u8, written: u32) -> Self;
49
50    /// Write this value into a buffer for C++ `WriteElement` to consume.
51    /// Returns the number of bytes written.
52    ///
53    /// # Safety
54    /// `buf` must point to at least `BUF_SIZE` writable bytes.
55    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32;
56}
57
58// ---------------------------------------------------------------------------
59// ContainerElement impls for primitives
60// ---------------------------------------------------------------------------
61
62macro_rules! impl_container_element_primitive {
63    ($ty:ty) => {
64        unsafe impl ContainerElement for $ty {
65            const BUF_SIZE: u32 = std::mem::size_of::<$ty>() as u32;
66            const RAW_COPYABLE: bool = true;
67
68            #[inline]
69            unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self { unsafe {
70                (buf as *const $ty).read_unaligned()
71            }}
72
73            #[inline]
74            unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
75                (buf as *mut $ty).write_unaligned(*self);
76                std::mem::size_of::<$ty>() as u32
77            }}
78        }
79    };
80}
81
82impl_container_element_primitive!(bool);
83impl_container_element_primitive!(i8);
84impl_container_element_primitive!(u8);
85impl_container_element_primitive!(i16);
86impl_container_element_primitive!(u16);
87impl_container_element_primitive!(i32);
88impl_container_element_primitive!(u32);
89impl_container_element_primitive!(i64);
90impl_container_element_primitive!(u64);
91impl_container_element_primitive!(f32);
92impl_container_element_primitive!(f64);
93
94// UObjectHandle: 8-byte pointer, raw memcpy in C++
95unsafe impl ContainerElement for UObjectHandle {
96    const BUF_SIZE: u32 = std::mem::size_of::<UObjectHandle>() as u32;
97
98    #[inline]
99    unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self { unsafe {
100        (buf as *const UObjectHandle).read_unaligned()
101    }}
102
103    #[inline]
104    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
105        (buf as *mut UObjectHandle).write_unaligned(*self);
106        std::mem::size_of::<UObjectHandle>() as u32
107    }}
108}
109
110// FNameHandle: 8-byte uint64, raw memcpy in C++
111unsafe impl ContainerElement for rusteal_ffi::FNameHandle {
112    const BUF_SIZE: u32 = 8;
113    const RAW_COPYABLE: bool = true;
114
115    #[inline]
116    unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self { unsafe {
117        (buf as *const rusteal_ffi::FNameHandle).read_unaligned()
118    }}
119
120    #[inline]
121    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
122        (buf as *mut rusteal_ffi::FNameHandle).write_unaligned(*self);
123        8
124    }}
125}
126
127// UObjectRef<T>: delegates to UObjectHandle (8-byte pointer)
128unsafe impl<T: UeClass> ContainerElement for UObjectRef<T> {
129    const BUF_SIZE: u32 = std::mem::size_of::<UObjectHandle>() as u32;
130
131    #[inline]
132    unsafe fn read_from_buf(buf: *const u8, _written: u32) -> Self { unsafe {
133        let handle = (buf as *const UObjectHandle).read_unaligned();
134        UObjectRef::from_raw(handle)
135    }}
136
137    #[inline]
138    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
139        (buf as *mut UObjectHandle).write_unaligned(self.raw());
140        std::mem::size_of::<UObjectHandle>() as u32
141    }}
142}
143
144// String: C++ uses [u32 len][utf8 bytes] format
145unsafe impl ContainerElement for String {
146    // Max buffer: 4 bytes length prefix + up to 4092 bytes of UTF-8 data
147    const BUF_SIZE: u32 = 4096;
148
149    unsafe fn read_from_buf(buf: *const u8, written: u32) -> Self { unsafe {
150        if written < 4 {
151            return String::new();
152        }
153        let len = (buf as *const u32).read_unaligned() as usize;
154        let data_len = (written as usize).saturating_sub(4).min(len);
155        let slice = std::slice::from_raw_parts(buf.add(4), data_len);
156        String::from_utf8_lossy(slice).into_owned()
157    }}
158
159    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
160        let bytes = self.as_bytes();
161        let len = bytes.len() as u32;
162        (buf as *mut u32).write_unaligned(len);
163        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf.add(4), bytes.len());
164        4 + len
165    }}
166}
167
168// ---------------------------------------------------------------------------
169// UeArray<T>
170// ---------------------------------------------------------------------------
171
172/// A view into a UE `TArray<T>` property on a UObject.
173///
174/// This is a lightweight `Copy` handle — it does not own the data.
175/// All operations go through FFI calls to the C++ container API.
176#[derive(Clone, Copy)]
177pub struct UeArray<T: ContainerElement> {
178    owner: UObjectHandle,
179    prop: FPropertyHandle,
180    _marker: PhantomData<T>,
181}
182
183impl<T: ContainerElement> UeArray<T> {
184    /// Create a new array view from an owner object handle and a property handle.
185    #[inline]
186    pub fn new(owner: UObjectHandle, prop: FPropertyHandle) -> Self {
187        UeArray {
188            owner,
189            prop,
190            _marker: PhantomData,
191        }
192    }
193
194    /// Returns the number of elements in the array.
195    pub fn len(&self) -> RustealResult<usize> {
196        let n = unsafe { ffi_dispatch::container_array_len(self.owner, self.prop) };
197        if n < 0 {
198            return Err(RustealError::ObjectDestroyed);
199        }
200        Ok(n as usize)
201    }
202
203    /// Returns true if the array is empty.
204    pub fn is_empty(&self) -> RustealResult<bool> {
205        Ok(self.len()? == 0)
206    }
207
208    /// Get the element at `index`.
209    pub fn get(&self, index: usize) -> RustealResult<T> {
210        let mut buf = [0u8; MAX_ELEM_BUF];
211        let mut written: u32 = 0;
212        check_ffi(unsafe {
213            ffi_dispatch::container_array_get(
214                self.owner,
215                self.prop,
216                index as i32,
217                buf.as_mut_ptr(),
218                T::BUF_SIZE,
219                &mut written,
220            )
221        })?;
222        Ok(unsafe { T::read_from_buf(buf.as_ptr(), written) })
223    }
224
225    /// Set the element at `index`.
226    pub fn set(&self, index: usize, val: &T) -> RustealResult<()> {
227        let mut buf = [0u8; MAX_ELEM_BUF];
228        // SAFETY: buf is freshly allocated with BUF_SIZE bytes.
229        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
230        check_ffi(unsafe {
231            ffi_dispatch::container_array_set(
232                self.owner,
233                self.prop,
234                index as i32,
235                buf.as_ptr(),
236                written,
237            )
238        })
239    }
240
241    /// Append an element to the end of the array.
242    pub fn push(&self, val: &T) -> RustealResult<()> {
243        let mut buf = [0u8; MAX_ELEM_BUF];
244        // SAFETY: buf is freshly allocated with BUF_SIZE bytes.
245        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
246        check_ffi(unsafe {
247            ffi_dispatch::container_array_add(self.owner, self.prop, buf.as_ptr(), written)
248        })
249    }
250
251    /// Remove the element at `index`, shifting subsequent elements down.
252    pub fn remove(&self, index: usize) -> RustealResult<()> {
253        check_ffi(unsafe {
254            ffi_dispatch::container_array_remove(self.owner, self.prop, index as i32)
255        })
256    }
257
258    /// Remove all elements from the array.
259    pub fn clear(&self) -> RustealResult<()> {
260        check_ffi(unsafe { ffi_dispatch::container_array_clear(self.owner, self.prop) })
261    }
262
263    /// Returns an iterator over the elements.
264    pub fn iter(&self) -> UeArrayIter<'_, T> {
265        let len = self.len().unwrap_or(0);
266        UeArrayIter {
267            array: self,
268            index: 0,
269            len,
270        }
271    }
272}
273
274impl<'a, T: ContainerElement> IntoIterator for &'a UeArray<T> {
275    type Item = RustealResult<T>;
276    type IntoIter = UeArrayIter<'a, T>;
277
278    fn into_iter(self) -> Self::IntoIter {
279        self.iter()
280    }
281}
282
283/// Iterator over `UeArray<T>` elements.
284pub struct UeArrayIter<'a, T: ContainerElement> {
285    array: &'a UeArray<T>,
286    index: usize,
287    len: usize,
288}
289
290impl<T: ContainerElement> Iterator for UeArrayIter<'_, T> {
291    type Item = RustealResult<T>;
292
293    fn next(&mut self) -> Option<Self::Item> {
294        if self.index >= self.len {
295            return None;
296        }
297        let result = self.array.get(self.index);
298        self.index += 1;
299        Some(result)
300    }
301
302    fn size_hint(&self) -> (usize, Option<usize>) {
303        let remaining = self.len.saturating_sub(self.index);
304        (remaining, Some(remaining))
305    }
306}
307
308impl<T: ContainerElement> ExactSizeIterator for UeArrayIter<'_, T> {}
309
310// ---------------------------------------------------------------------------
311// Bulk copy helper
312// ---------------------------------------------------------------------------
313
314/// Perform a bulk copy FFI call with automatic retry on BufferTooSmall.
315fn bulk_copy_with_retry(
316    estimate: usize,
317    call: impl Fn(*mut u8, u32, *mut u32, *mut i32) -> RustealErrorCode,
318) -> RustealResult<(Vec<u8>, i32)> {
319    let mut buf = vec![0u8; estimate.max(64)];
320    let mut written: u32 = 0;
321    let mut count: i32 = 0;
322    let code = call(buf.as_mut_ptr(), buf.len() as u32, &mut written, &mut count);
323    if code == RustealErrorCode::BufferTooSmall {
324        // Retry with the size hint from C++
325        let needed = (written as usize).max(buf.len() * 2);
326        buf.resize(needed, 0);
327        check_ffi(call(
328            buf.as_mut_ptr(),
329            buf.len() as u32,
330            &mut written,
331            &mut count,
332        ))?;
333    } else {
334        check_ffi(code)?;
335    }
336    buf.truncate(written as usize);
337    Ok((buf, count))
338}
339
340// ---------------------------------------------------------------------------
341// Bulk iterators
342// ---------------------------------------------------------------------------
343
344/// Iterator that owns a pre-fetched buffer from a single bulk FFI call.
345/// Yields elements without further FFI calls.
346///
347/// Supports two modes:
348/// - **Framed** (`raw_elem_size == 0`): `[u32 written][data]` per element
349/// - **Raw** (`raw_elem_size > 0`): contiguous fixed-size elements, no framing
350pub struct BulkArrayIter<T: ContainerElement> {
351    buf: Vec<u8>,
352    count: usize,
353    index: usize,
354    offset: usize,
355    raw_elem_size: usize, // 0 = framed, >0 = raw stride
356    _marker: PhantomData<T>,
357}
358
359impl<T: ContainerElement> Iterator for BulkArrayIter<T> {
360    type Item = T;
361
362    fn next(&mut self) -> Option<T> {
363        if self.index >= self.count {
364            return None;
365        }
366        if self.raw_elem_size > 0 {
367            // Raw mode: elements are contiguous with fixed stride
368            let elem = unsafe {
369                T::read_from_buf(
370                    self.buf.as_ptr().add(self.offset),
371                    self.raw_elem_size as u32,
372                )
373            };
374            self.offset += self.raw_elem_size;
375            self.index += 1;
376            Some(elem)
377        } else {
378            // Framed mode: [u32 written][data] per element
379            if self.offset + 4 > self.buf.len() {
380                return None;
381            }
382            let written = u32::from_ne_bytes(
383                self.buf[self.offset..self.offset + 4].try_into().unwrap(),
384            );
385            self.offset += 4;
386            let elem =
387                unsafe { T::read_from_buf(self.buf.as_ptr().add(self.offset), written) };
388            self.offset += written as usize;
389            self.index += 1;
390            Some(elem)
391        }
392    }
393
394    fn size_hint(&self) -> (usize, Option<usize>) {
395        let r = self.count - self.index;
396        (r, Some(r))
397    }
398}
399
400impl<T: ContainerElement> ExactSizeIterator for BulkArrayIter<T> {}
401
402/// Bulk iterator over `TMap<K,V>` key-value pairs. Single FFI call.
403pub struct BulkMapIter<K: ContainerElement, V: ContainerElement> {
404    buf: Vec<u8>,
405    count: usize,
406    index: usize,
407    offset: usize,
408    _marker: PhantomData<(K, V)>,
409}
410
411impl<K: ContainerElement, V: ContainerElement> Iterator for BulkMapIter<K, V> {
412    type Item = (K, V);
413
414    fn next(&mut self) -> Option<(K, V)> {
415        if self.index >= self.count {
416            return None;
417        }
418        // Key: [u32 written][data]
419        if self.offset + 4 > self.buf.len() {
420            return None;
421        }
422        let key_written = u32::from_ne_bytes(
423            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
424        );
425        self.offset += 4;
426        let key =
427            unsafe { K::read_from_buf(self.buf.as_ptr().add(self.offset), key_written) };
428        self.offset += key_written as usize;
429
430        // Value: [u32 written][data]
431        if self.offset + 4 > self.buf.len() {
432            return None;
433        }
434        let val_written = u32::from_ne_bytes(
435            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
436        );
437        self.offset += 4;
438        let val =
439            unsafe { V::read_from_buf(self.buf.as_ptr().add(self.offset), val_written) };
440        self.offset += val_written as usize;
441
442        self.index += 1;
443        Some((key, val))
444    }
445
446    fn size_hint(&self) -> (usize, Option<usize>) {
447        let r = self.count - self.index;
448        (r, Some(r))
449    }
450}
451
452impl<K: ContainerElement, V: ContainerElement> ExactSizeIterator for BulkMapIter<K, V> {}
453
454/// Bulk iterator over `TSet<T>` elements. Single FFI call.
455pub struct BulkSetIter<T: ContainerElement> {
456    buf: Vec<u8>,
457    count: usize,
458    index: usize,
459    offset: usize,
460    _marker: PhantomData<T>,
461}
462
463impl<T: ContainerElement> Iterator for BulkSetIter<T> {
464    type Item = T;
465
466    fn next(&mut self) -> Option<T> {
467        if self.index >= self.count {
468            return None;
469        }
470        if self.offset + 4 > self.buf.len() {
471            return None;
472        }
473        let written = u32::from_ne_bytes(
474            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
475        );
476        self.offset += 4;
477        let elem = unsafe { T::read_from_buf(self.buf.as_ptr().add(self.offset), written) };
478        self.offset += written as usize;
479        self.index += 1;
480        Some(elem)
481    }
482
483    fn size_hint(&self) -> (usize, Option<usize>) {
484        let r = self.count - self.index;
485        (r, Some(r))
486    }
487}
488
489impl<T: ContainerElement> ExactSizeIterator for BulkSetIter<T> {}
490
491// ---------------------------------------------------------------------------
492// UeArray bulk methods
493// ---------------------------------------------------------------------------
494
495impl<T: ContainerElement> UeArray<T> {
496    /// Bulk-copy all elements to a `Vec<T>` in a single FFI call.
497    pub fn to_vec(&self) -> RustealResult<Vec<T>> {
498        let len = self.len()?;
499        if len == 0 {
500            return Ok(Vec::new());
501        }
502        let owner = self.owner;
503        let prop = self.prop;
504        // Raw-copyable types have no per-element framing overhead
505        let estimate = if T::RAW_COPYABLE {
506            len * T::BUF_SIZE as usize
507        } else {
508            len * (T::BUF_SIZE as usize + 4)
509        };
510        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
511            ffi_dispatch::container_array_copy_all(owner, prop, out, size, written, cnt)
512        })?;
513        // Negative count = raw format from C++
514        let (actual_count, raw_elem_size) = if count < 0 {
515            ((-count) as usize, T::BUF_SIZE as usize)
516        } else {
517            (count as usize, 0)
518        };
519        Ok(BulkArrayIter::<T> {
520            buf,
521            count: actual_count,
522            index: 0,
523            offset: 0,
524            raw_elem_size,
525            _marker: PhantomData,
526        }
527        .collect())
528    }
529
530    /// Bulk-fetch all elements as a lazy iterator (single FFI call).
531    pub fn bulk_iter(&self) -> RustealResult<BulkArrayIter<T>> {
532        let len = self.len()?;
533        if len == 0 {
534            return Ok(BulkArrayIter {
535                buf: Vec::new(),
536                count: 0,
537                index: 0,
538                offset: 0,
539                raw_elem_size: 0,
540                _marker: PhantomData,
541            });
542        }
543        let owner = self.owner;
544        let prop = self.prop;
545        let estimate = if T::RAW_COPYABLE {
546            len * T::BUF_SIZE as usize
547        } else {
548            len * (T::BUF_SIZE as usize + 4)
549        };
550        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
551            ffi_dispatch::container_array_copy_all(owner, prop, out, size, written, cnt)
552        })?;
553        let (actual_count, raw_elem_size) = if count < 0 {
554            ((-count) as usize, T::BUF_SIZE as usize)
555        } else {
556            (count as usize, 0)
557        };
558        Ok(BulkArrayIter {
559            buf,
560            count: actual_count,
561            index: 0,
562            offset: 0,
563            raw_elem_size,
564            _marker: PhantomData,
565        })
566    }
567
568    /// Replace all array elements from a slice in a single FFI call.
569    pub fn set_all(&self, items: &[T]) -> RustealResult<()> {
570        if items.is_empty() {
571            return self.clear();
572        }
573        if T::RAW_COPYABLE {
574            // Raw format: contiguous elements, no per-element framing
575            let elem_size = T::BUF_SIZE as usize;
576            let mut buf = vec![0u8; items.len() * elem_size];
577            for (i, item) in items.iter().enumerate() {
578                unsafe { item.write_to_buf(buf.as_mut_ptr().add(i * elem_size)); }
579            }
580            // Negative count signals raw format to C++
581            check_ffi(unsafe {
582                ffi_dispatch::container_array_set_all(
583                    self.owner,
584                    self.prop,
585                    buf.as_ptr(),
586                    buf.len() as u32,
587                    -(items.len() as i32),
588                )
589            })
590        } else {
591            // Framed format: [u32 written][data] per element
592            let mut buf = Vec::with_capacity(items.len() * (T::BUF_SIZE as usize + 4));
593            let mut elem_buf = [0u8; MAX_ELEM_BUF];
594            for item in items {
595                let written = unsafe { item.write_to_buf(elem_buf.as_mut_ptr()) };
596                buf.extend_from_slice(&written.to_ne_bytes());
597                buf.extend_from_slice(&elem_buf[..written as usize]);
598            }
599            check_ffi(unsafe {
600                ffi_dispatch::container_array_set_all(
601                    self.owner,
602                    self.prop,
603                    buf.as_ptr(),
604                    buf.len() as u32,
605                    items.len() as i32,
606                )
607            })
608        }
609    }
610}
611
612// ---------------------------------------------------------------------------
613// UeMap<K, V>
614// ---------------------------------------------------------------------------
615
616/// A view into a UE `TMap<K, V>` property on a UObject.
617#[derive(Clone, Copy)]
618pub struct UeMap<K: ContainerElement, V: ContainerElement> {
619    owner: UObjectHandle,
620    prop: FPropertyHandle,
621    _marker: PhantomData<(K, V)>,
622}
623
624impl<K: ContainerElement, V: ContainerElement> UeMap<K, V> {
625    #[inline]
626    pub fn new(owner: UObjectHandle, prop: FPropertyHandle) -> Self {
627        UeMap {
628            owner,
629            prop,
630            _marker: PhantomData,
631        }
632    }
633
634    /// Returns the number of key-value pairs in the map.
635    pub fn len(&self) -> RustealResult<usize> {
636        let n = unsafe { ffi_dispatch::container_map_len(self.owner, self.prop) };
637        if n < 0 {
638            return Err(RustealError::ObjectDestroyed);
639        }
640        Ok(n as usize)
641    }
642
643    pub fn is_empty(&self) -> RustealResult<bool> {
644        Ok(self.len()? == 0)
645    }
646
647    /// Look up a value by key. Returns `Err(PropertyNotFound)` if the key
648    /// is not in the map.
649    pub fn find(&self, key: &K) -> RustealResult<V> {
650        let mut key_buf = [0u8; MAX_ELEM_BUF];
651        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
652        let mut val_buf = [0u8; MAX_ELEM_BUF];
653        let mut val_written: u32 = 0;
654
655        check_ffi(unsafe {
656            ffi_dispatch::container_map_find(
657                self.owner,
658                self.prop,
659                key_buf.as_ptr(),
660                key_written,
661                val_buf.as_mut_ptr(),
662                V::BUF_SIZE,
663                &mut val_written,
664            )
665        })?;
666        Ok(unsafe { V::read_from_buf(val_buf.as_ptr(), val_written) })
667    }
668
669    /// Insert or replace a key-value pair.
670    pub fn add(&self, key: &K, val: &V) -> RustealResult<()> {
671        let mut key_buf = [0u8; MAX_ELEM_BUF];
672        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
673        let mut val_buf = [0u8; MAX_ELEM_BUF];
674        let val_written = unsafe { val.write_to_buf(val_buf.as_mut_ptr()) };
675
676        check_ffi(unsafe {
677            ffi_dispatch::container_map_add(
678                self.owner,
679                self.prop,
680                key_buf.as_ptr(),
681                key_written,
682                val_buf.as_ptr(),
683                val_written,
684            )
685        })
686    }
687
688    /// Remove a key from the map.
689    pub fn remove(&self, key: &K) -> RustealResult<()> {
690        let mut key_buf = [0u8; MAX_ELEM_BUF];
691        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
692
693        check_ffi(unsafe {
694            ffi_dispatch::container_map_remove(
695                self.owner,
696                self.prop,
697                key_buf.as_ptr(),
698                key_written,
699            )
700        })
701    }
702
703    /// Remove all key-value pairs.
704    pub fn clear(&self) -> RustealResult<()> {
705        check_ffi(unsafe { ffi_dispatch::container_map_clear(self.owner, self.prop) })
706    }
707
708    /// Get the key-value pair at logical index (for iteration).
709    pub fn get_pair(&self, logical_index: usize) -> RustealResult<(K, V)> {
710        let mut key_buf = [0u8; MAX_ELEM_BUF];
711        let mut key_written: u32 = 0;
712        let mut val_buf = [0u8; MAX_ELEM_BUF];
713        let mut val_written: u32 = 0;
714
715        check_ffi(unsafe {
716            ffi_dispatch::container_map_get_pair(
717                self.owner,
718                self.prop,
719                logical_index as i32,
720                key_buf.as_mut_ptr(),
721                K::BUF_SIZE,
722                &mut key_written,
723                val_buf.as_mut_ptr(),
724                V::BUF_SIZE,
725                &mut val_written,
726            )
727        })?;
728        Ok(unsafe {
729            (
730                K::read_from_buf(key_buf.as_ptr(), key_written),
731                V::read_from_buf(val_buf.as_ptr(), val_written),
732            )
733        })
734    }
735
736    /// Returns an iterator over key-value pairs.
737    pub fn iter(&self) -> UeMapIter<'_, K, V> {
738        let len = self.len().unwrap_or(0);
739        UeMapIter {
740            map: self,
741            index: 0,
742            len,
743        }
744    }
745}
746
747impl<'a, K: ContainerElement, V: ContainerElement> IntoIterator for &'a UeMap<K, V> {
748    type Item = RustealResult<(K, V)>;
749    type IntoIter = UeMapIter<'a, K, V>;
750
751    fn into_iter(self) -> Self::IntoIter {
752        self.iter()
753    }
754}
755
756/// Iterator over `UeMap<K, V>` key-value pairs.
757pub struct UeMapIter<'a, K: ContainerElement, V: ContainerElement> {
758    map: &'a UeMap<K, V>,
759    index: usize,
760    len: usize,
761}
762
763impl<K: ContainerElement, V: ContainerElement> Iterator for UeMapIter<'_, K, V> {
764    type Item = RustealResult<(K, V)>;
765
766    fn next(&mut self) -> Option<Self::Item> {
767        if self.index >= self.len {
768            return None;
769        }
770        let result = self.map.get_pair(self.index);
771        self.index += 1;
772        Some(result)
773    }
774
775    fn size_hint(&self) -> (usize, Option<usize>) {
776        let remaining = self.len.saturating_sub(self.index);
777        (remaining, Some(remaining))
778    }
779}
780
781impl<K: ContainerElement, V: ContainerElement> ExactSizeIterator for UeMapIter<'_, K, V> {}
782
783// ---------------------------------------------------------------------------
784// UeMap bulk methods
785// ---------------------------------------------------------------------------
786
787impl<K: ContainerElement, V: ContainerElement> UeMap<K, V> {
788    /// Bulk-fetch all key-value pairs as a lazy iterator (single FFI call).
789    pub fn bulk_iter(&self) -> RustealResult<BulkMapIter<K, V>> {
790        let len = self.len()?;
791        if len == 0 {
792            return Ok(BulkMapIter {
793                buf: Vec::new(),
794                count: 0,
795                index: 0,
796                offset: 0,
797                _marker: PhantomData,
798            });
799        }
800        let owner = self.owner;
801        let prop = self.prop;
802        let estimate = len * (K::BUF_SIZE as usize + V::BUF_SIZE as usize + 8);
803        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
804            ffi_dispatch::container_map_copy_all(owner, prop, out, size, written, cnt)
805        })?;
806        Ok(BulkMapIter {
807            buf,
808            count: count as usize,
809            index: 0,
810            offset: 0,
811            _marker: PhantomData,
812        })
813    }
814}
815
816impl<K: ContainerElement + Hash + Eq, V: ContainerElement> UeMap<K, V> {
817    /// Bulk-copy all key-value pairs to a `HashMap` in a single FFI call.
818    pub fn to_hash_map(&self) -> RustealResult<HashMap<K, V>> {
819        let iter = self.bulk_iter()?;
820        let count = iter.count;
821        let mut map = HashMap::with_capacity(count);
822        for (k, v) in iter {
823            map.insert(k, v);
824        }
825        Ok(map)
826    }
827}
828
829// ---------------------------------------------------------------------------
830// UeSet<T>
831// ---------------------------------------------------------------------------
832
833/// A view into a UE `TSet<T>` property on a UObject.
834#[derive(Clone, Copy)]
835pub struct UeSet<T: ContainerElement> {
836    owner: UObjectHandle,
837    prop: FPropertyHandle,
838    _marker: PhantomData<T>,
839}
840
841impl<T: ContainerElement> UeSet<T> {
842    #[inline]
843    pub fn new(owner: UObjectHandle, prop: FPropertyHandle) -> Self {
844        UeSet {
845            owner,
846            prop,
847            _marker: PhantomData,
848        }
849    }
850
851    /// Returns the number of elements in the set.
852    pub fn len(&self) -> RustealResult<usize> {
853        let n = unsafe { ffi_dispatch::container_set_len(self.owner, self.prop) };
854        if n < 0 {
855            return Err(RustealError::ObjectDestroyed);
856        }
857        Ok(n as usize)
858    }
859
860    pub fn is_empty(&self) -> RustealResult<bool> {
861        Ok(self.len()? == 0)
862    }
863
864    /// Check if the set contains an element.
865    pub fn contains(&self, val: &T) -> RustealResult<bool> {
866        let mut buf = [0u8; MAX_ELEM_BUF];
867        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
868        Ok(unsafe {
869            ffi_dispatch::container_set_contains(self.owner, self.prop, buf.as_ptr(), written)
870        })
871    }
872
873    /// Add an element to the set.
874    pub fn add(&self, val: &T) -> RustealResult<()> {
875        let mut buf = [0u8; MAX_ELEM_BUF];
876        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
877        check_ffi(unsafe {
878            ffi_dispatch::container_set_add(self.owner, self.prop, buf.as_ptr(), written)
879        })
880    }
881
882    /// Remove an element from the set.
883    pub fn remove(&self, val: &T) -> RustealResult<()> {
884        let mut buf = [0u8; MAX_ELEM_BUF];
885        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
886        check_ffi(unsafe {
887            ffi_dispatch::container_set_remove(self.owner, self.prop, buf.as_ptr(), written)
888        })
889    }
890
891    /// Remove all elements from the set.
892    pub fn clear(&self) -> RustealResult<()> {
893        check_ffi(unsafe { ffi_dispatch::container_set_clear(self.owner, self.prop) })
894    }
895
896    /// Get the element at logical index (for iteration).
897    pub fn get_element(&self, logical_index: usize) -> RustealResult<T> {
898        let mut buf = [0u8; MAX_ELEM_BUF];
899        let mut written: u32 = 0;
900        check_ffi(unsafe {
901            ffi_dispatch::container_set_get_element(
902                self.owner,
903                self.prop,
904                logical_index as i32,
905                buf.as_mut_ptr(),
906                T::BUF_SIZE,
907                &mut written,
908            )
909        })?;
910        Ok(unsafe { T::read_from_buf(buf.as_ptr(), written) })
911    }
912
913    /// Returns an iterator over the elements.
914    pub fn iter(&self) -> UeSetIter<'_, T> {
915        let len = self.len().unwrap_or(0);
916        UeSetIter {
917            set: self,
918            index: 0,
919            len,
920        }
921    }
922}
923
924impl<'a, T: ContainerElement> IntoIterator for &'a UeSet<T> {
925    type Item = RustealResult<T>;
926    type IntoIter = UeSetIter<'a, T>;
927
928    fn into_iter(self) -> Self::IntoIter {
929        self.iter()
930    }
931}
932
933/// Iterator over `UeSet<T>` elements.
934pub struct UeSetIter<'a, T: ContainerElement> {
935    set: &'a UeSet<T>,
936    index: usize,
937    len: usize,
938}
939
940impl<T: ContainerElement> Iterator for UeSetIter<'_, T> {
941    type Item = RustealResult<T>;
942
943    fn next(&mut self) -> Option<Self::Item> {
944        if self.index >= self.len {
945            return None;
946        }
947        let result = self.set.get_element(self.index);
948        self.index += 1;
949        Some(result)
950    }
951
952    fn size_hint(&self) -> (usize, Option<usize>) {
953        let remaining = self.len.saturating_sub(self.index);
954        (remaining, Some(remaining))
955    }
956}
957
958impl<T: ContainerElement> ExactSizeIterator for UeSetIter<'_, T> {}
959
960// ---------------------------------------------------------------------------
961// UeSet bulk methods
962// ---------------------------------------------------------------------------
963
964impl<T: ContainerElement> UeSet<T> {
965    /// Bulk-fetch all elements as a lazy iterator (single FFI call).
966    pub fn bulk_iter(&self) -> RustealResult<BulkSetIter<T>> {
967        let len = self.len()?;
968        if len == 0 {
969            return Ok(BulkSetIter {
970                buf: Vec::new(),
971                count: 0,
972                index: 0,
973                offset: 0,
974                _marker: PhantomData,
975            });
976        }
977        let owner = self.owner;
978        let prop = self.prop;
979        let estimate = len * (T::BUF_SIZE as usize + 4);
980        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
981            ffi_dispatch::container_set_copy_all(owner, prop, out, size, written, cnt)
982        })?;
983        Ok(BulkSetIter {
984            buf,
985            count: count as usize,
986            index: 0,
987            offset: 0,
988            _marker: PhantomData,
989        })
990    }
991}
992
993impl<T: ContainerElement + Hash + Eq> UeSet<T> {
994    /// Bulk-copy all elements to a `HashSet` in a single FFI call.
995    pub fn to_hash_set(&self) -> RustealResult<HashSet<T>> {
996        let iter = self.bulk_iter()?;
997        let count = iter.count;
998        let mut set = HashSet::with_capacity(count);
999        for elem in iter {
1000            set.insert(elem);
1001        }
1002        Ok(set)
1003    }
1004}
1005
1006// ---------------------------------------------------------------------------
1007// OwnedStruct<T>: owned copy of struct data from a container
1008// ---------------------------------------------------------------------------
1009
1010/// An owned copy of UE struct data retrieved from a container.
1011///
1012/// Since UE structs are opaque (their layout is managed by C++), this type
1013/// holds the raw bytes copied from the container. Use [`as_ref`](Self::as_ref)
1014/// to get a `UStructRef<T>` for property access.
1015pub struct OwnedStruct<T: UeStruct> {
1016    data: Vec<u8>,
1017    needs_destroy: bool,
1018    _marker: PhantomData<T>,
1019}
1020
1021impl<T: UeStruct> Default for OwnedStruct<T> {
1022    fn default() -> Self {
1023        Self::new()
1024    }
1025}
1026
1027impl<T: UeStruct> OwnedStruct<T> {
1028    /// Allocate a new struct initialized via C++ default constructor.
1029    ///
1030    /// Uses the UE reflection system to determine the struct's size,
1031    /// allocates a zero-filled buffer, then calls `UScriptStruct::InitializeStruct`
1032    /// to properly construct non-trivial members (TArray, FString, etc.).
1033    /// The struct is destroyed via `UScriptStruct::DestroyStruct` on drop.
1034    pub fn new() -> Self {
1035        let ustruct = T::static_struct();
1036        let size = unsafe { ffi_dispatch::reflection_get_struct_size(ustruct) };
1037        debug_assert!(size > 0, "get_struct_size returned 0 for {}", std::any::type_name::<T>());
1038
1039        let mut data = vec![0u8; size as usize];
1040        ffi_infallible(unsafe {
1041            ffi_dispatch::reflection_initialize_struct(ustruct, data.as_mut_ptr())
1042        });
1043        OwnedStruct {
1044            data,
1045            needs_destroy: true,
1046            _marker: PhantomData,
1047        }
1048    }
1049
1050    /// Create from raw bytes (e.g., copied from a container element).
1051    ///
1052    /// The data is assumed to already be initialized by C++ — no destructor
1053    /// will be called on drop.
1054    pub fn from_bytes(data: Vec<u8>) -> Self {
1055        OwnedStruct {
1056            data,
1057            needs_destroy: false,
1058            _marker: PhantomData,
1059        }
1060    }
1061
1062    /// Get a `UStructRef<T>` for property access on this struct data.
1063    pub fn as_ref(&self) -> UStructRef<T> {
1064        unsafe { UStructRef::from_raw(self.data.as_ptr() as *mut u8) }
1065    }
1066
1067    /// Get the raw bytes of the struct data.
1068    pub fn as_bytes(&self) -> &[u8] {
1069        &self.data
1070    }
1071
1072    /// Get the raw bytes of the struct data as an owned Vec.
1073    pub fn to_bytes(&self) -> Vec<u8> {
1074        self.data.clone()
1075    }
1076}
1077
1078impl<T: UeStruct> Clone for OwnedStruct<T> {
1079    fn clone(&self) -> Self {
1080        OwnedStruct {
1081            data: self.data.clone(),
1082            needs_destroy: false,
1083            _marker: PhantomData,
1084        }
1085    }
1086}
1087
1088impl<T: UeStruct> Drop for OwnedStruct<T> {
1089    fn drop(&mut self) {
1090        if self.needs_destroy {
1091            unsafe {
1092                ffi_dispatch::reflection_destroy_struct(
1093                    T::static_struct(),
1094                    self.data.as_mut_ptr(),
1095                );
1096            }
1097        }
1098        // Vec<u8> is always freed automatically.
1099    }
1100}
1101
1102impl<T: UeStruct> std::fmt::Debug for OwnedStruct<T> {
1103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1104        f.debug_struct("OwnedStruct").field("size", &self.data.len()).finish()
1105    }
1106}
1107
1108// ContainerElement for OwnedStruct<T>: uses a fixed 4096-byte buffer.
1109// The C++ side copies struct data via CopyScriptStruct; we store the
1110// exact number of bytes written.
1111unsafe impl<T: UeStruct> ContainerElement for OwnedStruct<T> {
1112    const BUF_SIZE: u32 = 4096;
1113
1114    unsafe fn read_from_buf(buf: *const u8, written: u32) -> Self { unsafe {
1115        let data = vec![0u8; written as usize];
1116        std::ptr::copy_nonoverlapping(buf, data.as_ptr() as *mut u8, written as usize);
1117        OwnedStruct::from_bytes(data)
1118    }}
1119
1120    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
1121        let bytes = self.to_bytes();
1122        let len = bytes.len();
1123        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buf, len);
1124        len as u32
1125    }}
1126}