OXYGENHUB
# 6246d1e51f4f oxygenhub / crates / backdrop / src / swarm.rs
7.4 KiB · Rust · last changed feat(web): the mark, ripples, the swarm and adrift (closes #167) 23 days ago source blame history raw
1
//! The swarm (docs/backgrounds.md §2): Admin › Runners › Images. The hub
2
//! and the runners as nodes, each ringed by a dot for every image it
3
//! holds; while a runner fetches an image, chunks (small squares) stream
4
//! to it from every holder, the hub and its peers alike, as Jetstream's
5
//! swarm fetches them. The data is the page's own snapshot.
6
7
use crate::field::{margins, unit, Dot, Field, Named};
8
use crate::scene::Scene;
9
use crate::sky::{hash, Colours, MAX_SPEED};
10
use std::f32::consts::{PI, TAU};
11
12
/// Chunks streaming along each holder-to-fetcher path, their speed, and
13
/// the most streams drawn.
14
const CHUNKS: usize = 8;
15
const CHUNK_SPEED: f32 = 40.0;
16
const MAX_STREAMS: usize = 40;
17
18
/// One image: the nodes that hold it and those fetching it now.
19
pub struct Image {
20
    pub held: Vec<usize>,
21
    pub fetching: Vec<usize>,
22
}
23
24
struct Held {
25
    node: usize,
26
    radius: f32,
27
    angle: f32,
28
    speed: f32,
29
}
30
31
pub struct Swarm {
32
    field: Field,
33
    /// The hub first, then the runners.
34
    names: Vec<String>,
35
    pos: Vec<(f32, f32)>,
36
    held: Vec<Held>,
37
    /// Streams: from, to, and their chunks' first index.
38
    streams: Vec<(usize, usize, usize)>,
39
    w: f32,
40
    h: f32,
41
    t: f32,
42
}
43
44
impl Swarm {
45
    pub fn new(names: Vec<String>, images: Vec<Image>, still: bool) -> Swarm {
46
        let mut field = Field::new(still);
47
        for i in 0..names.len() {
48
            field.dots.push(Dot::new(0.0, 0.0, if i == 0 { 10.0 } else { 8.0 }, 4.0));
49
        }
50
        let mut held = Vec::new();
51
        let mut count = vec![0u32; names.len()];
52
        for (n, img) in images.iter().enumerate() {
53
            for &node in img.held.iter().filter(|&&n| n < names.len()) {
54
                let k = count[node];
55
                count[node] += 1;
56
                let h = hash(&format!("{}#{n}", names[node]));
57
                let radius = (16.0 + (k % 5) as f32 * 7.0 + unit(h, 0) * 4.0) * 1.4;
58
                held.push(Held {
59
                    node,
60
                    radius,
61
                    angle: unit(h, 16) * TAU,
62
                    speed: (MAX_SPEED / radius).min(0.2) * (0.5 + 0.5 * unit(h, 32)),
63
                });
64
                let mut d = Dot::new(0.0, 0.0, 2.6, 0.0);
65
                d.group = node as u32;
66
                d.order = k;
67
                d.bright = 0.7;
68
                field.dots.push(d);
69
            }
70
        }
71
        let mut streams = Vec::new();
72
        'images: for img in &images {
73
            for &to in img.fetching.iter().filter(|&&n| n < names.len()) {
74
                for &from in img.held.iter().filter(|&&n| n < names.len() && n != to) {
75
                    if streams.len() == MAX_STREAMS {
76
                        break 'images;
77
                    }
78
                    streams.push((from, to, field.dots.len()));
79
                    for _ in 0..CHUNKS {
80
                        let mut d = Dot::new(0.0, 0.0, 2.2, 5.0);
81
                        d.bright = 0.0;
82
                        field.dots.push(d);
83
                    }
84
                }
85
            }
86
        }
87
        let mut s = Swarm { field, names, pos: Vec::new(), held, streams, w: 0.0, h: 0.0, t: 0.0 };
88
        s.layout(None);
89
        s
90
    }
91
92
    fn layout(&mut self, avoid: Option<(f32, f32)>) {
93
        let (w, h) = (self.w, self.h);
94
        let runners = self.names.len().saturating_sub(1);
95
        let (hub_x, run_x) = match margins(w, avoid) {
96
            Some((l, x1)) => (l * 0.5, x1 + (w - x1) * 0.5),
97
            None => (w * 0.1, w * 0.9),
98
        };
99
        self.pos = (0..self.names.len())
100
            .map(|i| match i {
101
                0 => (hub_x, h * 0.5),
102
                _ if runners <= 1 => (run_x, h * 0.5),
103
                i => (run_x, h * 0.2 + h * 0.6 * (i - 1) as f32 / (runners - 1) as f32),
104
            })
105
            .collect();
106
        self.field.named = self
107
            .names
108
            .iter()
109
            .zip(&self.pos)
110
            .enumerate()
111
            .map(|(i, (name, &(x, y)))| {
112
                let reach = self.held.iter().filter(|h| h.node == i).map(|h| h.radius).fold(12.0, f32::max);
113
                Named { name: name.clone(), x, y, reach }
114
            })
115
            .collect();
116
    }
117
118
    fn place(&mut self) {
119
        let n = self.names.len();
120
        for (i, &(x, y)) in self.pos.iter().enumerate() {
121
            self.field.dots[i].x = x;
122
            self.field.dots[i].y = y;
123
        }
124
        for (k, hd) in self.held.iter().enumerate() {
125
            let (x, y) = self.pos[hd.node];
126
            let a = hd.angle + hd.speed * self.t;
127
            self.field.dots[n + k].x = x + a.cos() * hd.radius;
128
            self.field.dots[n + k].y = y + a.sin() * hd.radius;
129
        }
130
        for &(from, to, first) in &self.streams {
131
            let (a, b) = (self.pos[from], self.pos[to]);
132
            let len = (b.0 - a.0).hypot(b.1 - a.1).max(1.0);
133
            // Peers' streams bow one way, the hub's the other, so they
134
            // read as separate sources.
135
            let bow = if from == 0 { -0.12 } else { 0.12 };
136
            let ctrl = ((a.0 + b.0) / 2.0, (a.1 + b.1) / 2.0 + len * bow);
137
            for k in 0..CHUNKS {
138
                let s = (k as f32 / CHUNKS as f32 + self.t * CHUNK_SPEED / len).fract();
139
                let u = 1.0 - s;
140
                let d = &mut self.field.dots[first + k];
141
                d.x = u * u * a.0 + 2.0 * u * s * ctrl.0 + s * s * b.0;
142
                d.y = u * u * a.1 + 2.0 * u * s * ctrl.1 + s * s * b.1;
143
                d.bright = (s * PI).sin();
144
            }
145
        }
146
    }
147
}
148
149
impl Scene for Swarm {
150
    fn resize(&mut self, w: f32, h: f32, avoid: Option<(f32, f32)>) {
151
        self.w = w;
152
        self.h = h;
153
        self.layout(avoid);
154
        self.place();
155
    }
156
    fn point(&mut self, at: Option<(f32, f32)>) {
157
        self.field.point(at);
158
    }
159
    fn step(&mut self, dt: f32) {
160
        if !self.field.still {
161
            self.t += dt.clamp(0.0, 0.1);
162
        }
163
        self.place();
164
        self.field.light(dt);
165
    }
166
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
167
        self.field.instances(c, out)
168
    }
169
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
170
        self.field.links(c, out)
171
    }
172
    fn label(&self) -> Option<(&str, f32, f32)> {
173
        self.field.label()
174
    }
175
    fn busy(&self) -> bool {
176
        !self.field.still || self.field.easing()
177
    }
178
    fn still(&self) -> bool {
179
        self.field.still
180
    }
181
    fn size(&self) -> (f32, f32) {
182
        (self.w, self.h)
183
    }
184
}
185
186
#[cfg(test)]
187
mod tests {
188
    use super::*;
189
190
    #[test]
191
    fn chunks_stream_to_a_fetching_runner_from_every_holder() {
192
        let names = vec!["hub".to_string(), "earth-1".to_string(), "plex-1".to_string()];
193
        let images = vec![Image { held: vec![0, 1], fetching: vec![2] }, Image { held: vec![0, 1, 2], fetching: vec![] }];
194
        let mut s = Swarm::new(names, images, false);
195
        s.resize(2560.0, 1270.0, Some((707.0, 1838.0)));
196
        assert!(s.pos[0].0 < 707.0 && s.pos[1].0 > 1838.0 && s.pos[2].0 > 1838.0, "the hub on one side, runners on the other");
197
        assert_eq!(s.held.len(), 5, "a dot for every image each node holds");
198
        assert_eq!(s.streams.iter().map(|&(a, b, _)| (a, b)).collect::<Vec<_>>(), vec![(0, 2), (1, 2)], "from the hub and the peer");
199
        let first = s.streams[0].2;
200
        let before = s.field.dots[first].x;
201
        s.step(0.1);
202
        assert_ne!(before, s.field.dots[first].x);
203
        s.point(Some(s.pos[1]));
204
        for _ in 0..30 {
205
            s.step(0.02);
206
        }
207
        assert_eq!(s.label().map(|l| l.0), Some("earth-1"));
208
    }
209
}