diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0afcae79616e..ba481d0bfcfa 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -161,6 +161,7 @@ Update instructions: |----------------------|--------------------------------------------------------------------------| | re_entity_db | In-memory storage of Rerun entities | | re_query | Querying data in the re_chunk_store | +| re_query2 | Querying data in the re_chunk_store | | re_types | The built-in Rerun data types, component types, and archetypes. | | re_types_blueprint | The core traits and types that power Rerun's Blueprint sub-system. | | re_log_encoding | Helpers for encoding and transporting Rerun log messages | diff --git a/Cargo.lock b/Cargo.lock index e0f1268b141e..50f501c6afe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4715,6 +4715,39 @@ dependencies = [ "thiserror", ] +[[package]] +name = "re_query2" +version = "0.18.0-alpha.1+dev" +dependencies = [ + "ahash", + "anyhow", + "backtrace", + "criterion", + "indent", + "indexmap 2.1.0", + "itertools 0.13.0", + "mimalloc", + "nohash-hasher", + "parking_lot", + "paste", + "rand", + "re_arrow2", + "re_chunk", + "re_chunk_store", + "re_error", + "re_format", + "re_log", + "re_log_types", + "re_tracing", + "re_tuid", + "re_types", + "re_types_core", + "seq-macro", + "similar-asserts", + "static_assertions", + "thiserror", +] + [[package]] name = "re_renderer" version = "0.18.0-alpha.1+dev" diff --git a/Cargo.toml b/Cargo.toml index c5333aa85ebd..95314d6e95b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ re_format_arrow = { path = "crates/store/re_format_arrow", version = "=0.18.0-al re_log_encoding = { path = "crates/store/re_log_encoding", version = "=0.18.0-alpha.1", default-features = false } re_log_types = { path = "crates/store/re_log_types", version = "=0.18.0-alpha.1", default-features = false } re_query = { path = "crates/store/re_query", version = "=0.18.0-alpha.1", default-features = false } +re_query2 = { path = "crates/store/re_query2", version = "=0.18.0-alpha.1", default-features = false } re_sdk_comms = { path = "crates/store/re_sdk_comms", version = "=0.18.0-alpha.1", default-features = false } re_types = { path = "crates/store/re_types", version = "=0.18.0-alpha.1", default-features = false } re_types_blueprint = { path = "crates/store/re_types_blueprint", version = "=0.18.0-alpha.1", default-features = false } diff --git a/crates/store/re_query/Cargo.toml b/crates/store/re_query/Cargo.toml index ba50a64e7db3..e06ccfaf37b7 100644 --- a/crates/store/re_query/Cargo.toml +++ b/crates/store/re_query/Cargo.toml @@ -69,11 +69,13 @@ bench = false name = "clamped_zip" required-features = ["codegen"] bench = false +doc = false # we're already documenting the one is `re_query2` [[bin]] name = "range_zip" required-features = ["codegen"] bench = false +doc = false # we're already documenting the one is `re_query2` [[bench]] diff --git a/crates/store/re_query/src/cache.rs b/crates/store/re_query/src/cache.rs index dd838f7869d7..7036376f0354 100644 --- a/crates/store/re_query/src/cache.rs +++ b/crates/store/re_query/src/cache.rs @@ -4,11 +4,12 @@ use std::{ }; use ahash::{HashMap, HashSet}; +use nohash_hasher::IntSet; use parking_lot::RwLock; use re_chunk_store::{ChunkStore, ChunkStoreDiff, ChunkStoreEvent, ChunkStoreSubscriber}; use re_log_types::{EntityPath, ResolvedTimeRange, StoreId, TimeInt, Timeline}; -use re_types_core::ComponentName; +use re_types_core::{components::ClearIsRecursive, ComponentName, Loggable as _}; use crate::{LatestAtCache, RangeCache}; @@ -70,6 +71,14 @@ pub struct Caches { /// The [`StoreId`] of the associated [`ChunkStore`]. pub(crate) store_id: StoreId, + /// Keeps track of which entities have had any `Clear`-related data on any timeline at any + /// point in time. + /// + /// This is used to optimized read-time clears, so that we don't unnecessarily pay for the fixed + /// overhead of all the query layers when we know for a fact that there won't be any data there. + /// This is a huge performance improvement in practice, especially in recordings with many entities. + pub(crate) might_require_clearing: RwLock>, + // NOTE: `Arc` so we can cheaply free the top-level lock early when needed. pub(crate) latest_at_per_cache_key: RwLock>>>, @@ -81,12 +90,25 @@ impl std::fmt::Debug for Caches { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { store_id, + might_require_clearing, latest_at_per_cache_key, range_per_cache_key, } = self; let mut strings = Vec::new(); + strings.push(format!( + "[Entities that must be checked for clears @ {store_id}]\n" + )); + { + let sorted: BTreeSet = + might_require_clearing.read().iter().cloned().collect(); + for entity_path in sorted { + strings.push(format!(" * {entity_path}\n")); + } + strings.push("\n".to_owned()); + } + strings.push(format!("[LatestAt @ {store_id}]")); { let latest_at_per_cache_key = latest_at_per_cache_key.read(); @@ -130,6 +152,7 @@ impl Caches { pub fn new(store: &ChunkStore) -> Self { Self { store_id: store.id().clone(), + might_require_clearing: Default::default(), latest_at_per_cache_key: Default::default(), range_per_cache_key: Default::default(), } @@ -139,10 +162,12 @@ impl Caches { pub fn clear(&self) { let Self { store_id: _, + might_require_clearing, latest_at_per_cache_key, range_per_cache_key, } = self; + might_require_clearing.write().clear(); latest_at_per_cache_key.write().clear(); range_per_cache_key.write().clear(); } @@ -223,6 +248,7 @@ impl ChunkStoreSubscriber for Caches { } } + let mut might_require_clearing = self.might_require_clearing.write(); let caches_latest_at = self.latest_at_per_cache_key.write(); let caches_range = self.range_per_cache_key.write(); // NOTE: Don't release the top-level locks -- even though this cannot happen yet with @@ -237,6 +263,10 @@ impl ChunkStoreSubscriber for Caches { // But since this pretty much never happens in practice, let's not go there until we // have metrics showing that show we need to. for (entity_path, component_name) in compacted.static_ { + if component_name == ClearIsRecursive::name() { + might_require_clearing.insert(entity_path.clone()); + } + for (key, cache) in caches_latest_at.iter() { if key.entity_path == entity_path && key.component_name == component_name { cache.write().pending_invalidations.insert(TimeInt::STATIC); @@ -255,6 +285,10 @@ impl ChunkStoreSubscriber for Caches { re_tracing::profile_scope!("temporal"); for (key, times) in compacted.temporal { + if key.component_name == ClearIsRecursive::name() { + might_require_clearing.insert(key.entity_path.clone()); + } + if let Some(cache) = caches_latest_at.get(&key) { cache .write() diff --git a/crates/store/re_query/src/latest_at/query.rs b/crates/store/re_query/src/latest_at/query.rs index 8418ba80edb7..f54af6e85cfe 100644 --- a/crates/store/re_query/src/latest_at/query.rs +++ b/crates/store/re_query/src/latest_at/query.rs @@ -49,6 +49,13 @@ impl Caches { let mut results = LatestAtResults::default(); + // NOTE: This pre-filtering is extremely important: going through all these query layers + // has non-negligible overhead even if the final result ends up being nothing, and our + // number of queries for a frame grows linearly with the number of entity paths. + let component_names = component_names.into_iter().filter(|component_name| { + store.entity_has_component_on_timeline(&query.timeline(), entity_path, component_name) + }); + // Query-time clears // ----------------- // @@ -70,8 +77,22 @@ impl Caches { { re_tracing::profile_scope!("clears"); + let potential_clears = self.might_require_clearing.read(); + let mut clear_entity_path = entity_path.clone(); loop { + if !potential_clears.contains(&clear_entity_path) { + // This entity does not contain any `Clear`-related data at all, there's no + // point in running actual queries. + + let Some(parent_entity_path) = clear_entity_path.parent() else { + break; + }; + clear_entity_path = parent_entity_path; + + continue; + } + let key = CacheKey::new( clear_entity_path.clone(), query.timeline(), diff --git a/crates/store/re_query/src/range/query.rs b/crates/store/re_query/src/range/query.rs index afe0ec158f6a..7a3862992a17 100644 --- a/crates/store/re_query/src/range/query.rs +++ b/crates/store/re_query/src/range/query.rs @@ -30,6 +30,13 @@ impl Caches { let mut results = RangeResults::new(query.clone()); + // NOTE: This pre-filtering is extremely important: going through all these query layers + // has non-negligible overhead even if the final result ends up being nothing, and our + // number of queries for a frame grows linearly with the number of entity paths. + let component_names = component_names.into_iter().filter(|component_name| { + store.entity_has_component_on_timeline(&query.timeline(), entity_path, component_name) + }); + for component_name in component_names { let key = CacheKey::new(entity_path.clone(), query.timeline(), component_name); diff --git a/crates/store/re_query2/Cargo.toml b/crates/store/re_query2/Cargo.toml new file mode 100644 index 000000000000..f09c631c2b5f --- /dev/null +++ b/crates/store/re_query2/Cargo.toml @@ -0,0 +1,81 @@ +[package] +name = "re_query2" +authors.workspace = true +description = "High-level query APIs" +edition.workspace = true +homepage.workspace = true +include.workspace = true +license.workspace = true +publish = true +readme = "README.md" +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[lints] +workspace = true + +[package.metadata.docs.rs] +all-features = true + + +[features] +default = [] + +## Enable codegen helper binaries (generates ClampedZip & RangeZip implementations). +codegen = [] + + +[dependencies] +# Rerun dependencies: +re_chunk.workspace = true +re_chunk_store.workspace = true +re_error.workspace = true +re_format.workspace = true +re_log.workspace = true +re_log_types.workspace = true +re_tracing.workspace = true +re_tuid.workspace = true +re_types_core.workspace = true + +# External dependencies: +ahash.workspace = true +anyhow.workspace = true +arrow2.workspace = true +backtrace.workspace = true +indent.workspace = true +indexmap.workspace = true +itertools.workspace = true +nohash-hasher.workspace = true +parking_lot.workspace = true +paste.workspace = true +seq-macro.workspace = true +static_assertions.workspace = true +thiserror.workspace = true + + +[dev-dependencies] +criterion.workspace = true +mimalloc.workspace = true +rand = { workspace = true, features = ["std", "std_rng"] } +re_types.workspace = true +similar-asserts.workspace = true + +[lib] +bench = false + + +[[bin]] +name = "clamped_zip" +required-features = ["codegen"] +bench = false + +[[bin]] +name = "range_zip" +required-features = ["codegen"] +bench = false + + +[[bench]] +name = "latest_at" +harness = false diff --git a/crates/store/re_query2/README.md b/crates/store/re_query2/README.md new file mode 100644 index 000000000000..906e0113fc51 --- /dev/null +++ b/crates/store/re_query2/README.md @@ -0,0 +1,10 @@ +# re_query2 + +Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates. + +[![Latest version](https://img.shields.io/crates/v/re_query2.svg)](https://crates.io/crates/re_query2) +[![Documentation](https://docs.rs/re_query2/badge.svg)](https://docs.rs/re_query2) +![MIT](https://img.shields.io/badge/license-MIT-blue.svg) +![Apache](https://img.shields.io/badge/license-Apache-blue.svg) + +High-level query APIs. diff --git a/crates/store/re_query2/benches/latest_at.rs b/crates/store/re_query2/benches/latest_at.rs new file mode 100644 index 000000000000..561ddf097a44 --- /dev/null +++ b/crates/store/re_query2/benches/latest_at.rs @@ -0,0 +1,335 @@ +// Allow unwrap() in benchmarks +#![allow(clippy::unwrap_used)] + +use std::sync::Arc; + +use criterion::{criterion_group, criterion_main, Criterion}; +use itertools::Itertools; + +use re_chunk::{Chunk, RowId}; +use re_chunk_store::{ChunkStore, ChunkStoreSubscriber, LatestAtQuery}; +use re_log_types::{entity_path, EntityPath, TimeInt, TimeType, Timeline}; +use re_query2::clamped_zip_1x1; +use re_query2::{Caches, LatestAtResults}; +use re_types::{ + archetypes::Points2D, + components::{Color, Position2D, Text}, + Archetype as _, +}; + +// --- + +// `cargo test` also runs the benchmark setup code, so make sure they run quickly: +#[cfg(debug_assertions)] +mod constants { + pub const NUM_FRAMES_POINTS: u32 = 1; + pub const NUM_POINTS: u32 = 1; + pub const NUM_FRAMES_STRINGS: u32 = 1; + pub const NUM_STRINGS: u32 = 1; +} + +#[cfg(not(debug_assertions))] +mod constants { + pub const NUM_FRAMES_POINTS: u32 = 1_000; + pub const NUM_POINTS: u32 = 1_000; + pub const NUM_FRAMES_STRINGS: u32 = 1_000; + pub const NUM_STRINGS: u32 = 1_000; +} + +#[allow(clippy::wildcard_imports)] +use self::constants::*; + +// --- + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +criterion_group!( + benches, + mono_points, + mono_strings, + batch_points, + batch_strings +); +criterion_main!(benches); + +// --- + +fn mono_points(c: &mut Criterion) { + // Each mono point gets logged at a different path + let paths = (0..NUM_POINTS) + .map(move |point_idx| entity_path!("points", point_idx)) + .collect_vec(); + let msgs = build_points_chunks(&paths, 1); + + { + let mut group = c.benchmark_group("arrow_mono_points2"); + // Mono-insert is slow -- decrease the sample size + group.sample_size(10); + group.throughput(criterion::Throughput::Elements( + (NUM_POINTS * NUM_FRAMES_POINTS) as _, + )); + group.bench_function("insert", |b| { + b.iter(|| insert_chunks(msgs.iter())); + }); + } + + { + let mut group = c.benchmark_group("arrow_mono_points2"); + group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); + let (caches, store) = insert_chunks(msgs.iter()); + group.bench_function("query", |b| { + b.iter(|| query_and_visit_points(&caches, &store, &paths)); + }); + } +} + +fn mono_strings(c: &mut Criterion) { + // Each mono string gets logged at a different path + let paths = (0..NUM_STRINGS) + .map(move |string_idx| entity_path!("strings", string_idx)) + .collect_vec(); + let msgs = build_strings_chunks(&paths, 1); + + { + let mut group = c.benchmark_group("arrow_mono_strings2"); + group.sample_size(10); + group.throughput(criterion::Throughput::Elements( + (NUM_STRINGS * NUM_FRAMES_STRINGS) as _, + )); + group.bench_function("insert", |b| { + b.iter(|| insert_chunks(msgs.iter())); + }); + } + + { + let mut group = c.benchmark_group("arrow_mono_strings2"); + group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); + let (caches, store) = insert_chunks(msgs.iter()); + group.bench_function("query", |b| { + b.iter(|| query_and_visit_strings(&caches, &store, &paths)); + }); + } +} + +fn batch_points(c: &mut Criterion) { + // Batch points are logged together at a single path + let paths = [EntityPath::from("points")]; + let msgs = build_points_chunks(&paths, NUM_POINTS as _); + + { + let mut group = c.benchmark_group("arrow_batch_points2"); + group.throughput(criterion::Throughput::Elements( + (NUM_POINTS * NUM_FRAMES_POINTS) as _, + )); + group.bench_function("insert", |b| { + b.iter(|| insert_chunks(msgs.iter())); + }); + } + + { + let mut group = c.benchmark_group("arrow_batch_points2"); + group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); + let (caches, store) = insert_chunks(msgs.iter()); + group.bench_function("query", |b| { + b.iter(|| query_and_visit_points(&caches, &store, &paths)); + }); + } +} + +fn batch_strings(c: &mut Criterion) { + // Batch strings are logged together at a single path + let paths = [EntityPath::from("points")]; + let msgs = build_strings_chunks(&paths, NUM_STRINGS as _); + + { + let mut group = c.benchmark_group("arrow_batch_strings2"); + group.throughput(criterion::Throughput::Elements( + (NUM_STRINGS * NUM_FRAMES_STRINGS) as _, + )); + group.bench_function("insert", |b| { + b.iter(|| insert_chunks(msgs.iter())); + }); + } + + { + let mut group = c.benchmark_group("arrow_batch_strings2"); + group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); + let (caches, store) = insert_chunks(msgs.iter()); + group.bench_function("query", |b| { + b.iter(|| query_and_visit_strings(&caches, &store, &paths)); + }); + } +} + +// --- Helpers --- + +pub fn build_some_point2d(len: usize) -> Vec { + use rand::Rng as _; + let mut rng = rand::thread_rng(); + + (0..len) + .map(|_| Position2D::new(rng.gen_range(0.0..10.0), rng.gen_range(0.0..10.0))) + .collect() +} + +/// Create `len` dummy colors +pub fn build_some_colors(len: usize) -> Vec { + (0..len).map(|i| Color::from(i as u32)).collect() +} + +/// Build a ([`Timeline`], [`TimeInt`]) tuple from `frame_nr` suitable for inserting in a [`re_log_types::TimePoint`]. +pub fn build_frame_nr(frame_nr: TimeInt) -> (Timeline, TimeInt) { + (Timeline::new("frame_nr", TimeType::Sequence), frame_nr) +} + +pub fn build_some_strings(len: usize) -> Vec { + use rand::Rng as _; + let mut rng = rand::thread_rng(); + + (0..len) + .map(|_| { + let ilen: usize = rng.gen_range(0..100); + let s: String = rand::thread_rng() + .sample_iter(&rand::distributions::Alphanumeric) + .take(ilen) + .map(char::from) + .collect(); + Text::from(s) + }) + .collect() +} + +fn build_points_chunks(paths: &[EntityPath], num_points: usize) -> Vec> { + paths + .iter() + .map(|path| { + let mut builder = Chunk::builder(path.clone()); + for frame_idx in 0..NUM_FRAMES_POINTS { + builder = builder.with_component_batches( + RowId::new(), + [build_frame_nr((frame_idx as i64).try_into().unwrap())], + [ + &build_some_point2d(num_points) as _, + &build_some_colors(num_points) as _, + ], + ); + } + Arc::new(builder.build().unwrap()) + }) + .collect() +} + +fn build_strings_chunks(paths: &[EntityPath], num_strings: usize) -> Vec> { + paths + .iter() + .map(|path| { + let mut builder = Chunk::builder(path.clone()); + for frame_idx in 0..NUM_FRAMES_POINTS { + builder = builder.with_component_batches( + RowId::new(), + [build_frame_nr((frame_idx as i64).try_into().unwrap())], + [ + // We still need to create points because they are the primary for the + // archetype query we want to do. We won't actually deserialize the points + // during the query -- we just need it for the primary keys. + // TODO(jleibs): switch this to use `TextEntry` once the new type has + // landed. + &build_some_point2d(num_strings) as _, + &build_some_strings(num_strings) as _, + ], + ); + } + Arc::new(builder.build().unwrap()) + }) + .collect() +} + +fn insert_chunks<'a>(msgs: impl Iterator>) -> (Caches, ChunkStore) { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + msgs.for_each(|chunk| { + caches.on_events(&store.insert_chunk(chunk).unwrap()); + }); + + (caches, store) +} + +struct SavePoint { + _pos: Position2D, + _color: Option, +} + +fn query_and_visit_points( + caches: &Caches, + store: &ChunkStore, + paths: &[EntityPath], +) -> Vec { + let timeline_frame_nr = Timeline::new("frame_nr", TimeType::Sequence); + let query = LatestAtQuery::new(timeline_frame_nr, NUM_FRAMES_POINTS as i64 / 2); + + let mut ret = Vec::with_capacity(NUM_POINTS as _); + + // TODO(jleibs): Add Radius once we have support for it in field_types + for entity_path in paths { + let results: LatestAtResults = caches.latest_at( + store, + &query, + entity_path, + Points2D::all_components().iter().copied(), // no generics! + ); + + let points = results.component_batch_quiet::().unwrap(); + let colors = results.component_batch_quiet::().unwrap_or_default(); + let color_default_fn = || Color::from(0xFF00FFFF); + + for (point, color) in clamped_zip_1x1(points, colors, color_default_fn) { + ret.push(SavePoint { + _pos: point, + _color: Some(color), + }); + } + } + assert_eq!(NUM_POINTS as usize, ret.len()); + ret +} + +struct SaveString { + _label: Option, +} + +fn query_and_visit_strings( + caches: &Caches, + store: &ChunkStore, + paths: &[EntityPath], +) -> Vec { + let timeline_frame_nr = Timeline::new("frame_nr", TimeType::Sequence); + let query = LatestAtQuery::new(timeline_frame_nr, NUM_FRAMES_STRINGS as i64 / 2); + + let mut strings = Vec::with_capacity(NUM_STRINGS as _); + + for entity_path in paths { + let results: LatestAtResults = caches.latest_at( + store, + &query, + entity_path, + Points2D::all_components().iter().copied(), // no generics! + ); + + let points = results.component_batch_quiet::().unwrap(); + let labels = results.component_batch_quiet::().unwrap_or_default(); + let label_default_fn = || Text(String::new().into()); + + for (_point, label) in clamped_zip_1x1(points, labels, label_default_fn) { + strings.push(SaveString { + _label: Some(label), + }); + } + } + assert_eq!(NUM_STRINGS as usize, strings.len()); + criterion::black_box(strings) +} diff --git a/crates/store/re_query2/examples/latest_at.rs b/crates/store/re_query2/examples/latest_at.rs new file mode 100644 index 000000000000..32fc107977ec --- /dev/null +++ b/crates/store/re_query2/examples/latest_at.rs @@ -0,0 +1,131 @@ +use std::sync::Arc; + +use anyhow::Context; +use arrow2::array::PrimitiveArray as ArrowPrimitiveArray; +use itertools::Itertools; + +use re_chunk::{Chunk, RowId}; +use re_chunk_store::{ChunkStore, LatestAtQuery}; +use re_log_types::example_components::{MyColor, MyLabel, MyPoint, MyPoints}; +use re_log_types::{build_frame_nr, Timeline}; +use re_types::{ComponentBatch, Loggable as _}; +use re_types_core::Archetype as _; + +use re_query2::{clamped_zip_1x2, LatestAtResults}; + +// --- + +fn main() -> anyhow::Result<()> { + let store = store()?; + eprintln!("store:\n{store}"); + + let entity_path = "points"; + let timeline = Timeline::new_sequence("frame_nr"); + let query = LatestAtQuery::latest(timeline); + eprintln!("query:{query:?}"); + + let caches = re_query2::Caches::new(&store); + + // First, get the (potentially cached) results for this query. + let results: LatestAtResults = caches.latest_at( + &store, + &query, + &entity_path.into(), + MyPoints::all_components().iter().copied(), // no generics! + ); + + // The results can be accessed either through the low-level Chunk APIs, or the higher-level helpers. + + // Example of accessing the data using the higher-level APIs. + // + // These APIs will log errors instead of returning them. + { + let points = results.component_batch::().context("missing")?; + let colors = results.component_batch::().unwrap_or_default(); + let labels = results.component_batch::().unwrap_or_default(); + + // Then apply your instance-level joining logic, if any: + let color_default_fn = || MyColor(0xFF00FFFF); + let label_default_fn = || MyLabel("N/A".to_owned()); + let results = clamped_zip_1x2(points, colors, color_default_fn, labels, label_default_fn) + .collect_vec(); + + eprintln!("results 1:\n{results:#?}"); + } + + // Example of accessing the data using the Chunk APIs. + // + // Because a latest-at query can only ever return a single row's worth of data for each + // individual component, the chunks returned here will be so-called unit chunks, which are + // guaranteed to only contain a single row. + { + // * `get_required` returns an error if the chunk is missing. + // * `get` returns an option. + let points = results.get_required(&MyPoint::name())?; + let colors = results.get(&MyColor::name()); + let labels = results.get(&MyLabel::name()); + + // You can always use the standard deserialization path: + let points = points.component_batch::().context("missing")??; + let labels = labels + .and_then(|unit| unit.component_batch::()?.ok()) + .unwrap_or_default(); + + // Or, if you want every last bit of performance you can get, you can manipulate the raw + // data directly: + let colors = colors + .context("missing")? + .component_batch_raw(&MyColor::name()) + .context("invalid")?; + let colors = colors + .as_any() + .downcast_ref::>() + .context("invalid")?; + let colors = colors + .values() + .as_slice() + .iter() + .map(|&color| MyColor(color)); + + // And finally apply your instance-level joining logic, if any: + let color_default_fn = || MyColor(0xFF00FFFF); + let label_default_fn = || MyLabel("N/A".to_owned()); + let results = clamped_zip_1x2(points, colors, color_default_fn, labels, label_default_fn) + .collect_vec(); + + eprintln!("results 2:\n{results:#?}"); + } + + Ok(()) +} + +// --- + +fn store() -> anyhow::Result { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + + let entity_path = "points"; + + { + let timepoint = [build_frame_nr(123)]; + + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches( + RowId::new(), + timepoint, + [ + &[MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)] as &dyn ComponentBatch, // + &[MyColor::from_rgb(255, 0, 0)], + &[MyLabel("a".into()), MyLabel("b".into())], + ], + ) + .build()?; + + store.insert_chunk(&Arc::new(chunk))?; + } + + Ok(store) +} diff --git a/crates/store/re_query2/src/bin/clamped_zip.rs b/crates/store/re_query2/src/bin/clamped_zip.rs new file mode 100644 index 000000000000..d213cbdb3bbc --- /dev/null +++ b/crates/store/re_query2/src/bin/clamped_zip.rs @@ -0,0 +1,354 @@ +//! CLI tool to generate `ClampedZip` implementations of different arities. + +use itertools::{izip, Itertools}; + +struct Params { + num_required: usize, + num_optional: usize, +} + +impl Params { + fn to_num_required(&self) -> String { + self.num_required.to_string() + } + + fn to_num_optional(&self) -> String { + self.num_optional.to_string() + } + + /// `1x3`, `2x2`… + fn to_suffix(&self) -> String { + format!("{}x{}", self.to_num_required(), self.to_num_optional()) + } + + /// `r0, r1, r2…`. + fn to_required_names(&self) -> Vec { + (0..self.num_required) + .map(|n| format!("r{n}")) + .collect_vec() + } + + /// `R0, R1, R2…`. + fn to_required_types(&self) -> Vec { + self.to_required_names() + .into_iter() + .map(|s| s.to_uppercase()) + .collect() + } + + /// `r0: R0, r1: R1, r2: R2…`. + fn to_required_params(&self) -> Vec { + izip!(self.to_required_names(), self.to_required_types()) + .map(|(n, t)| format!("{n}: {t}")) + .collect() + } + + /// `R0: (Into)Iterator, R1: (Into)Iterator, R2: (Into)Iterator…` + fn to_required_clauses(&self, into: bool) -> Vec { + let trait_name = if into { "IntoIterator" } else { "Iterator" }; + self.to_required_types() + .into_iter() + .map(|t| format!("{t}: {trait_name}")) + .collect() + } + + /// `o0, o1, o2…`. + fn to_optional_names(&self) -> Vec { + (0..self.num_optional) + .map(|n| format!("o{n}")) + .collect_vec() + } + + /// `O0, O1, O2…`. + fn to_optional_types(&self) -> Vec { + self.to_optional_names() + .into_iter() + .map(|s| s.to_uppercase()) + .collect() + } + + /// `o0: O0, o1: O1, o2: O2…`. + fn to_optional_params(&self) -> Vec { + izip!(self.to_optional_names(), self.to_optional_types()) + .map(|(n, t)| format!("{n}: {t}")) + .collect() + } + + /// `O0: IntoIterator, O0::Item: Clone, O1: IntoIterator, O1::Item: Clone…` + fn to_optional_clauses(&self, into: bool) -> Vec { + let trait_name = if into { "IntoIterator" } else { "Iterator" }; + self.to_optional_types() + .into_iter() + .map(|t| format!("{t}: {trait_name}, {t}::Item: Clone")) + .collect() + } + + /// `o0_default_fn, o1_default_fn, o2_default_fn…`. + fn to_optional_fn_names(&self) -> Vec { + (0..self.num_optional) + .map(|n| format!("o{n}_default_fn")) + .collect_vec() + } + + /// `D0, D1, D2…`. + fn to_optional_fn_types(&self) -> Vec { + (0..self.num_optional) + .map(|n| format!("D{n}")) + .collect_vec() + } + + /// `o0_default_fn: D0, o1_default_fn: D1…`. + fn to_optional_fn_params(&self) -> Vec { + izip!(self.to_optional_fn_names(), self.to_optional_fn_types()) + .map(|(n, t)| format!("{n}: {t}")) + .collect() + } + + /// `D0: Fn() -> O0::Item, D1: Fn() -> O1::Item…` + fn to_optional_fn_clauses(&self) -> Vec { + izip!(self.to_optional_fn_types(), self.to_optional_types()) + .map(|(tl, tr)| format!("{tl}: Fn() -> {tr}::Item")) + .collect() + } +} + +fn backticked(strs: impl IntoIterator) -> Vec { + strs.into_iter().map(|s| format!("`{s}`")).collect() +} + +fn generate_helper_func(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_names = backticked(params.to_required_names()).join(", "); + let optional_names = backticked(params.to_optional_names()).join(", "); + let optional_fn_names = backticked(params.to_optional_fn_names()).join(", "); + let required_types = params.to_required_types().join(", "); + let optional_types = params.to_optional_types().join(", "); + let optional_fn_types = params.to_optional_fn_types().join(", "); + let required_clauses = params.to_required_clauses(true /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(true /* into */).join(", "); + let optional_fn_clauses = params.to_optional_fn_clauses().join(", "); + let required_params = params.to_required_params().join(", "); + let optional_params = izip!(params.to_optional_params(), params.to_optional_fn_params()) + .map(|(o, d)| format!("{o}, {d}")) + .collect_vec() + .join(",\n"); + + let ret_clause = params + .to_required_types() + .into_iter() + .map(|r| format!("{r}::IntoIter")) + .chain( + params + .to_optional_types() + .into_iter() + .map(|o| format!("{o}::IntoIter")), + ) + .chain(params.to_optional_fn_types()) + .collect_vec() + .join(", "); + + let ret = params + .to_required_names() + .into_iter() + .map(|r| format!("{r}: {r}.into_iter()")) + .chain( + params + .to_optional_names() + .into_iter() + .map(|o| format!("{o}: {o}.into_iter()")), + ) + .chain(params.to_optional_fn_names()) + .chain( + params + .to_optional_names() + .into_iter() + .map(|o| format!("{o}_latest_value: None")), + ) + .collect_vec() + .join(",\n"); + + format!( + r#" + /// Returns a new [`ClampedZip{suffix}`] iterator. + /// + /// The number of elements in a clamped zip iterator corresponds to the number of elements in the + /// shortest of its required iterators ({required_names}). + /// + /// Optional iterators ({optional_names}) will repeat their latest values if they happen to be too short + /// to be zipped with the shortest of the required iterators. + /// + /// If an optional iterator is not only too short but actually empty, its associated default function + /// ({optional_fn_names}) will be executed and the resulting value repeated as necessary. + pub fn clamped_zip_{suffix}<{required_types}, {optional_types}, {optional_fn_types}>( + {required_params}, + {optional_params}, + ) -> ClampedZip{suffix}<{ret_clause}> + where + {required_clauses}, + {optional_clauses}, + {optional_fn_clauses}, + {{ + ClampedZip{suffix} {{ + {ret} + }} + }} + "# + ) +} + +fn generate_struct(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_types = params.to_required_types().join(", "); + let optional_types = params.to_optional_types().join(", "); + let optional_fn_types = params.to_optional_fn_types().join(", "); + let required_clauses = params.to_required_clauses(false /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(false /* into */).join(", "); + let optional_fn_clauses = params.to_optional_fn_clauses().join(", "); + let required_params = params.to_required_params().join(", "); + let optional_params = params.to_optional_params().join(", "); + let optional_fn_params = params.to_optional_fn_params().join(", "); + + let latest_values = izip!(params.to_optional_names(), params.to_optional_types()) + .map(|(n, t)| format!("{n}_latest_value: Option<{t}::Item>")) + .collect_vec() + .join(",\n"); + + format!( + r#" + /// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional + /// iterators. + /// + /// See [`clamped_zip_{suffix}`] for more information. + pub struct ClampedZip{suffix}<{required_types}, {optional_types}, {optional_fn_types}> + where + {required_clauses}, + {optional_clauses}, + {optional_fn_clauses}, + {{ + {required_params}, + {optional_params}, + {optional_fn_params}, + + {latest_values} + }} + "# + ) +} + +fn generate_impl(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_types = params.to_required_types().join(", "); + let optional_types = params.to_optional_types().join(", "); + let optional_fn_types = params.to_optional_fn_types().join(", "); + let required_clauses = params.to_required_clauses(false /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(false /* into */).join(", "); + let optional_fn_clauses = params.to_optional_fn_clauses().join(", "); + + let items = params + .to_required_types() + .into_iter() + .map(|r| format!("{r}::Item")) + .chain( + params + .to_optional_types() + .into_iter() + .map(|o| format!("{o}::Item")), + ) + .collect_vec() + .join(", "); + + let next = + params + .to_required_names() + .into_iter() + .map(|r| format!("let {r}_next = self.{r}.next()?;")) + .chain(params.to_optional_names().into_iter().map(|o| { + format!("let {o}_next = self.{o}.next().or(self.{o}_latest_value.take());") + })) + .collect_vec() + .join("\n"); + + let update_latest = params + .to_optional_names() + .into_iter() + .map(|o| format!("self.{o}_latest_value.clone_from(&{o}_next);")) + .collect_vec() + .join("\n"); + + let ret = params + .to_required_names() + .into_iter() + .map(|r| format!("{r}_next")) + .chain( + params + .to_optional_names() + .into_iter() + .map(|o| format!("{o}_next.unwrap_or_else(|| (self.{o}_default_fn)())")), + ) + .collect_vec() + .join(",\n"); + + format!( + r#" + impl<{required_types}, {optional_types}, {optional_fn_types}> Iterator for ClampedZip{suffix}<{required_types}, {optional_types}, {optional_fn_types}> + where + {required_clauses}, + {optional_clauses}, + {optional_fn_clauses}, + {{ + type Item = ({items}); + + #[inline] + fn next(&mut self) -> Option {{ + {next} + + {update_latest} + + Some(( + {ret} + )) + }} + }} + "# + ) +} + +fn main() { + let num_required = 1..3; + let num_optional = 1..10; + + let output = num_required + .flat_map(|num_required| { + num_optional + .clone() + .map(move |num_optional| (num_required, num_optional)) + }) + .flat_map(|(num_required, num_optional)| { + let params = Params { + num_required, + num_optional, + }; + + [ + generate_helper_func(¶ms), + generate_struct(¶ms), + generate_impl(¶ms), + ] + }) + .collect_vec() + .join("\n"); + + println!( + " + // This file was generated using `cargo r -p re_query2 --all-features --bin clamped_zip`. + // DO NOT EDIT. + + // --- + + #![allow(clippy::too_many_arguments)] + #![allow(clippy::type_complexity)] + + {output} + " + ); +} diff --git a/crates/store/re_query2/src/bin/range_zip.rs b/crates/store/re_query2/src/bin/range_zip.rs new file mode 100644 index 000000000000..a32c70e6c9e9 --- /dev/null +++ b/crates/store/re_query2/src/bin/range_zip.rs @@ -0,0 +1,499 @@ +//! CLI tool to generate `RangeZip` implementations of different arities. + +#![allow(clippy::tuple_array_conversions)] // false positive + +use itertools::{izip, Itertools}; + +struct Params { + num_required: usize, + num_optional: usize, +} + +impl Params { + fn to_num_required(&self) -> String { + self.num_required.to_string() + } + + fn to_num_optional(&self) -> String { + self.num_optional.to_string() + } + + /// `1x3`, `2x2`… + fn to_suffix(&self) -> String { + format!("{}x{}", self.to_num_required(), self.to_num_optional()) + } + + /// `r0, r1, r2…`. + fn to_required_names(&self) -> Vec { + (0..self.num_required) + .map(|n| format!("r{n}")) + .collect_vec() + } + + /// `R0, R1, R2…`. + fn to_required_types(&self) -> Vec { + self.to_required_names() + .into_iter() + .map(|s| s.to_uppercase()) + .collect() + } + + /// `r0: IR0, r1: IR1, r2: IR2…`. + fn to_required_params(&self) -> Vec { + izip!(self.to_required_names(), self.to_required_types()) + .map(|(n, t)| format!("{n}: I{t}")) + .collect() + } + + /// `IR0: (Into)Iterator, IR1: (Into)Iterator…` + fn to_required_clauses(&self, into: bool) -> Vec { + let trait_name = if into { "IntoIterator" } else { "Iterator" }; + self.to_required_types() + .into_iter() + .map(|t| format!("I{t}: {trait_name}")) + .collect() + } + + /// `o0, o1, o2…`. + fn to_optional_names(&self) -> Vec { + (0..self.num_optional) + .map(|n| format!("o{n}")) + .collect_vec() + } + + /// `O0, O1, O2…`. + fn to_optional_types(&self) -> Vec { + self.to_optional_names() + .into_iter() + .map(|s| s.to_uppercase()) + .collect() + } + + /// `o0: IO0, o1: IO1, o2: IO2…`. + fn to_optional_params(&self) -> Vec { + izip!(self.to_optional_names(), self.to_optional_types()) + .map(|(n, t)| format!("{n}: I{t}")) + .collect() + } + + /// `o0: Peekable, o1: Peekable, o2: Peekable…`. + fn to_optional_peekable_params(&self) -> Vec { + izip!(self.to_optional_names(), self.to_optional_types()) + .map(|(n, t)| format!("{n}: Peekable")) + .collect() + } + + /// `IO0: (Into)Iterator, IO1: (Into)Iterator…` + fn to_optional_clauses(&self, into: bool) -> Vec { + let trait_name = if into { "IntoIterator" } else { "Iterator" }; + self.to_optional_types() + .into_iter() + .map(|t| format!("I{t}: {trait_name}")) + .collect() + } +} + +fn backticked(strs: impl IntoIterator) -> Vec { + strs.into_iter().map(|s| format!("`{s}`")).collect() +} + +/// Output: +/// ```ignore +/// pub fn range_zip_2x2( +/// r0: IR0, +/// r1: IR1, +/// o0: IO0, +/// o1: IO1, +/// ) -> RangeZip2x2 +/// where +/// Idx: std::cmp::Ord, +/// IR0: IntoIterator, +/// IR1: IntoIterator, +/// IO0: IntoIterator, +/// IO1: IntoIterator, +/// { +/// RangeZip2x2 { +/// r0: r0.into_iter(), +/// r1: r1.into_iter(), +/// o0: o0.into_iter().peekable(), +/// o1: o1.into_iter().peekable(), +/// +/// o0_data_latest: None, +/// o1_data_latest: None, +/// } +/// } +/// ``` +fn generate_helper_func(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_names = backticked(params.to_required_names()).join(", "); + let required_types = izip!( + params + .to_required_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_required_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let optional_types = izip!( + params + .to_optional_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_optional_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let required_clauses = params.to_required_clauses(true /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(true /* into */).join(", "); + let required_params = params.to_required_params().join(", "); + let optional_params = params.to_optional_params().join(", "); + + let ret_clause = params + .to_required_types() + .into_iter() + .map(|r| format!("I{r}::IntoIter, {r}")) + .chain( + params + .to_optional_types() + .into_iter() + .map(|o| format!("I{o}::IntoIter, {o}")), + ) + .collect_vec() + .join(", "); + + let ret = params + .to_required_names() + .into_iter() + .map(|r| format!("{r}: {r}.into_iter()")) + .chain( + params + .to_optional_names() + .into_iter() + .map(|o| format!("{o}: {o}.into_iter().peekable()")), + ) + .collect_vec() + .join(",\n"); + + let latest = params + .to_optional_names() + .into_iter() + .map(|o| format!("{o}_data_latest: None")) + .collect_vec() + .join(",\n"); + + format!( + r#" + /// Returns a new [`RangeZip{suffix}`] iterator. + /// + /// The number of elements in a range zip iterator corresponds to the number of elements in the + /// shortest of its required iterators ({required_names}). + /// + /// Each call to `next` is guaranteed to yield the next value for each required iterator, + /// as well as the most recent index amongst all of them. + /// + /// Optional iterators accumulate their state and yield their most recent value (if any), + /// each time the required iterators fire. + pub fn range_zip_{suffix}( + {required_params}, + {optional_params}, + ) -> RangeZip{suffix} + where + Idx: std::cmp::Ord, + {required_clauses}, + {optional_clauses}, + {{ + RangeZip{suffix} {{ + {ret}, + + {latest}, + }} + }} + "# + ) +} + +/// Output: +/// ```ignore +/// pub struct RangeZip2x2 +/// where +/// Idx: std::cmp::Ord, +/// IR0: Iterator, +/// IR1: Iterator, +/// IO0: Iterator, +/// IO1: Iterator, +/// { +/// r0: IR0, +/// r1: IR1, +/// o0: Peekable, +/// o1: Peekable, +/// +/// o0_data_latest: Option, +/// o1_data_latest: Option, +/// } +/// ``` +fn generate_struct(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_types = izip!( + params + .to_required_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_required_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let optional_types = izip!( + params + .to_optional_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_optional_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let required_clauses = params.to_required_clauses(false /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(false /* into */).join(", "); + let required_params = params.to_required_params().join(", "); + let optional_params = params.to_optional_peekable_params().join(", "); + let optional_latest_params = izip!(params.to_optional_names(), params.to_optional_types()) + .map(|(n, t)| format!("{n}_data_latest: Option<{t}>")) + .join(", "); + + format!( + r#" + /// Implements a range zip iterator combinator with 2 required iterators and 2 optional + /// iterators. + /// + /// See [`range_zip_{suffix}`] for more information. + pub struct RangeZip{suffix} + where + Idx: std::cmp::Ord, + {required_clauses}, + {optional_clauses}, + {{ + {required_params}, + {optional_params}, + + {optional_latest_params}, + }} + "# + ) +} + +/// Output: +/// ```ignore +/// impl Iterator +/// for RangeZip2x2 +/// where +/// Idx: std::cmp::Ord, +/// IR0: Iterator, +/// IR1: Iterator, +/// IO0: Iterator, +/// IO1: Iterator, +/// O0: Clone, +/// O1: Clone, +/// { +/// type Item = (Idx, R0, R1, Option, Option); +/// +/// #[inline] +/// fn next(&mut self) -> Option { +/// let Self { +/// r0, +/// r1, +/// o0, +/// o1, +/// o0_data_latest, +/// o1_data_latest, +/// } = self; +/// +/// let (r0_index, r0_data) = r0.next()?; +/// let (r1_index, r1_data) = r1.next()?; +/// +/// let max_index = [r0_index, r1_index].into_iter().max()?; +/// +/// let mut o0_data = None; +/// while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { +/// o0_data = Some(data); +/// } +/// let o0_data = o0_data.or(o0_data_latest.take()); +/// o0_data_latest.clone_from(&o0_data); +/// +/// let mut o1_data = None; +/// while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { +/// o1_data = Some(data); +/// } +/// let o1_data = o1_data.or(o1_data_latest.take()); +/// o1_data_latest.clone_from(&o1_data); +/// +/// Some((max_index, r0_data, r1_data, o0_data, o1_data)) +/// } +/// } +/// ``` +fn generate_impl(params: &Params) -> String { + let suffix = params.to_suffix(); + let required_types = izip!( + params + .to_required_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_required_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let optional_types = izip!( + params + .to_optional_types() + .into_iter() + .map(|t| format!("I{t}")), + params.to_optional_types() + ) + .flat_map(|(tr, r)| [tr, r]) + .collect_vec() + .join(", "); + let required_names = params.to_required_names().join(", "); + let optional_names = params.to_optional_names().join(", "); + let optional_latest_names = params + .to_optional_names() + .into_iter() + .map(|n| format!("{n}_data_latest")) + .join(", "); + let required_indices = params + .to_required_names() + .into_iter() + .map(|n| format!("{n}_index")) + .collect_vec() + .join(", "); + let required_data = params + .to_required_names() + .into_iter() + .map(|n| format!("{n}_data")) + .collect_vec() + .join(", "); + let optional_data = params + .to_optional_names() + .into_iter() + .map(|n| format!("{n}_data")) + .collect_vec() + .join(", "); + let required_clauses = params.to_required_clauses(false /* into */).join(", "); + let optional_clauses = params.to_optional_clauses(false /* into */).join(", "); + let optional_clone_clauses = params + .to_optional_types() + .into_iter() + .map(|o| format!("{o}: Clone")) + .collect_vec() + .join(", "); + + let items = params + .to_required_types() + .into_iter() + .chain( + params + .to_optional_types() + .into_iter() + .map(|o| format!("Option<{o}>")), + ) + .collect_vec() + .join(", "); + + let next_required = params + .to_required_names() + .into_iter() + .map(|r| format!("let ({r}_index, {r}_data) = {r}.next()?;")) + .collect_vec() + .join("\n"); + + let next_optional = params + .to_optional_names() + .into_iter() + .map(|o| { + format!( + " + let mut {o}_data = None; + while let Some((_, data)) = {o}.next_if(|(index, _)| index <= &max_index) {{ + {o}_data = Some(data); + }} + let {o}_data = {o}_data.or({o}_data_latest.take()); + {o}_data_latest.clone_from(&{o}_data); + " + ) + }) + .collect_vec() + .join("\n"); + + format!( + r#" + impl Iterator for RangeZip{suffix} + where + Idx: std::cmp::Ord, + {required_clauses}, + {optional_clauses}, + {optional_clone_clauses}, + {{ + type Item = (Idx, {items}); + + #[inline] + fn next(&mut self) -> Option {{ + let Self {{ {required_names}, {optional_names}, {optional_latest_names} }} = self; + + {next_required} + + let max_index = [{required_indices}].into_iter().max()?; + + {next_optional} + + Some((max_index, {required_data}, {optional_data})) + }} + }} + "# + ) +} + +fn main() { + let num_required = 1..3; + let num_optional = 1..10; + + let output = num_required + .flat_map(|num_required| { + num_optional + .clone() + .map(move |num_optional| (num_required, num_optional)) + }) + .flat_map(|(num_required, num_optional)| { + let params = Params { + num_required, + num_optional, + }; + + [ + generate_helper_func(¶ms), + generate_struct(¶ms), + generate_impl(¶ms), + ] + }) + .collect_vec() + .join("\n"); + + println!( + " + // This file was generated using `cargo r -p re_query2 --all-features --bin range_zip`. + // DO NOT EDIT. + + // --- + + #![allow(clippy::iter_on_single_items)] + #![allow(clippy::too_many_arguments)] + #![allow(clippy::type_complexity)] + + use std::iter::Peekable; + + {output} + " + ); +} diff --git a/crates/store/re_query2/src/cache.rs b/crates/store/re_query2/src/cache.rs new file mode 100644 index 000000000000..2be830778a06 --- /dev/null +++ b/crates/store/re_query2/src/cache.rs @@ -0,0 +1,275 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; + +use ahash::HashMap; +use nohash_hasher::IntSet; +use parking_lot::RwLock; + +use re_chunk::ChunkId; +use re_chunk_store::{ChunkStore, ChunkStoreDiff, ChunkStoreEvent, ChunkStoreSubscriber}; +use re_log_types::{EntityPath, ResolvedTimeRange, StoreId, TimeInt, Timeline}; +use re_types_core::{components::ClearIsRecursive, ComponentName, Loggable as _}; + +use crate::LatestAtCache; + +// --- + +/// Uniquely identifies cached query results in the [`Caches`]. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct CacheKey { + pub entity_path: EntityPath, + pub timeline: Timeline, + pub component_name: ComponentName, +} + +impl re_types_core::SizeBytes for CacheKey { + #[inline] + fn heap_size_bytes(&self) -> u64 { + let Self { + entity_path, + timeline, + component_name, + } = self; + entity_path.heap_size_bytes() + + timeline.heap_size_bytes() + + component_name.heap_size_bytes() + } +} + +impl std::fmt::Debug for CacheKey { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + entity_path, + timeline, + component_name, + } = self; + f.write_fmt(format_args!( + "{entity_path}:{component_name} on {}", + timeline.name() + )) + } +} + +impl CacheKey { + #[inline] + pub fn new( + entity_path: impl Into, + timeline: impl Into, + component_name: impl Into, + ) -> Self { + Self { + entity_path: entity_path.into(), + timeline: timeline.into(), + component_name: component_name.into(), + } + } +} + +pub struct Caches { + /// The [`StoreId`] of the associated [`ChunkStore`]. + pub(crate) store_id: StoreId, + + /// Keeps track of which entities have had any `Clear`-related data on any timeline at any + /// point in time. + /// + /// This is used to optimized read-time clears, so that we don't unnecessarily pay for the fixed + /// overhead of all the query layers when we know for a fact that there won't be any data there. + /// This is a huge performance improvement in practice, especially in recordings with many entities. + pub(crate) might_require_clearing: RwLock>, + + // NOTE: `Arc` so we can cheaply free the top-level lock early when needed. + pub(crate) latest_at_per_cache_key: RwLock>>>, +} + +impl std::fmt::Debug for Caches { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + store_id, + might_require_clearing, + latest_at_per_cache_key, + } = self; + + let mut strings = Vec::new(); + + strings.push(format!( + "[Entities that must be checked for clears @ {store_id}]\n" + )); + { + let sorted: BTreeSet = + might_require_clearing.read().iter().cloned().collect(); + for entity_path in sorted { + strings.push(format!(" * {entity_path}\n")); + } + strings.push("\n".to_owned()); + } + + strings.push(format!("[LatestAt @ {store_id}]")); + { + let latest_at_per_cache_key = latest_at_per_cache_key.read(); + let latest_at_per_cache_key: BTreeMap<_, _> = latest_at_per_cache_key.iter().collect(); + + for (cache_key, cache) in &latest_at_per_cache_key { + let cache = cache.read(); + strings.push(format!( + " [{cache_key:?} (pending_invalidation_min={:?})]", + cache.pending_invalidation.map(|t| cache_key + .timeline + .format_time_range_utc(&ResolvedTimeRange::new(t, TimeInt::MAX))), + )); + strings.push(indent::indent_all_by(4, format!("{cache:?}"))); + } + } + + f.write_str(&strings.join("\n").replace("\n\n", "\n")) + } +} + +impl Caches { + #[inline] + pub fn new(store: &ChunkStore) -> Self { + Self { + store_id: store.id().clone(), + might_require_clearing: Default::default(), + latest_at_per_cache_key: Default::default(), + } + } + + #[inline] + pub fn clear(&self) { + let Self { + store_id: _, + might_require_clearing, + latest_at_per_cache_key, + } = self; + + might_require_clearing.write().clear(); + latest_at_per_cache_key.write().clear(); + } +} + +impl ChunkStoreSubscriber for Caches { + #[inline] + fn name(&self) -> String { + "rerun.store_subscribers.QueryCache".into() + } + + #[inline] + fn as_any(&self) -> &dyn std::any::Any { + self + } + + #[inline] + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn on_events(&mut self, events: &[ChunkStoreEvent]) { + re_tracing::profile_function!(format!("num_events={}", events.len())); + + #[derive(Default, Debug)] + struct CompactedEvents { + static_: HashMap<(EntityPath, ComponentName), BTreeSet>, + temporal_latest_at: HashMap, + } + + let mut compacted = CompactedEvents::default(); + + for event in events { + let ChunkStoreEvent { + store_id, + store_generation: _, + event_id: _, + diff, + } = event; + + assert!( + self.store_id == *store_id, + "attempted to use a query cache {} with the wrong datastore ({})", + self.store_id, + store_id, + ); + + let ChunkStoreDiff { + kind: _, // Don't care: both additions and deletions invalidate query results. + chunk, + compacted: _, + } = diff; + + { + re_tracing::profile_scope!("compact events"); + + if chunk.is_static() { + for component_name in chunk.component_names() { + compacted + .static_ + .entry((chunk.entity_path().clone(), component_name)) + .or_default() + .insert(chunk.id()); + } + } + + for (&timeline, time_chunk) in chunk.timelines() { + for data_time in time_chunk.times() { + for component_name in chunk.component_names() { + let key = CacheKey::new( + chunk.entity_path().clone(), + timeline, + component_name, + ); + + compacted + .temporal_latest_at + .entry(key.clone()) + .and_modify(|time| *time = TimeInt::min(*time, data_time)) + .or_insert(data_time); + } + } + } + } + } + + let mut might_require_clearing = self.might_require_clearing.write(); + let caches_latest_at = self.latest_at_per_cache_key.write(); + // NOTE: Don't release the top-level locks -- even though this cannot happen yet with + // our current macro-architecture, we want to prevent queries from concurrently + // running while we're updating the invalidation flags. + + { + re_tracing::profile_scope!("static"); + + // TODO(cmc): This is horribly stupid and slow and can easily be made faster by adding + // yet another layer of caching indirection. + // But since this pretty much never happens in practice, let's not go there until we + // have metrics showing that show we need to. + for ((entity_path, component_name), _chunk_ids) in compacted.static_ { + if component_name == ClearIsRecursive::name() { + might_require_clearing.insert(entity_path.clone()); + } + + for (key, cache) in caches_latest_at.iter() { + if key.entity_path == entity_path && key.component_name == component_name { + cache.write().pending_invalidation = Some(TimeInt::STATIC); + } + } + } + } + + { + re_tracing::profile_scope!("temporal"); + + for (key, time) in compacted.temporal_latest_at { + if key.component_name == ClearIsRecursive::name() { + might_require_clearing.insert(key.entity_path.clone()); + } + + if let Some(cache) = caches_latest_at.get(&key) { + let mut cache = cache.write(); + cache.pending_invalidation = Some(time); + } + } + } + } +} diff --git a/crates/store/re_query2/src/cache_stats.rs b/crates/store/re_query2/src/cache_stats.rs new file mode 100644 index 000000000000..859dfed1a469 --- /dev/null +++ b/crates/store/re_query2/src/cache_stats.rs @@ -0,0 +1,78 @@ +use std::collections::BTreeMap; + +use re_types_core::SizeBytes as _; + +use crate::{CacheKey, Caches}; + +// --- + +/// Stats for all primary caches. +/// +/// Fetch them via [`Caches::stats`]. +#[derive(Default, Debug, Clone)] +pub struct CachesStats { + pub latest_at: BTreeMap, +} + +impl CachesStats { + #[inline] + pub fn total_size_bytes(&self) -> u64 { + re_tracing::profile_function!(); + + let Self { latest_at } = self; + + let latest_at_size_bytes: u64 = latest_at + .values() + .map(|stats| stats.total_actual_size_bytes) + .sum(); + + latest_at_size_bytes + } +} + +/// Stats for a single `crate::RangeCache`. +#[derive(Default, Debug, Clone)] +pub struct CacheStats { + /// How many chunks in the cache? + pub total_chunks: u64, + + /// What would be the size of this cache in the worst case, i.e. if all chunks had + /// been fully copied? + pub total_effective_size_bytes: u64, + + /// What is the actual size of this cache after deduplication? + pub total_actual_size_bytes: u64, +} + +impl Caches { + /// Computes the stats for all primary caches. + pub fn stats(&self) -> CachesStats { + re_tracing::profile_function!(); + + let latest_at = { + let latest_at = self.latest_at_per_cache_key.read().clone(); + // Implicitly releasing top-level cache mappings -- concurrent queries can run once again. + + latest_at + .iter() + .map(|(key, cache)| { + let cache = cache.read(); + ( + key.clone(), + CacheStats { + total_chunks: cache.per_query_time.len() as _, + total_effective_size_bytes: cache + .per_query_time + .values() + .map(|cached| cached.unit.total_size_bytes()) + .sum(), + total_actual_size_bytes: cache.per_query_time.total_size_bytes(), + }, + ) + }) + .collect() + }; + + CachesStats { latest_at } + } +} diff --git a/crates/store/re_query2/src/clamped_zip/.gitattributes b/crates/store/re_query2/src/clamped_zip/.gitattributes new file mode 100644 index 000000000000..30d202506064 --- /dev/null +++ b/crates/store/re_query2/src/clamped_zip/.gitattributes @@ -0,0 +1 @@ +generated.rs linguist-generated=true diff --git a/crates/store/re_query2/src/clamped_zip/generated.rs b/crates/store/re_query2/src/clamped_zip/generated.rs new file mode 100644 index 000000000000..37ad262e6c8a --- /dev/null +++ b/crates/store/re_query2/src/clamped_zip/generated.rs @@ -0,0 +1,3124 @@ +// This file was generated using `cargo r -p re_query2 --all-features --bin clamped_zip`. +// DO NOT EDIT. + +// --- + +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + +/// Returns a new [`ClampedZip1x1`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x1( + r0: R0, + o0: O0, + o0_default_fn: D0, +) -> ClampedZip1x1 +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + ClampedZip1x1 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o0_default_fn, + o0_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x1`] for more information. +pub struct ClampedZip1x1 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + r0: R0, + o0: O0, + o0_default_fn: D0, + + o0_latest_value: Option, +} + +impl Iterator for ClampedZip1x1 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + type Item = (R0::Item, O0::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + + Some((r0_next, o0_next.unwrap_or_else(|| (self.o0_default_fn)()))) + } +} + +/// Returns a new [`ClampedZip1x2`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x2( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, +) -> ClampedZip1x2 +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + ClampedZip1x2 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o0_default_fn, + o1_default_fn, + o0_latest_value: None, + o1_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x2`] for more information. +pub struct ClampedZip1x2 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o0_default_fn: D0, + o1_default_fn: D1, + + o0_latest_value: Option, + o1_latest_value: Option, +} + +impl Iterator for ClampedZip1x2 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + type Item = (R0::Item, O0::Item, O1::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x3`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x3( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, +) -> ClampedZip1x3 +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + ClampedZip1x3 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x3`] for more information. +pub struct ClampedZip1x3 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, +} + +impl Iterator for ClampedZip1x3 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + type Item = (R0::Item, O0::Item, O1::Item, O2::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x4`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x4( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, +) -> ClampedZip1x4< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + D0, + D1, + D2, + D3, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + ClampedZip1x4 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x4`] for more information. +pub struct ClampedZip1x4 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, +} + +impl Iterator + for ClampedZip1x4 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + type Item = (R0::Item, O0::Item, O1::Item, O2::Item, O3::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x5`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x5( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, +) -> ClampedZip1x5< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + D0, + D1, + D2, + D3, + D4, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + ClampedZip1x5 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x5`] for more information. +pub struct ClampedZip1x5 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, +} + +impl Iterator + for ClampedZip1x5 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + type Item = (R0::Item, O0::Item, O1::Item, O2::Item, O3::Item, O4::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x6`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x6( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, +) -> ClampedZip1x6< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + ClampedZip1x6 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x6`] for more information. +pub struct ClampedZip1x6 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, +} + +impl Iterator + for ClampedZip1x6 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + type Item = ( + R0::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x7`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x7( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, +) -> ClampedZip1x7< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + ClampedZip1x7 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x7`] for more information. +pub struct ClampedZip1x7 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, +} + +impl Iterator + for ClampedZip1x7 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + type Item = ( + R0::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x8`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x8( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, + o7: O7, + o7_default_fn: D7, +) -> ClampedZip1x8< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + O7::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + O7: IntoIterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + ClampedZip1x8 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o7: o7.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o7_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + o7_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x8`] for more information. +pub struct ClampedZip1x8 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o7: O7, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + o7_default_fn: D7, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, + o7_latest_value: Option, +} + +impl Iterator + for ClampedZip1x8 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + type Item = ( + R0::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + O7::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + let o7_next = self.o7.next().or(self.o7_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + self.o7_latest_value.clone_from(&o7_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + o7_next.unwrap_or_else(|| (self.o7_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip1x9`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`, `o8`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`, `o8_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_1x9( + r0: R0, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, + o7: O7, + o7_default_fn: D7, + o8: O8, + o8_default_fn: D8, +) -> ClampedZip1x9< + R0::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + O7::IntoIter, + O8::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, +> +where + R0: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + O7: IntoIterator, + O7::Item: Clone, + O8: IntoIterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + ClampedZip1x9 { + r0: r0.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o7: o7.into_iter(), + o8: o8.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o7_default_fn, + o8_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + o7_latest_value: None, + o8_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_1x9`] for more information. +pub struct ClampedZip1x9 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + O8: Iterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + r0: R0, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o7: O7, + o8: O8, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + o7_default_fn: D7, + o8_default_fn: D8, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, + o7_latest_value: Option, + o8_latest_value: Option, +} + +impl Iterator + for ClampedZip1x9 +where + R0: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + O8: Iterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + type Item = ( + R0::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + O7::Item, + O8::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + let o7_next = self.o7.next().or(self.o7_latest_value.take()); + let o8_next = self.o8.next().or(self.o8_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + self.o7_latest_value.clone_from(&o7_next); + self.o8_latest_value.clone_from(&o8_next); + + Some(( + r0_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + o7_next.unwrap_or_else(|| (self.o7_default_fn)()), + o8_next.unwrap_or_else(|| (self.o8_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x1`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x1( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, +) -> ClampedZip2x1 +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + ClampedZip2x1 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o0_default_fn, + o0_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x1`] for more information. +pub struct ClampedZip2x1 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + + o0_latest_value: Option, +} + +impl Iterator for ClampedZip2x1 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + D0: Fn() -> O0::Item, +{ + type Item = (R0::Item, R1::Item, O0::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x2`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x2( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, +) -> ClampedZip2x2 +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + ClampedZip2x2 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o0_default_fn, + o1_default_fn, + o0_latest_value: None, + o1_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x2`] for more information. +pub struct ClampedZip2x2 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o0_default_fn: D0, + o1_default_fn: D1, + + o0_latest_value: Option, + o1_latest_value: Option, +} + +impl Iterator for ClampedZip2x2 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, +{ + type Item = (R0::Item, R1::Item, O0::Item, O1::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x3`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x3( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, +) -> ClampedZip2x3 +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + ClampedZip2x3 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x3`] for more information. +pub struct ClampedZip2x3 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, +} + +impl Iterator for ClampedZip2x3 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, +{ + type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x4`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x4( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, +) -> ClampedZip2x4< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + D0, + D1, + D2, + D3, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + ClampedZip2x4 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x4`] for more information. +pub struct ClampedZip2x4 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, +} + +impl Iterator + for ClampedZip2x4 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, +{ + type Item = (R0::Item, R1::Item, O0::Item, O1::Item, O2::Item, O3::Item); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x5`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x5( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, +) -> ClampedZip2x5< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + D0, + D1, + D2, + D3, + D4, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + ClampedZip2x5 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x5`] for more information. +pub struct ClampedZip2x5 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, +} + +impl Iterator + for ClampedZip2x5 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, +{ + type Item = ( + R0::Item, + R1::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x6`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x6( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, +) -> ClampedZip2x6< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + ClampedZip2x6 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x6`] for more information. +pub struct ClampedZip2x6 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, +} + +impl Iterator + for ClampedZip2x6 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, +{ + type Item = ( + R0::Item, + R1::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x7`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x7( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, +) -> ClampedZip2x7< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + ClampedZip2x7 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x7`] for more information. +pub struct ClampedZip2x7 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, +} + +impl Iterator + for ClampedZip2x7 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, +{ + type Item = ( + R0::Item, + R1::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x8`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x8( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, + o7: O7, + o7_default_fn: D7, +) -> ClampedZip2x8< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + O7::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + O7: IntoIterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + ClampedZip2x8 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o7: o7.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o7_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + o7_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x8`] for more information. +pub struct ClampedZip2x8 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o7: O7, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + o7_default_fn: D7, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, + o7_latest_value: Option, +} + +impl Iterator + for ClampedZip2x8 +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, +{ + type Item = ( + R0::Item, + R1::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + O7::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + let o7_next = self.o7.next().or(self.o7_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + self.o7_latest_value.clone_from(&o7_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + o7_next.unwrap_or_else(|| (self.o7_default_fn)()), + )) + } +} + +/// Returns a new [`ClampedZip2x9`] iterator. +/// +/// The number of elements in a clamped zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Optional iterators (`o0`, `o1`, `o2`, `o3`, `o4`, `o5`, `o6`, `o7`, `o8`) will repeat their latest values if they happen to be too short +/// to be zipped with the shortest of the required iterators. +/// +/// If an optional iterator is not only too short but actually empty, its associated default function +/// (`o0_default_fn`, `o1_default_fn`, `o2_default_fn`, `o3_default_fn`, `o4_default_fn`, `o5_default_fn`, `o6_default_fn`, `o7_default_fn`, `o8_default_fn`) will be executed and the resulting value repeated as necessary. +pub fn clamped_zip_2x9< + R0, + R1, + O0, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, +>( + r0: R0, + r1: R1, + o0: O0, + o0_default_fn: D0, + o1: O1, + o1_default_fn: D1, + o2: O2, + o2_default_fn: D2, + o3: O3, + o3_default_fn: D3, + o4: O4, + o4_default_fn: D4, + o5: O5, + o5_default_fn: D5, + o6: O6, + o6_default_fn: D6, + o7: O7, + o7_default_fn: D7, + o8: O8, + o8_default_fn: D8, +) -> ClampedZip2x9< + R0::IntoIter, + R1::IntoIter, + O0::IntoIter, + O1::IntoIter, + O2::IntoIter, + O3::IntoIter, + O4::IntoIter, + O5::IntoIter, + O6::IntoIter, + O7::IntoIter, + O8::IntoIter, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, +> +where + R0: IntoIterator, + R1: IntoIterator, + O0: IntoIterator, + O0::Item: Clone, + O1: IntoIterator, + O1::Item: Clone, + O2: IntoIterator, + O2::Item: Clone, + O3: IntoIterator, + O3::Item: Clone, + O4: IntoIterator, + O4::Item: Clone, + O5: IntoIterator, + O5::Item: Clone, + O6: IntoIterator, + O6::Item: Clone, + O7: IntoIterator, + O7::Item: Clone, + O8: IntoIterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + ClampedZip2x9 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter(), + o1: o1.into_iter(), + o2: o2.into_iter(), + o3: o3.into_iter(), + o4: o4.into_iter(), + o5: o5.into_iter(), + o6: o6.into_iter(), + o7: o7.into_iter(), + o8: o8.into_iter(), + o0_default_fn, + o1_default_fn, + o2_default_fn, + o3_default_fn, + o4_default_fn, + o5_default_fn, + o6_default_fn, + o7_default_fn, + o8_default_fn, + o0_latest_value: None, + o1_latest_value: None, + o2_latest_value: None, + o3_latest_value: None, + o4_latest_value: None, + o5_latest_value: None, + o6_latest_value: None, + o7_latest_value: None, + o8_latest_value: None, + } +} + +/// Implements a clamped zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`clamped_zip_2x9`] for more information. +pub struct ClampedZip2x9< + R0, + R1, + O0, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, +> where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + O8: Iterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + r0: R0, + r1: R1, + o0: O0, + o1: O1, + o2: O2, + o3: O3, + o4: O4, + o5: O5, + o6: O6, + o7: O7, + o8: O8, + o0_default_fn: D0, + o1_default_fn: D1, + o2_default_fn: D2, + o3_default_fn: D3, + o4_default_fn: D4, + o5_default_fn: D5, + o6_default_fn: D6, + o7_default_fn: D7, + o8_default_fn: D8, + + o0_latest_value: Option, + o1_latest_value: Option, + o2_latest_value: Option, + o3_latest_value: Option, + o4_latest_value: Option, + o5_latest_value: Option, + o6_latest_value: Option, + o7_latest_value: Option, + o8_latest_value: Option, +} + +impl Iterator + for ClampedZip2x9< + R0, + R1, + O0, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + > +where + R0: Iterator, + R1: Iterator, + O0: Iterator, + O0::Item: Clone, + O1: Iterator, + O1::Item: Clone, + O2: Iterator, + O2::Item: Clone, + O3: Iterator, + O3::Item: Clone, + O4: Iterator, + O4::Item: Clone, + O5: Iterator, + O5::Item: Clone, + O6: Iterator, + O6::Item: Clone, + O7: Iterator, + O7::Item: Clone, + O8: Iterator, + O8::Item: Clone, + D0: Fn() -> O0::Item, + D1: Fn() -> O1::Item, + D2: Fn() -> O2::Item, + D3: Fn() -> O3::Item, + D4: Fn() -> O4::Item, + D5: Fn() -> O5::Item, + D6: Fn() -> O6::Item, + D7: Fn() -> O7::Item, + D8: Fn() -> O8::Item, +{ + type Item = ( + R0::Item, + R1::Item, + O0::Item, + O1::Item, + O2::Item, + O3::Item, + O4::Item, + O5::Item, + O6::Item, + O7::Item, + O8::Item, + ); + + #[inline] + fn next(&mut self) -> Option { + let r0_next = self.r0.next()?; + let r1_next = self.r1.next()?; + let o0_next = self.o0.next().or(self.o0_latest_value.take()); + let o1_next = self.o1.next().or(self.o1_latest_value.take()); + let o2_next = self.o2.next().or(self.o2_latest_value.take()); + let o3_next = self.o3.next().or(self.o3_latest_value.take()); + let o4_next = self.o4.next().or(self.o4_latest_value.take()); + let o5_next = self.o5.next().or(self.o5_latest_value.take()); + let o6_next = self.o6.next().or(self.o6_latest_value.take()); + let o7_next = self.o7.next().or(self.o7_latest_value.take()); + let o8_next = self.o8.next().or(self.o8_latest_value.take()); + + self.o0_latest_value.clone_from(&o0_next); + self.o1_latest_value.clone_from(&o1_next); + self.o2_latest_value.clone_from(&o2_next); + self.o3_latest_value.clone_from(&o3_next); + self.o4_latest_value.clone_from(&o4_next); + self.o5_latest_value.clone_from(&o5_next); + self.o6_latest_value.clone_from(&o6_next); + self.o7_latest_value.clone_from(&o7_next); + self.o8_latest_value.clone_from(&o8_next); + + Some(( + r0_next, + r1_next, + o0_next.unwrap_or_else(|| (self.o0_default_fn)()), + o1_next.unwrap_or_else(|| (self.o1_default_fn)()), + o2_next.unwrap_or_else(|| (self.o2_default_fn)()), + o3_next.unwrap_or_else(|| (self.o3_default_fn)()), + o4_next.unwrap_or_else(|| (self.o4_default_fn)()), + o5_next.unwrap_or_else(|| (self.o5_default_fn)()), + o6_next.unwrap_or_else(|| (self.o6_default_fn)()), + o7_next.unwrap_or_else(|| (self.o7_default_fn)()), + o8_next.unwrap_or_else(|| (self.o8_default_fn)()), + )) + } +} diff --git a/crates/store/re_query2/src/clamped_zip/mod.rs b/crates/store/re_query2/src/clamped_zip/mod.rs new file mode 100644 index 000000000000..65bd61b05021 --- /dev/null +++ b/crates/store/re_query2/src/clamped_zip/mod.rs @@ -0,0 +1,64 @@ +mod generated; +pub use self::generated::*; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn r0_is_empty_o0_is_empty() { + let r0 = std::iter::empty::(); + let o0 = (0..).map(|n| n.to_string()); + + let expected: Vec<(u32, String)> = vec![]; + let got = clamped_zip_1x1(r0, o0, String::new).collect::>(); + + similar_asserts::assert_eq!(expected, got); + } + + #[test] + fn r0_and_o0_are_matched() { + let r0 = 0..20u32; + let o0 = (0..20).map(|n| n.to_string()); + + let expected: Vec<(u32, String)> = (0..20u32).map(|n| (n, n.to_string())).collect(); + let got = clamped_zip_1x1(r0, o0, String::new).collect::>(); + + similar_asserts::assert_eq!(expected, got); + } + + #[test] + fn r0_is_shorter() { + let r0 = 0..10u32; + let o0 = (0..20).map(|n| n.to_string()); + + let expected: Vec<(u32, String)> = (0..10u32).map(|n| (n, n.to_string())).collect(); + let got = clamped_zip_1x1(r0, o0, String::new).collect::>(); + + similar_asserts::assert_eq!(expected, got); + } + + #[test] + fn r0_is_longer() { + let r0 = 0..30u32; + let o0 = (0..20).map(|n| n.to_string()); + + let expected: Vec<(u32, String)> = (0..30u32) + .map(|n| (n, u32::min(n, 19).to_string())) + .collect(); + let got = clamped_zip_1x1(r0, o0, String::new).collect::>(); + + similar_asserts::assert_eq!(expected, got); + } + + #[test] + fn r0_is_longer_and_o0_is_empty() { + let r0 = 0..10u32; + let o0 = std::iter::empty(); + + let expected: Vec<(u32, String)> = (0..10u32).map(|n| (n, "hey".to_owned())).collect(); + let got = clamped_zip_1x1(r0, o0, || "hey".to_owned()).collect::>(); + + similar_asserts::assert_eq!(expected, got); + } +} diff --git a/crates/store/re_query2/src/latest_at.rs b/crates/store/re_query2/src/latest_at.rs new file mode 100644 index 000000000000..b4390d80b9b8 --- /dev/null +++ b/crates/store/re_query2/src/latest_at.rs @@ -0,0 +1,683 @@ +use std::{collections::BTreeMap, sync::Arc}; + +use arrow2::array::Array as ArrowArray; +use nohash_hasher::IntMap; +use parking_lot::RwLock; + +use re_chunk::{Chunk, RowId, UnitChunkShared}; +use re_chunk_store::{ChunkStore, LatestAtQuery, TimeInt}; +use re_log_types::EntityPath; +use re_types_core::{ + components::ClearIsRecursive, Component, ComponentName, Loggable as _, SizeBytes, +}; + +use crate::{CacheKey, Caches, QueryError}; + +// --- Public API --- + +/// Compute the ordering of two data indices, making sure to deal with `STATIC` data appropriately. +// +// TODO(cmc): Maybe at some point we'll want to introduce a dedicated `DataIndex` type with +// proper ordering operators etc. +// It's harder than it sounds though -- depending on the context, you don't necessarily want index +// ordering to behave the same way. +fn compare_indices(lhs: (TimeInt, RowId), rhs: (TimeInt, RowId)) -> std::cmp::Ordering { + match (lhs, rhs) { + ((TimeInt::STATIC, lhs_row_id), (TimeInt::STATIC, rhs_row_id)) => { + lhs_row_id.cmp(&rhs_row_id) + } + ((_, _), (TimeInt::STATIC, _)) => std::cmp::Ordering::Less, + ((TimeInt::STATIC, _), (_, _)) => std::cmp::Ordering::Greater, + _ => lhs.cmp(&rhs), + } +} + +impl Caches { + /// Queries for the given `component_names` using latest-at semantics. + /// + /// See [`LatestAtResults`] for more information about how to handle the results. + /// + /// This is a cached API -- data will be lazily cached upon access. + pub fn latest_at( + &self, + store: &ChunkStore, + query: &LatestAtQuery, + entity_path: &EntityPath, + component_names: impl IntoIterator, + ) -> LatestAtResults { + re_tracing::profile_function!(entity_path.to_string()); + + let mut results = LatestAtResults::empty(entity_path.clone(), query.clone()); + + // NOTE: This pre-filtering is extremely important: going through all these query layers + // has non-negligible overhead even if the final result ends up being nothing, and our + // number of queries for a frame grows linearly with the number of entity paths. + let component_names = component_names.into_iter().filter(|component_name| { + store.entity_has_component_on_timeline(&query.timeline(), entity_path, component_name) + }); + + // Query-time clears + // ----------------- + // + // We need to find, at query time, whether there exist a `Clear` component that should + // shadow part or all of the results that we are about to return. + // + // This is a two-step process. + // + // First, we need to find all `Clear` components that could potentially affect the returned + // results, i.e. any `Clear` component on the entity itself, or any recursive `Clear` + // component on any of its recursive parents. + // + // Then, we need to compare the index of each component result with the index of the most + // recent relevant `Clear` component that was found: if there exists a `Clear` component with + // both a _data time_ lesser or equal to the _query time_ and an index greater or equal + // than the indexed of the returned data, then we know for sure that the `Clear` shadows + // the data. + let mut max_clear_index = (TimeInt::MIN, RowId::ZERO); + { + re_tracing::profile_scope!("clears"); + + let potential_clears = self.might_require_clearing.read(); + + let mut clear_entity_path = entity_path.clone(); + loop { + if !potential_clears.contains(&clear_entity_path) { + // This entity does not contain any `Clear`-related data at all, there's no + // point in running actual queries. + + let Some(parent_entity_path) = clear_entity_path.parent() else { + break; + }; + clear_entity_path = parent_entity_path; + + continue; + } + + let key = CacheKey::new( + clear_entity_path.clone(), + query.timeline(), + ClearIsRecursive::name(), + ); + + let cache = Arc::clone( + self.latest_at_per_cache_key + .write() + .entry(key.clone()) + .or_insert_with(|| Arc::new(RwLock::new(LatestAtCache::new(key.clone())))), + ); + + let mut cache = cache.write(); + cache.handle_pending_invalidation(); + if let Some(cached) = + cache.latest_at(store, query, &clear_entity_path, ClearIsRecursive::name()) + { + let found_recursive_clear = cached + .component_mono::() + .and_then(Result::ok) + == Some(ClearIsRecursive(true.into())); + // When checking the entity itself, any kind of `Clear` component + // (i.e. recursive or not) will do. + // + // For (recursive) parents, we need to deserialize the data to make sure the + // recursive flag is set. + #[allow(clippy::collapsible_if)] // readability + if clear_entity_path == *entity_path || found_recursive_clear { + if let Some(index) = cached.index(&query.timeline()) { + if compare_indices(index, max_clear_index) + == std::cmp::Ordering::Greater + { + max_clear_index = index; + } + } + } + } + + let Some(parent_entity_path) = clear_entity_path.parent() else { + break; + }; + + clear_entity_path = parent_entity_path; + } + } + + for component_name in component_names { + let key = CacheKey::new(entity_path.clone(), query.timeline(), component_name); + + let cache = Arc::clone( + self.latest_at_per_cache_key + .write() + .entry(key.clone()) + .or_insert_with(|| Arc::new(RwLock::new(LatestAtCache::new(key.clone())))), + ); + + let mut cache = cache.write(); + cache.handle_pending_invalidation(); + if let Some(cached) = cache.latest_at(store, query, entity_path, component_name) { + // 1. A `Clear` component doesn't shadow its own self. + // 2. If a `Clear` component was found with an index greater than or equal to the + // component data, then we know for sure that it should shadow it. + if let Some(index) = cached.index(&query.timeline()) { + if component_name == ClearIsRecursive::name() + || compare_indices(index, max_clear_index) == std::cmp::Ordering::Greater + { + results.add(component_name, index, cached); + } + } + } + } + + results + } +} + +// --- Results --- + +/// Results for a latest-at query. +/// +/// Use [`LatestAtResults::get`] and/or [`LatestAtResults::get_required`] in order to access +/// the results for each individual component. +#[derive(Debug)] +pub struct LatestAtResults { + /// The associated [`EntityPath`]. + pub entity_path: EntityPath, + + /// The query that yielded these results. + pub query: LatestAtQuery, + + /// The compound index of this query result. + /// + /// A latest-at query is a compound operation that gathers data from many different rows. + /// The index of that compound result corresponds to the index of most the recent row in all the + /// sub-results, as defined by time and row-id order. + pub compound_index: (TimeInt, RowId), + + /// Results for each individual component. + /// + /// Each [`UnitChunkShared`] MUST always contain the corresponding component. + pub components: IntMap, +} + +impl LatestAtResults { + #[inline] + pub fn empty(entity_path: EntityPath, query: LatestAtQuery) -> Self { + Self { + entity_path, + query, + compound_index: (TimeInt::STATIC, RowId::ZERO), + components: Default::default(), + } + } +} + +impl LatestAtResults { + #[inline] + pub fn contains(&self, component_name: &ComponentName) -> bool { + self.components.contains_key(component_name) + } + + /// Returns the [`UnitChunkShared`] for the specified [`Component`]. + #[inline] + pub fn get(&self, component_name: &ComponentName) -> Option<&UnitChunkShared> { + self.components.get(component_name) + } + + /// Returns the [`UnitChunkShared`] for the specified [`Component`]. + /// + /// Returns an error if the component is not present. + #[inline] + pub fn get_required(&self, component_name: &ComponentName) -> crate::Result<&UnitChunkShared> { + if let Some(component) = self.get(component_name) { + Ok(component) + } else { + Err(QueryError::PrimaryNotFound(*component_name)) + } + } + + /// Returns the compound index (`(TimeInt, RowId)` pair) of the results. + #[inline] + pub fn index(&self) -> (TimeInt, RowId) { + self.compound_index + } +} + +impl LatestAtResults { + #[doc(hidden)] + #[inline] + pub fn add( + &mut self, + component_name: ComponentName, + index: (TimeInt, RowId), + chunk: UnitChunkShared, + ) { + debug_assert!(chunk.num_rows() == 1); + + // NOTE: Since this is a compound API that actually emits multiple queries, the index of the + // final result is the most recent index among all of its components, as defined by time + // and row-id order. + if index > self.compound_index { + self.compound_index = index; + } + + self.components.insert(component_name, chunk); + } +} + +// --- Helpers --- +// +// Helpers for UI and other high-level/user-facing code. +// +// In particular, these replace all error handling with logs instead. + +impl LatestAtResults { + // --- Batch --- + + /// Returns the raw data for the specified component. + #[inline] + pub fn component_batch_raw( + &self, + component_name: &ComponentName, + ) -> Option> { + self.components + .get(component_name) + .and_then(|unit| unit.component_batch_raw(component_name)) + } + + /// Returns the deserialized data for the specified component. + /// + /// Logs at the specified `log_level` if the data cannot be deserialized. + #[inline] + pub fn component_batch_with_log_level( + &self, + log_level: re_log::Level, + ) -> Option> { + self.components + .get(&C::name()) + .and_then(|unit| self.ok_or_log_err(log_level, C::name(), unit.component_batch()?)) + } + + /// Returns the deserialized data for the specified component. + /// + /// Logs an error if the data cannot be deserialized. + #[inline] + pub fn component_batch(&self) -> Option> { + self.component_batch_with_log_level(re_log::Level::Error) + } + + /// Returns the deserialized data for the specified component. + #[inline] + pub fn component_batch_quiet(&self) -> Option> { + self.components + .get(&C::name()) + .and_then(|unit| unit.component_batch()?.ok()) + } + + // --- Instance --- + + /// Returns the raw data for the specified component at the given instance index. + /// + /// Logs at the specified `log_level` if the instance index is out of bounds. + #[inline] + pub fn component_instance_raw_with_log_level( + &self, + log_level: re_log::Level, + component_name: &ComponentName, + instance_index: usize, + ) -> Option> { + self.components.get(component_name).and_then(|unit| { + self.ok_or_log_err( + log_level, + *component_name, + unit.component_instance_raw(component_name, instance_index)?, + ) + }) + } + + /// Returns the raw data for the specified component at the given instance index. + /// + /// Logs an error if the instance index is out of bounds. + #[inline] + pub fn component_instance_raw( + &self, + component_name: &ComponentName, + instance_index: usize, + ) -> Option> { + self.component_instance_raw_with_log_level( + re_log::Level::Error, + component_name, + instance_index, + ) + } + + /// Returns the raw data for the specified component at the given instance index. + #[inline] + pub fn component_instance_raw_quiet( + &self, + component_name: &ComponentName, + instance_index: usize, + ) -> Option> { + self.components.get(component_name).and_then(|unit| { + unit.component_instance_raw(component_name, instance_index)? + .ok() + }) + } + + /// Returns the deserialized data for the specified component at the given instance index. + /// + /// Logs at the specified `log_level` if the data cannot be deserialized, or if the instance index + /// is out of bounds. + #[inline] + pub fn component_instance_with_log_level( + &self, + log_level: re_log::Level, + instance_index: usize, + ) -> Option { + self.components.get(&C::name()).and_then(|unit| { + self.ok_or_log_err( + log_level, + C::name(), + unit.component_instance(instance_index)?, + ) + }) + } + + /// Returns the deserialized data for the specified component at the given instance index. + /// + /// Logs an error if the data cannot be deserialized, or if the instance index is out of bounds. + #[inline] + pub fn component_instance(&self, instance_index: usize) -> Option { + self.component_instance_with_log_level(re_log::Level::Error, instance_index) + } + + /// Returns the deserialized data for the specified component at the given instance index. + /// + /// Returns an error if the data cannot be deserialized, or if the instance index is out of bounds. + #[inline] + pub fn component_instance_quiet(&self, instance_index: usize) -> Option { + self.components + .get(&C::name()) + .and_then(|unit| unit.component_instance(instance_index)?.ok()) + } + + // --- Mono --- + + /// Returns the raw data for the specified component, assuming a mono-batch. + /// + /// Logs at the specified `log_level` if the underlying batch is not of unit length. + #[inline] + pub fn component_mono_raw_with_log_level( + &self, + log_level: re_log::Level, + component_name: &ComponentName, + ) -> Option> { + self.components.get(component_name).and_then(|unit| { + self.ok_or_log_err( + log_level, + *component_name, + unit.component_mono_raw(component_name)?, + ) + }) + } + + /// Returns the raw data for the specified component, assuming a mono-batch. + /// + /// Returns an error if the underlying batch is not of unit length. + #[inline] + pub fn component_mono_raw( + &self, + component_name: &ComponentName, + ) -> Option> { + self.component_mono_raw_with_log_level(re_log::Level::Error, component_name) + } + + /// Returns the raw data for the specified component, assuming a mono-batch. + /// + /// Returns an error if the underlying batch is not of unit length. + #[inline] + pub fn component_mono_raw_quiet( + &self, + component_name: &ComponentName, + ) -> Option> { + self.components + .get(component_name) + .and_then(|unit| unit.component_mono_raw(component_name)?.ok()) + } + + /// Returns the deserialized data for the specified component, assuming a mono-batch. + /// + /// Logs at the specified `log_level` if the data cannot be deserialized, or if the underlying batch + /// is not of unit length. + #[inline] + pub fn component_mono_with_log_level( + &self, + log_level: re_log::Level, + ) -> Option { + self.components + .get(&C::name()) + .and_then(|unit| self.ok_or_log_err(log_level, C::name(), unit.component_mono()?)) + } + + /// Returns the deserialized data for the specified component, assuming a mono-batch. + /// + /// Returns an error if the data cannot be deserialized, or if the underlying batch is not of unit length. + #[inline] + pub fn component_mono(&self) -> Option { + self.component_mono_with_log_level(re_log::Level::Error) + } + + /// Returns the deserialized data for the specified component, assuming a mono-batch. + /// + /// Returns an error if the data cannot be deserialized, or if the underlying batch is not of unit length. + #[inline] + pub fn component_mono_quiet(&self) -> Option { + self.components + .get(&C::name()) + .and_then(|unit| unit.component_mono()?.ok()) + } + + // --- + + fn ok_or_log_err( + &self, + log_level: re_log::Level, + component_name: ComponentName, + res: re_chunk::ChunkResult, + ) -> Option { + match res { + Ok(data) => Some(data), + + // NOTE: It is expected for UI code to look for OOB instance indices on purpose. + // E.g. it is very common to look at index 0 in blueprint data that has been cleared. + Err(re_chunk::ChunkError::IndexOutOfBounds { len: 0, .. }) => None, + + Err(err) => { + let entity_path = &self.entity_path; + let index = self.compound_index; + let err = re_error::format_ref(&err); + re_log::log_once!( + log_level, + "Couldn't read {entity_path}:{component_name} @ ({index:?}): {err}", + ); + None + } + } + } +} + +// --- Cached implementation --- + +/// Caches the results of `LatestAt` queries for a given [`CacheKey`]. +pub struct LatestAtCache { + /// For debugging purposes. + pub cache_key: CacheKey, + + /// Organized by _query_ time. + /// + /// If the key is present but has a `None` value associated with it, it means we cached the + /// lack of result. + /// This is important to do performance-wise: we run _a lot_ of queries each frame to figure + /// out what to render, and this scales linearly with the number of entity. + pub per_query_time: BTreeMap, + + /// The smallest timestamp that has been invalidated. + /// + /// The next time this cache gets queried, it must remove any invalidated entries accordingly. + /// + /// Invalidation is deferred to query time because it is far more efficient that way: the frame + /// time effectively behaves as a natural micro-batching mechanism. + pub pending_invalidation: Option, +} + +impl LatestAtCache { + #[inline] + pub fn new(cache_key: CacheKey) -> Self { + Self { + cache_key, + per_query_time: Default::default(), + pending_invalidation: Default::default(), + } + } +} + +impl std::fmt::Debug for LatestAtCache { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + cache_key, + per_query_time, + pending_invalidation: _, + } = self; + + let mut strings = Vec::new(); + + for (query_time, unit) in per_query_time { + strings.push(format!( + "query_time={} ({})", + cache_key.timeline.typ().format_utc(*query_time), + re_format::format_bytes(unit.total_size_bytes() as _) + )); + } + + if strings.is_empty() { + return f.write_str(""); + } + + f.write_str(&strings.join("\n").replace("\n\n", "\n")) + } +} + +#[derive(Clone)] +pub struct LatestAtCachedChunk { + pub unit: UnitChunkShared, + + /// Is this just a reference to another entry in the cache? + pub is_reference: bool, +} + +impl SizeBytes for LatestAtCachedChunk { + #[inline] + fn heap_size_bytes(&self) -> u64 { + let Self { + unit: chunk, + is_reference, + } = self; + + if *is_reference { + // This chunk is just a reference to another one in the cache. + // Consider it amortized. + 0 + } else { + Chunk::heap_size_bytes(chunk) + } + } +} + +impl SizeBytes for LatestAtCache { + #[inline] + fn heap_size_bytes(&self) -> u64 { + let Self { + cache_key: _, + per_query_time, + pending_invalidation, + } = self; + + let per_query_time = per_query_time.total_size_bytes(); + let pending_invalidation = pending_invalidation.total_size_bytes(); + + per_query_time + pending_invalidation + } +} + +impl LatestAtCache { + /// Queries cached latest-at data for a single component. + pub fn latest_at( + &mut self, + store: &ChunkStore, + query: &LatestAtQuery, + entity_path: &EntityPath, + component_name: ComponentName, + ) -> Option { + re_tracing::profile_scope!("latest_at", format!("{component_name} @ {query:?}")); + + debug_assert_eq!(query.timeline(), self.cache_key.timeline); + + let Self { + cache_key: _, + per_query_time, + pending_invalidation: _, + } = self; + + if let Some(cached) = per_query_time.get(&query.at()) { + return Some(cached.unit.clone()); + } + + let ((data_time, _row_id), unit) = store + .latest_at_relevant_chunks(query, entity_path, component_name) + .into_iter() + .filter_map(|chunk| { + chunk + .latest_at(query, component_name) + .into_unit() + .and_then(|chunk| chunk.index(&query.timeline()).map(|index| (index, chunk))) + }) + .max_by_key(|(index, _chunk)| *index)?; + + let to_be_cached = if let Some(cached) = per_query_time.get(&data_time) { + // If already cached, just reference that, it's still cheaper than cloning all the + // arrow arrays etc. + LatestAtCachedChunk { + unit: cached.unit.clone(), + is_reference: true, + } + } else { + LatestAtCachedChunk { + unit, + is_reference: false, + } + }; + + per_query_time.insert(query.at(), to_be_cached.clone()); + // Even though we're caching per query-time, we know for a fact that a query at that + // data-time would also yield the same result, i.e. this is the one case where + // data-time == query_time. + per_query_time.insert(data_time, to_be_cached.clone()); + + Some(to_be_cached.unit) + } + + pub fn handle_pending_invalidation(&mut self) { + let Self { + cache_key: _, + per_query_time, + pending_invalidation, + } = self; + + // Remove any data indexed by a _query time_ that's more recent than the oldest + // _data time_ that's been invalidated. + // + // Note that this data time might very well be `TimeInt::STATIC`, in which case the entire + // query-time-based index will be dropped. + if let Some(oldest_data_time) = pending_invalidation.take() { + per_query_time.retain(|&query_time, _| query_time < oldest_data_time); + } + } +} diff --git a/crates/store/re_query2/src/lib.rs b/crates/store/re_query2/src/lib.rs new file mode 100644 index 000000000000..b2d92c611488 --- /dev/null +++ b/crates/store/re_query2/src/lib.rs @@ -0,0 +1,69 @@ +//! Caching datastructures for `re_query2`. + +mod cache; +mod cache_stats; +mod latest_at; + +pub mod clamped_zip; +pub mod range_zip; + +pub use self::cache::{CacheKey, Caches}; +pub use self::cache_stats::{CacheStats, CachesStats}; +pub use self::clamped_zip::*; +pub use self::latest_at::LatestAtResults; +pub use self::range_zip::*; + +pub(crate) use self::latest_at::LatestAtCache; + +pub mod external { + pub use paste; + pub use seq_macro; +} + +// --- + +#[derive(Debug, Clone, Copy)] +pub struct ComponentNotFoundError(pub re_types_core::ComponentName); + +impl std::fmt::Display for ComponentNotFoundError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("Could not find component: {}", self.0)) + } +} + +impl std::error::Error for ComponentNotFoundError {} + +#[derive(thiserror::Error, Debug)] +pub enum QueryError { + #[error("Tried to access a column that doesn't exist")] + BadAccess, + + #[error("Could not find primary component: {0}")] + PrimaryNotFound(re_types_core::ComponentName), + + #[error(transparent)] + ComponentNotFound(#[from] ComponentNotFoundError), + + #[error("Tried to access component of type '{actual:?}' using component '{requested:?}'")] + TypeMismatch { + actual: re_types_core::ComponentName, + requested: re_types_core::ComponentName, + }, + + #[error("Error deserializing: {0}")] + DeserializationError(#[from] re_types_core::DeserializationError), + + #[error("Error serializing: {0}")] + SerializationError(#[from] re_types_core::SerializationError), + + #[error("Error converting arrow data: {0}")] + ArrowError(#[from] arrow2::error::Error), + + #[error("Not implemented")] + NotImplemented, + + #[error(transparent)] + Other(#[from] anyhow::Error), +} + +pub type Result = std::result::Result; diff --git a/crates/store/re_query2/src/range_zip/.gitattributes b/crates/store/re_query2/src/range_zip/.gitattributes new file mode 100644 index 000000000000..30d202506064 --- /dev/null +++ b/crates/store/re_query2/src/range_zip/.gitattributes @@ -0,0 +1 @@ +generated.rs linguist-generated=true diff --git a/crates/store/re_query2/src/range_zip/generated.rs b/crates/store/re_query2/src/range_zip/generated.rs new file mode 100644 index 000000000000..863b48719759 --- /dev/null +++ b/crates/store/re_query2/src/range_zip/generated.rs @@ -0,0 +1,3490 @@ +// This file was generated using `cargo r -p re_query2 --all-features --bin range_zip`. +// DO NOT EDIT. + +// --- + +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + +use std::iter::Peekable; + +/// Returns a new [`RangeZip1x1`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x1( + r0: IR0, + o0: IO0, +) -> RangeZip1x1 +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, +{ + RangeZip1x1 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + + o0_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x1`] for more information. +pub struct RangeZip1x1 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, +{ + r0: IR0, + o0: Peekable, + + o0_data_latest: Option, +} + +impl Iterator for RangeZip1x1 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + O0: Clone, +{ + type Item = (Idx, R0, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o0_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + Some((max_index, r0_data, o0_data)) + } +} + +/// Returns a new [`RangeZip1x2`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x2( + r0: IR0, + o0: IO0, + o1: IO1, +) -> RangeZip1x2 +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, +{ + RangeZip1x2 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x2`] for more information. +pub struct RangeZip1x2 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, +} + +impl Iterator for RangeZip1x2 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + O0: Clone, + O1: Clone, +{ + type Item = (Idx, R0, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o0_data_latest, + o1_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + Some((max_index, r0_data, o0_data, o1_data)) + } +} + +/// Returns a new [`RangeZip1x3`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x3( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, +) -> RangeZip1x3 +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, +{ + RangeZip1x3 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x3`] for more information. +pub struct RangeZip1x3 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, +} + +impl Iterator + for RangeZip1x3 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, +{ + type Item = (Idx, R0, Option, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o0_data_latest, + o1_data_latest, + o2_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + Some((max_index, r0_data, o0_data, o1_data, o2_data)) + } +} + +/// Returns a new [`RangeZip1x4`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x4( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, +) -> RangeZip1x4< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, +{ + RangeZip1x4 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x4`] for more information. +pub struct RangeZip1x4 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, +} + +impl Iterator + for RangeZip1x4 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, +{ + type Item = (Idx, R0, Option, Option, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + Some((max_index, r0_data, o0_data, o1_data, o2_data, o3_data)) + } +} + +/// Returns a new [`RangeZip1x5`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x5( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, +) -> RangeZip1x5< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, +{ + RangeZip1x5 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x5`] for more information. +pub struct RangeZip1x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, +} + +impl Iterator + for RangeZip1x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, + )) + } +} + +/// Returns a new [`RangeZip1x6`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x6( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, +) -> RangeZip1x6< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, +{ + RangeZip1x6 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x6`] for more information. +pub struct RangeZip1x6 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, +} + +impl Iterator + for RangeZip1x6 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o5, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + )) + } +} + +/// Returns a new [`RangeZip1x7`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x7( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, +) -> RangeZip1x7< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, +{ + RangeZip1x7 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x7`] for more information. +pub struct RangeZip1x7 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, +} + +impl Iterator + for RangeZip1x7 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, o6_data, + )) + } +} + +/// Returns a new [`RangeZip1x8`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x8< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, +>( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, + o7: IO7, +) -> RangeZip1x8< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, + IO7::IntoIter, + O7, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, + IO7: IntoIterator, +{ + RangeZip1x8 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + o7_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x8`] for more information. +pub struct RangeZip1x8< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + o7: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, + o7_data_latest: Option, +} + +impl Iterator + for RangeZip1x8< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, + O7: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + o7_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or(o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, o6_data, + o7_data, + )) + } +} + +/// Returns a new [`RangeZip1x9`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_1x9< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, +>( + r0: IR0, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, + o7: IO7, + o8: IO8, +) -> RangeZip1x9< + Idx, + IR0::IntoIter, + R0, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, + IO7::IntoIter, + O7, + IO8::IntoIter, + O8, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, + IO7: IntoIterator, + IO8: IntoIterator, +{ + RangeZip1x9 { + r0: r0.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), + o8: o8.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + o7_data_latest: None, + o8_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_1x9`] for more information. +pub struct RangeZip1x9< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, +{ + r0: IR0, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + o7: Peekable, + o8: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, + o7_data_latest: Option, + o8_data_latest: Option, +} + +impl< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + > Iterator + for RangeZip1x9< + Idx, + IR0, + R0, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, + O7: Clone, + O8: Clone, +{ + type Item = ( + Idx, + R0, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + o7_data_latest, + o8_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + + let max_index = [r0_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or(o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + + let mut o8_data = None; + while let Some((_, data)) = o8.next_if(|(index, _)| index <= &max_index) { + o8_data = Some(data); + } + let o8_data = o8_data.or(o8_data_latest.take()); + o8_data_latest.clone_from(&o8_data); + + Some(( + max_index, r0_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, o6_data, + o7_data, o8_data, + )) + } +} + +/// Returns a new [`RangeZip2x1`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x1( + r0: IR0, + r1: IR1, + o0: IO0, +) -> RangeZip2x1 +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, +{ + RangeZip2x1 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + + o0_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x1`] for more information. +pub struct RangeZip2x1 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + + o0_data_latest: Option, +} + +impl Iterator for RangeZip2x1 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + O0: Clone, +{ + type Item = (Idx, R0, R1, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o0_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + Some((max_index, r0_data, r1_data, o0_data)) + } +} + +/// Returns a new [`RangeZip2x2`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x2( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, +) -> RangeZip2x2 +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, +{ + RangeZip2x2 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x2`] for more information. +pub struct RangeZip2x2 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, +} + +impl Iterator + for RangeZip2x2 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + O0: Clone, + O1: Clone, +{ + type Item = (Idx, R0, R1, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o0_data_latest, + o1_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + Some((max_index, r0_data, r1_data, o0_data, o1_data)) + } +} + +/// Returns a new [`RangeZip2x3`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x3( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, +) -> RangeZip2x3< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, +{ + RangeZip2x3 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x3`] for more information. +pub struct RangeZip2x3 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, +} + +impl Iterator + for RangeZip2x3 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, +{ + type Item = (Idx, R0, R1, Option, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o0_data_latest, + o1_data_latest, + o2_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + Some((max_index, r0_data, r1_data, o0_data, o1_data, o2_data)) + } +} + +/// Returns a new [`RangeZip2x4`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x4( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, +) -> RangeZip2x4< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, +{ + RangeZip2x4 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x4`] for more information. +pub struct RangeZip2x4 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, +} + +impl Iterator + for RangeZip2x4 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, +{ + type Item = (Idx, R0, R1, Option, Option, Option, Option); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, + )) + } +} + +/// Returns a new [`RangeZip2x5`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x5( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, +) -> RangeZip2x5< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, +{ + RangeZip2x5 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x5`] for more information. +pub struct RangeZip2x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, +} + +impl Iterator + for RangeZip2x5 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, + )) + } +} + +/// Returns a new [`RangeZip2x6`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x6( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, +) -> RangeZip2x6< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, +{ + RangeZip2x6 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x6`] for more information. +pub struct RangeZip2x6 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, +} + +impl Iterator + for RangeZip2x6 +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o5, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + )) + } +} + +/// Returns a new [`RangeZip2x7`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, +>( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, +) -> RangeZip2x7< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, +{ + RangeZip2x7 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x7`] for more information. +pub struct RangeZip2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, +} + +impl Iterator + for RangeZip2x7< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + o6_data, + )) + } +} + +/// Returns a new [`RangeZip2x8`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x8< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, +>( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, + o7: IO7, +) -> RangeZip2x8< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, + IO7::IntoIter, + O7, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, + IO7: IntoIterator, +{ + RangeZip2x8 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + o7_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x8`] for more information. +pub struct RangeZip2x8< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + o7: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, + o7_data_latest: Option, +} + +impl< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + > Iterator + for RangeZip2x8< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, + O7: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + o7_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or(o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + o6_data, o7_data, + )) + } +} + +/// Returns a new [`RangeZip2x9`] iterator. +/// +/// The number of elements in a range zip iterator corresponds to the number of elements in the +/// shortest of its required iterators (`r0`, `r1`). +/// +/// Each call to `next` is guaranteed to yield the next value for each required iterator, +/// as well as the most recent index amongst all of them. +/// +/// Optional iterators accumulate their state and yield their most recent value (if any), +/// each time the required iterators fire. +pub fn range_zip_2x9< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, +>( + r0: IR0, + r1: IR1, + o0: IO0, + o1: IO1, + o2: IO2, + o3: IO3, + o4: IO4, + o5: IO5, + o6: IO6, + o7: IO7, + o8: IO8, +) -> RangeZip2x9< + Idx, + IR0::IntoIter, + R0, + IR1::IntoIter, + R1, + IO0::IntoIter, + O0, + IO1::IntoIter, + O1, + IO2::IntoIter, + O2, + IO3::IntoIter, + O3, + IO4::IntoIter, + O4, + IO5::IntoIter, + O5, + IO6::IntoIter, + O6, + IO7::IntoIter, + O7, + IO8::IntoIter, + O8, +> +where + Idx: std::cmp::Ord, + IR0: IntoIterator, + IR1: IntoIterator, + IO0: IntoIterator, + IO1: IntoIterator, + IO2: IntoIterator, + IO3: IntoIterator, + IO4: IntoIterator, + IO5: IntoIterator, + IO6: IntoIterator, + IO7: IntoIterator, + IO8: IntoIterator, +{ + RangeZip2x9 { + r0: r0.into_iter(), + r1: r1.into_iter(), + o0: o0.into_iter().peekable(), + o1: o1.into_iter().peekable(), + o2: o2.into_iter().peekable(), + o3: o3.into_iter().peekable(), + o4: o4.into_iter().peekable(), + o5: o5.into_iter().peekable(), + o6: o6.into_iter().peekable(), + o7: o7.into_iter().peekable(), + o8: o8.into_iter().peekable(), + + o0_data_latest: None, + o1_data_latest: None, + o2_data_latest: None, + o3_data_latest: None, + o4_data_latest: None, + o5_data_latest: None, + o6_data_latest: None, + o7_data_latest: None, + o8_data_latest: None, + } +} + +/// Implements a range zip iterator combinator with 2 required iterators and 2 optional +/// iterators. +/// +/// See [`range_zip_2x9`] for more information. +pub struct RangeZip2x9< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, +> where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, +{ + r0: IR0, + r1: IR1, + o0: Peekable, + o1: Peekable, + o2: Peekable, + o3: Peekable, + o4: Peekable, + o5: Peekable, + o6: Peekable, + o7: Peekable, + o8: Peekable, + + o0_data_latest: Option, + o1_data_latest: Option, + o2_data_latest: Option, + o3_data_latest: Option, + o4_data_latest: Option, + o5_data_latest: Option, + o6_data_latest: Option, + o7_data_latest: Option, + o8_data_latest: Option, +} + +impl< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + > Iterator + for RangeZip2x9< + Idx, + IR0, + R0, + IR1, + R1, + IO0, + O0, + IO1, + O1, + IO2, + O2, + IO3, + O3, + IO4, + O4, + IO5, + O5, + IO6, + O6, + IO7, + O7, + IO8, + O8, + > +where + Idx: std::cmp::Ord, + IR0: Iterator, + IR1: Iterator, + IO0: Iterator, + IO1: Iterator, + IO2: Iterator, + IO3: Iterator, + IO4: Iterator, + IO5: Iterator, + IO6: Iterator, + IO7: Iterator, + IO8: Iterator, + O0: Clone, + O1: Clone, + O2: Clone, + O3: Clone, + O4: Clone, + O5: Clone, + O6: Clone, + O7: Clone, + O8: Clone, +{ + type Item = ( + Idx, + R0, + R1, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + Option, + ); + + #[inline] + fn next(&mut self) -> Option { + let Self { + r0, + r1, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + o8, + o0_data_latest, + o1_data_latest, + o2_data_latest, + o3_data_latest, + o4_data_latest, + o5_data_latest, + o6_data_latest, + o7_data_latest, + o8_data_latest, + } = self; + + let (r0_index, r0_data) = r0.next()?; + let (r1_index, r1_data) = r1.next()?; + + let max_index = [r0_index, r1_index].into_iter().max()?; + + let mut o0_data = None; + while let Some((_, data)) = o0.next_if(|(index, _)| index <= &max_index) { + o0_data = Some(data); + } + let o0_data = o0_data.or(o0_data_latest.take()); + o0_data_latest.clone_from(&o0_data); + + let mut o1_data = None; + while let Some((_, data)) = o1.next_if(|(index, _)| index <= &max_index) { + o1_data = Some(data); + } + let o1_data = o1_data.or(o1_data_latest.take()); + o1_data_latest.clone_from(&o1_data); + + let mut o2_data = None; + while let Some((_, data)) = o2.next_if(|(index, _)| index <= &max_index) { + o2_data = Some(data); + } + let o2_data = o2_data.or(o2_data_latest.take()); + o2_data_latest.clone_from(&o2_data); + + let mut o3_data = None; + while let Some((_, data)) = o3.next_if(|(index, _)| index <= &max_index) { + o3_data = Some(data); + } + let o3_data = o3_data.or(o3_data_latest.take()); + o3_data_latest.clone_from(&o3_data); + + let mut o4_data = None; + while let Some((_, data)) = o4.next_if(|(index, _)| index <= &max_index) { + o4_data = Some(data); + } + let o4_data = o4_data.or(o4_data_latest.take()); + o4_data_latest.clone_from(&o4_data); + + let mut o5_data = None; + while let Some((_, data)) = o5.next_if(|(index, _)| index <= &max_index) { + o5_data = Some(data); + } + let o5_data = o5_data.or(o5_data_latest.take()); + o5_data_latest.clone_from(&o5_data); + + let mut o6_data = None; + while let Some((_, data)) = o6.next_if(|(index, _)| index <= &max_index) { + o6_data = Some(data); + } + let o6_data = o6_data.or(o6_data_latest.take()); + o6_data_latest.clone_from(&o6_data); + + let mut o7_data = None; + while let Some((_, data)) = o7.next_if(|(index, _)| index <= &max_index) { + o7_data = Some(data); + } + let o7_data = o7_data.or(o7_data_latest.take()); + o7_data_latest.clone_from(&o7_data); + + let mut o8_data = None; + while let Some((_, data)) = o8.next_if(|(index, _)| index <= &max_index) { + o8_data = Some(data); + } + let o8_data = o8_data.or(o8_data_latest.take()); + o8_data_latest.clone_from(&o8_data); + + Some(( + max_index, r0_data, r1_data, o0_data, o1_data, o2_data, o3_data, o4_data, o5_data, + o6_data, o7_data, o8_data, + )) + } +} diff --git a/crates/store/re_query2/src/range_zip/mod.rs b/crates/store/re_query2/src/range_zip/mod.rs new file mode 100644 index 000000000000..8bf59965386c --- /dev/null +++ b/crates/store/re_query2/src/range_zip/mod.rs @@ -0,0 +1,70 @@ +mod generated; +pub use self::generated::*; + +#[cfg(test)] +mod tests { + use itertools::Itertools as _; + + use re_chunk::RowId; + use re_log_types::TimeInt; + + use super::*; + + #[test] + fn overview_1x1() { + let t9 = TimeInt::new_temporal(9); + let t10 = TimeInt::new_temporal(10); + let t11 = TimeInt::new_temporal(11); + let t12 = TimeInt::new_temporal(12); + let t13 = TimeInt::new_temporal(13); + let t14 = TimeInt::new_temporal(14); + + let p0: Vec<((TimeInt, RowId), u32)> = vec![ + ((t9, RowId::ZERO), 90), // + // + ((t10, RowId::ZERO), 100), // + // + ((t13, RowId::ZERO.incremented_by(0)), 130), // + ((t13, RowId::ZERO.incremented_by(0)), 130), // + ((t13, RowId::ZERO.incremented_by(0)), 130), // + ((t13, RowId::ZERO.incremented_by(1)), 131), // + ((t13, RowId::ZERO.incremented_by(2)), 132), // + ((t13, RowId::ZERO.incremented_by(5)), 135), // + // + ((t14, RowId::ZERO), 140), // + ]; + + let c0: Vec<((TimeInt, RowId), &'static str)> = vec![ + ((t10, RowId::ZERO.incremented_by(1)), "101"), // + ((t10, RowId::ZERO.incremented_by(2)), "102"), // + ((t10, RowId::ZERO.incremented_by(3)), "103"), // + // + ((t11, RowId::ZERO), "110"), // + // + ((t12, RowId::ZERO), "120"), // + // + ((t13, RowId::ZERO.incremented_by(1)), "131"), // + ((t13, RowId::ZERO.incremented_by(2)), "132"), // + ((t13, RowId::ZERO.incremented_by(4)), "134"), // + ((t13, RowId::ZERO.incremented_by(6)), "136"), // + ]; + + let expected: Vec<((TimeInt, RowId), u32, Option<&'static str>)> = vec![ + ((t9, RowId::ZERO), 90, None), // + // + ((t10, RowId::ZERO), 100, None), // + // + ((t13, RowId::ZERO.incremented_by(0)), 130, Some("120")), // + ((t13, RowId::ZERO.incremented_by(0)), 130, Some("120")), // + ((t13, RowId::ZERO.incremented_by(0)), 130, Some("120")), // + ((t13, RowId::ZERO.incremented_by(1)), 131, Some("131")), // + ((t13, RowId::ZERO.incremented_by(2)), 132, Some("132")), // + ((t13, RowId::ZERO.incremented_by(5)), 135, Some("134")), // + // + ((t14, RowId::ZERO), 140, Some("136")), // + ]; + let got = range_zip_1x1(p0, c0).collect_vec(); + + similar_asserts::assert_eq!(expected, got); + } +} diff --git a/crates/store/re_query2/tests/latest_at.rs b/crates/store/re_query2/tests/latest_at.rs new file mode 100644 index 000000000000..d8112f3c7ca2 --- /dev/null +++ b/crates/store/re_query2/tests/latest_at.rs @@ -0,0 +1,579 @@ +// https://github.com/rust-lang/rust-clippy/issues/10011 +#![cfg(test)] + +use std::sync::Arc; + +use re_chunk::RowId; +use re_chunk_store::{ + external::re_chunk::Chunk, ChunkStore, ChunkStoreSubscriber as _, LatestAtQuery, +}; +use re_log_types::{ + build_frame_nr, + example_components::{MyColor, MyPoint, MyPoints}, + EntityPath, TimeInt, TimePoint, +}; +use re_query2::Caches; +use re_types::{Archetype as _, ComponentBatch}; + +// --- + +#[test] +fn simple_query() { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + let entity_path = "point"; + let timepoint = [build_frame_nr(123)]; + + let row_id1 = RowId::new(); + let points1 = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; + let row_id2 = RowId::new(); + let colors2 = vec![MyColor::from_rgb(255, 0, 0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batch(row_id1, timepoint, &points1) + .with_component_batch(row_id2, timepoint, &colors2) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); + let expected_compound_index = (TimeInt::new_temporal(123), row_id2); + let expected_points = &points1; + let expected_colors = &colors2; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); +} + +#[test] +fn static_query() { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + let entity_path = "point"; + let timepoint = [build_frame_nr(123)]; + + let row_id1 = RowId::new(); + let points = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id1, timepoint, [&points as &dyn ComponentBatch]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let row_id2 = RowId::new(); + let colors = vec![MyColor::from_rgb(255, 0, 0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches( + row_id2, + TimePoint::default(), + [&colors as &dyn ComponentBatch], + ) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); + let expected_compound_index = (TimeInt::new_temporal(123), row_id1); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); +} + +#[test] +fn invalidation() { + let entity_path = "point"; + + let test_invalidation = |query: LatestAtQuery, + present_data_timepoint: TimePoint, + past_data_timepoint: TimePoint, + future_data_timepoint: TimePoint| { + let past_timestamp = past_data_timepoint + .get(&query.timeline()) + .copied() + .unwrap_or(TimeInt::STATIC); + let present_timestamp = present_data_timepoint + .get(&query.timeline()) + .copied() + .unwrap_or(TimeInt::STATIC); + + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + let row_id1 = RowId::new(); + let points = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id1, present_data_timepoint.clone(), [&points as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let row_id2 = RowId::new(); + let colors = vec![MyColor::from_rgb(1, 2, 3)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id2, present_data_timepoint.clone(), [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let expected_compound_index = (present_timestamp, row_id2); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // --- Modify present --- + + // Modify the PoV component + let row_id3 = RowId::new(); + let points = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id3, present_data_timepoint.clone(), [&points as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let expected_compound_index = (present_timestamp, row_id3); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // Modify the optional component + let row_id4 = RowId::new(); + let colors = vec![MyColor::from_rgb(4, 5, 6), MyColor::from_rgb(7, 8, 9)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id4, present_data_timepoint.clone(), [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let expected_compound_index = (present_timestamp, row_id4); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // --- Modify past --- + + // Modify the PoV component + let row_id5 = RowId::new(); + let points_past = vec![MyPoint::new(100.0, 200.0), MyPoint::new(300.0, 400.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id5, past_data_timepoint.clone(), [&points_past as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let expected_compound_index = (present_timestamp, row_id4); + let expected_points = if past_timestamp.is_static() { + &points_past + } else { + &points + }; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // Modify the optional component + let row_id6 = RowId::new(); + let colors_past = vec![MyColor::from_rgb(10, 11, 12), MyColor::from_rgb(13, 14, 15)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id6, past_data_timepoint.clone(), [&colors_past as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let (expected_compound_index, expected_colors) = if past_timestamp.is_static() { + ((past_timestamp, row_id6), &colors_past) + } else { + ((present_timestamp, row_id4), &colors) + }; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // --- Modify future --- + + // Modify the PoV component + let row_id7 = RowId::new(); + let points_future = vec![MyPoint::new(1000.0, 2000.0), MyPoint::new(3000.0, 4000.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches( + row_id7, + future_data_timepoint.clone(), + [&points_future as _], + ) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let (expected_compound_index, expected_points) = if past_timestamp.is_static() { + ((past_timestamp, row_id6), &points_past) + } else { + ((present_timestamp, row_id4), &points) + }; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + // Modify the optional component + let row_id8 = RowId::new(); + let colors_future = vec![MyColor::from_rgb(16, 17, 18)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches( + row_id8, + future_data_timepoint.clone(), + [&colors_future as _], + ) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let (expected_compound_index, expected_colors) = if past_timestamp.is_static() { + ((past_timestamp, row_id6), &colors_past) + } else { + ((present_timestamp, row_id4), &colors) + }; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + }; + + let static_ = TimePoint::default(); + let frame_122 = build_frame_nr(122); + let frame_123 = build_frame_nr(123); + let frame_124 = build_frame_nr(124); + + test_invalidation( + LatestAtQuery::new(frame_123.0, frame_123.1), + [frame_123].into(), + [frame_122].into(), + [frame_124].into(), + ); + + test_invalidation( + LatestAtQuery::new(frame_123.0, frame_123.1), + [frame_123].into(), + static_, + [frame_124].into(), + ); +} + +// Test the following scenario: +// ```py +// rr.log("points", rr.Points3D([1, 2, 3]), static=True) +// +// # Do first query here: LatestAt(+inf) +// # Expected: points=[[1,2,3]] colors=[] +// +// rr.set_time(2) +// rr.log_components("points", rr.components.MyColor(0xFF0000)) +// +// # Do second query here: LatestAt(+inf) +// # Expected: points=[[1,2,3]] colors=[0xFF0000] +// +// rr.set_time(3) +// rr.log_components("points", rr.components.MyColor(0x0000FF)) +// +// # Do third query here: LatestAt(+inf) +// # Expected: points=[[1,2,3]] colors=[0x0000FF] +// +// rr.set_time(3) +// rr.log_components("points", rr.components.MyColor(0x00FF00)) +// +// # Do fourth query here: LatestAt(+inf) +// # Expected: points=[[1,2,3]] colors=[0x00FF00] +// ``` +#[test] +fn invalidation_of_future_optionals() { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + let entity_path = "points"; + + let static_ = TimePoint::default(); + let frame2 = [build_frame_nr(2)]; + let frame3 = [build_frame_nr(3)]; + + let query_time = [build_frame_nr(9999)]; + + let row_id1 = RowId::new(); + let points = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id1, static_, [&points as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::STATIC, row_id1); + let expected_points = &points; + let expected_colors = &[]; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + let row_id2 = RowId::new(); + let colors = vec![MyColor::from_rgb(255, 0, 0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id2, frame2, [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::new_temporal(2), row_id2); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + let row_id3 = RowId::new(); + let colors = vec![MyColor::from_rgb(0, 0, 255)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id3, frame3, [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::new_temporal(3), row_id3); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + let row_id4 = RowId::new(); + let colors = vec![MyColor::from_rgb(0, 255, 0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id4, frame3, [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::new_temporal(3), row_id4); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); +} + +#[test] +fn static_invalidation() { + let mut store = ChunkStore::new( + re_log_types::StoreId::random(re_log_types::StoreKind::Recording), + Default::default(), + ); + let mut caches = Caches::new(&store); + + let entity_path = "points"; + + let timeless = TimePoint::default(); + + let query_time = [build_frame_nr(9999)]; + + let row_id1 = RowId::new(); + let points = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id1, timeless.clone(), [&points as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::STATIC, row_id1); + let expected_points = &points; + let expected_colors = &[]; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + let row_id2 = RowId::new(); + let colors = vec![MyColor::from_rgb(255, 0, 0)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id2, timeless.clone(), [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::STATIC, row_id2); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); + + let row_id3 = RowId::new(); + let colors = vec![MyColor::from_rgb(0, 0, 255)]; + let chunk = Chunk::builder(entity_path.into()) + .with_component_batches(row_id3, timeless.clone(), [&colors as _]) + .build() + .unwrap(); + insert_and_react(&mut store, &mut caches, &Arc::new(chunk)); + + let query = re_chunk_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); + let expected_compound_index = (TimeInt::STATIC, row_id3); + let expected_points = &points; + let expected_colors = &colors; + query_and_compare( + &caches, + &store, + &query, + &entity_path.into(), + expected_compound_index, + expected_points, + expected_colors, + ); +} + +// --- + +fn insert_and_react(store: &mut ChunkStore, caches: &mut Caches, chunk: &Arc) { + caches.on_events(&store.insert_chunk(chunk).unwrap()); +} + +fn query_and_compare( + caches: &Caches, + store: &ChunkStore, + query: &LatestAtQuery, + entity_path: &EntityPath, + expected_compound_index: (TimeInt, RowId), + expected_points: &[MyPoint], + expected_colors: &[MyColor], +) { + re_log::setup_logging(); + + for _ in 0..3 { + let cached = caches.latest_at( + store, + query, + entity_path, + MyPoints::all_components().iter().copied(), + ); + + let cached_points = cached.component_batch::().unwrap(); + let cached_colors = cached.component_batch::().unwrap_or_default(); + + eprintln!("{store}"); + eprintln!("{query:?}"); + // eprintln!("{}", store.to_data_table().unwrap()); + + similar_asserts::assert_eq!(expected_compound_index, cached.compound_index); + similar_asserts::assert_eq!(expected_points, cached_points); + similar_asserts::assert_eq!(expected_colors, cached_colors); + } +}