use super::View;
use super::clock::Clock;
use super::marks::Shown;
use crate::data::{PAGE_ROWS, RowIds, Table};
use crate::gpu::Inner;
use crate::gpu::shaders::{self as wgsl, Shader};
use crate::scene::mark::MarkKind;
pub(crate) struct Transition {
start: f64,
seconds: f64,
fades: bool,
}
pub(crate) struct Begin {
sources: Option<Vec<u32>>,
fades: bool,
}
impl Transition {
pub(crate) fn motion(&self, clock: &Clock) -> f32 {
if self.seconds <= 0.0 { 1.0 } else { clock.eased(self.start, self.seconds) as f32 }
}
fn done(&self, clock: &Clock) -> bool {
clock.done(self.start, self.seconds)
}
pub(crate) fn settles(&self) -> bool {
!self.fades
}
}
pub(crate) fn remap(old: &Table, new: &Table) -> Option<(Vec<u32>, Vec<u32>)> {
if old.structure == new.structure && old.order == new.order {
return None;
}
if old.order == new.order && new.len() >= old.len() {
let mut sources: Vec<u32> = (0..new.len()).map(|r| if r < old.len() { r as u32 } else { wgsl::NONE }).collect();
let mut exits = Vec::new();
for block in rewritten(old, new).unwrap_or_default() {
let rows = block as usize * PAGE_ROWS..((block as usize + 1) * PAGE_ROWS).min(old.len());
for r in rows.filter(|&r| old.ids.key(r) != new.ids.key(r)) {
sources[r] = wgsl::NONE;
if old.ids.key(r).is_some() {
exits.push(r as u32);
}
}
}
return Some((sources, exits));
}
let mut sources = vec![wgsl::NONE; new.len()];
let mut exits = Vec::new();
if old.len() <= new.len() {
for o in 0..old.len() {
match old.ids.key(o).and_then(|key| new.row(key)) {
Some(r) => sources[r] = o as u32,
None => exits.push(o as u32),
}
}
} else {
let mut kept = vec![false; old.len()];
for (r, source) in sources.iter_mut().enumerate() {
if let Some(o) = new.ids.key(r).and_then(|key| old.row(key)) {
kept[o] = true;
*source = o as u32;
}
}
exits.extend((0..old.len()).filter(|&o| !kept[o]).map(|o| o as u32));
}
Some((sources, exits))
}
pub(crate) fn rewritten(old: &Table, new: &Table) -> Option<Vec<u32>> {
let blocks = |len: usize| len.div_ceil(PAGE_ROWS) as u32;
match (&old.ids, &new.ids) {
(RowIds::Count(a), RowIds::Count(b)) if b >= a => {
Some(((*a as usize / PAGE_ROWS) as u32..blocks(*b as usize)).collect())
}
(RowIds::Listed(a), RowIds::Listed(b)) if b.len() >= a.len() => {
let (a, b) = (a.pages(), b.pages());
Some((0..b.len()).filter(|&p| a.get(p).is_none_or(|page| page.id != b[p].id)).map(|p| p as u32).collect())
}
_ => None,
}
}
pub(crate) fn replaced(old: &Table, new: &Table) -> (Vec<u32>, Vec<u32>) {
(vec![wgsl::NONE; new.len()], (0..old.len() as u32).collect())
}
impl Shown {
pub(crate) fn inherit(&mut self, before: &Shown) {
self.region = before.region;
self.held = before.held;
self.rows = before.rows;
self.drawn.clone_from(&before.drawn);
self.version = before.version + 1;
self.place_version = before.place_version + 1;
}
pub(crate) fn begin(&mut self, moved: Option<(Vec<u32>, Vec<u32>)>) {
if self.held == 0 {
return;
}
let fades = moved.as_ref().is_some_and(|(sources, _)| sources.iter().all(|&s| s == wgsl::NONE));
let sources = moved.map(|(mut sources, exits)| {
self.exits = exits.len() as u32;
sources.extend(exits);
sources
});
if sources.is_none() {
self.exits = 0;
}
self.begin = Some(Begin { sources, fades });
self.all_dirty();
}
}
impl View {
pub(super) fn transitions(&mut self, encoder: &mut wgpu::CommandEncoder, inner: &mut Inner) -> bool {
let seconds = if self.dragging() { 0.0 } else { self.ease().as_secs_f64() };
let mut starting: Vec<usize> = (0..self.marks.len()).filter(|&i| self.marks[i].begin.is_some()).collect();
if seconds == 0.0 {
starting.retain(|&i| {
let shown = &mut self.marks[i];
let carries =
shown.mark.flows() && shown.begin.as_ref().is_some_and(|b| b.sources.is_some() && !b.fades);
if !carries {
shown.begin = None;
shown.exits = 0;
}
carries
});
}
if !starting.is_empty() {
let fades: Vec<bool> =
starting.iter().map(|&i| self.marks[i].begin.as_ref().is_some_and(|b| b.fades)).collect();
self.freeze(encoder, inner, &starting);
let start = self.clock.now();
for (&i, fades) in starting.iter().zip(fades) {
self.marks[i].transition = Some(Transition { start, seconds, fades });
}
}
let mut running = false;
for (i, shown) in self.marks.iter_mut().enumerate() {
let Some(transition) = &shown.transition else { continue };
if transition.done(&self.clock) && !starting.contains(&i) {
shown.transition = None;
shown.exits = 0;
shown.all_dirty();
if shown.tables.frozen().is_some() {
shown.tables.thaw(inner);
shown.packed = false;
}
} else {
running = true;
shown.easing = transition.settles() && self.clock.moved();
}
}
if !running && self.marks.iter().all(|m| m.transition.is_none()) {
let resources = self.resources.as_mut().expect("created");
resources.drop_frozen();
resources.drop_settled();
}
running
}
fn freeze(&mut self, encoder: &mut wgpu::CommandEncoder, inner: &mut Inner, starting: &[usize]) {
let gpu = self.gpu.clone();
let (device, queue) = (&gpu.0.device, &gpu.0.queue);
let slots = self.slot_table(starting);
let resources = self.resources.as_mut().expect("created before transitions");
resources.ensure_frozen(device);
if self.marks.iter().any(|m| m.begin.as_ref().is_some_and(|b| !b.fades)) {
resources.ensure_settled(device);
}
let mut words: Vec<u32> = Vec::new();
let table = slots.map(|table| {
words.extend(table);
0
});
let mut gathers = Vec::new();
for &i in starting {
let shown = &mut self.marks[i];
let begin = shown.begin.take().expect("starting");
let was = shown.moved.unwrap_or(shown.region);
let now = shown.region;
match begin.sources {
None => {
let source = if shown.moved.is_some() { resources.retired_state() } else { None };
let rows = shown.held.min(shown.rows);
resources.copy_frozen(encoder, source, was, now, rows);
}
Some(sources) => {
let at = words.len() as u32;
let slot_words = match shown.kind() {
MarkKind::Edges => (1 << wgsl::EDGE_FROM) | (1 << wgsl::EDGE_TO),
MarkKind::Faces => (1 << wgsl::FACE_A) | (1 << wgsl::FACE_B) | (1 << wgsl::FACE_C),
_ => 0,
};
words.extend([was.state, was.extra, slot_words, table.unwrap_or(wgsl::NONE)]);
words.extend(sources);
shown.remap = at;
shown.carry = true;
gathers.push(i);
}
}
}
if gathers.is_empty() {
return;
}
resources.write_remap(device, queue, &words);
let moved = gathers.iter().any(|&i| self.marks[i].moved.is_some());
let groups = resources.groups(&gpu.0, inner);
let group = resources.freeze_group(device, inner, moved);
let pipeline = inner.pipelines.compute(device, Shader::Freeze, "gather");
let mut pass = encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("latkit freeze"), timestamp_writes: None });
pass.set_pipeline(&pipeline);
pass.set_bind_group(0, &groups.view, &[0]);
pass.set_bind_group(2, &group, &[]);
for &i in &gathers {
let shown = &self.marks[i];
pass.set_bind_group(1, &groups.mark, &[super::passes::offset(i)]);
super::passes::dispatch(&mut pass, shown.rows + shown.exits);
}
}
fn slot_table(&self, starting: &[usize]) -> Option<Vec<u32>> {
let needed = starting.iter().any(|&i| {
let m = &self.marks[i];
m.kind() != MarkKind::Vertices && m.begin.as_ref().is_some_and(|b| b.sources.is_some())
});
if !needed {
return None;
}
let vertices = self.marks.iter().filter(|m| m.kind() == MarkKind::Vertices);
let old_slot = |m: &Shown| m.moved.unwrap_or(m.region).state / wgsl::VERTEX_WORDS;
let size = vertices.clone().map(|m| old_slot(m) + m.held).max().unwrap_or(0);
let mut table = vec![wgsl::NONE; size as usize];
for m in vertices {
let (from, to) = (old_slot(m), m.region.state / wgsl::VERTEX_WORDS);
match m.begin.as_ref().and_then(|b| b.sources.as_ref()) {
Some(sources) => {
for (new_row, &old_row) in sources.iter().enumerate() {
if old_row != wgsl::NONE && old_row < m.held {
table[(from + old_row) as usize] = to + new_row as u32;
}
}
}
None => {
for row in 0..m.held.min(m.rows) {
table[(from + row) as usize] = to + row;
}
}
}
}
Some(table)
}
}