From d075f51e1510d57ef341d592130d45df51617291 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 16 Apr 2024 15:05:32 +0200 Subject: [PATCH] rm -rf re_query_cache --- Cargo.toml | 1 - crates/re_query_cache/Cargo.toml | 66 - crates/re_query_cache/README.md | 10 - .../re_query_cache/benches/flat_vec_deque.rs | 333 ----- crates/re_query_cache/benches/latest_at.rs | 343 ------ crates/re_query_cache/src/cache.rs | 1085 ----------------- crates/re_query_cache/src/cache_stats.rs | 198 --- crates/re_query_cache/src/flat_vec_deque.rs | 933 -------------- crates/re_query_cache/src/latest_at.rs | 332 ----- crates/re_query_cache/src/lib.rs | 26 - crates/re_query_cache/src/query.rs | 251 ---- crates/re_query_cache/src/range.rs | 334 ----- crates/re_query_cache/tests/latest_at.rs | 524 -------- crates/re_query_cache/tests/range.rs | 659 ---------- 14 files changed, 5095 deletions(-) delete mode 100644 crates/re_query_cache/Cargo.toml delete mode 100644 crates/re_query_cache/README.md delete mode 100644 crates/re_query_cache/benches/flat_vec_deque.rs delete mode 100644 crates/re_query_cache/benches/latest_at.rs delete mode 100644 crates/re_query_cache/src/cache.rs delete mode 100644 crates/re_query_cache/src/cache_stats.rs delete mode 100644 crates/re_query_cache/src/flat_vec_deque.rs delete mode 100644 crates/re_query_cache/src/latest_at.rs delete mode 100644 crates/re_query_cache/src/lib.rs delete mode 100644 crates/re_query_cache/src/query.rs delete mode 100644 crates/re_query_cache/src/range.rs delete mode 100644 crates/re_query_cache/tests/latest_at.rs delete mode 100644 crates/re_query_cache/tests/range.rs diff --git a/Cargo.toml b/Cargo.toml index 5df25ff9ff7d7..4172ba3c7f8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,6 @@ re_log_encoding = { path = "crates/re_log_encoding", version = "=0.16.0-alpha.2" re_log_types = { path = "crates/re_log_types", version = "=0.16.0-alpha.2", default-features = false } re_memory = { path = "crates/re_memory", version = "=0.16.0-alpha.2", default-features = false } re_query = { path = "crates/re_query", version = "=0.16.0-alpha.2", default-features = false } -re_query_cache = { path = "crates/re_query_cache", version = "=0.16.0-alpha.2", default-features = false } re_query_cache2 = { path = "crates/re_query_cache2", version = "=0.16.0-alpha.2", default-features = false } re_query2 = { path = "crates/re_query2", version = "=0.16.0-alpha.2", default-features = false } re_renderer = { path = "crates/re_renderer", version = "=0.16.0-alpha.2", default-features = false } diff --git a/crates/re_query_cache/Cargo.toml b/crates/re_query_cache/Cargo.toml deleted file mode 100644 index be8c2adf62afd..0000000000000 --- a/crates/re_query_cache/Cargo.toml +++ /dev/null @@ -1,66 +0,0 @@ -[package] -name = "re_query_cache" -authors.workspace = true -description = "Caching datastructures for re_query" -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 = [] - -[dependencies] -# Rerun dependencies: -re_data_store.workspace = true -re_format.workspace = true -re_log.workspace = true -re_log_types.workspace = true -re_query.workspace = true -re_tracing.workspace = true -re_types_core.workspace = true - -# External dependencies: -ahash.workspace = true -indent.workspace = true -itertools.workspace = true -parking_lot.workspace = true -paste.workspace = true -seq-macro.workspace = true -static_assertions.workspace = true -web-time.workspace = true - - -[dev-dependencies] -re_types = { workspace = true, features = ["datagen"] } - -criterion.workspace = true -mimalloc.workspace = true -rand = { workspace = true, features = ["std", "std_rng"] } -similar-asserts.workspace = true - - -[lib] -bench = false - - -[[bench]] -name = "flat_vec_deque" -harness = false - - -[[bench]] -name = "latest_at" -harness = false diff --git a/crates/re_query_cache/README.md b/crates/re_query_cache/README.md deleted file mode 100644 index 70613732471d6..0000000000000 --- a/crates/re_query_cache/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# re_query_cache - -Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates. - -[![Latest version](https://img.shields.io/crates/v/re_query_cache.svg)](https://crates.io/crates/re_query_cache) -[![Documentation](https://docs.rs/re_query/badge.svg)](https://docs.rs/re_query) -![MIT](https://img.shields.io/badge/license-MIT-blue.svg) -![Apache](https://img.shields.io/badge/license-Apache-blue.svg) - -Caching datastructures for `re_query`. diff --git a/crates/re_query_cache/benches/flat_vec_deque.rs b/crates/re_query_cache/benches/flat_vec_deque.rs deleted file mode 100644 index d12e10293c0e3..0000000000000 --- a/crates/re_query_cache/benches/flat_vec_deque.rs +++ /dev/null @@ -1,333 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; - -use itertools::Itertools as _; - -use re_query_cache::FlatVecDeque; - -// --- - -#[global_allocator] -static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; - -criterion_group!( - benches, - range, - insert, - insert_many, - insert_deque, - remove, - remove_range -); -criterion_main!(benches); - -// --- - -// `cargo test` also runs the benchmark setup code, so make sure they run quickly: -#[cfg(debug_assertions)] -mod constants { - pub const INITIAL_VALUES_PER_ENTRY: usize = 1; - pub const INITIAL_NUM_ENTRIES: usize = 1; - pub const ADDED_VALUES_PER_ENTRY: usize = 1; - pub const ADDED_NUM_ENTRIES: usize = 1; -} - -#[cfg(not(debug_assertions))] -mod constants { - pub const INITIAL_VALUES_PER_ENTRY: usize = 1000; - pub const INITIAL_NUM_ENTRIES: usize = 100; - pub const ADDED_VALUES_PER_ENTRY: usize = 1000; - pub const ADDED_NUM_ENTRIES: usize = 5; -} - -#[allow(clippy::wildcard_imports)] -use self::constants::*; - -// --- - -fn range(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements( - (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, - )); - - { - group.bench_function("range/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let v: FlatVecDeque = base.clone(); - v.range(0..ADDED_NUM_ENTRIES) - .map(ToOwned::to_owned) - .collect_vec() - }); - }); - group.bench_function("range/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let v: FlatVecDeque = base.clone(); - v.range( - INITIAL_NUM_ENTRIES / 2 - ADDED_NUM_ENTRIES / 2 - ..INITIAL_NUM_ENTRIES / 2 + ADDED_NUM_ENTRIES / 2, - ) - .map(ToOwned::to_owned) - .collect_vec() - }); - }); - group.bench_function("range/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let v: FlatVecDeque = base.clone(); - v.range(INITIAL_NUM_ENTRIES - ADDED_NUM_ENTRIES..INITIAL_NUM_ENTRIES) - .map(ToOwned::to_owned) - .collect_vec() - }); - }); - } -} - -fn insert(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let added = (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec(); - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements(added.len() as _)); - - { - group.bench_function("insert/empty", |b| { - b.iter(|| { - let mut v: FlatVecDeque = FlatVecDeque::new(); - v.insert(0, added.clone()); - v - }); - }); - } - - { - group.bench_function("insert/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert(0, added.clone()); - v - }); - }); - group.bench_function("insert/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert(INITIAL_NUM_ENTRIES / 2, added.clone()); - v - }); - }); - group.bench_function("insert/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert(INITIAL_NUM_ENTRIES, added.clone()); - v - }); - }); - } -} - -fn insert_many(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let added = (0..ADDED_NUM_ENTRIES as i64) - .map(|_| (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()) - .collect_vec(); - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements( - (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, - )); - - { - group.bench_function("insert_many/empty", |b| { - b.iter(|| { - let mut v: FlatVecDeque = FlatVecDeque::new(); - v.insert_many(0, added.clone()); - v - }); - }); - } - - { - group.bench_function("insert_many/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_many(0, added.clone()); - v - }); - }); - group.bench_function("insert_many/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_many(INITIAL_NUM_ENTRIES / 2, added.clone()); - v - }); - }); - group.bench_function("insert_many/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_many(INITIAL_NUM_ENTRIES, added.clone()); - v - }); - }); - } -} - -fn insert_deque(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let mut added: FlatVecDeque = FlatVecDeque::new(); - for i in 0..ADDED_NUM_ENTRIES { - added.insert(i, (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()); - } - - let added = FlatVecDeque::from_vecs( - std::iter::repeat_with(|| (0..ADDED_VALUES_PER_ENTRY as i64).collect_vec()) - .take(ADDED_NUM_ENTRIES), - ); - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements( - (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, - )); - - { - group.bench_function("insert_deque/empty", |b| { - b.iter(|| { - let mut v: FlatVecDeque = FlatVecDeque::new(); - v.insert_deque(0, added.clone()); - v - }); - }); - } - - { - group.bench_function("insert_deque/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_deque(0, added.clone()); - v - }); - }); - group.bench_function("insert_deque/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_deque(INITIAL_NUM_ENTRIES / 2, added.clone()); - v - }); - }); - group.bench_function("insert_deque/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.insert_deque(INITIAL_NUM_ENTRIES, added.clone()); - v - }); - }); - } -} - -fn remove(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements(1)); - - { - group.bench_function("remove/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove(0); - v - }); - }); - group.bench_function("remove/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove(INITIAL_NUM_ENTRIES / 2); - v - }); - }); - group.bench_function("remove/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove(INITIAL_NUM_ENTRIES - 1); - v - }); - }); - } -} - -fn remove_range(c: &mut Criterion) { - if std::env::var("CI").is_ok() { - return; - } - - let mut group = c.benchmark_group("flat_vec_deque"); - group.throughput(criterion::Throughput::Elements( - (ADDED_NUM_ENTRIES * ADDED_VALUES_PER_ENTRY) as _, - )); - - { - group.bench_function("remove_range/prefilled/front", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove_range(0..ADDED_NUM_ENTRIES); - v - }); - }); - group.bench_function("remove_range/prefilled/middle", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove_range( - INITIAL_NUM_ENTRIES / 2 - ADDED_NUM_ENTRIES / 2 - ..INITIAL_NUM_ENTRIES / 2 + ADDED_NUM_ENTRIES / 2, - ); - v - }); - }); - group.bench_function("remove_range/prefilled/back", |b| { - let base = create_prefilled(); - b.iter(|| { - let mut v: FlatVecDeque = base.clone(); - v.remove_range(INITIAL_NUM_ENTRIES - ADDED_NUM_ENTRIES..INITIAL_NUM_ENTRIES); - v - }); - }); - } -} - -// --- - -fn create_prefilled() -> FlatVecDeque { - FlatVecDeque::from_vecs( - std::iter::repeat_with(|| (0..INITIAL_VALUES_PER_ENTRY as i64).collect_vec()) - .take(INITIAL_NUM_ENTRIES), - ) -} diff --git a/crates/re_query_cache/benches/latest_at.rs b/crates/re_query_cache/benches/latest_at.rs deleted file mode 100644 index e74a7e0a20df3..0000000000000 --- a/crates/re_query_cache/benches/latest_at.rs +++ /dev/null @@ -1,343 +0,0 @@ -//! Contains: -//! - A 1:1 port of the benchmarks in `crates/re_query/benches/query_benchmarks.rs`, with caching enabled. - -use criterion::{criterion_group, criterion_main, Criterion}; - -use itertools::Itertools; -use re_data_store::{DataStore, LatestAtQuery, StoreSubscriber}; -use re_log_types::{entity_path, DataRow, EntityPath, RowId, TimeInt, TimeType, Timeline}; -use re_query_cache::Caches; -use re_types::{ - archetypes::Points2D, - components::{Color, InstanceKey, Position2D, Text}, -}; -use re_types_core::Loggable 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_rows(&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_rows(msgs.iter())); - }); - } - - { - let mut group = c.benchmark_group("arrow_mono_points2"); - group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); - let (caches, store) = insert_rows(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_rows(&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_rows(msgs.iter())); - }); - } - - { - let mut group = c.benchmark_group("arrow_mono_strings2"); - group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); - let (caches, store) = insert_rows(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_rows(&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_rows(msgs.iter())); - }); - } - - { - let mut group = c.benchmark_group("arrow_batch_points2"); - group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); - let (caches, store) = insert_rows(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_rows(&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_rows(msgs.iter())); - }); - } - - { - let mut group = c.benchmark_group("arrow_batch_strings2"); - group.throughput(criterion::Throughput::Elements(NUM_POINTS as _)); - let (caches, store) = insert_rows(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..10000); - let s: String = rand::thread_rng() - .sample_iter(&rand::distributions::Alphanumeric) - .take(ilen) - .map(char::from) - .collect(); - Text::from(s) - }) - .collect() -} - -fn build_points_rows(paths: &[EntityPath], num_points: usize) -> Vec { - (0..NUM_FRAMES_POINTS) - .flat_map(move |frame_idx| { - paths.iter().map(move |path| { - let mut row = DataRow::from_cells2( - RowId::new(), - path.clone(), - [build_frame_nr((frame_idx as i64).try_into().unwrap())], - num_points as _, - ( - build_some_point2d(num_points), - build_some_colors(num_points), - ), - ) - .unwrap(); - // NOTE: Using unsized cells will crash in debug mode, and benchmarks are run for 1 iteration, - // in debug mode, by the standard test harness. - if cfg!(debug_assertions) { - row.compute_all_size_bytes(); - } - row - }) - }) - .collect() -} - -fn build_strings_rows(paths: &[EntityPath], num_strings: usize) -> Vec { - (0..NUM_FRAMES_STRINGS) - .flat_map(move |frame_idx| { - paths.iter().map(move |path| { - let mut row = DataRow::from_cells2( - RowId::new(), - path.clone(), - [build_frame_nr((frame_idx as i64).try_into().unwrap())], - num_strings as _, - // 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), - build_some_strings(num_strings), - ), - ) - .unwrap(); - // NOTE: Using unsized cells will crash in debug mode, and benchmarks are run for 1 iteration, - // in debug mode, by the standard test harness. - if cfg!(debug_assertions) { - row.compute_all_size_bytes(); - } - row - }) - }) - .collect() -} - -fn insert_rows<'a>(msgs: impl Iterator) -> (Caches, DataStore) { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - msgs.for_each(|row| { - caches.on_events(&[store.insert_row(row).unwrap()]); - }); - - (caches, store) -} - -struct SavePoint { - _pos: Position2D, - _color: Option, -} - -fn query_and_visit_points( - caches: &Caches, - store: &DataStore, - 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 points = Vec::with_capacity(NUM_POINTS as _); - - // TODO(jleibs): Add Radius once we have support for it in field_types - for path in paths { - caches - .query_archetype_pov1_comp1::( - store, - &query.clone().into(), - path, - |(_, _, positions, colors)| { - itertools::izip!(positions.iter(), colors.unwrap().iter()).for_each( - |(pos, color)| { - points.push(SavePoint { - _pos: *pos, - _color: *color, - }); - }, - ); - }, - ) - .unwrap(); - } - assert_eq!(NUM_POINTS as usize, points.len()); - points -} - -struct SaveString { - _label: Option, -} - -fn query_and_visit_strings( - caches: &Caches, - store: &DataStore, - 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 path in paths { - caches - .query_archetype_pov1_comp1::( - store, - &query.clone().into(), - path, - |(_, _, _, labels)| { - for label in labels.unwrap() { - strings.push(SaveString { - _label: label.clone(), - }); - } - }, - ) - .unwrap(); - } - assert_eq!(NUM_STRINGS as usize, strings.len()); - - criterion::black_box(strings) -} diff --git a/crates/re_query_cache/src/cache.rs b/crates/re_query_cache/src/cache.rs deleted file mode 100644 index 6b03a1b2fe5eb..0000000000000 --- a/crates/re_query_cache/src/cache.rs +++ /dev/null @@ -1,1085 +0,0 @@ -use std::{ - collections::{BTreeMap, VecDeque}, - ops::Range, - sync::Arc, -}; - -use ahash::{HashMap, HashSet}; -use itertools::Itertools; -use parking_lot::RwLock; -use paste::paste; -use seq_macro::seq; - -use re_data_store::{DataStore, LatestAtQuery, RangeQuery, StoreDiff, StoreEvent, StoreSubscriber}; -use re_log_types::{EntityPath, RowId, StoreId, TimeInt, TimeRange, Timeline}; -use re_query::ArchetypeView; -use re_types_core::{ - components::InstanceKey, Archetype, ArchetypeName, Component, ComponentName, SizeBytes as _, -}; - -use crate::{ErasedFlatVecDeque, FlatVecDeque, LatestAtCache, RangeCache}; - -// --- - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum AnyQuery { - LatestAt(LatestAtQuery), - Range(RangeQuery), -} - -impl From for AnyQuery { - #[inline] - fn from(query: LatestAtQuery) -> Self { - Self::LatestAt(query) - } -} - -impl From for AnyQuery { - #[inline] - fn from(query: RangeQuery) -> Self { - Self::Range(query) - } -} - -// --- - -/// Maintains the top-level cache mappings. -pub struct Caches { - /// The [`StoreId`] of the associated [`DataStore`]. - store_id: StoreId, - - // NOTE: `Arc` so we can cheaply free the top-level lock early when needed. - 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, - per_cache_key, - } = self; - - let mut strings = Vec::new(); - - strings.push(format!("[Caches({store_id})]")); - - let per_cache_key = per_cache_key.read(); - let per_cache_key: BTreeMap<_, _> = per_cache_key.iter().collect(); - - for (cache_key, caches_per_archetype) in &per_cache_key { - let caches_per_archetype = caches_per_archetype.read(); - strings.push(format!( - " [{cache_key:?} (pending_temporal={:?} pending_timeless={:?})]", - caches_per_archetype - .pending_temporal_invalidation - .map(|t| cache_key - .timeline - .format_time_range_utc(&TimeRange::new(t, TimeInt::MAX))), - caches_per_archetype.pending_static_invalidation, - )); - strings.push(indent::indent_all_by( - 4, - format!("{caches_per_archetype:?}"), - )); - } - - f.write_str(&strings.join("\n").replace("\n\n", "\n")) - } -} - -impl std::ops::Deref for Caches { - type Target = RwLock>>>; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.per_cache_key - } -} - -impl Caches { - #[inline] - pub fn new(store: &DataStore) -> Self { - Self { - store_id: store.id().clone(), - per_cache_key: Default::default(), - } - } -} - -#[derive(Default)] -pub struct CachesPerArchetype { - /// Which [`Archetype`] are we querying for? - /// - /// This is very important because of our data model: we not only query for components, but we - /// query for components from a specific point-of-view (the so-called primary component). - /// Different archetypes have different point-of-views, and therefore can end up with different - /// results, even from the same raw data. - // - // NOTE: `Arc` so we can cheaply free the archetype-level lock early when needed. - // - // TODO(cmc): At some point we should probably just store the PoV and optional components rather - // than an `ArchetypeName`: the query system doesn't care about archetypes. - pub(crate) latest_at_per_archetype: RwLock>>>, - - /// Which [`Archetype`] are we querying for? - /// - /// This is very important because of our data model: we not only query for components, but we - /// query for components from a specific point-of-view (the so-called primary component). - /// Different archetypes have different point-of-views, and therefore can end up with different - /// results, even from the same raw data. - // - // NOTE: `Arc` so we can cheaply free the archetype-level lock early when needed. - // - // TODO(cmc): At some point we should probably just store the PoV and optional components rather - // than an `ArchetypeName`: the query system doesn't care about archetypes. - pub(crate) range_per_archetype: RwLock>>>, - - /// Everything greater than or equal to this timestamp has been asynchronously invalidated. - /// - /// The next time this cache gets queried, it must remove any entry matching this criteria. - /// `None` indicates that there's no pending invalidation. - /// - /// 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. - pending_temporal_invalidation: Option, - - /// If `true`, the timeless data associated with this cache has been asynchronously invalidated. - /// - /// If `true`, this cache must remove all of its timeless entries the next time it gets queried. - /// `false` indicates that there's no pending invalidation. - /// - /// 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. - pending_static_invalidation: bool, -} - -impl std::fmt::Debug for CachesPerArchetype { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let CachesPerArchetype { - latest_at_per_archetype, - range_per_archetype, - pending_temporal_invalidation: _, - pending_static_invalidation: _, - } = self; - - let mut strings = Vec::new(); - - { - let latest_at_per_archetype = latest_at_per_archetype.read(); - let latest_at_per_archetype: BTreeMap<_, _> = latest_at_per_archetype.iter().collect(); - - for (archetype_name, latest_at_cache) in &latest_at_per_archetype { - let latest_at_cache = latest_at_cache.read(); - strings.push(format!( - "[latest_at for {archetype_name} ({})]", - re_format::format_bytes(latest_at_cache.total_size_bytes() as _) - )); - strings.push(indent::indent_all_by(2, format!("{latest_at_cache:?}"))); - } - } - - { - let range_per_archetype = range_per_archetype.read(); - let range_per_archetype: BTreeMap<_, _> = range_per_archetype.iter().collect(); - - for (archetype_name, range_cache) in &range_per_archetype { - let range_cache = range_cache.read(); - strings.push(format!( - "[range for {archetype_name} ({})]", - re_format::format_bytes(range_cache.total_size_bytes() as _) - )); - strings.push(indent::indent_all_by(2, format!("{range_cache:?}"))); - } - } - - f.write_str(&strings.join("\n").replace("\n\n", "\n")) - } -} - -impl Caches { - /// Clears all caches. - #[inline] - pub fn clear(&self) { - self.write().clear(); - } - - /// Gives access to the appropriate `LatestAtCache` according to the specified - /// query parameters. - /// - /// `upsert` is a user-defined callback that will be run first, with full mutable access to the cache. - /// `iter` is a user-defined callback that will be run last, with shared access. - /// - /// These callback semantics allow for reentrancy: you can use the same cache from multiple - /// query contexts (i.e. space views), even in a work-stealing environment. - #[inline] - pub fn with_latest_at( - &self, - store: &DataStore, - entity_path: EntityPath, - query: &LatestAtQuery, - mut upsert: F1, - mut iter: F2, - ) -> (Option, R2) - where - A: Archetype, - F1: FnMut(&mut LatestAtCache) -> R1, - F2: FnMut(&LatestAtCache) -> R2, - { - assert!( - self.store_id == *store.id(), - "attempted to use a query cache {} with the wrong datastore ({})", - self.store_id, - store.id(), - ); - - let key = CacheKey::new(entity_path, query.timeline()); - - let cache = { - let caches_per_archetype = Arc::clone(self.write().entry(key.clone()).or_default()); - // Implicitly releasing top-level cache mappings -- concurrent queries can run once again. - - let removed_bytes = caches_per_archetype.write().handle_pending_invalidation(); - // Implicitly releasing archetype-level cache mappings -- concurrent queries using the - // same `CacheKey` but a different `ArchetypeName` can run once again. - if removed_bytes > 0 { - re_log::trace!( - store_id=%self.store_id, - entity_path = %key.entity_path, - removed = removed_bytes, - "invalidated latest-at caches" - ); - } - - let caches_per_archetype = caches_per_archetype.read(); - let mut latest_at_per_archetype = caches_per_archetype.latest_at_per_archetype.write(); - Arc::clone(latest_at_per_archetype.entry(A::name()).or_default()) - // Implicitly releasing bottom-level cache mappings -- identical concurrent queries - // can run once again. - }; - - // # Multithreading semantics - // - // There is only one situation where this `try_write()` might fail: there is another task that - // is already in the process of upserting that specific cache (e.g. a cloned space view). - // - // That task might be on the same thread (due to work-stealing), or a different one. - // Either way, we need to give up trying to upsert the cache in order to prevent a - // deadlock in case the other task is in fact running on the same thread. - // - // It's fine, though: - // - Best case scenario, the data we need is already cached. - // - Worst case scenario, the data is missing and we'll be missing some data for the current - // frame. - // It'll get cached at some point in an upcoming frame (statistically, we're bound to win - // the race at some point). - // - // Data invalidation happens at the per-archetype cache layer, so this won't return - // out-of-date data in either scenario. - // - // There is a lot of complexity we could add to make this whole process more efficient: - // keep track of failed queries in a queue so we don't rely on probabilities, keep track - // of the thread-local reentrancy state to skip this logic when it's not needed, return raw - // data when the lock is busy and the data isn't already cached, etc. - // - // In the end, this is a edge-case inherent to our current "immediate query" model that we - // already know we want -- and have to -- move away from; the extra complexity isn't worth it. - let r1 = cache.try_write().map(|mut cache| upsert(&mut cache)); - // Implicitly releasing the write lock -- if any. - - // # Multithreading semantics - // - // We need the reentrant lock because query contexts (i.e. space views) generally run on a - // work-stealing thread-pool and might swap a task on one thread with another task on the - // same thread, where both tasks happen to query the same exact data (e.g. cloned space views). - // - // See comment above for more details. - let r2 = iter(&cache.read_recursive()); - - (r1, r2) - } - - /// Gives access to the appropriate `RangeCache` according to the specified query parameters. - /// - /// `upsert` is a user-defined callback that will be run first, with full mutable access to the cache. - /// `iter` is a user-defined callback that will be run last, with shared access. - /// - /// These callback semantics allow for reentrancy: you can use the same cache from multiple - /// query contexts (i.e. space views), even in a work-stealing environment. - #[inline] - pub fn with_range( - &self, - store: &DataStore, - entity_path: EntityPath, - query: &RangeQuery, - mut upsert: F1, - mut iter: F2, - ) -> (Option, R2) - where - A: Archetype, - F1: FnMut(&mut RangeCache) -> R1, - F2: FnMut(&RangeCache) -> R2, - { - assert!( - self.store_id == *store.id(), - "attempted to use a query cache {} with the wrong datastore ({})", - self.store_id, - store.id(), - ); - - let key = CacheKey::new(entity_path, query.timeline); - - let cache = { - let caches_per_archetype = Arc::clone(self.write().entry(key.clone()).or_default()); - // Implicitly releasing top-level cache mappings -- concurrent queries can run once again. - - let removed_bytes = caches_per_archetype.write().handle_pending_invalidation(); - // Implicitly releasing archetype-level cache mappings -- concurrent queries using the - // same `CacheKey` but a different `ArchetypeName` can run once again. - if removed_bytes > 0 { - re_log::trace!( - store_id=%self.store_id, - entity_path = %key.entity_path, - removed = removed_bytes, - "invalidated range caches" - ); - } - - let caches_per_archetype = caches_per_archetype.read(); - let mut range_per_archetype = caches_per_archetype.range_per_archetype.write(); - Arc::clone(range_per_archetype.entry(A::name()).or_default()) - // Implicitly releasing bottom-level cache mappings -- identical concurrent queries - // can run once again. - }; - - // # Multithreading semantics - // - // There is only one situation where this `try_write()` might fail: there is another task that - // is already in the process of upserting that specific cache (e.g. a cloned space view). - // - // That task might be on the same thread (due to work-stealing), or a different one. - // Either way, we need to give up trying to upsert the cache in order to prevent a - // deadlock in case the other task is in fact running on the same thread. - // - // It's fine, though: - // - Best case scenario, the data we need is already cached. - // - Worst case scenario, the data is missing and we'll be missing some data for the current - // frame. - // It'll get cached at some point in an upcoming frame (statistically, we're bound to win - // the race at some point). - // - // Data invalidation happens at the per-archetype cache layer, so this won't return - // out-of-date data in either scenario. - // - // There is a lot of complexity we could add to make this whole process more efficient: - // keep track of failed queries in a queue so we don't rely on probabilities, keep track - // of the thread-local reentrancy state to skip this logic when it's not needed, keep track - // of min-max timestamp values per entity so we can clamp range queries and thus know - // whether the data is already cached or not, etc. - // - // In the end, this is a edge-case inherent to our current "immediate query" model that we - // already know we want -- and have to -- move away from; the extra complexity isn't worth it. - let r1 = cache.try_write().map(|mut cache| upsert(&mut cache)); - // Implicitly releasing the write lock -- if any. - - // # Multithreading semantics - // - // We need the reentrant lock because query contexts (i.e. space views) generally run on a - // work-stealing thread-pool and might swap a task on one thread with another task on the - // same thread, where both tasks happen to query the same exact data (e.g. cloned space views). - // - // See comment above for more details. - let r2 = iter(&cache.read_recursive()); - - (r1, r2) - } -} - -/// Uniquely identifies cached query results in the [`Caches`]. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CacheKey { - /// Which [`EntityPath`] is the query targeting? - pub entity_path: EntityPath, - - /// Which [`Timeline`] is the query targeting? - pub timeline: Timeline, -} - -impl std::fmt::Debug for CacheKey { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - entity_path, - timeline, - } = self; - f.write_fmt(format_args!("{entity_path} on {}", timeline.name())) - } -} - -impl CacheKey { - #[inline] - pub fn new(entity_path: impl Into, timeline: impl Into) -> Self { - Self { - entity_path: entity_path.into(), - timeline: timeline.into(), - } - } -} - -// --- Invalidation --- - -impl StoreSubscriber 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: &[StoreEvent]) { - re_tracing::profile_function!(format!("num_events={}", events.len())); - - for event in events { - let StoreEvent { - 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 StoreDiff { - kind: _, // Don't care: both additions and deletions invalidate query results. - row_id: _, - times, - entity_path, - cells: _, // Don't care: we invalidate at the entity level, not component level. - } = diff; - - #[derive(Default, Debug)] - struct CompactedEvents { - timeless: HashSet, - temporal: HashMap, - } - - let mut compacted = CompactedEvents::default(); - { - re_tracing::profile_scope!("compact events"); - - if times.is_empty() { - compacted.timeless.insert(entity_path.clone()); - } - - for &(timeline, time) in times { - let key = CacheKey::new(entity_path.clone(), timeline); - let min_time = compacted.temporal.entry(key).or_insert(TimeInt::MAX); - *min_time = TimeInt::min(*min_time, time); - } - } - - let caches = self.write(); - // NOTE: Don't release the top-level lock -- 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. - - // 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. - { - re_tracing::profile_scope!("timeless"); - - for entity_path in compacted.timeless { - for (key, caches_per_archetype) in caches.iter() { - if key.entity_path == entity_path { - caches_per_archetype.write().pending_static_invalidation = true; - } - } - } - } - - { - re_tracing::profile_scope!("temporal"); - - for (key, time) in compacted.temporal { - if let Some(caches_per_archetype) = caches.get(&key) { - // NOTE: Do _NOT_ lock from within the if clause itself or the guard will live - // for the remainder of the if statement and hell will ensue. - // is - // supposed to catch that but it doesn't, I don't know why. - let mut caches_per_archetype = caches_per_archetype.write(); - if let Some(min_time) = - caches_per_archetype.pending_temporal_invalidation.as_mut() - { - *min_time = TimeInt::min(*min_time, time); - } else { - caches_per_archetype.pending_temporal_invalidation = Some(time); - } - } - } - } - } - } -} - -impl CachesPerArchetype { - /// Removes all entries from the cache that have been asynchronously invalidated. - /// - /// 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. - /// - /// Returns the number of bytes removed. - fn handle_pending_invalidation(&mut self) -> u64 { - let pending_static_invalidation = self.pending_static_invalidation; - let pending_temporal_invalidation = self.pending_temporal_invalidation.is_some(); - - if !pending_static_invalidation && !pending_temporal_invalidation { - return 0; - } - - re_tracing::profile_function!(); - - let time_threshold = self.pending_temporal_invalidation.unwrap_or(TimeInt::MAX); - - self.pending_temporal_invalidation = None; - self.pending_static_invalidation = false; - - // Timeless being infinitely into the past, this effectively invalidates _everything_ with - // the current coarse-grained / archetype-level caching strategy. - if pending_static_invalidation { - re_tracing::profile_scope!("static"); - - let latest_at_removed_bytes = self - .latest_at_per_archetype - .read() - .values() - .map(|latest_at_cache| latest_at_cache.read().total_size_bytes()) - .sum::(); - let range_removed_bytes = self - .range_per_archetype - .read() - .values() - .map(|range_cache| range_cache.read().total_size_bytes()) - .sum::(); - - *self = CachesPerArchetype::default(); - - return latest_at_removed_bytes + range_removed_bytes; - } - - re_tracing::profile_scope!("temporal"); - - let mut removed_bytes = 0u64; - - for latest_at_cache in self.latest_at_per_archetype.read().values() { - let mut latest_at_cache = latest_at_cache.write(); - removed_bytes = - removed_bytes.saturating_add(latest_at_cache.truncate_at_time(time_threshold)); - } - - for range_cache in self.range_per_archetype.read().values() { - let mut range_cache = range_cache.write(); - removed_bytes = - removed_bytes.saturating_add(range_cache.truncate_at_time(time_threshold)); - } - - removed_bytes - } -} - -// --- - -/// Caches the results of any query for an arbitrary range of time. -/// -/// This caches all the steps involved in getting data ready for space views: -/// - index search, -/// - instance key joining, -/// - deserialization. -/// -/// We share the `CacheBucket` implementation between all types of queries to avoid duplication of -/// logic, especially for things that require metaprogramming, to keep the macro madness to a -/// minimum. -/// In the case of `LatestAt` queries, a `CacheBucket` will always contain a single timestamp worth -/// of data. -#[derive(Default)] -pub struct CacheBucket { - /// The _data_ timestamps and [`RowId`]s of all cached rows. - /// - /// This corresponds to the data time and `RowId` returned by `re_query::query_archetype`. - /// - /// This is guaranteed to always be sorted and dense (i.e. there cannot be a hole in the cached - /// data, unless the raw data itself in the store has a hole at that particular point in time). - /// - /// Reminder: within a single timestamp, rows are sorted according to their [`RowId`]s. - /// - /// Invariant: `data_times.len() == pov_instance_keys.num_entries()` - pub(crate) data_times: VecDeque<(TimeInt, RowId)>, - - /// The [`InstanceKey`]s of the point-of-view components. - /// - /// Invariant: `data_times.len() == pov_instance_keys.num_entries()` - pub(crate) pov_instance_keys: FlatVecDeque, - - /// The resulting component data, pre-deserialized, pre-joined. - /// - /// All the contained FlatVecDeques have the same length as `data_times`. - // - // TODO(#4733): Don't denormalize auto-generated instance keys. - // TODO(#4734): Don't denormalize splatted values. - pub(crate) components: BTreeMap>, - - /// The total size in bytes stored in this bucket. - /// - /// Only used so we can decrement the global cache size when the last reference to a bucket - /// gets dropped. - pub(crate) total_size_bytes: u64, - // - // TODO(cmc): secondary cache -} - -impl std::fmt::Debug for CacheBucket { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - data_times: _, - pov_instance_keys: _, - components, - total_size_bytes: _, - } = self; - - let strings = components - .iter() - .filter(|(_, data)| data.dyn_num_values() > 0) - .map(|(name, data)| { - format!( - "{} {name} values spread across {} entries ({})", - data.dyn_num_values(), - data.dyn_num_entries(), - re_format::format_bytes(data.dyn_total_size_bytes() as _), - ) - }) - .collect_vec(); - - f.write_str(&strings.join("\n").replace("\n\n", "\n")) - } -} - -impl CacheBucket { - // Check invariants in debug builds - fn sanity_check(&self) { - if cfg!(debug_assertions) { - assert_eq!(self.data_times.len(), self.pov_instance_keys.num_entries()); - let n = self.data_times.len(); - for (name, data) in &self.components { - assert_eq!(data.dyn_num_entries(), n, "{name}"); - } - } - } - - #[inline] - pub fn time_range(&self) -> Option { - let first_time = self.data_times.front().map(|(t, _)| *t)?; - let last_time = self.data_times.back().map(|(t, _)| *t)?; - Some(TimeRange::new(first_time, last_time)) - } - - #[inline] - pub fn contains_data_time(&self, data_time: TimeInt) -> bool { - let first_time = self.data_times.front().map_or(&TimeInt::MAX, |(t, _)| t); - let last_time = self.data_times.back().map_or(&TimeInt::MIN, |(t, _)| t); - *first_time <= data_time && data_time <= *last_time - } - - #[inline] - pub fn contains_data_row(&self, data_time: TimeInt, row_id: RowId) -> bool { - self.data_times.binary_search(&(data_time, row_id)).is_ok() - } - - /// How many timestamps' worth of data is stored in this bucket? - #[inline] - pub fn num_entries(&self) -> usize { - self.data_times.len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.num_entries() == 0 - } - - // --- - - /// Iterate over the timestamps of the point-of-view components. - #[inline] - pub fn iter_data_times(&self) -> impl Iterator { - self.data_times.iter() - } - - /// Iterate over the [`InstanceKey`] batches of the point-of-view components. - #[inline] - pub fn iter_pov_instance_keys(&self) -> impl Iterator { - self.pov_instance_keys.iter() - } - - /// Iterate over the batches of the specified non-optional component. - #[inline] - pub fn iter_component(&self) -> Option> { - let data = self - .components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>())?; - Some(data.iter()) - } - - /// Iterate over the batches of the specified optional component. - #[inline] - pub fn iter_component_opt(&self) -> Option]>> { - let data = self - .components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>>())?; - Some(data.iter()) - } - - // --- - - /// Returns the index range that corresponds to the static data (if any). - /// - /// Use the returned range with one of the range iteration methods: - /// - [`Self::range_data_times`] - /// - [`Self::range_pov_instance_keys`] - /// - [`Self::range_component`] - /// - [`Self::range_component_opt`] - /// - /// Make sure that the bucket hasn't been modified in-between! - /// - /// This is `O(2*log(n))`, so make sure to clone the returned range rather than calling this - /// multiple times. - #[inline] - pub fn static_range(&self) -> Range { - static_assertions::const_assert_eq!(TimeInt::STATIC.as_i64(), i64::MIN); - let start_index = 0; - let end_index = self - .data_times - .partition_point(|(data_time, _)| data_time <= &TimeInt::STATIC); - start_index..end_index - } - - /// Returns the index range that corresponds to the specified `time_range`. - /// - /// Use the returned range with one of the range iteration methods: - /// - [`Self::range_data_times`] - /// - [`Self::range_pov_instance_keys`] - /// - [`Self::range_component`] - /// - [`Self::range_component_opt`] - /// - /// Make sure that the bucket hasn't been modified in-between! - /// - /// This is `O(2*log(n))`, so make sure to clone the returned range rather than calling this - /// multiple times. - #[inline] - pub fn entry_range(&self, time_range: TimeRange) -> Range { - let start_index = self - .data_times - .partition_point(|(data_time, _)| data_time < &time_range.min()); - let end_index = self - .data_times - .partition_point(|(data_time, _)| data_time <= &time_range.max()); - start_index..end_index - } - - /// Range over the timestamps of the point-of-view components. - #[inline] - pub fn range_data_times( - &self, - entry_range: Range, - ) -> impl Iterator { - self.data_times.range(entry_range) - } - - /// Range over the [`InstanceKey`] batches of the point-of-view components. - #[inline] - pub fn range_pov_instance_keys( - &self, - entry_range: Range, - ) -> impl Iterator { - self.pov_instance_keys.range(entry_range) - } - - /// Get the raw batches for the specified non-optional component. - #[inline] - pub fn component(&self) -> Option<&FlatVecDeque> { - self.components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>()) - } - - /// Range over the batches of the specified non-optional component. - #[inline] - pub fn range_component( - &self, - entry_range: Range, - ) -> Option> { - let data = self - .components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>())?; - Some(data.range(entry_range)) - } - - /// Get the raw batches for the specified optional component. - #[inline] - pub fn component_opt(&self) -> Option<&FlatVecDeque>> { - self.components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>>()) - } - - /// Range over the batches of the specified optional component. - #[inline] - pub fn range_component_opt( - &self, - entry_range: Range, - ) -> Option]>> { - let data = self - .components - .get(&C::name()) - .and_then(|data| data.as_any().downcast_ref::>>())?; - Some(data.range(entry_range)) - } - - /// Removes everything from the bucket that corresponds to a time equal or greater than the - /// specified `threshold`. - /// - /// Returns the number of bytes removed. - #[inline] - pub fn truncate_at_time(&mut self, threshold: TimeInt) -> u64 { - self.sanity_check(); - - let Self { - data_times, - pov_instance_keys, - components, - total_size_bytes, - } = self; - - let mut removed_bytes = 0u64; - - let threshold_idx = data_times.partition_point(|(data_time, _)| data_time < &threshold); - - { - let total_size_bytes_before = data_times.total_size_bytes(); - data_times.truncate(threshold_idx); - removed_bytes += total_size_bytes_before - data_times.total_size_bytes(); - } - - { - let total_size_bytes_before = pov_instance_keys.total_size_bytes(); - pov_instance_keys.truncate(threshold_idx); - removed_bytes += total_size_bytes_before - pov_instance_keys.total_size_bytes(); - } - - for data in components.values_mut() { - let total_size_bytes_before = data.dyn_total_size_bytes(); - data.dyn_truncate(threshold_idx); - removed_bytes += total_size_bytes_before - data.dyn_total_size_bytes(); - } - - *total_size_bytes = total_size_bytes - .checked_sub(removed_bytes) - .unwrap_or_else(|| { - re_log::debug!( - current = *total_size_bytes, - removed = removed_bytes, - "book keeping underflowed" - ); - u64::MIN - }); - - self.sanity_check(); - - removed_bytes - } -} - -macro_rules! impl_insert { - (for N=$N:expr, M=$M:expr => povs=[$($pov:ident)+] comps=[$($comp:ident)*]) => { paste! { - #[doc = "Inserts the contents of the given [`ArchetypeView`], which are made of the specified"] - #[doc = "`" $N "` point-of-view components and `" $M "` optional components, to the cache."] - #[doc = ""] - #[doc = "Returns the size in bytes of the data that was cached."] - #[doc = ""] - #[doc = "`query_time` must be the time of query, _not_ of the resulting data."] - pub fn []( - &mut self, - query_time: TimeInt, - arch_view: &ArchetypeView, - ) -> ::re_query::Result - where - A: Archetype, - $($pov: Component,)+ - $($comp: Component,)* - { - // NOTE: not `profile_function!` because we want them merged together. - re_tracing::profile_scope!("CacheBucket::insert", format!("arch={} pov={} comp={}", A::name(), $N, $M)); - - self.sanity_check(); - - let pov_row_id = arch_view.primary_row_id(); - let index = self.data_times.partition_point(|t| t < &(query_time, pov_row_id)); - - let mut added_size_bytes = 0u64; - - self.data_times.insert(index, (query_time, pov_row_id)); - added_size_bytes += (query_time, pov_row_id).total_size_bytes(); - - { - // The `FlatVecDeque` will have to collect the data one way or another: do it ourselves - // instead, that way we can efficiently compute its size while we're at it. - let added: FlatVecDeque = arch_view - .iter_instance_keys() - .collect::>() - .into(); - added_size_bytes += added.total_size_bytes(); - self.pov_instance_keys.insert_deque(index, added); - } - - $(added_size_bytes += self.insert_component::(index, arch_view)?;)+ - $(added_size_bytes += self.insert_component_opt::(index, arch_view)?;)* - - self.sanity_check(); - - self.total_size_bytes += added_size_bytes; - - Ok(added_size_bytes) - } } - }; - - // TODO(cmc): Supporting N>1 generically is quite painful due to limitations in declarative macros, - // not that we care at the moment. - (for N=1, M=$M:expr) => { - seq!(COMP in 1..=$M { - impl_insert!(for N=1, M=$M => povs=[R1] comps=[#(C~COMP)*]); - }); - }; -} - -impl CacheBucket { - /// Alias for [`Self::insert_pov1_comp0`]. - #[inline] - #[allow(dead_code)] - fn insert_pov1( - &mut self, - query_time: TimeInt, - arch_view: &ArchetypeView, - ) -> ::re_query::Result - where - A: Archetype, - R1: Component, - { - self.insert_pov1_comp0::(query_time, arch_view) - } - - seq!(NUM_COMP in 0..10 { - impl_insert!(for N=1, M=NUM_COMP); - }); - - #[inline] - fn insert_component( - &mut self, - at: usize, - arch_view: &ArchetypeView, - ) -> re_query::Result { - re_tracing::profile_function!(C::name()); - // no sanity checks here - we are called while in an invariant-breaking state! - - let num_entries = self.data_times.len(); - - let data = self.components.entry(C::name()).or_insert_with(|| { - Box::new(FlatVecDeque::::from_vecs( - std::iter::repeat(vec![]).take( - num_entries - .checked_sub(1) - .expect("We should have been called AFTER inserting to data_times"), - ), - )) - }); - - debug_assert!(at <= data.dyn_num_entries()); - - // The `FlatVecDeque` will have to collect the data one way or another: do it ourselves - // instead, that way we can efficiently compute its size while we're at it. - let added: FlatVecDeque = arch_view - .iter_required_component::()? - .collect::>() - .into(); - let added_size_bytes = added.total_size_bytes(); - - // NOTE: downcast cannot fail, we create it just above. - let data = data.as_any_mut().downcast_mut::>().unwrap(); - data.insert_deque(at, added); - - Ok(added_size_bytes) - } - - /// This will insert an empty slice for a missing component (instead of N `None` values). - #[inline] - fn insert_component_opt( - &mut self, - at: usize, - arch_view: &ArchetypeView, - ) -> re_query::Result { - re_tracing::profile_function!(C::name()); - // no sanity checks here - we are called while in an invariant-breaking state! - - let num_entries = self.num_entries(); - - let data = self.components.entry(C::name()).or_insert_with(|| { - Box::new(FlatVecDeque::>::from_vecs( - std::iter::repeat(vec![]).take( - num_entries - .checked_sub(1) - .expect("We should have been called AFTER inserting to data_times"), - ), - )) - }); - - debug_assert!(at <= data.dyn_num_entries()); - - let added: FlatVecDeque> = if arch_view.has_component::() { - // The `FlatVecDeque` will have to collect the data one way or another: do it ourselves - // instead, that way we can efficiently computes its size while we're at it. - arch_view - .iter_optional_component::()? - .collect::>>() - .into() - } else { - // If an optional component is missing entirely, we just store an empty slice in its - // stead, rather than a bunch of `None` values. - let mut added = FlatVecDeque::>::new(); - added.push_back(std::iter::empty()); - added - }; - let added_size_bytes = added.total_size_bytes(); - - // NOTE: downcast cannot fail, we create it just above. - let data = data - .as_any_mut() - .downcast_mut::>>() - .unwrap(); - data.insert_deque(at, added); - - Ok(added_size_bytes) - } -} diff --git a/crates/re_query_cache/src/cache_stats.rs b/crates/re_query_cache/src/cache_stats.rs deleted file mode 100644 index 7c360454f000c..0000000000000 --- a/crates/re_query_cache/src/cache_stats.rs +++ /dev/null @@ -1,198 +0,0 @@ -use std::collections::BTreeMap; - -use re_log_types::{EntityPath, TimeRange, Timeline}; -use re_types_core::{components::InstanceKey, ComponentName, Loggable as _, SizeBytes as _}; - -use crate::{cache::CacheBucket, Caches, LatestAtCache, RangeCache}; - -// --- - -/// Stats for all primary caches. -/// -/// Fetch them via [`Caches::stats`]. -#[derive(Default, Debug, Clone)] -pub struct CachesStats { - pub latest_at: BTreeMap, - pub range: BTreeMap>, -} - -impl CachesStats { - #[inline] - pub fn total_size_bytes(&self) -> u64 { - re_tracing::profile_function!(); - - let Self { latest_at, range } = self; - - let latest_at_size_bytes: u64 = - latest_at.values().map(|stats| stats.total_size_bytes).sum(); - let range_size_bytes: u64 = range - .values() - .flat_map(|all_ranges| { - all_ranges - .iter() - .map(|(_, _, stats)| stats.total_size_bytes) - }) - .sum(); - - latest_at_size_bytes + range_size_bytes - } -} - -/// Stats for a cached entity. -#[derive(Debug, Clone)] -pub struct CachedEntityStats { - pub total_rows: u64, - pub total_size_bytes: u64, - - /// Only if `detailed_stats` is `true` (see [`Caches::stats`]). - pub per_component: Option>, -} - -impl CachedEntityStats { - #[inline] - pub fn is_empty(&self) -> bool { - // NOTE: That looks non-sensical, but it can happen if the cache is bugged, which we'd like - // to know. - self.total_rows == 0 && self.total_size_bytes == 0 - } -} - -/// Stats for a cached component. -#[derive(Default, Debug, Clone)] -pub struct CachedComponentStats { - pub total_rows: u64, - pub total_instances: u64, - pub total_size_bytes: u64, -} - -impl Caches { - /// Computes the stats for all primary caches. - /// - /// `per_component` toggles per-component stats. - pub fn stats(&self, detailed_stats: bool) -> CachesStats { - re_tracing::profile_function!(); - - fn upsert_bucket_stats( - per_component: &mut BTreeMap, - bucket: &CacheBucket, - ) { - let CacheBucket { - data_times, - pov_instance_keys, - components, - total_size_bytes: _, - } = bucket; - - { - let stats: &mut CachedComponentStats = - per_component.entry("".into()).or_default(); - stats.total_rows += data_times.len() as u64; - stats.total_instances += data_times.len() as u64; - stats.total_size_bytes += data_times.total_size_bytes(); - } - - { - let stats: &mut CachedComponentStats = - per_component.entry(InstanceKey::name()).or_default(); - stats.total_rows += pov_instance_keys.num_entries() as u64; - stats.total_instances += pov_instance_keys.num_values() as u64; - stats.total_size_bytes += pov_instance_keys.total_size_bytes(); - } - - for (component_name, data) in components { - let stats: &mut CachedComponentStats = - per_component.entry(*component_name).or_default(); - stats.total_rows += data.dyn_num_entries() as u64; - stats.total_instances += data.dyn_num_values() as u64; - stats.total_size_bytes += data.dyn_total_size_bytes(); - } - } - - let caches = self.read().clone(); - // Implicitly releasing top-level cache mappings -- concurrent queries can run once again. - - let latest_at = caches - .iter() - .map(|(key, caches_per_arch)| { - (key.entity_path.clone(), { - let mut total_size_bytes = 0u64; - let mut total_rows = 0u64; - let mut per_component = detailed_stats.then(BTreeMap::default); - - for latest_at_cache in caches_per_arch - .read() - .latest_at_per_archetype - .read() - .values() - { - let latest_at_cache @ LatestAtCache { - per_query_time: _, - per_data_time, - .. - } = &*latest_at_cache.read(); - - total_size_bytes += latest_at_cache.total_size_bytes(); - total_rows = per_data_time.len() as u64; - - if let Some(per_component) = per_component.as_mut() { - re_tracing::profile_scope!("detailed"); - - for bucket in per_data_time.values() { - upsert_bucket_stats(per_component, bucket); - } - } - } - - CachedEntityStats { - total_size_bytes, - total_rows, - - per_component, - } - }) - }) - .collect(); - - let range = caches - .iter() - .map(|(key, caches_per_arch)| { - (key.entity_path.clone(), { - caches_per_arch - .read() - .range_per_archetype - .read() - .values() - .map(|range_cache| { - let range_cache @ RangeCache { - per_data_time, - timeline: _, - } = &*range_cache.read(); - - let total_rows = per_data_time.data_times.len() as u64; - - let mut per_component = detailed_stats.then(BTreeMap::default); - if let Some(per_component) = per_component.as_mut() { - re_tracing::profile_scope!("detailed"); - - upsert_bucket_stats(per_component, per_data_time); - } - - ( - key.timeline, - per_data_time.time_range().unwrap_or(TimeRange::EMPTY), - CachedEntityStats { - total_size_bytes: range_cache.total_size_bytes(), - total_rows, - - per_component, - }, - ) - }) - .collect() - }) - }) - .collect(); - - CachesStats { latest_at, range } - } -} diff --git a/crates/re_query_cache/src/flat_vec_deque.rs b/crates/re_query_cache/src/flat_vec_deque.rs deleted file mode 100644 index 8cc9abc9b1a8b..0000000000000 --- a/crates/re_query_cache/src/flat_vec_deque.rs +++ /dev/null @@ -1,933 +0,0 @@ -use std::{collections::VecDeque, ops::Range}; - -use itertools::Itertools as _; - -use re_types_core::SizeBytes; - -// --- - -/// A [`FlatVecDeque`] that can be erased into a trait object. -/// -/// Methods that don't require monomorphization over `T` are made dynamically dispatchable. -pub trait ErasedFlatVecDeque: std::any::Any { - fn as_any(&self) -> &dyn std::any::Any; - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any; - - fn into_any(self: Box) -> Box; - - /// Dynamically dispatches to [`FlatVecDeque::num_entries`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_num_entries(&self) -> usize; - - /// Dynamically dispatches to [`FlatVecDeque::num_values`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_num_values(&self) -> usize; - - /// Dynamically dispatches to [`FlatVecDeque::remove`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_remove(&mut self, at: usize); - - /// Dynamically dispatches to [`FlatVecDeque::remove`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_remove_range(&mut self, range: Range); - - /// Dynamically dispatches to [`FlatVecDeque::truncate`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_truncate(&mut self, at: usize); - - /// Dynamically dispatches to [` as SizeBytes>::total_size_bytes(self)`]. - /// - /// This is prefixed with `dyn_` to avoid method dispatch ambiguities that are very hard to - /// avoid even with explicit syntax and that silently lead to infinite recursions. - fn dyn_total_size_bytes(&self) -> u64; -} - -impl ErasedFlatVecDeque for FlatVecDeque { - #[inline] - fn as_any(&self) -> &dyn std::any::Any { - self - } - - #[inline] - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - - #[inline] - fn into_any(self: Box) -> Box { - self - } - - #[inline] - fn dyn_num_entries(&self) -> usize { - self.num_entries() - } - - #[inline] - fn dyn_num_values(&self) -> usize { - self.num_values() - } - - #[inline] - fn dyn_remove(&mut self, at: usize) { - FlatVecDeque::::remove(self, at); - } - - #[inline] - fn dyn_remove_range(&mut self, range: Range) { - FlatVecDeque::::remove_range(self, range); - } - - #[inline] - fn dyn_truncate(&mut self, at: usize) { - FlatVecDeque::::truncate(self, at); - } - - #[inline] - fn dyn_total_size_bytes(&self) -> u64 { - as SizeBytes>::total_size_bytes(self) - } -} - -// --- - -/// A double-ended queue implemented with a pair of growable ring buffers, where every single -/// entry is a flattened array of values. -/// -/// Logically like a `VecDeque>`, but with a less fragmented memory layout (each `Box<[T]>` -/// gets copied/inlined into the `FlatVecDeque`). -/// `FlatVecDeque` therefore optimizes for reads (cache locality, specifically) while `VecDeque>` -/// optimizes for writes. -/// -/// You can think of this as the native/deserialized version of an Arrow `ListArray`. -/// This is particularly useful when working with many small arrays of data (e.g. Rerun's `Scalar`s). -// -// TODO(cmc): We could even use a bitmap for T=Option, which would bring this that much -// closer to a deserialized version of an Arrow array. -#[derive(Debug, Clone)] -pub struct FlatVecDeque { - /// Stores every value in the `FlatVecDeque` in a flattened `VecDeque`. - /// - /// E.g.: - /// - `FlatVecDeque[]` -> values=`[]`. - /// - `FlatVecDeque[[], [], []]` -> values=`[]`. - /// - `FlatVecDeque[[], [0], [1, 2, 3], [4, 5]]` -> values=`[0, 1, 2, 3, 4, 5]`. - values: VecDeque, - - /// Keeps track of each entry, i.e. logical slices of data. - /// - /// E.g.: - /// - `FlatVecDeque[]` -> offsets=`[]`. - /// - `FlatVecDeque[[], [], []]` -> offsets=`[0, 0, 0]`. - /// - `FlatVecDeque[[], [0], [1, 2, 3], [4, 5]]` -> offsets=`[0, 1, 4, 6]`. - offsets: VecDeque, -} - -impl SizeBytes for FlatVecDeque { - #[inline] - fn heap_size_bytes(&self) -> u64 { - // NOTE: It's all on the heap at this point. - - let values_size_bytes = if T::is_pod() { - (self.num_values() * std::mem::size_of::()) as _ - } else { - self.values - .iter() - .map(SizeBytes::total_size_bytes) - .sum::() - }; - - let offsets_size_bytes = self.num_entries() * std::mem::size_of::(); - - values_size_bytes + offsets_size_bytes as u64 - } -} - -impl From> for FlatVecDeque { - #[inline] - fn from(values: VecDeque) -> Self { - let num_values = values.len(); - Self { - values, - offsets: std::iter::once(num_values).collect(), - } - } -} - -impl Default for FlatVecDeque { - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl FlatVecDeque { - #[inline] - pub const fn new() -> Self { - Self { - values: VecDeque::new(), - offsets: VecDeque::new(), - } - } - - #[inline] - pub fn from_vecs(entries: impl IntoIterator>) -> Self { - let mut this = Self::new(); - - // NOTE: Do not use any of the insertion methods, they rely on `from_vecs` in the first - // place! - let mut value_offset = 0; - for entry in entries { - value_offset += entry.len(); // increment first! - this.offsets.push_back(value_offset); - this.values.extend(entry); - } - - this - } - - /// How many entries are there in the deque? - /// - /// Keep in mind: each entry is itself an array of values. - /// Use [`Self::num_values`] to get the total number of values across all entries. - #[inline] - pub fn num_entries(&self) -> usize { - self.offsets.len() - } - - /// How many values are there in the deque? - /// - /// Keep in mind: each entry in the deque holds an array of values. - /// Use [`Self::num_entries`] to get the total number of entries, irrelevant of how many - /// values each entry holds. - #[inline] - pub fn num_values(&self) -> usize { - self.values.len() - } - - #[inline] - fn value_offset(&self, entry_index: usize) -> usize { - if entry_index == 0 { - 0 - } else { - self.offsets[entry_index - 1] - } - } - - #[inline] - fn iter_offset_ranges(&self) -> impl Iterator> + '_ { - std::iter::once(0) - .chain(self.offsets.iter().copied()) - .tuple_windows::<(_, _)>() - .map(|(start, end)| (start..end)) - } -} - -// --- - -impl FlatVecDeque { - /// Iterates over all the entries in the deque. - /// - /// This is the same as `self.range(0..self.num_entries())`. - /// - /// Keep in mind that each entry is an array of values! - #[inline] - pub fn iter(&self) -> impl Iterator { - self.range(0..self.num_entries()) - } - - /// Iterates over all the entries in the deque in the given `entry_range`. - /// - /// Keep in mind that each entry is an array of values! - #[inline] - pub fn range(&self, entry_range: Range) -> impl Iterator { - let (values_left, values_right) = self.values.as_slices(); - // NOTE: We can't slice into our offsets, we don't even know if they're contiguous in - // memory at this point -> skip() and take(). - self.iter_offset_ranges() - .skip(entry_range.start) - .take(entry_range.len()) - .map(|offsets| { - if offsets.is_empty() { - return &[] as &'_ [T]; - } - - // NOTE: We do not need `make_contiguous` here because we always guarantee - // that a single entry's worth of values is fully contained in either the left or - // right buffer, but never straddling across both. - if offsets.start < values_left.len() { - &values_left[offsets] - } else { - &values_right[offsets] - } - }) - } -} - -#[test] -fn range() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - assert_iter_eq(&[&[1, 2, 3]], v.range(0..1)); - assert_iter_eq(&[&[4, 5, 6, 7]], v.range(1..2)); - assert_iter_eq(&[&[8, 9, 10]], v.range(2..3)); - - assert_iter_eq( - &[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], - v.range(0..v.num_entries()), - ); - - assert_iter_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], v.iter()); -} - -// --- - -impl FlatVecDeque { - /// Prepends an entry comprised of `values` to the deque. - /// - /// This is the same as `self.insert(0, values)`. - /// - /// See [`Self::insert`] for more information. - #[inline] - pub fn push_front(&mut self, values: impl IntoIterator) { - self.insert(0, values); - } - - /// Appends an entry comprised of `values` to the deque. - /// - /// This is the same as `self.insert(self.num_entries(), values)`. - /// - /// See [`Self::insert`] for more information. - #[inline] - pub fn push_back(&mut self, values: impl IntoIterator) { - self.insert(self.num_entries(), values); - } - - /// Inserts a single entry at `entry_index`, comprised of the multiple elements given as `values`. - /// - /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. - /// Otherwise, this requires splitting the deque into two pieces then stitching them back together - /// at both ends of the added data. - /// - /// Panics if `entry_index` is out of bounds. - /// Panics if `values` is empty. - #[inline] - pub fn insert(&mut self, entry_index: usize, values: impl IntoIterator) { - let values: VecDeque = values.into_iter().collect(); - let deque = values.into(); - self.insert_deque(entry_index, deque); - } - - /// Prepends multiple entries, each comprised of the multiple elements given in `entries`, - /// to the deque. - /// - /// This is the same as `self.insert_many(0, entries)`. - /// - /// See [`Self::insert_many`] for more information. - #[inline] - pub fn push_many_front(&mut self, entries: impl IntoIterator>) { - self.insert_many(0, entries); - } - - /// Appends multiple entries, each comprised of the multiple elements given in `entries`, - /// to the deque. - /// - /// This is the same as `self.insert_many(self.num_entries(), entries)`. - /// - /// See [`Self::insert_many`] for more information. - #[inline] - pub fn push_many_back(&mut self, entries: impl IntoIterator>) { - self.insert_many(self.num_entries(), entries); - } - - /// Inserts multiple entries, starting at `entry_index` onwards, each comprised of the multiple elements - /// given in `entries`. - /// - /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. - /// Otherwise, this requires splitting the deque into two pieces then stitching them back together - /// at both ends of the added data. - /// - /// Panics if `entry_index` is out of bounds. - /// Panics if any of the value arrays in `entries` is empty. - #[inline] - pub fn insert_many(&mut self, entry_index: usize, entries: impl IntoIterator>) { - let deque = Self::from_vecs(entries); - self.insert_deque(entry_index, deque); - } - - /// Prepends another full deque to the deque. - /// - /// This is the same as `self.insert_deque(0, rhs)`. - /// - /// See [`Self::insert_deque`] for more information. - #[inline] - pub fn push_front_deque(&mut self, rhs: FlatVecDeque) { - self.insert_deque(0, rhs); - } - - /// Appends another full deque to the deque. - /// - /// This is the same as `self.insert_deque(0, rhs)`. - /// - /// See [`Self::insert_deque`] for more information. - #[inline] - pub fn push_back_deque(&mut self, rhs: FlatVecDeque) { - self.insert_deque(self.num_entries(), rhs); - } - - /// Inserts another full deque, starting at `entry_index` and onwards. - /// - /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. - /// Otherwise, this requires splitting the deque into two pieces then stitching them back together - /// at both ends of the added data. - /// - /// Panics if `entry_index` is out of bounds. - /// Panics if any of the value arrays in `entries` is empty. - pub fn insert_deque(&mut self, entry_index: usize, mut rhs: FlatVecDeque) { - // NOTE: We're inserting _beyond_ the last element. - if entry_index == self.num_entries() { - let max_value_offset = self.offsets.back().copied().unwrap_or_default(); - self.offsets - .extend(rhs.offsets.into_iter().map(|o| o + max_value_offset)); - self.values.extend(rhs.values); - return; - } else if entry_index == 0 { - rhs.push_back_deque(std::mem::take(self)); - *self = rhs; - return; - } - - let right = self.split_off(entry_index); - self.push_back_deque(rhs); - self.push_back_deque(right); - - debug_assert!(self.iter_offset_ranges().all(|r| r.start <= r.end)); - } -} - -#[test] -fn insert() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert(0, [1, 2, 3]); - assert_deque_eq(&[&[1, 2, 3]], &v); - - v.insert(0, [4, 5, 6, 7]); - assert_deque_eq(&[&[4, 5, 6, 7], &[1, 2, 3]], &v); - - v.insert(0, [8, 9]); - assert_deque_eq(&[&[8, 9], &[4, 5, 6, 7], &[1, 2, 3]], &v); - - v.insert(2, [10, 11, 12, 13]); - assert_deque_eq(&[&[8, 9], &[4, 5, 6, 7], &[10, 11, 12, 13], &[1, 2, 3]], &v); - - v.insert(v.num_entries(), [14, 15]); - assert_deque_eq( - &[ - &[8, 9], - &[4, 5, 6, 7], - &[10, 11, 12, 13], - &[1, 2, 3], - &[14, 15], - ], - &v, - ); - - v.insert(v.num_entries() - 1, [42]); - assert_deque_eq( - &[ - &[8, 9], - &[4, 5, 6, 7], - &[10, 11, 12, 13], - &[1, 2, 3], - &[42], - &[14, 15], - ], - &v, - ); -} - -#[test] -fn insert_empty() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.push_back([]); - v.push_back([]); - v.push_back([]); - - assert_deque_eq(&[&[], &[], &[]], &v); -} - -// Simulate the bug that was making everything crash on the face tracking example (ultimately -// caused by recursive clears). -#[test] -fn insert_some_and_empty() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.push_back([0]); - v.push_back([]); - - v.push_back([1]); - v.push_back([]); - - v.push_back([2]); - v.push_back([]); - - // That used to crash. - assert_deque_eq(&[&[0], &[], &[1], &[], &[2], &[]], &v); -} - -#[test] -fn insert_many() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - v.insert_many(0, [vec![20], vec![21], vec![22]]); - assert_deque_eq( - &[&[20], &[21], &[22], &[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], - &v, - ); - - v.insert_many(4, [vec![41, 42], vec![43]]); - assert_deque_eq( - &[ - &[20], - &[21], - &[22], - &[1, 2, 3], - &[41, 42], - &[43], - &[4, 5, 6, 7], - &[8, 9, 10], - ], - &v, - ); - - v.insert_many(v.num_entries(), [vec![100], vec![200, 300, 400]]); - assert_deque_eq( - &[ - &[20], - &[21], - &[22], - &[1, 2, 3], - &[41, 42], - &[43], - &[4, 5, 6, 7], - &[8, 9, 10], - &[100], - &[200, 300, 400], - ], - &v, - ); -} - -#[test] -fn insert_deque() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_deque( - 0, - FlatVecDeque::from_vecs([vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]), - ); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - v.insert_deque(0, FlatVecDeque::from_vecs([vec![20], vec![21], vec![22]])); - assert_deque_eq( - &[&[20], &[21], &[22], &[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], - &v, - ); - - v.insert_deque(4, FlatVecDeque::from_vecs([vec![41, 42], vec![43]])); - assert_deque_eq( - &[ - &[20], - &[21], - &[22], - &[1, 2, 3], - &[41, 42], - &[43], - &[4, 5, 6, 7], - &[8, 9, 10], - ], - &v, - ); - - v.insert_deque( - v.num_entries(), - FlatVecDeque::from_vecs([vec![100], vec![200, 300, 400]]), - ); - assert_deque_eq( - &[ - &[20], - &[21], - &[22], - &[1, 2, 3], - &[41, 42], - &[43], - &[4, 5, 6, 7], - &[8, 9, 10], - &[100], - &[200, 300, 400], - ], - &v, - ); -} - -// --- - -impl FlatVecDeque { - /// Splits the deque into two at the given index. - /// - /// Returns a newly allocated `FlatVecDeque`. `self` contains entries `[0, entry_index)`, - /// and the returned deque contains entries `[entry_index, num_entries)`. - /// - /// Note that the capacity of `self` does not change. - /// - /// Panics if `entry_index` is out of bounds. - #[inline] - #[must_use = "use `.truncate()` if you don't need the other half"] - pub fn split_off(&mut self, entry_index: usize) -> Self { - let value_offset = self.value_offset(entry_index); - - let mut offsets = self.offsets.split_off(entry_index); - for offset in &mut offsets { - *offset -= value_offset; - } - - Self { - values: self.values.split_off(value_offset), - offsets, - } - } - - /// Shortens the deque, keeping all entries up to `entry_index` (excluded), and - /// dropping the rest. - /// - /// If `entry_index` is greater or equal to [`Self::num_entries`], this has no effect. - #[inline] - pub fn truncate(&mut self, entry_index: usize) { - if entry_index < self.num_entries() { - self.values.truncate(self.value_offset(entry_index)); - self.offsets.truncate(entry_index); - } - } - - /// Removes the entry at `entry_index` from the deque. - /// - /// This is O(1) if `entry_index` corresponds to either the start or the end of the deque. - /// Otherwise, this requires splitting the deque into three pieces, dropping the superfluous - /// one, then stitching the two remaining pices back together. - /// - /// Panics if `entry_index` is out of bounds. - pub fn remove(&mut self, entry_index: usize) { - let (start_offset, end_offset) = ( - self.value_offset(entry_index), - self.value_offset(entry_index + 1), - ); - let offset_count = end_offset - start_offset; - - if entry_index + 1 == self.num_entries() { - self.offsets.truncate(self.num_entries() - 1); - self.values.truncate(self.values.len() - offset_count); - return; - } else if entry_index == 0 { - *self = self.split_off(entry_index + 1); - return; - } - - // NOTE: elegant, but way too slow :) - // let right = self.split_off(entry_index + 1); - // _ = self.split_off(self.num_entries() - 1); - // self.push_back_deque(right); - - _ = self.offsets.remove(entry_index); - for offset in self.offsets.range_mut(entry_index..) { - *offset -= offset_count; - } - - let right = self.values.split_off(end_offset); - self.values.truncate(self.values.len() - offset_count); - self.values.extend(right); - } - - /// Removes all entries within the given `entry_range` from the deque. - /// - /// This is O(1) if `entry_range` either starts at the beginning of the deque, or ends at - /// the end of the deque, or both. - /// Otherwise, this requires splitting the deque into three pieces, dropping the superfluous - /// one, then stitching the two remaining pieces back together. - /// - /// Panics if `entry_range` is either out of bounds or isn't monotonically increasing. - #[inline] - pub fn remove_range(&mut self, entry_range: Range) { - assert!(entry_range.start <= entry_range.end); - - if entry_range.start == entry_range.end { - return; - } - - let (start_offset, end_offset) = ( - self.value_offset(entry_range.start), - self.value_offset(entry_range.end), - ); - let offset_count = end_offset - start_offset; - - // Reminder: `entry_range.end` is exclusive. - if entry_range.end == self.num_entries() { - self.offsets - .truncate(self.num_entries() - entry_range.len()); - self.values.truncate(self.values.len() - offset_count); - return; - } else if entry_range.start == 0 { - *self = self.split_off(entry_range.end); - return; - } - - let right = self.split_off(entry_range.end); - _ = self.split_off(self.num_entries() - entry_range.len()); - self.push_back_deque(right); - } -} - -#[test] -fn truncate() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - { - let mut v = v.clone(); - v.truncate(0); - assert_deque_eq(&[], &v); - } - - { - let mut v = v.clone(); - v.truncate(1); - assert_deque_eq(&[&[1, 2, 3]], &v); - } - - { - let mut v = v.clone(); - v.truncate(2); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); - } - - { - let mut v = v.clone(); - v.truncate(3); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - } -} - -#[test] -fn split_off() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - { - let mut left = v.clone(); - let right = left.split_off(0); - - assert_deque_eq(&[], &left); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &right); - } - - { - let mut left = v.clone(); - let right = left.split_off(1); - - assert_deque_eq(&[&[1, 2, 3]], &left); - assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9, 10]], &right); - } - - { - let mut left = v.clone(); - let right = left.split_off(2); - - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &left); - assert_deque_eq(&[&[8, 9, 10]], &right); - } - - { - let mut left = v.clone(); - let right = left.split_off(3); - - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &left); - assert_deque_eq(&[], &right); - } -} - -#[test] -fn remove() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert(0, [1, 2, 3]); - assert_deque_eq(&[&[1, 2, 3]], &v); - - v.remove(0); - assert_deque_eq(&[], &v); - - v.insert(0, [1, 2, 3]); - assert_deque_eq(&[&[1, 2, 3]], &v); - - v.insert(1, [4, 5, 6, 7]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); - - v.insert(2, [8, 9]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); - - v.remove(0); - assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9]], &v); - - v.insert(0, [1, 2, 3]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); - - v.remove(1); - assert_deque_eq(&[&[1, 2, 3], &[8, 9]], &v); - - v.insert(1, [4, 5, 6, 7]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9]], &v); - - v.remove(2); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); - - v.remove(0); - assert_deque_eq(&[&[4, 5, 6, 7]], &v); - - v.remove(0); - assert_deque_eq(&[], &v); -} - -#[test] -#[should_panic(expected = "Out of bounds access")] -fn remove_empty() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.remove(0); -} - -#[test] -#[should_panic(expected = "Out of bounds access")] -fn remove_oob() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert(0, [1, 2, 3]); - assert_deque_eq(&[&[1, 2, 3]], &v); - - assert_eq!(1, v.num_entries()); - assert_eq!(3, v.num_values()); - - v.remove(1); -} - -#[test] -fn remove_range() { - let mut v: FlatVecDeque = FlatVecDeque::new(); - - assert_eq!(0, v.num_entries()); - assert_eq!(0, v.num_values()); - - v.insert_many(0, [vec![1, 2, 3], vec![4, 5, 6, 7], vec![8, 9, 10]]); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7], &[8, 9, 10]], &v); - - { - let mut v = v.clone(); - v.remove_range(0..1); - assert_deque_eq(&[&[4, 5, 6, 7], &[8, 9, 10]], &v); - } - - { - let mut v = v.clone(); - v.remove_range(1..2); - assert_deque_eq(&[&[1, 2, 3], &[8, 9, 10]], &v); - } - - { - let mut v = v.clone(); - v.remove_range(2..3); - assert_deque_eq(&[&[1, 2, 3], &[4, 5, 6, 7]], &v); - } - - { - let mut v = v.clone(); - v.remove_range(0..2); - assert_deque_eq(&[&[8, 9, 10]], &v); - } - - { - let mut v = v.clone(); - v.remove_range(1..3); - assert_deque_eq(&[&[1, 2, 3]], &v); - } - - { - let mut v = v.clone(); - v.remove_range(0..3); - assert_deque_eq(&[], &v); - } -} - -// --- - -#[cfg(test)] -fn assert_deque_eq(expected: &[&'_ [i64]], got: &FlatVecDeque) { - similar_asserts::assert_eq!(expected, got.iter().collect_vec()); -} - -#[cfg(test)] -fn assert_iter_eq<'a>(expected: &[&'_ [i64]], got: impl Iterator) { - similar_asserts::assert_eq!(expected, got.collect_vec()); -} diff --git a/crates/re_query_cache/src/latest_at.rs b/crates/re_query_cache/src/latest_at.rs deleted file mode 100644 index 4804b513801de..0000000000000 --- a/crates/re_query_cache/src/latest_at.rs +++ /dev/null @@ -1,332 +0,0 @@ -use std::{collections::BTreeMap, sync::Arc}; - -use ahash::HashMap; -use paste::paste; -use seq_macro::seq; - -use re_data_store::{DataStore, LatestAtQuery, TimeInt}; -use re_log_types::{EntityPath, RowId, Timeline}; -use re_query::query_archetype; -use re_types_core::{components::InstanceKey, Archetype, Component, SizeBytes}; - -use crate::{CacheBucket, Caches}; - -// --- Data structures --- - -/// Caches the results of `LatestAt` queries. -#[derive(Default)] -pub struct LatestAtCache { - /// Organized by _query_ time. - /// - /// If the data you're looking for isn't in here, try partially running the query and check - /// if there is any data available for the resulting _data_ time in [`Self::per_data_time`]. - // - // NOTE: `Arc` so we can deduplicate buckets across query time & data time. - pub per_query_time: BTreeMap>, - - /// Organized by _data_ time. - /// - /// Due to how our latest-at semantics work, any number of queries at time `T+n` where `n >= 0` - /// can result in a data time of `T`. - // - // NOTE: `Arc` so we can deduplicate buckets across query time & data time. - pub per_data_time: BTreeMap>, - - /// For debugging purposes. - pub(crate) timeline: Timeline, - - /// Total size of the data stored in this cache in bytes. - total_size_bytes: u64, -} - -impl std::fmt::Debug for LatestAtCache { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - per_query_time, - per_data_time, - timeline, - total_size_bytes: _, - } = self; - - let mut strings = Vec::new(); - - let data_times_per_bucket: HashMap<_, _> = per_data_time - .iter() - .map(|(time, bucket)| (Arc::as_ptr(bucket), *time)) - .collect(); - - for (query_time, bucket) in per_query_time { - let query_time = timeline.typ().format_utc(*query_time); - let data_time = data_times_per_bucket - .get(&Arc::as_ptr(bucket)) - .map_or_else(|| "MISSING?!".to_owned(), |t| timeline.typ().format_utc(*t)); - strings.push(format!( - "query_time={query_time} -> data_time={data_time} ({})", - re_format::format_bytes(bucket.total_size_bytes as _), - )); - strings.push(indent::indent_all_by(2, format!("{bucket:?}"))); - } - - f.write_str(&strings.join("\n").replace("\n\n", "\n")) - } -} - -impl SizeBytes for LatestAtCache { - #[inline] - fn heap_size_bytes(&self) -> u64 { - self.total_size_bytes - } -} - -impl LatestAtCache { - /// Removes everything from the cache that corresponds to a time equal or greater than the - /// specified `threshold`. - /// - /// Reminder: invalidating static data is the same as invalidating everything, so just reset - /// the `LatestAtCache` entirely in that case. - /// - /// Returns the number of bytes removed. - #[inline] - pub fn truncate_at_time(&mut self, threshold: TimeInt) -> u64 { - let Self { - per_query_time, - per_data_time, - timeline: _, - total_size_bytes, - } = self; - - let mut removed_bytes = 0u64; - - per_query_time.retain(|&query_time, _| query_time < threshold); - - // Buckets for latest-at queries are guaranteed to only ever contain a single entry, so - // just remove the buckets entirely directly. - per_data_time.retain(|&data_time, bucket| { - if data_time < threshold { - return true; - } - - // Only if that bucket is about to be dropped. - if Arc::strong_count(bucket) == 1 { - removed_bytes += bucket.total_size_bytes; - } - - false - }); - - *total_size_bytes = total_size_bytes - .checked_sub(removed_bytes) - .unwrap_or_else(|| { - re_log::debug!( - current = *total_size_bytes, - removed = removed_bytes, - "book keeping underflowed" - ); - u64::MIN - }); - - removed_bytes - } -} - -// --- Queries --- - -macro_rules! impl_query_archetype_latest_at { - (for N=$N:expr, M=$M:expr => povs=[$($pov:ident)+] comps=[$($comp:ident)*]) => { paste! { - #[doc = "Cached implementation of [`re_query::query_archetype`] and [`re_query::range_archetype`]"] - #[doc = "(combined) for `" $N "` point-of-view components and `" $M "` optional components."] - #[allow(non_snake_case)] - pub fn []<'a, A, $($pov,)+ $($comp,)* F>( - &self, - store: &'a DataStore, - query: &LatestAtQuery, - entity_path: &'a EntityPath, - mut f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - $($pov: Component,)+ - $($comp: Component,)* - F: FnMut( - ( - (TimeInt, RowId), - &[InstanceKey], - $(&[$pov],)+ - $(Option<&[Option<$comp>]>,)* - ), - ), - { - let iter_results = |bucket: &crate::CacheBucket, f: &mut F| -> crate::Result<()> { - // Profiling this in isolation can be useful, but adds a lot of noise for small queries. - // re_tracing::profile_scope!("iter"); - - let it = itertools::izip!( - bucket.iter_data_times(), - bucket.iter_pov_instance_keys(), - $(bucket.iter_component::<$pov>() - .ok_or_else(|| re_query::ComponentNotFoundError(<$pov>::name()))?,)+ - $(bucket.iter_component_opt::<$comp>() - .map_or_else( - || itertools::Either::Left(std::iter::repeat(&[] as &[Option<$comp>])), - |it| itertools::Either::Right(it)), - )* - ).map(|((time, row_id), instance_keys, $($pov,)+ $($comp,)*)| { - ( - (*time, *row_id), - instance_keys, - $($pov,)+ - $((!$comp.is_empty()).then_some($comp),)* - ) - }); - - for data in it { - f(data); - } - - Ok(()) - }; - - let create_and_fill_bucket = | - data_time: TimeInt, - arch_view: &::re_query::ArchetypeView, - | -> crate::Result { - re_log::trace!(data_time=?data_time, ?data_time, "fill"); - - // Grabbing the current time is quite costly on web. - #[cfg(not(target_arch = "wasm32"))] - let now = web_time::Instant::now(); - - let mut bucket = crate::CacheBucket::default(); - bucket.[]::(data_time, &arch_view)?; - - #[cfg(not(target_arch = "wasm32"))] - { - let elapsed = now.elapsed(); - ::re_log::trace!( - store_id=%store.id(), - %entity_path, - archetype=%A::name(), - added_size_bytes=bucket.total_size_bytes, - "cached new entry in {elapsed:?} ({:0.3} entries/s)", - 1f64 / elapsed.as_secs_f64() - ); - } - - Ok(bucket) - }; - - let upsert_callback = |query: &LatestAtQuery, latest_at_cache: &mut crate::LatestAtCache| -> crate::Result<()> { - re_tracing::profile_scope!("latest_at", format!("{query:?}")); - - let crate::LatestAtCache { - per_query_time, - per_data_time, - timeline: _, - total_size_bytes, - } = latest_at_cache; - - let query_time_bucket_at_query_time = match per_query_time.entry(query.at()) { - std::collections::btree_map::Entry::Occupied(_) => { - // Fastest path: we have an entry for this exact query time, no need to look any - // further. - re_log::trace!(query_time=?query.at(), "cache hit (query time)"); - return Ok(()); - } - std::collections::btree_map::Entry::Vacant(entry) => entry, - }; - - let arch_view = query_archetype::(store, &query, entity_path)?; - let data_time = arch_view.data_time(); - - // Fast path: we've run the query and realized that we already have the data for the resulting - // _data_ time, so let's use that to avoid join & deserialization costs. - if let Some(data_time_bucket_at_data_time) = per_data_time.get(&data_time) { - re_log::trace!(query_time=?query.at(), ?data_time, "cache hit (data time)"); - - query_time_bucket_at_query_time.insert(Arc::clone(&data_time_bucket_at_data_time)); - - // We now know for a fact that a query at that data time would yield the same - // results: copy the bucket accordingly so that the next cache hit for that query - // time ends up taking the fastest path. - let query_time_bucket_at_data_time = per_query_time.entry(data_time); - query_time_bucket_at_data_time - .and_modify(|v| *v = Arc::clone(&data_time_bucket_at_data_time)) - .or_insert(Arc::clone(&data_time_bucket_at_data_time)); - - return Ok(()); - } - - // Slowest path: this is a complete cache miss. - { - re_log::trace!(query_time=?query.at(), ?data_time, "cache miss"); - - let bucket = Arc::new(create_and_fill_bucket(data_time, &arch_view)?); - *total_size_bytes += bucket.total_size_bytes; - let query_time_bucket_at_query_time = query_time_bucket_at_query_time.insert(bucket); - - let data_time_bucket_at_data_time = per_data_time.entry(data_time); - data_time_bucket_at_data_time - .and_modify(|v| *v = Arc::clone(&query_time_bucket_at_query_time)) - .or_insert(Arc::clone(&query_time_bucket_at_query_time)); - - Ok(()) - } - }; - - let iter_callback = |query: &LatestAtQuery, latest_at_cache: &crate::LatestAtCache, f: &mut F| { - re_tracing::profile_scope!("latest_at", format!("{query:?}")); - - let crate::LatestAtCache { - per_query_time, - per_data_time: _, - timeline: _, - total_size_bytes: _, - } = latest_at_cache; - - // Expected path: cache was properly upserted. - if let Some(query_time_bucket_at_query_time) = per_query_time.get(&query.at()) { - return iter_results(query_time_bucket_at_query_time, f); - } - - re_log::trace!( - store_id = %store.id(), - %entity_path, - ?query, - "either no data exist at this time or we couldn't upsert the cache (write lock was busy)" - ); - - Ok(()) - }; - - - let (res1, res2) = self.with_latest_at::( - store, - entity_path.clone(), - query, - |latest_at_cache| upsert_callback(query, latest_at_cache), - |latest_at_cache| iter_callback(query, latest_at_cache, &mut f), - ); - - if let Some(res1) = res1 { - res1?; - } - res2?; - - Ok(()) - } } - }; - - // TODO(cmc): Supporting N>1 generically is quite painful due to limitations in declarative macros, - // not that we care at the moment. - (for N=1, M=$M:expr) => { - seq!(COMP in 1..=$M { - impl_query_archetype_latest_at!(for N=1, M=$M => povs=[R1] comps=[#(C~COMP)*]); - }); - }; -} - -impl Caches { - seq!(NUM_COMP in 0..10 { - impl_query_archetype_latest_at!(for N=1, M=NUM_COMP); - }); -} diff --git a/crates/re_query_cache/src/lib.rs b/crates/re_query_cache/src/lib.rs deleted file mode 100644 index df9044b10f42e..0000000000000 --- a/crates/re_query_cache/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Caching datastructures for `re_query`. - -mod cache; -mod cache_stats; -mod flat_vec_deque; -mod latest_at; -mod query; -mod range; - -pub use self::cache::{AnyQuery, Caches}; -pub use self::cache_stats::{CachedComponentStats, CachedEntityStats, CachesStats}; -pub use self::flat_vec_deque::{ErasedFlatVecDeque, FlatVecDeque}; -pub use self::query::iter_or_repeat_opt; - -pub(crate) use self::cache::CacheBucket; -pub(crate) use self::latest_at::LatestAtCache; -pub(crate) use self::range::RangeCache; - -pub use re_query::{QueryError, Result}; // convenience - -pub mod external { - pub use re_query; - - pub use paste; - pub use seq_macro; -} diff --git a/crates/re_query_cache/src/query.rs b/crates/re_query_cache/src/query.rs deleted file mode 100644 index 50e4036bff323..0000000000000 --- a/crates/re_query_cache/src/query.rs +++ /dev/null @@ -1,251 +0,0 @@ -use paste::paste; -use seq_macro::seq; - -use re_data_store::{DataStore, LatestAtQuery, RangeQuery, TimeInt, Timeline}; -use re_log_types::{EntityPath, RowId}; -use re_query::{ExtraQueryHistory, VisibleHistory}; -use re_types_core::{components::InstanceKey, Archetype, Component}; - -use crate::{AnyQuery, Caches}; - -// --- - -/// Iterates over the data of an optional component, or repeat `None` values if it's missing. -#[inline] -pub fn iter_or_repeat_opt( - this: Option<&[Option]>, - len: usize, -) -> impl Iterator> + '_ { - this.as_ref().map_or( - itertools::Either::Left(std::iter::repeat(&None).take(len)), - |data| itertools::Either::Right(data.iter()), - ) -} - -// --- - -/// Cached implementation of [`re_query::query_archetype`] and [`re_query::range_archetype`] -/// (combined) for 1 point-of-view component and no optional components. -/// -/// Alias for [`Self::query_archetype_pov1_comp0`]. -impl Caches { - #[inline] - pub fn query_archetype_pov1<'a, A, R1, F>( - &self, - store: &'a DataStore, - query: &AnyQuery, - entity_path: &'a EntityPath, - f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - R1: Component, - F: FnMut(((TimeInt, RowId), &[InstanceKey], &[R1])), - { - self.query_archetype_pov1_comp0::(store, query, entity_path, f) - } -} - -macro_rules! impl_query_archetype { - (for N=$N:expr, M=$M:expr => povs=[$($pov:ident)+] comps=[$($comp:ident)*]) => { paste! { - #[doc = "Cached implementation of [`re_query::query_archetype`] and [`re_query::range_archetype`]"] - #[doc = "(combined) for `" $N "` point-of-view components and `" $M "` optional components."] - #[allow(non_snake_case)] - pub fn []<'a, A, $($pov,)+ $($comp,)* F>( - &self, - store: &'a DataStore, - query: &AnyQuery, - entity_path: &'a EntityPath, - mut f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - $($pov: Component,)+ - $($comp: Component,)* - F: FnMut( - ( - (TimeInt, RowId), - &[InstanceKey], - $(&[$pov],)+ - $(Option<&[Option<$comp>]>,)* - ), - ), - { - // NOTE: not `profile_function!` because we want them merged together. - re_tracing::profile_scope!( - "query_archetype", - format!("cached=true arch={} pov={} comp={}", A::name(), $N, $M) - ); - - match &query { - AnyQuery::LatestAt(query) => { - re_tracing::profile_scope!("latest_at", format!("{query:?}")); - - self.[]::( - store, - query, - entity_path, - f, - ) - } - - AnyQuery::Range(query) => { - re_tracing::profile_scope!("range", format!("{query:?}")); - - self.[]::( - store, - query, - entity_path, - |entry_range, (data_times, pov_instance_keys, $($pov,)+ $($comp,)*)| { - let it = itertools::izip!( - data_times.range(entry_range.clone()), - pov_instance_keys.range(entry_range.clone()), - $($pov.range(entry_range.clone()),)+ - $($comp.map_or_else( - || itertools::Either::Left(std::iter::repeat(&[] as &[Option<$comp>])), - |data| itertools::Either::Right(data.range(entry_range.clone()))) - ,)* - ).map(|((time, row_id), instance_keys, $($pov,)+ $($comp,)*)| { - ( - (*time, *row_id), - instance_keys, - $($pov,)+ - $((!$comp.is_empty()).then_some($comp),)* - ) - }); - - for data in it { - f(data); - } - }, - ) - } - } - } } - }; - - // TODO(cmc): Supporting N>1 generically is quite painful due to limitations in declarative macros, - // not that we care at the moment. - (for N=1, M=$M:expr) => { - seq!(COMP in 1..=$M { - impl_query_archetype!(for N=1, M=$M => povs=[R1] comps=[#(C~COMP)*]); - }); - }; -} - -impl Caches { - seq!(NUM_COMP in 0..10 { - impl_query_archetype!(for N=1, M=NUM_COMP); - }); -} - -// --- - -/// Cached implementation of [`re_query::query_archetype_with_history`] for 1 point-of-view component -/// and no optional components. -/// -/// Alias for [`Self::query_archetype_with_history_pov1_comp0`]. -impl Caches { - #[allow(clippy::too_many_arguments)] - #[inline] - pub fn query_archetype_with_history_pov1<'a, A, R1, F>( - &self, - store: &'a DataStore, - timeline: &'a Timeline, - time: &'a TimeInt, - history: &ExtraQueryHistory, - ent_path: &'a EntityPath, - f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - R1: Component, - F: FnMut(((TimeInt, RowId), &[InstanceKey], &[R1])), - { - self.query_archetype_with_history_pov1_comp0::( - store, timeline, time, history, ent_path, f, - ) - } -} - -/// Generates a function to cache a (potentially historical) query with N point-of-view components and M -/// other components. -macro_rules! impl_query_archetype_with_history { - (for N=$N:expr, M=$M:expr => povs=[$($pov:ident)+] comps=[$($comp:ident)*]) => { paste! { - #[doc = "Cached implementation of [`re_query::query_archetype_with_history`] for `" $N "` point-of-view"] - #[doc = "components and `" $M "` optional components."] - #[allow(clippy::too_many_arguments)] - pub fn []<'a, A, $($pov,)+ $($comp,)* F>( - &self, - store: &'a DataStore, - timeline: &'a Timeline, - time: &'a TimeInt, - history: &ExtraQueryHistory, - ent_path: &'a EntityPath, - f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - $($pov: Component,)+ - $($comp: Component,)* - F: FnMut( - ( - (TimeInt, RowId), - &[InstanceKey], - $(&[$pov],)+ - $(Option<&[Option<$comp>]>,)* - ), - ), - { - - let visible_history = match timeline.typ() { - re_log_types::TimeType::Time => history.nanos, - re_log_types::TimeType::Sequence => history.sequences, - }; - - if !history.enabled || visible_history == VisibleHistory::OFF { - // NOTE: not `profile_function!` because we want them merged together. - re_tracing::profile_scope!( - "query_archetype_with_history", - format!("cached=true arch={} pov={} comp={}", A::name(), $N, $M) - ); - - let query = LatestAtQuery::new(*timeline, *time); - self.[]::( - store, - &query.clone().into(), - ent_path, - f, - ) - } else { - // NOTE: not `profile_function!` because we want them merged together. - re_tracing::profile_scope!( - "query_archetype_with_history", - format!("cached=true arch={} pov={} comp={}", A::name(), $N, $M) - ); - - let query = RangeQuery::new(*timeline, visible_history.time_range(*time)); - self.[]::( - store, - &query.clone().into(), - ent_path, - f, - ) - } - } } - }; - - // TODO(cmc): Supporting N>1 generically is quite painful due to limitations in declarative macros, - // not that we care at the moment. - (for N=1, M=$M:expr) => { - seq!(COMP in 1..=$M { - impl_query_archetype_with_history!(for N=1, M=$M => povs=[R1] comps=[#(C~COMP)*]); - }); - }; -} - -impl Caches { - seq!(NUM_COMP in 0..10 { - impl_query_archetype_with_history!(for N=1, M=NUM_COMP); - }); -} diff --git a/crates/re_query_cache/src/range.rs b/crates/re_query_cache/src/range.rs deleted file mode 100644 index 39ffcc07fd911..0000000000000 --- a/crates/re_query_cache/src/range.rs +++ /dev/null @@ -1,334 +0,0 @@ -use paste::paste; -use seq_macro::seq; - -use re_data_store::{DataStore, RangeQuery, TimeInt}; -use re_log_types::{EntityPath, TimeRange, Timeline}; -use re_types_core::{components::InstanceKey, Archetype, Component, SizeBytes}; - -use crate::{CacheBucket, Caches}; - -// --- Data structures --- - -/// Caches the results of `Range` queries. -#[derive(Default)] -pub struct RangeCache { - /// All temporal data, organized by _data_ time. - /// - /// Query time is irrelevant for range queries. - // - // TODO(#4810): bucketize - pub per_data_time: CacheBucket, - - /// For debugging purposes. - pub(crate) timeline: Timeline, -} - -impl std::fmt::Debug for RangeCache { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { - per_data_time, - timeline, - } = self; - - let mut strings = Vec::new(); - - let mut data_time_min = TimeInt::MAX; - let mut data_time_max = TimeInt::MIN; - - if !per_data_time.is_empty() { - data_time_min = TimeInt::min( - data_time_min, - per_data_time.data_times.front().map(|(t, _)| *t).unwrap(), - ); - data_time_max = TimeInt::max( - data_time_max, - per_data_time.data_times.back().map(|(t, _)| *t).unwrap(), - ); - } - - strings.push(format!( - "{} ({})", - timeline - .typ() - .format_range_utc(TimeRange::new(data_time_min, data_time_max)), - re_format::format_bytes((per_data_time.total_size_bytes) as _), - )); - strings.push(indent::indent_all_by(2, format!("{per_data_time:?}"))); - - f.write_str(&strings.join("\n").replace("\n\n", "\n")) - } -} - -impl SizeBytes for RangeCache { - #[inline] - fn heap_size_bytes(&self) -> u64 { - let Self { - per_data_time, - timeline: _, - } = self; - - per_data_time.total_size_bytes - } -} - -impl RangeCache { - /// Removes everything from the cache that corresponds to a time equal or greater than the - /// specified `threshold`. - /// - /// Reminder: invalidating static data is the same as invalidating everything, so just reset - /// the `RangeCache` entirely in that case. - /// - /// Returns the number of bytes removed. - #[inline] - pub fn truncate_at_time(&mut self, threshold: TimeInt) -> u64 { - let Self { - per_data_time, - timeline: _, - } = self; - - per_data_time.truncate_at_time(threshold) - } -} - -impl RangeCache { - /// Given a `query`, returns N reduced queries that are sufficient to fill the missing data - /// on both the front & back sides of the cache. - #[inline] - pub fn compute_queries(&self, query: &RangeQuery) -> impl Iterator { - let front = self.compute_front_query(query); - let back = self.compute_back_query(query); - front.into_iter().chain(back) - } - - /// Given a `query`, returns a reduced query that is sufficient to fill the missing data - /// on the front side of the cache, or `None` if all the necessary data is already - /// cached. - pub fn compute_front_query(&self, query: &RangeQuery) -> Option { - let mut reduced_query = query.clone(); - - if self.per_data_time.is_empty() { - return Some(reduced_query); - } - - if let Some(bucket_time_range) = self.per_data_time.time_range() { - reduced_query.range.set_max(i64::min( - reduced_query.range.max().as_i64(), - bucket_time_range.min().as_i64().saturating_sub(1), - )); - } else { - return Some(reduced_query); - } - - if reduced_query.range.max() < reduced_query.range.min() { - return None; - } - - Some(reduced_query) - } - - /// Given a `query`, returns a reduced query that is sufficient to fill the missing data - /// on the back side of the cache, or `None` if all the necessary data is already - /// cached. - pub fn compute_back_query(&self, query: &RangeQuery) -> Option { - let mut reduced_query = query.clone(); - - if let Some(bucket_time_range) = self.per_data_time.time_range() { - reduced_query.range.set_min(i64::max( - reduced_query.range.min().as_i64(), - bucket_time_range.max().as_i64().saturating_add(1), - )); - } else { - return Some(reduced_query); - } - - if reduced_query.range.max() < reduced_query.range.min() { - return None; - } - - Some(reduced_query) - } -} - -// --- Queries --- - -macro_rules! impl_query_archetype_range { - (for N=$N:expr, M=$M:expr => povs=[$($pov:ident)+] comps=[$($comp:ident)*]) => { paste! { - #[doc = "Cached implementation of [`re_query::query_archetype`] and [`re_query::range_archetype`]"] - #[doc = "(combined) for `" $N "` point-of-view components and `" $M "` optional components."] - #[allow(non_snake_case)] - pub fn []<'a, A, $($pov,)+ $($comp,)* F>( - &self, - store: &'a DataStore, - query: &RangeQuery, - entity_path: &'a EntityPath, - mut f: F, - ) -> ::re_query::Result<()> - where - A: Archetype + 'a, - $($pov: Component,)+ - $($comp: Component,)* - F: FnMut( - std::ops::Range, - ( - &'_ std::collections::VecDeque<(re_data_store::TimeInt, re_log_types::RowId)>, - &'_ crate::FlatVecDeque, - $(&'_ crate::FlatVecDeque<$pov>,)+ - $(Option<&'_ crate::FlatVecDeque>>,)* - ) - ), - { - let range_results = | - bucket: &crate::CacheBucket, - time_range: TimeRange, - f: &mut F, - | -> crate::Result<()> { - re_tracing::profile_scope!("iter"); - - // Yield the static data that's available first. - let static_range = bucket.static_range(); - f( - static_range, - ( - &bucket.data_times, - &bucket.pov_instance_keys, - $(bucket.component::<$pov>() - .ok_or_else(|| re_query::ComponentNotFoundError(<$pov>::name()))?,)+ - $(bucket.component_opt::<$comp>(),)* - ) - ); - - let entry_range = bucket.entry_range(time_range); - f( - entry_range, - ( - &bucket.data_times, - &bucket.pov_instance_keys, - $(bucket.component::<$pov>() - .ok_or_else(|| re_query::ComponentNotFoundError(<$pov>::name()))?,)+ - $(bucket.component_opt::<$comp>(),)* - ) - ); - - Ok(()) - }; - - fn upsert_results<'a, A, $($pov,)+ $($comp,)*>( - arch_views: impl Iterator>, - bucket: &mut crate::CacheBucket, - ) -> crate::Result - where - A: Archetype + 'a, - $($pov: Component,)+ - $($comp: Component,)* - { - re_tracing::profile_scope!("fill"); - - // Grabbing the current time is quite costly on web. - #[cfg(not(target_arch = "wasm32"))] - let now = web_time::Instant::now(); - - #[cfg(not(target_arch = "wasm32"))] - let mut added_entries = 0u64; - - let mut added_size_bytes = 0u64; - - for arch_view in arch_views { - let data_time = arch_view.data_time(); - - if bucket.contains_data_row(data_time, arch_view.primary_row_id()) { - continue; - } - - added_size_bytes += bucket.[]::(data_time, &arch_view)?; - - #[cfg(not(target_arch = "wasm32"))] - { - added_entries += 1; - } - } - - #[cfg(not(target_arch = "wasm32"))] - if added_entries > 0 { - let elapsed = now.elapsed(); - ::re_log::trace!( - archetype=%A::name(), - added_size_bytes, - "cached {added_entries} entries in {elapsed:?} ({:0.3} entries/s)", - added_entries as f64 / elapsed.as_secs_f64() - ); - } - - Ok(added_size_bytes) - } - - let upsert_callback = |query: &RangeQuery, range_cache: &mut crate::RangeCache| -> crate::Result<()> { - re_tracing::profile_scope!("range", format!("{query:?}")); - - let mut query = query.clone(); - query.range.set_min(TimeInt::max(TimeInt::MIN, query.range.min())); - - for reduced_query in range_cache.compute_queries(&query) { - // NOTE: `+ 1` because we always grab the instance keys. - let arch_views = ::re_query::range_component_set::( - store, &reduced_query, entity_path, - &[$(<$pov>::name(),)+], - [::name(), $(<$pov>::name(),)+ $(<$comp>::name(),)*], - ); - upsert_results::(arch_views, &mut range_cache.per_data_time)?; - } - - Ok(()) - }; - - let iter_callback = |query: &RangeQuery, range_cache: &crate::RangeCache, f: &mut F| -> crate::Result<()> { - re_tracing::profile_scope!("range", format!("{query:?}")); - - // We don't bother implementing the slow path here (busy write lock), as that would - // require adding a bunch more complexity in order to know whether a range query is - // already cached (how can you know whether `TimeInt::MAX` is cached? you need to - // clamp queries based on store metadata first, etc). - // - // We can add the extra complexity if this proves to be glitchy in real-world - // scenarios -- otherwise all of this is giant hack meant to go away anyhow. - - let mut query = query.clone(); - query.range.set_min(TimeInt::max(TimeInt::MIN, query.range.min())); - - if !range_cache.per_data_time.is_empty() { - range_results(&range_cache.per_data_time, query.range, f)?; - } - - Ok(()) - }; - - let (res1, res2) = self.with_range::( - store, - entity_path.clone(), - query, - |range_cache| upsert_callback(query, range_cache), - |range_cache| iter_callback(query, range_cache, &mut f), - ); - - if let Some(res1) = res1 { - res1?; - } - res2?; - - Ok(()) - } } - }; - - // TODO(cmc): Supporting N>1 generically is quite painful due to limitations in declarative macros, - // not that we care at the moment. - (for N=1, M=$M:expr) => { - seq!(COMP in 1..=$M { - impl_query_archetype_range!(for N=1, M=$M => povs=[R1] comps=[#(C~COMP)*]); - }); - }; -} - -impl Caches { - seq!(NUM_COMP in 0..10 { - impl_query_archetype_range!(for N=1, M=NUM_COMP); - }); -} diff --git a/crates/re_query_cache/tests/latest_at.rs b/crates/re_query_cache/tests/latest_at.rs deleted file mode 100644 index e05d07c05adcd..0000000000000 --- a/crates/re_query_cache/tests/latest_at.rs +++ /dev/null @@ -1,524 +0,0 @@ -//! Contains: -//! - A 1:1 port of the tests in `crates/re_query/tests/archetype_query_tests.rs`, with caching enabled. -//! - Invalidation tests. - -use itertools::Itertools as _; - -use re_data_store::{DataStore, LatestAtQuery, StoreSubscriber}; -use re_log_types::{ - build_frame_nr, - example_components::{MyColor, MyPoint, MyPoints}, - DataRow, EntityPath, RowId, TimePoint, -}; -use re_query_cache::Caches; -use re_types_core::{components::InstanceKey, Loggable as _}; - -// --- - -#[test] -fn simple_query() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "point"; - let timepoint = [build_frame_nr(123)]; - - // Create some positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timepoint, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn static_query() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "point"; - let timepoint = [build_frame_nr(123)]; - - // Create some positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance.. static_! - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - TimePoint::default(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn no_instance_join_query() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "point"; - let timepoint = [build_frame_nr(123)]; - - // Create some positions with an implicit instance - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign them colors with explicit instances - let colors = vec![MyColor::from_rgb(255, 0, 0), MyColor::from_rgb(0, 255, 0)]; - let row = DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, colors).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn missing_column_join_query() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "point"; - let timepoint = [build_frame_nr(123)]; - - // Create some positions with an implicit instance - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn splatted_query() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "point"; - let timepoint = [build_frame_nr(123)]; - - // Create some positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timepoint, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign all of them a color via splat - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timepoint, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(timepoint[0].0, timepoint[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[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 mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - // Create some positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(1, 2, 3)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify present --- - - // Modify the PoV component - let positions = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(4, 5, 6), MyColor::from_rgb(7, 8, 9)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint, - 2, - colors, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify past --- - - // Modify the PoV component - let positions = vec![MyPoint::new(100.0, 200.0), MyPoint::new(300.0, 400.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - past_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(10, 11, 12), MyColor::from_rgb(13, 14, 15)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, past_data_timepoint, 2, colors) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify future --- - - // Modify the PoV component - let positions = vec![MyPoint::new(1000.0, 2000.0), MyPoint::new(3000.0, 4000.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - future_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(16, 17, 18)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, future_data_timepoint, 1, colors) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - }; - - let timeless = 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(), - timeless, - [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 = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "points"; - - let timeless = TimePoint::default(); - let frame2 = [build_frame_nr(2)]; - let frame3 = [build_frame_nr(3)]; - - let query_time = [build_frame_nr(9999)]; - - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timeless, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame2, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 0, 255)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame3, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 255, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame3, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn invalidation_timeless() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "points"; - - let timeless = TimePoint::default(); - - let query_time = [build_frame_nr(9999)]; - - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = DataRow::from_cells1_sized(RowId::new(), entity_path, timeless.clone(), 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timeless.clone(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 0, 255)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timeless, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - let query = re_data_store::LatestAtQuery::new(query_time[0].0, query_time[0].1); - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -// --- - -fn insert_and_react(store: &mut DataStore, caches: &mut Caches, row: &DataRow) { - caches.on_events(&[store.insert_row(row).unwrap()]); -} - -fn query_and_compare( - caches: &Caches, - store: &DataStore, - query: &LatestAtQuery, - entity_path: &EntityPath, -) { - re_log::setup_logging(); - - for _ in 0..3 { - let mut cached_data_time = None; - let mut cached_row_id = None; - let mut cached_instance_keys = Vec::new(); - let mut cached_positions = Vec::new(); - let mut cached_colors = Vec::new(); - caches - .query_archetype_pov1_comp1::( - store, - &query.clone().into(), - entity_path, - |((data_time, row_id), instance_keys, positions, colors)| { - cached_data_time = Some(data_time); - cached_row_id = Some(row_id); - cached_instance_keys.extend(instance_keys.iter().copied()); - cached_positions.extend(positions.iter().copied()); - cached_colors - .extend(re_query_cache::iter_or_repeat_opt(colors, positions.len())); - }, - ) - .unwrap(); - - let expected = re_query::query_archetype::(store, query, entity_path).unwrap(); - let expected_data_time = expected.data_time(); - let expected_row_id = expected.primary_row_id(); - - let expected_instance_keys = expected.iter_instance_keys().collect_vec(); - let expected_positions = expected - .iter_required_component::() - .unwrap() - .collect_vec(); - let expected_colors = expected - .iter_optional_component::() - .unwrap() - .collect_vec(); - - // Keep this around for the next unlucky chap. - // eprintln!("i={i} (expected={expected_data_time:?}, cached={cached_data_time:?})"); - - similar_asserts::assert_eq!(Some(expected_data_time), cached_data_time); - similar_asserts::assert_eq!(Some(expected_row_id), cached_row_id); - similar_asserts::assert_eq!(expected_instance_keys, cached_instance_keys); - similar_asserts::assert_eq!(expected_positions, cached_positions); - similar_asserts::assert_eq!(expected_colors, cached_colors); - } -} diff --git a/crates/re_query_cache/tests/range.rs b/crates/re_query_cache/tests/range.rs deleted file mode 100644 index 8dd3a76cbbb82..0000000000000 --- a/crates/re_query_cache/tests/range.rs +++ /dev/null @@ -1,659 +0,0 @@ -//! Contains: -//! - A 1:1 port of the tests in `crates/re_query/tests/archetype_range_tests.rs`, with caching enabled. -//! - Invalidation tests. - -use itertools::Itertools as _; - -use re_data_store::{DataStore, RangeQuery, StoreSubscriber}; -use re_log_types::{ - build_frame_nr, - example_components::{MyColor, MyLabel, MyPoint, MyPoints}, - DataRow, EntityPath, RowId, TimeInt, TimePoint, TimeRange, -}; -use re_query_cache::Caches; -use re_types::components::InstanceKey; -use re_types_core::Loggable as _; - -// --- - -#[test] -fn simple_range() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path: EntityPath = "point".into(); - - let timepoint1 = [build_frame_nr(123)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path.clone(), timepoint1, 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint1, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint2 = [build_frame_nr(223)]; - { - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(0)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint2, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint3 = [build_frame_nr(323)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path.clone(), timepoint3, 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - // --- First test: `(timepoint1, timepoint3]` --- - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1.as_i64() + 1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); - - // --- Second test: `[timepoint1, timepoint3]` --- - - // The inclusion of `timepoint1` means latest-at semantics will _not_ kick in! - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); -} - -#[test] -fn static_range() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path: EntityPath = "point".into(); - - let timepoint1 = [build_frame_nr(123)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let mut row = - DataRow::from_cells1(RowId::new(), entity_path.clone(), timepoint1, 2, positions) - .unwrap(); - row.compute_all_size_bytes(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint1, - 1, - (color_instances.clone(), colors.clone()), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Insert timelessly too! - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - TimePoint::default(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint2 = [build_frame_nr(223)]; - { - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(0)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint2, - 1, - (color_instances.clone(), colors.clone()), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Insert static_ too! - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - TimePoint::default(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint3 = [build_frame_nr(323)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path.clone(), timepoint3, 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - // --- First test: `(timepoint1, timepoint3]` --- - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1.as_i64() + 1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); - - // --- Second test: `[timepoint1, timepoint3]` --- - - // The inclusion of `timepoint1` means latest-at semantics will fall back to timeless data! - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); - - // --- Third test: `[-inf, +inf]` --- - - let query = - re_data_store::RangeQuery::new(timepoint1[0].0, TimeRange::new(TimeInt::MIN, TimeInt::MAX)); - - query_and_compare(&caches, &store, &query, &entity_path); -} - -#[test] -fn simple_splatted_range() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path: EntityPath = "point".into(); - - let timepoint1 = [build_frame_nr(123)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path.clone(), timepoint1, 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint1, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint2 = [build_frame_nr(223)]; - { - // Assign one of them a color with a splatted instance - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 255, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path.clone(), - timepoint2, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - let timepoint3 = [build_frame_nr(323)]; - { - // Create some Positions with implicit instances - let positions = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path.clone(), timepoint3, 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - } - - // --- First test: `(timepoint1, timepoint3]` --- - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1.as_i64() + 1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); - - // --- Second test: `[timepoint1, timepoint3]` --- - - // The inclusion of `timepoint1` means latest-at semantics will _not_ kick in! - - let query = re_data_store::RangeQuery::new( - timepoint1[0].0, - TimeRange::new(timepoint1[0].1, timepoint3[0].1), - ); - - query_and_compare(&caches, &store, &query, &entity_path); -} - -#[test] -fn invalidation() { - let entity_path = "point"; - - let test_invalidation = |query: RangeQuery, - present_data_timepoint: TimePoint, - past_data_timepoint: TimePoint, - future_data_timepoint: TimePoint| { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - // Create some positions with implicit instances - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - // Assign one of them a color with an explicit instance - let color_instances = vec![InstanceKey(1)]; - let colors = vec![MyColor::from_rgb(1, 2, 3)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify present --- - - // Modify the PoV component - let positions = vec![MyPoint::new(10.0, 20.0), MyPoint::new(30.0, 40.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(4, 5, 6), MyColor::from_rgb(7, 8, 9)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - present_data_timepoint, - 2, - colors, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify past --- - - // Modify the PoV component - let positions = vec![MyPoint::new(100.0, 200.0), MyPoint::new(300.0, 400.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - past_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(10, 11, 12), MyColor::from_rgb(13, 14, 15)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - past_data_timepoint.clone(), - 2, - colors, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // --- Modify future --- - - // Modify the PoV component - let positions = vec![MyPoint::new(1000.0, 2000.0), MyPoint::new(3000.0, 4000.0)]; - let row = DataRow::from_cells1_sized( - RowId::new(), - entity_path, - future_data_timepoint.clone(), - 2, - positions, - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - // Modify the optional component - let colors = vec![MyColor::from_rgb(16, 17, 18)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, future_data_timepoint, 1, colors) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - }; - - let timeless = 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( - RangeQuery::new(frame_123.0, TimeRange::EVERYTHING), - [frame_123].into(), - [frame_122].into(), - [frame_124].into(), - ); - - test_invalidation( - RangeQuery::new(frame_123.0, TimeRange::EVERYTHING), - [frame_123].into(), - timeless, - [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() { - // TODO(cmc): this test is coming back in the next PR. - if true { - return; - } - - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "points"; - - let timeless = TimePoint::default(); - let frame2 = [build_frame_nr(2)]; - let frame3 = [build_frame_nr(3)]; - - let query = re_data_store::RangeQuery::new(frame2[0].0, TimeRange::EVERYTHING); - - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = - DataRow::from_cells1_sized(RowId::new(), entity_path, timeless, 2, positions).unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame2, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 0, 255)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame3, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 255, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - frame3, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -#[test] -fn invalidation_static() { - let mut store = DataStore::new( - re_log_types::StoreId::random(re_log_types::StoreKind::Recording), - InstanceKey::name(), - Default::default(), - ); - let mut caches = Caches::new(&store); - - let entity_path = "points"; - - let timeless = TimePoint::default(); - - let frame0 = [build_frame_nr(TimeInt::ZERO)]; - let query = re_data_store::RangeQuery::new(frame0[0].0, TimeRange::EVERYTHING); - - let positions = vec![MyPoint::new(1.0, 2.0), MyPoint::new(3.0, 4.0)]; - let row = DataRow::from_cells1_sized(RowId::new(), entity_path, timeless.clone(), 2, positions) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(255, 0, 0)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timeless.clone(), - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); - - let color_instances = vec![InstanceKey::SPLAT]; - let colors = vec![MyColor::from_rgb(0, 0, 255)]; - let row = DataRow::from_cells2_sized( - RowId::new(), - entity_path, - timeless, - 1, - (color_instances, colors), - ) - .unwrap(); - insert_and_react(&mut store, &mut caches, &row); - - query_and_compare(&caches, &store, &query, &entity_path.into()); -} - -// --- - -fn insert_and_react(store: &mut DataStore, caches: &mut Caches, row: &DataRow) { - caches.on_events(&[store.insert_row(row).unwrap()]); -} - -fn query_and_compare( - caches: &Caches, - store: &DataStore, - query: &RangeQuery, - entity_path: &EntityPath, -) { - for _ in 0..3 { - let mut cached_data_times = Vec::new(); - let mut cached_instance_keys = Vec::new(); - let mut cached_positions = Vec::new(); - let mut cached_colors = Vec::new(); - caches - .query_archetype_pov1_comp2::( - store, - &query.clone().into(), - entity_path, - |((data_time, _), instance_keys, positions, colors, _)| { - cached_data_times.push(data_time); - cached_instance_keys.push(instance_keys.to_vec()); - cached_positions.push(positions.to_vec()); - cached_colors.push( - re_query_cache::iter_or_repeat_opt(colors, positions.len()) - .copied() - .collect_vec(), - ); - }, - ) - .unwrap(); - - let mut expected_data_times = Vec::new(); - let mut expected_instance_keys = Vec::new(); - let mut expected_positions = Vec::new(); - let mut expected_colors = Vec::new(); - let expected = re_query::range_archetype::( - store, - query, - entity_path, - ); - for arch_view in expected { - expected_data_times.push(arch_view.data_time()); - expected_instance_keys.push(arch_view.iter_instance_keys().collect_vec()); - expected_positions.push( - arch_view - .iter_required_component::() - .unwrap() - .collect_vec(), - ); - expected_colors.push( - arch_view - .iter_optional_component::() - .unwrap() - .collect_vec(), - ); - } - - // Keep this around for the next unlucky chap. - // eprintln!("(expected={expected_data_times:?}, cached={cached_data_times:?})"); - eprintln!("{}", store.to_data_table().unwrap()); - - similar_asserts::assert_eq!(expected_data_times, cached_data_times); - similar_asserts::assert_eq!(expected_instance_keys, cached_instance_keys); - similar_asserts::assert_eq!(expected_positions, cached_positions); - similar_asserts::assert_eq!(expected_colors, cached_colors); - } -}