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