Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
936f355
upgrade pinned vec version
orxfun Sep 27, 2025
9dd03d8
convec into-iter placeholders
orxfun Sep 27, 2025
1306e4b
IntoIterPtrOfConSlices
orxfun Sep 27, 2025
897ef02
ConcurrentSplitVecIntoIter is defined
orxfun Sep 27, 2025
618c73d
impl iterator for ConcurrentSplitVecIntoIter
orxfun Sep 27, 2025
661cfd0
ConcurrentSplitVecIntoIter yields read elements rather than pointer
orxfun Sep 27, 2025
9d13816
ConcurrentSplitVecIntoIter is wired up with con pin vec
orxfun Sep 27, 2025
db84e11
refactor ConcurrentSplitVecIntoIter
orxfun Sep 27, 2025
0356893
revise drop to drop in place
orxfun Sep 27, 2025
bf0588c
drop is implemented for IntoIterPtrOfConSlices
orxfun Sep 27, 2025
2b83beb
IntoIterPtrOfConSlices is made private
orxfun Sep 27, 2025
561db8f
test modules cor con-pin-vec
orxfun Sep 27, 2025
85f7a74
define destruct
orxfun Sep 27, 2025
5612516
fix destruct to zero capacity
orxfun Sep 27, 2025
15c4be3
clippy fix
orxfun Sep 27, 2025
fcd2c02
into iter tests made generic over growth
orxfun Sep 27, 2025
a8acc13
into iter tests are implemented
orxfun Sep 27, 2025
c3ff5ae
fix memory leak with empty iter
orxfun Sep 27, 2025
4b72715
impl default for IterPtrOfCon
orxfun Sep 27, 2025
6143492
test iter_ptr_default
orxfun Sep 27, 2025
c29fa4b
upgrade pinned-vec dependency
orxfun Sep 30, 2025
12d422f
impl Default for ConcurrentSplitVecIntoIter
orxfun Sep 30, 2025
715a112
upgrade pinned vec version
orxfun Sep 30, 2025
60759dc
remove IntoIter default implementation
orxfun Sep 30, 2025
c954cd2
temporarily exclude tests with con bag
orxfun Sep 30, 2025
17cda97
temporarily exclude con-bag from tests
orxfun Sep 30, 2025
b801ef7
clippy fixes
orxfun Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "orx-split-vec"
version = "3.20.0"
version = "3.21.0"
edition = "2024"
authors = ["orxfun <orx.ugur.arikan@gmail.com>"]
description = "An efficient dynamic capacity vector with pinned element guarantees."
Expand All @@ -12,7 +12,7 @@ categories = ["data-structures", "rust-patterns", "no-std"]
[dependencies]
orx-iterable = { version = "1.3.0", default-features = false }
orx-pseudo-default = { version = "2.1.0", default-features = false }
orx-pinned-vec = { version = "3.18.0", default-features = false }
orx-pinned-vec = { version = "3.20.0", default-features = false }
orx-concurrent-iter = { version = "3.1.0", default-features = false }

[[bench]]
Expand All @@ -24,4 +24,4 @@ criterion = "0.7.0"
rand = { version = "0.9.2", default-features = false }
rand_chacha = { version = "0.9", default-features = false }
test-case = "3.3.1"
orx-concurrent-bag = "3.1.0"
# orx-concurrent-bag = "3.1.0"
4 changes: 2 additions & 2 deletions src/concurrent_iter/tests/con_iter_owned/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod con_iter;
mod into;
// mod con_iter;
// mod into;
mod par;
mod transformations;
4 changes: 2 additions & 2 deletions src/concurrent_iter/tests/con_iter_ref/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod con_iter;
mod into;
// mod con_iter;
// mod into;
mod par;
mod transformations;
26 changes: 21 additions & 5 deletions src/concurrent_pinned_vec/con_pinvec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
Doubling, Fragment, GrowthWithConstantTimeAccess, SplitVec,
common_traits::iterator::{IterOfSlicesOfCon, SliceBorrowAsMut, SliceBorrowAsRef},
concurrent_pinned_vec::iter_ptr::IterPtrOfCon,
concurrent_pinned_vec::{into_iter::ConcurrentSplitVecIntoIter, iter_ptr::IterPtrOfCon},
fragment::transformations::{fragment_from_raw, fragment_into_raw},
};
use alloc::vec::Vec;
Expand All @@ -10,10 +10,10 @@ use core::sync::atomic::{AtomicUsize, Ordering};
use core::{cell::UnsafeCell, ops::Range};
use orx_pinned_vec::ConcurrentPinnedVec;

struct FragmentData {
f: usize,
len: usize,
capacity: usize,
pub struct FragmentData {
pub f: usize,
pub len: usize,
pub capacity: usize,
}

/// Concurrent wrapper ([`orx_pinned_vec::ConcurrentPinnedVec`]) for the `SplitVec`.
Expand All @@ -35,6 +35,15 @@ impl<T, G: GrowthWithConstantTimeAccess> Drop for ConcurrentSplitVec<T, G> {
}

impl<T, G: GrowthWithConstantTimeAccess> ConcurrentSplitVec<T, G> {
pub(super) fn destruct(mut self) -> (G, Vec<UnsafeCell<*mut T>>, usize) {
let mut data = Vec::new();
core::mem::swap(&mut self.data, &mut data);
let capacity = self.capacity.load(Ordering::Relaxed);
let growth = self.growth.clone();
self.zero();
(growth, data, capacity)
}

unsafe fn get_raw_mut_unchecked_fi(&self, f: usize, i: usize) -> *mut T {
let p = unsafe { *self.data[f].get() };
unsafe { p.add(i) }
Expand Down Expand Up @@ -195,6 +204,8 @@ impl<T, G: GrowthWithConstantTimeAccess> ConcurrentPinnedVec<T> for ConcurrentSp
where
Self: 'a;

type IntoIter = ConcurrentSplitVecIntoIter<T, G>;

unsafe fn into_inner(mut self, len: usize) -> Self::P {
let mut fragments = Vec::with_capacity(self.max_num_fragments);
let mut take_fragment = |fragment| fragments.push(fragment);
Expand Down Expand Up @@ -434,4 +445,9 @@ impl<T, G: GrowthWithConstantTimeAccess> ConcurrentPinnedVec<T> for ConcurrentSp
unsafe fn ptr_iter_unchecked(&self, range: Range<usize>) -> Self::PtrIter<'_> {
IterPtrOfCon::new(self.capacity(), &self.data, self.growth.clone(), range)
}

unsafe fn into_iter(self, range: Range<usize>) -> Self::IntoIter {
let (growth, data, capacity) = self.destruct();
ConcurrentSplitVecIntoIter::new(capacity, data, growth, range)
}
}
117 changes: 117 additions & 0 deletions src/concurrent_pinned_vec/into_iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::{
GrowthWithConstantTimeAccess,
concurrent_pinned_vec::into_iter_ptr_slices::IntoIterPtrOfConSlices,
};
use alloc::vec::Vec;
use core::{cell::UnsafeCell, ops::Range};

pub struct ConcurrentSplitVecIntoIter<T, G>
where
G: GrowthWithConstantTimeAccess,
{
slices: IntoIterPtrOfConSlices<T, G>,
len_of_remaining_slices: usize,
current_ptr: *const T,
current_last: *const T,
}

impl<T, G> ConcurrentSplitVecIntoIter<T, G>
where
G: GrowthWithConstantTimeAccess,
{
pub fn new(
capacity: usize,
fragments: Vec<UnsafeCell<*mut T>>,
growth: G,
range: Range<usize>,
) -> Self {
let len_of_remaining_slices = range.len();
let slices = IntoIterPtrOfConSlices::new(capacity, fragments, growth, range);
Self {
slices,
len_of_remaining_slices,
current_ptr: core::ptr::null(),
current_last: core::ptr::null(),
}
}

fn remaining(&self) -> usize {
let remaining_current = match self.current_ptr.is_null() {
true => 0,
// SAFETY: whenever current_ptr is not null, we know that current_last is also not
// null which is >= current_ptr.
false => unsafe { self.current_last.offset_from(self.current_ptr) as usize + 1 },
};

self.len_of_remaining_slices + remaining_current
}

fn next_ptr(&mut self) -> Option<*mut T> {
match self.current_ptr {
ptr if ptr.is_null() => self.next_slice(),
ptr if ptr == self.current_last => {
self.current_ptr = core::ptr::null_mut();
Some(ptr as *mut T)
}
ptr => {
// SAFETY: current_ptr is not the last element, hance current_ptr+1 is in bounds
self.current_ptr = unsafe { self.current_ptr.add(1) };

// SAFETY: ptr is valid and its value can be taken.
// Drop will skip this position which is now uninitialized.
Some(ptr as *mut T)
}
}
}

fn next_slice(&mut self) -> Option<*mut T> {
self.slices.next().and_then(|(ptr, len)| {
debug_assert!(len > 0);
self.len_of_remaining_slices -= len;
// SAFETY: pointers are not null since slice is not empty
self.current_ptr = ptr;
self.current_last = unsafe { ptr.add(len - 1) };
self.next_ptr()
})
}
}

impl<T, G> Iterator for ConcurrentSplitVecIntoIter<T, G>
where
G: GrowthWithConstantTimeAccess,
{
type Item = T;

#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
self.next_ptr().map(|ptr| unsafe { ptr.read() })
}

fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.remaining();
(len, Some(len))
}
}

impl<T, G> ExactSizeIterator for ConcurrentSplitVecIntoIter<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fn len(&self) -> usize {
self.remaining()
}
}

impl<T, G> Drop for ConcurrentSplitVecIntoIter<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fn drop(&mut self) {
if core::mem::needs_drop::<T>() {
while let Some(ptr) = self.next_ptr() {
// SAFETY: ptr is in bounds and have not been dropped yet
unsafe { ptr.drop_in_place() };
}
}
}
}
185 changes: 185 additions & 0 deletions src/concurrent_pinned_vec/into_iter_ptr_slices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use crate::{
GrowthWithConstantTimeAccess,
fragment::transformations::fragment_from_raw,
range_helpers::{range_end, range_start},
};
use alloc::vec::Vec;
use core::cmp::min;
use core::{cell::UnsafeCell, iter::FusedIterator, ops::Range};

#[derive(Default)]
pub(super) struct IntoIterPtrOfConSlices<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fragments: Vec<UnsafeCell<*mut T>>,
growth: G,
sf: usize,
si: usize,
si_end: usize,
ef: usize,
ei: usize,
f: usize,
}

impl<T, G> Drop for IntoIterPtrOfConSlices<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fn drop(&mut self) {
Self::drop_fragments(&self.growth, &mut self.fragments);
}
}

impl<T, G> IntoIterPtrOfConSlices<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fn empty() -> Self {
Self {
fragments: Default::default(),
growth: G::pseudo_default(),
sf: 0,
si: 0,
si_end: 0,
ef: 0,
ei: 0,
f: 1,
}
}

fn single_slice(
fragments: Vec<UnsafeCell<*mut T>>,
growth: G,
f: usize,
begin: usize,
end: usize,
) -> Self {
Self {
fragments,
growth,
sf: f,
si: begin,
si_end: end,
ef: f,
ei: 0,
f,
}
}

pub fn new(
capacity: usize,
mut fragments: Vec<UnsafeCell<*mut T>>,
growth: G,
range: Range<usize>,
) -> Self {
let fragment_and_inner_indices = |i| growth.get_fragment_and_inner_indices_unchecked(i);

let a = range_start(&range);
let b = min(capacity, range_end(&range, capacity));

match b.saturating_sub(a) {
0 => {
Self::drop_fragments(&growth, &mut fragments);
Self::empty()
}
_ => {
let (sf, si) = fragment_and_inner_indices(a);
let (ef, ei) = fragment_and_inner_indices(b - 1);

match sf == ef {
true => Self::single_slice(fragments, growth, sf, si, ei + 1),
false => {
let si_end = growth.fragment_capacity_of(sf);
Self {
fragments,
growth,
sf,
si,
si_end,
ef,
ei,
f: sf,
}
}
}
}
}
}

fn drop_fragments(growth: &G, fragments: &mut [UnsafeCell<*mut T>]) {
for (f, cell) in fragments.iter().enumerate() {
let ptr = unsafe { *cell.get() };
match ptr.is_null() {
true => continue,
false => {
let capacity = growth.fragment_capacity_of(f);
let _fragment_to_drop = unsafe { fragment_from_raw(ptr, 0, capacity) };
}
}
}
}

#[inline(always)]
fn remaining_len(&self) -> usize {
(1 + self.ef).saturating_sub(self.f)
}

#[inline(always)]
fn get_ptr_fi(&self, f: usize, i: usize) -> *mut T {
let p = unsafe { *self.fragments[f].get() };
unsafe { p.add(i) }
}

#[inline(always)]
fn capacity_of(&self, f: usize) -> usize {
self.growth.fragment_capacity_of(f)
}
}

impl<T, G> Iterator for IntoIterPtrOfConSlices<T, G>
where
G: GrowthWithConstantTimeAccess,
{
type Item = (*mut T, usize);

fn next(&mut self) -> Option<Self::Item> {
match self.f {
f if f == self.sf => {
self.f += 1;
let len = self.si_end - self.si;
let p = self.get_ptr_fi(self.sf, self.si);
Some((p, len))
}
f if f < self.ef => {
self.f += 1;
let len = self.capacity_of(f);
let p = self.get_ptr_fi(f, 0);
Some((p, len))
}
f if f == self.ef => {
self.f += 1;
let len = self.ei + 1;
let p = self.get_ptr_fi(self.ef, 0);
Some((p, len))
}
_ => None,
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.remaining_len();
(len, Some(len))
}
}

impl<T, G> FusedIterator for IntoIterPtrOfConSlices<T, G> where G: GrowthWithConstantTimeAccess {}

impl<T, G> ExactSizeIterator for IntoIterPtrOfConSlices<T, G>
where
G: GrowthWithConstantTimeAccess,
{
fn len(&self) -> usize {
self.remaining_len()
}
}
Loading