use crate::core::id::EdgeId;
use crate::core::interning::InternedString;
use crate::index::incremental_adjacency::MergedAdjacencyGuard;
macro_rules! impl_edge_iter {
(
$(#[$meta:meta])*
$name:ident,
$method_link:literal,
$direction:literal
) => {
$(#[$meta])*
#[doc = concat!(
"\n\nThis iterator provides zero-allocation traversal of ", $direction, " edges,",
"\navoiding the `Vec` allocation overhead of [`", $method_link, "`](crate::storage::CurrentStorage::", $method_link, ").",
"\n\n# Performance",
"\n\n- **Zero allocation**: Holds a `MergedAdjacencyGuard` (merges frozen + delta)",
"\n- **Lazy evaluation**: Only computes results as you iterate",
"\n- **Cache-friendly**: Sequential access to CSR adjacency data",
"\n\n# Issue #187",
"\n\nThis iterator addresses the performance issue where edge traversal methods",
"\nunnecessarily allocated a new `Vec<EdgeId>` on every call, adding 100-500ns",
"\noverhead per traversal."
)]
pub struct $name<'a> {
guard: MergedAdjacencyGuard<'a>,
index: usize,
}
impl<'a> $name<'a> {
#[doc = concat!("Create a new iterator over ", $direction, " edges.")]
#[inline]
pub(crate) fn new(guard: MergedAdjacencyGuard<'a>) -> Self {
Self { guard, index: 0 }
}
}
impl<'a> Iterator for $name<'a> {
type Item = EdgeId;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let frozen_slice = self.guard.frozen_slice();
let delta_slice = self.guard.delta_slice();
while self.index < frozen_slice.len() + delta_slice.len() {
let entry = if self.index < frozen_slice.len() {
&frozen_slice[self.index]
} else {
&delta_slice[self.index - frozen_slice.len()]
};
self.index += 1;
if !self.guard.is_tombstoned(entry.edge_id) {
return Some(entry.edge_id);
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(len) = self.guard.fast_len() {
let remaining = len.saturating_sub(self.index);
(remaining, Some(remaining))
} else {
let upper = self.guard.capacity_hint().saturating_sub(self.index);
(0, Some(upper))
}
}
}
impl<'a> ExactSizeIterator for $name<'a> {
#[inline]
fn len(&self) -> usize {
if let Some(len) = self.guard.fast_len() {
len.saturating_sub(self.index)
} else {
self.guard.len().saturating_sub(self.index)
}
}
}
impl<'a> std::iter::FusedIterator for $name<'a> {}
};
}
macro_rules! impl_edge_iter_with_label {
(
$(#[$meta:meta])*
$name:ident,
$method_link:literal,
$direction:literal
) => {
$(#[$meta])*
#[doc = concat!(
"\n\nThis iterator provides zero-allocation traversal of ", $direction, " edges",
"\nthat match a specific label, avoiding the `Vec` allocation overhead of",
"\n[`", $method_link, "`](crate::storage::CurrentStorage::", $method_link, ").",
"\n\n# Performance",
"\n\n- **Zero allocation**: Holds a `MergedAdjacencyGuard` and pre-resolved label ID",
"\n- **Lazy evaluation**: Filters as you iterate",
"\n- **O(n) complexity**: Single linear scan regardless of match distribution",
"\n- **Early termination**: If label doesn't exist, returns empty iterator"
)]
pub struct $name<'a> {
guard: MergedAdjacencyGuard<'a>,
index: usize,
label_id: Option<InternedString>,
}
impl<'a> $name<'a> {
#[doc = concat!("Create a new iterator over ", $direction, " edges with a specific label.")]
#[inline]
pub(crate) fn new(guard: MergedAdjacencyGuard<'a>, label_id: Option<InternedString>) -> Self {
Self {
guard,
index: 0,
label_id,
}
}
}
impl<'a> Iterator for $name<'a> {
type Item = EdgeId;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let label_id = self.label_id?;
let frozen_slice = self.guard.frozen_slice();
let delta_slice = self.guard.delta_slice();
while self.index < frozen_slice.len() + delta_slice.len() {
let entry = if self.index < frozen_slice.len() {
&frozen_slice[self.index]
} else {
&delta_slice[self.index - frozen_slice.len()]
};
self.index += 1;
if entry.label == label_id && !self.guard.is_tombstoned(entry.edge_id) {
return Some(entry.edge_id);
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.guard.capacity_hint().saturating_sub(self.index);
(0, Some(remaining))
}
}
impl<'a> std::iter::FusedIterator for $name<'a> {}
};
}
impl_edge_iter!(
OutgoingEdgesIter,
"get_outgoing_edges",
"outgoing"
);
impl_edge_iter!(
IncomingEdgesIter,
"get_incoming_edges",
"incoming"
);
impl_edge_iter_with_label!(
OutgoingEdgesWithLabelIter,
"get_outgoing_edges_with_label",
"outgoing"
);
impl_edge_iter_with_label!(
IncomingEdgesWithLabelIter,
"get_incoming_edges_with_label",
"incoming"
);