Skip to main content

uika_runtime/
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 uika_ffi::{FPropertyHandle, UObjectHandle, UikaErrorCode};
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::api::api;
18use crate::error::{check_ffi, ffi_infallible, UikaError, UikaResult};
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 uika_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 uika_ffi::FNameHandle).read_unaligned()
118    }}
119
120    #[inline]
121    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
122        (buf as *mut uika_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) -> UikaResult<usize> {
196        let n = unsafe { ((*api().container).array_len)(self.owner, self.prop) };
197        if n < 0 {
198            return Err(UikaError::ObjectDestroyed);
199        }
200        Ok(n as usize)
201    }
202
203    /// Returns true if the array is empty.
204    pub fn is_empty(&self) -> UikaResult<bool> {
205        Ok(self.len()? == 0)
206    }
207
208    /// Get the element at `index`.
209    pub fn get(&self, index: usize) -> UikaResult<T> {
210        let mut buf = [0u8; MAX_ELEM_BUF];
211        let mut written: u32 = 0;
212        check_ffi(unsafe {
213            ((*api().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) -> UikaResult<()> {
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            ((*api().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) -> UikaResult<()> {
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            ((*api().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) -> UikaResult<()> {
253        check_ffi(unsafe {
254            ((*api().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) -> UikaResult<()> {
260        check_ffi(unsafe { ((*api().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 = UikaResult<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 = UikaResult<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) -> UikaErrorCode,
318) -> UikaResult<(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 == UikaErrorCode::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            let written = u32::from_ne_bytes(
380                self.buf[self.offset..self.offset + 4].try_into().unwrap(),
381            );
382            self.offset += 4;
383            let elem =
384                unsafe { T::read_from_buf(self.buf.as_ptr().add(self.offset), written) };
385            self.offset += written as usize;
386            self.index += 1;
387            Some(elem)
388        }
389    }
390
391    fn size_hint(&self) -> (usize, Option<usize>) {
392        let r = self.count - self.index;
393        (r, Some(r))
394    }
395}
396
397impl<T: ContainerElement> ExactSizeIterator for BulkArrayIter<T> {}
398
399/// Bulk iterator over `TMap<K,V>` key-value pairs. Single FFI call.
400pub struct BulkMapIter<K: ContainerElement, V: ContainerElement> {
401    buf: Vec<u8>,
402    count: usize,
403    index: usize,
404    offset: usize,
405    _marker: PhantomData<(K, V)>,
406}
407
408impl<K: ContainerElement, V: ContainerElement> Iterator for BulkMapIter<K, V> {
409    type Item = (K, V);
410
411    fn next(&mut self) -> Option<(K, V)> {
412        if self.index >= self.count {
413            return None;
414        }
415        // Key: [u32 written][data]
416        let key_written = u32::from_ne_bytes(
417            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
418        );
419        self.offset += 4;
420        let key =
421            unsafe { K::read_from_buf(self.buf.as_ptr().add(self.offset), key_written) };
422        self.offset += key_written as usize;
423
424        // Value: [u32 written][data]
425        let val_written = u32::from_ne_bytes(
426            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
427        );
428        self.offset += 4;
429        let val =
430            unsafe { V::read_from_buf(self.buf.as_ptr().add(self.offset), val_written) };
431        self.offset += val_written as usize;
432
433        self.index += 1;
434        Some((key, val))
435    }
436
437    fn size_hint(&self) -> (usize, Option<usize>) {
438        let r = self.count - self.index;
439        (r, Some(r))
440    }
441}
442
443impl<K: ContainerElement, V: ContainerElement> ExactSizeIterator for BulkMapIter<K, V> {}
444
445/// Bulk iterator over `TSet<T>` elements. Single FFI call.
446pub struct BulkSetIter<T: ContainerElement> {
447    buf: Vec<u8>,
448    count: usize,
449    index: usize,
450    offset: usize,
451    _marker: PhantomData<T>,
452}
453
454impl<T: ContainerElement> Iterator for BulkSetIter<T> {
455    type Item = T;
456
457    fn next(&mut self) -> Option<T> {
458        if self.index >= self.count {
459            return None;
460        }
461        let written = u32::from_ne_bytes(
462            self.buf[self.offset..self.offset + 4].try_into().unwrap(),
463        );
464        self.offset += 4;
465        let elem = unsafe { T::read_from_buf(self.buf.as_ptr().add(self.offset), written) };
466        self.offset += written as usize;
467        self.index += 1;
468        Some(elem)
469    }
470
471    fn size_hint(&self) -> (usize, Option<usize>) {
472        let r = self.count - self.index;
473        (r, Some(r))
474    }
475}
476
477impl<T: ContainerElement> ExactSizeIterator for BulkSetIter<T> {}
478
479// ---------------------------------------------------------------------------
480// UeArray bulk methods
481// ---------------------------------------------------------------------------
482
483impl<T: ContainerElement> UeArray<T> {
484    /// Bulk-copy all elements to a `Vec<T>` in a single FFI call.
485    pub fn to_vec(&self) -> UikaResult<Vec<T>> {
486        let len = self.len()?;
487        if len == 0 {
488            return Ok(Vec::new());
489        }
490        let owner = self.owner;
491        let prop = self.prop;
492        // Raw-copyable types have no per-element framing overhead
493        let estimate = if T::RAW_COPYABLE {
494            len * T::BUF_SIZE as usize
495        } else {
496            len * (T::BUF_SIZE as usize + 4)
497        };
498        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
499            ((*api().container).array_copy_all)(owner, prop, out, size, written, cnt)
500        })?;
501        // Negative count = raw format from C++
502        let (actual_count, raw_elem_size) = if count < 0 {
503            ((-count) as usize, T::BUF_SIZE as usize)
504        } else {
505            (count as usize, 0)
506        };
507        Ok(BulkArrayIter::<T> {
508            buf,
509            count: actual_count,
510            index: 0,
511            offset: 0,
512            raw_elem_size,
513            _marker: PhantomData,
514        }
515        .collect())
516    }
517
518    /// Bulk-fetch all elements as a lazy iterator (single FFI call).
519    pub fn bulk_iter(&self) -> UikaResult<BulkArrayIter<T>> {
520        let len = self.len()?;
521        if len == 0 {
522            return Ok(BulkArrayIter {
523                buf: Vec::new(),
524                count: 0,
525                index: 0,
526                offset: 0,
527                raw_elem_size: 0,
528                _marker: PhantomData,
529            });
530        }
531        let owner = self.owner;
532        let prop = self.prop;
533        let estimate = if T::RAW_COPYABLE {
534            len * T::BUF_SIZE as usize
535        } else {
536            len * (T::BUF_SIZE as usize + 4)
537        };
538        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
539            ((*api().container).array_copy_all)(owner, prop, out, size, written, cnt)
540        })?;
541        let (actual_count, raw_elem_size) = if count < 0 {
542            ((-count) as usize, T::BUF_SIZE as usize)
543        } else {
544            (count as usize, 0)
545        };
546        Ok(BulkArrayIter {
547            buf,
548            count: actual_count,
549            index: 0,
550            offset: 0,
551            raw_elem_size,
552            _marker: PhantomData,
553        })
554    }
555
556    /// Replace all array elements from a slice in a single FFI call.
557    pub fn set_all(&self, items: &[T]) -> UikaResult<()> {
558        if items.is_empty() {
559            return self.clear();
560        }
561        if T::RAW_COPYABLE {
562            // Raw format: contiguous elements, no per-element framing
563            let elem_size = T::BUF_SIZE as usize;
564            let mut buf = vec![0u8; items.len() * elem_size];
565            for (i, item) in items.iter().enumerate() {
566                unsafe { item.write_to_buf(buf.as_mut_ptr().add(i * elem_size)); }
567            }
568            // Negative count signals raw format to C++
569            check_ffi(unsafe {
570                ((*api().container).array_set_all)(
571                    self.owner,
572                    self.prop,
573                    buf.as_ptr(),
574                    buf.len() as u32,
575                    -(items.len() as i32),
576                )
577            })
578        } else {
579            // Framed format: [u32 written][data] per element
580            let mut buf = Vec::with_capacity(items.len() * (T::BUF_SIZE as usize + 4));
581            let mut elem_buf = [0u8; MAX_ELEM_BUF];
582            for item in items {
583                let written = unsafe { item.write_to_buf(elem_buf.as_mut_ptr()) };
584                buf.extend_from_slice(&written.to_ne_bytes());
585                buf.extend_from_slice(&elem_buf[..written as usize]);
586            }
587            check_ffi(unsafe {
588                ((*api().container).array_set_all)(
589                    self.owner,
590                    self.prop,
591                    buf.as_ptr(),
592                    buf.len() as u32,
593                    items.len() as i32,
594                )
595            })
596        }
597    }
598}
599
600// ---------------------------------------------------------------------------
601// UeMap<K, V>
602// ---------------------------------------------------------------------------
603
604/// A view into a UE `TMap<K, V>` property on a UObject.
605#[derive(Clone, Copy)]
606pub struct UeMap<K: ContainerElement, V: ContainerElement> {
607    owner: UObjectHandle,
608    prop: FPropertyHandle,
609    _marker: PhantomData<(K, V)>,
610}
611
612impl<K: ContainerElement, V: ContainerElement> UeMap<K, V> {
613    #[inline]
614    pub fn new(owner: UObjectHandle, prop: FPropertyHandle) -> Self {
615        UeMap {
616            owner,
617            prop,
618            _marker: PhantomData,
619        }
620    }
621
622    /// Returns the number of key-value pairs in the map.
623    pub fn len(&self) -> UikaResult<usize> {
624        let n = unsafe { ((*api().container).map_len)(self.owner, self.prop) };
625        if n < 0 {
626            return Err(UikaError::ObjectDestroyed);
627        }
628        Ok(n as usize)
629    }
630
631    pub fn is_empty(&self) -> UikaResult<bool> {
632        Ok(self.len()? == 0)
633    }
634
635    /// Look up a value by key. Returns `Err(PropertyNotFound)` if the key
636    /// is not in the map.
637    pub fn find(&self, key: &K) -> UikaResult<V> {
638        let mut key_buf = [0u8; MAX_ELEM_BUF];
639        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
640        let mut val_buf = [0u8; MAX_ELEM_BUF];
641        let mut val_written: u32 = 0;
642
643        check_ffi(unsafe {
644            ((*api().container).map_find)(
645                self.owner,
646                self.prop,
647                key_buf.as_ptr(),
648                key_written,
649                val_buf.as_mut_ptr(),
650                V::BUF_SIZE,
651                &mut val_written,
652            )
653        })?;
654        Ok(unsafe { V::read_from_buf(val_buf.as_ptr(), val_written) })
655    }
656
657    /// Insert or replace a key-value pair.
658    pub fn add(&self, key: &K, val: &V) -> UikaResult<()> {
659        let mut key_buf = [0u8; MAX_ELEM_BUF];
660        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
661        let mut val_buf = [0u8; MAX_ELEM_BUF];
662        let val_written = unsafe { val.write_to_buf(val_buf.as_mut_ptr()) };
663
664        check_ffi(unsafe {
665            ((*api().container).map_add)(
666                self.owner,
667                self.prop,
668                key_buf.as_ptr(),
669                key_written,
670                val_buf.as_ptr(),
671                val_written,
672            )
673        })
674    }
675
676    /// Remove a key from the map.
677    pub fn remove(&self, key: &K) -> UikaResult<()> {
678        let mut key_buf = [0u8; MAX_ELEM_BUF];
679        let key_written = unsafe { key.write_to_buf(key_buf.as_mut_ptr()) };
680
681        check_ffi(unsafe {
682            ((*api().container).map_remove)(
683                self.owner,
684                self.prop,
685                key_buf.as_ptr(),
686                key_written,
687            )
688        })
689    }
690
691    /// Remove all key-value pairs.
692    pub fn clear(&self) -> UikaResult<()> {
693        check_ffi(unsafe { ((*api().container).map_clear)(self.owner, self.prop) })
694    }
695
696    /// Get the key-value pair at logical index (for iteration).
697    pub fn get_pair(&self, logical_index: usize) -> UikaResult<(K, V)> {
698        let mut key_buf = [0u8; MAX_ELEM_BUF];
699        let mut key_written: u32 = 0;
700        let mut val_buf = [0u8; MAX_ELEM_BUF];
701        let mut val_written: u32 = 0;
702
703        check_ffi(unsafe {
704            ((*api().container).map_get_pair)(
705                self.owner,
706                self.prop,
707                logical_index as i32,
708                key_buf.as_mut_ptr(),
709                K::BUF_SIZE,
710                &mut key_written,
711                val_buf.as_mut_ptr(),
712                V::BUF_SIZE,
713                &mut val_written,
714            )
715        })?;
716        Ok(unsafe {
717            (
718                K::read_from_buf(key_buf.as_ptr(), key_written),
719                V::read_from_buf(val_buf.as_ptr(), val_written),
720            )
721        })
722    }
723
724    /// Returns an iterator over key-value pairs.
725    pub fn iter(&self) -> UeMapIter<'_, K, V> {
726        let len = self.len().unwrap_or(0);
727        UeMapIter {
728            map: self,
729            index: 0,
730            len,
731        }
732    }
733}
734
735impl<'a, K: ContainerElement, V: ContainerElement> IntoIterator for &'a UeMap<K, V> {
736    type Item = UikaResult<(K, V)>;
737    type IntoIter = UeMapIter<'a, K, V>;
738
739    fn into_iter(self) -> Self::IntoIter {
740        self.iter()
741    }
742}
743
744/// Iterator over `UeMap<K, V>` key-value pairs.
745pub struct UeMapIter<'a, K: ContainerElement, V: ContainerElement> {
746    map: &'a UeMap<K, V>,
747    index: usize,
748    len: usize,
749}
750
751impl<K: ContainerElement, V: ContainerElement> Iterator for UeMapIter<'_, K, V> {
752    type Item = UikaResult<(K, V)>;
753
754    fn next(&mut self) -> Option<Self::Item> {
755        if self.index >= self.len {
756            return None;
757        }
758        let result = self.map.get_pair(self.index);
759        self.index += 1;
760        Some(result)
761    }
762
763    fn size_hint(&self) -> (usize, Option<usize>) {
764        let remaining = self.len.saturating_sub(self.index);
765        (remaining, Some(remaining))
766    }
767}
768
769impl<K: ContainerElement, V: ContainerElement> ExactSizeIterator for UeMapIter<'_, K, V> {}
770
771// ---------------------------------------------------------------------------
772// UeMap bulk methods
773// ---------------------------------------------------------------------------
774
775impl<K: ContainerElement, V: ContainerElement> UeMap<K, V> {
776    /// Bulk-fetch all key-value pairs as a lazy iterator (single FFI call).
777    pub fn bulk_iter(&self) -> UikaResult<BulkMapIter<K, V>> {
778        let len = self.len()?;
779        if len == 0 {
780            return Ok(BulkMapIter {
781                buf: Vec::new(),
782                count: 0,
783                index: 0,
784                offset: 0,
785                _marker: PhantomData,
786            });
787        }
788        let owner = self.owner;
789        let prop = self.prop;
790        let estimate = len * (K::BUF_SIZE as usize + V::BUF_SIZE as usize + 8);
791        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
792            ((*api().container).map_copy_all)(owner, prop, out, size, written, cnt)
793        })?;
794        Ok(BulkMapIter {
795            buf,
796            count: count as usize,
797            index: 0,
798            offset: 0,
799            _marker: PhantomData,
800        })
801    }
802}
803
804impl<K: ContainerElement + Hash + Eq, V: ContainerElement> UeMap<K, V> {
805    /// Bulk-copy all key-value pairs to a `HashMap` in a single FFI call.
806    pub fn to_hash_map(&self) -> UikaResult<HashMap<K, V>> {
807        let iter = self.bulk_iter()?;
808        let count = iter.count;
809        let mut map = HashMap::with_capacity(count);
810        for (k, v) in iter {
811            map.insert(k, v);
812        }
813        Ok(map)
814    }
815}
816
817// ---------------------------------------------------------------------------
818// UeSet<T>
819// ---------------------------------------------------------------------------
820
821/// A view into a UE `TSet<T>` property on a UObject.
822#[derive(Clone, Copy)]
823pub struct UeSet<T: ContainerElement> {
824    owner: UObjectHandle,
825    prop: FPropertyHandle,
826    _marker: PhantomData<T>,
827}
828
829impl<T: ContainerElement> UeSet<T> {
830    #[inline]
831    pub fn new(owner: UObjectHandle, prop: FPropertyHandle) -> Self {
832        UeSet {
833            owner,
834            prop,
835            _marker: PhantomData,
836        }
837    }
838
839    /// Returns the number of elements in the set.
840    pub fn len(&self) -> UikaResult<usize> {
841        let n = unsafe { ((*api().container).set_len)(self.owner, self.prop) };
842        if n < 0 {
843            return Err(UikaError::ObjectDestroyed);
844        }
845        Ok(n as usize)
846    }
847
848    pub fn is_empty(&self) -> UikaResult<bool> {
849        Ok(self.len()? == 0)
850    }
851
852    /// Check if the set contains an element.
853    pub fn contains(&self, val: &T) -> UikaResult<bool> {
854        let mut buf = [0u8; MAX_ELEM_BUF];
855        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
856        Ok(unsafe {
857            ((*api().container).set_contains)(self.owner, self.prop, buf.as_ptr(), written)
858        })
859    }
860
861    /// Add an element to the set.
862    pub fn add(&self, val: &T) -> UikaResult<()> {
863        let mut buf = [0u8; MAX_ELEM_BUF];
864        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
865        check_ffi(unsafe {
866            ((*api().container).set_add)(self.owner, self.prop, buf.as_ptr(), written)
867        })
868    }
869
870    /// Remove an element from the set.
871    pub fn remove(&self, val: &T) -> UikaResult<()> {
872        let mut buf = [0u8; MAX_ELEM_BUF];
873        let written = unsafe { val.write_to_buf(buf.as_mut_ptr()) };
874        check_ffi(unsafe {
875            ((*api().container).set_remove)(self.owner, self.prop, buf.as_ptr(), written)
876        })
877    }
878
879    /// Remove all elements from the set.
880    pub fn clear(&self) -> UikaResult<()> {
881        check_ffi(unsafe { ((*api().container).set_clear)(self.owner, self.prop) })
882    }
883
884    /// Get the element at logical index (for iteration).
885    pub fn get_element(&self, logical_index: usize) -> UikaResult<T> {
886        let mut buf = [0u8; MAX_ELEM_BUF];
887        let mut written: u32 = 0;
888        check_ffi(unsafe {
889            ((*api().container).set_get_element)(
890                self.owner,
891                self.prop,
892                logical_index as i32,
893                buf.as_mut_ptr(),
894                T::BUF_SIZE,
895                &mut written,
896            )
897        })?;
898        Ok(unsafe { T::read_from_buf(buf.as_ptr(), written) })
899    }
900
901    /// Returns an iterator over the elements.
902    pub fn iter(&self) -> UeSetIter<'_, T> {
903        let len = self.len().unwrap_or(0);
904        UeSetIter {
905            set: self,
906            index: 0,
907            len,
908        }
909    }
910}
911
912impl<'a, T: ContainerElement> IntoIterator for &'a UeSet<T> {
913    type Item = UikaResult<T>;
914    type IntoIter = UeSetIter<'a, T>;
915
916    fn into_iter(self) -> Self::IntoIter {
917        self.iter()
918    }
919}
920
921/// Iterator over `UeSet<T>` elements.
922pub struct UeSetIter<'a, T: ContainerElement> {
923    set: &'a UeSet<T>,
924    index: usize,
925    len: usize,
926}
927
928impl<T: ContainerElement> Iterator for UeSetIter<'_, T> {
929    type Item = UikaResult<T>;
930
931    fn next(&mut self) -> Option<Self::Item> {
932        if self.index >= self.len {
933            return None;
934        }
935        let result = self.set.get_element(self.index);
936        self.index += 1;
937        Some(result)
938    }
939
940    fn size_hint(&self) -> (usize, Option<usize>) {
941        let remaining = self.len.saturating_sub(self.index);
942        (remaining, Some(remaining))
943    }
944}
945
946impl<T: ContainerElement> ExactSizeIterator for UeSetIter<'_, T> {}
947
948// ---------------------------------------------------------------------------
949// UeSet bulk methods
950// ---------------------------------------------------------------------------
951
952impl<T: ContainerElement> UeSet<T> {
953    /// Bulk-fetch all elements as a lazy iterator (single FFI call).
954    pub fn bulk_iter(&self) -> UikaResult<BulkSetIter<T>> {
955        let len = self.len()?;
956        if len == 0 {
957            return Ok(BulkSetIter {
958                buf: Vec::new(),
959                count: 0,
960                index: 0,
961                offset: 0,
962                _marker: PhantomData,
963            });
964        }
965        let owner = self.owner;
966        let prop = self.prop;
967        let estimate = len * (T::BUF_SIZE as usize + 4);
968        let (buf, count) = bulk_copy_with_retry(estimate, |out, size, written, cnt| unsafe {
969            ((*api().container).set_copy_all)(owner, prop, out, size, written, cnt)
970        })?;
971        Ok(BulkSetIter {
972            buf,
973            count: count as usize,
974            index: 0,
975            offset: 0,
976            _marker: PhantomData,
977        })
978    }
979}
980
981impl<T: ContainerElement + Hash + Eq> UeSet<T> {
982    /// Bulk-copy all elements to a `HashSet` in a single FFI call.
983    pub fn to_hash_set(&self) -> UikaResult<HashSet<T>> {
984        let iter = self.bulk_iter()?;
985        let count = iter.count;
986        let mut set = HashSet::with_capacity(count);
987        for elem in iter {
988            set.insert(elem);
989        }
990        Ok(set)
991    }
992}
993
994// ---------------------------------------------------------------------------
995// OwnedStruct<T>: owned copy of struct data from a container
996// ---------------------------------------------------------------------------
997
998/// An owned copy of UE struct data retrieved from a container.
999///
1000/// Since UE structs are opaque (their layout is managed by C++), this type
1001/// holds the raw bytes copied from the container. Use [`as_ref`](Self::as_ref)
1002/// to get a `UStructRef<T>` for property access.
1003pub struct OwnedStruct<T: UeStruct> {
1004    data: Vec<u8>,
1005    needs_destroy: bool,
1006    _marker: PhantomData<T>,
1007}
1008
1009impl<T: UeStruct> OwnedStruct<T> {
1010    /// Allocate a new struct initialized via C++ default constructor.
1011    ///
1012    /// Uses the UE reflection system to determine the struct's size,
1013    /// allocates a zero-filled buffer, then calls `UScriptStruct::InitializeStruct`
1014    /// to properly construct non-trivial members (TArray, FString, etc.).
1015    /// The struct is destroyed via `UScriptStruct::DestroyStruct` on drop.
1016    pub fn new() -> Self {
1017        let ustruct = T::static_struct();
1018        let size = unsafe { ((*api().reflection).get_struct_size)(ustruct) };
1019        debug_assert!(size > 0, "get_struct_size returned 0 for {}", std::any::type_name::<T>());
1020        let mut data = vec![0u8; size as usize];
1021        ffi_infallible(unsafe {
1022            ((*api().reflection).initialize_struct)(ustruct, data.as_mut_ptr())
1023        });
1024        OwnedStruct {
1025            data,
1026            needs_destroy: true,
1027            _marker: PhantomData,
1028        }
1029    }
1030
1031    /// Create from raw bytes (e.g., copied from a container element).
1032    ///
1033    /// The data is assumed to already be initialized by C++ — no destructor
1034    /// will be called on drop.
1035    pub fn from_bytes(data: Vec<u8>) -> Self {
1036        OwnedStruct {
1037            data,
1038            needs_destroy: false,
1039            _marker: PhantomData,
1040        }
1041    }
1042
1043    /// Get a `UStructRef<T>` for property access on this struct data.
1044    pub fn as_ref(&self) -> UStructRef<T> {
1045        unsafe { UStructRef::from_raw(self.data.as_ptr() as *mut u8) }
1046    }
1047
1048    /// Get the raw bytes of the struct data.
1049    pub fn as_bytes(&self) -> &[u8] {
1050        &self.data
1051    }
1052}
1053
1054impl<T: UeStruct> Clone for OwnedStruct<T> {
1055    fn clone(&self) -> Self {
1056        OwnedStruct {
1057            data: self.data.clone(),
1058            needs_destroy: false,
1059            _marker: PhantomData,
1060        }
1061    }
1062}
1063
1064impl<T: UeStruct> Drop for OwnedStruct<T> {
1065    fn drop(&mut self) {
1066        if self.needs_destroy {
1067            unsafe {
1068                ((*api().reflection).destroy_struct)(
1069                    T::static_struct(),
1070                    self.data.as_mut_ptr(),
1071                );
1072            }
1073        }
1074    }
1075}
1076
1077impl<T: UeStruct> std::fmt::Debug for OwnedStruct<T> {
1078    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1079        f.debug_struct("OwnedStruct")
1080            .field("size", &self.data.len())
1081            .finish()
1082    }
1083}
1084
1085// ContainerElement for OwnedStruct<T>: uses a fixed 4096-byte buffer.
1086// The C++ side copies struct data via CopyScriptStruct; we store the
1087// exact number of bytes written.
1088unsafe impl<T: UeStruct> ContainerElement for OwnedStruct<T> {
1089    const BUF_SIZE: u32 = 4096;
1090
1091    unsafe fn read_from_buf(buf: *const u8, written: u32) -> Self { unsafe {
1092        let mut data = vec![0u8; written as usize];
1093        std::ptr::copy_nonoverlapping(buf, data.as_mut_ptr(), written as usize);
1094        OwnedStruct {
1095            data,
1096            needs_destroy: false,
1097            _marker: PhantomData,
1098        }
1099    }}
1100
1101    unsafe fn write_to_buf(&self, buf: *mut u8) -> u32 { unsafe {
1102        let len = self.data.len();
1103        std::ptr::copy_nonoverlapping(self.data.as_ptr(), buf, len);
1104        len as u32
1105    }}
1106}