use std::error::Error;
use std::ops::Range;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use arrow_array::{
ArrayRef, Float64Array, Int64Array, RecordBatch, StringArray, TimestampMillisecondArray,
};
use densa_arrow::arrow_schema::{DataType, Field, Schema, SchemaRef, TimeUnit};
use densa_arrow::{FetchError, PageError, PageQuery, PageSource, PagedModel};
use densa_grid::{Column, GridModel, SortOrder};
use densa_ui::{Align, Style, Theme, Ui, WidgetId};
use densa_widgets::{DataGrid, Label, Panel};
use densa_window::{App, Host, WindowOptions};
const ROWS: u64 = 1_000_000_000;
const SENSORS: u64 = 500;
const SPREAD: u64 = 7_919;
const SHIFT: u64 = 123_457;
const FIRST_MS: i64 = 1_700_000_000_000;
const EVERY_MS: i64 = 100;
type Grid = DataGrid<PagedModel>;
struct Readings {
latency: Duration,
unspread: u64,
}
impl Readings {
fn new(latency: Duration) -> Self {
Self {
latency,
unspread: inverse(SPREAD, ROWS),
}
}
fn schema() -> SchemaRef {
Arc::new(Schema::new(vec![
Field::new("id", DataType::Int64, false),
Field::new("sensor", DataType::Utf8, false),
Field::new(
"time",
DataType::Timestamp(TimeUnit::Millisecond, Some("+00:00".into())),
false,
),
Field::new("value", DataType::Int64, false),
Field::new("celsius", DataType::Float64, false),
]))
}
fn id_at(&self, query: &PageQuery, position: u64) -> u64 {
let Some(key) = query.sort().first() else {
return position;
};
let k = if key.is_descending() {
ROWS - 1 - position
} else {
position
};
match key.column() {
"value" | "celsius" => mul_mod(k + ROWS - SHIFT, self.unspread, ROWS),
"sensor" => {
let per = ROWS / SENSORS;
k / per + SENSORS * (k % per)
}
_ => k,
}
}
}
impl PageSource for Readings {
fn row_count(&mut self, _: &PageQuery) -> Result<u64, PageError> {
thread::sleep(self.latency);
Ok(ROWS)
}
fn page(
&mut self,
query: &PageQuery,
offset: u64,
limit: usize,
) -> Result<Vec<RecordBatch>, PageError> {
thread::sleep(self.latency);
let end = offset.saturating_add(limit as u64).min(ROWS);
let ids: Vec<u64> = (offset..end).map(|at| self.id_at(query, at)).collect();
let id_column: Vec<i64> = ids.iter().map(|&id| id as i64).collect();
let values: Vec<i64> = ids.iter().map(|&id| value_of(id) as i64).collect();
let sensors: Vec<String> = ids
.iter()
.map(|id| format!("s-{:03}", id % SENSORS))
.collect();
let times: Vec<i64> = id_column
.iter()
.map(|&id| FIRST_MS + id * EVERY_MS)
.collect();
let celsius: Vec<f64> = values
.iter()
.map(|&v| -40.0 + v as f64 / ROWS as f64 * 100.0)
.collect();
let columns: Vec<ArrayRef> = vec![
Arc::new(Int64Array::from(id_column)),
Arc::new(StringArray::from(sensors)),
Arc::new(TimestampMillisecondArray::from(times).with_timezone("+00:00")),
Arc::new(Int64Array::from(values)),
Arc::new(Float64Array::from(celsius)),
];
Ok(vec![RecordBatch::try_new(Self::schema(), columns)?])
}
}
fn value_of(id: u64) -> u64 {
(mul_mod(id, SPREAD, ROWS) + SHIFT) % ROWS
}
fn mul_mod(a: u64, b: u64, m: u64) -> u64 {
(u128::from(a) * u128::from(b) % u128::from(m)) as u64
}
fn inverse(a: u64, m: u64) -> u64 {
let (mut old, mut r) = (i128::from(a), i128::from(m));
let (mut s, mut next) = (1_i128, 0_i128);
while r != 0 {
let q = old / r;
(old, r) = (r, old - q * r);
(s, next) = (next, s - q * next);
}
s.rem_euclid(i128::from(m)) as u64
}
struct PagedApp {
light: bool,
latency: Duration,
close_at: Option<Instant>,
ids: Option<Ids>,
status: Status,
}
struct Ids {
grid: WidgetId,
status: WidgetId,
}
#[derive(Debug, Default)]
struct Status {
rows: Option<u64>,
pages: usize,
shown: Range<usize>,
sort: Option<(usize, SortOrder)>,
error: Option<FetchError>,
}
impl Status {
fn of(grid: &Grid) -> Self {
let model = grid.model();
Self {
rows: model.row_count(),
pages: model.pages_held(),
shown: shown(grid),
sort: grid.sort_order(),
error: model.error().cloned(),
}
}
fn tells(&self, grid: &Grid) -> bool {
let model = grid.model();
self.rows == model.row_count()
&& self.pages == model.pages_held()
&& self.shown == shown(grid)
&& self.sort == grid.sort_order()
&& self.error.as_ref() == model.error()
}
fn text(&self, columns: &[Column], page_rows: usize) -> String {
if let Some(error) = &self.error {
return error.to_string();
}
let Some(rows) = self.rows else {
return "Counting the rows...".to_owned();
};
let shown = match self.shown.clone() {
shown if shown.is_empty() => "no rows shown".to_owned(),
shown => format!(
"rows {} to {} shown",
grouped(shown.start as u64),
grouped(shown.end as u64 - 1)
),
};
let sorted = self
.sort
.and_then(|(column, order)| {
let name = &columns.get(column)?.title;
let way = match order {
SortOrder::Descending => "largest first",
_ => "smallest first",
};
Some(format!(" · sorted by {name}, {way}"))
})
.unwrap_or_default();
format!(
"{} rows · {} pages of {} held · {shown}{sorted}",
grouped(rows),
self.pages,
page_rows
)
}
}
fn shown(grid: &Grid) -> Range<usize> {
let first = grid.first_row();
first..(first + grid.visible_rows()).min(grid.model().rows())
}
fn grouped(n: u64) -> String {
let digits = n.to_string();
let mut text = String::new();
for (i, digit) in digits.chars().enumerate() {
if i > 0 && (digits.len() - i).is_multiple_of(3) {
text.push(',');
}
text.push(digit);
}
text
}
impl App for PagedApp {
fn build(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> Result<(), Box<dyn Error>> {
if self.light {
ui.set_theme(Theme::light());
}
let root = ui.root();
let bar = Style::row()
.padding(8.0)
.gap(12.0)
.shrink(0.0)
.align_items(Align::Center);
let toolbar = ui.add(root, Panel::new(), bar)?;
ui.add(
toolbar,
Label::new("A billion rows").with_size(15.0),
Style::row(),
)?;
let hint = "Scroll, drag the bar or press End: only the rows on screen are fetched. \
Click a title to sort.";
ui.add(toolbar, Label::new(hint).dim(), Style::row())?;
let waker = host.waker();
let source = Readings::new(self.latency);
let mut model = PagedModel::new(Readings::schema(), source, move || {
waker.wake();
})?;
model.set_decimals(4, Some(2))?;
let grid = Grid::new(model).with_frozen_columns(1);
let grid = ui.add(root, grid, Grid::style())?;
let bar = Style::row().padding(6.0).shrink(0.0);
let bar = ui.add(root, Panel::new(), bar)?;
let text = self.status.text(&[], 0);
let status = ui.add(bar, Label::new(text).dim(), Style::row())?;
self.ids = Some(Ids { grid, status });
Ok(())
}
fn frame(&mut self, ui: &mut Ui, host: &mut Host<'_>) -> bool {
if self.close_at.is_some_and(|at| Instant::now() >= at) {
host.close();
return false;
}
let Some(ids) = &self.ids else {
return false;
};
ui.update::<Grid, _>(ids.grid, |grid, cx| grid.model_mut(cx).prepare());
let text = match ui.widget::<Grid>(ids.grid) {
Some(grid) if !self.status.tells(grid) => {
self.status = Status::of(grid);
let model = grid.model();
Some(self.status.text(model.columns(), model.page_rows()))
}
_ => None,
};
if let Some(text) = text {
ui.update::<Label, _>(ids.status, |label, cx| label.set_text(cx, text));
}
self.close_at.is_some()
}
}
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = std::env::args().collect();
let value = |flag: &str| {
args.iter()
.skip_while(|a| *a != flag)
.nth(1)
.map(|s| s.parse::<u64>())
.transpose()
};
let light = args.iter().any(|a| a == "--light");
let seconds = value("--seconds")?;
let latency = value("--latency")?.unwrap_or(40);
let app = PagedApp {
light,
latency: Duration::from_millis(latency),
close_at: seconds.map(|s| Instant::now() + Duration::from_secs(s)),
ids: None,
status: Status::default(),
};
densa_window::run(
WindowOptions::new("densa: a billion rows").with_size(1000.0, 700.0),
app,
)?;
Ok(())
}