use std::collections::BTreeMap;
use serde::Serialize;
use crate::fold::{replay, FunnelFold, LineageFold, ManifestFold};
use crate::log::LogRecord;
use crate::manifest::AttributionManifest;
use crate::slug::Stage;
use crate::token::Token;
#[derive(Debug, Default, Serialize)]
pub struct WorldState {
pub manifests: BTreeMap<Token, AttributionManifest>,
pub funnels: BTreeMap<Token, BTreeMap<Stage, u64>>,
pub lineage: BTreeMap<Token, Vec<Token>>,
}
#[must_use]
pub fn reconstruct(records: impl IntoIterator<Item = LogRecord>) -> WorldState {
let mut per_token: BTreeMap<Token, BTreeMap<(u32, u8), LogRecord>> = BTreeMap::new();
for rec in records {
let kind = match &rec {
LogRecord::Minted { .. } => 0u8,
LogRecord::Mutation { .. } => 1,
LogRecord::Event(_) => 2,
};
per_token
.entry(rec.token())
.or_default()
.entry((rec.seq().0, kind))
.or_insert(rec);
}
let ordered = per_token.into_values().flat_map(BTreeMap::into_values);
let (manifests, (funnels, lineage)) = replay(
ordered,
(
ManifestFold::default(),
(FunnelFold::default(), LineageFold::default()),
),
);
WorldState {
manifests: manifests.manifests,
funnels: funnels.per_token,
lineage: lineage.children,
}
}
#[must_use]
pub fn apply_suffix(
snapshot: WorldState,
suffix: impl IntoIterator<Item = LogRecord>,
) -> WorldState {
let mut manifests = ManifestFold {
manifests: snapshot.manifests,
};
let mut funnels = FunnelFold {
per_token: snapshot.funnels,
};
let mut lineage = LineageFold {
children: snapshot.lineage,
};
for rec in suffix {
use crate::fold::Fold as _;
manifests.apply(&rec);
funnels.apply(&rec);
lineage.apply(&rec);
}
WorldState {
manifests: manifests.manifests,
funnels: funnels.per_token,
lineage: lineage.children,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{ActorClass, Event, Seq};
use crate::{CanonicalUrl, Channel, MintOptions, MintSpec, ResolverContext, Sharer, Timestamp};
#[test]
fn seq_zero_event_never_conflates_with_minted() {
let mut entropy = |buf: &mut [u8]| {
buf.fill(9);
Ok(())
};
let m = crate::mint(
MintSpec::new(
CanonicalUrl::new("ws://a/b").unwrap(),
Sharer::new("lead").unwrap(),
Channel::subagent_general(),
),
&MintOptions::default(),
&mut entropy,
Timestamp::from_unix_ms(1),
)
.unwrap();
let token = m.token;
let records = vec![
LogRecord::Event(Event {
token,
stage: crate::Stage::impression(),
actor: ActorClass::from_context(&ResolverContext::human()),
at: Timestamp::from_unix_ms(2),
seq: Seq(0), variant: None,
regions: None,
}),
LogRecord::Minted {
manifest: Box::new(m),
},
];
let world = reconstruct(records);
assert!(world.manifests.contains_key(&token));
assert_eq!(world.funnels[&token][&crate::Stage::impression()], 1);
}
}