1use std::{hash::BuildHasher as _, mem, sync::Arc};
8
9use rustc_hash::{FxBuildHasher, FxHashMap};
10
11use super::{ChangeSet, Ingest, Op};
12use crate::{
13 highlight::{ChangeDirection, RowChange, Stamp},
14 model::{ColumnIx, ColumnSet, GridColumns as _, GridRow},
15};
16
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum WriteCoalescing {
32 #[default]
41 ByKey,
42 KeepEveryWrite,
45}
46
47pub(super) enum Queued<R: GridRow> {
49 Op(Op<R>),
51 Merged(u32),
54}
55
56pub(super) struct MergedUpsert<R: GridRow> {
59 first: Arc<R>,
60 highlight: bool,
62 latest: Arc<R>,
63 changes: MergedChanges,
64}
65
66#[derive(Default)]
69struct MergedChanges {
70 columns: ColumnSet,
72 stamped: ColumnSet,
74 up: ColumnSet,
76 down: ColumnSet,
77 parent: bool,
79}
80
81impl MergedChanges {
82 fn record<R: GridRow>(&mut self, row: &R, previous: &R, highlight: bool) {
84 let mut columns = ColumnSet::EMPTY;
85 row.changed_columns(previous, &mut columns);
86 self.parent |= row.parent_key() != previous.parent_key();
87 self.columns.union_with(columns);
88 if !highlight {
89 return;
90 }
91 let all = R::Column::all();
92 let columns = columns.intersection(ColumnSet::first(all.len()));
94 self.stamped.union_with(columns);
95 for column in columns.iter() {
96 let direction = row.change_direction(previous, all[column.get()]);
97 self.up.remove(column);
98 self.down.remove(column);
99 match direction {
100 Some(ChangeDirection::Up) => self.up.insert(column),
101 Some(ChangeDirection::Down) => self.down.insert(column),
102 None => {}
103 }
104 }
105 }
106
107 fn direction(&self, column: ColumnIx) -> Option<ChangeDirection> {
108 if self.up.contains(column) {
109 Some(ChangeDirection::Up)
110 } else if self.down.contains(column) {
111 Some(ChangeDirection::Down)
112 } else {
113 None
114 }
115 }
116}
117
118#[derive(Clone, Copy, Debug, Default)]
120pub(super) struct QueueCounters {
121 pub(super) writes: u64,
122 pub(super) coalesced: u64,
123 pub(super) applied: u64,
124 pub(super) pending: usize,
125 pub(super) peak_pending: usize,
126}
127
128pub(super) struct WriteQueue<R: GridRow> {
130 coalescing: WriteCoalescing,
131 entries: Vec<Queued<R>>,
132 merges: Vec<MergedUpsert<R>>,
133 upserts: FxHashMap<u64, u32>,
137 floor: u32,
140 queued: u64,
142 counters: QueueCounters,
143}
144
145pub(super) struct TakenWrites<R: GridRow> {
147 entries: Vec<Queued<R>>,
148 merges: Vec<MergedUpsert<R>>,
149}
150
151impl<R: GridRow> WriteQueue<R> {
152 pub(super) fn new(coalescing: WriteCoalescing) -> Self {
153 Self {
154 coalescing,
155 entries: Vec::new(),
156 merges: Vec::new(),
157 upserts: FxHashMap::default(),
158 floor: 0,
159 queued: 0,
160 counters: QueueCounters::default(),
161 }
162 }
163
164 pub(super) fn counters(&self) -> QueueCounters {
165 QueueCounters {
166 pending: self.entries.len(),
167 ..self.counters
168 }
169 }
170
171 pub(super) fn count_buffered(&mut self) {
173 self.counters.writes += 1;
174 }
175
176 pub(super) fn push(&mut self, op: Op<R>) {
179 self.counters.writes += 1;
180 self.queued += 1;
181 match op {
182 Op::Upsert(row) => self.push_upsert(row, true),
183 Op::UpsertSilent(row) => self.push_upsert(row, false),
184 Op::Remove(key) => {
185 self.forget(&key);
186 self.push_entry(Op::Remove(key));
187 }
188 Op::MarkChanged(key, columns) => {
189 self.forget(&key);
190 self.push_entry(Op::MarkChanged(key, columns));
191 }
192 op @ (Op::SetPinnedRows { .. } | Op::SetProgress(_)) => self.push_entry(op),
193 op => {
194 self.barrier();
195 self.push_entry(op);
196 }
197 }
198 }
199
200 pub(super) fn push_finished_reload(&mut self, snapshot: Vec<Arc<R>>, live: Vec<Op<R>>) {
203 self.queued += live.len() as u64;
204 self.barrier();
205 self.push_entry(Op::FinishReload { snapshot, live });
206 }
207
208 pub(super) fn push_cancelled_reload(&mut self, live: Vec<Op<R>>) {
211 self.queued += live.len() as u64;
212 self.barrier();
213 self.entries.extend(live.into_iter().map(Queued::Op));
214 self.note_depth();
215 }
216
217 pub(super) fn take(&mut self) -> TakenWrites<R> {
219 self.upserts.clear();
220 self.floor = 0;
221 self.counters.applied += mem::take(&mut self.queued);
222 TakenWrites {
223 entries: mem::take(&mut self.entries),
224 merges: mem::take(&mut self.merges),
225 }
226 }
227
228 fn push_entry(&mut self, op: Op<R>) {
229 self.entries.push(Queued::Op(op));
230 self.note_depth();
231 }
232
233 fn note_depth(&mut self) {
234 self.counters.peak_pending = self.counters.peak_pending.max(self.entries.len());
235 }
236
237 fn barrier(&mut self) {
239 self.floor = self.entries.len() as u32;
240 }
241
242 fn forget(&mut self, key: &R::Key) {
244 if self.coalescing == WriteCoalescing::ByKey {
245 self.upserts.remove(&FxBuildHasher.hash_one(key));
246 }
247 }
248
249 fn push_upsert(&mut self, row: Arc<R>, highlight: bool) {
250 let hash = match self.coalescing {
251 WriteCoalescing::ByKey => FxBuildHasher.hash_one(row.key()),
252 WriteCoalescing::KeepEveryWrite => {
253 self.push_entry(upsert(row, highlight));
254 return;
255 }
256 };
257 let row = match self.upserts.get(&hash) {
258 Some(&ix) if ix >= self.floor => match self.merge(ix as usize, row, highlight) {
259 Ok(()) => {
260 self.counters.coalesced += 1;
261 return;
262 }
263 Err(row) => row,
264 },
265 _ => row,
266 };
267 self.upserts.insert(hash, self.entries.len() as u32);
268 self.push_entry(upsert(row, highlight));
269 }
270
271 fn merge(&mut self, ix: usize, row: Arc<R>, highlight: bool) -> Result<(), Arc<R>> {
274 let (first, first_highlight) = match &self.entries[ix] {
275 Queued::Merged(merge_ix) => {
276 let merge = &mut self.merges[*merge_ix as usize];
277 if merge.latest.key() != row.key() {
278 return Err(row);
279 }
280 if !Arc::ptr_eq(&merge.latest, &row) {
282 merge.changes.record(&*row, &*merge.latest, highlight);
283 merge.latest = row;
284 }
285 return Ok(());
286 }
287 Queued::Op(Op::Upsert(first)) => (first, true),
288 Queued::Op(Op::UpsertSilent(first)) => (first, false),
289 Queued::Op(_) => return Err(row),
290 };
291 if first.key() != row.key() {
292 return Err(row);
293 }
294 if Arc::ptr_eq(first, &row) {
295 return Ok(());
296 }
297 let mut changes = MergedChanges::default();
298 changes.record(&*row, &**first, highlight);
299 let first = first.clone();
300 self.entries[ix] = Queued::Merged(self.merges.len() as u32);
301 self.merges.push(MergedUpsert {
302 first,
303 highlight: first_highlight,
304 latest: row,
305 changes,
306 });
307 Ok(())
308 }
309}
310
311fn upsert<R: GridRow>(row: Arc<R>, highlight: bool) -> Op<R> {
312 if highlight {
313 Op::Upsert(row)
314 } else {
315 Op::UpsertSilent(row)
316 }
317}
318
319impl<R: GridRow> Ingest<R> {
320 pub(super) fn apply_writes(
323 &mut self,
324 writes: TakenWrites<R>,
325 now: web_time::Instant,
326 ) -> ChangeSet {
327 let now_ms = self.now_ms(now);
328 let mut changes = ChangeSet::default();
329 let TakenWrites { entries, merges } = writes;
330 for entry in entries {
331 match entry {
332 Queued::Op(op) => self.apply_op(op, now_ms, &mut changes),
333 Queued::Merged(ix) => self.apply_merged(&merges[ix as usize], now_ms, &mut changes),
334 }
335 }
336 self.finish_batch(now_ms, changes)
337 }
338
339 fn apply_merged(&mut self, merge: &MergedUpsert<R>, now_ms: u32, changes: &mut ChangeSet) {
344 self.upsert(merge.first.clone(), now_ms, merge.highlight, changes);
345 let Some(&id) = self.index.get(merge.latest.key()) else {
346 return;
347 };
348 let ix = id.ix();
349 self.slot_mut(ix).row = Some(merge.latest.clone());
350 let merged = &merge.changes;
351 changes.parent |= merged.parent;
352 changes.updated.push(id);
353 changes.columns.union_with(merged.columns);
354 if !merged.stamped.is_empty() {
355 self.stamp(ix, merged.stamped, now_ms, |column| {
356 Stamp::with_direction(now_ms, RowChange::Updated, merged.direction(column))
357 });
358 }
359 }
360}