clawft_kernel/
mesh_swarm_picker.rs1use std::collections::{BTreeMap, HashMap};
17
18use dashmap::DashMap;
19
20use clawft_types::placement::TrustTier;
21
22use crate::mesh_artifact_types::Bitfield;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Pick {
27 Piece(u32),
29 Wait,
32 Nothing,
34}
35
36#[derive(Debug, Clone)]
38pub struct PiecePicker {
39 have: Bitfield,
40 in_flight: BTreeMap<u32, String>,
41 peers: HashMap<String, Bitfield>,
42}
43
44impl PiecePicker {
45 pub fn new(have: Bitfield) -> Self {
47 Self {
48 have,
49 in_flight: BTreeMap::new(),
50 peers: HashMap::new(),
51 }
52 }
53
54 pub fn add_peer(&mut self, peer: &str, has: Bitfield) {
56 self.peers.insert(peer.to_string(), has);
57 }
58
59 pub fn remove_peer(&mut self, peer: &str) {
62 self.peers.remove(peer);
63 self.in_flight.retain(|_, p| p != peer);
64 }
65
66 pub fn peer_lacks(&mut self, peer: &str, i: u32) {
68 if let Some(b) = self.peers.get_mut(peer) {
69 b.set(i, false);
70 }
71 if self.in_flight.get(&i).is_some_and(|p| p == peer) {
72 self.in_flight.remove(&i);
73 }
74 }
75
76 pub fn availability(&self, i: u32) -> usize {
78 self.peers.values().filter(|b| b.get(i)).count()
79 }
80
81 pub fn pick(&mut self, peer: &str) -> Pick {
83 let Some(has) = self.peers.get(peer) else {
84 return Pick::Nothing;
85 };
86 let mut best: Option<(usize, u32)> = None;
87 let mut blocked = false;
88 for i in self.have.missing() {
89 if !has.get(i) {
90 continue;
91 }
92 if self.in_flight.contains_key(&i) {
93 blocked = true;
94 continue;
95 }
96 let key = (self.availability(i), i);
97 if best.is_none_or(|b| key < b) {
98 best = Some(key);
99 }
100 }
101 match best {
102 Some((_, i)) => {
103 self.in_flight.insert(i, peer.to_string());
104 Pick::Piece(i)
105 }
106 None if blocked => Pick::Wait,
107 None => Pick::Nothing,
108 }
109 }
110
111 pub fn complete(&mut self, i: u32) {
113 self.have.set(i, true);
114 self.in_flight.remove(&i);
115 }
116
117 pub fn release(&mut self, i: u32) {
119 self.in_flight.remove(&i);
120 }
121
122 pub fn is_complete(&self) -> bool {
124 self.have.is_complete()
125 }
126
127 pub fn missing(&self) -> u32 {
129 self.have.len() - self.have.count()
130 }
131
132 pub fn unavailable(&self) -> usize {
134 self.have.missing().filter(|&i| self.availability(i) == 0).count()
135 }
136}
137
138#[derive(Debug, Clone, PartialEq)]
140pub struct PeerCandidate {
141 pub peer_id: String,
143 pub lan_id: Option<String>,
146 pub tier: TrustTier,
148}
149
150impl PeerCandidate {
151 pub fn new(peer_id: impl Into<String>) -> Self {
153 Self {
154 peer_id: peer_id.into(),
155 lan_id: None,
156 tier: TrustTier::Discovered,
157 }
158 }
159
160 pub fn with_tier(mut self, tier: TrustTier) -> Self {
162 self.tier = tier;
163 self
164 }
165
166 pub fn on_lan(mut self, lan: impl Into<String>) -> Self {
168 self.lan_id = Some(lan.into());
169 self
170 }
171}
172
173pub const ASSUMED_LINK_BPS: f64 = 10.0 * 1024.0 * 1024.0;
176
177pub fn order_peers(
180 candidates: Vec<PeerCandidate>,
181 local_lan: Option<&str>,
182 links: &LinkStats,
183 is_banned: &dyn Fn(&str) -> bool,
184) -> Vec<PeerCandidate> {
185 let mut v: Vec<(bool, f64, PeerCandidate)> = candidates
186 .into_iter()
187 .filter(|c| !is_banned(&c.peer_id))
188 .map(|c| {
189 let same_lan = matches!((local_lan, c.lan_id.as_deref()), (Some(a), Some(b)) if a == b);
190 let bps = links.bytes_per_sec(&c.peer_id).unwrap_or(ASSUMED_LINK_BPS);
191 (same_lan, bps, c)
192 })
193 .collect();
194 v.sort_by(|a, b| {
195 b.2.tier
196 .cmp(&a.2.tier)
197 .then(b.0.cmp(&a.0))
198 .then(b.1.total_cmp(&a.1))
199 .then(a.2.peer_id.cmp(&b.2.peer_id))
200 });
201 v.into_iter().map(|(_, _, c)| c).collect()
202}
203
204#[derive(Debug, Default)]
206pub struct LinkStats {
207 peers: DashMap<String, f64>,
208}
209
210const EWMA_ALPHA: f64 = 0.3;
212
213impl LinkStats {
214 pub fn record(&self, peer: &str, bytes: u64, secs: f64) {
216 if bytes == 0 || secs <= 0.0 {
217 return;
218 }
219 let sample = bytes as f64 / secs;
220 self.peers
221 .entry(peer.to_string())
222 .and_modify(|v| *v = EWMA_ALPHA * sample + (1.0 - EWMA_ALPHA) * *v)
223 .or_insert(sample);
224 }
225
226 pub fn bytes_per_sec(&self, peer: &str) -> Option<f64> {
228 self.peers.get(peer).map(|v| *v)
229 }
230}
231
232#[cfg(test)]
233#[path = "mesh_swarm_picker_tests.rs"]
234mod tests;