#![allow(dead_code)]
use core::ptr::{self, NonNull};
#[cfg(debug_assertions)]
use std::os::raw::c_ulong;
use objc2::mutability::IsIdCloneable;
use objc2::rc::Id;
use objc2::runtime::AnyObject;
use objc2::{mutability::IsMutable, runtime::ProtocolObject};
use objc2::{ClassType, Message};
use super::util;
use crate::Foundation::{NSFastEnumeration, NSFastEnumerationState};
const BUF_SIZE: usize = 16;
#[derive(Debug, PartialEq)]
struct FastEnumeratorHelper {
state: NSFastEnumerationState,
buf: [*mut AnyObject; BUF_SIZE],
current_item: usize,
items_count: usize,
#[cfg(debug_assertions)]
mutations_state: Option<c_ulong>,
}
unsafe impl Send for FastEnumeratorHelper {}
unsafe impl Sync for FastEnumeratorHelper {}
impl FastEnumeratorHelper {
#[inline]
fn new() -> Self {
Self {
state: NSFastEnumerationState {
state: 0,
itemsPtr: ptr::null_mut(),
mutationsPtr: ptr::null_mut(),
extra: [0; 5],
},
buf: [ptr::null_mut(); BUF_SIZE],
current_item: 0,
items_count: 0,
#[cfg(debug_assertions)]
mutations_state: None,
}
}
#[inline]
const fn remaining_items_at_least(&self) -> usize {
self.items_count - self.current_item
}
#[inline]
#[track_caller]
unsafe fn load_next_items(&mut self, collection: &ProtocolObject<dyn NSFastEnumeration>) {
let buf_ptr = unsafe { NonNull::new_unchecked(self.buf.as_mut_ptr()) };
self.items_count = unsafe {
collection.countByEnumeratingWithState_objects_count(
NonNull::from(&mut self.state),
buf_ptr,
self.buf.len(),
)
};
#[cfg(debug_assertions)]
{
if self.items_count > 0 && self.state.itemsPtr.is_null() {
panic!("`itemsPtr` was NULL");
}
}
self.current_item = 0;
}
#[inline]
#[track_caller]
unsafe fn next_from(
&mut self,
collection: &ProtocolObject<dyn NSFastEnumeration>,
) -> Option<NonNull<AnyObject>> {
if self.current_item >= self.items_count {
unsafe { self.load_next_items(collection) };
if self.items_count == 0 {
return None;
}
}
#[cfg(debug_assertions)]
{
if let Some(ptr) = NonNull::new(self.state.mutationsPtr) {
let new_state = unsafe { ptr.as_ptr().read_unaligned() };
match self.mutations_state {
None => {
self.mutations_state = Some(new_state);
}
Some(current_state) => {
if current_state != new_state {
panic!("mutation detected during enumeration. This is undefined behaviour, and must be avoided");
}
}
}
}
}
let ptr = unsafe { self.state.itemsPtr.add(self.current_item) };
self.current_item += 1;
let obj = unsafe { ptr.read_unaligned() };
Some(unsafe { NonNull::new_unchecked(obj) })
}
}
pub(crate) unsafe trait FastEnumerationHelper: Message + NSFastEnumeration {
type Item: Message;
fn maybe_len(&self) -> Option<usize>;
}
#[derive(Debug, PartialEq)]
pub(crate) struct Iter<'a, C: ?Sized + 'a> {
helper: FastEnumeratorHelper,
collection: &'a C,
}
impl<'a, C: ?Sized + FastEnumerationHelper> Iter<'a, C> {
pub(crate) fn new(collection: &'a C) -> Self {
Self {
helper: FastEnumeratorHelper::new(),
collection,
}
}
}
impl<'a, C: FastEnumerationHelper> Iterator for Iter<'a, C> {
type Item = &'a C::Item;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<&'a C::Item> {
let obj = unsafe {
self.helper
.next_from(ProtocolObject::from_ref(self.collection))?
};
Some(unsafe { obj.cast::<C::Item>().as_ref() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IterMut<'a, C: ?Sized + 'a> {
helper: FastEnumeratorHelper,
collection: &'a mut C,
}
impl<'a, C: ?Sized + FastEnumerationHelper> IterMut<'a, C>
where
C::Item: IsMutable,
{
pub(crate) fn new(collection: &'a mut C) -> Self {
Self {
helper: FastEnumeratorHelper::new(),
collection,
}
}
}
impl<'a, C: FastEnumerationHelper> Iterator for IterMut<'a, C>
where
C::Item: IsMutable,
{
type Item = &'a mut C::Item;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<&'a mut C::Item> {
let obj = unsafe {
self.helper
.next_from(ProtocolObject::from_mut(self.collection))?
};
Some(unsafe { obj.cast::<C::Item>().as_mut() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IterRetained<'a, C: ?Sized + 'a> {
inner: Iter<'a, C>,
}
impl<'a, C: ?Sized + FastEnumerationHelper> IterRetained<'a, C>
where
C::Item: IsIdCloneable,
{
pub(crate) fn new(collection: &'a C) -> Self {
Self {
inner: Iter::new(collection),
}
}
}
impl<'a, C: FastEnumerationHelper> Iterator for IterRetained<'a, C>
where
C::Item: IsIdCloneable,
{
type Item = Id<C::Item>;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<Id<C::Item>> {
let obj = self.inner.next()?;
Some(unsafe { util::collection_retain_id(obj) })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IntoIter<C: ?Sized> {
helper: FastEnumeratorHelper,
collection: Id<C>,
}
impl<C: FastEnumerationHelper> IntoIter<C> {
pub(crate) fn new_immutable(collection: Id<C>) -> Self
where
C: IsIdCloneable,
C::Item: IsIdCloneable,
{
Self {
helper: FastEnumeratorHelper::new(),
collection,
}
}
pub(crate) fn new_mutable<T: ClassType<Super = C> + IsMutable>(collection: Id<T>) -> Self
where
C: IsIdCloneable,
{
Self {
helper: FastEnumeratorHelper::new(),
collection: unsafe { Id::cast(collection) },
}
}
pub(crate) fn new(collection: Id<C>) -> Self
where
C: IsMutable,
{
Self {
helper: FastEnumeratorHelper::new(),
collection,
}
}
}
impl<C: FastEnumerationHelper> Iterator for IntoIter<C> {
type Item = Id<C::Item>;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<Id<C::Item>> {
let collection = ProtocolObject::from_ref(&*self.collection);
let obj = unsafe { self.helper.next_from(collection)? };
let obj = unsafe { Id::retain(obj.cast::<C::Item>().as_ptr()) };
Some(unsafe { obj.unwrap_unchecked() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IterWithBackingEnum<'a, C: ?Sized + 'a, E: ?Sized + 'a> {
helper: FastEnumeratorHelper,
collection: &'a C,
enumerator: Id<E>,
}
impl<'a, C, E> IterWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: ?Sized + FastEnumerationHelper,
{
pub(crate) unsafe fn new(collection: &'a C, enumerator: Id<E>) -> Self {
Self {
helper: FastEnumeratorHelper::new(),
collection,
enumerator,
}
}
}
impl<'a, C, E> Iterator for IterWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: FastEnumerationHelper,
{
type Item = &'a E::Item;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<&'a E::Item> {
let obj = unsafe {
self.helper
.next_from(ProtocolObject::from_ref(&*self.enumerator))?
};
Some(unsafe { obj.cast::<E::Item>().as_ref() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IterMutWithBackingEnum<'a, C: ?Sized + 'a, E: ?Sized + 'a> {
helper: FastEnumeratorHelper,
collection: &'a mut C,
enumerator: Id<E>,
}
impl<'a, C, E> IterMutWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: ?Sized + FastEnumerationHelper + IsMutable,
E::Item: IsMutable,
{
pub(crate) unsafe fn new(collection: &'a mut C, enumerator: Id<E>) -> Self {
Self {
helper: FastEnumeratorHelper::new(),
collection,
enumerator,
}
}
}
impl<'a, C, E> Iterator for IterMutWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: FastEnumerationHelper + IsMutable,
E::Item: IsMutable,
{
type Item = &'a mut E::Item;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<&'a mut E::Item> {
let obj = unsafe {
self.helper
.next_from(ProtocolObject::from_mut(&mut *self.enumerator))?
};
Some(unsafe { obj.cast::<E::Item>().as_mut() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IterRetainedWithBackingEnum<'a, C: ?Sized + 'a, E: ?Sized + 'a> {
inner: IterWithBackingEnum<'a, C, E>,
}
impl<'a, C, E> IterRetainedWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: ?Sized + FastEnumerationHelper,
E::Item: IsIdCloneable,
{
pub(crate) unsafe fn new(collection: &'a C, enumerator: Id<E>) -> Self {
let inner = unsafe { IterWithBackingEnum::new(collection, enumerator) };
Self { inner }
}
}
impl<'a, C, E> Iterator for IterRetainedWithBackingEnum<'a, C, E>
where
C: ?Sized + FastEnumerationHelper,
E: FastEnumerationHelper,
E::Item: IsIdCloneable,
{
type Item = Id<E::Item>;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<Id<E::Item>> {
let obj = self.inner.next()?;
Some(unsafe { util::collection_retain_id(obj) })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct IntoIterWithBackingEnum<C: ?Sized, E: ?Sized> {
helper: FastEnumeratorHelper,
collection: Id<C>,
enumerator: Id<E>,
}
impl<C, E> IntoIterWithBackingEnum<C, E>
where
C: FastEnumerationHelper,
E: ?Sized + FastEnumerationHelper,
{
pub(crate) unsafe fn new_immutable(collection: Id<C>, enumerator: Id<E>) -> Self
where
C: IsIdCloneable,
E::Item: IsIdCloneable,
{
Self {
helper: FastEnumeratorHelper::new(),
collection,
enumerator,
}
}
pub(crate) unsafe fn new_mutable<T: ClassType<Super = C> + IsMutable>(
collection: Id<T>,
enumerator: Id<E>,
) -> Self
where
C: IsIdCloneable,
{
Self {
collection: unsafe { Id::cast(collection) },
enumerator,
helper: FastEnumeratorHelper::new(),
}
}
}
impl<C, E> Iterator for IntoIterWithBackingEnum<C, E>
where
C: ?Sized + FastEnumerationHelper,
E: FastEnumerationHelper,
{
type Item = Id<E::Item>;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<Id<E::Item>> {
let enumerator = ProtocolObject::from_ref(&*self.enumerator);
let obj = unsafe { self.helper.next_from(enumerator)? };
let obj = unsafe { Id::retain(obj.cast::<E::Item>().as_ptr()) };
Some(unsafe { obj.unwrap_unchecked() })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.helper.remaining_items_at_least(),
self.collection.maybe_len(),
)
}
}
#[doc(hidden)]
macro_rules! __impl_iter {
(
impl<$($lifetime:lifetime, )? $t1:ident: $bound1:ident $(+ $bound1_b:ident)? $(, $t2:ident: $bound2:ident $(+ $bound2_b:ident)?)?> Iterator<Item = $item:ty> for $for:ty { ... }
) => {
impl<$($lifetime, )? $t1: $bound1 $(+ $bound1_b)? $(, $t2: $bound2 $(+ $bound2_b)?)?> Iterator for $for {
type Item = $item;
#[inline]
#[track_caller]
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
}
}
#[doc(hidden)]
macro_rules! __impl_into_iter {
() => {};
(
$(#[$m:meta])*
impl<T: Message> IntoIterator for &$ty:ident<T> {
type IntoIter = $iter:ident<'_, T>;
}
$($rest:tt)*
) => {
$(#[$m])*
impl<'a, T: Message> IntoIterator for &'a $ty<T> {
type Item = &'a T;
type IntoIter = $iter<'a, T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
$iter(super::iter::Iter::new(&self))
}
}
__impl_into_iter! {
$($rest)*
}
};
(
$(#[$m:meta])*
impl<T: Message + IsMutable> IntoIterator for &mut $ty:ident<T> {
type IntoIter = $iter_mut:ident<'_, T>;
}
$($rest:tt)*
) => {
$(#[$m])*
impl<'a, T: Message + IsMutable> IntoIterator for &'a mut $ty<T> {
type Item = &'a mut T;
type IntoIter = $iter_mut<'a, T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
$iter_mut(super::iter::IterMut::new(&mut *self))
}
}
__impl_into_iter! {
$($rest)*
}
};
(
$(#[$m:meta])*
impl<T: Message + IsIdCloneable> IntoIterator for Id<$ty:ident<T>> {
type IntoIter = $into_iter:ident<T>;
}
$($rest:tt)*
) => {
$(#[$m])*
impl<T: Message + IsIdCloneable> objc2::rc::IdIntoIterator for $ty<T> {
type Item = Id<T>;
type IntoIter = $into_iter<T>;
#[inline]
fn id_into_iter(this: Id<Self>) -> Self::IntoIter {
$into_iter(super::iter::IntoIter::new_immutable(this))
}
}
__impl_into_iter! {
$($rest)*
}
};
(
$(#[$m:meta])*
impl<T: Message> IntoIterator for Id<$ty:ident<T>> {
type IntoIter = $into_iter:ident<T>;
}
$($rest:tt)*
) => {
$(#[$m])*
impl<T: Message> objc2::rc::IdIntoIterator for $ty<T> {
type Item = Id<T>;
type IntoIter = $into_iter<T>;
#[inline]
fn id_into_iter(this: Id<Self>) -> Self::IntoIter {
$into_iter(super::iter::IntoIter::new_mutable(this))
}
}
__impl_into_iter! {
$($rest)*
}
};
}
#[cfg(test)]
#[cfg(feature = "NSArray")]
#[cfg(feature = "NSValue")]
mod tests {
use super::*;
use core::mem::size_of;
use crate::Foundation::{NSArray, NSNumber};
#[test]
#[cfg_attr(
any(not(target_pointer_width = "64"), debug_assertions),
ignore = "assertions assume pointer-width of 64, and the size only really matter in release mode"
)]
fn test_enumerator_helper() {
assert_eq!(size_of::<NSFastEnumerationState>(), 64);
assert_eq!(size_of::<FastEnumeratorHelper>(), 208);
assert_eq!(size_of::<Iter<'_, NSArray<NSNumber>>>(), 216);
}
#[test]
fn test_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.iter();
assert_eq!(enumerator.count(), 4);
let enumerator = array.iter();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
#[test]
fn test_into_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.into_iter();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
}