OXYGENHUB
◆ v0.8.0 oxygenhub / crates / backdrop / src / swarm.rs
7.2 KiB · Rust · last changed Live arrivals on the sky 22 days ago source blame history raw
6246d1e5 Andy McCall ↶ 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
f993f792 Andy McCall ↶ 7
use crate::field::{knot, margins, Dot, Field, Named, Orbit, ZOOM};
6246d1e5 Andy McCall ↶ 8
use crate::scene::Scene;
f993f792 Andy McCall ↶ 9
use crate::sky::Colours;
10
use std::f32::consts::PI;
6246d1e5 Andy McCall ↶ 11
f993f792 Andy McCall ↶ 12
/// Dots for each image a node holds (its chunks, drawn as a sky's knot).
13
const PER_IMAGE: usize = 6;
6246d1e5 Andy McCall ↶ 14
/// Chunks streaming along each holder-to-fetcher path, their speed, and
15
/// the most streams drawn.
f993f792 Andy McCall ↶ 16
const CHUNKS: usize = 20;
6246d1e5 Andy McCall ↶ 17
const CHUNK_SPEED: f32 = 40.0;
18
const MAX_STREAMS: usize = 40;
19
20
/// One image: the nodes that hold it and those fetching it now.
21
pub struct Image {
22
    pub held: Vec<usize>,
23
    pub fetching: Vec<usize>,
24
}
25
26
struct Held {
27
    node: usize,
f993f792 Andy McCall ↶ 28
    orbit: Orbit,
6246d1e5 Andy McCall ↶ 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();
f993f792 Andy McCall ↶ 51
        let mut count = vec![0usize; names.len()];
52
        for img in &images {
6246d1e5 Andy McCall ↶ 53
            for &node in img.held.iter().filter(|&&n| n < names.len()) {
54
                count[node] += 1;
f993f792 Andy McCall ↶ 55
            }
56
        }
57
        for (node, name) in names.iter().enumerate() {
58
            for (k, orbit) in knot(name, count[node] * PER_IMAGE).into_iter().enumerate() {
59
                let mut d = Dot::new(0.0, 0.0, 2.0 * ZOOM, if k % PER_IMAGE == 0 { 5.0 } else { 0.0 });
6246d1e5 Andy McCall ↶ 60
                d.group = node as u32;
f993f792 Andy McCall ↶ 61
                d.order = k as u32;
62
                d.bright = 0.75;
6246d1e5 Andy McCall ↶ 63
                field.dots.push(d);
f993f792 Andy McCall ↶ 64
                held.push(Held { node, orbit });
6246d1e5 Andy McCall ↶ 65
            }
66
        }
67
        let mut streams = Vec::new();
68
        'images: for img in &images {
69
            for &to in img.fetching.iter().filter(|&&n| n < names.len()) {
70
                for &from in img.held.iter().filter(|&&n| n < names.len() && n != to) {
71
                    if streams.len() == MAX_STREAMS {
72
                        break 'images;
73
                    }
74
                    streams.push((from, to, field.dots.len()));
75
                    for _ in 0..CHUNKS {
f993f792 Andy McCall ↶ 76
                        let mut d = Dot::new(0.0, 0.0, 1.8 * ZOOM, 5.0);
6246d1e5 Andy McCall ↶ 77
                        d.bright = 0.0;
78
                        field.dots.push(d);
79
                    }
80
                }
81
            }
82
        }
83
        let mut s = Swarm { field, names, pos: Vec::new(), held, streams, w: 0.0, h: 0.0, t: 0.0 };
84
        s.layout(None);
85
        s
86
    }
87
88
    fn layout(&mut self, avoid: Option<(f32, f32)>) {
89
        let (w, h) = (self.w, self.h);
90
        let runners = self.names.len().saturating_sub(1);
91
        let (hub_x, run_x) = match margins(w, avoid) {
92
            Some((l, x1)) => (l * 0.5, x1 + (w - x1) * 0.5),
93
            None => (w * 0.1, w * 0.9),
94
        };
95
        self.pos = (0..self.names.len())
96
            .map(|i| match i {
97
                0 => (hub_x, h * 0.5),
98
                _ if runners <= 1 => (run_x, h * 0.5),
99
                i => (run_x, h * 0.2 + h * 0.6 * (i - 1) as f32 / (runners - 1) as f32),
100
            })
101
            .collect();
102
        self.field.named = self
103
            .names
104
            .iter()
105
            .zip(&self.pos)
106
            .enumerate()
107
            .map(|(i, (name, &(x, y)))| {
f993f792 Andy McCall ↶ 108
                let reach = self.held.iter().filter(|h| h.node == i).map(|h| h.orbit.radius).fold(12.0, f32::max);
6246d1e5 Andy McCall ↶ 109
                Named { name: name.clone(), x, y, reach }
110
            })
111
            .collect();
112
    }
113
114
    fn place(&mut self) {
115
        let n = self.names.len();
116
        for (i, &(x, y)) in self.pos.iter().enumerate() {
117
            self.field.dots[i].x = x;
118
            self.field.dots[i].y = y;
119
        }
120
        for (k, hd) in self.held.iter().enumerate() {
f993f792 Andy McCall ↶ 121
            let (x, y) = hd.orbit.at(self.pos[hd.node], self.t);
122
            self.field.dots[n + k].x = x;
123
            self.field.dots[n + k].y = y;
6246d1e5 Andy McCall ↶ 124
        }
125
        for &(from, to, first) in &self.streams {
126
            let (a, b) = (self.pos[from], self.pos[to]);
127
            let len = (b.0 - a.0).hypot(b.1 - a.1).max(1.0);
128
            // Peers' streams bow one way, the hub's the other, so they
129
            // read as separate sources.
130
            let bow = if from == 0 { -0.12 } else { 0.12 };
131
            let ctrl = ((a.0 + b.0) / 2.0, (a.1 + b.1) / 2.0 + len * bow);
132
            for k in 0..CHUNKS {
133
                let s = (k as f32 / CHUNKS as f32 + self.t * CHUNK_SPEED / len).fract();
134
                let u = 1.0 - s;
135
                let d = &mut self.field.dots[first + k];
136
                d.x = u * u * a.0 + 2.0 * u * s * ctrl.0 + s * s * b.0;
137
                d.y = u * u * a.1 + 2.0 * u * s * ctrl.1 + s * s * b.1;
138
                d.bright = (s * PI).sin();
139
            }
140
        }
141
    }
142
}
143
144
impl Scene for Swarm {
145
    fn resize(&mut self, w: f32, h: f32, avoid: Option<(f32, f32)>) {
146
        self.w = w;
147
        self.h = h;
148
        self.layout(avoid);
149
        self.place();
150
    }
151
    fn point(&mut self, at: Option<(f32, f32)>) {
152
        self.field.point(at);
153
    }
154
    fn step(&mut self, dt: f32) {
155
        if !self.field.still {
156
            self.t += dt.clamp(0.0, 0.1);
157
        }
158
        self.place();
159
        self.field.light(dt);
160
    }
161
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
162
        self.field.instances(c, out)
163
    }
164
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
165
        self.field.links(c, out)
166
    }
167
    fn label(&self) -> Option<(&str, f32, f32)> {
168
        self.field.label()
169
    }
170
    fn busy(&self) -> bool {
da8f7826 Andy McCall ↶ 171
        self.field.easing()
6246d1e5 Andy McCall ↶ 172
    }
173
    fn still(&self) -> bool {
174
        self.field.still
175
    }
176
    fn size(&self) -> (f32, f32) {
177
        (self.w, self.h)
178
    }
179
}
180
181
#[cfg(test)]
182
mod tests {
183
    use super::*;
184
185
    #[test]
186
    fn chunks_stream_to_a_fetching_runner_from_every_holder() {
187
        let names = vec!["hub".to_string(), "earth-1".to_string(), "plex-1".to_string()];
188
        let images = vec![Image { held: vec![0, 1], fetching: vec![2] }, Image { held: vec![0, 1, 2], fetching: vec![] }];
189
        let mut s = Swarm::new(names, images, false);
190
        s.resize(2560.0, 1270.0, Some((707.0, 1838.0)));
191
        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");
f993f792 Andy McCall ↶ 192
        assert_eq!(s.held.len(), 5 * PER_IMAGE, "dots for every image each node holds");
6246d1e5 Andy McCall ↶ 193
        assert_eq!(s.streams.iter().map(|&(a, b, _)| (a, b)).collect::<Vec<_>>(), vec![(0, 2), (1, 2)], "from the hub and the peer");
194
        let first = s.streams[0].2;
195
        let before = s.field.dots[first].x;
196
        s.step(0.1);
197
        assert_ne!(before, s.field.dots[first].x);
198
        s.point(Some(s.pos[1]));
199
        for _ in 0..30 {
200
            s.step(0.02);
201
        }
202
        assert_eq!(s.label().map(|l| l.0), Some("earth-1"));
203
    }
204
}