use std::ops::Range;
use std::sync::Arc;
use crate::data::{Column, Field, FramePage, PAGE_ROWS, PageId, Run, Samples, Version, detail};
use crate::derive::Beside;
use crate::expr::compile::{ColumnUse, Program, Reads, Words};
use crate::gpu::Inner;
use crate::gpu::arena::{Content, PIECE_POINTS, PageKey, path_pieces, path_points};
use crate::gpu::shaders as wgsl;
use crate::{Data, Name, Table};
const WORDS: usize = wgsl::COLUMN_WORDS as usize;
#[derive(Clone, PartialEq, Debug)]
enum Stamp {
Missing,
Field(Version),
Sampled(Version, Option<u64>, u32),
Trace(Version, Plan),
Join(Version, Version),
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) struct Window {
pub(crate) across: Option<[f64; 2]>,
pub(crate) columns: u32,
pub(crate) steps: u32,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct Plan {
pub(crate) level: u32,
pub(crate) runs: Vec<(Run, u32)>,
pub(crate) points: u32,
steps: u32,
}
impl Plan {
pub(crate) fn entry(&self, point: u32) -> Option<(u32, u64, usize)> {
let i = self.runs.partition_point(|(_, first)| *first <= point).checked_sub(1)?;
let (run, first) = &self.runs[i];
let (k, n) = (point - first, run.len() as u32);
if run.level > 0 {
return (k < 2 * n).then(|| (run.level, run.entries.start + u64::from(k / 2), (k % 2) as usize));
}
let (up, count) = match self.steps {
wgsl::STEP_AFTER => (k / 2, 2 * n - 1),
wgsl::STEP_BEFORE => (k.div_ceil(2), 2 * n - 1),
_ => (k, n),
};
(k < count).then(|| (0, run.entries.start + u64::from(up), 0))
}
}
struct ColumnTable {
stamp: Stamp,
descriptor: [u32; WORDS],
words: Vec<u32>,
pins: Vec<PageKey>,
plan: Option<Plan>,
points: u32,
pages: Option<Vec<PageId>>,
}
struct Join {
of: (Name, Name),
structure: (Version, Version),
pages: Vec<(PageId, Arc<[u32]>)>,
}
#[derive(Default)]
pub(crate) struct Tables {
current: Vec<ColumnTable>,
pending: Vec<(usize, ColumnTable)>,
frozen: Option<ColumnTable>,
joins: Vec<Join>,
synced: bool,
structure: Option<crate::data::Version>,
generation: u64,
}
pub(crate) enum Synced {
Same,
Waiting,
Changed { columns: u64, blocks: Option<Vec<u32>> },
}
pub(crate) struct Source<'a> {
pub(crate) data: &'a Data,
pub(crate) table: &'a Table,
pub(crate) derived: Beside<'a>,
}
enum Use<'a> {
Field { column: &'a ColumnUse, join: u32 },
Join(&'a (Name, Name)),
}
impl Tables {
#[allow(clippy::too_many_arguments, reason = "one sync of a mark's tables")]
pub(crate) fn sync(
&mut self,
program: &Program,
source: &Source<'_>,
all: &[Data],
at: f64,
window: Window,
geometry: u32,
gpu: &mut Inner,
) -> Synced {
let joins = joins(program);
let fields = program.columns.iter().map(|column| {
let join = column.of.as_ref().map_or(wgsl::NONE, |of| {
(program.columns.len() + joins.iter().position(|j| *j == of).expect("listed")) as u32
});
Use::Field { column, join }
});
let uses: Vec<Use<'_>> = fields.chain(joins.iter().map(|of| Use::Join(of))).collect();
let reshape = !self.synced
|| self.current.len() != uses.len()
|| (uses.is_empty() && self.structure != Some(source.table.structure));
self.structure = Some(source.table.structure);
let mut rebuilt = Vec::new();
let mut ready = true;
for (i, use_) in uses.iter().enumerate() {
let stamp = match use_ {
Use::Field { column, .. } => stamp(column, source, all, at, window),
Use::Join(of) => match joined(of, all) {
Some(other) => Stamp::Join(source.table.structure, other.structure),
None => Stamp::Missing,
},
};
if !reshape && self.current[i].stamp == stamp {
continue;
}
let mut builder = Builder {
words: Vec::new(),
pins: Vec::new(),
ready: true,
plan: None,
points: 0,
pages: Some(Vec::new()),
gpu: &mut *gpu,
};
let descriptor = match use_ {
Use::Field { column, join } => {
let mut descriptor = builder.column(column, source, all, at, window);
descriptor[wgsl::COLUMN_JOIN as usize] = *join;
descriptor
}
Use::Join(of) => {
let join = self.join(of, source, all);
let descriptor = builder.join(&join, source.table.len());
self.remember(join);
descriptor
}
};
let Builder { words, pins, ready: column_ready, plan, points, pages, gpu: _ } = builder;
for &key in &pins {
gpu.arena.pin(key);
}
ready &= column_ready;
rebuilt.push((i, ColumnTable { stamp, descriptor, words, pins, plan, points, pages }));
}
for (_, table) in std::mem::take(&mut self.pending) {
unpin(&table.pins, gpu);
}
if rebuilt.is_empty() && !reshape {
return Synced::Same;
}
if !ready {
self.pending = rebuilt;
return Synced::Waiting;
}
self.generation += 1;
let mut blocks = Some(Vec::new());
let changed = if reshape {
self.thaw(gpu);
for table in std::mem::take(&mut self.current) {
unpin(&table.pins, gpu);
}
self.current = rebuilt.into_iter().map(|(_, table)| table).collect();
blocks = None;
u64::MAX
} else {
let mut changed = 0;
for (i, table) in rebuilt {
match (blocks.as_mut(), changed_blocks(&self.current[i], &table)) {
(Some(blocks), Some(more)) => blocks.extend(more),
_ => blocks = None,
}
let old = std::mem::replace(&mut self.current[i], table);
if i as u32 == geometry {
self.thaw(gpu);
self.frozen = Some(old);
} else {
unpin(&old.pins, gpu);
}
changed |= match &uses[i] {
Use::Field { .. } => Reads::column(i),
Use::Join(of) => columns_of(program, of),
};
}
changed
};
self.synced = true;
Synced::Changed { columns: changed, blocks }
}
pub(crate) fn thaw(&mut self, gpu: &mut Inner) {
if let Some(table) = self.frozen.take() {
unpin(&table.pins, gpu);
}
}
pub(crate) fn frozen(&self) -> Option<u32> {
self.frozen.as_ref().map(|_| self.current.len() as u32)
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
pub(crate) fn points(&self, column: u32) -> u32 {
self.current.get(column as usize).map_or(0, |t| t.points)
}
pub(crate) fn holds(&self, column: u32) -> bool {
self.current.get(column as usize).is_some_and(|t| t.stamp != Stamp::Missing)
}
pub(crate) fn synced(&self) -> bool {
self.synced
}
pub(crate) fn plan(&self) -> Option<&Plan> {
self.current.iter().find_map(|t| t.plan.as_ref())
}
pub(crate) fn traced(&self) -> u32 {
self.plan().map_or(0, |plan| plan.points)
}
pub(crate) fn waiting(&self) -> bool {
!self.pending.is_empty()
}
pub(crate) fn len(&self) -> usize {
self.packed().map(|t| WORDS + t.words.len()).sum()
}
fn packed(&self) -> impl Iterator<Item = &ColumnTable> + Clone {
self.current.iter().chain(&self.frozen)
}
pub(crate) fn pack(&self, base: u32, out: &mut Vec<u32>) {
let mut at = base + (self.packed().count() * WORDS) as u32;
for table in self.packed() {
let mut d = table.descriptor;
d[wgsl::COLUMN_TABLE as usize] += at;
if d[wgsl::COLUMN_LOW as usize] != wgsl::NONE {
d[wgsl::COLUMN_LOW as usize] += at;
}
out.extend(d);
at += table.words.len() as u32;
}
for table in self.packed() {
out.extend(&table.words);
}
}
pub(crate) fn release(&mut self, gpu: &mut Inner) {
self.thaw(gpu);
for table in self.current.drain(..).chain(self.pending.drain(..).map(|(_, t)| t)) {
unpin(&table.pins, gpu);
}
self.synced = false;
}
fn join(&self, of: &(Name, Name), source: &Source<'_>, all: &[Data]) -> Join {
let Some(other) = joined(of, all) else {
return Join {
of: of.clone(),
structure: (source.table.structure, source.table.structure),
pages: Vec::new(),
};
};
let structure = (source.table.structure, other.structure);
if let Some(join) = self.joins.iter().find(|j| &j.of == of && j.structure == structure) {
return Join { of: of.clone(), structure, pages: join.pages.clone() };
}
let rows: Vec<u32> = (0..source.table.len())
.map(|r| source.table.ids.key(r).and_then(|key| other.row(key)).map_or(wgsl::NONE, |row| row as u32))
.collect();
let pages = rows.chunks(PAGE_ROWS).map(|chunk| (PageId::next(), Arc::from(chunk))).collect();
Join { of: of.clone(), structure, pages }
}
fn remember(&mut self, join: Join) {
self.joins.retain(|j| j.of != join.of);
self.joins.push(join);
}
}
fn changed_blocks(before: &ColumnTable, now: &ColumnTable) -> Option<Vec<u32>> {
let (Some(old), Some(new)) = (&before.pages, &now.pages) else { return None };
Some((0..old.len().max(new.len())).filter(|&p| old.get(p) != new.get(p)).map(|p| p as u32).collect())
}
fn unpin(pins: &[PageKey], gpu: &mut Inner) {
for &key in pins {
gpu.arena.unpin(key);
}
}
fn joins(program: &Program) -> Vec<&(Name, Name)> {
let mut joins: Vec<&(Name, Name)> = Vec::new();
for of in program.columns.iter().filter_map(|c| c.of.as_ref()) {
if !joins.contains(&of) {
joins.push(of);
}
}
joins
}
fn columns_of(program: &Program, of: &(Name, Name)) -> u64 {
program
.columns
.iter()
.enumerate()
.filter(|(_, c)| c.of.as_ref() == Some(of))
.fold(0, |bits, (i, _)| bits | Reads::column(i))
}
pub(crate) fn table_of<'a>(column: &ColumnUse, source: &Source<'a>, all: &'a [Data]) -> Option<(&'a Name, &'a Table)> {
if column.derived {
let mut beside = source.derived.iter().flatten();
let table = beside.find(|t| t.field_named(&column.name).is_some()).copied().or(source.derived[0])?;
return Some((source.data.name(), table));
}
match &column.of {
None => Some((source.data.name(), source.table)),
Some((data, table)) => {
let data = all.iter().find(|d| d.name() == data)?;
Some((data.name(), data.table(table)?))
}
}
}
fn joined<'a>((data, table): &(Name, Name), all: &'a [Data]) -> Option<&'a Table> {
all.iter().find(|d| d.name() == data)?.table(table)
}
fn plan(samples: &Samples, window: Window) -> Plan {
let frames = window.across.map_or(samples.start()..samples.end(), |across| samples.across(across));
let level = detail(frames.end - frames.start, window.columns);
let (mut runs, mut point, mut last) = (Vec::new(), 0, None);
for run in samples.cover(frames, level) {
let spanned = run.frames();
if last.is_some_and(|end| end != spanned.start) {
point += 1;
}
let n = run.len() as u32;
let points = match (run.level, window.steps) {
(0, 0) => n,
(0, _) => 2 * n - 1,
_ => 2 * n,
};
last = Some(spanned.end);
runs.push((run, point));
point += points;
}
Plan { level, runs, points: point, steps: window.steps }
}
fn stamp(column: &ColumnUse, source: &Source<'_>, all: &[Data], at: f64, window: Window) -> Stamp {
let Some((_, table)) = table_of(column, source, all) else { return Stamp::Missing };
match table.field_named(&column.name) {
None => Stamp::Missing,
Some(Field::Sampled(samples, version)) if column.trace => Stamp::Trace(*version, plan(samples, window)),
Some(Field::Sampled(samples, version)) => {
let frame = samples.frame_at(at);
let t = if column.step { 0.0 } else { frame.map_or(0.0, |f| samples.t(f, at)) };
Stamp::Sampled(*version, frame, t.to_bits())
}
Some(field) => Stamp::Field(field.version()),
}
}
struct Builder<'g> {
words: Vec<u32>,
pins: Vec<PageKey>,
ready: bool,
plan: Option<Plan>,
points: u32,
pages: Option<Vec<PageId>>,
gpu: &'g mut Inner,
}
impl Builder<'_> {
fn column(
&mut self,
column: &ColumnUse,
source: &Source<'_>,
all: &[Data],
at: f64,
window: Window,
) -> [u32; WORDS] {
let mut d = [0; WORDS];
d[wgsl::COLUMN_LOW as usize] = wgsl::NONE;
if column.words == Words::Bits {
d[wgsl::COLUMN_FLAGS as usize] = wgsl::BITS;
}
let Some((data, table)) = table_of(column, source, all) else {
self.pages = None;
return d;
};
d[wgsl::COLUMN_ROWS as usize] = table.len() as u32;
match table.field_named(&column.name) {
Some(Field::Values(values, _)) if column.words == Words::Codes => {
let field = (data, &table.name, &column.name);
self.listed(values);
for page in values.pages() {
let key = PageKey::of(page.id, Words::Codes, false);
let codes = self.gpu.dictionaries.field(field.0, field.1, field.2).codes(page);
let offset =
self.gpu.arena.get(key, page.values.len() as u32, || Content::Computed(codes.to_vec()));
self.push(key, offset);
}
}
Some(Field::Values(values, _)) if column.words == Words::Path => self.path(values),
Some(Field::Values(values, _)) => {
self.listed(values);
self.pages(values, column.words, false);
if column.low {
d[wgsl::COLUMN_LOW as usize] = self.words.len() as u32;
self.pages(values, column.words, true);
}
}
Some(Field::Reference(reference)) => {
self.listed(&reference.rows);
self.pages(&reference.rows, Words::Bits, false);
}
Some(Field::Sampled(samples, _)) if column.trace => {
self.pages = None;
d[wgsl::COLUMN_FLAGS as usize] |= wgsl::SAMPLED;
self.trace(samples, plan(samples, window), table.len());
}
Some(Field::Sampled(samples, _)) => {
self.pages = None;
d[wgsl::COLUMN_FLAGS as usize] |= wgsl::SAMPLED | if column.step { wgsl::STEP } else { 0 };
let frame = samples.frame_at(at);
let t = if column.step { 0.0 } else { frame.map_or(0.0, |f| samples.t(f, at)) };
d[wgsl::COLUMN_T as usize] = t.to_bits();
self.cursors(samples, frame, table.len());
}
None => self.pages = None,
}
d
}
fn join(&mut self, join: &Join, rows: usize) -> [u32; WORDS] {
self.pages = None;
let mut d = [0; WORDS];
d[wgsl::COLUMN_FLAGS as usize] = wgsl::BITS;
d[wgsl::COLUMN_ROWS as usize] = rows as u32;
d[wgsl::COLUMN_LOW as usize] = wgsl::NONE;
d[wgsl::COLUMN_JOIN as usize] = wgsl::NONE;
for (id, words) in &join.pages {
let key = PageKey::of(*id, Words::Bits, false);
let offset = self.gpu.arena.get(key, words.len() as u32, || Content::Computed(words.to_vec()));
self.push(key, offset);
}
d
}
fn listed(&mut self, column: &Column) {
if let Some(pages) = &mut self.pages {
pages.extend(column.pages().iter().map(|page| page.id));
}
}
fn pages(&mut self, column: &Column, words: Words, low: bool) {
for page in column.pages() {
let key = PageKey::of(page.id, words, low);
let values = page.values.clone();
let offset = self.gpu.arena.get(key, values.len() as u32, || Content::Values { values, words, low });
self.push(key, offset);
}
}
fn path(&mut self, column: &Column) {
self.listed(column);
let pages = column.pages();
self.words.resize(3 * pages.len(), 0);
for (p, page) in pages.iter().enumerate() {
let points = path_points(&page.values);
self.points += points as u32;
let mut made: Option<Vec<Vec<u32>>> = None;
let mut blocks = Vec::with_capacity(1 + points.div_ceil(PIECE_POINTS));
for piece in 0..=points.div_ceil(PIECE_POINTS) {
let key = PageKey { page: page.id, words: Words::Path, low: false, piece: piece as u32 };
let offset = self.gpu.arena.get(key, 1, || {
let pieces = made.get_or_insert_with(|| path_pieces(&page.values));
Content::Computed(std::mem::take(&mut pieces[piece]))
});
self.pins.push(key);
self.ready &= offset.is_some();
blocks.push(offset.unwrap_or(wgsl::NONE));
}
let listed = self.words.len() as u32;
self.words[3 * p..3 * p + 3].copy_from_slice(&[blocks[0], listed, points as u32]);
self.words.extend(&blocks[1..]);
}
}
fn trace(&mut self, samples: &Samples, plan: Plan, rows: usize) {
let blocks = rows.div_ceil(PAGE_ROWS);
self.words.extend([plan.runs.len() as u32, blocks as u32, plan.runs.len() as u32, plan.points]);
let origins: Vec<u64> = plan
.runs
.iter()
.map(|(r, _)| r.entries.start.saturating_sub(crate::data::FRAME_PAGE_WORDS as u64))
.collect();
for (i, (run, point)) in plan.runs.iter().enumerate() {
self.words.extend([i as u32, (run.entries.start - origins[i]) as u32, run.len() as u32, *point]);
}
let at = self.words.len();
self.words.resize(at + 4 * plan.runs.len(), 0);
for (i, (run, _)) in plan.runs.iter().enumerate() {
let origin = origins[i];
let axis = samples.axis_pages(run.level, run.entries.clone());
let listed_at = self.words.len() as u32;
self.words[at + 4 * i..at + 4 * i + 3].copy_from_slice(&[run.level, axis.len() as u32, listed_at]);
for (page, held) in &axis {
let key = PageKey::of(page.id, Words::Values, false);
let offset =
self.gpu.arena.get(key, ((held.end - page.first) * 2) as u32, || Content::Axis(page.clone()));
self.pins.push(key);
self.ready &= offset.is_some();
self.words.extend([offset.unwrap_or(wgsl::NONE), (page.first - origin) as u32]);
}
let lists = self.words.len();
self.words[at + 4 * i + 3] = lists as u32;
self.words.resize(lists + 4 * blocks, 0);
for block in 0..blocks {
for side in 0..crate::data::sides(run.level) {
let pages = samples.pages(block, run.level, side, run.entries.clone());
self.words[lists + 4 * block + 2 * side] = pages.len() as u32;
self.words[lists + 4 * block + 2 * side + 1] = self.words.len() as u32;
self.sample_pages(&pages, origin);
}
}
}
self.plan = Some(plan);
}
fn sample_pages(&mut self, pages: &[(FramePage, Range<u64>)], origin: u64) {
for (page, held) in pages {
let key = PageKey::of(page.id, Words::Values, false);
let need = ((held.end - page.first) * u64::from(page.per)) as u32;
let offset = self.gpu.arena.get(key, need, || Content::Frames(page.clone()));
self.pins.push(key);
self.ready &= offset.is_some();
self.words.extend([offset.unwrap_or(wgsl::NONE), (page.first - origin) as u32, page.per]);
}
}
fn cursors(&mut self, samples: &Samples, frame: Option<u64>, rows: usize) {
for block in 0..rows.div_ceil(PAGE_ROWS) {
let mut cursor = [wgsl::NONE, 0, wgsl::NONE, 0, 0, 0];
for (i, f) in [frame, frame.map(|f| f + 1)].into_iter().enumerate() {
let Some((page, base)) = f.and_then(|f| samples.cursor(block, f)) else { continue };
let key = PageKey::of(page.id, Words::Values, false);
let rows_in_page = page.per;
let offset = self.gpu.arena.get(key, base + rows_in_page, || Content::Frames(page.clone()));
self.pins.push(key);
self.ready &= offset.is_some();
cursor[2 * i] = offset.unwrap_or(wgsl::NONE);
cursor[2 * i + 1] = base;
cursor[4 + i] = rows_in_page;
}
self.words.extend(cursor);
}
}
fn push(&mut self, key: PageKey, offset: Option<u32>) {
self.pins.push(key);
self.ready &= offset.is_some();
self.words.push(offset.unwrap_or(wgsl::NONE));
}
}