aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/tree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm/tree.rs')
-rw-r--r--src/swarm/tree.rs96
1 files changed, 70 insertions, 26 deletions
diff --git a/src/swarm/tree.rs b/src/swarm/tree.rs
index 8093c91..b21908e 100644
--- a/src/swarm/tree.rs
+++ b/src/swarm/tree.rs
@@ -105,6 +105,73 @@ pub(crate) async fn children(state: &AppState, writer: u64) -> Vec<String> {
.unwrap_or_default()
}
+/// Where `member` comes in the writer's tree (by `distance` from it over `graph`):
+/// its distance, then whether it reaches a member a hop closer (`reached(by, other)`:
+/// whether `by` reaches `other`, as known; the writer does), then its pair key. None if
+/// the graph doesn't have it.
+fn rank(
+ graph: &Graph,
+ distance: &HashMap<String, u32>,
+ reached: &dyn Fn(&str, &str) -> bool,
+ member: &str,
+) -> Option<(u32, bool, u64)> {
+ let d = *distance.get(member)?;
+ let closer =
+ d == 0
+ || graph.get(member).into_iter().flatten().any(|other| {
+ distance.get(other.as_str()) == Some(&(d - 1)) && reached(member, other)
+ });
+ Some((d, !closer, topology::pair_key(member)))
+}
+
+/// `member`'s parent in the writer's tree (`rank`): the neighbour it reaches that comes
+/// before it, the closest to the writer first, then one that reaches closer, then by
+/// pair score; None if none does (or it is the writer).
+fn parent<'g>(
+ graph: &'g Graph,
+ distance: &HashMap<String, u32>,
+ reached: &dyn Fn(&str, &str) -> bool,
+ member: &str,
+) -> Option<&'g str> {
+ let its_rank = rank(graph, distance, reached, member)?;
+ graph
+ .get(member)
+ .into_iter()
+ .flatten()
+ .map(String::as_str)
+ .filter(|candidate| {
+ rank(graph, distance, reached, candidate).is_some_and(|r| r < its_rank)
+ && reached(member, candidate)
+ })
+ .min_by_key(|candidate| {
+ let (d, far, _) = rank(graph, distance, reached, candidate).expect("ranked");
+ (d, far, topology::pair_score(candidate, member))
+ })
+}
+
+/// `writer`'s whole tree, as far as `reached` knows whom each member reaches (the
+/// dashboard's map, from the links members report): every member's parent, None for
+/// those without one (they get the writer's changes by announcement). None while the
+/// member list isn't known, or the writer isn't in it.
+#[cfg_attr(not(feature = "dashboard"), allow(dead_code))]
+pub(crate) fn whole(
+ state: &AppState,
+ writer: &str,
+ reached: &dyn Fn(&str, &str) -> bool,
+) -> Option<BTreeMap<String, Option<String>>> {
+ let (graph, distance) = distances(state, topology::pair_key(writer))?;
+ Some(
+ graph
+ .keys()
+ .filter(|member| distance.get(member.as_str()).is_some_and(|d| *d > 0))
+ .map(|member| {
+ let parent = parent(&graph, &distance, reached, member).map(str::to_owned);
+ (member.clone(), parent)
+ })
+ .collect(),
+ )
+}
+
/// What `writer`'s tree means for us: our children in it, and our neighbours it leaves
/// out (`uncovered`).
pub(crate) struct Plan {
@@ -137,40 +204,17 @@ pub(crate) async fn plan(state: &AppState, writer: u64) -> Option<Plan> {
.get(by)
.is_none_or(|keys| keys.contains(&topology::pair_key(member)))
};
- // Whether a member reaches one a hop closer to the writer (the writer: yes).
- let closer = |member: &str| {
- let d = distance[member];
- d == 0
- || graph.get(member).into_iter().flatten().any(|other| {
- distance.get(other.as_str()) == Some(&(d - 1)) && reached(member, other)
- })
- };
- let rank = |member: &str| {
- distance
- .get(member)
- .map(|d| (*d, !closer(member), topology::pair_key(member)))
- };
- let my_rank = rank(me)?;
+ let my_rank = rank(&graph, &distance, &reached, me)?;
let mut children = Vec::new();
let mut uncovered = Vec::new();
for member in graph.get(me).into_iter().flatten() {
- let Some(its_rank) = rank(member) else {
+ let Some(its_rank) = rank(&graph, &distance, &reached, member) else {
continue;
};
if distance[member.as_str()] == 0 {
continue;
}
- let parent = graph
- .get(member)
- .into_iter()
- .flatten()
- .filter(|candidate| rank(candidate).is_some_and(|r| r < its_rank))
- .filter(|candidate| reached(member, candidate))
- .min_by_key(|candidate| {
- let (d, far, _) = rank(candidate).expect("ranked");
- (d, far, topology::pair_score(candidate, member))
- });
- match parent {
+ match parent(&graph, &distance, &reached, member) {
None => uncovered.push(member.clone()),
Some(parent)
if parent == me && its_rank > my_rank && topology::reaches(state, member).await =>