Skip to main content

reifydb_core/window/engine/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4//! Schema-agnostic windowing state-machine engines.
5//!
6//! Each engine owns the per-(group,window) accumulator state, high-water late
7//! rejection, eviction, and diff routing (`Insert -> add`,
8//! `Update -> remove(pre) + add(post)`, `Remove -> remove(pre)`). The caller
9//! (the "face") owns extraction (`row -> (group, coord, contribution)`) and
10//! output construction; it hands the engine pre-bucketed events and receives
11//! [`WindowResult`]s to translate into diffs.
12
13pub mod config;
14pub mod multi_rolling;
15pub mod rolling;
16pub mod rolling_incremental;
17pub mod tumbling;
18pub mod tumbling_carry;
19
20use std::{
21	collections::{BTreeMap, BTreeSet},
22	ops::Bound,
23};
24
25use reifydb_codec::key::{
26	encode_u64,
27	encoded::{EncodedKey, EncodedKeyRange, IntoEncodedKey},
28};
29use reifydb_value::{Result, value::row_number::RowNumber};
30use serde::{Deserialize, Serialize, de::DeserializeOwned};
31
32use crate::{
33	key::flow_node_internal_state::FlowNodeInternalStateKey,
34	window::{
35		accumulator::WindowAccumulator,
36		span::{Slot, WindowSpan},
37		state::StateCache,
38		store::WindowStore,
39	},
40};
41
42/// One contribution routed to a window accumulator.
43pub enum AccumulatorEvent<C> {
44	Add(C),
45	Remove(C),
46}
47
48/// The seal horizon: window anchors (window start for bucketed engines, the
49/// coordinate for rolling ledgers) strictly below this value are sealed -
50/// immutable and eligible for state reclamation. Computed by the face as
51/// `watermark - seal_after`, where `seal_after` folds the window span and the
52/// grace duration into one number in coordinate units.
53pub fn seal_horizon(watermark: u64, seal_after: u64) -> u64 {
54	watermark.saturating_sub(seal_after)
55}
56
57pub fn is_sealed(anchor: u64, horizon: u64) -> bool {
58	anchor < horizon
59}
60
61/// How a finalized window value should be emitted downstream.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum EmitKind {
64	Insert,
65	Update,
66	Remove,
67}
68
69/// A finalized window the engine produced; the face turns it into a diff.
70pub struct WindowResult<G, Coord, Output> {
71	pub row_number: RowNumber,
72	pub group: G,
73	pub span: WindowSpan<Coord>,
74	pub value: Output,
75	/// The finalized value before this batch's events, when the window was
76	/// non-empty (used by faces that emit a real pre on Update/Remove). `None`
77	/// for a brand-new window. Faces that don't need it (the sdk drivers)
78	/// ignore it.
79	pub prior: Option<Output>,
80	pub kind: EmitKind,
81}
82
83/// Per-group metadata: the highest window start seen, used to drop late events
84/// for already-closed windows.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(bound(serialize = "K: Serialize", deserialize = "K: serde::de::DeserializeOwned"))]
87pub struct GroupMeta<K> {
88	pub high_water: Option<K>,
89}
90
91impl<K> Default for GroupMeta<K> {
92	fn default() -> Self {
93		Self {
94			high_water: None,
95		}
96	}
97}
98
99/// Read the group's high-water anchor as a comparable order key, shared by every
100/// engine's per-group meta so the meta sweep is uniform. A group whose high water
101/// has fallen below the sweep threshold has stopped advancing (no recent events)
102/// and its meta is safe to reclaim: the meta only drives late-event rejection, and
103/// by the time the threshold (>= the operator's lateness/retention) passes it, any
104/// late event for the group is already past its horizon.
105pub(crate) trait MetaHighWater {
106	fn high_water_order(&self) -> Option<u64>;
107}
108
109impl<C: Slot> MetaHighWater for GroupMeta<C> {
110	fn high_water_order(&self) -> Option<u64> {
111		self.high_water.as_ref().map(|hw| hw.order_key())
112	}
113}
114
115/// The internal-key range covering every per-group meta ('W'), used by the sweep.
116pub(crate) fn meta_range() -> EncodedKeyRange {
117	EncodedKeyRange::new(
118		Bound::Included(EncodedKey::new(vec![FlowNodeInternalStateKey::WINDOW_META_TAG])),
119		Bound::Excluded(EncodedKey::new(vec![FlowNodeInternalStateKey::WINDOW_META_TAG + 1])),
120	)
121}
122
123/// Reclaim every group meta whose high water is strictly below `threshold`.
124///
125/// `low_water` is the smallest high water among the groups that survived the previous sweep - a lower
126/// bound on the current minimum, since a group's high water only advances and a newly-seen group starts
127/// at an unsealed window (>= the caller's seal horizon >= `threshold`, so it can never be the stale
128/// minimum). When the bound is already at/above the threshold nothing can be stale and the whole scan is
129/// skipped - the steady-state case, so most apply-time sweeps are O(1). The full scan runs only when the
130/// threshold has crossed that minimum (the oldest group has genuinely gone stale); it then drops every
131/// stale meta in one pass and recomputes the bound to the smallest surviving high water.
132///
133/// Staleness is a value, not a key prefix, so the scan must cover the whole meta keyspace (a key-bounded
134/// scan would only ever see the lowest-keyed groups). It flushes the meta cache first so the scan sees
135/// the latest high water, drops stale keys through the cache (never bypassing it), and flushes the drops.
136/// Scoped to the meta keyspace, so row-number mappings and accumulators are untouched.
137pub(crate) fn sweep_stale_meta<S, M>(
138	store: &mut S,
139	meta: &mut StateCache<MetaKey, M>,
140	threshold: u64,
141	low_water: &mut Option<u64>,
142) -> Result<usize>
143where
144	S: WindowStore,
145	M: MetaHighWater + Clone + Serialize + DeserializeOwned,
146{
147	if low_water.is_some_and(|lw| lw >= threshold) {
148		return Ok(0);
149	}
150	meta.flush(store)?;
151	let mut stale: Vec<MetaKey> = Vec::new();
152	let mut min_surviving: Option<u64> = None;
153	store.internal_range_visit::<M>(meta_range(), None, &mut |key, value| {
154		if let Some(hw) = value.high_water_order() {
155			if hw < threshold {
156				stale.push(MetaKey(EncodedKey::new(key.as_bytes()[1..].to_vec())));
157			} else {
158				min_surviving = Some(min_surviving.map_or(hw, |m| m.min(hw)));
159			}
160		}
161		Ok(())
162	})?;
163	*low_water = min_surviving;
164	let count = stale.len();
165	for key in &stale {
166		meta.remove(store, key)?;
167	}
168	meta.flush(store)?;
169	Ok(count)
170}
171
172/// State-cache key for a group's [`GroupMeta`], tagged so it lives in a
173/// distinct keyspace from the per-window accumulators.
174#[derive(Clone, Hash, PartialEq, Eq)]
175pub struct MetaKey(pub EncodedKey);
176
177#[derive(Clone, Copy, Hash, PartialEq, Eq)]
178pub struct RunningKey(pub RowNumber);
179
180impl IntoEncodedKey for &RunningKey {
181	fn into_encoded_key(self) -> EncodedKey {
182		let inner = (&self.0).into_encoded_key();
183		let inner = inner.as_ref();
184		let mut bytes = Vec::with_capacity(1 + inner.len());
185		bytes.push(FlowNodeInternalStateKey::WINDOW_RUNNING_TAG);
186		bytes.extend_from_slice(inner);
187		EncodedKey::new(bytes)
188	}
189}
190
191#[derive(Clone, Copy, Hash, PartialEq, Eq)]
192pub struct WindowStateKey(pub RowNumber);
193
194impl IntoEncodedKey for &WindowStateKey {
195	fn into_encoded_key(self) -> EncodedKey {
196		let inner = (&self.0).into_encoded_key();
197		let inner = inner.as_ref();
198		let mut bytes = Vec::with_capacity(1 + inner.len());
199		bytes.push(FlowNodeInternalStateKey::WINDOW_ROW_STATE_TAG);
200		bytes.extend_from_slice(inner);
201		EncodedKey::new(bytes)
202	}
203}
204
205#[derive(Clone, Copy, Hash, PartialEq, Eq)]
206pub struct EmitKey(pub RowNumber);
207
208impl IntoEncodedKey for &EmitKey {
209	fn into_encoded_key(self) -> EncodedKey {
210		let inner = (&self.0).into_encoded_key();
211		let inner = inner.as_ref();
212		let mut bytes = Vec::with_capacity(1 + inner.len());
213		bytes.push(FlowNodeInternalStateKey::WINDOW_EMIT_TAG);
214		bytes.extend_from_slice(inner);
215		EncodedKey::new(bytes)
216	}
217}
218
219impl IntoEncodedKey for &MetaKey {
220	fn into_encoded_key(self) -> EncodedKey {
221		let inner = self.0.as_ref();
222		let mut bytes = Vec::with_capacity(1 + inner.len());
223		bytes.push(FlowNodeInternalStateKey::WINDOW_META_TAG);
224		bytes.extend_from_slice(inner);
225		EncodedKey::new(bytes)
226	}
227}
228
229pub fn meta_key_for<G>(group: &G) -> MetaKey
230where
231	for<'a> &'a G: IntoEncodedKey,
232{
233	MetaKey(group.into_encoded_key())
234}
235
236pub fn expiry_key<G>(expiry: u64, group: &G, suffix: &[u8]) -> EncodedKey
237where
238	for<'a> &'a G: IntoEncodedKey,
239{
240	let group = group.into_encoded_key();
241	let group = group.as_ref();
242	let mut bytes = Vec::with_capacity(1 + 8 + group.len() + suffix.len());
243	bytes.push(FlowNodeInternalStateKey::WINDOW_EXPIRY_TAG);
244	bytes.extend_from_slice(&encode_u64(expiry));
245	bytes.extend_from_slice(group);
246	bytes.extend_from_slice(suffix);
247	EncodedKey::new(bytes)
248}
249
250pub fn coord_entry_key(row_number: RowNumber, coord: u64) -> EncodedKey {
251	let mut bytes = Vec::with_capacity(17);
252	bytes.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
253	bytes.extend_from_slice(&row_number.0.to_be_bytes());
254	bytes.extend_from_slice(&coord.to_be_bytes());
255	EncodedKey::new(bytes)
256}
257
258pub fn coord_row_range(row_number: RowNumber) -> EncodedKeyRange {
259	let mut start = Vec::with_capacity(9);
260	start.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
261	start.extend_from_slice(&row_number.0.to_be_bytes());
262	let mut end = Vec::with_capacity(9);
263	end.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
264	end.extend_from_slice(&(row_number.0 + 1).to_be_bytes());
265	EncodedKeyRange::new(Bound::Included(EncodedKey::new(start)), Bound::Excluded(EncodedKey::new(end)))
266}
267
268pub fn coord_due_range(row_number: RowNumber, cutoff: u64) -> EncodedKeyRange {
269	let mut start = Vec::with_capacity(9);
270	start.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
271	start.extend_from_slice(&row_number.0.to_be_bytes());
272	let end = match cutoff.checked_add(1) {
273		Some(exclusive) => {
274			let mut end = Vec::with_capacity(17);
275			end.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
276			end.extend_from_slice(&row_number.0.to_be_bytes());
277			end.extend_from_slice(&exclusive.to_be_bytes());
278			end
279		}
280		None => {
281			let mut end = Vec::with_capacity(9);
282			end.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
283			end.extend_from_slice(&(row_number.0 + 1).to_be_bytes());
284			end
285		}
286	};
287	EncodedKeyRange::new(Bound::Included(EncodedKey::new(start)), Bound::Excluded(EncodedKey::new(end)))
288}
289
290pub fn coord_between_range(row_number: RowNumber, after: u64, upto: u64) -> EncodedKeyRange {
291	let start = coord_entry_key(row_number, after);
292	let end = match upto.checked_add(1) {
293		Some(exclusive) => {
294			let mut end = Vec::with_capacity(17);
295			end.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
296			end.extend_from_slice(&row_number.0.to_be_bytes());
297			end.extend_from_slice(&exclusive.to_be_bytes());
298			end
299		}
300		None => {
301			let mut end = Vec::with_capacity(9);
302			end.push(FlowNodeInternalStateKey::WINDOW_COORD_TAG);
303			end.extend_from_slice(&(row_number.0 + 1).to_be_bytes());
304			end
305		}
306	};
307	EncodedKeyRange::new(Bound::Excluded(start), Bound::Excluded(EncodedKey::new(end)))
308}
309
310pub fn expiry_due_range(threshold: u64) -> EncodedKeyRange {
311	let mut start = Vec::with_capacity(1 + 8);
312	start.push(FlowNodeInternalStateKey::WINDOW_EXPIRY_TAG);
313	start.extend_from_slice(&encode_u64(threshold));
314	let end = vec![FlowNodeInternalStateKey::WINDOW_EXPIRY_TAG + 1];
315	EncodedKeyRange::new(Bound::Included(EncodedKey::new(start)), Bound::Excluded(EncodedKey::new(end)))
316}
317
318pub(crate) fn entry_key_coord(key: &EncodedKey) -> Option<u64> {
319	let bytes = key.as_bytes();
320	if bytes.len() == 17 {
321		let mut coord = [0u8; 8];
322		coord.copy_from_slice(&bytes[9..17]);
323		Some(u64::from_be_bytes(coord))
324	} else {
325		None
326	}
327}
328
329pub(crate) fn load_buffer<S, C, A>(store: &mut S, row_number: RowNumber) -> Result<(BTreeMap<C, A>, Vec<u64>)>
330where
331	S: WindowStore,
332	C: Slot,
333	A: WindowAccumulator,
334{
335	let mut buffer = BTreeMap::new();
336	let mut loaded: Vec<u64> = Vec::new();
337	store.internal_range_visit::<A>(coord_row_range(row_number), None, &mut |key, accumulator| {
338		if let Some(order) = entry_key_coord(&key) {
339			buffer.insert(C::from_order_key(order), accumulator);
340			loaded.push(order);
341		}
342		Ok(())
343	})?;
344	Ok((buffer, loaded))
345}
346
347pub(crate) fn persist_buffer<S, C, A>(
348	store: &mut S,
349	row_number: RowNumber,
350	buffer: &BTreeMap<C, A>,
351	loaded_coords: &[u64],
352	dirty: &BTreeSet<u64>,
353) -> Result<()>
354where
355	S: WindowStore,
356	C: Slot,
357	A: WindowAccumulator,
358{
359	let live: BTreeSet<u64> = buffer.keys().map(|c| c.order_key()).collect();
360	let loaded: BTreeSet<u64> = loaded_coords.iter().copied().collect();
361	for old in loaded_coords {
362		if !live.contains(old) {
363			store.internal_drop(&coord_entry_key(row_number, *old))?;
364		}
365	}
366	for (coord, accumulator) in buffer {
367		let order = coord.order_key();
368		if dirty.contains(&order) || !loaded.contains(&order) {
369			store.internal_set(&coord_entry_key(row_number, order), accumulator)?;
370		}
371	}
372	Ok(())
373}
374
375pub(crate) fn drop_all_coords<S, A>(store: &mut S, row_number: RowNumber) -> Result<()>
376where
377	S: WindowStore,
378	A: WindowAccumulator,
379{
380	let mut keys: Vec<EncodedKey> = Vec::new();
381	store.internal_range_visit::<A>(coord_row_range(row_number), None, &mut |key, _accumulator| {
382		keys.push(key);
383		Ok(())
384	})?;
385	for key in keys {
386		store.internal_drop(&key)?;
387	}
388	Ok(())
389}
390
391#[cfg(test)]
392pub(crate) mod test_support {
393	use std::{collections::HashMap, ops::Bound};
394
395	use postcard::{from_bytes, to_allocvec};
396	use reifydb_codec::key::encoded::{EncodedKey, EncodedKeyRange};
397	use reifydb_value::{Result, value::row_number::RowNumber};
398	use serde::{Deserialize, Serialize, de::DeserializeOwned};
399
400	use crate::{
401		key::flow_node_internal_state::FlowNodeInternalStateKey,
402		window::{accumulator::WindowAccumulator, store::WindowStore},
403	};
404
405	#[derive(Default)]
406	pub(crate) struct MockStore {
407		data: HashMap<Vec<u8>, Vec<u8>>,
408		internal: HashMap<Vec<u8>, Vec<u8>>,
409		rows: HashMap<Vec<u8>, u64>,
410		next_row: u64,
411	}
412
413	impl MockStore {
414		pub(crate) fn index_entry_count(&mut self) -> usize {
415			self.internal
416				.keys()
417				.filter(|k| k.first() == Some(&FlowNodeInternalStateKey::WINDOW_EXPIRY_TAG))
418				.count()
419		}
420
421		pub(crate) fn coord_entry_count(&mut self) -> usize {
422			self.internal
423				.keys()
424				.filter(|k| k.first() == Some(&FlowNodeInternalStateKey::WINDOW_COORD_TAG))
425				.count()
426		}
427
428		pub(crate) fn running_entry_count(&mut self) -> usize {
429			self.internal
430				.keys()
431				.filter(|k| k.first() == Some(&FlowNodeInternalStateKey::WINDOW_RUNNING_TAG))
432				.count()
433		}
434
435		pub(crate) fn meta_entry_count(&mut self) -> usize {
436			self.internal
437				.keys()
438				.filter(|k| k.first() == Some(&FlowNodeInternalStateKey::WINDOW_META_TAG))
439				.count()
440		}
441
442		pub(crate) fn mapping_entry_count(&mut self) -> usize {
443			self.internal
444				.keys()
445				.filter(|k| k.first() == Some(&FlowNodeInternalStateKey::ROW_NUMBER_MAPPING_TAG))
446				.count()
447		}
448
449		pub(crate) fn seed_mapping_key(&mut self, suffix: u8) {
450			self.internal.insert(vec![FlowNodeInternalStateKey::ROW_NUMBER_MAPPING_TAG, suffix], vec![0u8]);
451		}
452
453		pub(crate) fn contains_row_mapping(&self, key: &EncodedKey) -> bool {
454			self.rows.contains_key(key.as_bytes())
455		}
456	}
457
458	impl WindowStore for MockStore {
459		fn state_get<V: DeserializeOwned>(&mut self, key: &EncodedKey) -> Result<Option<V>> {
460			Ok(self.data.get(key.as_bytes()).map(|b| from_bytes(b).expect("decode")))
461		}
462		fn state_get_many_visit<V: DeserializeOwned>(
463			&mut self,
464			keys: &[EncodedKey],
465			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
466		) -> Result<()> {
467			for key in keys {
468				if let Some(b) = self.data.get(key.as_bytes()) {
469					visit(key.clone(), from_bytes(b).expect("decode"))?;
470				}
471			}
472			Ok(())
473		}
474		fn state_set<V: Serialize>(&mut self, key: &EncodedKey, value: &V) -> Result<()> {
475			self.data.insert(key.as_bytes().to_vec(), to_allocvec(value).expect("encode"));
476			Ok(())
477		}
478		fn state_remove(&mut self, key: &EncodedKey) -> Result<()> {
479			self.data.remove(key.as_bytes());
480			Ok(())
481		}
482		fn state_drop(&mut self, key: &EncodedKey) -> Result<()> {
483			self.data.remove(key.as_bytes());
484			Ok(())
485		}
486		fn internal_get<V: DeserializeOwned>(&mut self, key: &EncodedKey) -> Result<Option<V>> {
487			Ok(self.internal.get(key.as_bytes()).map(|b| from_bytes(b).expect("decode")))
488		}
489		fn internal_get_many_visit<V: DeserializeOwned>(
490			&mut self,
491			keys: &[EncodedKey],
492			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
493		) -> Result<()> {
494			for key in keys {
495				if let Some(b) = self.internal.get(key.as_bytes()) {
496					visit(key.clone(), from_bytes(b).expect("decode"))?;
497				}
498			}
499			Ok(())
500		}
501		fn internal_set<V: Serialize>(&mut self, key: &EncodedKey, value: &V) -> Result<()> {
502			self.internal.insert(key.as_bytes().to_vec(), to_allocvec(value).expect("encode"));
503			Ok(())
504		}
505		fn internal_remove(&mut self, key: &EncodedKey) -> Result<()> {
506			self.internal.remove(key.as_bytes());
507			Ok(())
508		}
509		fn internal_drop(&mut self, key: &EncodedKey) -> Result<()> {
510			self.internal.remove(key.as_bytes());
511			Ok(())
512		}
513		fn internal_range_visit<V: DeserializeOwned>(
514			&mut self,
515			range: EncodedKeyRange,
516			limit: Option<usize>,
517			visit: &mut dyn FnMut(EncodedKey, V) -> Result<()>,
518		) -> Result<()> {
519			let after_start = |k: &[u8]| match &range.start {
520				Bound::Included(s) => k >= s.as_bytes(),
521				Bound::Excluded(s) => k > s.as_bytes(),
522				Bound::Unbounded => true,
523			};
524			let before_end = |k: &[u8]| match &range.end {
525				Bound::Included(e) => k <= e.as_bytes(),
526				Bound::Excluded(e) => k < e.as_bytes(),
527				Bound::Unbounded => true,
528			};
529			let mut matched: Vec<(Vec<u8>, Vec<u8>)> = self
530				.internal
531				.iter()
532				.filter(|(k, _)| after_start(k) && before_end(k))
533				.map(|(k, v)| (k.clone(), v.clone()))
534				.collect();
535			matched.sort_by(|a, b| a.0.cmp(&b.0));
536			if let Some(limit) = limit {
537				matched.truncate(limit);
538			}
539			for (k, b) in matched {
540				visit(EncodedKey::new(k), from_bytes(&b).expect("decode"))?;
541			}
542			Ok(())
543		}
544		fn get_or_create_row_number(&mut self, key: &EncodedKey) -> Result<(RowNumber, bool)> {
545			if let Some(rn) = self.rows.get(key.as_bytes()) {
546				return Ok((RowNumber(*rn), false));
547			}
548			self.next_row += 1;
549			self.rows.insert(key.as_bytes().to_vec(), self.next_row);
550			Ok((RowNumber(self.next_row), true))
551		}
552		fn get_or_create_row_numbers(&mut self, keys: &[EncodedKey]) -> Result<Vec<(RowNumber, bool)>> {
553			keys.iter().map(|k| self.get_or_create_row_number(k)).collect()
554		}
555		fn drop_row_number(&mut self, key: &EncodedKey) -> Result<()> {
556			self.rows.remove(key.as_bytes());
557			Ok(())
558		}
559		fn allocate_row_numbers(&mut self, count: u64) -> Result<RowNumber> {
560			let start = self.next_row + 1;
561			self.next_row += count;
562			Ok(RowNumber(start))
563		}
564		fn clock_now_nanos(&self) -> u64 {
565			0
566		}
567	}
568
569	#[derive(Clone, Debug, Default, Serialize, Deserialize)]
570	pub(crate) struct SumAccumulator {
571		pub sum: i64,
572		pub count: u64,
573	}
574
575	impl WindowAccumulator for SumAccumulator {
576		type Contribution = i64;
577		type Output = i64;
578
579		fn add(&mut self, contribution: &i64) {
580			self.sum += *contribution;
581			self.count += 1;
582		}
583		fn remove(&mut self, contribution: &i64) {
584			self.sum -= *contribution;
585			self.count = self.count.saturating_sub(1);
586		}
587		fn finalize(&self) -> Option<i64> {
588			if self.count == 0 {
589				None
590			} else {
591				Some(self.sum)
592			}
593		}
594		fn is_empty(&self) -> bool {
595			self.count == 0
596		}
597		fn merge(&mut self, other: &Self) {
598			self.sum += other.sum;
599			self.count += other.count;
600		}
601		fn unmerge(&mut self, other: &Self) {
602			self.sum -= other.sum;
603			self.count = self.count.saturating_sub(other.count);
604		}
605	}
606
607	#[derive(Clone, Debug, Default, Serialize, Deserialize)]
608	pub(crate) struct StampedSum {
609		pub sum: i64,
610		pub count: u64,
611		pub stamp: Option<u64>,
612	}
613
614	impl WindowAccumulator for StampedSum {
615		type Contribution = (i64, u64);
616		type Output = i64;
617
618		fn add(&mut self, contribution: &(i64, u64)) {
619			self.sum += contribution.0;
620			self.count += 1;
621			self.stamp = Some(self.stamp.map_or(contribution.1, |s| s.max(contribution.1)));
622		}
623		fn remove(&mut self, contribution: &(i64, u64)) {
624			self.sum -= contribution.0;
625			self.count = self.count.saturating_sub(1);
626		}
627		fn finalize(&self) -> Option<i64> {
628			if self.count == 0 {
629				None
630			} else {
631				Some(self.sum)
632			}
633		}
634		fn is_empty(&self) -> bool {
635			self.count == 0
636		}
637		fn stamp(&self) -> Option<u64> {
638			self.stamp
639		}
640	}
641}