use std::time::{Duration, Instant};
use std::collections::BTreeMap;
use macrame::prelude::*;
use macrame::temporal::resolve_beliefs;
use macrame::BranchId;
const T0: &str = "2026-01-01T00:00:00.000000Z";
const FOREVER: &str = "9999-12-31T23:59:59.999999Z";
const NODES: usize = 400;
const ROUNDS: usize = 20;
const PASSES: usize = 3;
async fn best_of<F, Fut, T>(rounds: usize, mut f: F) -> Duration
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = T>,
{
let mut best = Duration::MAX;
for _ in 0..rounds {
let t = Instant::now();
let out = f().await;
best = best.min(t.elapsed());
std::hint::black_box(out);
}
best
}
fn id(n: &str) -> BranchId {
BranchId::new(n).unwrap()
}
async fn seeded(
dir: &std::path::Path,
depth: usize,
snapshots: Option<i64>,
) -> (Database, std::path::PathBuf) {
let name = format!(
"d{depth}_{}.db",
if snapshots.is_some() { "snap" } else { "raw" }
);
let cadence = snapshots.map(|n| {
SnapshotCadence::default()
.every_entries(n)
.poll_interval(std::time::Duration::from_millis(20))
});
let path = dir.join(name);
let db = Database::open_with_cadence(&path, cadence).await.unwrap();
let names: Vec<String> = (0..NODES).map(|i| format!("n{i:04}")).collect();
db.write_concepts(
names
.iter()
.map(|n| ConceptUpsert::new(n.as_str(), "t").valid_from(T0))
.collect(),
)
.await
.unwrap();
db.bulk_import(
names
.windows(2)
.map(|w| {
EdgeAssertion::new(w[0].as_str(), w[1].as_str(), "CITES")
.valid_from(T0)
.valid_to(FOREVER)
})
.collect(),
)
.await
.unwrap();
let mut parent = BranchId::main();
for i in 1..=depth {
let child = id(&format!("b{i}"));
db.fork(child.clone(), parent).await.unwrap();
parent = child;
db.assert_edge(
EdgeAssertion::new(names[i].as_str(), names[NODES - 1 - i].as_str(), "CITES")
.valid_from(T0)
.valid_to(FOREVER),
)
.await
.unwrap();
}
tokio::time::sleep(std::time::Duration::from_millis(250)).await;
(db, path)
}
async fn fold_per_bound(db: &Database, ts: &str, branch: &str) -> Vec<EdgeBelief> {
let ancestry = db.ancestry(branch).await.unwrap();
let mut by_bound: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
for a in &ancestry {
let bound = match a.cutoff.as_deref() {
Some(c) if c < ts => c,
_ => ts,
};
by_bound.entry(bound).or_default().push(&a.branch_id);
}
let mut visible: Vec<EdgeBelief> = Vec::new();
for (bound, names) in &by_bound {
let state = db.reconstruct(bound).await.unwrap();
visible.extend(
state
.edges
.into_iter()
.filter(|e| names.iter().any(|n| *n == e.branch_id)),
);
}
resolve_beliefs(&visible, &ancestry)
}
fn snapshot_count(db_path: &std::path::Path) -> usize {
let mut dir = db_path.to_path_buf();
let stem = db_path.file_stem().and_then(|s| s.to_str()).unwrap_or("x");
dir.set_file_name(format!("{stem}_snapshots"));
std::fs::read_dir(dir)
.map(|d| d.flatten().count())
.unwrap_or(0)
}
#[tokio::main]
async fn main() {
let dir = std::env::temp_dir().join(format!("macrame_recon_probe_{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
println!(
"reconstruct_on_probe — crate {}\n{NODES} concepts, best of {ROUNDS}, {PASSES} \
alternating passes\n",
env!("CARGO_PKG_VERSION")
);
for snapshots in [None, Some(200_i64)] {
println!(
"snapshots: {}",
match snapshots {
Some(n) => format!("every {n} entries"),
None => "off".to_string(),
}
);
for depth in [1_usize, 8] {
let (db, path) = seeded(&dir, depth, snapshots).await;
let leaf = format!("b{depth}");
let flat = db.reconstruct_on(FOREVER, &leaf).await.unwrap().edges;
let per_bound = fold_per_bound(&db, FOREVER, &leaf).await;
assert_eq!(
flat, per_bound,
"depth {depth}: the two shapes disagree, so the timings below are not comparing one thing"
);
let files = snapshot_count(&path);
match snapshots {
Some(_) => assert!(files > 0, "asked for snapshots and got none written"),
None => assert_eq!(files, 0, "snapshots off, but files appeared"),
}
let mut whole = Duration::MAX;
let mut one = Duration::MAX;
let mut multi = Duration::MAX;
for _ in 0..PASSES {
whole = whole.min(
best_of(ROUNDS, || async { db.reconstruct(FOREVER).await.unwrap() }).await,
);
one = one.min(
best_of(ROUNDS, || async {
db.reconstruct_on(FOREVER, &leaf).await.unwrap()
})
.await,
);
multi = multi.min(
best_of(ROUNDS, || async {
fold_per_bound(&db, FOREVER, &leaf).await
})
.await,
);
}
let us = |d: Duration| d.as_secs_f64() * 1e6;
println!(
" depth {depth} ({files} snapshot file(s), {} edges)\n \x20 reconstruct (whole ledger) {:>9.1} µs\n \x20 flat bounded fold {:>9.1} µs ({:.2}x)\n \x20 fold per bound {:>9.1} µs ({:.2}x)",
flat.len(),
us(whole),
us(one),
us(one) / us(whole),
us(multi),
us(multi) / us(whole),
);
db.close().await.unwrap();
}
println!();
}
let _ = std::fs::remove_dir_all(&dir);
}