diff options
Diffstat (limited to 'src/swarm/tree.rs')
| -rw-r--r-- | src/swarm/tree.rs | 96 |
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 => |
