use std::sync::Arc;
use std::time::Instant;
use crate::engine::output::{JsonOptions, write_json};
use crate::engine::snapshot::{
composite_secondary, is_ais_pgn, per_iteration_secondary, repeating_pk_set,
};
use crate::engine::{DecodedField, DecodedPgn, FieldValue, PgnDatabase};
pub struct SnapshotStore {
inner: Arc<crate::engine::snapshot::SnapshotStore>,
json_opts: JsonOptions,
db: &'static PgnDatabase,
}
impl SnapshotStore {
pub fn new(json_opts: JsonOptions, db: &'static PgnDatabase) -> Self {
Self {
inner: Arc::new(crate::engine::snapshot::SnapshotStore::new()),
json_opts,
db,
}
}
pub fn core(&self) -> Arc<crate::engine::snapshot::SnapshotStore> {
Arc::clone(&self.inner)
}
pub fn store(&self, decoded: &Arc<DecodedPgn>, now: Instant) {
let is_ais = is_ais_pgn(decoded.pgn);
let Some(info) = self.db.first_pgn(decoded.pgn) else {
self.store_whole(decoded, None, is_ais, now);
return;
};
if let Some(rs) = repeating_pk_set(info) {
self.store_per_iteration(decoded, info, rs, is_ais, now);
return;
}
let secondary = composite_secondary(info, |f| top_field_text(decoded, f));
self.store_whole(decoded, secondary, is_ais, now);
}
fn store_whole(
&self,
decoded: &Arc<DecodedPgn>,
secondary: Option<String>,
is_ais: bool,
now: Instant,
) {
let d = Arc::clone(decoded);
let opts = self.json_opts.clone();
self.inner.store_lazy(
decoded.pgn,
decoded.src,
secondary,
is_ais,
decoded.description,
Box::new(move || {
let mut s = String::with_capacity(256);
let _ = write_json(&mut s, &d, &opts);
s
}),
now,
);
}
fn store_per_iteration(
&self,
decoded: &Arc<DecodedPgn>,
info: &crate::engine::PgnInfo,
rs: u8,
is_ais: bool,
now: Instant,
) {
let iters: Vec<u32> = collect_iter_indexes(decoded, rs);
if iters.is_empty() {
return;
}
for iter in iters {
let secondary = per_iteration_secondary(
info,
rs,
iter,
|f| top_field_text(decoded, f),
|f, ii| iter_field_text(decoded, rs, ii, f),
);
let d = Arc::clone(decoded);
let opts = self.json_opts.clone();
self.inner.store_lazy(
decoded.pgn,
decoded.src,
secondary,
is_ais,
decoded.description,
Box::new(move || synthesize_iter_line(&d, rs, iter, &opts)),
now,
);
}
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
self.inner.len()
}
}
fn top_field_text(decoded: &DecodedPgn, f: &crate::engine::FieldInfo) -> Option<String> {
decoded
.fields
.iter()
.find(|d| d.repeat_set == 0 && d.info.order == f.order)
.and_then(field_value_text)
}
fn iter_field_text(
decoded: &DecodedPgn,
repeat_set: u8,
iter: u32,
f: &crate::engine::FieldInfo,
) -> Option<String> {
decoded
.fields
.iter()
.find(|d| {
d.repeat_set == repeat_set && d.repeat_index == Some(iter) && d.info.order == f.order
})
.and_then(field_value_text)
}
fn collect_iter_indexes(decoded: &DecodedPgn, repeat_set: u8) -> Vec<u32> {
let mut seen: Vec<u32> = Vec::new();
for f in &decoded.fields {
if f.repeat_set != repeat_set {
continue;
}
let Some(i) = f.repeat_index else { continue };
if !seen.contains(&i) {
seen.push(i);
}
}
seen.sort_unstable();
seen
}
fn synthesize_iter_line(
decoded: &DecodedPgn,
repeat_set: u8,
iter: u32,
opts: &JsonOptions,
) -> String {
let mut sub = decoded.clone();
sub.fields.retain(|f| {
f.repeat_set == 0 || (f.repeat_set == repeat_set && f.repeat_index == Some(iter))
});
for f in &mut sub.fields {
if f.repeat_set == repeat_set {
f.repeat_index = Some(0);
}
}
let mut buf = String::with_capacity(256);
let _ = write_json(&mut buf, &sub, opts);
buf
}
fn field_value_text(field: &DecodedField) -> Option<String> {
match &field.value {
FieldValue::Lookup { value, .. } => Some(value.to_string()),
FieldValue::Integer(v) => Some(v.to_string()),
FieldValue::Number(v) | FieldValue::Float(v) => Some(format!("{v}")),
FieldValue::String(s) => Some(s.clone()),
FieldValue::Mmsi(v) => Some(v.to_string()),
FieldValue::Pgn { value, .. } => Some(value.to_string()),
FieldValue::Date(d) => Some(d.to_string()),
FieldValue::Time { raw, .. } => Some(raw.to_string()),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collect_iter_indexes_is_sorted_and_deduplicated() {
use crate::engine::{DecodedField, DecodedPgn, FieldValue};
let info = crate::engine::PgnDatabase::embedded(crate::engine::Units::Metric)
.first_pgn(130824)
.expect("PGN 130824 in schema");
let info_field = &info.fields[0];
let mk = |set: u8, idx: Option<u32>| DecodedField {
info: info_field,
value: FieldValue::Integer(0),
bit_offset: None,
bit_length: None,
repeat_index: idx,
repeat_set: set,
overrides: None,
};
let pgn = DecodedPgn {
timestamp: None,
prio: 0,
pgn: 130824,
src: 0,
dst: 0,
description: "",
id: "",
id_is_pinned: false,
data: Vec::new(),
fields: vec![
mk(1, Some(2)),
mk(0, None),
mk(1, Some(0)),
mk(1, Some(2)),
mk(2, Some(7)),
mk(1, Some(1)),
],
has_repeating_set: [true, true],
index_by_order: [i8::MIN; 32],
};
assert_eq!(collect_iter_indexes(&pgn, 1), vec![0, 1, 2]);
assert_eq!(collect_iter_indexes(&pgn, 2), vec![7]);
}
}