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